/// <returns> number of rows we could safely skip during scanning, based on the first /// two finder patterns that have been located. In some cases their position will /// allow us to infer that the third pattern must lie below a certain point farther /// down in the image. /// </returns> private int findRowSkip() { int max = possibleCenters.Count; if (max <= 1) { return(0); } FinderPattern firstConfirmedCenter = null; foreach (var center in possibleCenters) { if (center.Count >= CENTER_QUORUM) { if (firstConfirmedCenter == null) { firstConfirmedCenter = center; } else { // We have two confirmed centers // How far down can we skip before resuming looking for the next // pattern? In the worst case, only the difference between the // difference in the x / y coordinates of the two centers. // This is the case where you find top left last. hasSkipped = true; //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" return((int)(Math.Abs(firstConfirmedCenter.X - center.X) - Math.Abs(firstConfirmedCenter.Y - center.Y)) / 2); } } } return(0); }
/// <summary> /// Returns the z component of the cross product between vectors BC and BA. /// </summary> private static float crossProductZ(FinderPattern pointA, FinderPattern pointB, FinderPattern pointC) { float bX = pointB.x; float bY = pointB.y; return(((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX))); }
/// <summary> /// Get square of distance between a and b. /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> private static double squaredDistance(FinderPattern a, FinderPattern b) { double x = a.X - b.X; double y = a.Y - b.Y; return(x * x + y * y); }
/// <summary> /// <p>This is called when a horizontal scan finds a possible alignment pattern. It will /// cross check with a vertical scan, and if successful, will, ah, cross-cross-check /// with another horizontal scan. This is needed primarily to locate the real horizontal /// center of the pattern in cases of extreme skew. /// And then we cross-cross-cross check with another diagonal scan.</p> /// If that succeeds the finder pattern location is added to a list that tracks /// the number of times each location has been nearly-matched as a finder pattern. /// Each additional find is more evidence that the location is in fact a finder /// pattern center /// </summary> /// <param name="stateCount">reading state module counts from horizontal scan</param> /// <param name="i">row where finder pattern may be found</param> /// <param name="j">end of possible finder pattern in row</param> /// <param name="pureBarcode">true if in "pure barcode" mode</param> /// <returns> /// true if a finder pattern candidate was found this time /// </returns> protected bool handlePossibleCenter(int[] stateCount, int i, int j, bool pureBarcode) { int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; float?centerJ = centerFromEnd(stateCount, j); if (centerJ == null) { return(false); } float?centerI = crossCheckVertical(i, (int)centerJ.Value, stateCount[2], stateCountTotal); if (centerI != null) { // Re-cross check centerJ = crossCheckHorizontal((int)centerJ.Value, (int)centerI.Value, stateCount[2], stateCountTotal); if (centerJ != null && (!pureBarcode || crossCheckDiagonal((int)centerI, (int)centerJ, stateCount[2], stateCountTotal))) { float estimatedModuleSize = stateCountTotal / 7.0f; bool found = false; for (int index = 0; index < possibleCenters.Count; index++) { var center = possibleCenters[index]; // Look for about the same center and module size: if (center.aboutEquals(estimatedModuleSize, centerI.Value, centerJ.Value)) { possibleCenters.RemoveAt(index); possibleCenters.Insert(index, center.combineEstimate(centerI.Value, centerJ.Value, estimatedModuleSize)); found = true; break; } } if (!found) { var point = new FinderPattern(centerJ.Value, centerI.Value, estimatedModuleSize); possibleCenters.Add(point); if (resultPointCallback != null) { resultPointCallback(point); } } return(true); } } return(false); }
/// <summary> /// Orders an array of three ResultPoints in an order [A,B,C] such that AB < AC and /// BC < AC and the angle between BC and BA is less than 180 degrees. /// </summary> internal static void orderBestPatterns(FinderPattern[] patterns) { // Find distances between pattern centers float zeroOneDistance = distance(patterns[0], patterns[1]); float oneTwoDistance = distance(patterns[1], patterns[2]); float zeroTwoDistance = distance(patterns[0], patterns[2]); FinderPattern pointA, pointB, pointC; // Assume one closest to other two is B; A and C will just be guesses at first if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) { pointB = patterns[0]; pointA = patterns[1]; pointC = patterns[2]; } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) { pointB = patterns[1]; pointA = patterns[0]; pointC = patterns[2]; } else { pointB = patterns[2]; pointA = patterns[0]; pointC = patterns[1]; } // Use cross product to figure out whether A and C are correct or flipped. // This asks whether BC x BA has a positive z component, which is the arrangement // we want for A, B, C. If it's negative, then we've got it flipped around and // should swap A and C. if (crossProductZ(pointA, pointB, pointC) < 0.0f) { FinderPattern temp = pointA; pointA = pointC; pointC = temp; } patterns[0] = pointA; patterns[1] = pointB; patterns[2] = pointC; }
/// <summary> <p>Estimates module size based on two finder patterns -- it uses /// {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the /// width of each, measuring along the axis between their centers.</p> /// </summary> private float calculateModuleSizeOneWay(FinderPattern pattern, FinderPattern otherPattern) { //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int)pattern.X, (int)pattern.Y, (int)otherPattern.X, (int)otherPattern.Y); //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int)otherPattern.X, (int)otherPattern.Y, (int)pattern.X, (int)pattern.Y); if (Single.IsNaN(moduleSizeEst1)) { return(moduleSizeEst2 / 7.0f); } if (Single.IsNaN(moduleSizeEst2)) { return(moduleSizeEst1 / 7.0f); } // Average them, and divide by 7 since we've counted the width of 3 black modules, // and 1 white and 1 black module on either side. Ergo, divide sum by 14. return((moduleSizeEst1 + moduleSizeEst2) / 14.0f); }
/// <summary> <p>Computes the dimension (number of modules on a size) of the QR Code based on the position /// of the finder patterns and estimated module size.</p> /// </summary> private static bool computeDimension(FinderPattern topLeft, FinderPattern topRight, FinderPattern bottomLeft, float moduleSize, out int dimension) { int tltrCentersDimension = MathUtils.round(FinderPattern.distance(topLeft, topRight) / moduleSize); int tlblCentersDimension = MathUtils.round(FinderPattern.distance(topLeft, bottomLeft) / moduleSize); dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7; switch (dimension & 0x03) { // mod 4 case 0: dimension++; break; // 1? do nothing case 2: dimension--; break; case 3: return(true); } return(true); }
/// <summary> <p>Computes an average estimated module size based on estimated derived from the positions /// of the three finder patterns.</p> /// </summary> protected internal virtual float calculateModuleSize(FinderPattern topLeft, FinderPattern topRight, FinderPattern bottomLeft) { // Take the average return((calculateModuleSizeOneWay(topLeft, topRight) + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f); }
/// <summary> /// Processes the finder pattern info. /// </summary> /// <param name="info">The info.</param> /// <returns></returns> protected internal virtual DetectorResult processFinderPatternInfo(FinderPatternInfo info) { FinderPattern topLeft = info.TopLeft; FinderPattern topRight = info.TopRight; FinderPattern bottomLeft = info.BottomLeft; float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft); if (moduleSize < 1.0f) { return(null); } int dimension; if (!computeDimension(topLeft, topRight, bottomLeft, moduleSize, out dimension)) { return(null); } Internal.Version provisionalVersion = Internal.Version.getProvisionalVersionForDimension(dimension); if (provisionalVersion == null) { return(null); } int modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7; AlignmentPattern alignmentPattern = null; // Anything above version 1 has an alignment pattern if (provisionalVersion.AlignmentPatternCenters.Length > 0) { // Guess where a "bottom right" finder pattern would have been float bottomRightX = topRight.X - topLeft.X + bottomLeft.X; float bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y; // Estimate that alignment pattern is closer by 3 modules // from "bottom right" to known top left location //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" float correctionToTopLeft = 1.0f - 3.0f / (float)modulesBetweenFPCenters; //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" int estAlignmentX = (int)(topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X)); //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" int estAlignmentY = (int)(topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y)); // Kind of arbitrary -- expand search radius before giving up for (int i = 4; i <= 16; i <<= 1) { alignmentPattern = findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, (float)i); if (alignmentPattern == null) { continue; } break; } // If we didn't find alignment pattern... well try anyway without it } PerspectiveTransform transform = createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension); BitMatrix bits = sampleGrid(image, transform, dimension); if (bits == null) { return(null); } ResultPoint[] points; if (alignmentPattern == null) { points = new ResultPoint[] { bottomLeft, topLeft, topRight }; } else { points = new ResultPoint[] { bottomLeft, topLeft, topRight, alignmentPattern }; } return(new DetectorResult(bits, points)); }
/// <returns> the 3 best {@link FinderPattern}s from our list of candidates. The "best" are /// those that have been detected at least {@link #CENTER_QUORUM} times, and whose module /// size differs from the average among those patterns the least /// </returns> private FinderPattern[] selectBestPatterns() { int startSize = possibleCenters.Count; if (startSize < 3) { // Couldn't find enough finder patterns return(null); } // Filter outlier possibilities whose module size is too different if (startSize > 3) { // But we can only afford to do so if we have at least 4 possibilities to choose from float totalModuleSize = 0.0f; float square = 0.0f; foreach (var center in possibleCenters) { float size = center.EstimatedModuleSize; totalModuleSize += size; square += size * size; } float average = totalModuleSize / startSize; float stdDev = (float)Math.Sqrt(square / startSize - average * average); possibleCenters.Sort(new FurthestFromAverageComparator(average)); float limit = Math.Max(0.2f * average, stdDev); for (int i = 0; i < possibleCenters.Count && possibleCenters.Count > 3; i++) { FinderPattern pattern = possibleCenters[i]; if (Math.Abs(pattern.EstimatedModuleSize - average) > limit) { possibleCenters.RemoveAt(i); i--; } } } if (possibleCenters.Count > 3) { // Throw away all but those first size candidate points we found. float totalModuleSize = 0.0f; foreach (var possibleCenter in possibleCenters) { totalModuleSize += possibleCenter.EstimatedModuleSize; } float average = totalModuleSize / possibleCenters.Count; possibleCenters.Sort(new CenterComparator(average)); //possibleCenters.subList(3, possibleCenters.Count).clear(); possibleCenters = possibleCenters.GetRange(0, 3); } return(new[] { possibleCenters[0], possibleCenters[1], possibleCenters[2] }); }
/// <summary> <p>Computes an average estimated module size based on estimated derived from the positions /// of the three finder patterns.</p> /// </summary> protected internal virtual float calculateModuleSize(FinderPattern topLeft, FinderPattern topRight, FinderPattern bottomLeft) { // Take the average return (calculateModuleSizeOneWay(topLeft, topRight) + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f; }
/// <summary> /// Returns the z component of the cross product between vectors BC and BA. /// </summary> private static float crossProductZ(FinderPattern pointA, FinderPattern pointB, FinderPattern pointC) { float bX = pointB.x; float bY = pointB.y; return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX)); }
/// <returns> /// distance between two points /// </returns> public static float distance(FinderPattern pattern1, FinderPattern pattern2) { return ZXing.Common.Detector.MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y); }
internal virtual FinderPatternInfo find(IDictionary <DecodeHintType, object> hints) { bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); bool pureBarcode = hints != null && hints.ContainsKey(DecodeHintType.PURE_BARCODE); int maxI = image.Height; int maxJ = image.Width; // We are looking for black/white/black/white/black modules in // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the // image, and then account for the center being 3 modules in size. This gives the smallest // number of pixels the center could be, so skip this often. When trying harder, look for all // QR versions regardless of how dense they are. int iSkip = (3 * maxI) / (4 * MAX_MODULES); if (iSkip < MIN_SKIP || tryHarder) { iSkip = MIN_SKIP; } bool done = false; int[] stateCount = new int[5]; for (int i = iSkip - 1; i < maxI && !done; i += iSkip) { // Get a row of black/white values stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; stateCount[3] = 0; stateCount[4] = 0; int currentState = 0; for (int j = 0; j < maxJ; j++) { if (image[j, i]) { // Black pixel if ((currentState & 1) == 1) { // Counting white pixels currentState++; } stateCount[currentState]++; } else { // White pixel if ((currentState & 1) == 0) { // Counting black pixels if (currentState == 4) { // A winner? if (foundPatternCross(stateCount)) { // Yes bool confirmed = handlePossibleCenter(stateCount, i, j, pureBarcode); if (confirmed) { // Start examining every other line. Checking each line turned out to be too // expensive and didn't improve performance. iSkip = 2; if (hasSkipped) { done = haveMultiplyConfirmedCenters(); } else { int rowSkip = findRowSkip(); if (rowSkip > stateCount[2]) { // Skip rows between row of lower confirmed center // and top of presumed third confirmed center // but back up a bit to get a full chance of detecting // it, entire width of center of finder pattern // Skip by rowSkip, but back off by stateCount[2] (size of last center // of pattern we saw) to be conservative, and also back off by iSkip which // is about to be re-added i += rowSkip - stateCount[2] - iSkip; j = maxJ - 1; } } } else { stateCount[0] = stateCount[2]; stateCount[1] = stateCount[3]; stateCount[2] = stateCount[4]; stateCount[3] = 1; stateCount[4] = 0; currentState = 3; continue; } // Clear state to start looking again currentState = 0; stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; stateCount[3] = 0; stateCount[4] = 0; } else { // No, shift counts back by two stateCount[0] = stateCount[2]; stateCount[1] = stateCount[3]; stateCount[2] = stateCount[4]; stateCount[3] = 1; stateCount[4] = 0; currentState = 3; } } else { stateCount[++currentState]++; } } else { // Counting white pixels stateCount[currentState]++; } } } if (foundPatternCross(stateCount)) { bool confirmed = handlePossibleCenter(stateCount, i, maxJ, pureBarcode); if (confirmed) { iSkip = stateCount[0]; if (hasSkipped) { // Found a third one done = haveMultiplyConfirmedCenters(); } } } } FinderPattern[] patternInfo = selectBestPatterns(); if (patternInfo == null) { return(null); } FinderPattern.orderBestPatterns(patternInfo); return(new FinderPatternInfo(patternInfo)); }
/// <summary> <p>Computes the dimension (number of modules on a size) of the QR Code based on the position /// of the finder patterns and estimated module size.</p> /// </summary> private static bool computeDimension(FinderPattern topLeft, FinderPattern topRight, FinderPattern bottomLeft, float moduleSize, out int dimension) { int tltrCentersDimension = MathUtils.round(FinderPattern.distance(topLeft, topRight) / moduleSize); int tlblCentersDimension = MathUtils.round(FinderPattern.distance(topLeft, bottomLeft) / moduleSize); dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7; switch (dimension & 0x03) { // mod 4 case 0: dimension++; break; // 1? do nothing case 2: dimension--; break; case 3: return true; } return true; }
/// <returns> the 3 best {@link FinderPattern}s from our list of candidates. The "best" are /// those have similar module size and form a shape closer to a isosceles right triangle. /// </returns> private FinderPattern[] selectBestPatterns() { int startSize = possibleCenters.Count; if (startSize < 3) { // Couldn't find enough finder patterns return(null); } possibleCenters.Sort(moduleComparator); double distortion = Double.MaxValue; double[] squares = new double[3]; FinderPattern[] bestPatterns = new FinderPattern[3]; for (int i = 0; i < possibleCenters.Count - 2; i++) { FinderPattern fpi = possibleCenters[i]; float minModuleSize = fpi.EstimatedModuleSize; for (int j = i + 1; j < possibleCenters.Count - 1; j++) { FinderPattern fpj = possibleCenters[j]; double squares0 = squaredDistance(fpi, fpj); for (int k = j + 1; k < possibleCenters.Count; k++) { FinderPattern fpk = possibleCenters[k]; float maxModuleSize = fpk.EstimatedModuleSize; if (maxModuleSize > minModuleSize * 1.4f) { // module size is not similar continue; } squares[0] = squares0; squares[1] = squaredDistance(fpj, fpk); squares[2] = squaredDistance(fpi, fpk); Array.Sort(squares); // a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle). // Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0, // we need to check both two equal sides separately. // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity // from isosceles right triangle. double d = Math.Abs(squares[2] - 2 * squares[1]) + Math.Abs(squares[2] - 2 * squares[0]); if (d < distortion) { distortion = d; bestPatterns[0] = fpi; bestPatterns[1] = fpj; bestPatterns[2] = fpk; } } } } if (distortion == Double.MaxValue) { return(null); } return(bestPatterns); }
/// <summary> <p>Estimates module size based on two finder patterns -- it uses /// {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the /// width of each, measuring along the axis between their centers.</p> /// </summary> private float calculateModuleSizeOneWay(FinderPattern pattern, FinderPattern otherPattern) { //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int)pattern.X, (int)pattern.Y, (int)otherPattern.X, (int)otherPattern.Y); //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'" float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int)otherPattern.X, (int)otherPattern.Y, (int)pattern.X, (int)pattern.Y); if (Single.IsNaN(moduleSizeEst1)) { return moduleSizeEst2 / 7.0f; } if (Single.IsNaN(moduleSizeEst2)) { return moduleSizeEst1 / 7.0f; } // Average them, and divide by 7 since we've counted the width of 3 black modules, // and 1 white and 1 black module on either side. Ergo, divide sum by 14. return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; }
/// <returns> /// distance between two points /// </returns> public static float distance(FinderPattern pattern1, FinderPattern pattern2) { return(ZXing.Common.Detector.MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y)); }