【问题标题】:How to locate QR code in large image to improve decoding performance?如何在大图中定位二维码以提高解码性能?
【发布时间】:2020-11-21 12:30:51
【问题描述】:

背景

我需要在 Raspberry Pi 上检测和解码大图像 (2500x2000) 中相对较小的 QR 码(110x110 像素)。二维码可以在框架中的任何位置,但方向应该是正常的,即充值。我们使用的是高质量的工业相机和镜头,因此图像通常质量良好且清晰。

目前,当我使用 aprox 600x500 的窗口裁剪 QR 码周围的图像时,我能够使用 pyzbar 可靠地检测和解码图像。如果我尝试解码完整图像,则不会检测/解码该符号。

我所尝试的

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

我也尝试过使用 OpenCV 进行检测/解码,但性能并不比 pyzbar 好

我的解决方案存在问题

影响我当前项目的问题:

滑动窗口方式难以调优,效率低且b/c慢:

  1. 它使整个区域被分析了近 4 次;将窗口移动 50% 的副作用,
  2. 最可靠的窗口尺寸往往很小并且需要多次迭代,
  3. 符号大小可能会因距离相机更近/更远而有所不同。

可能会影响我将使用此方法的其他项目的问题:

  1. 滑动窗口可能会多次捕捉到一个符号,因此很难确定该符号是否多次出现。

问题

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

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

示例图片

这是我用于测试的示例图片之一。故意降低照明质量来近似最坏的情况,但是在裁剪时各个代码仍然可以正确检测和解码。

【问题讨论】:

    标签: python image-processing computer-vision qr-code


    【解决方案1】:

    我想我找到了一种简单而可靠的方法,可以检测 QR 码的角落。但是,我的方法假设 QR 与其周围区域之间存在一些对比(越多越好)。另外,我们必须记住,pyzbar 和 opencv.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. 扩张+轮廓。这一步有点棘手,如果我的英语在这里不完全清楚,我深表歉意。从上图中我们可以看到,二维码内部的白色之间有黑色空间。如果我们只是找到轮廓,那么 opencv 将假设这些空间是独立的实体,而不是整体的一部分。如果我们想要转换 QR 码并使其看起来只是一个白色方块,我们必须做一点morphological operations。也就是说,我们必须扩大图像。

    # 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. 过滤和获取边界框。 找到的大多数轮廓都太小而无法包含条形码,因此我们必须对其进行过滤以使我们的搜索空间更小。过滤掉弱候选后,我们可以获取强候选的边界框。

    编辑:在这种情况下,我们按区域过滤(小区域 = 弱候选),但我们也可以按检测的extent 过滤。基本上,范围测量的是对象的矩形,我们可以使用该信息,因为我们知道 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. 检测条形码。我们已将搜索空间缩小为实际的二维码!现在终于可以使用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 对象。如果您不介意拥有这些信息,那么您可以像我在这里所做的那样使用一组。

    希望这会有所帮助:)。

    【讨论】:

    • 这对我来说工作得相当好,但仍然可以使用一些微调。我的条形码将始终在一张白纸上 - 是否可以利用这一事实来改进对候选区域的检测?
    • 此方法依赖于 QR 将位于白色表面上的事实(对比度越高越好),所以是的,它已经包含了该信息。如果您想获得更准确的候选人,我有一个想法,让我尝试一下,然后我会编辑我的答案。
    • 完成!似乎额外的过滤参数工作得很好。请注意,您必须使用额外的阈值参数来满足您的需求。
    • 我喜欢这个更新,但是将所有代码剪切并粘贴到 Colab 笔记本中会给我 125 个轮廓,其中没有一个符合 extent > np.pi / 4 的条件,但是有 7 个符合 area > 100。我的阈值图像看起来和你的一样……有什么想法吗?
    • 根据您对e = a/(w*h) 的计算,这不代表填充的百分比,而不是轮廓的形状吗?
    【解决方案2】:

    这个解决方案效率很低,因为它在蛮力方面,但应该可以工作:图像大小为 2000x2000,检测阈值大约为 500x500,条码大小为 110x110。

    理论上它比滑动窗口方法更好,但可能不会好很多,因为它更像是一种变体。

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

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

    如果找不到条形码,则在 x 轴上将网格偏移 250。再次运行它,然后在 y 轴上尝试网格偏移 250,再次运行它,然后在 x 和 y 轴上尝试 250 并再次运行它。理论上,这应该确保条形码存在于一些 500x500 或更小的网格内。如果这不起作用,建议改用 250x250 网格(当然,运行时间会延长 4 倍),因为 250 仍然是条形码大小的两倍多。

    此处提供的其他建议可用于缩小搜索范围并舍弃绝对不能包含条形码的部分或关注可能包含条形码的部分。 Sebastian 的回答可能会很好地确定要关注的网格方块。

    另一种选择可能是考虑条形码最有可能在哪里,我希望条形码更靠近屏幕中心。因此,由内而外的螺旋式搜索模式可能会有所帮助。

    【讨论】:

    • 我考虑过这种方法,但由于二维码可以在任何位置,因此没有最佳起始位置。另外,既然我想捕获图像中的所有代码,我是否不必遍历增量缩放方法的每一级?
    • 如果两个 QR 码在同一个网格单元内,您可能会遇到问题。
    【解决方案3】:

    在每个二维码中,角落处都有 3 个正方形/矩形。如果你能用openCV找到那些部分,就可以裁剪二维码区域。

    我也找到了this

    【讨论】:

    • 我已经尝试过 OpenCV 条码检测器 - 性能并不比 pyzbar 好。你能展示如何可靠地检测二维码角吗?
    猜你喜欢
    • 2022-07-20
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多