这两年做视觉检测项目,被问得最多的一个问题就是:条形码识别还有啥难的,手机扫一下不就完事了?我一开始也是这么想的,直到真去做了才发现,手机上扫码那是经过大量算法优化的结果,你自己用传统解码头在工业场景里跑一遍,处处是坑。所以我做了这个基于深度学习YOLOv12的条形码检测系统,整套东西包含训练好的模型、能直接跑的Python项目源码、登录注册界面和可视化检测UI。这篇文章就是把这个项目从数据准备、模型训练到界面打包的完整过程写下来,包括踩过的坑和一些我后来才想明白的取舍。如果你正准备做类似的检测系统,或者想把YOLOv12实际落地到一个具体业务里,这篇应该能帮你少走不少弯路。
1. 为什么条码识别要从传统解码头切到YOLOv12
先说结论:条形码识别这个需求,拆开看其实是两个子任务——先要找到条形码在哪里,再把条形码里的内容解出来。传统方案里,这两件事经常混在一起做,而上深度学习之后我发现,把"定位"和"解码"拆开,各用各的强项,整体系统反而稳得多。
1.1 传统方案被现实毒打的几个典型场景
传统方案主要靠开源解码头和一堆图像预处理操作来走,比如边缘检测、二值化、形态学滤波。处理一张打印清晰、背景干净的条码图,它确实够用。但一旦进入真实场景,问题就成串地冒出来。
最典型的就是光照不均匀。我在模拟项目X里初期测试时,拿了一个手持扫描枪拍出来的图,画面里条形码正对着窗口,一半亮一半暗。传统二值化一上去,暗部的条和空直接糊成一团,解码头要么解不出来,要么解出一个错误编码——比解不出来更坑,因为错误编码传到后续系统里,会导致货品匹配错乱。
还有一种情况是条形码只占画面的一小部分。传统解码头在整张图里找条码区域是靠梯度扫描,如果背景本身有纹理、有黑色色块,它很容易把别的东西当成条码区域,然后在这个区域上跑解码,结果自然是一堆乱码。更麻烦的是多个条码同时出现在一个画面里,传统方案基本不具备逐个定位的能力,谁在前谁在后全靠运气。
传统方案还有一个致命问题:它给不出置信度。系统拿到一个解码结果,根本不知道这个结果到底是"干净解码出来的"还是"强行凑出来的"。而生产线上需要的是可追溯、可人工复核的记录,光有一个字符串没有位置信息,出了问题都不知道该去查哪个环节。
1.2 YOLOv12恰好补上了这些短板
YOLOv12是从YOLOv8那套框架发展过来的新版本,2025年开源之后很快就有人在各种场景里做了测试。相比前几代,它的核心变化是引入了区域注意力机制,简单说就是网络在提取特征的时候,会对画面里不同区域的重要程度做一个加权,而不是每个像素一视同仁。条形码这种有明确纹理结构和长宽比的目标,在这种机制下会比较好认,而且推理速度并没有因此明显变慢——这才是它能落地到实时检测系统的原因。
放到条码检测这个具体场景里,YOLOv12给到我们的能力有两个。第一,它输出的是目标框,也就是条形码在画面里的具体位置,这个位置信息就是前面说的人工复核依据。第二,它输出每条检测的置信度,比如0.92,这说明模型有92%的把握这块区域是条码。置信度可以拿来设阈值,低于阈值的直接标成"待人工确认",避免误报传导到下游。
当然,用YOLOv12做检测不等于完全抛弃解码头。我的方案是两级串联:模型负责定位,把条码区域裁剪出来之后,再交给传统解码头做内容解析。模型解决"在哪"的问题,解码头解决"是什么"的问题,各司其职。这样模型即使对倾斜和模糊的条码也能框住,而解码头只在一个干净得多的小图上工作,成功率自然显著上升。
2. 系统骨架:检测模块、UI界面、登录注册各自分工
这个项目既然要做成一套完整的系统,就不能只拿一个检测脚本糊弄事。我见过太多项目案例,模型训练得挺漂亮,最后交付的时候是一堆训练代码和一个Jupyter Notebook,客户根本没法用。所以我在动手写代码之前,先把整个系统的结构画清楚。
2.1 先画一张模块地图
整套东西我用的是Python,原因是整个生态最顺:模型训练、图像处理、GUI工具全都有现成库,不用跨语言折腾。整个项目分成四个大的模块。
第一个是检测模块,核心是训练好的YOLOv12权重文件,以及封装好的推理类。这个模块做的事情是加载模型、接收图像、返回检测框和置信度,同时调用解码头把框内的条码内容解析出来。为了让主程序不依赖具体的模型源,我把它写成一个独立的包,后面想换模型只需要替换权重文件,不用改主程序。
第二个是UI模块,负责跟用户互动。它包含登录注册窗口和主检测窗口。主检测窗口里又有图片加载、摄像头采集、结果显示、历史记录这些功能。UI模块我用了PySide6来做,它是Qt官方的Python绑定,许可证对商用相对友好,界面风格也现代一些。
第三个是数据模块,负责跟SQLite数据库打交道。用户信息、登录日志、检测记录都存在这里。为什么用SQLite而不是MySQL?因为这个系统是单机部署的,不需要多人并发访问和网络分发,SQLite文件数据库零配置、免维护,完全够用。
第四个是资源目录,里面放训练好的模型、字体、图标、示例图片。目录单独划分的好处是,打包发布的时候方便,打包工具只需要把这个目录整个带上,程序就能在别的机器上跑起来,不用再去找模型文件在哪里。
模块之间的关系很简单:UI模块接收用户的登录请求,数据模块校验身份;登录成功后UI模块显示主窗口,用户选择图片或打开摄像头,UI把图片交给检测模块,检测模块返回检测结果和可视化后的图像,UI把这个结果显示到界面上,同时把检测记录写入数据库。
2.2 数据流、线程与关键表的约定
画出模块地图之后,还要想清楚数据是怎么流动的,否则写代码的时候容易你调我我调你,到最后变成一团乱麻。
我的约定是这样:UI和检测模块之间不直接传文件路径,而是传图像数组。也就是UI把图片读成numpy数组传给检测模块,检测模块处理完返回一个字典,里面包含检测框列表、每个框的置信度、解码字符串、耗时这些字段。这样有一个好处,后续如果要把检测模块替换成更快的ONNX推理,接口可以保持不变,只改内部实现。
线程分离是这个系统里一个必须注意的点。YOLOv12虽然推理速度可观,但单张图也要几十毫秒到上百毫秒,如果把推理丢在UI线程里跑,界面会直接卡死,用户体验非常差。我用QThread单独开了一个工作线程来跑模型推理,UI线程只负责显示。检测完通过信号把结果传回UI线程,Qt的信号槽机制天然是线程安全的,不用自己再加锁。
数据库的表结构也提前定了。用户表是users,字段有id、username、password_hash、salt、created_at。登录日志表是login_logs,记录user_id、login_time、ip。检测记录表是detect_logs,记录user_id、image_path、detect_info、created_at。表结构这样设计,后面做登录历史查询、检测记录回放都很方便。
CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, salt TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now', 'localtime')) ); CREATE TABLE login_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, login_time TEXT DEFAULT (datetime('now', 'localtime')), ip TEXT ); CREATE TABLE detect_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, image_path TEXT, detect_info TEXT, created_at TEXT DEFAULT (datetime('now', 'localtime')) );数据库这类东西,一开始不想清楚字段含义,后期加字段就得改一堆代码,所以我宁可前期多花十分钟设计表结构,也不愿后面返工。
3. 训练数据从哪来:合成图与真实图混合的一个可行配方
模型训练这件事,很多人一上来就到处找现成的条形码数据集。说实话,网上能直接用的条码检测数据集非常少,要么是OCR类型的,标注的是一串数字而不是条码位置;要么是某个特定场景的采集集,跟你自己业务里的包装风格完全对不上。所以我做数据时走了一条混合路线:自己采集真实图,再用程序批量合成训练图,效果比我预想的好很多。
3.1 数据采集策略:别一上来就指望网上找现成数据集
真实采集的部分,我拿了一部手机和一台固定摄像头,在不同光照、不同距离下拍了几百张带条码的实物图。来源包括包装盒、快递面单、超市散称标签、打印出来的测试条码。这些图的价值在于它们包含了真实世界的复杂度——指纹痕迹、表面褶皱、塑料膜反光、油墨不均匀——这些是合成数据很难完美模拟的。
我的建议是真实图至少准备800到1200张。太少的话,模型学不到真实拍摄的纹理分布;太多的话,标注成本又非常高。条形码检测相对其他目标检测任务不算复杂,1200张真实图配合适量的合成图,已经能训练出能用的模型。采集的时候注意尽里覆盖不同角度和尺度,大条码、小条码、横的、竖的、斜的都要有。
真实图的标注我用的是labelImg,标注格式直接导出成YOLO格式。YOLO格式的标注文件是一个txt,每行代表一个目标:类别id、归一化后的中心点x坐标、中心点y坐标、宽度、高度。条形码只有一个类别,所以每行开头的数字就是0。这里有个细节要注意:条形码通常是细长矩形,标注框的宽高比可能到3比1甚至4比1,标注时要贴着条码实际边界,不要多留背景,不然模型学到的条码特征会被背景干扰。
3.2 合成数据时,标注框其实可以白拿
合成数据是我这次比较得意的一步。手动标注1200张真实图已经很累了,如果让合成数据也跟着一张张标,那就本末倒置了。实际上合成数据的标注框是可以程序自动生成的。
我写了一个合成脚本,整体流程是:先用python-barcode库生成Code128、EAN-13、UPC-A这些常见编码格式的条码图片,然后把条码图做随机透视变换、旋转、缩放,最后粘贴到从网上找的或者自己拍的背景图上。因为粘贴的位置和变换的参数程序里都有记录,所以条码在最终图片上的坐标是可以直接算出来的。这样合成一张带标注的图片只需要一次批量运行,根本不用人工介入。
import barcode from barcode.writer import ImageWriter import cv2 import numpy as np # 生成 EAN-13 条形码并保存为 PNG ean = barcode.get('ean13', '6901234567892', writer=ImageWriter()) ean.save('barcode_tmp') barcode_img = cv2.imread('barcode_tmp.png')合成数量控制在3000张左右就够用了。再多当然模型不一定有显著提升,反而增加训练时间。我的合成逻辑会随机切换背景、随机调整条码位置和角度、随机叠加光照变化和模糊效果。这样生成的图片,条码在画面里的位置和形态几乎不重样,模型见过的变化多了,真实场景里遇到倾斜、模糊的情况才不至于抓瞎。
3.3 增强策略要收敛,别把条码"增强"没了
数据集准备好之后,训练时YOLOv12自带的数据增强也会发挥作用。这里我得专门提醒一句:目标检测训练里的数据增强不是越猛越好。YOLO系列默认的增强策略包括随机翻转、Mosaic拼接、HSV色域变换、随机旋转等,这些增强在通用目标检测里效果很好,但用在条码上要留心。
原因是条码解码对图像细节非常敏感。如果HSV增强把条码的对比度拉得过低,或者Mosaic拼接时把条码切得只剩半截,模型看到的训练样本就跟"残废"一样。我遇到过的情况是,开头的两百多个epoch里模型表现不错,后面突然指标下跌,一查发现是增强参数里有一个色域变换的值调得太大,导致大量训练样本变成了低对比度条码,模型反而学歪了。
所以训练配置文件里,增强参数的设置原则是"保留条码的核心纹理结构"。我会把HSV变换饱和度、明度的变化幅度调低一些,旋转角度控制在正负30度以内,Mosaic拼接保留但让YOLO自动决定是否启用。这些参数在ultralytics的配置里都有对应字段,改起来不麻烦,但对最终效果影响很大。条码检测数据集训得好不好,很大程度不是看模型结构多先进,而是看数据分布合不合理。
4. YOLOv12训练笔记:从环境到收敛的完整过程
数据准备妥当之后就是训练了。这部分我把实际操作的完整过程和中间的关键判断都写出来,方便你照着复现。
4.1 环境搭建与版本搭配
我训练的机器配置是酷睿i7处理、NVIDIA GeForce系列显卡,显存16G。环境方面,建议直接用Python 3.10,不要用3.13这种太新的版本,因为很多依赖库还没跟上,会遇到奇怪的兼容性报错。PyTorch我用的是2.2以上版本,这个版本对YOLOv12的注意力机制相关算子的支持比较完善。
项目依赖全部用一个requirements.txt管理,核心就几样:ultralytics(负责模型训练和导出)、opencv-python、pyzbar(解码)、PySide6(界面)、Pillow、numpy。ultralytics这个库的好处是,训练、验证、导出、推理一套API走下来,不用自己在训练循环里做很多底层操作,对做应用落地的人比较友好。
4.2 配置文件与超参数选型思路
数据集的配置文件data.yaml要写在项目里,内容很简单:
path: D:/code/barcode-detector/datasets/barcode train: images/train val: images/val test: images/test names: 0: barcodepath是数据集根目录,train和val指向对应的图片文件夹,names里定义类别。这个项目只有一个类别"barcode",所以索引就是0。写配置文件的时候最需要注意的是路径分隔符,Windows系统里反斜杠路径最好统一改成正斜杠,避免解析时出问题。
模型本体我用的是yolov12s。YOLOv12从n到x有不同尺寸,n最大、最快但精度略底,x更重、更慢但精度上限高。条码检测任务不复杂,不需要上大模型,yolov12s已经能在速度和精度之间取得不错的平衡。如果你部署的机器性能非常弱,可以考虑用yolov12n,也就是说用更少的显存和更快的推理换几个百分点的精度,在条码这种单类目标上完全可行。
训练命令长这样:
yolo detect train data=data.yaml model=yolov12s.pt epochs=200 imgsz=640 batch=16 device=0 patience=30这几个参数我逐个说一下。epochs设置成200,是因为条码数据集规模不大,200轮足够让模型收敛,再往后就可能过拟合。patience是早停参数,连续30轮验证集mAP没有提升就自动停止训练,避免白烧电。batch设为16,16G显存跑yolov12s刚好能装下,如果你的卡显存小一些,可以降到8。imgsz选640,这是YOLO系列的训练尺寸标准,一般不用改。
4.3 训练过程观察什么、怎么判断收敛
训练开始之后,ultralytics会在终端实时打印指标,包括box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。对于条码检测,我最看重的是recall和mAP50。召回率管的是"该检测出来的条码有没有漏掉",mAP50是判定定位准不准的核心指标。因为条码漏检比误检更严重——漏检了整条数据就丢了,误检还能靠置信度阈值筛掉一部分。
训练过程中的观察技巧是盯验证集的变化曲线,别老盯着训练集。训练集损失降得快是正常的,但验证集指标如果出现大幅波动甚至下滑,那就要小心过拟合。我这次训练的曲线大概是这样的:前60轮提升非常快,mAP50从0.4左右一路涨到0.9附近;中间60到140轮是缓慢爬坡,主要在提升mAP50-95;到170轮左右基本就平了,再加轮数也没有明显收益。最终训练出来的是best.pt,选择它的依据是验证集mAP50最高,而不是最后一个epoch。
训练完成后做一次验证确认:
yolo detect val model=best.pt data=data.yaml这个命令会在验证集上跑一遍,输出详细指标。我们的验证结果大概是mAP50在0.95左右,mAP50-95在0.7出头。对于单类目标检测来说,这个成绩已经算不错了。模型收敛后,我顺手导出了ONNX格式的权重,方便后面在部署阶段使用:
yolo export model=best.pt format=onnx imgsz=640导出的best.onnx可以直接用onnxruntime做推理,这样在客户机器上不用装PyTorch那一套重型依赖,部署体积小很多,速度也不错。这是我后来给别人部署时觉得最省事的一步。
5. UI界面与登录注册:把模型包成别人能用的东西
模型训练好了,如果到这一步就交差,那只能算交付了一个实验Demo。一个能给别人用的系统,必须有门槛低的入口。这个项目的入口就是登录注册界面和主检测界面。
5.1 登录注册模块:用户名、密码和哈希那点事
登录注册模块看起来简单,无非两个输入框加两个按钮,但里面有几个地方不能糊弄。
首先是密码存储。明文存储密码是绝对不能干的,一旦数据库文件泄露,所有用户信息就全暴露了。我在方案里用的是PBKDF2加盐哈希,原理是给每个用户生成一个随机的盐值,把密码和盐拼在一起用PBKDF2算法迭代10万次得到一个哈希值。盐值不同,同样的密码最终哈希也不同。这样即使两个用户密码一模一样,数据库里存的哈希值也完全不一样。
import hashlib import os def make_salt(): return os.urandom(16).hex() def hash_password(password, salt): return hashlib.pbkdf2_hmac( 'sha256', password.encode('utf-8'), salt.encode('utf-8'), 100000 ).hex()登录的时候,从数据库里根据用户名取出对应的盐和哈希,重新计算一次哈希,跟库里存的比对。注册的时候,检查用户名唯一性,如果已经被占用就不能注册。密码在界面输入框里设置成密文显示,也就是QLineEdit的EchoMode设成Password,防止别人在旁边偷看。
5.2 主界面布局与检测线程分离
登录进来就是主界面。整个主界面分三块:左边是图像显示区,用来展示加载的图片或者摄像头画面;右边是操作区,放"加载图片""打开摄像头""开始检测""保存结果"这些按钮;底部是信息栏,显示当前登录用户、检测耗时、检测到的条码数量和解码结果。
布局这块用QGridLayout或者QHBoxLayout组合起来就行。图像显示区我习惯用QLabel加QPixmap的方式,图片加载进来之后按比例缩放显示。检测完成后的可视化图像,也就是画了检测框的图像,也在这个区域替换显示。
前面说过,检测逻辑必须放在QThread里跑。我写了一个DetectThread类,接收一张图像数组,在run方法里调用模型推理,推理结束后用信号把结果传回主线程。这样无论模型推理多慢,界面都不会卡住,用户点完检测按钮还能正常操作,体验完全不一样。
from PySide6.QtCore import QThread, Signal class DetectThread(QThread): result_ready = Signal(object, list, float) def __init__(self, model, image): super().__init__() self.model = model self.image = image def run(self): import time t0 = time.time() results = self.model.predict( self.image, conf=0.5, iou=0.5, imgsz=640, verbose=False ) elapsed = time.time() - t0 boxes = results[0].boxes.xyxy.cpu().numpy().tolist() confs = results[0].boxes.conf.cpu().numpy().tolist() self.result_ready.emit(boxes, confs, elapsed)摄像头采集是另一个线程,我用OpenCV的VideoCapture读取,把每帧图像送到检测线程,检测结果直接回显到界面上。如果你只需要静态图片检测,摄像头这个功能可以做成可选按钮,不影响主流程。
5.3 检测结果如何展示,解码信息怎么带出来
模型推理输出的是检测框坐标和置信度,但这些只是"定位"的结果,用户更关心的是条形码里的内容是什么。
我的处理流程是:模型检测出条码框后,用OpenCV把框内的区域裁剪出来,然后喂给pyzbar解码头做内容解析。pyzbar底层依赖本地库,对比较清晰的条码图解码效果很好。在UI上,我用OpenCV的rectangle函数把检测框画在原图上,框的旁边标注置信度,如果解码成功再标注解码结果字符串。
import cv2 from pyzbar.pyzbar import decode as zbar_decode def decode_barcode_from_box(image, box): x1, y1, x2, y2 = map(int, box) crop = image[y1:y2, x1:x2] results = zbar_decode(crop) if results: return results[0].data.decode('utf-8') return None解码结果会跟检测框一起显示在图像上,也会存入检测记录表里。这样每个检测动作在数据库里都有迹可查,之后做统计分析也有数据基础。
6. 实测效果与踩坑复盘
系统完整跑通之后,我在不同场景下做了很多次实测。这里必须说一句实话:测试集上的指标好看,跟真机摄像头上的表现完全是两回事。很多问题都是在实拍环节里才暴露出来的。
6.1 测试集数字挺好看,真机摄像头立刻现原形
训练集和验证集上的mAP50达到了0.95左右,这个数字刚开始让我挺乐观。结果把摄像头架到货架前实测,画面里条码很小,再加上人员走动带来的轻微抖动、光线忽明忽暗,模型漏检率一下子就上来了。
后来我仔细看了看失败的样本,发现问题主要集中在三个方面:条码面积太小、条码边缘模糊、镜头反光。这三个问题单看每一个都不至于让模型完全失效,但叠加在一起,条码特征就被压得很弱。比如货架上层贴的条码,离摄像头有两三米,640分辨率下整个条码宽度可能不到40个像素,模型很难从这么少的像素里提取出可靠的结构信息。
针对这个情况,我的处理是调整输入分辨率。检测的时候把imgsz从640提高到960或者1280,模型在小目标上的表现明显变好。代价是推理耗时变长,但对静态图片检测来说完全可接受,反正有独立线程挡着,界面不会卡。如果你的场景要求实时视频识别,那更推荐的做法是调整摄像头的安装角度和距离,让条码在画面里尽量大一些,而不是盲目靠模型硬扛。
6.2 三个经典失败案例和对应的修法
第一个案例是大角度倾斜条码。某次测试时,我把条码标签斜着贴在收纳盒侧面,摄像头从上方俯拍,条码在画面里是一个明显的平行四边形。模型照样框住了它,但pyzbar解码头在这个角度下直接失败。解决办法是在解码前先用OpenCV对裁剪出的区域做一次透视矫正,尽量把条码拉正,再送解码头。这一步对解码成功率提升非常明显。
第二个案例是塑料膜反光。食品包装盒上有一层不平整的塑料膜,光源打上去会出现一道高光,高光区域条码的黑色线条和白色间隙对比度变得很低,解码头一片空白。我的做法是在解码前对裁剪图做直方图均衡化,提升对比度,同时调整检测阈值。这个方法不是对所有反光都有效,但至少能让一部分反光图从完全解不出变成能解出。真实场景里,更好的思路是调整光源角度,减少镜面反射。
第三个案例是条码被遮挡一部分。包装箱上贴着条码,但标签被透明胶带或者边角卷起来遮掉了一块。模型在训练时见过类似的遮挡样本,所以检测框还是能框出来,但解码必然失败。这种情况我的处理是向用户提示"条码可能被遮挡",在界面上做一个明显的状态标记,提醒人工复核,而不是直接给一个空的解码结果。能检测到但解不出来,至少比漏检出框要好得多。
6.3 "先检测后解码"这套架构的真正价值
做完这个项目,我对"先检测后解码"这套两级架构有了一些更深的体会。
如果纯用传统解码头,它输出的是一个字符串,但不会告诉你可靠度,也不会告诉你条码在画面哪里。而如果纯用深度学习模型,它最多告诉你"这里有条码",却读不出条码里的内容。这两者本来就该是配合关系,而不是互斥关系。
我现在回头看,这套架构最大的价值在于它把系统的每一步都有了可解释性。模型给出检测框,解码头给出解码内容,两个阶段各自有各自的置信度信息。一旦出现问题,从系统层面很容易判断是哪一步失败了,是没检测到,还是检测到了但解码失败。这种可追溯性在真正的业务场景里,比单纯一个高准确率指标重要得多。
7. 训练和部署时最容易忽略的几个细节
最后再分享几个实际工程里比较容易翻车的地方,都是一次次试出来的经验。
显存不足的时候,优先降batch,不要降imgsz。imgsz降到320以下之后,小条码几乎就废了。如果你的数据集里条码普遍比较小,甚至可以考虑把imgsz设为768来训练,用显存换精度。另外,训练时记得在data.yaml里把val路径指对,否则验证时找不到文件,程序会报一个不太起眼的警告,然后静默跳过验证。
部署的时候建议直接使用ONNX格式的权重,而不是原始PyTorch权重。PyTorch模型在别的机器上运行需要安装对应版本的torch,几十个文件,ONNX Runtime只需要装一个轻量库。我在某次部署时就吃了这个亏,客户那边临时环境装torch装到一半报CUDA版本不匹配,最后还是在机器上重新配了环境,浪费了不少时间。
界面与模型之间的图像格式转换也是个坑。OpenCV读图默认是BGR顺序,而模型内部大概率是按照RGB处理的。在推理前做一次颜色通道反转,能避免一些莫名其妙的检测精度下降。这个转换在ultralytics内部其实会自动做,但如果你自己写了预处理管线,一定要确认这条转换是对的。
最后要说的是测试用例的设计。不要只拿着测试集里那些"好看"的样本验证系统,多拍一些极端情况的样本,比如极端倾斜、条码占画面比例极小、条码被强光直射、条码上有水渍。这些样本才是检验一个检测系统是否真正可用的标准。我这个项目里,后期花的调试时间大部分都是在处理这些边界情况,而不是在调模型结构本身。