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) Dave MacLachlan (Google)
David Phillip Oster (Google) David Phillip Oster (Google)
David Albert (Bug Labs) David Albert (Bug Labs)
David Olivier
Diego Pierotto Diego Pierotto
Eduardo Castillejo (University of Deusto) Eduardo Castillejo (University of Deusto)
Eric Kobrin (Velocitude) 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.Comparator;
import com.google.zxing.common.DetectorResult; import com.google.zxing.common.DetectorResult;
import com.google.zxing.common.GridSampler; 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.Enumeration;
import java.util.Hashtable; import java.util.Hashtable;
@ -37,7 +37,7 @@ import java.util.Vector;
*/ */
public final class Detector { 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 // 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 // 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() // No, can't use valueOf()
private final BitMatrix image; private final BitMatrix image;
private final MonochromeRectangleDetector rectangleDetector; private final WhiteRectangleDetector rectangleDetector;
public Detector(BitMatrix image) { public Detector(BitMatrix image) {
this.image = image; this.image = image;
rectangleDetector = new MonochromeRectangleDetector(image); rectangleDetector = new WhiteRectangleDetector(image);
} }
/** /**
* <p>Detects a Data Matrix Code in an image.</p> * <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 * @throws NotFoundException if no Data Matrix Code can be found
*/ */
public DetectorResult detect() throws NotFoundException { public DetectorResult detect() throws NotFoundException {
@ -82,6 +82,12 @@ public final class Detector {
ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions.elementAt(0); ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions.elementAt(0);
ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions.elementAt(1); 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 // 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. // endpoints in the four endpoints. One will show up twice.
Hashtable pointCount = new Hashtable(); 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 // 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 // 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 // or bottom right should work here.
// due to noise. So we try both and take the min. int dimension = Math.max(transitionsBetween(topLeft, topRight).getTransitions(),
int dimension = Math.min(transitionsBetween(topLeft, topRight).getTransitions(),
transitionsBetween(bottomRight, topRight).getTransitions()); transitionsBetween(bottomRight, topRight).getTransitions());
if ((dimension & 0x01) == 1) { if ((dimension & 0x01) == 1) {
// it can't be odd, so, round... up? // it can't be odd, so, round... up?
@ -153,8 +157,79 @@ public final class Detector {
} }
dimension += 2; dimension += 2;
BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, dimension); //correct top right point to match the white module
return new DetectorResult(bits, new ResultPoint[] {pointA, pointB, pointC, pointD}); 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 topLeft,
ResultPoint bottomLeft, ResultPoint bottomLeft,
ResultPoint bottomRight, ResultPoint bottomRight,
ResultPoint topRight,
int dimension) throws NotFoundException { 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(); GridSampler sampler = GridSampler.getInstance();
return sampler.sampleGrid(
image, return sampler.sampleGrid(image,
dimension, dimension,
0.0f, 0.5f,
0.0f, 0.5f,
dimension, dimension - 0.5f,
0.0f, 0.5f,
dimension, dimension - 0.5f,
dimension, dimension - 0.5f,
0.0f, 0.5f,
dimension, dimension - 0.5f,
topLeft.getX(), topLeft.getX(),
topLeft.getY(), topLeft.getY(),
topRightX, topRight.getX(),
topRightY, topRight.getY(),
bottomRight.getX(), bottomRight.getX(),
bottomRight.getY(), bottomRight.getY(),
bottomLeft.getX(), bottomLeft.getX(),
bottomLeft.getY()); bottomLeft.getY());
} }
/** /**

View file

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