我认为我找到了一种简单而可靠的方法来检测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))
- 阈值化。 我们可以利用条形码通常是黑色的白色表面这个事实。对比度越高越好。
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
3. 膨胀+轮廓。这一步有点棘手,如果我的英语表述不太清楚,我表示歉意。我们可以从前面的图像中看到,在QR码的白色内部之间有黑色空隙。如果我们只是找到轮廓,那么opencv将会认为这些空隙是独立的实体而不是整体的一部分。如果我们想要将QR码转换成一个看起来像一个白色正方形,我们需要进行一些形态学操作。即,我们必须对图像进行膨胀处理。
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)
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)
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
对象。如果您不介意拥有这些信息,那么您可以像我这里一样使用一个集合。希望这对您有所帮助 :)。
e = a/(w*h)
,这不是代表轮廓形状,而是代表填充百分比吗? - Jens Ehrich