Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to locate QR code in large image to improve decoding performance?

Background

I need to detect and decode a relatively small QR code (110x110 pixels) in a large image (2500x2000) on a Raspberry Pi. The QR code can be at any location in the frame, but the orientation is expected to be normal, i.e. top-up. We are using high quality industrial cameras and lenses, so images are generally good quality and in focus.

Currently, I am able to detect and decode the image reliably with pyzbar when I crop the image around the QR code using a window of aprox 600x500. If I attempt to decode the full image, the symbol is not detected/decoded.

What I Have Tried

I have written a loop that slides a crop window over the image, and attempts to decode each cropped frame separately. I move the window by 50% each iteration to ensure I don't miss any symbols at the edge of the window.

I have also tried using OpenCV for detection/decoding but the performance was no better than with pyzbar

Problems With My Solution

Problems which affect my current project:

The sliding window approach is difficult to tune, inefficient and slow b/c:

  1. it causes the entire area to be analyzed nearly 4 times; a side effect of shifting the window by 50%,
  2. the most reliable window sizes tend to be small and require many iterations,
  3. the symbol size may vary due to being closer/further from the camera.

Problems that may affect other projects where I would use this approach:

  1. The sliding window may catch a symbol more than once, making it difficult to determine if the symbol was present more than once.

The Question

How can I find the approximate location of the QR code(s) so I can crop the image accordingly?

I am interested in any solutions to improve the detection/decoding performance, but prefer ones that (a) use machine learning techniques (I'm a ML newbie but willing to learn), (b) use OpenCV image pre-processing or (c) make improvements to my basic cropping algorithm.

Sample Image

Here is one of the sample images that I'm using for testing. It's purposely poor lighting quality to approximate the worst case scenario, however the individual codes still detect and decode correctly when cropped.

QR Code Test Image 001

like image 700
Jens Ehrich Avatar asked Jul 31 '20 16:07

Jens Ehrich


People also ask

Where should QR codes be placed?

You can place QR codes on nearly anything your target audience comes in contact, so get creative. Put one of the back of your cash register, your menus or brochures, t-shirts, business cards, banners, books, magazines, coffee mugs, napkins, posters, social media sites, and any other promotional product.

How do I find the QR Code for a picture?

The Google App (Android and iOS)The Google Lens screen will appear showing the images on your phone. Tap on the image containing the QR code. Wait for Google Lens to scan the QR code, which will direct you to the desired information.

How big should a QR Code be on a screen?

Although there is a minimum size QR Code of 2 x 2 cm (0.8 x 0.8 in), QR Codes be made as large as you need as long as you use high-quality images so that the pixels don't become too blurred.

What is the maximum size of a QR Code?

It is able to do so because a QR code has a maximum symbol size of 177x177 modules. So, it can have as much as 31,329 squares which can encode 3KB of data. That translates to a QR code data size of a total of 7,089 numeric characters or 4,269 alphanumeric ones.

How to detect and decode a QR code in image using OpenCV?

This tutorial provides example how detect and decode a QR code in image using OpenCV. We create an object of class QRCodeDetector. QR code is detected and decoded by using detectAndDecode method. It allows to get decoded data and an array of vertices of the found QR code.

How to detect the QR code of an image in Python?

qrCodeDetector = cv2.QRCodeDetector () Followed by that we will call the detectAndDecode method on this object, passing as input the image where we want to detect the QR Code. This method returns a tuple of values, by the following order: Decoded text [1]. It is an empty string if the QR Code is not found.

Why does it take so long to scan a QR code?

The more algorithms are enabled for barcode recognition, the more time it takes. Now, we upload the perspective distorted QR image to see the difference between the best speed and best coverage.

How to compare the QR code decoding performance based on parameter templates?

We can write a Python program to compare the QR code decoding performance based on different parameter templates: In multiple QR code scenario, we hope to find QR codes as many as possible. Although the l5 template found the most QR codes, the time cost is unbearable.


1 Answers

I think I have found a simple yet reliable way in which the corners of the QR code can be detected. However, my approach assumes there is some contrast (the more the better) between the QR and its surrounding area. Also, we have to keep in mind that neither pyzbar nor opencv.QRCodeDetector are 100% reliable.

So, here is my approach:

  1. Resize image. After some experimentation I have come to the conclusion that pyzbar is not completely scale invariant. Although I don't have references that can back this claim, I still use small to medium images for barcode detection as a rule of thumb. You can skip this step as it might seem completely arbitrary.
image = cv2.imread("image.jpg")
scale = 0.3
width = int(image.shape[1] * scale)
height = int(image.shape[0] * scale)
image = cv2.resize(image, (width, height))
  1. Thresholding. We can take advantage on the fact that barcodes are generally black on white surfaces. The more contrast the better.
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

image after masking 3. Dilation + contours. This step is a little bit trickier and I do apologize if my english is not completely clear here. We can see from the previous image that there are black spaces in between the white inside the QR code. If we were to just find the contours, then opencv will assume these spaces are separate entities and not part of a whole. If we want to transform the QR code and make it seem as just a white square, we have to do a bit of morphological operations. Namely, we have to dilate the image.

# The bigger the kernel, the more the white region increases.
# If the resizing step was ignored, then the kernel will have to be bigger
# than the one given here.
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.dilate(thresh, kernel, iterations=1)
contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

threshold after dilation 4. Filtering and getting bounding boxes. Most of the found contours are too small to contain a barcode, so we have to filter them in order to make our search space smaller. After filtering out the weak candidates, we can fetch the bounding boxes of the strong ones.

EDIT: In this case we are filtering by area (small area = weak candidate), but we can also filter by the extent of the detection. Basically what the extent measures is the rectangularity of an object, and we can use that information since we know a QR code is a square. I chose the extent to be greater than pi / 4, since that is the extent of a perfect circle, meaning we are also filtering out circular objects.

bboxes = []
for cnt in contours:
  area = cv2.contourArea(cnt)
  xmin, ymin, width, height = cv2.boundingRect(cnt)
  extent = area / (width * height)
  
  # filter non-rectangular objects and small objects
  if (extent > np.pi / 4) and (area > 100):
    bboxes.append((xmin, ymin, xmin + width, ymin + height))

Search space 5. Detect barcodes. We have reduced our search space to just the actual QR codes! Now we can finally use pyzbar without worrying too much about it taking too long to do barcode detection.

qrs = []
info = set()
for xmin, ymin, xmax, ymax in bboxes:
  roi = image[ymin:ymax, xmin:xmax]
  detections = pyzbar.decode(roi, symbols=[pyzbar.ZBarSymbol.QRCODE])
  for barcode in detections:
     info.add(barcode.data)
     # bounding box coordinates
     x, y, w, h = barcode.rect
     qrs.append((xmin + x, ymin + y, xmin + x + w, ymin + y + height))

Unfortunately, pyzbar was only able to decode the information of the largest QR code (b'3280406-001'), even though both barcodes were in the search space. With regard to knowing how many times was a particular code detected, you can use a Counter object from the collections standard module. If you don't mind having that information, then you can just use a set as I did here.

Hope this could be of help :).

like image 154
Sebastian Liendo Avatar answered Oct 04 '22 00:10

Sebastian Liendo