zxing-cpp / zxing-cpp

C++ port of ZXing
Apache License 2.0
1.22k stars 390 forks source link

Unable to detect "perfect" micro-QR codes with particular data payloads #578

Closed simon-staal closed 1 year ago

simon-staal commented 1 year ago

I'm using the python wrapper of this library to test microQR code detection capabilities. I'm generating version M4 micro-QR codes specifically using the segno package as follows:

import cv2
import segno
import zxingcpp
from time import time

TEST_FILE = 'qr_micro_test.png'
START = 0
ATTEMPTS = 1000

total = 0
bad_codes = []

for i in range(START, START+ATTEMPTS):
    x = i
    qrcode = segno.make(x, version='M4')
    assert qrcode.error == 'Q'
    qrcode.save(TEST_FILE, scale = 5)

    img = cv2.imread(TEST_FILE)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = cv2.resize(img, (88, 88))

    start = time()
    barcodes = zxingcpp.read_barcodes(img)
    end = time()
    if len(barcodes) == 0:
        print(f"Unable to detect {x}")
        bad_codes.append(x)
    else:
        assert len(barcodes) == 1, f"Detected {len(barcodes)} qr codes (input = {x})"
        assert barcodes[0].text == str(x), f"Mismatch between detected value {barcodes[0].text} and input {str(x)}"
    total += (end - start) * 1000

print(f'Took an average of {(total / ATTEMPTS):.2f}ms for {ATTEMPTS} attempts')
print(f"Unable to detect the following codes:\n{bad_codes}")

Of the 1000 codes tested by this loop, the following 84 cannot be detected:

[32, 38, 39, 69, 70, 94, 125, 127, 140, 158, 164, 182, 190, 213, 237, 244, 261, 278, 283, 290, 296, 308, 327, 342, 364, 366, 374, 411, 414, 423, 429, 438, 443, 446, 452, 457, 463, 490, 505, 510, 512, 525, 530, 533, 534, 536, 562, 573, 588, 597, 606, 621, 632, 636, 642, 644, 660, 705, 726, 759, 792, 797, 798, 825, 827, 838, 856, 857, 867, 868, 870, 890, 906, 910, 914, 919, 925, 930, 946, 955, 968, 974, 991, 992]

I've tested these codes with BoofCV's micro-QR code detector, which is able to detect these correctly.

My venv is as follows - note that not all packages are needed, I've been testing many qr code detection libraries:

contourpy==1.0.7
cycler==0.11.0
fonttools==4.39.4
joblib==1.2.0
kiwisolver==1.4.4
matplotlib==3.7.1
numpy==1.24.3
opencv-python==4.7.0.72
packaging==23.1
Pillow==9.5.0
py4j==0.10.9.7
PyBoof==0.43.1
pyparsing==3.0.9
python-dateutil==2.8.2
pyzbar==0.1.9
pyzxing==1.0.2
segno==1.5.2
six==1.16.0
transforms3d==0.4.1
zxing-cpp==2.0.0
axxel commented 1 year ago

Could you please save one of the images that don't work as a png and add it here?

simon-staal commented 1 year ago

32 Here's the image for the payload 32

axxel commented 1 year ago

Thanks for providing the image. The problem is, the image is in fact not "perfect" and that is due to the resizing to 88x88. Is there a specific reason why you did this?

There are a number of ways to make it work: 1) set the is_pure parameter to true 2) set the binarizer parameter to Binarizer.FixedThreshold 3) resize your image to a multiple of 21 (like 84x84 instead of 88x88)

I might push a fix that works for this image, probably others as well but not necessarily for all your samples.

simon-staal commented 1 year ago

To provide a bit more context on my use-case, I'm developing an intelligent scrabble board which is using embedded cameras with QR codes under the tiles to detect the position and values of the tiles playerd. The reason why I resize the images to 88x88 is that this system, after preprocessing, returns 88x88 binarized images of each board square, which is then passed into the QR code detector.

These will obviously be noisier than the "perfect" QR codes which I'm testing on, which is why I'm expecting the performance on this initial testbench to provide an upper bound. What are the significance of these parameters you've mentioned above / where can I find the documentation for these? I'm assuming the binarizer parameter can always be set to Binarizer.FixedThreshold given then I'll only be using black and white images, but I'm not sure when the use of is_pure would be appropriate.

As a bit of an update, I've tested all codes in the $[0, 10^6]$ range - currently obtaining 11420 failures.

axxel commented 1 year ago

Thanks for the background info. In that case Binarizer.GlobalHistogram would likely be the best option. The FixedThreshold binarizer makes a simple cut at 128. the is_pure mode is not suitable for your real-world image data. It is meant for situations where you scan "perfect" input like this, where you have generated images with no noise, white background and no rotation/skew/etc.

I suggest two things to move forward in your case: 1) maybe switch to DataMatrix, which has an even higher density and is not as sensitive in low-res situations as the MircoQR detector 2) actually get some real images from your hardware and see where that fails. the issues you have here don't let you predict what you'll get with your actual input at the end.

simon-staal commented 1 year ago

Thanks for the advice! I have just tried using Binarizer.GlobalHistogram and this seems to completely solve the issue for the generated images, it's now able to detect all of themI'll definitely try out some more testing with real images, and if necessary try switching to DataMatrix.