前言
在日常生活中,经常会看到条形码的应用,比如超市买东西的生活,图书馆借书的时候。那么这些东西是如何做到准确检测出条形码的位置呢?本篇博文的目标是演示使用计算机视觉和图像处理技术实现条形码的检测。
通过本篇文章的学习,我们能学到的内容包括:
图像处理中常用的一些操作流程,包括滤波、阈值化处理、膨胀、腐蚀和轮廓查找等;
更重要的一点,希望通过这个案例,能够帮助大家建立分析问题和处理问题的思路。
需要注意的是,这个算法并不适用于所有的条形码,但是它应该能给你一个基本的直觉,告诉你应该应用什么类型的技术。
一、条形码的检测
1.1 目标:找到条形码的位置,而去除掉干扰的因素。
1.2 思路:利用条形码的自身特点,一般都是矩形形状,而且条码的条带是黑色的,矩形区域是白色的。
1.3 代码
step 1: 得到只剩下高水平梯度和低垂直梯度的图像区域。
import argparse import imutils import cv2 # 构造参数解析并分析参数 ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to the image file") args = vars(ap.parse_args()) image = cv2.imread(args["image"]) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 计算图片 x 和 y 方向的 Scharr 梯度大小 # 使用 Scharr 操作符(这里制定ksize=1)去构造图片在水平和垂直方向上的梯度幅值表示。 ddepth = cv2.cv.CV_32F if imutils.is_cv2() else cv2.CV_32F gradX = cv2.Sobel(gray, ddepth=ddepth, dx=1, dy=0, ksize=-1) gradY = cv2.Sobel(gray, ddepth=ddepth, dx=0, dy=1, ksize=-1) # 用 x 方向的梯度减去 y 方向的梯度 -> 得到只剩下了高水平梯度和低垂直梯度的图像区域。 gradient = cv2.subtract(gradX, gradY) gradient = cv2.convertScaleAbs(gradient) cv2.imwrite('./test_img/gradient.jpg', gradient)
step 2: 如何过滤掉图片中的噪声,重点关注条形码区域。
# 对图片进行模糊和阈值化操作 # 使用一个卷积核大小为 9x9 的均值滤波作用于梯度图片。对图片进行这个操作将有助于平滑图片中的高频噪声。 # 然后将模糊化后的图片进行阈值化, blurred = cv2.blur(gradient, (9, 9)) cv2.imwrite('./test_img/blurred.jpg', blurred) (_, thresh) = cv2.threshold(blurred, 200, 255, cv2.THRESH_BINARY) # thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 5, 3) cv2.imwrite('./test_img/thresh.jpg', thresh) # 进行阈值化操作,更加容易的检测出条形码的“斑点”状区域 # 核的宽度大于高度,因此允许我们缩小条形码垂直条带之间的间隙 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (21, 7)) closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) cv2.imwrite('./test_img/closed.jpg', closed)
step 3: 进行腐蚀与膨胀操作,腐蚀操作将会“腐蚀”掉图片中的白色像素点,因此将会清除这些小的斑点,而膨胀操作将会“扩张”剩余的白色像素,并使白色区域变长。如果在腐蚀过程中去除了小的斑点,则在膨胀的过程中不会再次出现。在一系列的腐蚀和膨胀操作之后,这些小斑点已经被成功的移除了,只剩下条形码的区域。
step 4: 最后寻找一下图片中条形码的区域的轮廓。
# 找到阈值化后图片中的轮廓,然后进行根据区域进行排序,仅保留最大区域 cnts = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) c = sorted(cnts, key=cv2.contourArea, reverse=True)[0] # 计算最大轮廓的旋转边界框 rect = cv2.minAreaRect(c) box = cv2.cv.BoxPoints(rect) if imutils.is_cv2() else cv2.boxPoints(rect) box = np.int0(box) # 在检测到的条形码周围绘制边界框并显示图片 cv2.drawContours(image, [box], -1, (0, 255, 0), 3) cv2.imwrite('./test_img/bar_det.jpg', image)
二、条形码的识别
识别图片中的条码(pyzbar)及条码图片矫正和增强。
step 1: 导入所需要的包
2.1 正常角度
2.2 180度旋转
2.3 45度旋转
def barcode(gray): texts = pyzbar.decode(gray) if not texts: angle = barcode_angle(gray) if angle < -45: angle = -90 - angle texts = bar(gray, angle) if not texts: gray = np.uint8(np.clip((1.1 * gray + 10), 0, 255)) angle = barcode_angle(gray) if angle < -45: angle = -90 - angle texts = bar(gray, angle) return texts def bar(image, angle): gray = image bar = rotate_bound(gray, 0 - angle) roi = cv2.cvtColor(bar, cv2.COLOR_BGR2RGB) texts = pyzbar.decode(roi) return texts def barcode_angle(image): gray = image ret, binary = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV) kernel = np.ones((8, 8), np.uint8) dilation = cv2.dilate(binary, kernel, iterations=1) erosion = cv2.erode(dilation, kernel, iterations=1) erosion = cv2.erode(erosion, kernel, iterations=1) erosion = cv2.erode(erosion, kernel, iterations=1) contours, hierarchy = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) if len(contours) == 0: rect = [0, 0, 0] else: rect = cv2.minAreaRect(contours[0]) return rect[2] def rotate_bound(image, angle): (h, w) = image.shape[:2] (cX, cY) = (w // 2, h // 2) M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0) cos = np.abs(M[0, 0]) sin = np.abs(M[0, 1]) nW = int((h * sin) + (w * cos)) nH = int((h * cos) + (w * sin)) M[0, 2] += (nW / 2) - cX M[1, 2] += (nH / 2) - cY return cv2.warpAffine(image, M, (nW, nH)) image = cv2.imread("./test_img/test03.png") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) texts = barcode(gray) print(texts) if not texts: print("未识别成功") else: tt = '' for text in texts: tt = text.data.decode("utf-8") print("识别成功") print(tt)
编辑:黄飞
评论
查看更多