Improve FinderPatternFinder.selectBestPatterns (#1158)

* rewrite FinderPatternFinder.selectBestPatterns
This commit is contained in:
MakKi (makki_d) 2019-04-18 22:42:52 +09:00 committed by Sean Owen
parent 0e8c46e262
commit 653ac2a3be
3 changed files with 56 additions and 67 deletions

View file

@ -23,6 +23,7 @@ Chang Hyun Park
Christian Brunschen (Google) Christian Brunschen (Google)
Christoph Schulz (creatale GmbH) Christoph Schulz (creatale GmbH)
crowdin.net crowdin.net
Daisuke Makiuchi
Daniel Switkin (Google) Daniel Switkin (Google)
Dave MacLachlan (Google) Dave MacLachlan (Google)
David Phillip Oster (Google) David Phillip Oster (Google)

View file

@ -24,6 +24,7 @@ import com.google.zxing.common.BitMatrix;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -40,6 +41,7 @@ import java.util.Map;
public class FinderPatternFinder { public class FinderPatternFinder {
private static final int CENTER_QUORUM = 2; private static final int CENTER_QUORUM = 2;
private static final EstimatedModuleComparator moduleComparator = new EstimatedModuleComparator();
protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients protected static final int MAX_MODULES = 97; // support up to version 20 for mobile clients
@ -590,10 +592,18 @@ public class FinderPatternFinder {
return totalDeviation <= 0.05f * totalModuleSize; return totalDeviation <= 0.05f * totalModuleSize;
} }
/**
* Get square of distance between a and b.
*/
private static double squaredDistance(FinderPattern a, FinderPattern b) {
double x = a.getX() - b.getX();
double y = a.getY() - b.getY();
return x * x + y * y;
}
/** /**
* @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are * @return 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 * those have similar module size and form a shape closer to a isosceles right triangle.
* size differs from the average among those patterns the least
* @throws NotFoundException if 3 such finder patterns do not exist * @throws NotFoundException if 3 such finder patterns do not exist
*/ */
private FinderPattern[] selectBestPatterns() throws NotFoundException { private FinderPattern[] selectBestPatterns() throws NotFoundException {
@ -604,85 +614,63 @@ public class FinderPatternFinder {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
// Filter outlier possibilities whose module size is too different Collections.sort(possibleCenters, moduleComparator);
if (startSize > 3) {
// But we can only afford to do so if we have at least 4 possibilities to choose from double distortion = Double.MAX_VALUE;
double totalModuleSize = 0.0; double[] squares = new double[3];
double square = 0.0; FinderPattern[] bestPatterns = new FinderPattern[3];
for (FinderPattern center : possibleCenters) {
float size = center.getEstimatedModuleSize(); for (int i = 0; i < possibleCenters.size() - 2; i++) {
totalModuleSize += size; FinderPattern fpi = possibleCenters.get(i);
square += (double) size * size; float minModuleSize = fpi.getEstimatedModuleSize();
for (int j = i + 1; j < possibleCenters.size() - 1; j++) {
FinderPattern fpj = possibleCenters.get(j);
double squares0 = squaredDistance(fpi, fpj);
for (int k = j + 1; k < possibleCenters.size(); k++) {
FinderPattern fpk = possibleCenters.get(k);
float maxModuleSize = fpk.getEstimatedModuleSize();
if (maxModuleSize > minModuleSize * 1.4f) {
// module size is not similar
continue;
} }
double average = totalModuleSize / startSize;
float stdDev = (float) Math.sqrt(square / startSize - average * average);
Collections.sort(possibleCenters, new FurthestFromAverageComparator((float) average)); squares[0] = squares0;
squares[1] = squaredDistance(fpj, fpk);
squares[2] = squaredDistance(fpi, fpk);
Arrays.sort(squares);
float limit = Math.max(0.2f * (float) average, stdDev); // 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,
for (int i = 0; i < possibleCenters.size() && possibleCenters.size() > 3; i++) { // we need to check both two equal sides separately.
FinderPattern pattern = possibleCenters.get(i); // The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity
if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) { // from isosceles right triangle.
possibleCenters.remove(i); double d = Math.abs(squares[2] - 2 * squares[1]) + Math.abs(squares[2] - 2 * squares[0]);
i--; if (d < distortion) {
distortion = d;
bestPatterns[0] = fpi;
bestPatterns[1] = fpj;
bestPatterns[2] = fpk;
}
} }
} }
} }
if (possibleCenters.size() > 3) { if (distortion == Double.MAX_VALUE) {
// Throw away all but those first size candidate points we found. throw NotFoundException.getNotFoundInstance();
float totalModuleSize = 0.0f;
for (FinderPattern possibleCenter : possibleCenters) {
totalModuleSize += possibleCenter.getEstimatedModuleSize();
} }
float average = totalModuleSize / possibleCenters.size(); return bestPatterns;
Collections.sort(possibleCenters, new CenterComparator(average));
possibleCenters.subList(3, possibleCenters.size()).clear();
}
return new FinderPattern[]{
possibleCenters.get(0),
possibleCenters.get(1),
possibleCenters.get(2)
};
} }
/** /**
* <p>Orders by furthest from average</p> * <p>Orders by {@link FinderPatternFinder#getEstimatedModuleSize()}</p>
*/ */
private static final class FurthestFromAverageComparator implements Comparator<FinderPattern>, Serializable { private static final class EstimatedModuleComparator implements Comparator<FinderPattern>, Serializable {
private final float average;
private FurthestFromAverageComparator(float f) {
average = f;
}
@Override @Override
public int compare(FinderPattern center1, FinderPattern center2) { public int compare(FinderPattern center1, FinderPattern center2) {
return Float.compare(Math.abs(center2.getEstimatedModuleSize() - average), return Float.compare(center1.getEstimatedModuleSize(), center2.getEstimatedModuleSize());
Math.abs(center1.getEstimatedModuleSize() - average));
}
}
/**
* <p>Orders by {@link FinderPattern#getCount()}, descending.</p>
*/
private static final class CenterComparator implements Comparator<FinderPattern>, Serializable {
private final float average;
private CenterComparator(float f) {
average = f;
}
@Override
public int compare(FinderPattern center1, FinderPattern center2) {
int countCompare = Integer.compare(center2.getCount(), center1.getCount());
if (countCompare == 0) {
return Float.compare(Math.abs(center1.getEstimatedModuleSize() - average),
Math.abs(center2.getEstimatedModuleSize() - average));
}
return countCompare;
} }
} }

View file

@ -28,7 +28,7 @@ public final class QRCodeBlackBox2TestCase extends AbstractBlackBoxTestCase {
public QRCodeBlackBox2TestCase() { public QRCodeBlackBox2TestCase() {
super("src/test/resources/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE); super("src/test/resources/blackbox/qrcode-2", new MultiFormatReader(), BarcodeFormat.QR_CODE);
addTest(31, 31, 0.0f); addTest(31, 31, 0.0f);
addTest(29, 29, 90.0f); addTest(30, 30, 90.0f);
addTest(30, 30, 180.0f); addTest(30, 30, 180.0f);
addTest(30, 30, 270.0f); addTest(30, 30, 270.0f);
} }