Improve detector logic to throw out false positive finder patterns in a more reasonab...
[zxing.git] / core / src / com / google / zxing / qrcode / detector / FinderPatternFinder.java
index 70d9a39..199b895 100755 (executable)
 \r
 package com.google.zxing.qrcode.detector;\r
 \r
-import com.google.zxing.BinaryBitmap;\r
 import com.google.zxing.DecodeHintType;\r
 import com.google.zxing.ReaderException;\r
 import com.google.zxing.ResultPoint;\r
-import com.google.zxing.common.BitArray;\r
 import com.google.zxing.common.Collections;\r
 import com.google.zxing.common.Comparator;\r
+import com.google.zxing.common.BitMatrix;\r
 \r
 import java.util.Hashtable;\r
 import java.util.Vector;\r
@@ -42,7 +41,7 @@ public class FinderPatternFinder {
   protected static final int MAX_MODULES = 57; // support up to version 10 for mobile clients\r
   private static final int INTEGER_MATH_SHIFT = 8;\r
 \r
-  private final BinaryBitmap image;\r
+  private final BitMatrix image;\r
   private final Vector possibleCenters;\r
   private boolean hasSkipped;\r
   private final int[] crossCheckStateCount;\r
@@ -52,13 +51,13 @@ public class FinderPatternFinder {
    *\r
    * @param image image to search\r
    */\r
-  public FinderPatternFinder(BinaryBitmap image) {\r
+  public FinderPatternFinder(BitMatrix image) {\r
     this.image = image;\r
     this.possibleCenters = new Vector();\r
     this.crossCheckStateCount = new int[5];\r
   }\r
 \r
-  protected BinaryBitmap getImage() {\r
+  protected BitMatrix getImage() {\r
     return image;\r
   }\r
 \r
@@ -84,10 +83,8 @@ public class FinderPatternFinder {
 \r
     boolean done = false;\r
     int[] stateCount = new int[5];\r
-    BitArray blackRow = new BitArray(maxJ);\r
     for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {\r
       // Get a row of black/white values\r
-      blackRow = image.getBlackRow(i, blackRow, 0, maxJ);\r
       stateCount[0] = 0;\r
       stateCount[1] = 0;\r
       stateCount[2] = 0;\r
@@ -95,7 +92,7 @@ public class FinderPatternFinder {
       stateCount[4] = 0;\r
       int currentState = 0;\r
       for (int j = 0; j < maxJ; j++) {\r
-        if (blackRow.get(j)) {\r
+        if (image.get(j, i)) {\r
           // Black pixel\r
           if ((currentState & 1) == 1) { // Counting white pixels\r
             currentState++;\r
@@ -131,7 +128,7 @@ public class FinderPatternFinder {
                   // Advance to next black pixel\r
                   do {\r
                     j++;\r
-                  } while (j < maxJ && !blackRow.get(j));\r
+                  } while (j < maxJ && !image.get(j, i));\r
                   j--; // back up to that last white pixel\r
                 }\r
                 // Clear state to start looking again\r
@@ -231,22 +228,22 @@ public class FinderPatternFinder {
    * @return vertical center of finder pattern, or {@link Float#NaN} if not found\r
    */\r
   private float crossCheckVertical(int startI, int centerJ, int maxCount,\r
-      int originalStateCountTotal) throws ReaderException {\r
-    BinaryBitmap image = this.image;\r
+      int originalStateCountTotal) {\r
+    BitMatrix image = this.image;\r
 \r
     int maxI = image.getHeight();\r
     int[] stateCount = getCrossCheckStateCount();\r
 \r
     // Start counting up from center\r
     int i = startI;\r
-    while (i >= 0 && image.isBlack(centerJ, i)) {\r
+    while (i >= 0 && image.get(centerJ, i)) {\r
       stateCount[2]++;\r
       i--;\r
     }\r
     if (i < 0) {\r
       return Float.NaN;\r
     }\r
-    while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[1] <= maxCount) {\r
+    while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) {\r
       stateCount[1]++;\r
       i--;\r
     }\r
@@ -254,7 +251,7 @@ public class FinderPatternFinder {
     if (i < 0 || stateCount[1] > maxCount) {\r
       return Float.NaN;\r
     }\r
-    while (i >= 0 && image.isBlack(centerJ, i) && stateCount[0] <= maxCount) {\r
+    while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) {\r
       stateCount[0]++;\r
       i--;\r
     }\r
@@ -264,21 +261,21 @@ public class FinderPatternFinder {
 \r
     // Now also count down from center\r
     i = startI + 1;\r
-    while (i < maxI && image.isBlack(centerJ, i)) {\r
+    while (i < maxI && image.get(centerJ, i)) {\r
       stateCount[2]++;\r
       i++;\r
     }\r
     if (i == maxI) {\r
       return Float.NaN;\r
     }\r
-    while (i < maxI && !image.isBlack(centerJ, i) && stateCount[3] < maxCount) {\r
+    while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) {\r
       stateCount[3]++;\r
       i++;\r
     }\r
     if (i == maxI || stateCount[3] >= maxCount) {\r
       return Float.NaN;\r
     }\r
-    while (i < maxI && image.isBlack(centerJ, i) && stateCount[4] < maxCount) {\r
+    while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) {\r
       stateCount[4]++;\r
       i++;\r
     }\r
@@ -303,28 +300,28 @@ public class FinderPatternFinder {
    * check a vertical cross check and locate the real center of the alignment pattern.</p>\r
    */\r
   private float crossCheckHorizontal(int startJ, int centerI, int maxCount,\r
-      int originalStateCountTotal) throws ReaderException {\r
-    BinaryBitmap image = this.image;\r
+      int originalStateCountTotal) {\r
+    BitMatrix image = this.image;\r
 \r
     int maxJ = image.getWidth();\r
     int[] stateCount = getCrossCheckStateCount();\r
 \r
     int j = startJ;\r
-    while (j >= 0 && image.isBlack(j, centerI)) {\r
+    while (j >= 0 && image.get(j, centerI)) {\r
       stateCount[2]++;\r
       j--;\r
     }\r
     if (j < 0) {\r
       return Float.NaN;\r
     }\r
-    while (j >= 0 && !image.isBlack(j, centerI) && stateCount[1] <= maxCount) {\r
+    while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) {\r
       stateCount[1]++;\r
       j--;\r
     }\r
     if (j < 0 || stateCount[1] > maxCount) {\r
       return Float.NaN;\r
     }\r
-    while (j >= 0 && image.isBlack(j, centerI) && stateCount[0] <= maxCount) {\r
+    while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) {\r
       stateCount[0]++;\r
       j--;\r
     }\r
@@ -333,21 +330,21 @@ public class FinderPatternFinder {
     }\r
 \r
     j = startJ + 1;\r
-    while (j < maxJ && image.isBlack(j, centerI)) {\r
+    while (j < maxJ && image.get(j, centerI)) {\r
       stateCount[2]++;\r
       j++;\r
     }\r
     if (j == maxJ) {\r
       return Float.NaN;\r
     }\r
-    while (j < maxJ && !image.isBlack(j, centerI) && stateCount[3] < maxCount) {\r
+    while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) {\r
       stateCount[3]++;\r
       j++;\r
     }\r
     if (j == maxJ || stateCount[3] >= maxCount) {\r
       return Float.NaN;\r
     }\r
-    while (j < maxJ && image.isBlack(j, centerI) && stateCount[4] < maxCount) {\r
+    while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) {\r
       stateCount[4]++;\r
       j++;\r
     }\r
@@ -466,7 +463,7 @@ public class FinderPatternFinder {
     // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"\r
     // and that we need to keep looking. We detect this by asking if the estimated module sizes\r
     // vary too much. We arbitrarily say that when the total deviation from average exceeds\r
-    // 15% of the total module size estimates, it's too much.\r
+    // 5% of the total module size estimates, it's too much.\r
     float average = totalModuleSize / (float) max;\r
     float totalDeviation = 0.0f;\r
     for (int i = 0; i < max; i++) {\r
@@ -483,33 +480,34 @@ public class FinderPatternFinder {
    * @throws ReaderException if 3 such finder patterns do not exist\r
    */\r
   private FinderPattern[] selectBestPatterns() throws ReaderException {\r
-    Collections.insertionSort(possibleCenters, new CenterComparator());\r
-    int size = 0;\r
-    int max = possibleCenters.size();\r
-    while (size < max) {\r
-      if (((FinderPattern) possibleCenters.elementAt(size)).getCount() < CENTER_QUORUM) {\r
-        break;\r
-      }\r
-      size++;\r
-    }\r
 \r
-    if (size < 3) {\r
+    int startSize = possibleCenters.size();\r
+    if (startSize < 3) {\r
       // Couldn't find enough finder patterns\r
       throw ReaderException.getInstance();\r
     }\r
 \r
-    if (size > 3) {\r
-      // Throw away all but those first size candidate points we found.\r
-      possibleCenters.setSize(size);\r
-      //  We need to pick the best three. Find the most\r
-      // popular ones whose module size is nearest the average\r
-      float averageModuleSize = 0.0f;\r
-      for (int i = 0; i < size; i++) {\r
-        averageModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();\r
+    // Filter outlier possibilities whose module size is too different\r
+    if (startSize > 3) {\r
+      // But we can only afford to do so if we have at least 4 possibilities to choose from\r
+      float totalModuleSize = 0.0f;\r
+      for (int i = 0; i < startSize; i++) {\r
+        totalModuleSize += ((FinderPattern) possibleCenters.get(i)).getEstimatedModuleSize();\r
       }\r
-      averageModuleSize /= (float) size;\r
-      // We don't have java.util.Collections in J2ME\r
-      Collections.insertionSort(possibleCenters, new ClosestToAverageComparator(averageModuleSize));\r
+      float average = totalModuleSize / (float) startSize;\r
+      for (int i = 0; i < possibleCenters.size() && possibleCenters.size() > 3; i++) {\r
+        FinderPattern pattern = (FinderPattern) possibleCenters.get(i);\r
+        if (Math.abs(pattern.getEstimatedModuleSize() - average) > 0.2f * average) {\r
+          possibleCenters.remove(i);\r
+          i--;\r
+        }\r
+      }\r
+    }\r
+\r
+    if (possibleCenters.size() > 3) {\r
+      // Throw away all but those first size candidate points we found.\r
+      Collections.insertionSort(possibleCenters, new CenterComparator());      \r
+      possibleCenters.setSize(3);\r
     }\r
 \r
     return new FinderPattern[]{\r
@@ -528,22 +526,4 @@ public class FinderPatternFinder {
     }\r
   }\r
 \r
-  /**\r
-   * <p>Orders by variance from average module size, ascending.</p>\r
-   */\r
-  private static class ClosestToAverageComparator implements Comparator {\r
-    private final float averageModuleSize;\r
-\r
-    private ClosestToAverageComparator(float averageModuleSize) {\r
-      this.averageModuleSize = averageModuleSize;\r
-    }\r
-\r
-    public int compare(Object center1, Object center2) {\r
-      return Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - averageModuleSize) <\r
-          Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - averageModuleSize) ?\r
-          -1 :\r
-          1;\r
-    }\r
-  }\r
-\r
 }\r