接手这个项目的时候,甲方给的需求并不复杂:在一条产线上,用海康工业相机对产品表面做实时检测,检出微小缺陷并分类。但真正把相机架起来、把YOLOv5跑起来之后才发现,高分辨率输入带来的问题远比想象中多——显存占用暴涨、推理帧率跳水、小目标漏检严重,甚至相机偶尔丢帧、触发信号不稳定都把整个流程带崩过。
这篇文章把整个实践过程从头到尾拆开讲,包括工业相机怎么接、触发信号怎么排查、YOLOv5在高分辨率输入下怎么改怎么调、数据集标注怎么快速搞定、TensorRT加速后能跑到什么程度,以及在Jetson Nano这类边缘设备上部署的实测结果。内容是我踩坑踩出来的,能帮想入这个坑的人少走不少弯路。
1. 高分辨率检测为什么是个"硬骨头":从漏检追溯到根本矛盾
先说结论:大多数跑在YOLOv5上的项目,训练和推理都用了640x640或1280x1280的输入,这在高分辨率工业检测场景下会直接翻车。我一开始用的是一张4096x3000的样图,直接resize到640x640送进模型,检测结果惨不忍睹——几个毫米级划痕全漏了,只有一个较大缺陷被框了出来。
原因不复杂,就是目标尺寸和输入缩放之间的矛盾。一个只有30x20像素的微小缺陷,在4096宽的图像里占比不到1%,直接缩放到640宽时,缺陷区域被压成了大概5x3像素,这个大小已经远低于YOLOv5特征层能够有效感知的极限。要知道,YOLOv5下采样32倍后的最小特征图只有20x20(640输入),5x3像素的目标在特征图上可能连一个完整anchor都分不到。
这种情况下,第一个直觉是“把模型输入调大”,比如直接上1280甚至1536。这确实有效果,但随之而来的代价非常现实。
我这里列一下实测的显存和速度数据,用的是一张RTX 3080 10G的卡,模型是YOLOv5s:
| 输入分辨率 | 推理耗时(ms) | 显存占用(GB) | 缺陷召回率 |
|---|---|---|---|
| 640x640 | 6.8 | 1.2 | 52% |
| 1280x1280 | 21.5 | 4.1 | 78% |
| 1920x1920 | 47.2 | 8.7 | 86% |
| 原始4032x3024 | 爆显存 | 10G+ | 无法运行 |
从这个表格能看到,输入分辨率从640涨到1920,召回率确实在涨,但显存占用和推理延时的增长更吓人。如果产线实时性要求是每秒处理10帧以上,1920输入配合YOLOv5s在3080上勉强才够,换到普通配置就完全跑不动了。
所以高分辨率检测的核心矛盾从来不是“模型能不能检测”,而是“在分辨率、速度、显存之间怎么找到工程上可接受的平衡点”。这个平衡点取决于你手上的硬件、产线节拍、以及缺陷的物理尺寸。
这里还要纠正一个常见误区:很多人以为工业相机的分辨率越高越好,甚至为了“看得清”直接上2500万像素或更高。但在检测算法侧,高分辨率图像如果喂不进模型,本质上就是一堆用不上的像素。搞工业检测项目,算法工程师必须和硬件选型绑定思考——分辨率的关键不是“拍得清不清楚”,而是“目标在模型输入尺寸下能占多少个像素”。
一个我实际沿用的经验值:缺陷最短边在模型输入图上至少要占到8个像素以上,否则漏检概率会急剧上升。反过来算,如果你需要在4096宽的图中检测5mm的缺陷,模型输入1280时缺陷约占1.6像素,这远远不够。所以要么提高输入分辨率,要么物理上缩短视场宽度,让同样像素数覆盖更小的物理范围。
2. 海康工业相机的接入与触发同步:从MVS驱动到“未收到触发信号”的完整排查
2.1 相机选型和连接的基本配置
海康工业相机按照接口主要分GigE(网口)和USB 3.0两种。项目里我用的是MV-CA系列GigE相机,型号具体不说了,感光芯片是Sony的,分辨率500万像素,帧率理论上能做到17fps@2448x2048。但这是理论值,实际跑起来还要受网卡带宽、曝光时间、传输协议等多重限制。
连接采图用的软件是海康官方提供的MVS(Machine Vision Software),这个工具集成了相机搜索、参数配置、实时预览、SDK调用示例等功能。装好之后,第一步是配置IP地址——GigE相机和电脑之间需要能ping通,一般把电脑网卡IP设为和相机同一网段(比如192.168.1.x),子网掩码255.255.255.0。
这里有一个非常容易踩的坑:Windows系统的防火墙默认会拦截GigE相机的广播包,导致MVS搜索不到设备。不是每次都会拦,但搜不到设备时第一反应应该是检查防火墙,而不是反复重装驱动。我当时在这个问题上折腾了两个小时,最后把防火墙对私有网络的拦截关掉,设备立刻出现了。
2.2 触发模式下“未收到触发信号”的根因定位
项目实际运行时用的是硬件触发而不是连续采图,这样可以保证每一帧图像都在固定光照条件下拍摄,避免运动模糊和亮度波动。海康相机默认是连续模式,需要到MVS里把触发模式从Off切到On,并选择触发源为Line0或Line2。
但切到硬件触发之后,麻烦就来了。软件提示“未收到触发信号”,图像预览黑屏。当时第一反应是plc那边没有发出信号,用万用表量了之后确认信号是有的,于是开始逐步排查相机侧的配置。
排查链路大致如下:
- 检查触发源配置是否正确。海康相机触发源可以是Line0、Line2、软件触发等,不同相机型号支持的光耦隔离输入引脚不同,我用的型号Line0和Line2是输入,Line1是输出,配置错了自然会没信号。
- 检查触发沿类型。相机常见的有上升沿触发和下降沿触发两种,PLC那侧如果输出的是高电平脉冲,相机要配上升沿有效,如果配置成下降沿就会一直收不到有效信号。
- 检查曝光时间与帧率上限的约束。当时我把曝光时间设到9500us,接近该型号上限,触发信号间隔又很短,相机处于“忙”状态时会跳过触发。这个问题如果不看相机日志根本发现不了,MVS的SDK可以读取错误码,排除了硬件问题后我调了SDK日志才发现有帧丢失的记录。
- 接线干扰问题。工业现场的电机、变频器会对信号线产生电磁干扰,信号线必须用双绞屏蔽线,并且屏蔽层单端接地。
最终定位到的问题是Line2引脚因为接线端子氧化导致接触电阻过大,信号幅值被拉低,触发源电平阈值判断不到高电平。换了一条线缆后问题彻底消失。排查这个问题的最大教训是:不要一看到“未收到触发信号”就认定是PLC的问题,相机侧的触发源配置、触发沿、曝光时间、硬件接线,每一个环节都可能是根因。
2.3 多相机同时采集时的同步
后续项目又加了第二台相机,两台相机需要同时抓拍同一运动物体,这时候触发同步就变得有点讲究了。工业上常用的做法是用一台PLC或信号发生器同时输出两路触发信号给两台相机。但电信号在传输过程中会有微小的延迟差,高速运动下两台相机的抓拍位置差异可能达到毫米级。如果精度要求高,可以用海康的同步触发盒,或者由一台相机输出同步信号给另一台。
软件层面,MVS SDK也提供了多相机同步采集的API,核心是调用MV_CC_SetCommandValue设置同步触发模式,然后统一等待回调。我的建议是:能靠硬件同步就不要依赖软件同步,因为Windows不是实时操作系统,网络传输抖动会导致帧时刻不稳定,这在精密测量里是完全不能接受的。
3. YOLOv5在高分辨率输入下的显存与推理优化:从切图到TensorRT的完整路线
3.1 为什么不建议直接裸跑大分辨率
前面已经说过,直接把4000x3000的图像送进YOLOv5会爆显存。就算显存够,推理耗时也无法接受。有一种做法是把模型本身的stride调大或减少层数来换取速度,但牺牲的是小目标检测能力,在工业缺陷检测场景下得不偿失。
更合理的做法是切图推理,也就是把高分辨率图像切分成多个有重叠的patch,分别送进模型检测,最后把检测框映射回原图坐标做融合。
3.2 切图策略和重叠率的工程考量
切图看似简单,里面细节不少。直接用滑窗切割最简单,比如4096x3000切成1280x1280,步长也设为1280,一共得到12张图。但这样漏检风险很大:如果一个缺陷正好被切在两张图的交界边缘,模型很可能因为上下文信息不完整而漏检。
最直接的解法是设置overlap,也就是让相邻切块之间有一定重叠。我在项目里用的重叠率大约是15%,1280的滑窗配192像素的重叠,保证缺陷至少完整出现在某一张切图里。但不能为了不漏检就把重叠率无脑调高,重叠率越高,同样一张大图被切成的patch数量就越多,推理总耗时越长。
从算法角度出发还有一个更聪明的做法——根据检测需求动态调整切图策略。比如先用低分辨率快速检测一遍,在目标密集区域做二次高分辨率切图,这样可以把单帧处理时间明显降下来,但工程复杂度会上一个台阶。当时项目时间比较紧,我没有用这种动态策略,而是用固定切图加TensorRT加速的方式把整体帧率拉了起来。
切图后的坐标映射也是个容易出错的点。模型输出的边界框坐标是针对patch自身的,需要加上patch在原图中的左上角偏移量,才能在原图上画框。如果做了overlap,同一个目标可能在多张切图中被重复检测,这时要做NMS合并。我的做法是把所有检测框先还原到原图坐标,然后用一个较低置信度阈值过滤掉单张图里的弱检测,再做全局NMS,置信度阈值设为0.35,IoU阈值0.45,复测效果比较稳定。
切图还有一个好处是可以配合GPU并行。如果把一张大图切成12个patch,在批处理维度上一次性送进模型,GPU利用率会高很多,总推理时间反而比一张张串行快不少。
3.3 高分辨率场景下的模型结构选择
模型结构选择上,我对比过YOLOv5s、YOLOv5m、YOLOv5l,以及YOLOv5的P6模型。
P6模型是YOLOv5针对大分辨率输入专门设计的变体,在原来的P3/P4/P5三个输出层基础上增加了一个P6层,下采样倍数从32倍降到64倍,更适合检测1280x1280甚至更大尺寸输入中的中小目标。我最后用的就是YOLOv5s-P6,输入1280x1280,单张patch推理速度在3080上大约32ms,和小目标的召回率平衡下来比较理想。
如果切图patch较小(比如640或768),就不建议用P6模型了,因为P6层在小编码器上体现不出优势,白白增加计算量。模型选型要跟着patch尺寸走,这算是我自己实践下来的一条经验。
3.4 TensorRT加速:int8量化的实用经验
YOLOv5的PyTorch权重在GPU上直接推理,速度其实并没有榨干GPU的能力。把模型转成TensorRT引擎之后,速度可以提升2~3倍。TensorRT的转换流程不复杂,官方仓库提供了gen_wts.py脚本,先把PyTorch权重转成wts文件,再用TensorRT版本的YOLOv5工程把wts转成engine。
这里我要分享三个最实用的经验:
第一个是动态batch和动态shape。工业检测场景下,输入尺寸固定其实更好优化,TensorRT构建引擎时用固定shape能获得更好的kernel选择和显存分配。我当时固定了1x3x1280x1280,这样每个patch串行推理虽然单张慢一点,但内存占用低、逻辑简单。如果单帧多patch并行用固定batch=8,性能表现更好,但显存占用会明显上涨,8张1280x1280输入在10G显存上已经比较紧张。
第二个是INT8量化的标定集选择。TensorRT的INT8量化需要标定数据(calibration dataset),很多人随便拿几十张图跑一遍就完事。我的经验是标定集必须覆盖所有缺陷类型和背景分布,特别是要包含带缺陷的图,否则量化后模型会对缺陷这种“稀疏模式”产生明显的精度损失。我当时用了项目里的500张标注图做标定,量化前后在测试集上map只掉了约0.8%,完全可接受。
第三个是不要盲信FP16和INT8的速度对比。在很多GPU上,FP16和INT8的推理速度差距没有传说中那么大,尤其在显存带宽吃紧的卡上。我实测在3080上用YOLOv5s-P6推理1280x1280,FP16引擎大约33ms,INT8引擎大约16ms,INT8快了一倍,但如果精度衰减超过2%,我宁愿用FP16。量化本身是一个精度和速度的trade-off,不是非用INT8不可。
4. 数据集标注与训练落地:半自动标注、超参数调整和一套让我少踩坑的流程
4.1 用界面工具搞定YOLOv5数据集的自动标注
之前项目里用LabelImg纯手工一张张框缺陷,两三千张图让人画到怀疑人生。这次学聪明了,先用手头已有的检测模型做预标注,再用专门的标注工具做人工复核修正。
我用的工具是X-AnyLabeling,这个工具可以直接挂载YOLOv5模型权重,把一批待标注的图跑一遍自动生成框,然后人工在界面上修正错框和漏框。一个几百张图的数据集,纯手工标注要两天,半自动标注加修正半天就差不多搞定了。
具体流程是这样的:
- 先把已经标注好的少量图片(200张左右)训练出一个初版模型,哪怕精度不高也没关系,反正只是用来预标注。
- 把待标注的大批量图片放进一个文件夹,用训练好的权重自动推理,生成一个带标签的json或txt标注文件。
- 用X-AnyLabeling打开这些图片和自动标注结果,人工过一遍,调整错框、删除误检、补上漏检。
- 保存为YOLO格式的txt标注文件,直接就能放进训练流程。
这套流程对工业检测特别友好,因为产线场景相对固定,背景变化不大,初版模型的预标注准确率通常能有70%以上,人工复核压力不大。
4.2 训练策略中的几个关键配置
训练超参数的调整在YOLOv5里其实很直观,主要通过data/*.yaml和hyp.scratch-low.yaml配置文件来控制。
我重点调过的几个参数:
- imgsz:训练时的输入尺寸,我设为1280,和推理时保持一致。这一点很重要,训练和推理的输入尺寸最好一致,否则模型要适配不同的尺度分布,影响检测精度。
- batch size:在3080上,YOLOv5s-P6用1280输入时batch设到8已经是显存极限了。batch太小训练不稳定,batch太大容易吃显存。我的建议是能多大就多大,但不要牺牲稳定性,一般8~16比较健康。
- epochs:训练了300轮。其实YOLOv5会在训练后期自动早停,我当时设了patience=50,如果50轮内map没有提升就自动停止,实际模型在第180轮左右就收敛了。
- mosaic:YOLOv5默认使用马赛克增强,这个增强策略在工业小目标检测里尤为重要,它会把四张图拼接起来随机缩放,相当于变相增加了小目标在图像中的占比,对小目标召回率提升非常大。我在最后一少部分epoch开始关闭mosaic(约总epoch的20%),让模型在接近真实分布的数据上微调,避免mosaic增强带来的分布偏差。
训练时的迁移学习也很关键。用COCO预训练权重做初始化比从零开始训练收敛快得多,而且最终精度普遍更高。不用自定义数据集太小就担心训练不上,恰恰因为数据集小,预训练权重的价值更大。
4.3 数据不平衡和背景干扰的处理
工业场景的一个共性是:缺陷样本和正常样本数量严重失衡。一个产品表面可能只有1%的概率存在缺陷,如果训练集里全是真实场景下的缺陷样本,模型很容易对“正常”区域产生误检。
我的处理方法是正常样本加背景负样本混合训练,在数据集里保持20%~30%的负样本比例,这些负样本不需要标注,模型会学会将它们预测为background。这点在工业检测里很关键,纯缺陷样本训练出来的模型即使map很高,上了产线也会被误检率打爆。
还有一个容易被忽略的细节是光照一致性。工业相机的安装角度、光源亮度、频闪方式稍有变化,图像分布就变了,模型可能瞬间失效。所以采集训练数据时,尽量覆盖光源亮度的上下浮动范围,让模型见过足够的亮度变化,这对产线鲁棒性帮助很大。
5. 从PC到边缘部署:Jetson Nano上的移植与性能实测
5.1 Jetson Nano上的环境配置关键点
Jetson Nano不是x86架构,而是ARM架构,PyTorch不能直接用pip装,必须用英伟达官方提供的JetPack里的预编译版本。JetPack自带CUDA、cuDNN、TensorRT,版本之间是有绑定关系的,装环境时最忌讳混装。
我当时用的是JetPack 4.6.1,自带CUDA 10.2、TensorRT 8.2。YOLOv5仓库有一个requirements.txt,但里面的torch版本是x86的,需要到英伟达官方论坛下ARM版torch的whl文件来装。
一个很实用的经验:先在PC上把所有代码调通,再到Jetson Nano上部署。在ARM板上调试代码的效率大约是PC的十分之一,内存和CPU性能都差很多,除非必要不要在板上跑训练或大量调试逻辑。
5.2 边缘设备上的实测数据
Jetson Nano的GPU算力大约是0.5 TFLOPS FP16,和桌面级显卡完全不是一个量级。把前面优化的YOLOv5s-P6跑到Nano上,1280x1280输入,TensorRT FP16引擎推理耗时大约420ms,大概2.4帧每秒。这里不是它不快,是这个设备的算力上限摆在那里。
但实际项目里,我发现把输入降到640x640用Nano跑,单帧推理大约105ms,接近10帧每秒的实时性,代价是小缺陷召回率会有所下降。如果像素-物理尺寸换算之后640输入已经满足检测精度需求,那Nano完全可用;如果非要1280输入,建议直接上Xavier NX或Orin Nano,算力翻了好几倍。
5.3 边缘部署时的省显存技巧
Jetson Nano的显存和内存是共享的,4GB型号跑起大模型很容易OOM。我当时用了两个技巧把显存压了下来:
第一个是开启TensorRT的显存池复用,避免每次推理都重新分配显存,长期运行过程中能减少非常多内存碎片和峰值占用。
第二个是把图像的预处理放到CPU上用cv2异步完成。GPU负责推理,CPU负责缩放、归一化等预处理,双流水线并行能让帧率再上一个台阶。如果图像分辨率很高、预处理部分耗时明显占比高,可以尝试用OpenCV的UMat把部分图像处理放到GPU上做,但是否值得取决于具体瓶颈在哪里。
6. 实测效果复盘:数据、问题与可复用的调优建议
把整个流程跑通后,我对最后的效果做了一次完整复盘。在这个部分,直接上实测数据和踩坑后的建议,方便后来者直接参考。
6.1 最终效果数据
最终方案是YOLOv5s-P6,TensorRT FP16引擎,输入1280x1280,在RTX 3080上,单patch推理33ms,单帧4096x3000图像切12个patch并行(batch=12)总耗时约110ms,折合9帧每秒。在Jetson Nano上单patch推理420ms,但通过切图并行并不能提升帧率上限(Nano的内存带宽限制很严重),只能串行跑,所以单帧大图需要5秒左右,在产线节拍要求不高时可以接受。
精度方面,测试集缺陷召回率约91.6%,误检率约每帧0.7个。和最初640输入时52%的召回率相比,整套优化下来提升非常明显。
6.2 后来者可直接参考的调优清单
根据整个项目踩过的坑,我整理了一份可以直接套用的清单:
- 不要盲目希望低分辨率模型处理高分辨率图像,小目标检测需要给模型足够的输入尺寸。
- 切图必须有overlap,15%到20%是一个合理的起点。
- 训练和推理的输入尺寸尽量保持一致。
- 如果是工业小目标场景,优先看YOLOv5s-P6这一档模型,性价比很高。
- TensorRT的INT8量化要做,但标定集必须覆盖真实分布,做完量化一定要在测试集上验证精度损失。
- 工业检测数据集一定要加入负样本,否则产线误检率会失控。
- 相机触发信号异常时,按“触发源配置-触发沿-曝光时间和帧率约束-硬件接线”的顺序排查,不要一上来就怀疑PLC。
- Jetson Nano上跑大模型要提前算好算力预算,别指望它能跑出和3080一样的效果。
6.3 关于实时性的一个延伸思考
实时检测的定义在工业场景里不是固定的。有些产线节拍是每秒30帧,有些只需要每秒2帧甚至更低。做方案设计时,先问清楚产线的节拍要求和缺陷大小,再反推计算输入分辨率、模型结构和推理后端,这样才能做出来真正可落地的方案。我自己之前一直想在算法上一步到位,结果反而把简单问题复杂化了。
另外,如果后续想进一步提升小目标的检测准确率,除了换更大模型、更高分辨率之外,可以尝试在YOLOv5的检测头之前增加基于注意力机制的模块,例如在Backbone的深层特征上添加SE或CBAM模块,让模型更关注缺陷区域的特征响应。对工业小目标检测来说,这是一个有效但常被忽略的优化方向。
这次项目整体下来,最大的感悟是:高分辨率实时检测不难,难的是在高分辨率、实时性、显存容量、检测精度四个变量之间找到平衡。很多教程只告诉你YOLOv5怎么训练怎么部署,却忽略了工业相机接入、触发信号同步、数据分布适配这些“脏活累活”。这些细节才是决定项目能否真正落地的东西。