PDF417 improvements from Hartmut, Christoph

git-svn-id: https://zxing.googlecode.com/svn/trunk@2594 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen@gmail.com 2013-03-25 15:06:58 +00:00
parent 843a181d73
commit a42d32ca15
9 changed files with 973 additions and 309 deletions

View file

@ -20,6 +20,7 @@ Brian Brown (Google)
Bruce Allen Bruce Allen
Chang Hyun Park Chang Hyun Park
Christian Brunschen (Google) Christian Brunschen (Google)
Christoph Schulz (creatale GmbH)
crowdin.net crowdin.net
Daniel Switkin (Google) Daniel Switkin (Google)
Dave MacLachlan (Google) Dave MacLachlan (Google)
@ -39,6 +40,7 @@ Fred Lin (Anobiit)
gcstang gcstang
Guillaume Le Biller Guillaume Le Biller
Hannes Erven Hannes Erven
Hartmut Neubauer (Schweers Informationstechnologie GmbH)
hosigumayuugi hosigumayuugi
hypest (Barcorama project) hypest (Barcorama project)
Ian W. Davis Ian W. Davis

View file

@ -26,11 +26,8 @@ import com.google.zxing.common.BitMatrix;
* *
* @author SITA Lab (kevin.osullivan@sita.aero) * @author SITA Lab (kevin.osullivan@sita.aero)
*/ */
final class BitMatrixParser { public final class BitMatrixParser {
private static final int[] NO_ERRORS = new int[0];
private static final int MAX_ROW_DIFFERENCE = 6;
private static final int MAX_ROWS = 90; private static final int MAX_ROWS = 90;
//private static final int MAX_COLUMNS = 30; //private static final int MAX_COLUMNS = 30;
// Maximum Codewords (Data + Error) // Maximum Codewords (Data + Error)
@ -38,7 +35,7 @@ final class BitMatrixParser {
private static final int MODULES_IN_SYMBOL = 17; private static final int MODULES_IN_SYMBOL = 17;
private final BitMatrix bitMatrix; private final BitMatrix bitMatrix;
private int rows = 0; //private int rows = 0;
//private int columns = 0; //private int columns = 0;
private int leftColumnECData = 0; private int leftColumnECData = 0;
@ -61,138 +58,47 @@ final class BitMatrixParser {
* @return an array of codewords. * @return an array of codewords.
*/ */
int[] readCodewords() throws FormatException { int[] readCodewords() throws FormatException {
int width = bitMatrix.getWidth(); //int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight(); int height = bitMatrix.getHeight();
erasures = new int[MAX_CW_CAPACITY]; erasures = new int[MAX_CW_CAPACITY];
// Get the number of pixels in a module across the X dimension
//float moduleWidth = bitMatrix.getModuleWidth();
float moduleWidth = 1.0f; // Image has been sampled and reduced
int[] rowCounters = new int[width];
int[] codewords = new int[MAX_CW_CAPACITY]; int[] codewords = new int[MAX_CW_CAPACITY];
int next = 0; int next = 0;
int matchingConsecutiveScans = 0;
boolean rowInProgress = false;
int rowNumber = 0; int rowNumber = 0;
int rowHeight = 0; for (int i = 0; i < height; i++) {
for (int i = 1; i < height; i++) {
if (rowNumber >= MAX_ROWS) { if (rowNumber >= MAX_ROWS) {
// Something is wrong, since we have exceeded // Something is wrong, since we have exceeded
// the maximum rows in the specification. // the maximum rows in the specification.
throw FormatException.getFormatInstance(); throw FormatException.getFormatInstance();
} }
int rowDifference = 0;
// Scan a line of modules and check the
// difference between this and the previous line
for (int j = 0; j < width; j++) {
// Accumulate differences between this line and the
// previous line.
if (bitMatrix.get(j, i) != bitMatrix.get(j, i - 1)) {
rowDifference++;
}
}
if (rowDifference <= moduleWidth * MAX_ROW_DIFFERENCE) {
for (int j = 0; j < width; j++) {
// Accumulate the black pixels on this line
if (bitMatrix.get(j, i)) {
rowCounters[j]++;
}
}
// Increment the number of consecutive rows of pixels
// that are more or less the same
matchingConsecutiveScans++;
// Height of a row is a multiple of the module size in pixels
// It's supposed to be >= 3x module width, but, accept anything >= 2x
if ((matchingConsecutiveScans + 1) >= 2.0f * moduleWidth) {
// We have some previous matches as well as a match here
// Set processing a unique row.
rowInProgress = true;
}
} else {
if (rowInProgress) {
// Process Row
next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
if (next == -1) {
// Something is wrong, since we have exceeded
// the maximum columns in the specification.
throw FormatException.getFormatInstance();
}
// Reinitialize the row counters.
for (int j = 0; j < rowCounters.length; j++) {
rowCounters[j] = 0;
}
rowNumber++;
rowHeight = 0;
}
matchingConsecutiveScans = 0;
rowInProgress = false;
}
rowHeight++;
}
// Check for a row that was in progress before we exited above.
if (rowInProgress) {
// Process Row // Process Row
if (rowNumber >= MAX_ROWS) { next = processRow(rowNumber, codewords, next);
// Something is wrong, since we have exceeded
// the maximum rows in the specification.
throw FormatException.getFormatInstance();
}
next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
rowNumber++; rowNumber++;
rows = rowNumber;
} }
erasures = trimArray(erasures, eraseCount); erasures = trimArray(erasures, eraseCount);
return trimArray(codewords, next); return trimArray(codewords, next);
} }
/**
* Trim the array to the required size.
*
* @param array the array
* @param size the size to trim it to
* @return the new trimmed array
*/
private static int[] trimArray(int[] array, int size) {
if (size < 0) {
throw new IllegalArgumentException();
}
if (size == 0) {
return NO_ERRORS;
}
int[] a = new int[size];
System.arraycopy(array, 0, a, 0, size);
return a;
}
/** /**
* Convert the symbols in the row to codewords. * Convert the symbols in the row to codewords.
* Each PDF417 symbol character consists of four bar elements and four space * Each PDF417 symbol character consists of four bar elements and four space
* elements, each of which can be one to six modules wide. The four bar and * elements, each of which can be one to six modules wide. The four bar and
* four space elements shall measure 17 modules in total. * four space elements shall measure 17 modules in total.
* *
* @param rowCounters an array containing the counts of black pixels for each column
* in the row.
* @param rowNumber the current row number of codewords. * @param rowNumber the current row number of codewords.
* @param rowHeight the height of this row in pixels.
* @param codewords the codeword array to save codewords into. * @param codewords the codeword array to save codewords into.
* @param next the next available index into the codewords array. * @param next the next available index into the codewords array.
* @return the next available index into the codeword array after processing * @return the next available index into the codeword array after processing
* this row. * this row.
*/ */
int processRow(int[] rowCounters, int rowNumber, int rowHeight, int[] codewords, int next) int processRow(int rowNumber, int[] codewords, int next) throws FormatException {
throws FormatException {
int width = bitMatrix.getWidth(); int width = bitMatrix.getWidth();
int columnNumber = 0; int columnNumber = 0;
long symbol = 0; long symbol = 0;
for (int i = 0; i < width; i += MODULES_IN_SYMBOL) { for (int i = 0; i < width; i += MODULES_IN_SYMBOL) {
// This happens in real life and is almost surely a rare misdecode
if (i + MODULES_IN_SYMBOL > rowCounters.length) {
throw FormatException.getFormatInstance();
}
for (int mask = MODULES_IN_SYMBOL - 1; mask >= 0; mask--) { for (int mask = MODULES_IN_SYMBOL - 1; mask >= 0; mask--) {
if (rowCounters[i + (MODULES_IN_SYMBOL - 1 - mask)] >= rowHeight >>> 1) { if (bitMatrix.get(i + (MODULES_IN_SYMBOL - 1 - mask), rowNumber)) {
symbol |= 1L << mask; symbol |= 1L << mask;
} }
} }
@ -227,8 +133,9 @@ final class BitMatrixParser {
--next; --next;
if (ecLevel < 0 && rowNumber % 3 == 2) { if (ecLevel < 0 && rowNumber % 3 == 2) {
rightColumnECData = codewords[next]; rightColumnECData = codewords[next];
if (rightColumnECData == leftColumnECData && leftColumnECData != 0) { if (rightColumnECData == leftColumnECData && leftColumnECData > 0) {
ecLevel = ((rightColumnECData % 30) - rows % 3) / 3; //ecLevel = ((rightColumnECData % 30) - rows % 3) / 3;
ecLevel = (rightColumnECData % 30) / 3;
} }
} }
codewords[next] = 0; codewords[next] = 0;
@ -236,21 +143,34 @@ final class BitMatrixParser {
return next; return next;
} }
/**
* Trim the array to the required size.
*
* @param array the array
* @param size the size to trim it to
* @return the new trimmed array
*/
private static int[] trimArray(int[] array, int size) {
if (size < 0) {
throw new IllegalArgumentException();
}
int[] a = new int[size];
System.arraycopy(array, 0, a, 0, size);
return a;
}
/** /**
* Translate the symbol into a codeword. * Translate the symbol into a codeword.
* *
* @return the codeword corresponding to the symbol. * @return the codeword corresponding to the symbol.
*/ */
private static int getCodeword(long symbol) { public static int getCodeword(long symbol) {
long sym = symbol & 0x3FFFF; long sym = symbol & 0x3FFFF;
int i = findCodewordIndex(sym); int i = findCodewordIndex(sym);
if (i == -1) { if (i == -1) {
return -1; return -1;
} else {
long cw = CODEWORD_TABLE[i] - 1;
cw %= 929;
return (int) cw;
} }
return (CODEWORD_TABLE[i] - 1) % 929;
} }
/** /**
@ -292,7 +212,7 @@ final class BitMatrixParser {
* specification. The index of a symbol in this table corresponds to the * specification. The index of a symbol in this table corresponds to the
* index into the codeword table. * index into the codeword table.
*/ */
private static final int[] SYMBOL_TABLE = {0x1025e, 0x1027a, 0x1029e, public static final int[] SYMBOL_TABLE = {0x1025e, 0x1027a, 0x1029e,
0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396, 0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396,
0x103a6, 0x103ac, 0x10422, 0x10428, 0x10436, 0x10442, 0x10444, 0x103a6, 0x103ac, 0x10422, 0x10428, 0x10436, 0x10442, 0x10444,
0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482, 0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482,

View file

@ -23,6 +23,7 @@ import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
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.PerspectiveTransform;
import com.google.zxing.common.detector.MathUtils; import com.google.zxing.common.detector.MathUtils;
import java.util.Arrays; import java.util.Arrays;
@ -34,6 +35,8 @@ import java.util.Map;
* *
* @author SITA Lab (kevin.osullivan@sita.aero) * @author SITA Lab (kevin.osullivan@sita.aero)
* @author dswitkin@google.com (Daniel Switkin) * @author dswitkin@google.com (Daniel Switkin)
* @author Schweers Informationstechnologie GmbH (hartmut.neubauer@schweers.de)
* @author creatale GmbH (christoph.schulz@creatale.de)
*/ */
public final class Detector { public final class Detector {
@ -41,7 +44,6 @@ public final class Detector {
private static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT; private static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f); private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);
private static final int SKEW_THRESHOLD = 3;
// B S B S B S B S Bar/Space pattern // B S B S B S B S Bar/Space pattern
// 11111111 0 1 0 1 0 1 000 // 11111111 0 1 0 1 0 1 000
@ -84,18 +86,17 @@ public final class Detector {
// Fetch the 1 bit matrix once up front. // Fetch the 1 bit matrix once up front.
BitMatrix matrix = image.getBlackMatrix(); BitMatrix matrix = image.getBlackMatrix();
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
// Try to find the vertices assuming the image is upright. // Try to find the vertices assuming the image is upright.
ResultPoint[] vertices = findVertices(matrix, tryHarder); int rowStep = 8;
ResultPoint[] vertices = findVertices(matrix, rowStep);
if (vertices == null) { if (vertices == null) {
// Maybe the image is rotated 180 degrees? // Maybe the image is rotated 180 degrees?
vertices = findVertices180(matrix, tryHarder); vertices = findVertices180(matrix, rowStep);
if (vertices != null) { if (vertices != null) {
correctCodeWordVertices(vertices, true); correctVertices(matrix, vertices, true);
} }
} else { } else {
correctCodeWordVertices(vertices, false); correctVertices(matrix, vertices, false);
} }
if (vertices == null) { if (vertices == null) {
@ -107,18 +108,22 @@ public final class Detector {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
int dimension = computeDimension(vertices[4], vertices[6], int dimension = computeDimension(vertices[12], vertices[14],
vertices[5], vertices[7], moduleWidth); vertices[13], vertices[15], moduleWidth);
if (dimension < 1) { if (dimension < 1) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
int ydimension = computeYDimension(vertices[4], vertices[6], vertices[5], vertices[7], moduleWidth); int yDimension = Math.max(computeYDimension(vertices[12], vertices[14],
ydimension = ydimension > dimension ? ydimension : dimension; vertices[13], vertices[15], moduleWidth), dimension);
// Deskew and sample image. // Deskew and over-sample image.
BitMatrix bits = sampleGrid(matrix, vertices[4], vertices[5], vertices[6], vertices[7], dimension, ydimension); BitMatrix linesMatrix = sampleLines(vertices, dimension, yDimension);
return new DetectorResult(bits, new ResultPoint[]{vertices[5], vertices[4], vertices[6], vertices[7]}); BitMatrix linesGrid = new LinesSampler(linesMatrix, dimension).sample();
//TODO: verify vertex indices.
return new DetectorResult(linesGrid, new ResultPoint[]{
vertices[5], vertices[4], vertices[6], vertices[7]});
} }
/** /**
@ -126,6 +131,7 @@ public final class Detector {
* and Stop patterns as locators. * and Stop patterns as locators.
* *
* @param matrix the scanned barcode image. * @param matrix the scanned barcode image.
* @param rowStep the step size for iterating rows (every n-th row).
* @return an array containing the vertices: * @return an array containing the vertices:
* vertices[0] x, y top left barcode * vertices[0] x, y top left barcode
* vertices[1] x, y bottom left barcode * vertices[1] x, y bottom left barcode
@ -136,17 +142,15 @@ public final class Detector {
* vertices[6] x, y top right codeword area * vertices[6] x, y top right codeword area
* vertices[7] x, y bottom right codeword area * vertices[7] x, y bottom right codeword area
*/ */
private static ResultPoint[] findVertices(BitMatrix matrix, boolean tryHarder) { private static ResultPoint[] findVertices(BitMatrix matrix, int rowStep) {
int height = matrix.getHeight(); int height = matrix.getHeight();
int width = matrix.getWidth(); int width = matrix.getWidth();
ResultPoint[] result = new ResultPoint[8]; ResultPoint[] result = new ResultPoint[16];
boolean found = false; boolean found = false;
int[] counters = new int[START_PATTERN.length]; int[] counters = new int[START_PATTERN.length];
int rowStep = Math.max(1, height >> (tryHarder ? 9 : 7));
// Top Left // Top Left
for (int i = 0; i < height; i += rowStep) { for (int i = 0; i < height; i += rowStep) {
int[] loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN, counters); int[] loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN, counters);
@ -207,9 +211,9 @@ public final class Detector {
* and Stop patterns as locators. This assumes that the image is rotated 180 * and Stop patterns as locators. This assumes that the image is rotated 180
* degrees and if it locates the start and stop patterns at it will re-map * degrees and if it locates the start and stop patterns at it will re-map
* the vertices for a 0 degree rotation. * the vertices for a 0 degree rotation.
* TODO: Change assumption about barcode location.
* *
* @param matrix the scanned barcode image. * @param matrix the scanned barcode image.
* @param rowStep the step size for iterating rows (every n-th row).
* @return an array containing the vertices: * @return an array containing the vertices:
* vertices[0] x, y top left barcode * vertices[0] x, y top left barcode
* vertices[1] x, y bottom left barcode * vertices[1] x, y bottom left barcode
@ -220,18 +224,19 @@ public final class Detector {
* vertices[6] x, y top right codeword area * vertices[6] x, y top right codeword area
* vertices[7] x, y bottom right codeword area * vertices[7] x, y bottom right codeword area
*/ */
private static ResultPoint[] findVertices180(BitMatrix matrix, boolean tryHarder) { private static ResultPoint[] findVertices180(BitMatrix matrix, int rowStep) {
// TODO: Change assumption about barcode location.
int height = matrix.getHeight(); int height = matrix.getHeight();
int width = matrix.getWidth(); int width = matrix.getWidth();
int halfWidth = width >> 1; int halfWidth = width >> 1;
ResultPoint[] result = new ResultPoint[8]; ResultPoint[] result = new ResultPoint[16];
boolean found = false; boolean found = false;
int[] counters = new int[START_PATTERN_REVERSE.length]; int[] counters = new int[START_PATTERN_REVERSE.length];
int rowStep = Math.max(1, height >> (tryHarder ? 9 : 7));
// Top Left // Top Left
for (int i = height - 1; i > 0; i -= rowStep) { for (int i = height - 1; i > 0; i -= rowStep) {
int[] loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE, counters); int[] loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE, counters);
@ -287,175 +292,6 @@ public final class Detector {
return found ? result : null; return found ? result : null;
} }
/**
* Because we scan horizontally to detect the start and stop patterns, the vertical component of
* the codeword coordinates will be slightly wrong if there is any skew or rotation in the image.
* This method moves those points back onto the edges of the theoretically perfect bounding
* quadrilateral if needed.
*
* @param vertices The eight vertices located by findVertices().
*/
private static void correctCodeWordVertices(ResultPoint[] vertices, boolean upsideDown) {
float v0x = vertices[0].getX();
float v0y = vertices[0].getY();
float v2x = vertices[2].getX();
float v2y = vertices[2].getY();
float v4x = vertices[4].getX();
float v4y = vertices[4].getY();
float v6x = vertices[6].getX();
float v6y = vertices[6].getY();
float skew = v4y - v6y;
if (upsideDown) {
skew = -skew;
}
if (skew > SKEW_THRESHOLD) {
// Fix v4
float deltax = v6x - v0x;
float deltay = v6y - v0y;
float delta2 = deltax * deltax + deltay * deltay;
float correction = (v4x - v0x) * deltax / delta2;
vertices[4] = new ResultPoint(v0x + correction * deltax, v0y + correction * deltay);
} else if (-skew > SKEW_THRESHOLD) {
// Fix v6
float deltax = v2x - v4x;
float deltay = v2y - v4y;
float delta2 = deltax * deltax + deltay * deltay;
float correction = (v2x - v6x) * deltax / delta2;
vertices[6] = new ResultPoint(v2x - correction * deltax, v2y - correction * deltay);
}
float v1x = vertices[1].getX();
float v1y = vertices[1].getY();
float v3x = vertices[3].getX();
float v3y = vertices[3].getY();
float v5x = vertices[5].getX();
float v5y = vertices[5].getY();
float v7x = vertices[7].getX();
float v7y = vertices[7].getY();
skew = v7y - v5y;
if (upsideDown) {
skew = -skew;
}
if (skew > SKEW_THRESHOLD) {
// Fix v5
float deltax = v7x - v1x;
float deltay = v7y - v1y;
float delta2 = deltax * deltax + deltay * deltay;
float correction = (v5x - v1x) * deltax / delta2;
vertices[5] = new ResultPoint(v1x + correction * deltax, v1y + correction * deltay);
} else if (-skew > SKEW_THRESHOLD) {
// Fix v7
float deltax = v3x - v5x;
float deltay = v3y - v5y;
float delta2 = deltax * deltax + deltay * deltay;
float correction = (v3x - v7x) * deltax / delta2;
vertices[7] = new ResultPoint(v3x - correction * deltax, v3y - correction * deltay);
}
}
/**
* <p>Estimates module size (pixels in a module) based on the Start and End
* finder patterns.</p>
*
* @param vertices an array of vertices:
* vertices[0] x, y top left barcode
* vertices[1] x, y bottom left barcode
* vertices[2] x, y top right barcode
* vertices[3] x, y bottom right barcode
* vertices[4] x, y top left codeword area
* vertices[5] x, y bottom left codeword area
* vertices[6] x, y top right codeword area
* vertices[7] x, y bottom right codeword area
* @return the module size.
*/
private static float computeModuleWidth(ResultPoint[] vertices) {
float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
return (moduleWidth1 + moduleWidth2) / 2.0f;
}
/**
* Computes the dimension (number of modules in a row) of the PDF417 Code
* based on vertices of the codeword area and estimated module size.
*
* @param topLeft of codeword area
* @param topRight of codeword area
* @param bottomLeft of codeword area
* @param bottomRight of codeword are
* @param moduleWidth estimated module size
* @return the number of modules in a row.
*/
private static int computeDimension(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
ResultPoint bottomRight,
float moduleWidth) {
int topRowDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleWidth);
int bottomRowDimension = MathUtils.round(ResultPoint.distance(bottomLeft, bottomRight) / moduleWidth);
return ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
}
/**
* Computes the y dimension (number of modules in a column) of the PDF417 Code
* based on vertices of the codeword area and estimated module size.
*
* @param topLeft of codeword area
* @param topRight of codeword area
* @param bottomLeft of codeword area
* @param bottomRight of codeword are
* @param moduleWidth estimated module size
* @return the number of modules in a row.
*/
private static int computeYDimension(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
ResultPoint bottomRight,
float moduleWidth) {
int leftColumnDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleWidth);
int rightColumnDimension = MathUtils.round(ResultPoint.distance(topRight, bottomRight) / moduleWidth);
return (leftColumnDimension + rightColumnDimension) >> 1;
}
private static BitMatrix sampleGrid(BitMatrix matrix,
ResultPoint topLeft,
ResultPoint bottomLeft,
ResultPoint topRight,
ResultPoint bottomRight,
int xdimension,
int ydimension) throws NotFoundException {
// Note that unlike 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(
matrix,
xdimension, ydimension,
0.0f, // p1ToX
0.0f, // p1ToY
xdimension, // p2ToX
0.0f, // p2ToY
xdimension, // p3ToX
ydimension, // p3ToY
0.0f, // p4ToX
ydimension, // p4ToY
topLeft.getX(), // p1FromX
topLeft.getY(), // p1FromY
topRight.getX(), // p2FromX
topRight.getY(), // p2FromY
bottomRight.getX(), // p3FromX
bottomRight.getY(), // p3FromY
bottomLeft.getX(), // p4FromX
bottomLeft.getY()); // p4FromY
}
/** /**
* @param matrix row of black/white values to search * @param matrix row of black/white values to search
* @param column x position to start search * @param column x position to start search
@ -550,4 +386,267 @@ public final class Detector {
return totalVariance / total; return totalVariance / total;
} }
/**
* <p>Correct the vertices by searching for top and bottom vertices of wide
* bars, then locate the intersections between the upper and lower horizontal
* line and the inner vertices vertical lines.</p>
*
* @param matrix the scanned barcode image.
* @param vertices the vertices vector is extended and the new members are:
* vertices[ 8] x,y point on upper border of left wide bar
* vertices[ 9] x,y point on lower border of left wide bar
* vertices[10] x,y point on upper border of right wide bar
* vertices[11] x,y point on lower border of right wide bar
* vertices[12] x,y final top left codeword area
* vertices[13] x,y final bottom left codeword area
* vertices[14] x,y final top right codeword area
* vertices[15] x,y final bottom right codeword area
* @param upsideDown true if rotated by 180 degree.
*/
private static void correctVertices(BitMatrix matrix,
ResultPoint[] vertices,
boolean upsideDown) throws NotFoundException {
boolean isLowLeft = Math.abs(vertices[4].getY() - vertices[5].getY()) < 20.0;
boolean isLowRight = Math.abs(vertices[6].getY() - vertices[7].getY()) < 20.0;
if (isLowLeft || isLowRight) {
throw NotFoundException.getNotFoundInstance();
} else {
findWideBarTopBottom(matrix, vertices, 0, 0, 8, 17, upsideDown ? 1 : -1);
findWideBarTopBottom(matrix, vertices, 1, 0, 8, 17, upsideDown ? -1 : 1);
findWideBarTopBottom(matrix, vertices, 2, 11, 7, 18, upsideDown ? 1 : -1);
findWideBarTopBottom(matrix, vertices, 3, 11, 7, 18, upsideDown ? -1 : 1);
findCrossingPoint(vertices, 12, 4, 5, 8, 10, matrix);
findCrossingPoint(vertices, 13, 4, 5, 9, 11, matrix);
findCrossingPoint(vertices, 14, 6, 7, 8, 10, matrix);
findCrossingPoint(vertices, 15, 6, 7, 9, 11, matrix);
}
}
/**
* <p>Locate the top or bottom of one of the two wide black bars of a guard pattern.</p>
*
* <p>Warning: it only searches along the y axis, so the return points would not be
* right if the barcode is too curved.</p>
*
* @param matrix The bit matrix.
* @param vertices The 16 vertices located by findVertices(); the result
* points are stored into vertices[8], ... , vertices[11].
* @param offsetVertex The offset of the outer vertex and the inner
* vertex (+ 4) to be corrected and (+ 8) where the result is stored.
* @param startWideBar start of a wide bar.
* @param lenWideBar length of wide bar.
* @param lenPattern length of the pattern.
* @param rowStep +1 if corner should be exceeded towards the bottom, -1 towards the top.
*/
private static void findWideBarTopBottom(BitMatrix matrix,
ResultPoint[] vertices,
int offsetVertex,
int startWideBar,
int lenWideBar,
int lenPattern,
int rowStep) {
ResultPoint verticeStart = vertices[offsetVertex];
ResultPoint verticeEnd = vertices[offsetVertex + 4];
// Start horizontally at the middle of the bar.
int endWideBar = startWideBar + lenWideBar;
float barDiff = verticeEnd.getX() - verticeStart.getX();
float barStart = verticeStart.getX() + (barDiff * startWideBar) / lenPattern;
float barEnd = verticeStart.getX() + (barDiff * endWideBar) / lenPattern;
int x = Math.round((barStart + barEnd) / 2.0f);
// Start vertically between the preliminary vertices.
int yStart = Math.round(verticeStart.getY());
int y = yStart;
// Find offset of thin bar to the right as additional safeguard.
int nextBarX = (int)Math.max(barStart, barEnd) + 1;
while (nextBarX < matrix.getWidth()) {
if (!matrix.get(nextBarX - 1, y) && matrix.get(nextBarX, y)) {
break;
}
nextBarX++;
}
nextBarX -= x;
boolean isEnd = false;
while (!isEnd) {
if (matrix.get(x, y)) {
// If the thin bar to the right ended, stop as well
isEnd = !matrix.get(x + nextBarX, y) && !matrix.get(x + nextBarX + 1, y);
y += rowStep;
if (y <= 0 || y >= matrix.getHeight() - 1) {
// End of barcode image reached.
isEnd = true;
}
} else {
// Look sidewise whether black bar continues? (in the case the image is skewed)
if (x > 0 && matrix.get(x - 1, y)) {
x--;
} else if (x < matrix.getWidth() - 1 && matrix.get(x + 1, y)) {
x++;
} else {
// End of pattern regarding big bar and big gap reached.
isEnd = true;
if (y != yStart) {
// Turn back one step, because target has been exceeded.
y -= rowStep;
}
}
}
}
vertices[offsetVertex + 8] = new ResultPoint(x, y);
}
/**
* <p>Finds the intersection of two lines.</p>
*
* @param vertices The reference of the vertices vector
* @param idxResult Index of result point inside the vertices vector.
* @param idxLineA1
* @param idxLineA2 Indices two points inside the vertices vector that define the first line.
* @param idxLineB1
* @param idxLineB2 Indices two points inside the vertices vector that define the second line.
* @param matrix: bit matrix, here only for testing whether the result is inside the matrix.
* @return Returns true when the result is valid and lies inside the matrix. Otherwise throws an
* exception.
*/
private static void findCrossingPoint(ResultPoint[] vertices,
int idxResult,
int idxLineA1, int idxLineA2,
int idxLineB1, int idxLineB2,
BitMatrix matrix) throws NotFoundException {
ResultPoint result = intersection(vertices[idxLineA1], vertices[idxLineA2],
vertices[idxLineB1], vertices[idxLineB2]);
if (result == null) {
throw NotFoundException.getNotFoundInstance();
}
int x = Math.round(result.getX());
int y = Math.round(result.getY());
if (x < 0 || x >= matrix.getWidth() || y < 0 || y >= matrix.getHeight()) {
throw NotFoundException.getNotFoundInstance();
}
vertices[idxResult] = result;
}
/**
* Computes the intersection between two lines.
*/
private static ResultPoint intersection(ResultPoint a1, ResultPoint a2, ResultPoint b1, ResultPoint b2) {
float dxa = a1.getX() - a2.getX();
float dxb = b1.getX() - b2.getX();
float dya = a1.getY() - a2.getY();
float dyb = b1.getY() - b2.getY();
float p = a1.getX() * a2.getY() - a1.getY() * a2.getX();
float q = b1.getX() * b2.getY() - b1.getY() * b2.getX();
float denom = dxa * dyb - dya * dxb;
if (denom == 0) {
// Lines don't intersect
return null;
}
float x = (p * dxb - dxa * q) / denom;
float y = (p * dyb - dya * q) / denom;
return new ResultPoint(x, y);
}
/**
* <p>Estimates module size (pixels in a module) based on the Start and End
* finder patterns.</p>
*
* @param vertices an array of vertices:
* vertices[0] x, y top left barcode
* vertices[1] x, y bottom left barcode
* vertices[2] x, y top right barcode
* vertices[3] x, y bottom right barcode
* vertices[4] x, y top left codeword area
* vertices[5] x, y bottom left codeword area
* vertices[6] x, y top right codeword area
* vertices[7] x, y bottom right codeword area
* @return the module size.
*/
private static float computeModuleWidth(ResultPoint[] vertices) {
float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
return (moduleWidth1 + moduleWidth2) / 2.0f;
}
/**
* Computes the dimension (number of modules in a row) of the PDF417 Code
* based on vertices of the codeword area and estimated module size.
*
* @param topLeft of codeword area
* @param topRight of codeword area
* @param bottomLeft of codeword area
* @param bottomRight of codeword are
* @param moduleWidth estimated module size
* @return the number of modules in a row.
*/
private static int computeDimension(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
ResultPoint bottomRight,
float moduleWidth) {
int topRowDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleWidth);
int bottomRowDimension = MathUtils.round(ResultPoint.distance(bottomLeft, bottomRight) / moduleWidth);
return ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
}
/**
* Computes the y dimension (number of modules in a column) of the PDF417 Code
* based on vertices of the codeword area and estimated module size.
*
* @param topLeft of codeword area
* @param topRight of codeword area
* @param bottomLeft of codeword area
* @param bottomRight of codeword are
* @param moduleWidth estimated module size
* @return the number of modules in a row.
*/
private static int computeYDimension(ResultPoint topLeft,
ResultPoint topRight,
ResultPoint bottomLeft,
ResultPoint bottomRight,
float moduleWidth) {
int leftColumnDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleWidth);
int rightColumnDimension = MathUtils.round(ResultPoint.distance(topRight, bottomRight) / moduleWidth);
return (leftColumnDimension + rightColumnDimension) >> 1;
}
/**
* Deskew and over-sample image.
*
* @param vertices vertices from findVertices()
* @param dimension x dimension
* @param yDimension y dimension
* @return an over-sampled BitMatrix.
*/
private BitMatrix sampleLines(ResultPoint[] vertices, int dimension, int yDimension) throws NotFoundException {
int sampleDimensionX = dimension * 8;
int sampleDimensionY = yDimension * 4;
PerspectiveTransform transform = PerspectiveTransform
.quadrilateralToQuadrilateral(0.0f, 0.0f,
sampleDimensionX, 0.0f, 0.0f,
sampleDimensionY, sampleDimensionX,
sampleDimensionY, vertices[12].getX(),
vertices[12].getY(), vertices[14].getX(),
vertices[14].getY(), vertices[13].getX(),
vertices[13].getY(), vertices[15].getX(),
vertices[15].getY());
return GridSampler.getInstance().sampleGrid(image.getBlackMatrix(),
sampleDimensionX, sampleDimensionY, transform);
}
} }

View file

@ -0,0 +1,643 @@
/*
* Copyright 2013 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.pdf417.detector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.pdf417.decoder.BitMatrixParser;
/**
* <p>Encapsulates logic that detects valid codewords from a deskewed lines matrix.
* To sample the grid several properties of PDF417 are used:</p>
*
* <ul>
* <li>each codeword/symbol has 17 modules</li>
* <li>each codeword/symbol begins with a black bar and ends with a white bar</li>
* <li>each codeword consists of 4 black bars + 4 white bars</li>
* <li>all valid codewords are known (obviously)</li>
* </ul>
* @author creatale GmbH (christoph.schulz@creatale.de)
*/
public final class LinesSampler {
private static final int MODULES_IN_SYMBOL = 17;
private static final int BARS_IN_SYMBOL = 8;
private static final int BARCODE_START_OFFSET = 2;
private static final float[] RATIOS_TABLE;
static {
// Pre-computes and outputs the symbol ratio table.
float[][] table = new float[BitMatrixParser.SYMBOL_TABLE.length][BARS_IN_SYMBOL];
RATIOS_TABLE = new float[BitMatrixParser.SYMBOL_TABLE.length * BARS_IN_SYMBOL];
int x = 0;
for (int i = 0; i < BitMatrixParser.SYMBOL_TABLE.length; i++) {
int currentSymbol = BitMatrixParser.SYMBOL_TABLE[i];
int currentBit = currentSymbol & 0x1;
for (int j = 0; j < BARS_IN_SYMBOL; j++) {
float size = 0.0f;
while ((currentSymbol & 0x1) == currentBit) {
size += 1.0f;
currentSymbol >>= 1;
}
currentBit = currentSymbol & 0x1;
table[i][BARS_IN_SYMBOL - j - 1] = size / MODULES_IN_SYMBOL;
}
for (int j = 0; j < BARS_IN_SYMBOL; j++) {
RATIOS_TABLE[x] = table[i][j];
x++;
}
}
}
private final BitMatrix linesMatrix;
private final int symbolsPerLine;
private final int dimension;
public LinesSampler(BitMatrix linesMatrix, int dimension) {
this.linesMatrix = linesMatrix;
this.symbolsPerLine = dimension / MODULES_IN_SYMBOL;
this.dimension = dimension;
}
/**
* Samples a grid from a lines matrix.
*
* @return the potentially decodable bit matrix.
*/
public BitMatrix sample() {
List<Float> symbolWidths = findSymbolWidths();
int[][] codewords = new int[linesMatrix.getHeight()][];
int[][] clusterNumbers = new int[linesMatrix.getHeight()][];
linesMatrixToCodewords(codewords, clusterNumbers, symbolWidths);
List<List<Map<Integer, Integer>>> votes = distributeVotes(codewords, clusterNumbers);
List<List<Integer>> detectedCodeWords = new ArrayList<List<Integer>>();
resize3(detectedCodeWords, votes.size());
for (int i = 0; i < votes.size(); i++) {
resize4(detectedCodeWords.get(i), votes.get(i).size());
for (int j = 0; j < votes.get(i).size(); j++) {
if (!votes.get(i).get(j).isEmpty()) {
detectedCodeWords.get(i).set(j, getValueWithMaxVotes(votes.get(i).get(j)).getVote());
}
}
}
List<Integer> insertLinesAt = findMissingLines(detectedCodeWords);
int rowCount = decodeRowCount(detectedCodeWords, insertLinesAt);
resize3(detectedCodeWords, rowCount);
return codewordsToBitMatrix(detectedCodeWords, dimension, detectedCodeWords.size());
}
/**
* Use the following property of PDF417 barcodes to detect symbols:
* Every symbol starts with a black module and every symbol is 17 modules
* wide, therefore there have to be columns in the line matrix that are
* completely composed of black pixels.
*
* @return array containing with symbol widths.
*/
private List<Float> findSymbolWidths() {
float expectedSymbolWidth;
if (symbolsPerLine > 0) {
expectedSymbolWidth = linesMatrix.getWidth() / (float) symbolsPerLine;
} else {
expectedSymbolWidth = linesMatrix.getWidth();
}
List<Float> symbolWidths = new ArrayList<Float>();
int symbolStart = 0;
boolean lastWasSymbolStart = true;
int[] blackCount = new int[linesMatrix.getWidth()];
for (int x = BARCODE_START_OFFSET; x < linesMatrix.getWidth(); x++) {
for (int y = 0; y < linesMatrix.getHeight(); y++) {
if (linesMatrix.get(x, y)) {
blackCount[x]++;
}
}
if (blackCount[x] == linesMatrix.getHeight()) {
if (!lastWasSymbolStart) {
float currentWidth = x - symbolStart;
// Make sure we really found a symbol by asserting a minimal size of
// 75% of the expected symbol width. This might break highly distorted
// barcodes, but fixes an issue with barcodes where there is a full black
// column from top to bottom within a symbol.
if (currentWidth > 0.75 * expectedSymbolWidth) {
// The actual symbol width might be slightly bigger than the expected
// symbol width, but if we are more than half an expected symbol width
// bigger, we assume that we missed one or more symbols and assume that
// they were the expected symbol width.
while (currentWidth > 1.5 * expectedSymbolWidth) {
symbolWidths.add(expectedSymbolWidth);
currentWidth -= expectedSymbolWidth;
}
symbolWidths.add(currentWidth);
lastWasSymbolStart = true;
symbolStart = x;
}
}
} else {
if (lastWasSymbolStart) {
lastWasSymbolStart = false;
}
}
}
// The last symbol ends at the right edge of the matrix, where there usually is no black bar.
float currentWidth = linesMatrix.getWidth() - symbolStart;
while (currentWidth > 1.5 * expectedSymbolWidth) {
symbolWidths.add(expectedSymbolWidth);
currentWidth -= expectedSymbolWidth;
}
symbolWidths.add(currentWidth);
return symbolWidths;
}
private void linesMatrixToCodewords(int[][] codewords, int[][] clusterNumbers, List<Float> symbolWidths) {
for (int y = 0; y < linesMatrix.getHeight(); y++) {
codewords[y] = new int[symbolsPerLine];
clusterNumbers[y] = new int [symbolsPerLine];
Arrays.fill(clusterNumbers[y], 0, clusterNumbers[y].length, -1);
List<Integer> barWidths = new ArrayList<Integer>();
// Run-length encode the bars in the scanned linesMatrix.
// We assume that the first bar is black, as determined by the PDF417 standard.
// Filter small white bars at the beginning of the barcode.
// Small white bars may occur due to small deviations in scan line sampling.
barWidths.add(BARCODE_START_OFFSET);
boolean isSetBar = true;
for (int x = BARCODE_START_OFFSET; x < linesMatrix.getWidth(); x++) {
if (linesMatrix.get(x, y)) {
if (!isSetBar) {
isSetBar = true;
barWidths.add(0);
}
} else {
if (isSetBar) {
isSetBar = false;
barWidths.add(0);
}
}
int lastIndex = barWidths.size() - 1;
barWidths.set(lastIndex, barWidths.get(lastIndex) + 1);
}
// Find the symbols in the line by counting bar lengths until we reach symbolWidth.
// We make sure, that the last bar of a symbol is always white, as determined by the PDF417 standard.
// This helps to reduce the amount of errors done during the symbol recognition.
// The symbolWidth usually is not constant over the width of the barcode.
int[] cwStarts = new int[symbolsPerLine];
cwStarts[0] = 0;
int cwCount = 1;
int cwWidth = 0;
for (int i = 0; i < barWidths.size() && cwCount < symbolsPerLine; i++) {
cwWidth += barWidths.get(i);
if ((float)cwWidth > symbolWidths.get(cwCount - 1)) {
if ((i % 2) == 1) { // check if bar is white
i++;
}
if (i < barWidths.size()) {
cwWidth = barWidths.get(i);
}
cwStarts[cwCount] = i;
cwCount++;
}
}
float[][] cwRatios = new float[symbolsPerLine][BARS_IN_SYMBOL];
// Distribute bar widths to modules of a codeword.
for (int i = 0; i < symbolsPerLine; i++) {
int cwStart = cwStarts[i];
int cwEnd = (i == symbolsPerLine - 1) ? barWidths.size() : cwStarts[i + 1];
int cwLength = cwEnd - cwStart;
if (cwLength < 7 || cwLength > 9) {
// We try to recover symbols with 7 or 9 bars and spaces with heuristics, but everything else is beyond repair.
continue;
}
// For symbols with 9 bar length simply ignore the last bar.
float cwWidthF = 0.0f;
for (int j = 0; j < Math.min(BARS_IN_SYMBOL, cwLength); ++j) {
cwWidthF += (float)barWidths.get(cwStart + j);
}
// If there were only 7 bars and spaces detected use the following heuristic:
// Assume the length of the symbol is symbolWidth and the last (unrecognized) bar uses all remaining space.
if (cwLength == 7) {
for (int j = 0; j < cwLength; ++j) {
cwRatios[i][j] = (float)barWidths.get(cwStart + j) / symbolWidths.get(i);
}
cwRatios[i][7] = (symbolWidths.get(i) - cwWidthF) / symbolWidths.get(i);
} else {
for (int j = 0; j < cwRatios[i].length; ++j) {
cwRatios[i][j] = barWidths.get(cwStart + j) / cwWidthF;
}
}
float bestMatchError = Float.MAX_VALUE;
int bestMatch = 0;
// Search for the most possible codeword by comparing the ratios of bar size to symbol width.
// The sum of the squared differences is used as similarity metric.
// (Picture it as the square euclidian distance in the space of eight tuples where a tuple represents the bar ratios.)
for (int j = 0; j < BitMatrixParser.SYMBOL_TABLE.length; j++) {
float error = 0.0f;
for (int k = 0; k < BARS_IN_SYMBOL; k++) {
float diff = RATIOS_TABLE[j * BARS_IN_SYMBOL + k] - cwRatios[i][k];
error += diff * diff;
}
if (error < bestMatchError) {
bestMatchError = error;
bestMatch = BitMatrixParser.SYMBOL_TABLE[j];
}
}
codewords[y][i] = bestMatch;
clusterNumbers[y][i] = calculateClusterNumber(bestMatch);
}
}
}
private List<List<Map<Integer, Integer>>> distributeVotes(int[][] codewords, int[][] clusterNumbers) {
// Matrix of votes for codewords which are possible at this position.
List<List<Map<Integer, Integer>>> votes = new ArrayList<List<Map<Integer, Integer>>>();
votes.add(new ArrayList<Map<Integer,Integer>>());
resize2(votes.get(0), symbolsPerLine);
int currentRow = 0;
Map<Integer, Integer> clusterNumberVotes = new HashMap<Integer, Integer>();
int lastLineClusterNumber = -1;
for (int y = 0; y < codewords.length; y++) {
// Vote for the most probable cluster number for this row.
clusterNumberVotes.clear();
for (int i = 0; i < codewords[y].length; i++) {
if (clusterNumbers[y][i] != -1) {
clusterNumberVotes.put(clusterNumbers[y][i], defaultValue(clusterNumberVotes.get(clusterNumbers[y][i]), 0) + 1);
}
}
// Ignore lines where no codeword could be read.
if (!clusterNumberVotes.isEmpty()) {
VoteResult voteResult = getValueWithMaxVotes(clusterNumberVotes);
boolean lineClusterNumberIsIndecisive = voteResult.isIndecisive();
int lineClusterNumber = voteResult.getVote();
// If there are to few votes on the lines cluster number, we keep the old one.
// This avoids switching lines because of damaged inter line readings, but
// may cause problems for barcodes with four or less rows.
if (lineClusterNumberIsIndecisive) {
lineClusterNumber = lastLineClusterNumber;
}
if ((lineClusterNumber != ((lastLineClusterNumber + 3) % 9)) && (lastLineClusterNumber != -1)) {
lineClusterNumber = lastLineClusterNumber;
}
// Ignore broken lines at the beginning of the barcode.
if ((lineClusterNumber == 0 && lastLineClusterNumber == -1) || (lastLineClusterNumber != -1)) {
if ((lineClusterNumber == ((lastLineClusterNumber + 3) % 9)) && (lastLineClusterNumber != -1)) {
currentRow++;
if (votes.size() < currentRow + 1) {
resize1(votes, currentRow + 1);
resize2(votes.get(currentRow), symbolsPerLine);
}
}
if ((lineClusterNumber == ((lastLineClusterNumber + 6) % 9)) && (lastLineClusterNumber != -1)) {
currentRow += 2;
if (votes.size() < currentRow + 1) {
resize1(votes, currentRow + 1);
resize2(votes.get(currentRow), symbolsPerLine);
}
}
for (int i = 0; i < codewords[y].length; i++) {
if (clusterNumbers[y][i] != -1) {
if (clusterNumbers[y][i] == lineClusterNumber) {
Map<Integer, Integer> votesMap = votes.get(currentRow).get(i);
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
} else if (clusterNumbers[y][i] == ((lineClusterNumber + 3) % 9)) {
if (votes.size() < currentRow + 2) {
resize1(votes, currentRow + 2);
resize2(votes.get(currentRow + 1), symbolsPerLine);
}
Map<Integer, Integer> votesMap = votes.get(currentRow + 1).get(i);
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
} else if ((clusterNumbers[y][i] == ((lineClusterNumber + 6) % 9)) && (currentRow > 0)) {
Map<Integer, Integer> votesMap = votes.get(currentRow - 1).get(i);
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
}
}
}
lastLineClusterNumber = lineClusterNumber;
}
}
}
return votes;
}
private List<Integer> findMissingLines(List<List<Integer>> detectedCodeWords) {
List<Integer> insertLinesAt = new ArrayList<Integer>();
if (detectedCodeWords.size() > 1) {
for (int i = 0; i < detectedCodeWords.size() - 1; i++) {
int clusterNumberRow = -1;
for (int j = 0; j < detectedCodeWords.get(i).size() && clusterNumberRow == -1; j++) {
int clusterNumber = calculateClusterNumber(detectedCodeWords.get(i).get(j));
if (clusterNumber != -1) {
clusterNumberRow = clusterNumber;
}
}
if (i == 0) {
// The first line must have the cluster number 0. Insert empty lines to match this.
if (clusterNumberRow > 0) {
insertLinesAt.add(0);
if (clusterNumberRow > 3) {
insertLinesAt.add(0);
}
}
}
int clusterNumberNextRow = -1;
for (int j = 0; j < detectedCodeWords.get(i + 1).size() && clusterNumberNextRow == -1; j++) {
int clusterNumber = calculateClusterNumber(detectedCodeWords.get(i + 1).get(j));
if (clusterNumber != -1) {
clusterNumberNextRow = clusterNumber;
}
}
if ((clusterNumberRow + 3) % 9 != clusterNumberNextRow
&& clusterNumberRow != -1
&& clusterNumberNextRow != -1) {
// The cluster numbers are not consecutive. Insert an empty line between them.
insertLinesAt.add(i + 1);
if (clusterNumberRow == clusterNumberNextRow) {
// There may be two lines missing. This is detected when two consecutive lines have the same cluster number.
insertLinesAt.add(i + 1);
}
}
}
}
for (int i = 0; i < insertLinesAt.size(); i++) {
List<Integer> v = new ArrayList<Integer>();
for (int j = 0; j < symbolsPerLine; ++j) {
v.add(0);
}
detectedCodeWords.add(insertLinesAt.get(i) + i, v);
}
return insertLinesAt;
}
private int decodeRowCount(List<List<Integer>> detectedCodeWords, List<Integer> insertLinesAt) {
// Use the information in the first and last column to determin the number of rows and find more missing rows.
// For missing rows insert blank space, so the error correction can try to fill them in.
insertLinesAt.clear();
Map<Integer,Integer> rowCountVotes = new HashMap<Integer, Integer>();
Map<Integer,Integer> ecLevelVotes = new HashMap<Integer, Integer>();
Map<Integer,Integer> rowNumberVotes = new HashMap<Integer, Integer>();
int lastRowNumber = -1;
for (int i = 0; i + 2 < detectedCodeWords.size(); i += 3) {
rowNumberVotes.clear();
int firstCodewordDecodedLeft = -1;
if (detectedCodeWords.get(i).get(0) != 0) {
firstCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i).get(0));
}
int secondCodewordDecodedLeft = -1;
if (detectedCodeWords.get(i + 1).get(0) != 0) {
secondCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 1).get(0));
}
int thirdCodewordDecodedLeft = -1;
if (detectedCodeWords.get(i + 2).get(0) != 0) {
thirdCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 2).get(0));
}
int firstCodewordDecodedRight = -1;
if (detectedCodeWords.get(i).get(detectedCodeWords.get(i).size() - 1) != 0) {
firstCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i).get(detectedCodeWords.get(i).size() - 1));
}
int secondCodewordDecodedRight = -1;
if (detectedCodeWords.get(i + 1).get(detectedCodeWords.get(i + 1).size() - 1) != 0) {
secondCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 1).get(detectedCodeWords.get(i + 1).size() - 1));
}
int thirdCodewordDecodedRight = -1;
if (detectedCodeWords.get(i + 2).get(detectedCodeWords.get(i + 2).size() - 1) != 0) {
thirdCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 2).get(detectedCodeWords.get(i + 2).size() - 1));
}
if (firstCodewordDecodedLeft != -1 && secondCodewordDecodedLeft != -1) {
int leftRowCount = ((firstCodewordDecodedLeft % 30) * 3) + ((secondCodewordDecodedLeft % 30) % 3);
int leftECLevel = (secondCodewordDecodedLeft % 30) / 3;
rowCountVotes.put(leftRowCount, defaultValue(rowCountVotes.get(leftRowCount), 0) + 1);
ecLevelVotes.put(leftECLevel, defaultValue(ecLevelVotes.get(leftECLevel), 0) + 1);
}
if (secondCodewordDecodedRight != -1 && thirdCodewordDecodedRight != -1) {
int rightRowCount = ((secondCodewordDecodedRight % 30) * 3) + ((thirdCodewordDecodedRight % 30) % 3);
int rightECLevel = (thirdCodewordDecodedRight % 30) / 3;
rowCountVotes.put(rightRowCount, defaultValue(rowCountVotes.get(rightRowCount), 0) + 1);
ecLevelVotes.put(rightECLevel, defaultValue(ecLevelVotes.get(rightECLevel), 0) + 1);
}
if (firstCodewordDecodedLeft != -1) {
int rowNumber = firstCodewordDecodedLeft / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
if (secondCodewordDecodedLeft != -1) {
int rowNumber = secondCodewordDecodedLeft / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
if (thirdCodewordDecodedLeft != -1) {
int rowNumber = thirdCodewordDecodedLeft / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
if (firstCodewordDecodedRight != -1) {
int rowNumber = firstCodewordDecodedRight / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
if (secondCodewordDecodedRight != -1) {
int rowNumber = secondCodewordDecodedRight / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
if (thirdCodewordDecodedRight != -1) {
int rowNumber = thirdCodewordDecodedRight / 30;
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
}
int rowNumber = getValueWithMaxVotes(rowNumberVotes).getVote();
if (lastRowNumber + 1 < rowNumber) {
for (int j = lastRowNumber + 1; j < rowNumber; j++) {
insertLinesAt.add(i);
insertLinesAt.add(i);
insertLinesAt.add(i);
}
}
lastRowNumber = rowNumber;
}
for (int i = 0; i < insertLinesAt.size(); i++) {
List<Integer> v = new ArrayList<Integer>();
for (int j = 0; j < symbolsPerLine; ++j) {
v.add(0);
}
detectedCodeWords.add(insertLinesAt.get(i) + i, v);
}
int rowCount = getValueWithMaxVotes(rowCountVotes).getVote();
//int ecLevel = getValueWithMaxVotes(ecLevelVotes).getVote();
rowCount += 1;
return rowCount;
}
private static class VoteResult {
private boolean indecisive;
private int vote;
boolean isIndecisive() {
return indecisive;
}
void setIndecisive(boolean indecisive) {
this.indecisive = indecisive;
}
int getVote() {
return vote;
}
void setVote(int vote) {
this.vote = vote;
}
}
private static VoteResult getValueWithMaxVotes(Map<Integer, Integer> votes) {
VoteResult result = new VoteResult();
int maxVotes = 0;
for (Map.Entry<Integer, Integer> entry : votes.entrySet()) {
if (entry.getValue() > maxVotes) {
maxVotes = entry.getValue();
result.setVote(entry.getKey());
result.setIndecisive(false);
} else if (entry.getValue() == maxVotes) {
result.setIndecisive(true);
}
}
return result;
}
private static BitMatrix codewordsToBitMatrix(List<List<Integer>> codewords, int dimension, int yDimension) {
BitMatrix result = new BitMatrix(dimension, yDimension);
for (int i = 0; i < codewords.size(); i++) {
for (int j = 0; j < codewords.get(i).size(); j++) {
int moduleOffset = j * MODULES_IN_SYMBOL;
for (int k = 0; k < MODULES_IN_SYMBOL; k++) {
if ((codewords.get(i).get(j) & (1 << (MODULES_IN_SYMBOL - k - 1))) > 0) {
result.set(moduleOffset + k, i);
}
}
}
}
return result;
}
private static int calculateClusterNumber(int codeword) {
if (codeword == 0) {
return -1;
}
int barNumber = 0;
boolean blackBar = true;
int clusterNumber = 0;
for (int i = 0; i < MODULES_IN_SYMBOL; i++) {
if ((codeword & (1 << i)) > 0) {
if (!blackBar) {
blackBar = true;
barNumber++;
}
if (barNumber % 2 == 0) {
clusterNumber++;
} else {
clusterNumber--;
}
} else {
if (blackBar) {
blackBar = false;
}
}
}
return (clusterNumber + 9) % 9;
}
private static void resize1(List<List<Map<Integer,Integer>>> list, int size) {
// Delete some
for (int i = size; i < list.size(); i++) {
list.remove(i);
}
// Append some.
for (int i = list.size(); i < size; i++) {
list.add(new ArrayList<Map<Integer,Integer>>());
}
}
private static void resize2(List<Map<Integer,Integer>> list, int size) {
// Delete some
for (int i = size; i < list.size(); i++) {
list.remove(i);
}
// Append some.
for (int i = list.size(); i < size; i++) {
list.add(new HashMap<Integer, Integer>());
}
}
private static void resize3(List<List<Integer>> list, int size) {
// Delete some
for (int i = size; i < list.size(); i++) {
list.remove(i);
}
// Append some.
for (int i = list.size(); i < size; i++) {
list.add(new ArrayList<Integer>());
}
}
private static void resize4(List<Integer> list, int size) {
// Delete some
for (int i = size; i < list.size(); i++) {
list.remove(i);
}
// Append some.
for (int i = list.size(); i < size; i++) {
list.add(0);
}
}
private static <T> T defaultValue(T value, T d) {
return value == null ? d : value;
}
}

View file

@ -1 +1 @@
Unknown - change me! -ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ

View file

@ -1 +1 @@
Unknown - change me! -ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890

View file

@ -29,7 +29,7 @@ public final class PDF417BlackBox1TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox1TestCase() { public PDF417BlackBox1TestCase() {
super("test/data/blackbox/pdf417", new MultiFormatReader(), BarcodeFormat.PDF_417); super("test/data/blackbox/pdf417", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(4, 4, 0.0f); addTest(6, 6, 0.0f);
addTest(4, 4, 180.0f); addTest(4, 4, 180.0f);
} }

View file

@ -29,8 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox2TestCase() { public PDF417BlackBox2TestCase() {
super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417); super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(19, 19, 0, 0, 0.0f); addTest(23, 23, 0, 0, 0.0f);
addTest(17, 17, 0, 0, 180.0f); addTest(22, 22, 0, 0, 180.0f);
} }
} }

View file

@ -24,8 +24,8 @@ public final class PDF417BlackBox3TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox3TestCase() { public PDF417BlackBox3TestCase() {
super("test/data/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417); super("test/data/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(4, 4, 0, 0, 0.0f); addTest(9, 9, 0, 0, 0.0f);
addTest(4, 4, 0, 0, 180.0f); addTest(9, 9, 0, 0, 180.0f);
} }
} }