Issue 563: Support non-rectangular Data Matrix

git-svn-id: https://zxing.googlecode.com/svn/trunk@1616 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dav.olivier@gmail.com 2010-10-05 20:19:53 +00:00
parent c988952a07
commit 94679de34d
19 changed files with 208 additions and 79 deletions

View file

@ -38,44 +38,63 @@ public final class DefaultGridSampler extends GridSampler {
p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
return sampleGrid(image, dimension, transform); return sampleGrid(image, dimension, dimension, transform);
} }
public BitMatrix sampleGrid(BitMatrix image,
int dimensionX,
int dimensionY,
float p1ToX, float p1ToY,
float p2ToX, float p2ToY,
float p3ToX, float p3ToY,
float p4ToX, float p4ToY,
float p1FromX, float p1FromY,
float p2FromX, float p2FromY,
float p3FromX, float p3FromY,
float p4FromX, float p4FromY) throws NotFoundException {
PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(
p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
return sampleGrid(image, dimensionX, dimensionY, transform);
}
public BitMatrix sampleGrid(BitMatrix image, public BitMatrix sampleGrid(BitMatrix image,
int dimension, int dimensionX, int dimensionY,
PerspectiveTransform transform) throws NotFoundException { PerspectiveTransform transform) throws NotFoundException {
BitMatrix bits = new BitMatrix(dimension); BitMatrix bits = new BitMatrix(dimensionX, dimensionY);
float[] points = new float[dimension << 1]; float[] points = new float[dimensionX << 1];
for (int y = 0; y < dimension; y++) { for (int y = 0; y < dimensionY; y++) {
int max = points.length; int max = points.length;
float iValue = (float) y + 0.5f; float iValue = (float) y + 0.5f;
for (int x = 0; x < max; x += 2) { for (int x = 0; x < max; x += 2) {
points[x] = (float) (x >> 1) + 0.5f; points[x] = (float) (x >> 1) + 0.5f;
points[x + 1] = iValue; points[x + 1] = iValue;
} }
transform.transformPoints(points); transform.transformPoints(points);
// Quick check to see if points transformed to something inside the image; // Quick check to see if points transformed to something inside the image;
// sufficient to check the endpoints // sufficient to check the endpoints
checkAndNudgePoints(image, points); checkAndNudgePoints(image, points);
try { try {
for (int x = 0; x < max; x += 2) { for (int x = 0; x < max; x += 2) {
if (image.get((int) points[x], (int) points[x + 1])) { if (image.get((int) points[x], (int) points[x + 1])) {
// Black(-ish) pixel // Black(-ish) pixel
bits.set(x >> 1, y); bits.set(x >> 1, y);
} }
} }
} catch (ArrayIndexOutOfBoundsException aioobe) { } catch (ArrayIndexOutOfBoundsException aioobe) {
// This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
// transform gets "twisted" such that it maps a straight line of points to a set of points // transform gets "twisted" such that it maps a straight line of points to a set of points
// whose endpoints are in bounds, but others are not. There is probably some mathematical // whose endpoints are in bounds, but others are not. There is probably some mathematical
// way to detect this about the transformation that I don't know yet. // way to detect this about the transformation that I don't know yet.
// This results in an ugly runtime exception despite our clever checks above -- can't have // This results in an ugly runtime exception despite our clever checks above -- can't have
// that. We could check each point's coordinates but that feels duplicative. We settle for // that. We could check each point's coordinates but that feels duplicative. We settle for
// catching and wrapping ArrayIndexOutOfBoundsException. // catching and wrapping ArrayIndexOutOfBoundsException.
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
} }
return bits; return bits;
} }
} }

View file

@ -92,6 +92,28 @@ public abstract class GridSampler {
float p3FromX, float p3FromY, float p3FromX, float p3FromY,
float p4FromX, float p4FromY) throws NotFoundException; float p4FromX, float p4FromY) throws NotFoundException;
/**
* Samples an image for a rectangular matrix of bits of the given dimension.
* @param image image to sample
* @param dimensionX width of {@link BitMatrix} to sample from image
* @param dimensionY height of {@link BitMatrix} to sample from image
* @return {@link BitMatrix} representing a grid of points sampled from the image within a region
* defined by the "from" parameters
* @throws NotFoundException if image can't be sampled, for example, if the transformation defined
* by the given points is invalid or results in sampling outside the image boundaries
*/
public abstract BitMatrix sampleGrid(BitMatrix image,
int dimensionX,
int dimensionY,
float p1ToX, float p1ToY,
float p2ToX, float p2ToY,
float p3ToX, float p3ToY,
float p4ToX, float p4ToY,
float p1FromX, float p1FromY,
float p2FromX, float p2FromY,
float p3FromX, float p3FromY,
float p4FromX, float p4FromY) throws NotFoundException;
public BitMatrix sampleGrid(BitMatrix image, public BitMatrix sampleGrid(BitMatrix image,
int dimension, int dimension,
PerspectiveTransform transform) throws NotFoundException { PerspectiveTransform transform) throws NotFoundException {

View file

@ -30,18 +30,17 @@ final class BitMatrixParser {
/** /**
* @param bitMatrix {@link BitMatrix} to parse * @param bitMatrix {@link BitMatrix} to parse
* @throws FormatException if dimension is < 10 or > 144 or not 0 mod 2 * @throws FormatException if dimension is < 8 or > 144 or not 0 mod 2
*/ */
BitMatrixParser(BitMatrix bitMatrix) throws FormatException { BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
int dimension = bitMatrix.getHeight(); int dimension = bitMatrix.getHeight();
if (dimension < 10 || dimension > 144 || (dimension & 0x01) != 0) { if (dimension < 8 || dimension > 144 || (dimension & 0x01) != 0) {
throw FormatException.getFormatInstance(); throw FormatException.getFormatInstance();
} }
version = readVersion(bitMatrix); version = readVersion(bitMatrix);
this.mappingBitMatrix = extractDataRegion(bitMatrix); this.mappingBitMatrix = extractDataRegion(bitMatrix);
// TODO(bbrown): Make this work for rectangular symbols this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getWidth(), this.mappingBitMatrix.getHeight());
this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getHeight());
} }
/** /**
@ -61,9 +60,8 @@ final class BitMatrixParser {
return version; return version;
} }
// TODO(bbrown): make this work for rectangular dimensions as well.
int numRows = bitMatrix.getHeight(); int numRows = bitMatrix.getHeight();
int numColumns = numRows; int numColumns = bitMatrix.getWidth();
return Version.getVersionForDimensions(numRows, numColumns); return Version.getVersionForDimensions(numRows, numColumns);
} }
@ -83,9 +81,9 @@ final class BitMatrixParser {
int row = 4; int row = 4;
int column = 0; int column = 0;
// TODO(bbrown): Data Matrix can be rectangular, assuming square for now
int numRows = mappingBitMatrix.getHeight(); int numRows = mappingBitMatrix.getHeight();
int numColumns = numRows; int numColumns = mappingBitMatrix.getWidth();
boolean corner1Read = false; boolean corner1Read = false;
boolean corner2Read = false; boolean corner2Read = false;
@ -407,7 +405,6 @@ final class BitMatrixParser {
int symbolSizeRows = version.getSymbolSizeRows(); int symbolSizeRows = version.getSymbolSizeRows();
int symbolSizeColumns = version.getSymbolSizeColumns(); int symbolSizeColumns = version.getSymbolSizeColumns();
// TODO(bbrown): Make this work with rectangular codes
if (bitMatrix.getHeight() != symbolSizeRows) { if (bitMatrix.getHeight() != symbolSizeRows) {
throw new IllegalArgumentException("Dimension of bitMarix must match the version size"); throw new IllegalArgumentException("Dimension of bitMarix must match the version size");
} }
@ -419,10 +416,10 @@ final class BitMatrixParser {
int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns; int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns;
int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows; int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows;
//int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns; int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns;
// TODO(bbrown): Make this work with rectangular codes // TODO(bbrown): Make this work with rectangular codes
BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionRow); BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionColumn, sizeDataRegionRow);
for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) { for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) {
int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows; int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows;
for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) { for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) {

View file

@ -232,7 +232,7 @@ public final class Version {
new ECBlocks(14, new ECB(1, 16))), new ECBlocks(14, new ECB(1, 16))),
new Version(28, 12, 36, 10, 16, new Version(28, 12, 36, 10, 16,
new ECBlocks(18, new ECB(1, 22))), new ECBlocks(18, new ECB(1, 22))),
new Version(29, 16, 36, 10, 16, new Version(29, 16, 36, 14, 16,
new ECBlocks(24, new ECB(1, 32))), new ECBlocks(24, new ECB(1, 32))),
new Version(30, 16, 48, 14, 22, new Version(30, 16, 48, 14, 22,
new ECBlocks(28, new ECB(1, 49))) new ECBlocks(28, new ECB(1, 49)))

View file

@ -141,35 +141,118 @@ 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. // or bottom right should work here.
int dimension = Math.min(transitionsBetween(topLeft, topRight).getTransitions(),
transitionsBetween(bottomRight, topRight).getTransitions());
if ((dimension & 0x01) == 1) { int dimensionTop = transitionsBetween(topLeft, topRight).getTransitions();
int dimensionRight = transitionsBetween(bottomRight, topRight).getTransitions();
if ((dimensionTop & 0x01) == 1) {
// it can't be odd, so, round... up? // it can't be odd, so, round... up?
dimension++; dimensionTop++;
} }
dimension += 2; dimensionTop += 2;
if ((dimensionRight & 0x01) == 1) {
// it can't be odd, so, round... up?
dimensionRight++;
}
dimensionRight += 2;
//correct top right point to match the white module BitMatrix bits = null;
ResultPoint correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension); ResultPoint correctedTopRight = null;
if (correctedTopRight == null){
correctedTopRight = topRight; if (dimensionTop >= dimensionRight * 2 || dimensionRight >= dimensionTop * 2){
//The matrix is rectangular
correctedTopRight = correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight);
if (correctedTopRight == null){
correctedTopRight = topRight;
}
dimensionTop = transitionsBetween(topLeft, correctedTopRight).getTransitions();
dimensionRight = transitionsBetween(bottomRight, correctedTopRight).getTransitions();
if ((dimensionTop & 0x01) == 1) {
// it can't be odd, so, round... up?
dimensionTop++;
}
if ((dimensionRight & 0x01) == 1) {
// it can't be odd, so, round... up?
dimensionRight++;
}
bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionTop, dimensionRight);
} else {
//The matrix is square
int dimension = Math.min(dimensionRight, dimensionTop);
//correct top right point to match the white module
correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
if (correctedTopRight == null){
correctedTopRight = topRight;
}
//We redetermine the dimension using the corrected top right point
int dimensionCorrected = Math.max(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
transitionsBetween(bottomRight, correctedTopRight).getTransitions());
dimensionCorrected++;
if ((dimensionCorrected & 0x01) == 1) {
dimensionCorrected++;
}
bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionCorrected, dimensionCorrected);
} }
//We redetermine the dimension using the corrected top right point
int dimension2 = Math.max(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}); 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 * Calculates the position of the white top right module using the output of the rectangle detector for a rectangular matrix
*/
private ResultPoint correctTopRightRectangular(ResultPoint bottomLeft,
ResultPoint bottomRight, ResultPoint topLeft, ResultPoint topRight,
int dimensionTop, int dimensionRight) {
float corr = distance(bottomLeft, bottomRight) / (float)dimensionTop;
int norm = distance(topLeft, topRight);
float cos = (topRight.getX() - topLeft.getX()) / norm;
float sin = (topRight.getY() - topLeft.getY()) / norm;
ResultPoint c1 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
corr = distance(bottomLeft, topLeft) / (float)dimensionRight;
norm = distance(bottomRight, topRight);
cos = (topRight.getX() - bottomRight.getX()) / norm;
sin = (topRight.getY() - bottomRight.getY()) / norm;
ResultPoint c2 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
if (!isValid(c1)){
if (isValid(c2)){
return c2;
}
return null;
} else if (!isValid(c2)){
return c1;
}
int l1 = Math.abs(dimensionTop - transitionsBetween(topLeft, c1).getTransitions()) +
Math.abs(dimensionRight - transitionsBetween(bottomRight, c1).getTransitions());
int l2 = Math.abs(dimensionTop - transitionsBetween(topLeft, c2).getTransitions()) +
Math.abs(dimensionRight - transitionsBetween(bottomRight, c2).getTransitions());
if (l1 <= l2){
return c1;
}
return c2;
}
/**
* Calculates the position of the white top right module using the output of the rectangle detector for a square matrix
*/ */
private ResultPoint correctTopRight(ResultPoint bottomLeft, private ResultPoint correctTopRight(ResultPoint bottomLeft,
ResultPoint bottomRight, ResultPoint bottomRight,
@ -242,20 +325,22 @@ public final class Detector {
ResultPoint bottomLeft, ResultPoint bottomLeft,
ResultPoint bottomRight, ResultPoint bottomRight,
ResultPoint topRight, ResultPoint topRight,
int dimension) throws NotFoundException { int dimensionX,
int dimensionY) throws NotFoundException {
GridSampler sampler = GridSampler.getInstance(); GridSampler sampler = GridSampler.getInstance();
return sampler.sampleGrid(image, return sampler.sampleGrid(image,
dimension, dimensionX,
dimensionY,
0.5f, 0.5f,
0.5f, 0.5f,
dimension - 0.5f, dimensionX - 0.5f,
0.5f, 0.5f,
dimension - 0.5f, dimensionX - 0.5f,
dimension - 0.5f, dimensionY - 0.5f,
0.5f, 0.5f,
dimension - 0.5f, dimensionY - 0.5f,
topLeft.getX(), topLeft.getX(),
topLeft.getY(), topLeft.getY(),
topRight.getX(), topRight.getX(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

View file

@ -0,0 +1 @@
abcde

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

View file

@ -0,0 +1 @@
abcdefghijklm

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

View file

@ -0,0 +1 @@
abcdef

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
abcdefghijklmnopq

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1 @@
abcdefghijklmnopqrstuvwxyz

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1 @@
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW

View file

@ -27,10 +27,10 @@ public final class DataMatrixBlackBox1TestCase extends AbstractBlackBoxTestCase
public DataMatrixBlackBox1TestCase() { public DataMatrixBlackBox1TestCase() {
// 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(13, 13, 0.0f);
addTest(7, 7, 90.0f); addTest(13, 13, 90.0f);
addTest(7, 7, 180.0f); addTest(13, 13, 180.0f);
addTest(7, 7, 270.0f); addTest(13, 13, 270.0f);
} }
} }

View file

@ -30,7 +30,7 @@ public final class DataMatrixBlackBox2TestCase extends AbstractBlackBoxTestCase
addTest(10, 10, 0.0f); addTest(10, 10, 0.0f);
addTest(13, 13, 90.0f); addTest(13, 13, 90.0f);
addTest(16, 16, 180.0f); addTest(16, 16, 180.0f);
addTest(12, 12, 270.0f); addTest(13, 13, 270.0f);
} }
} }