mirror of
https://github.com/zxing/zxing.git
synced 2024-11-09 20:44:03 -08:00
Improve FinderPatternFinder.selectBestPatterns (#1158)
* rewrite FinderPatternFinder.selectBestPatterns
This commit is contained in:
parent
0e8c46e262
commit
653ac2a3be
1
AUTHORS
1
AUTHORS
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue