如何在大图像中定位QR码以提高解码性能?

15

背景

我需要在树莓派上从一张大图(2500x2000)中检测和解码一个比较小的二维码(110x110像素)。二维码可能出现在任何位置,但是预期方向是正常的,即朝上。我们使用高品质的工业相机和镜头,所以图片通常是高质量且清晰的。

目前,当我使用一个窗口大小为约600x500的区域来裁剪图像并尝试解码时,可以可靠地使用pyzbar进行检测和解码。如果我尝试解码整个图像,则无法检测/解码该符号。

我的尝试

我编写了一个循环,滑动一个裁剪窗口到图像上,并尝试单独解码每个裁剪帧。我每次迭代都将窗口移动50%,以确保不会错过窗口边缘上的任何符号。

我还尝试使用OpenCV进行检测/解码,但性能与pyzbar相比没有更好。

我的解决方案存在的问题

影响我当前项目的问题:

滑动窗口方法很难调整,效率低下,因为:

  1. 它导致整个区域被分析了近4次;这是移动窗口50%的副作用,
  2. 最可靠的窗口大小往往很小,并且需要多次迭代,
  3. 由于二维码相对于相机的距离不同,所以符号大小可能会变化。

可能会影响其他项目的问题,如果我使用这种方法:

  1. 滑动窗口可能会捕捉到同一个符号多次,使得难以确定该符号是否出现了多次。

问题

如何找到 QR 代码的大致位置,以便相应地裁剪图像?

我对任何能够提高检测/解码性能的解决方案都感兴趣,但更喜欢使用机器学习技术(我是机器学习新手但愿意学习),使用 OpenCV 图像预处理或改进基本裁剪算法的方案。

样例图片

这是我用于测试的样例图像之一。它特意采用较差的照明质量以近似最坏情况,但在裁剪后仍能正确检测和解码每个代码。

QR Code Test Image 001

3个回答

20
我认为我找到了一种简单而可靠的方法来检测QR码的角。然而,我的方法假设QR码与其周围区域之间有一定对比度(越多越好)。此外,我们必须牢记pyzbaropencv.QRCodeDetector都不是100%可靠的。
因此,这是我的方法:
1. 调整图像大小。经过一些实验后,我得出结论pyzbar并不完全具有尺度不变性。虽然我没有可以支持这一说法的参考资料,但我仍然将使用小到中等大小的图像进行条形码检测作为经验法则。您可以跳过此步骤,因为它可能完全是任意的。
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. 阈值化。 我们可以利用条形码通常是黑色的白色表面这个事实。对比度越高越好。
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

掩蔽后的图像 3. 膨胀+轮廓。这一步有点棘手,如果我的英语表述不太清楚,我表示歉意。我们可以从前面的图像中看到,在QR码的白色内部之间有黑色空隙。如果我们只是找到轮廓,那么opencv将会认为这些空隙是独立的实体而不是整体的一部分。如果我们想要将QR码转换成一个看起来像一个白色正方形,我们需要进行一些形态学操作。即,我们必须对图像进行膨胀处理。

# 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)

膨胀后的阈值 4. 过滤和获取边界框。 大部分找到的轮廓太小,无法包含条形码,因此我们必须对它们进行过滤,以缩小搜索空间。在过滤掉弱候选者之后,我们可以获取强候选者的边界框。

编辑: 在这种情况下,我们通过面积(小面积=弱候选者)进行过滤,但我们也可以按检测的程度进行过滤。基本上,程度衡量的是对象的矩形性,我们可以使用这些信息,因为我们知道QR码是一个正方形。我选择程度大于pi / 4,因为那是一个完美圆的程度,这意味着我们也在过滤掉圆形物体。

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))

搜索空间 5. 检测条形码。 我们已经将搜索空间缩小到实际的QR码!现在我们终于可以使用pyzbar而不用太担心它花费太长时间来进行条形码检测。

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))

很遗憾,pyzbar 只能解码最大的QR码 (b'3280406-001') 的信息,尽管两个条形码都在搜索空间中。关于知道特定代码被检测了多少次,您可以使用 collections 标准模块中的 Counter 对象。如果您不介意拥有这些信息,那么您可以像我这里一样使用一个集合。希望这对您有所帮助 :)。

1
这对我来说已经运作得相当不错了,但仍需要一些微调。我的条形码始终会在白纸上 - 是否可以利用这个事实来改善候选区域的检测? - Jens Ehrich
1
这种方法依赖于QR码位于白色表面上(对比度越高越好),所以它已经包含了这些信息。如果你想获得更准确的候选项,我有一个想法,让我稍微试验一下,然后我会编辑我的答案。 - Sebastian Liendo
1
完成了!额外的过滤参数似乎完美地发挥了作用。请注意,您需要稍微调整额外的阈值参数,以使其符合您的需求。 - Sebastian Liendo
根据您计算的 e = a/(w*h),这不是代表轮廓形状,而是代表填充百分比吗? - Jens Ehrich
是的,程度计算填充百分比,相对于包围边界框。因此,上限为1(表示完美的矩形形状),其他轮廓程度必然小于该值。您确实可以将轮廓近似为多边形并测量其正方形度,但我认为这有点过头了。如果程度对您不起作用,您可以尝试降低阈值。或者,您可以增加图像和内核大小。 - Sebastian Liendo
显示剩余3条评论

0

这个解决方案相当低效,因为它更倾向于蛮力方法,但是应该可以工作,因为:图像大小为2000x2000,检测阈值约为500x500,条码大小为110x110。

理论上,这比滑动窗口法更好,但可能差别不大,因为它更多地是一种变化。

这个想法是,如果我们不能可靠地在图像中找到条形码,但我们可以在图像的子部分中找到它,那么我们可以尝试将图像分成子部分,其中一个将包含图像。由于我们不能确定条形码是否会在拆分成部分时被拆分,因此我们必须仔细确保它将在可能的部分之一中。

首先,将图像分成500x500的网格(4x4),并在每个网格上运行pyzbar。理论上,条形码要么在这16个网格中的一个内,要么被其中一个网格所划分。

如果找不到条形码,则在 x 轴上将网格偏移 250。再次运行,然后尝试在 y 轴上将网格偏移 250,再次运行,然后尝试在 x 和 y 轴上都偏移 250 并再次运行。理论上,这应该确保条形码存在于某个 500x500 或更小的网格中。如果这样仍然找不到,请建议使用 250x250 的网格(当然需要 4 倍的时间才能运行),因为 250 仍然比条形码大小大两倍以上。

此外,这里提供的其他建议可用于缩小搜索区域并丢弃绝对不可能包含条形码的部分或集中于可能包含条形码的部分。Sebastian 的答案可能非常适用于确定要集中关注的网格。

另一种选择可能是考虑条形码最有可能出现的位置,我预计条形码会更靠近屏幕中心。因此,沿着内向外的螺旋搜索模式可能会有所帮助。


我考虑过这种方法,但由于二维码可以出现在图像的任何位置,所以没有最佳的起始位置。而且,由于我想捕获图像中的所有二维码,无论如何都必须运行每个级别的增量缩放方法吗? - Jens Ehrich
如果两个QR码在同一个网格单元格内,你可能会遇到问题。 - Nuclearman

0
在每个QR码中,都有3个方形/矩形在角落处。如果您可以使用openCV找到这些部分,则可以裁剪QR码区域。
此外,我发现this

我已经尝试过OpenCV条形码检测器 - 其性能并不比pyzbar好。你能展示如何可靠地检测QR代码的角落吗? - Jens Ehrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接