别再用ZBar硬扛了!YOLO+OpenCV搞定工业现场所有歪扭模糊条码

我跟你说,做工业视觉的,谁没被条码识别折磨过?
前两年接了个汽车零部件产线的项目,要求读取工件上的激光打码。一开始我想这还不简单,直接上ZBar,几行代码的事。
结果到现场直接傻眼。
工件是铝合金的,车间灯光一打,反光严重到晃眼睛。条码有的歪了45度,有的沾了点切削液油污,还有的因为激光头老化,线条特别浅,人眼都得凑很近才能看清楚。ZBar的识别率直接干到60%以下,产线停一次就是几万块损失,客户天天半夜给我打电话,我那段时间头发掉了一大把。
调参数调了快一个星期,什么二值化阈值从0调到255,高斯模糊核从3x3试到11x11,对比度亮度拉满,能调的都调了,识别率最多到75%,还是不行。有时候同一个条码,这次能识别,下次就不行,完全看运气。
后来实在没办法,死马当活马医,想着试试YOLO。当时还觉得大材小用,不就是读个条码吗,还用得上目标检测?
结果一试,直接给我整懵了。
歪的、斜的、反光的、模糊的,只要人眼能勉强看出来的,YOLO全能精准定位到条码的精确位置,然后裁剪出来稍微处理一下,ZBar一识别一个准。
那天我在现场直接跳起来了,终于不用再熬夜调那些破参数了。
环境特别简单,就两个库。
pip install opencv-python ultralytics pyzbar
没了。不用装什么乱七八糟的依赖,也不用编译,pip一下完事。
模型我用的是公开训练好的yolov8n-barcode.pt,这个是别人在几十万张工业条码数据集上训出来的,直接拿来用就行,不用自己从零标数据训练。
别想着自己标几百张图微调,太费时间了。工业条码的特征其实非常统一,这个预训练模型足够应付99%的场景了。我当初不信邪,自己标了300张图训了个模型,效果还不如这个现成的好,纯纯浪费时间。
先上最基础的单条码识别代码,跑通再说。
import cv2
from ultralytics import YOLO
from pyzbar.pyzbar import decode
# 加载YOLO条码检测模型
model = YOLO("yolov8n-barcode.pt")
# 读取图片
img = cv2.imread("test_barcode.jpg")
# 检测条码,置信度设0.5,IOU设0.4
results = model(img, conf=0.5, iou=0.4, verbose=False)
# 遍历检测结果
for result in results:
for box in result.boxes:
# 获取条码的bbox坐标
x1, y1, x2, y2 = map(int, box.xyxy[0])
# 裁剪出条码区域
barcode_roi = img[y1:y2, x1:x2]
# 基础预处理
gray = cv2.cvtColor(barcode_roi, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3, 3), 0)
_, binary = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 识别条码
barcodes = decode(binary)
for barcode in barcodes:
barcode_data = barcode.data.decode("utf-8")
print(f"识别到条码:{barcode_data}")
# 在原图上绘制结果
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(img, barcode_data, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
cv2.imshow("Result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
这个代码跑起来没问题,普通办公场景下识别率接近100%。
但是到了工业现场,还是会出问题。
最致命的就是条码倾斜。
很多产线上的工件不是正的,传送带送过来的时候歪个几十度太正常了。这时候裁剪出来的条码是斜的,ZBar根本识别不出来。
我当初在这里卡了整整一天。
一开始我以为是预处理的问题,调了各种模糊核、二值化方法,甚至把ZBar的所有参数都翻了一遍,没用。后来才发现,ZBar对条码的角度特别敏感,超过15度基本就废了,超过30度直接瞎了。
这时候就必须做角度矫正。
角度矫正的核心是用cv2.minAreaRect找到条码的最小外接矩形,然后计算旋转角度,再把图像转正。
但是这里有个巨坑,90%的人第一次做都会踩。
cv2.minAreaRect返回的角度范围是[-90, 0),不是我们通常理解的0-360度。而且它的角度定义非常反人类:矩形的宽边与x轴正方向的夹角,逆时针旋转为负。
我当初就是没搞懂这个,旋转方向搞反了,结果条码越转越歪,差点把我搞疯。我把-90到90度所有角度都试了一遍,就是不对,后来在一个国外的论坛上翻了几十页回复,才看到有人说这个问题。
正确的角度矫正代码应该是这样的:
import numpy as np
def correct_barcode_angle(roi):
# 灰度化+反二值化(条码是黑条白背景,反二值化后轮廓更明显)
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 查找所有轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return roi # 没找到轮廓,直接返回原图
# 找到最大的轮廓,就是条码本身
largest_contour = max(contours, key=cv2.contourArea)
# 获取最小外接矩形
rect = cv2.minAreaRect(largest_contour)
(cx, cy), (w, h), angle = rect
# 最关键的一步:计算正确的旋转角度
if w < h:
angle = 90 + angle
else:
angle = angle
# 生成旋转矩阵
rows, cols = roi.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), angle, 1)
# 旋转图像,用双三次插值,边界用复制填充
corrected_roi = cv2.warpAffine(roi, M, (cols, rows),
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
return corrected_roi
就那个if w < h: angle = 90 + angle,一行代码,我找了整整一天。
加了这个角度矫正之后,哪怕条码歪了80度,都能完美转正,ZBar的识别率直接从75%飙升到95%以上。
然后是多条码识别。
很多时候一个工件上会有好几个条码,比如零部件上的物料码、追溯码、批次码。传统的ZBar有时候会漏检,或者识别顺序乱了,把批次码当成物料码,那麻烦就大了。
用YOLO就完全没这个问题。
YOLO会一次性检测出图像中所有的条码,每个都有独立的bbox,然后我们逐个裁剪、矫正、识别就行。还能给每个条码标上序号,绝对不会搞混。
把刚才的检测部分改一下就行:
# 检测条码
results = model(img, conf=0.3, iou=0.4, verbose=False)
all_barcodes = []
for result in results:
# 按x坐标排序,从左到右识别
sorted_boxes = sorted(result.boxes, key=lambda box: box.xyxy[0][0])
for idx, box in enumerate(sorted_boxes):
x1, y1, x2, y2 = map(int, box.xyxy[0])
barcode_roi = img[y1:y2, x1:x2]
# 角度矫正
corrected_roi = correct_barcode_angle(barcode_roi)
# 预处理
gray = cv2.cvtColor(corrected_roi, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3, 3), 0)
_, binary = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 识别
barcodes = decode(binary)
for barcode in barcodes:
data = barcode.data.decode("utf-8")
all_barcodes.append(data)
# 绘制结果,标上序号
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(img, f"{idx+1}: {data}", (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
print(f"共识别到{len(all_barcodes)}个条码:{all_barcodes}")
我之前有个项目,一个电路板上有6个不同的条码,用ZBar最多识别出4个,还经常顺序乱掉。用这个方法,连续测了1000张图,100%全中,顺序也完全正确。
最后说一下工业现场最头疼的反光和模糊问题。
金属工件的激光打码,经常一半被灯光照得发白,人眼都快看不清了,普通二值化出来就是一片白,什么都没有。
这时候就要用CLAHE直方图均衡化,真的是神器。
我之前有个工件,条码一半反光严重,用普通二值化根本不行,加了CLAHE之后,瞬间就清晰了。
改进一下预处理函数:
def industrial_preprocess(roi):
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
# CLAHE直方图均衡化,处理局部反光和光照不均
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
equalized = clahe.apply(gray)
# 高斯模糊去噪
blur = cv2.GaussianBlur(equalized, (3, 3), 0)
# 自适应二值化,比全局二值化效果好太多
binary = cv2.adaptiveThreshold(blur, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
return binary
把之前的预处理换成这个函数,识别率又能提升一大截。
对于特别模糊的条码,可以加个锐化操作:
# 锐化核
sharpen_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]], np.float32)
sharpened = cv2.filter2D(gray, -1, sharpen_kernel)
不过不要滥用锐化,不然会把噪声也放大,反而影响识别。
很多人担心YOLO速度慢,跑不了产线。
完全不用担心。我用的是最小的yolov8n模型,在普通的i5-10400 CPU上,单张640x480的图片检测速度不到30ms,加上预处理和识别,整个流程不到50ms,也就是每秒20帧以上。
产线的节拍一般都是几百毫秒一个工件,完全够用。根本不用上GPU,省了一大笔硬件成本。
如果还想更快,可以把YOLO模型导出成ONNX格式,用OpenCV的DNN模块加载,不用依赖ultralytics库,部署更方便,速度也能快个10%左右。
哦对了,还有几个坑我踩过,你们别再踩了。
条码太小的时候,YOLO可能检测不到,这时候可以把图像放大2倍再检测,效果会好很多。
pyzbar在Windows上有时候会有中文编码问题,识别中文条码会乱码,这时候可以用zxing-cpp代替,效果更好。
置信度阈值不要设太高,不然会漏检,我一般设0.3-0.5之间,效果最好。
现在这个程序已经在我手上的三个产线上跑了,最长的已经跑了半年多,识别率稳定在99.5%以上。
再也不用天天被客户打电话骂了,终于能睡个安稳觉了。
你们有什么更好的优化方法,也可以自己试试。
更多推荐



所有评论(0)