news 2026/10/27 21:00:45

YOLOv5锥桶识别全流程:数据标注、训练调优与TensorRT部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5锥桶识别全流程:数据标注、训练调优与TensorRT部署

1. 锥桶识别这件事,难在哪儿

第一次接锥桶识别需求的时候,我以为这是个送分题。橙色、圆锥形、纹理单一、背景通常是路面,按直觉应该比行人车辆好检。真跑起来才发现,这个目标物几乎集合了所有让检测模型难受的特性:远距离下只有十几个像素、密集摆放时互相遮挡、逆光和隧道口进出时颜色剧烈跳变、被压扁的、倒地的、只剩半截的、贴着广告贴纸的,全都会出现在真实路面上。更麻烦的是,业务侧对它的要求从来不是"能框出来就行",而是要在几十米开外稳定给出位置,还要保证连续帧不闪烁。

YOLOv5 是这套任务里性价比最高的起点。它属于单阶段检测器,一次前向就出框和类别,结构上由 Backbone(CSPDarknet)、Neck(PANet 结构的特征融合)和 Head(三个尺度的检测头)组成,输入默认 640×640,下采样总步长 32,也就是说特征图上最小的格子对应原图 32 像素。这个数字决定了它对小目标的天然上限——一个 20 像素高的锥桶,在最小特征图上连一个格子都占不满,模型只能靠浅层特征勉强记住它。

我把这篇文章写给三类人:准备用 YOLOv5 做交通场景目标检测的算法同学、需要在边缘设备上跑实时推理的工程同学、以及手上有数据但训练结果总是不理想、想搞清楚每一步为什么这么做的实践者。下面提到的所有流程、参数和踩坑记录,都来自我自己反复跑过的几轮迭代,其中数据集是自建的,场景以城市道路、施工路段、停车场和园区内部路为主。文中出现的设备和环境都用代称,参数则是实测下来比较稳的取值,你可以直接拿去改。

2. 整体方案设计与技术选型拆解

2.1 为什么是 YOLOv5,而不是两阶段检测器或更新的版本

选型这件事,我一般看三个维度:精度够不够、延迟能不能接受、工程链路成不成熟。锥桶识别属于典型的实时感知任务,下游通常还要做车道关联、可行驶区域判断,单帧留给检测的时间预算往往只有 20~30 毫秒。

两阶段检测器(Faster R-CNN 那一类)在精度上确实有优势,尤其是小目标和密集遮挡场景,但它需要先出候选区域再逐个分类回归,推理耗时通常是单阶段的两到三倍,而且部署时后处理逻辑复杂,边缘设备上很难压到实时。锥桶这个类别本身语义单一,没有细粒度分类需求,两阶段那点精度增益在实际场景里换不回延迟代价。

至于更新的版本,理论上精度更好,但如果你的团队已经有 YOLOv5 的训练脚本、导出流程和部署代码,换版本意味着整条链路重新验证一遍,包括数据格式、增强策略、导出算子兼容性、后处理参数全部要重调。我在一个项目里试过中途换版本,结果 ONNX 导出后某个算子在某款推理框架上不支持,回退折腾了两天。所以我的建议是:新项目可以评估新版,已有链路稳定的情况下,把精力放在数据和后处理上,收益远大于换模型。

YOLOv5 本身按宽度和深度分成 n/s/m/l/x 五档。锥桶是单类别、形状规整的目标,我实测 s 档(约 720 万参数)在自建数据集上就能到 mAP@0.5 0.93 左右,m 档大概再高 1.5 个点,但推理耗时增加约 80%。如果部署在算力紧张的边缘盒子上,s 甚至 n 都够用;如果只跑在服务器端做离线分析,直接上 m 更省心。

模型档位参数量(约)640 输入单帧延迟(桌面 GPU,FP16)适用场景
n1.9M4~6 ms边缘设备、多路视频并行
s7.2M7~10 ms主流选择,性价比最高
m21.2M14~18 ms服务器端、精度优先
l46.5M25~30 ms离线分析、远距离小目标

提示:上表延迟是桌面级 GPU 上 batch=1、FP16 的实测区间,具体数值受驱动、TensorRT 版本、显存带宽影响很大,不要直接当成选型依据,一定要在自己的硬件上跑一遍。

2.2 整条链路的四个环节与各自的验收标准

很多人做这类项目,习惯先把模型训出来再看效果,结果发现指标挺好但现场不能用。我的做法是先定验收标准,再倒推每个环节要做到什么程度。

环节一:数据采集与标注。验收标准不是"标了多少张",而是"覆盖了多少种真实工况"。我一般按场景维度列表格:白天顺光、白天逆光、黄昏、夜间有路灯、夜间无路灯、雨天、隧道出入口、密集摆放、单桶远距离、倒地桶、部分遮挡。每个格子至少 200 个实例,缺哪个补哪个。

环节二:训练与验证。验收标准是验证集 mAP@0.5 ≥ 0.90,同时召回率在置信度 0.25 阈值下 ≥ 0.92。只看 mAP 容易被高分框拉高,实际漏检才是现场最痛的问题。

环节三:部署与性能。验收标准是端到端延迟(含预处理和后处理)≤ 30 ms,连续 1000 帧无内存增长,FP16 量化后精度掉点 ≤ 1 个 mAP 点。

环节四:现场回归。拿一段没参与训练的真实视频跑一遍,人工数漏检和误检。这一步最容易被省略,但恰恰是最能暴露问题的。我遇到过验证集 0.94、现场视频里隧道口连续漏检的情况,原因是训练集里隧道场景只有几十个实例,模型根本没学会那种低对比度下的橙色。

注意:四个环节的验收标准要在动手前和需求方对齐,尤其是"多远算远""漏检多少次算不可接受"。这类指标一旦模糊,后期会陷入无休止的返工。

2.3 数据流和目录结构:先把地基打平

YOLOv5 对数据组织方式有固定约定,早期把目录搞乱,后面每次换数据集都要重新整理。我的习惯是固定一套结构,所有项目共用:

cone_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── cone.yaml

划分比例我一般用 8:1:1,但如果场景种类多、单场景样本少,会改成按场景分层抽样,保证验证集里每个场景都有实例。随机划分在这类任务里是个坑:同一段视频抽出来的相邻帧高度相似,随机分到训练集和验证集会造成"数据泄漏",验证指标虚高好几倍。正确做法是按视频片段或按采集批次划分,同一段连续视频只能整体进一个集合。

cone.yaml的写法很直接:

path: ../cone_dataset train: images/train val: images/val test: images/test nc: 1 names: ['cone']

单类别时nc: 1,标签文件里每行是0 x_center y_center width height,全部归一化到 0~1。这里有个细节:如果后续想区分"直立锥桶"和"倒地锥桶",不要急着加类别,先看业务是否真的需要区分。加一个类别意味着模型要在特征空间里多分一瓣,小目标本来就特征弱,分成两类后每一类的样本量减半,精度通常会掉。我一般先用单类别跑通,确有需要再做多类别。

3. 数据集构建与标注规范

3.1 采集策略:场景覆盖比数量更重要

我见过太多"两万张图训不出效果"的案例,翻开一看,八成是从同一段路上连续抽帧,画面几乎一模一样。这种数据集对模型的价值极低,因为信息冗余度太高。

我的采集原则是"三三制":三分之一来自固定机位长时间录制(覆盖光照变化),三分之一来自移动采集(覆盖视角和距离变化),三分之一来自主动构造的极端场景(近距离密集摆放、远距离单桶、倒地、遮挡)。每类场景我至少保证 1500 个标注实例,低于这个数量模型很难稳定学到特征。

具体到锥桶,有几个必须覆盖的工况:

  • 距离梯度:从 5 米到 60 米,每隔 5 米都要有样本。远距离小目标是精度的主要瓶颈,如果训练集里 80% 的框都是大框,模型会偏向预测大目标。
  • 光照极端值:正午强光下的高光溢出、逆光剪影、夜间车灯直射、隧道出入口的明暗突变。逆光时锥桶的橙色会变成接近黑褐色,和路面几乎同色,这类样本不补,现场必漏。
  • 遮挡形态:被前车挡住下半部分、被护栏挡住一半、两个锥桶前后重叠。遮挡样本的标注要框住可见部分还是推断完整范围?我的做法是框住可见部分,让模型学会"看到多少框多少",因为推断完整范围会让框的位置在遮挡时严重偏移,下游做距离估计会出错。
  • 形态变异:倒地的、被压扁的、套了反光膜的、贴了广告的、颜色褪成粉白的。这些在施工路段非常常见。

3.2 标注规范:那些只有踩过才知道的细节

标注质量对最终精度的影响,我个人的体感是超过模型选型的。同一个数据集,标注规范不统一和规范统一,mAP 能差 5 个点以上。

框的边界怎么定。锥桶有个底座,底座通常比桶身宽。很多标注同学会把底座一起框进去,也有人只框桶身。两种都能训,但必须统一。我选的是"框住可见的完整轮廓,包括底座",理由是底座是锥桶的一部分,框进去后框的高宽比更稳定,有利于后续用长宽比做误检过滤。

长宽比先验。标准锥桶的高宽比大约在 1:2.5 到 1:3 之间(高:宽),倒地后接近 3:1。这个先验在部署阶段非常有用,可以在后处理里过滤掉明显不符合比例的可疑框,比如模型把一块橙色广告牌框成了锥桶,比例一看就不对。所以我要求标注时严格贴合,不要为了"好看"把框画得方正。

最小标注尺寸。低于 8×8 像素的实例我建议直接不标。原因很实际:这种目标在 640 输入下经过 letterbox 缩放后只剩 2~3 个像素,模型学不到有效特征,标了反而引入噪声,让损失函数在这些样本上反复震荡。如果业务要求必须检出这类极远目标,正确做法是提高输入分辨率,而不是硬标。

模糊样本的处理。运动模糊到看不清轮廓的、雨幕严重遮挡的,我的原则是"能确定是锥桶就标,不能确定就不标"。存疑的样本宁可丢弃,也不要猜。模型对错误标签的容忍度远低于你的想象。

提示:标注完成后一定要做一轮交叉复核,随机抽 10% 让另一个人重新标,然后算一下 IoU 一致性。如果一致率低于 0.85,说明规范写得不够细,回去补规范再重标,别急着训练。

3.3 数据增强的参数取舍:默认值不是万能钥匙

YOLOv5 的增强参数都在hyp.yaml里,默认值是为通用数据集调的,直接套到锥桶场景会有问题。我逐个说。

颜色增强(hsv_h / hsv_s / hsv_v)。默认 0.015 / 0.7 / 0.4。锥桶的颜色是强判别特征,但真实场景里颜色变化极大——夜间偏暗红、逆光偏黑、褪色后偏粉白。所以饱和度增强我保留 0.7,亮度增强保留 0.4,但色相增强我降到 0.008,因为色相大幅偏移会把橙色推成黄色或红色,模型可能学到错误的颜色先验,遇到真橙色反而犹豫。

几何增强(degrees / translate / scale / shear / perspective)。默认 scale=0.5,这个幅度对锥桶偏大。锥桶是小目标,缩放 0.5 倍后再经过 mosaic 拼接,很容易变成几个像素的噪点。我通常把 scale 降到 0.3,translate 保持 0.1,degrees 设为 0(车载摄像头基本没有旋转),perspective 保留 0.0005 用于模拟视角变化。

Mosaic 增强。默认 1.0,四张图拼一张,是小目标检测的利器,因为它让模型在一张图里看到更多的小目标实例。但 mosaid 有个副作用:拼接边界处会出现不自然的截断,模型可能学到"边缘有锥桶"这种虚假关联。我在训练后期(最后 10 个 epoch)会把 mosaic 关掉,让模型在真实分布上做最后的收敛,这个技巧对小目标任务提升明显,一般能涨 0.5~1 个点。

mixup 和 copy_paste。默认都是 0。mixup 对锥桶这种单类别任务收益不大,反而会让小目标更模糊,我保持关闭。copy_paste 是分割任务用的,检测任务不用管。

# hyp.yaml 关键项修改示例 hsv_h: 0.008 # 色相降幅,保护橙色先验 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 # 车载视角无明显旋转 translate: 0.1 scale: 0.3 # 缩放幅度降低,保护小目标 shear: 0.0 perspective: 0.0005 flipud: 0.0 # 上下翻转在道路场景不成立 fliplr: 0.5 # 左右翻转合理 mosaic: 1.0 mixup: 0.0

注意:flipud一定要设 0。上下翻转后的锥桶是"倒挂"的,现实中不存在,模型学到的特征会跑偏。这个参数在通用数据集上默认是 0,但有人为了增加数据量随手改成 0.5,这是在给自己挖坑。

4. 模型训练与超参配置

4.1 环境与版本:先把地基锁死

复现性在这类项目里比想象中重要。我踩过最亏的一次坑,是同一个脚本在另一台机器上训练结果差了 3 个点,查了半天发现是驱动版本不同导致的非确定性算子行为。所以现在我的习惯是把环境写成文件,锁死版本。

# 建议用虚拟环境隔离 python -m venv cone_env source cone_env/bin/activate pip install torch==1.13.1 torchvision==0.14.1 pip install -r requirements.txt

关键版本组合我实测稳定的是 PyTorch 1.13 + CUDA 11.7 这一套,往上的 2.x 版本也能跑,但某些增强算子的行为有细微差异,换版本后建议先跑 5 个 epoch 对比 loss 曲线,确认没有异常再全量训练。

另外,训练前务必确认这几件事:数据路径没写错(cone.yaml里的path是相对路径还是绝对路径)、标签文件数量和图片数量一致、没有空的 label 文件(空文件会被当成负样本,如果本意不是加入背景图,会造成类别不均衡)。我写了个两行的小检查脚本,每次换数据集都跑一遍:

import glob, os imgs = set(os.path.splitext(os.path.basename(p))[0] for p in glob.glob('images/train/*')) lbls = set(os.path.splitext(os.path.basename(p))[0] for p in glob.glob('labels/train/*')) print('图片数', len(imgs), '标签数', len(lbls)) print('缺标签', list(imgs - lbls)[:10]) print('多余标签', list(lbls - imgs)[:10])

这段代码很短,但帮我省过好几次事故——有一次某个子目录的标签漏拷,模型训练时自动跳过了那些图,指标看着还行,实际训练集少了两千张。

4.2 训练命令与超参计算过程

基础训练命令:

python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data cone_dataset/cone.yaml \ --weights yolov5s.pt \ --hyp data/hyps/hyp.cone.yaml \ --project runs/train --name cone_v1 \ --cache

每个参数都有讲究,我一个个拆。

--img 640。这是输入分辨率。前面说过,YOLOv5 的下采样总步长是 32,640 输入对应最小特征图 20×20,每个格子覆盖原图 32×32 的区域。远距离的锥桶如果只占 15 个像素高,在最小特征图上根本无法表达。如果你的业务对 40 米外的锥桶有硬性要求,两个选择:把输入提到 960 或 1280(耗时按面积增长,960 约是 640 的 2.25 倍),或者改用带 P2 检测头的结构(在 stride=4 的特征图上加检测头,代价是显存和耗时)。我用 960 输入在远距离场景上实测召回率提升了约 6 个点,代价是延迟从 9 ms 涨到 20 ms,需要看你的预算。

--batch 16。批大小和显存直接相关。8G 显存的卡,640 输入、s 档模型、开启 mosaic,batch 16 大概占用 6.5G,是比较稳的。如果爆显存,先降 batch 再考虑降 img,不要反过来,因为输入分辨率对精度的影响远大于批大小。

学习率缩放。默认lr0=0.01是针对 batch 64 左右调的。如果我用 batch 16,按线性缩放规则应该是 0.01 × 16/64 = 0.0025。但实践中完全线性缩放在小 batch 下偏保守,收敛慢。我的经验值是 batch 16 用 0.005~0.008,batch 32 用 0.01。判断标准看前 5 个 epoch 的 loss 曲线:如果 loss 下降过慢且震荡小,说明学习率偏小;如果 loss 剧烈震荡甚至上升出现 NaN,说明偏大。

训练轮次。单类别、一万张量级的数据集,150 epoch 基本够。但我更看重的是"什么时候停",而不是固定轮次。判断依据是验证集 mAP 连续 20 个 epoch 没有提升,就认为收敛了。YOLOv5 自带--patience参数可以设置早停,我用 30。

--cache。把图片缓存到内存或磁盘,能显著加速数据加载。但如果数据集超过内存容量,加这个参数会拖慢甚至崩溃。我的判断是:数据集体积低于内存的 60% 就加,否则用--cache disk或者干脆不加。曾经在一个 40G 数据集上加了--cache,机器直接卡死,排查了半天才想到是这个参数。

4.3 训练过程中的监控:从曲线里读出问题

训练启动后,runs/train/cone_v1/下会生成 loss 曲线、mAP 曲线、PR 曲线和一排验证集预测图。我一般重点看三张图,而且只看它们的前 30 个 epoch,因为后期曲线趋于平缓,看不出问题。

第一张:三个 loss 分量的相对关系。YOLOv5 的损失由box_loss(定位)、obj_loss(置信度)、cls_loss(分类)组成。单类别任务里cls_loss应该很快降到很低,如果它一直居高不下,说明标签有问题——最常见的是类别索引写错,或者某批数据的标签格式不对。

box_loss和obj_loss的相对关系更能说明问题。如果obj_loss明显偏高而box_loss正常,通常意味着背景误检多,模型对"什么不是锥桶"学得不好,这时候应该增加纯背景图(不含任何锥桶的路面图)。我一般会往训练集里掺 2%~5% 的背景图,比例太高会让模型过于保守,召回率下降。

第二张:mAP@0.5 和 mAP@0.5:0.95 的差距。这两个指标的差值反映了定位精度。如果 mAP@0.5 有 0.92 但 mAP@0.5:0.95 只有 0.55,说明模型能大致找到位置但框不准。原因通常是标注框不一致,或者目标尺度分布太集中。我遇到过一次,查下来是两位标注同学对底座的处理不一致,一半框了底座一半没框,统一之后 mAP@0.5:0.95 直接涨了 8 个点。

第三张:验证集的预测可视化图。这张图的信息量最大。我一般会重点看三类样本的预测:远距离小目标、密集摆放、逆光场景。如果远距离目标普遍漏检,看框的置信度是不是集中在 0.1~0.3 之间——如果是,说明模型"隐约感觉到了但不敢确认",这时候降低推理时的置信度阈值能救回来一部分,但根本解决还是要提升输入分辨率或补远距离样本。

4.4 一个容易被忽略的点:验证集要定期换血

我有个习惯,每轮迭代结束后,从新采集的数据里抽一批加入验证集,同时把模型在旧验证集上表现最好的那部分样本移出。原因是模型会"记住"验证集,尤其是当验证集只有几百张图时,反复调参很容易过拟合到验证集上。

判断是否过拟合验证集的方法很朴素:拿一段完全没碰过的新视频跑一遍,人工统计漏检率。如果新视频上的表现比验证集差 10 个点以上,说明验证集已经失去代表性了。我一般每完成一轮调参就做一次这样的"盲测",虽然费时间,但能避免上线后翻车。

5. 指标评估与定向调优

5.1 mAP 之外,业务真正关心的指标

mAP 是学术指标,业务侧关心的是另外几个数字,我通常在评估阶段就把它们算出来。

不同距离段的召回率。把验证集按标注框的高度分成四档:大于 80 像素(近距)、40~80 像素(中距)、20~40 像素(远距)、小于 20 像素(超远距),分别统计召回率。这个拆解能立刻暴露模型的短板。我最近一轮的结果是近距 0.97、中距 0.94、远距 0.81、超远距 0.42,很明显短板在远距离,后续优化就围绕这个展开,而不是盲目调全局参数。

误检率。统计每千帧的误检次数,并且要分类:是把路面反光误检了,还是把橙色衣物误检了,还是把其他橙色物体误检了。分类之后才能针对性解决。

连续帧稳定性。这个指标很少有人测,但对下游影响巨大。方法是跑一段视频,统计同一个锥桶在连续帧里被检出的比例。如果模型是"三帧检出两帧漏",即使单帧 mAP 很高,下游的跟踪和距离估计也会疯狂抖动。我的经验是,稳定性差往往不是模型问题,而是推理时的置信度阈值卡在了模型的"犹豫区间",把阈值从 0.25 降到 0.2 通常能显著改善。

评估维度我的验收线测量方法
近距召回(框高>80px)≥ 0.95按框高分段统计
远距召回(框高20~40px)≥ 0.80同上
误检率≤ 3 次/千帧人工复核
连续帧检出率≥ 0.90视频逐帧统计
端到端延迟≤ 30 ms含预处理与 NMS

5.2 漏检和误检的定向优化手段

漏检为主时,按这个顺序排查。第一,看漏检样本的尺度分布,如果集中在远距离,优先提高输入分辨率或补远距样本,不要动模型结构。第二,看漏检样本的置信度分布,如果大量落在 0.1~0.3,说明模型其实检测到了,只是分数低,把推理阈值调到 0.2 并配合后续的跟踪平滑,就能救回来。第三,检查是否有系统性偏差,比如所有隧道口的样本都漏,那就是训练集覆盖不足,回去补数据。第四,才是考虑换更大的模型档位。

误检为主时,我常用的三个手段。一是加背景图,把不含锥桶的路面、橙色广告牌、施工围挡单独截出来作为负样本,比例 3%~5%。二是加长宽比过滤,在 NMS 之后加一道规则:框的高宽比不在 1.2~4.0 范围内的直接丢弃,这一条能干掉大部分把平面橙色物体误检成锥桶的情况。三是加颜色校验,在框内统计橙色像素占比,低于阈值就丢弃,但这招在夜间会误杀,需要配合亮度判断,实现复杂度高,我一般放到最后才用。

一个具体的调优案例。早期版本在隧道出口处误检严重,模型把出口处的一块橙色施工标志牌反复检出。我先补了 800 张含该标志牌的负样本,误检下降了一半但没根除。后来分析发现,标志牌的框高宽比接近 1:1,而锥桶是 1:2.8 左右,于是加了长宽比过滤:

def filter_by_aspect(boxes, min_ratio=1.2, max_ratio=4.0): keep = [] for i, (x1, y1, x2, y2) in enumerate(boxes): w = max(x2 - x1, 1e-6) h = max(y2 - y1, 1e-6) ratio = h / w if min_ratio <= ratio <= max_ratio: keep.append(i) return keep

加上这道过滤后,该场景误检基本清零,同时因为锥桶本身比例稳定,召回率没有任何损失。这类"利用目标形状先验"的后处理,是工业落地里性价比最高的手段之一。

6. 推理部署与性能压榨

6.1 导出 ONNX 与 TensorRT:那些报错背后的原因

训练完的best.pt不能直接上生产,一般要转成 ONNX 再转 TensorRT 引擎。导出命令:

python export.py \ --weights runs/train/cone_v1/weights/best.pt \ --include onnx \ --img 640 640 \ --batch 1 \ --simplify \ --opset 12

几个参数的经验:--simplify一定要加,它会跑一遍 onnx-simplifier,把冗余算子合并掉,某些推理框架对未简化的图支持不好。--opset 12是我实测兼容性最好的版本,opset 13 以上在某些老版本 TensorRT 上会遇到Resize算子不支持的问题。--batch 1是给实时推理用的,如果做离线批处理再设大。

转 TensorRT 时最常见的两个坑:一是动态 shape 没设对,导出时用了固定 640×640,推理时送入 1280×720 的图就会报错。要么在导出时指定--dynamic,要么在推理前自己做 letterbox 到 640×640。我选后者,因为动态 shape 会让 TensorRT 无法充分优化,延迟反而更高。二是 FP16 精度掉点,量化后如果 mAP 掉了超过 1 个点,检查一下是不是某些层的数值范围超出了 FP16 表示能力,可以在转 TensorRT 时对这些层保持 FP32。

6.2 预处理和后处理:最容易出错的地方

letterbox 的坐标还原。YOLOv5 推理时会把原图等比缩放到 640×640,不足的部分用灰色填充。这意味着输出的框坐标是在填充后的图上的,必须还原回原图坐标,否则框会整体偏移。公式不复杂:

# r 为缩放比例,dw/dh 为左右/上下填充量 r = min(640 / w0, 640 / h0) new_w, new_h = round(w0 * r), round(h0 * r) dw, dh = (640 - new_w) / 2, (640 - new_h) / 2 # 还原:先减填充,再除以缩放比 x1 = (x1_padded - dw) / r y1 = (y1_padded - dh) / r x2 = (x2_padded - dw) / r y2 = (y2_padded - dh) / r

这个还原我见过至少三个项目写错过,症状是框整体往左上或右下偏,偏移量随图片宽高比变化。写完之后一定要拿一张已知目标位置的图验证,比如手动在图上画个标记,看还原后的框是否对齐。

NMS 的阈值取舍。锥桶经常密集摆放,前后重叠严重。iou_thres设得太低(比如 0.3),相邻的两个锥桶会被合并成一个,造成漏检;设得太高(比如 0.7),同一个锥桶可能出现多个重叠框。我的经验值是 0.45~0.5,密集场景取 0.5。另外 YOLOv5 默认用的是 class-agnostic NMS,单类别任务无所谓,多类别时要注意会不会跨类别抑制。

后处理耗时。很多人只关注模型前向耗时,忽略了 NMS。在检测框数量多的场景(比如一帧里 50 个锥桶),Python 实现的 NMS 可能要 3~5 ms,占总延迟的 20%。优化办法是用torchvision.ops.nms的 GPU 版本,或者把 NMS 也一起导出到 TensorRT 里(YOLOv5 的export.py支持--include engine时把 NMS 融进去,但需要特定版本配合)。

6.3 边缘设备上的实测数据与取舍

不同硬件的实测数据差别很大,我列一组自己测过的参考值,都是 640 输入、YOLOv5s、FP16、包含前后处理的端到端延迟:

平台类型端到端延迟可稳定运行的视频路数备注
桌面级独立显卡12~15 ms8 路以上显存充足,可开更大 batch
嵌入式模组 A(中端)28~35 ms2~3 路需降分辨率到 512
嵌入式模组 B(入门)55~70 ms1 路建议换 n 档模型
纯 CPU(服务器级)180~250 ms不推荐仅适合离线分析

入门级模组上跑不动 s 档是常态,我的处理办法是换 n 档 + 输入降到 512,实测延迟能压到 35 ms 左右,精度掉约 3 个点。这个取舍要看业务能不能接受,如果只是做锥桶计数或者区域入侵判断,3 个点完全可以接受;如果要做精确的距离估计,就得考虑加一路专门的算力。

提示:边缘设备上一定要做长时间稳定性测试。我遇到过跑 20 分钟就内存缓慢增长的情况,原因是每次推理都新建了一个 tensor 没释放,短时间测试根本发现不了。测试方法很简单:循环推理 5000 次,每 500 次打印一次内存占用,看曲线是否平直。

7. 常见问题排查速查表

7.1 训练阶段的问题与排查路径

现象最可能的原因排查动作
loss 出现 NaN学习率过大或标注坐标越界检查标签是否有 >1 或 <0 的值,lr 降到 0.003
mAP 一直不动数据路径错、标签全空、类别数不匹配打印一个 batch 的标签看看
训练集 mAP 高、验证集低数据泄漏或过拟合检查是否按视频片段划分,加增强
只有大目标能检出小目标样本不足或输入分辨率低统计框高分布,考虑提到 960
误检集中在某一类物体缺负样本针对性补背景图
训练后期指标突然下降学习率衰减策略或增强过强关掉 mosaic 观察最后 10 轮

这里重点说"标签坐标越界"这一条。归一化坐标理论上在 0~1 之间,但标注工具在边界处可能导出 1.0001 这样的值,YOLOv5 在某些版本里不会报错,而是直接算出异常的 loss,跑几十轮后才崩。我的做法是训练前跑一遍校验:

import glob bad = [] for f in glob.glob('labels/train/*.txt'): for line in open(f): vals = list(map(float, line.split()[1:])) if any(v < 0 or v > 1 for v in vals): bad.append(f); break print('越界标签数', len(bad))

7.2 部署阶段的问题与排查路径

现象最可能的原因排查动作
框整体偏移letterbox 还原公式错用已知位置的图验证
推理结果全是同一类类别映射表读错核对 names 顺序
ONNX 转 TensorRT 失败算子不支持或 opset 不兼容加 --simplify,opset 降到 11/12
精度比 PyTorch 掉很多预处理归一化不一致检查是否除以 255,通道顺序是否 BGR/RGB
延迟波动大显存不足导致换页降 batch,检查是否有其他进程占卡
长时间运行崩溃内存泄漏循环推理 5000 次观察内存曲线

"预处理不一致"这个坑我必须单独说。YOLOv5 训练时输入是 RGB、除以 255 归一化到 0~1。但很多图像采集库默认给的是 BGR,如果部署时忘了转,模型看到的是通道颠倒的图,橙色会变成蓝色,精度直接崩到接近随机。这个错误的表现很迷惑:模型能检出一些框,但位置和置信度都很奇怪。排查方法很简单,拿同一张图分别用 PyTorch 原版推理和部署版推理,对比输出的框,差异大就是预处理问题。

7.3 现场落地阶段的问题

锥桶被检出但 ID 频繁跳变。这是检测后面的跟踪环节的问题,不是检测本身的锅。但如果检测的置信度在阈值附近抖动,跟踪必然不稳定。解决办法是给检测输出做一点时间上的平滑,比如连续三帧都检出才确认,或者用简单的滑动平均平滑框的位置。

夜间误检率飙升。夜间图像噪声大,橙色在路灯下会偏黄,模型容易把车灯反光、路面标线误检成锥桶。我的处理是夜间场景单独补数据,同时在部署时加一道亮度判断:图像整体亮度低于某阈值时,把置信度阈值从 0.25 提到 0.4,宁可漏检也不误检,因为夜间误检对下游的干扰远大于漏检。

雨天性能下降。雨幕会让图像整体模糊,小目标直接消失。这个靠模型解决不了,只能靠补雨天数据 + 适当提高输入分辨率。我在雨天数据上实测,960 输入比 640 输入的召回率高 9 个点,提升非常明显,因为高分辨率保留了更多细节。

8. 我个人在这几轮迭代里的几点体会

数据集的价值被严重低估了。我前后三轮迭代,模型结构没动过,光是把标注规范统一、补了远距离和逆光样本、加了 4% 的负样本,mAP@0.5 就从 0.87 涨到 0.94,远距离召回从 0.68 涨到 0.83。相比之下,我从 s 换到 m 只涨了 1.5 个点,延迟却翻倍。如果只能改一件事,改数据。

输入分辨率的取舍要早点想清楚。很多人一开始用 640 跑通,等上线才发现远距离不行,这时候改分辨率意味着所有后处理参数、部署引擎、性能预算都要重来。我的建议是项目启动时就按业务的"最远检测距离"倒推需要多少像素,然后一次性定死输入尺寸。

后处理的规则比模型更"聪明"。长宽比过滤、颜色校验、亮度自适应阈值这些规则,加起来能解决 60% 以上的误检问题,而且零延迟成本。这些规则不需要多高深的技术,需要的是对场景的理解——你得知道锥桶长什么样、在什么场景下会出问题。这也是我觉得做这类项目最有意思的地方:模型只是中间产物,真正的功夫在数据和工程细节里。

最后一件事,别迷信验证集指标。我现在的习惯是,每次提交前都拿一段完全没参与训练的真实视频,从头到尾看一遍,手动数漏检和误检。这个过程很枯燥,但它抓到的问题,比任何曲线都真实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 20:59:49

营销技能的本质是可验证的商业反应系统

1. “marketingskills”不是技能清单&#xff0c;而是一套可验证的商业反应系统你搜“marketingskills”&#xff0c;页面弹出的全是“20个必备营销技能”“Top 10 Marketing Skills for 2024”这类标题——点进去一看&#xff0c;基本是“沟通能力”“数据分析”“内容创作”“…

作者头像 李华
网站建设 2026/10/27 20:59:45

K8S持久化存储:PV/PVC、StorageClass与CSI实践

容器里跑着跑着&#xff0c;Pod 被驱逐一次、节点宕机一次&#xff0c;或者只是简单地滚动更新了一版镜像&#xff0c;日志里就冒出一句no such file or directory——数据没了。这个场景在 K8S 持久化存储这个话题下几乎天天上演&#xff0c;也是我从单体应用迁到 K8S 之后踩得…

作者头像 李华
网站建设 2026/10/27 20:59:29

编程操作符全解析:从基础到高阶应用

1. 操作符深度解析&#xff1a;从基础到高阶的完整指南在编程世界里&#xff0c;操作符就像数学中的加减乘除符号&#xff0c;但功能远不止于此。作为代码中最基础的构建块&#xff0c;操作符直接决定了程序的行为逻辑和数据流向。很多初学者往往低估了操作符的重要性&#xff…

作者头像 李华
网站建设 2026/10/27 20:58:40

e2e测试的本质:可控现实模拟器与工程临界点

1. “e2e”不是缩写&#xff0c;是工程实践的临界点很多人第一次看到“e2e”三个字母&#xff0c;下意识会去查全称——End-to-End&#xff1f;没错&#xff0c;但这个答案只开了个门缝。真正让“e2e”在工程师日常中高频出现、反复调试、深夜报错、团队争论的&#xff0c;从来…

作者头像 李华
网站建设 2026/10/27 20:58:16

C# WinForms学生信息管理系统(含SQL Server完整源码)

简介&#xff1a;本资源是一套面向编程初学者与教育信息化实践者的《学生信息管理系统》完整开发包&#xff0c;聚焦小程序级数据库应用开发&#xff0c;适用于高校计算机基础课程设计、教务管理工具原型开发及PythonSQLite入门实战。压缩包共4个文件&#xff0c;含2份Word文档…

作者头像 李华
网站建设 2026/10/27 20:58:07

Linux Qt ffmpeg摄像机视频监控项目实战:从取流解码到多路预览

简介&#xff1a;面向Linux平台多媒体开发者的综合实践资源&#xff0c;聚焦Qt图形界面、大华摄像机SDK设备控制与ffmpeg视频解码三条技术主线&#xff0c;适合已具备C基础、希望打通「界面—设备—码流」链路的初中级开发者参考。压缩包共约2000个文件、156.52MB&#xff0c;以…

作者头像 李华