// -*- mode:c++; tab-width:2; indent-tabs-mode:nil; c-basic-offset:2 -*- /* * Copyright 2010 ZXing authors All rights reserved. * * 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. */ #include #include #include #include #include #include #include // vs12, std::min und std:max using std::max; using std::abs; using std::numeric_limits; using zxing::pdf417::detector::Detector; using zxing::common::detector::Math; using zxing::common::detector::MathUtils; using zxing::Ref; using zxing::ArrayRef; using zxing::DetectorResult; using zxing::ResultPoint; using zxing::Point; using zxing::BitMatrix; using zxing::GridSampler; // VC++ using zxing::BinaryBitmap; using zxing::DecodeHints; using zxing::Line; /** *

Encapsulates logic that can detect a PDF417 Code in an image, even if the * PDF417 Code is rotated or skewed, or partially obscured.

* * @author SITA Lab (kevin.osullivan@sita.aero) * @author Daniel Switkin (dswitkin@google.com) * @author Schweers Informationstechnologie GmbH (hartmut.neubauer@schweers.de) * @author creatale GmbH (christoph.schulz@creatale.de) */ const int Detector::MAX_AVG_VARIANCE= (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); const int Detector::MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f); // B S B S B S B S Bar/Space pattern // 11111111 0 1 0 1 0 1 000 const int Detector::START_PATTERN[] = {8, 1, 1, 1, 1, 1, 1, 3}; const int Detector::START_PATTERN_LENGTH = sizeof(START_PATTERN) / sizeof(int); // 11111111 0 1 0 1 0 1 000 const int Detector::START_PATTERN_REVERSE[] = {3, 1, 1, 1, 1, 1, 1, 8}; const int Detector::START_PATTERN_REVERSE_LENGTH = sizeof(START_PATTERN_REVERSE) / sizeof(int); // 1111111 0 1 000 1 0 1 00 1 const int Detector::STOP_PATTERN[] = {7, 1, 1, 3, 1, 1, 1, 2, 1}; const int Detector::STOP_PATTERN_LENGTH = sizeof(STOP_PATTERN) / sizeof(int); // B S B S B S B S B Bar/Space pattern // 1111111 0 1 000 1 0 1 00 1 const int Detector::STOP_PATTERN_REVERSE[] = {1, 2, 1, 1, 1, 3, 1, 1, 7}; const int Detector::STOP_PATTERN_REVERSE_LENGTH = sizeof(STOP_PATTERN_REVERSE) / sizeof(int); Detector::Detector(Ref image) : image_(image) {} Ref Detector::detect() { return detect(DecodeHints()); } Ref Detector::detect(DecodeHints const& hints) { (void)hints; // Fetch the 1 bit matrix once up front. Ref matrix = image_->getBlackMatrix(); // Try to find the vertices assuming the image is upright. const int rowStep = 8; ArrayRef< Ref > vertices (findVertices(matrix, rowStep)); if (!vertices) { // Maybe the image is rotated 180 degrees? vertices = findVertices180(matrix, rowStep); if (vertices) { correctVertices(matrix, vertices, true); } } else { correctVertices(matrix, vertices, false); } if (!vertices) { throw NotFoundException("No vertices found."); } float moduleWidth = computeModuleWidth(vertices); if (moduleWidth < 1.0f) { throw NotFoundException("Bad module width."); } int dimension = computeDimension(vertices[12], vertices[14], vertices[13], vertices[15], moduleWidth); if (dimension < 1) { throw NotFoundException("Bad dimension."); } int yDimension = max(computeYDimension(vertices[12], vertices[14], vertices[13], vertices[15], moduleWidth), dimension); // Deskew and sample lines from image. Ref linesMatrix = sampleLines(vertices, dimension, yDimension); Ref linesGrid(LinesSampler(linesMatrix, dimension).sample()); ArrayRef< Ref > points(4); points[0] = vertices[5]; points[1] = vertices[4]; points[2] = vertices[6]; points[3] = vertices[7]; return Ref(new DetectorResult(linesGrid, points)); } /** * Locate the vertices and the codewords area of a black blob using the Start * and Stop patterns as locators. * * @param matrix the scanned barcode image. * @param rowStep the step size for iterating rows (every n-th row). * @return an array containing the 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 */ ArrayRef< Ref > Detector::findVertices(Ref matrix, int rowStep) { const int height = matrix->getHeight(); const int width = matrix->getWidth(); ArrayRef< Ref > result(16); bool found = false; ArrayRef counters(new Array(START_PATTERN_LENGTH)); // Top Left for (int i = 0; i < height; i += rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN, START_PATTERN_LENGTH, counters); if (loc) { result[0] = new ResultPoint((float)loc[0], (float)i); result[4] = new ResultPoint((float)loc[1], (float)i); found = true; break; } } // Bottom left if (found) { // Found the Top Left vertex found = false; for (int i = height - 1; i > 0; i -= rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN, START_PATTERN_LENGTH, counters); if (loc) { result[1] = new ResultPoint((float)loc[0], (float)i); result[5] = new ResultPoint((float)loc[1], (float)i); found = true; break; } } } counters = new Array(STOP_PATTERN_LENGTH); // Top right if (found) { // Found the Bottom Left vertex found = false; for (int i = 0; i < height; i += rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, width, false, STOP_PATTERN, STOP_PATTERN_LENGTH, counters); if (loc) { result[2] = new ResultPoint((float)loc[1], (float)i); result[6] = new ResultPoint((float)loc[0], (float)i); found = true; break; } } } // Bottom right if (found) { // Found the Top right vertex found = false; for (int i = height - 1; i > 0; i -= rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, width, false, STOP_PATTERN, STOP_PATTERN_LENGTH, counters); if (loc) { result[3] = new ResultPoint((float)loc[1], (float)i); result[7] = new ResultPoint((float)loc[0], (float)i); found = true; break; } } } return found ? result : ArrayRef< Ref >(); } ArrayRef< Ref > Detector::findVertices180(Ref matrix, int rowStep) { const int height = matrix->getHeight(); const int width = matrix->getWidth(); const int halfWidth = width >> 1; ArrayRef< Ref > result(16); bool found = false; ArrayRef counters = new Array(START_PATTERN_REVERSE_LENGTH); // Top Left for (int i = height - 1; i > 0; i -= rowStep) { ArrayRef loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE, START_PATTERN_REVERSE_LENGTH, counters); if (loc) { result[0] = new ResultPoint((float)loc[1], (float)i); result[4] = new ResultPoint((float)loc[0], (float)i); found = true; break; } } // Bottom Left if (found) { // Found the Top Left vertex found = false; for (int i = 0; i < height; i += rowStep) { ArrayRef loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE, START_PATTERN_REVERSE_LENGTH, counters); if (loc) { result[1] = new ResultPoint((float)loc[1], (float)i); result[5] = new ResultPoint((float)loc[0], (float)i); found = true; break; } } } counters = new Array(STOP_PATTERN_REVERSE_LENGTH); // Top Right if (found) { // Found the Bottom Left vertex found = false; for (int i = height - 1; i > 0; i -= rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, halfWidth, false, STOP_PATTERN_REVERSE, STOP_PATTERN_REVERSE_LENGTH, counters); if (loc) { result[2] = new ResultPoint((float)loc[0], (float)i); result[6] = new ResultPoint((float)loc[1], (float)i); found = true; break; } } } // Bottom Right if (found) { // Found the Top Right vertex found = false; for (int i = 0; i < height; i += rowStep) { ArrayRef loc = findGuardPattern(matrix, 0, i, halfWidth, false, STOP_PATTERN_REVERSE, STOP_PATTERN_REVERSE_LENGTH, counters); if (loc) { result[3] = new ResultPoint((float)loc[0], (float)i); result[7] = new ResultPoint((float)loc[1], (float)i); found = true; break; } } } return found ? result : ArrayRef< Ref >(); } /** * @param matrix row of black/white values to search * @param column x position to start search * @param row y position to start search * @param width the number of pixels to search on this row * @param pattern pattern of counts of number of black and white pixels that are * being searched for as a pattern * @param counters array of counters, as long as pattern, to re-use * @return start/end horizontal offset of guard pattern, as an array of two ints. */ ArrayRef Detector::findGuardPattern(Ref matrix, int column, int row, int width, bool whiteFirst, const int pattern[], int patternSize, ArrayRef& counters) { counters->values().assign(counters->size(), 0); int patternLength = patternSize; bool isWhite = whiteFirst; int counterPosition = 0; int patternStart = column; for (int x = column; x < column + width; x++) { bool pixel = matrix->get(x, row); if (pixel ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { ArrayRef result = new Array(2); result[0] = patternStart; result[1] = x; return result; } patternStart += counters[0] + counters[1]; for(int i = 0; i < patternLength - 2; ++i) counters[i] = counters[ i + 2]; counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } return ArrayRef(); } /** * Determines how closely a set of observed counts of runs of black/white * values matches a given target pattern. This is reported as the ratio of * the total variance from the expected pattern proportions across all * pattern elements, to the length of the pattern. * * @param counters observed counters * @param pattern expected pattern * @param maxIndividualVariance The most any counter can differ before we give up * @return ratio of total variance between counters and pattern compared to * total pattern size, where the ratio has been multiplied by 256. * So, 0 means no variance (perfect match); 256 means the total * variance between counters and patterns equals the pattern length, * higher values mean even more variance */ int Detector::patternMatchVariance(ArrayRef& counters, const int pattern[], int maxIndividualVariance) { int numCounters = counters->size(); int total = 0; int patternLength = 0; for (int i = 0; i < numCounters; i++) { total += counters[i]; patternLength += pattern[i]; } if (total < patternLength) { // If we don't even have one pixel per unit of bar width, assume this // is too small to reliably match, so fail: return numeric_limits::max(); } // We're going to fake floating-point math in integers. We just need to use more bits. // Scale up patternLength so that intermediate values below like scaledCounter will have // more "significant digits". int unitBarWidth = (total << 8) / patternLength; maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> 8; int totalVariance = 0; for (int x = 0; x < numCounters; x++) { int counter = counters[x] << 8; int scaledPattern = pattern[x] * unitBarWidth; int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; if (variance > maxIndividualVariance) { return numeric_limits::max(); } totalVariance += variance; } return totalVariance / total; } /** *

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.

* * @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. */ void Detector::correctVertices(Ref matrix, ArrayRef< Ref >& vertices, bool upsideDown) { bool isLowLeft = abs(vertices[4]->getY() - vertices[5]->getY()) < 20.0; bool isLowRight = abs(vertices[6]->getY() - vertices[7]->getY()) < 20.0; if (isLowLeft || isLowRight) { throw NotFoundException("Cannot find enough PDF417 guard patterns!"); } 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); } } /** *

Locate the top or bottom of one of the two wide black bars of a guard pattern.

* *

Warning: it only searches along the y axis, so the return points would not be * right if the barcode is too curved.

* * @param matrix The bit matrix. * @param vertices The 16 vertices located by findVertices(); the result * points are stored into vertices[8], ... , vertices[11]. * @param offsetVertice The offset of the outer vertice and the inner * vertice (+ 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. */ void Detector::findWideBarTopBottom(Ref matrix, ArrayRef< Ref > &vertices, int offsetVertice, int startWideBar, int lenWideBar, int lenPattern, int rowStep) { Ref verticeStart(vertices[offsetVertice]); Ref verticeEnd(vertices[offsetVertice + 4]); // Start horizontally at the middle of the bar. int endWideBar = startWideBar + lenWideBar; float barDiff = verticeEnd->getX() - verticeStart->getX(); float barStart = verticeStart->getX() + barDiff * (float)startWideBar / (float)lenPattern; float barEnd = verticeStart->getX() + barDiff * (float)endWideBar / (float)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(max(barStart, barEnd) + 1); for (; nextBarX < matrix->getWidth(); nextBarX++) if (!matrix->get(nextBarX - 1, y) && matrix->get(nextBarX, y)) break; nextBarX -= x; bool 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 >= (int)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 < (int)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[offsetVertice + 8] = new ResultPoint((float)x, (float)y); } /** *

Finds the intersection of two lines.

* * @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. **/ void Detector::findCrossingPoint(ArrayRef< Ref >& vertices, int idxResult, int idxLineA1, int idxLineA2, int idxLineB1, int idxLineB2, Ref& matrix) { Point p1(vertices[idxLineA1]->getX(), vertices[idxLineA1]->getY()); Point p2(vertices[idxLineA2]->getX(), vertices[idxLineA2]->getY()); Point p3(vertices[idxLineB1]->getX(), vertices[idxLineB1]->getY()); Point p4(vertices[idxLineB2]->getX(), vertices[idxLineB2]->getY()); Point result(intersection(Line(p1, p2), Line(p3, p4))); if (result.x == numeric_limits::infinity() || result.y == numeric_limits::infinity()) { throw NotFoundException("PDF:Detector: cannot find the crossing of parallel lines!"); } int x = Math::round(result.x); int y = Math::round(result.y); if (x < 0 || x >= (int)matrix->getWidth() || y < 0 || y >= (int)matrix->getHeight()) { throw NotFoundException("PDF:Detector: crossing points out of region!"); } vertices[idxResult] = Ref(new ResultPoint(result.x, result.y)); } /** * Computes the intersection between two lines. */ Point Detector::intersection(Line a, Line b) { float dxa = a.start.x - a.end.x; float dxb = b.start.x - b.end.x; float dya = a.start.y - a.end.y; float dyb = b.start.y - b.end.y; float p = a.start.x * a.end.y - a.start.y * a.end.x; float q = b.start.x * b.end.y - b.start.y * b.end.x; float denom = dxa * dyb - dya * dxb; if(abs(denom) < 1e-12) // Lines don't intersect (replaces "denom == 0") return Point(numeric_limits::infinity(), numeric_limits::infinity()); float x = (p * dxb - dxa * q) / denom; float y = (p * dyb - dya * q) / denom; return Point(x, y); } /** *

Estimates module size (pixels in a module) based on the Start and End * finder patterns.

* * @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. */ float Detector::computeModuleWidth(ArrayRef< Ref >& 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. */ int Detector::computeDimension(Ref const& topLeft, Ref const& topRight, Ref const& bottomLeft, Ref const& 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. */ int Detector::computeYDimension(Ref const& topLeft, Ref const& topRight, Ref const& bottomLeft, Ref const& 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. */ Ref Detector::sampleLines(ArrayRef< Ref > const& vertices, int dimensionY, int dimension) { const int sampleDimensionX = dimension * 8; const int sampleDimensionY = dimensionY * 4; Ref transform( PerspectiveTransform::quadrilateralToQuadrilateral( 0.0f, 0.0f, (float)sampleDimensionX, 0.0f, 0.0f, (float)sampleDimensionY, (float)sampleDimensionX, (float)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())); Ref linesMatrix = GridSampler::getInstance().sampleGrid( image_->getBlackMatrix(), sampleDimensionX, sampleDimensionY, transform); return linesMatrix; }