在这里插入图片描述
我跟你说,做工业视觉的,谁没被条码识别折磨过?
前两年接了个汽车零部件产线的项目,要求读取工件上的激光打码。一开始我想这还不简单,直接上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%以上。
再也不用天天被客户打电话骂了,终于能睡个安稳觉了。
你们有什么更好的优化方法,也可以自己试试。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐