我想我找到了一种简单而可靠的方法,可以检测 QR 码的角落。但是,我的方法假设 QR 与其周围区域之间存在一些对比(越多越好)。另外,我们必须记住,pyzbar 和 opencv.QRCodeDetector 都不是 100% 可靠的。
所以,这是我的方法:
-
调整图像大小。 经过一些实验,我得出的结论是
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))
-
阈值化。我们可以利用条形码在白色表面上通常是黑色的这一事实。对比度越高越好。
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 对象。如果您不介意拥有这些信息,那么您可以像我在这里所做的那样使用一组。
希望这会有所帮助:)。