David Olivier's Data Matrix improvements

git-svn-id: https://zxing.googlecode.com/svn/trunk@1573 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2010-09-03 07:50:47 +00:00
parent 14a03f40b1
commit 47d2f3c798
5 changed files with 430 additions and 44 deletions

View file

@ -15,6 +15,7 @@ Daniel Switkin (Google)
Dave MacLachlan (Google)
David Phillip Oster (Google)
David Albert (Bug Labs)
David Olivier
Diego Pierotto
Eduardo Castillejo (University of Deusto)
Eric Kobrin (Velocitude)

View file

@ -0,0 +1,317 @@
/*
* Copyright 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.common.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
/**
* <p>
* Detects a candidate barcode-like rectangular region within an image. It
* starts around the center of the image, increases the size of the candidate
* region until it finds a white rectangular region. By keeping track of the
* last black points it encountered, it determines the corners of the barcode.
* </p>
*
* @author David Olivier
*/
public final class WhiteRectangleDetector {
private static final int INIT_SIZE = 40;
private static final int MIN_SIZE = 20;
private final BitMatrix image;
private final int height;
private final int width;
public WhiteRectangleDetector(BitMatrix image) {
this.image = image;
height = image.getHeight();
width = image.getWidth();
}
/**
* <p>
* Detects a candidate barcode-like rectangular region within an image. It
* starts around the center of the image, increases the size of the candidate
* region until it finds a white rectangular region.
* </p>
*
* @return {@link ResultPoint}[] describing the corners of the rectangular
* region. The first and last points are opposed on the diagonal, as
* are the second and third. The first point will be the topmost
* point and the last, the bottommost. The second point will be
* leftmost and the third, the rightmost
* @throws NotFoundException if no Data Matrix Code can be found
*/
public ResultPoint[] detect() throws NotFoundException {
int left = (width - INIT_SIZE) / 2;
int right = (width + INIT_SIZE) / 2;
int up = (height - INIT_SIZE) / 2;
int down = (height + INIT_SIZE) / 2;
boolean sizeExceeded = false;
boolean aBlackPointFoundOnBorder = true;
boolean atLeastOneBlackPointFoundOnBorder = false;
while (aBlackPointFoundOnBorder) {
aBlackPointFoundOnBorder = false;
// .....
// . |
// .....
boolean rightBorderNotWhite = true;
while (rightBorderNotWhite && right < width) {
rightBorderNotWhite = containsBlackPoint(up, down, right, false);
if (rightBorderNotWhite) {
right++;
aBlackPointFoundOnBorder = true;
}
}
// .....
// . .
// .___.
boolean bottomBorderNotWhite = true;
while (bottomBorderNotWhite && down < height) {
bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
if (bottomBorderNotWhite) {
down++;
aBlackPointFoundOnBorder = true;
}
}
// .....
// | .
// .....
boolean leftBorderNotWhite = true;
while (leftBorderNotWhite && left >= 0) {
leftBorderNotWhite = containsBlackPoint(up, down, left, false);
if (leftBorderNotWhite) {
left--;
aBlackPointFoundOnBorder = true;
}
}
// .___.
// . .
// .....
boolean topBorderNotWhite = true;
while (topBorderNotWhite && up >= 0) {
topBorderNotWhite = containsBlackPoint(left, right, up, true);
if (topBorderNotWhite) {
up--;
aBlackPointFoundOnBorder = true;
}
}
if (right >= width || down >= height || up < 0 || left < 0) {
sizeExceeded = true;
break;
}
if (aBlackPointFoundOnBorder) {
atLeastOneBlackPointFoundOnBorder = true;
}
}
if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {
// t t
//z x
// x OR z
// y y
ResultPoint x = getBlackPoint(up, down, right - 1, false);
ResultPoint y = getBlackPoint(left, right, down - 1, true);
ResultPoint z = getBlackPoint(up, down, left + 1, false);
ResultPoint t = getBlackPoint(left, right, up + 1, true);
// if the rectangle if perfectly horizontal (mostly in test cases)
// then we end up with:
// zt x
//
// y
if (distance(z, t) < MIN_SIZE) {
ResultPoint u = getBlackPointInverted(up, down, right - 1, false);
t = x;
x = u;
}
return centerEdges(y, z, x, t);
} else {
throw NotFoundException.getNotFoundInstance();
}
}
/**
* recenters the points of a constant distance towards the center
*
* @param y bottom most point
* @param z left most point
* @param x right most point
* @param t top most point
* @return {@link ResultPoint}[] describing the corners of the rectangular
* region. The first and last points are opposed on the diagonal, as
* are the second and third. The first point will be the topmost
* point and the last, the bottommost. The second point will be
* leftmost and the third, the rightmost
*/
private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z,
ResultPoint x, ResultPoint t) {
//
// t t
// z x
// x OR z
// y y
//
float yi = y.getX(), yj = y.getY(), zi = z.getX(), zj = z.getY(), xi = x
.getX(), xj = x.getY(), ti = t.getX(), tj = t.getY();
final int corr = 1;
if (yi < width / 2) {
return new ResultPoint[]{new ResultPoint(ti - corr, tj + corr),
new ResultPoint(zi + corr, zj + corr),
new ResultPoint(xi - corr, xj - corr),
new ResultPoint(yi + corr, yj - corr)};
} else {
return new ResultPoint[]{new ResultPoint(ti + corr, tj + corr),
new ResultPoint(zi + corr, zj - corr),
new ResultPoint(xi - corr, xj + corr),
new ResultPoint(yi - corr, yj - corr)};
}
}
// L1 distance (metropolitan distance)
private static float distance(ResultPoint a, ResultPoint b) {
return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY());
}
/**
* Gets the coordinate of an extreme black point of a segment
*
* @param a min value of the scanned coordinate
* @param b max value of the scanned coordinate
* @param fixed value of fixed coordinate
* @param horizontal set to true if scan must be horizontal, false if vertical
* @return {@link ResultPoint} describing the black point. If scan is horizontal,
* the returned point is the first encountered if it is on the left of the image,
* else the last one. If scan is vertical, the returned point is the first encountered
* if it is on the top of the image, else the last one.
* {@link ResultPoint} is null if not black point has been found
*/
private ResultPoint getBlackPoint(int a, int b, int fixed, boolean horizontal) {
ResultPoint last = null;
if (horizontal) {
for (int x = a; x < b; x++) {
if (image.get(x, fixed)) {
if (x < width / 2) {
return new ResultPoint(x, fixed);
} else {
while (x < width && image.get(x, fixed)) {
x++;
}
x--;
last = new ResultPoint(x, fixed);
}
}
}
} else {
for (int y = a; y < b; y++) {
if (image.get(fixed, y)) {
if (y < height / 2) {
return new ResultPoint(fixed, y);
} else {
while (y < height && image.get(fixed, y)) {
y++;
}
y--;
last = new ResultPoint(fixed, y);
}
}
}
}
return last;
}
/**
* Same as getBlackPoint, but returned point is the last one found.
*
* @param a min value of the scanned coordinate
* @param b max value of the scanned coordinate
* @param fixed value of fixed coordinate
* @param horizontal set to true if scan must be horizontal, false if vertical
* @return {@link ResultPoint} describing the black point.
*/
private ResultPoint getBlackPointInverted(int a, int b, int fixed, boolean horizontal) {
if (horizontal) {
for (int x = b + 1; x >= a; x--) {
if (image.get(x, fixed)) {
return new ResultPoint(x, fixed);
}
}
} else {
for (int y = b + 1; y >= a; y--) {
if (image.get(fixed, y)) {
return new ResultPoint(fixed, y);
}
}
}
return null;
}
/**
* Determines whether a segment contains a black point
*
* @param a min value of the scanned coordinate
* @param b max value of the scanned coordinate
* @param fixed value of fixed coordinate
* @param horizontal set to true if scan must be horizontal, false if vertical
* @return true if a black point has been found, else false.
*/
private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) {
if (horizontal) {
for (int x = a; x < b; x++) {
if (image.get(x, fixed)) {
return true;
}
}
} else {
for (int y = a; y < b; y++) {
if (image.get(fixed, y)) {
return true;
}
}
}
return false;
}
}

View file

@ -23,7 +23,7 @@ import com.google.zxing.common.Collections;
import com.google.zxing.common.Comparator;
import com.google.zxing.common.DetectorResult;
import com.google.zxing.common.GridSampler;
import com.google.zxing.common.detector.MonochromeRectangleDetector;
import com.google.zxing.common.detector.WhiteRectangleDetector;
import java.util.Enumeration;
import java.util.Hashtable;
@ -37,7 +37,7 @@ import java.util.Vector;
*/
public final class Detector {
//private static final int MAX_MODULES = 32;
private static final int MIN_GIVEUP_THRESHOLD = 3;
// Trick to avoid creating new Integer objects below -- a sort of crude copy of
// the Integer.valueOf(int) optimization added in Java 5, not in J2ME
@ -46,17 +46,17 @@ public final class Detector {
// No, can't use valueOf()
private final BitMatrix image;
private final MonochromeRectangleDetector rectangleDetector;
private final WhiteRectangleDetector rectangleDetector;
public Detector(BitMatrix image) {
this.image = image;
rectangleDetector = new MonochromeRectangleDetector(image);
rectangleDetector = new WhiteRectangleDetector(image);
}
/**
* <p>Detects a Data Matrix Code in an image.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code
* @throws NotFoundException if no Data Matrix Code can be found
*/
public DetectorResult detect() throws NotFoundException {
@ -82,6 +82,12 @@ public final class Detector {
ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions.elementAt(0);
ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions.elementAt(1);
//give up if there is no chance we'll decode something...
if (lSideOne.transitions > MIN_GIVEUP_THRESHOLD ||
lSideTwo.transitions > MIN_GIVEUP_THRESHOLD) {
throw NotFoundException.getNotFoundInstance();
}
// Figure out which point is their intersection by tallying up the number of times we see the
// endpoints in the four endpoints. One will show up twice.
Hashtable pointCount = new Hashtable();
@ -142,10 +148,8 @@ public final class Detector {
// The top right point is actually the corner of a module, which is one of the two black modules
// adjacent to the white module at the top right. Tracing to that corner from either the top left
// or bottom right should work here. The number of transitions could be higher than it should be
// due to noise. So we try both and take the min.
int dimension = Math.min(transitionsBetween(topLeft, topRight).getTransitions(),
// or bottom right should work here.
int dimension = Math.max(transitionsBetween(topLeft, topRight).getTransitions(),
transitionsBetween(bottomRight, topRight).getTransitions());
if ((dimension & 0x01) == 1) {
// it can't be odd, so, round... up?
@ -153,8 +157,79 @@ public final class Detector {
}
dimension += 2;
BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, dimension);
return new DetectorResult(bits, new ResultPoint[] {pointA, pointB, pointC, pointD});
//correct top right point to match the white module
ResultPoint correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
//We redetermine the dimension using the corrected top right point
int dimension2 = Math.min(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
transitionsBetween(bottomRight, correctedTopRight).getTransitions());
dimension2++;
if ((dimension2 & 0x01) == 1) {
dimension2++;
}
BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimension2);
return new DetectorResult(bits, new ResultPoint[]{topLeft, bottomLeft, bottomRight, correctedTopRight});
}
/**
* Calculates the position of the white top right module using the output of the rectangle detector
*/
private ResultPoint correctTopRight(ResultPoint bottomLeft,
ResultPoint bottomRight,
ResultPoint topLeft,
ResultPoint topRight,
int dimension) {
float corr = distance(bottomLeft, bottomRight) / (float) dimension;
float corrx = 0.0f;
float corry = 0.0f;
int norm = distance(topLeft, topRight);
float cos = (topRight.getX() - topLeft.getX()) / norm;
float sin = -(topRight.getY() - topLeft.getY()) / norm;
if (cos > 0.0f && sin > 0.0f) {
if (cos > sin) {
corrx = corr * cos;
corry = -corr * sin;
} else {
corrx = -corr * sin;
corry = -corr * cos;
}
} else if (cos > 0.0f && sin < 0.0f) {
if (cos > -sin) {
corrx = -corr * sin;
corry = -corr * cos;
} else {
corrx = corr * cos;
corry = -corr * sin;
}
} else if (cos < 0.0f && sin < 0.0f) {
if (-cos > -sin) {
corrx = corr * cos;
corry = -corr * sin;
} else {
corrx = -corr * sin;
corry = -corr * cos;
}
} else if (cos < 0.0f && sin > 0.0f) {
if (-cos > sin) {
corrx = -corr * sin;
corry = -corr * cos;
} else {
corrx = corr * cos;
corry = -corr * sin;
}
}
return new ResultPoint(topRight.getX() + corrx, topRight.getY() + corry);
}
// L2 distance
private static int distance(ResultPoint a, ResultPoint b) {
return (int) Math.round(Math.sqrt((a.getX() - b.getX())
* (a.getX() - b.getX()) + (a.getY() - b.getY())
* (a.getY() - b.getY())));
}
/**
@ -169,36 +244,29 @@ public final class Detector {
ResultPoint topLeft,
ResultPoint bottomLeft,
ResultPoint bottomRight,
ResultPoint topRight,
int dimension) throws NotFoundException {
// We make up the top right point for now, based on the others.
// TODO: we actually found a fourth corner above and figured out which of two modules
// it was the corner of. We could use that here and adjust for perspective distortion.
float topRightX = (bottomRight.getX() - bottomLeft.getX()) + topLeft.getX();
float topRightY = (bottomRight.getY() - bottomLeft.getY()) + topLeft.getY();
// Note that unlike in the QR Code sampler, we didn't find the center of modules, but the
// very corners. So there is no 0.5f here; 0.0f is right.
GridSampler sampler = GridSampler.getInstance();
return sampler.sampleGrid(
image,
dimension,
0.0f,
0.0f,
dimension,
0.0f,
dimension,
dimension,
0.0f,
dimension,
topLeft.getX(),
topLeft.getY(),
topRightX,
topRightY,
bottomRight.getX(),
bottomRight.getY(),
bottomLeft.getX(),
bottomLeft.getY());
return sampler.sampleGrid(image,
dimension,
0.5f,
0.5f,
dimension - 0.5f,
0.5f,
dimension - 0.5f,
dimension - 0.5f,
0.5f,
dimension - 0.5f,
topLeft.getX(),
topLeft.getY(),
topRight.getX(),
topRight.getY(),
bottomRight.getX(),
bottomRight.getY(),
bottomLeft.getX(),
bottomLeft.getY());
}
/**

View file

@ -28,9 +28,9 @@ public final class DataMatrixBlackBox1TestCase extends AbstractBlackBoxTestCase
// TODO use MultiFormatReader here once Data Matrix decoder is done
super("test/data/blackbox/datamatrix-1", new DataMatrixReader(), BarcodeFormat.DATA_MATRIX);
addTest(7, 7, 0.0f);
addTest(7, 7, 90.0f);
addTest(4, 4, 90.0f);
addTest(6, 6, 180.0f);
addTest(4, 4, 270.0f);
addTest(6, 6, 270.0f);
}
}

View file

@ -27,10 +27,10 @@ public final class DataMatrixBlackBox2TestCase extends AbstractBlackBoxTestCase
public DataMatrixBlackBox2TestCase() {
// TODO use MultiFormatReader here once Data Matrix decoder is done
super("test/data/blackbox/datamatrix-2", new DataMatrixReader(), BarcodeFormat.DATA_MATRIX);
addTest(4, 4, 0.0f);
addTest(1, 1, 90.0f);
addTest(3, 3, 180.0f);
addTest(1, 1, 270.0f);
addTest(5, 5, 0.0f);
addTest(6, 6, 90.0f);
addTest(7, 7, 180.0f);
addTest(7, 7, 270.0f);
}
}