简介:面向交通管理、运输安全监控与车辆身份识别场景的技术文献,围绕高速公路危化品运输车辆货物类型自动识别问题,给出基于YOLOv3的实时目标检测思路。PDF共1个文件,约2.41MB,内容来自《山西交通科技》2020年第4期,适合智能交通、计算机视觉及危化品运输安全方向的学习者与工程人员参考。文中覆盖机器视觉抓拍系统设计、高清监控与红外补光采集、危化品车辆货物类型数据集构建、labelImg标注流程、YOLOv3网格预测与损失函数,以及10000张背面图像、23类22174个标注框的训练实验设置和不同光照天气下的识别结果。已有107人学习,可用于快速了解危化品车辆标志识别从数据采集到模型验证的完整技术链路,并获取论文写作与课题申报时可借鉴的实验组织、指标描述和场景落地思路。
1. 需求先立住:卡口那几秒决定救援带不带防爆装备
危险货物运输车辆跑的是长距离、跨地区线路,事故的时间地点几乎无法预判,现场处置的第一个分岔口往往不是“派几台车”,而是“罐里到底装的是什么”。基于深度学习的危化品车辆货物类型识别技术,填的就是这个信息缺口:车辆驶过高速公路卡口、抓拍机拍下车尾的瞬间,把车尾那块菱形危险货物标志读出来,把类别实时回传给监管中心。它适合三类读者参考——正在做目标检测方向毕设或课设的学生、要给交通或园区做车辆管控方案的算法与集成工程师、以及需要把“识别结果”接进应急处置流程的系统设计方。这不是一个跑通就完事的分类 Demo,从抓拍硬件、数据集口径、YOLOv3 训练到验收指标,每一环都会决定最终能不能上线。
2. 技术选型:为什么单阶段检测器更适合卡口实时场景
先把链路摆清楚,再谈模型。很多方案翻车不是因为网络选错,而是把“识别”当成了一个孤立的离线任务,等到要接入卡口才发现时延、带宽、成像条件全都不配合。这一章把业务链路拆成三段,再解释为什么在实时约束下,单阶段检测器是更合理的起点。
2.1 业务链路拆解:抓拍、边缘推理、中心汇聚三段
完整链路分三段。第一段是抓拍单元,卡口摄像机架在收费广场或主线门架,镜头对着车辆尾部,因为危险货物标志按规定贴在车尾;夜间和逆光靠红外闪光灯补光。第二段是边缘计算单元,一台嵌入式智能硬件挂在摄像机旁边,本地完成推理,只把结构化结果(时间、点位、车牌、货物类别、置信度)和命中危化品车辆的原图往上送。第三段是中心平台,负责汇聚、检索、告警推送和与应急处置流程对接。
把推理放在边缘而不是中心,是一个被带宽逼出来的决定。4096×2160 的抓拍图,单张压缩后仍有 1~2 MB,一条主线卡口全天过车量按万级计算,全量回传对专线是灾难。边缘侧只回传结构化字段,一天的报文量还不如几十张原图大。这样做的代价是边缘盒子算力有限,模型不能太深,输入分辨率也不能无脑往上堆,这条约束会一路传导到后面的网络选择和输入尺寸设定上。
提示:链路设计阶段就要把“单帧推理时延预算”写进需求文档,通常从车辆触发抓拍到结果入库要求控制在 1 秒以内,其中留给模型推理的时间一般不超过 300 毫秒,剩下的留给解码、上传和平台写入。
时延预算定下来之后,模型选型其实已经没多少自由度了。三百毫秒的窗口里,要完成图像解码、预处理、前向推理、后处理(阈值过滤加非极大值抑制),还要留出抖动余量,模型本身的前向耗时就得压到两百毫秒以内。这个数字直接排除了参数量大、需要两阶段串行计算的方案,也是后面所有取舍的依据。
2.2 两阶段与单阶段的取舍:精度账要放在时延账后面算
目标检测的主流方法分两类。两阶段检测器以 R-CNN 系列为代表,先由区域建议网络生成一批候选区域,再对每个候选区域提取固定长度的特征向量,最后用分类器判定类别并回归边框。它的思路是“先找可能有东西的地方,再仔细看”,精度上限高,但两步串行,候选区域越多耗时越长,而且每个候选都要过一次特征提取,计算量随候选数量线性增长。
单阶段检测器把检测直接当成回归问题:在特征图上预先铺好锚点,密集覆盖整幅图像的所有空间位置,一次前向就同时输出位置偏移、置信度和类别概率。代表模型是 YOLO 系列、SSD 和 RetinaNet。它没有独立的区域建议环节,速度优势明显,代价是正负样本极度不均衡,早期版本对小目标和密集目标略吃亏。
放到卡口这个具体场景里,取舍就很清楚了。目标位置高度结构化——车辆背面基本占据画面中部,标志贴在车尾固定高度区间,不需要模型在整幅图里大海捞针;类别判定依赖的是标志上的图案和文字纹理,属于细粒度识别;而实时性是硬指标。三条件叠加,单阶段是更合适的起点。YOLOv3 在这一类里又多了两个实际好处:一是用了三个尺度的特征图做预测,对小目标比早期版本友好;二是工程生态成熟,权重、配置文件、转 ONNX 或 TensorRT 的路径都有大量现成实践,边缘盒子上的部署成本低。
需要说清局限。YOLOv3 在密集小目标上的表现不如后续版本,如果一张车尾同时贴了三四块紧挨着的标志,漏检概率会上升;骨干网络也偏重,边缘设备上想再压时延就得换轻量化骨干或者做量化。这些不是选型错误,是需要在方案里提前写明的边界条件。
2.3 YOLOv3 的检测原理:S×S 网格、B 个先验框与四项损失
YOLOv3 把输入图像经过骨干网络和特征金字塔,输出三个尺度的特征图,比如输入 608×608 时得到 19×19、38×38、76×76 三层。每层把特征图划成 S×S 个网格,每个网格分配 B 个先验框(通常是 3 个),每个先验框输出一组预测:中心点偏移、宽高缩放比、置信度,以及类别概率。三层加起来,模型一次性产出所有候选框,再经过置信度过滤和非极大值抑制得到最终结果。
训练时的损失由四项相加构成:中心点误差、宽高误差、置信度误差、分类误差。这四项不是简单相加就完事,每一项的掩码和权重都不一样,写代码时最容易在这上面出错。
# YOLOv3 四项损失的组装逻辑(示意,不绑定具体框架) # pred / target 形状一致:[N, A, H, W, 5 + C] # A 为先验框数量,C 为类别数,5 表示 tx, ty, tw, th, conf def yolo_loss(pred, target, obj_mask, noobj_mask, cls_target): # 1) 中心点误差:只对含目标的网格计算,常用二值交叉熵 loss_xy = bce(pred[..., 0:2], target[..., 0:2], mask=obj_mask) # 2) 宽高误差:回归的是相对先验框的缩放比,用平方误差更稳 loss_wh = mse(pred[..., 2:4], target[..., 2:4], mask=obj_mask) # 3) 置信度误差:正负样本分开算,负样本要降权 loss_conf_pos = bce(pred[..., 4], target[..., 4], mask=obj_mask) loss_conf_neg = bce(pred[..., 4], target[..., 4], mask=noobj_mask) * 0.5 loss_conf = loss_conf_pos + loss_conf_neg # 4) 分类误差:多标签二分类,因为属性层与细分层标签可能同时命中 loss_cls = bce(pred[..., 5:], cls_target, mask=obj_mask) return loss_xy + loss_wh + loss_conf + loss_cls几个参数值得单独说明。obj_mask定义的是正样本,也就是“哪个先验框负责这个目标”,通常按与真实框的 IoU 最大来指派,指派错了模型会一直学不会。noobj_mask只作用于置信度项,因为负样本只需要教会模型“这里没东西”,不需要参与坐标和分类回归。负样本权重压到 0.5 甚至更低,是因为一幅图里负样本数量是正样本的几十上百倍,不降权的话置信度分支会被负样本主导,模型倾向于全部预测为背景。
分类项用二值交叉熵而不是 softmax,是这个双层标签体系下的必要选择。属性层(易燃、腐蚀等粗粒度)和细分层(具体品名类别)可能同时命中同一个标志,用 softmax 会强制类别互斥,等于人为制造标签冲突。这一点在数据集设计那一章会更明显。
3. 数据集构建:10000 张车尾图与 23 类标注的落地口径
模型结构是公开的,真正决定这套方案能不能用的是数据集。这一章把采集、筛选、标注、类别体系、划分五件事按实际操作顺序讲清楚,每一步都给出可复现的口径,因为标注环节的随意性是这个项目里最大的隐性风险。
3.1 抓拍系统与成像参数:4096×2160、红外补光与逆光难题
抓拍单元用的是 900 万像素级的卡口抓拍摄像机,输出 4096×2160 的图片,配红外闪光灯做弱光补光,抓拍位置选在收费广场出口和主线门架两类点位,都是为了拍到相对正面的车尾。选这个分辨率不是堆参数,而是因为危险货物标志的菱形牌尺寸有限,在画面里往往只占几百像素见方,分辨率不够的话标志上的图案和数字根本区分不开。
| 环节 | 关键参数 | 典型取值 | 设计意图 |
|---|---|---|---|
| 成像 | 输出分辨率 | 4096×2160 | 保证车尾标志有足够像素 |
| 成像 | 补光方式 | 红外闪光灯 | 夜间与弱光下保住纹理 |
| 抓拍 | 触发方式 | 线圈或视频触发 | 保证车辆处于画面中心区 |
| 传输 | 回传内容 | 结构化结果 + 命中原图 | 控制专线带宽占用 |
| 推理 | 部署位置 | 卡口旁嵌入式设备 | 满足 1 秒级时延预算 |
成像上真正的难点有三个。一是逆光,早晚时段太阳低角度直射镜头,车尾整体压暗,标志变成一团黑影,靠曝光策略很难两全。二是夜间,红外补光能救回纹理,但色彩信息丢失,标志的底色(红、黄、蓝)区分度下降,而底色恰恰是判断类别的重要线索。三是雨雾天,水膜反光和能见度下降会同时拉低成像质量。这三类样本在采集阶段就得有意识地多留,不能等训练完发现夜间指标崩了再回头补数据。
3.2 从全量过车图里筛样本:三步筛选法
卡口抓的是所有车,不是危化品车,所以第一步是把危化品运输车辆挑出来。可行的做法是先按车辆备案库或专用号段做初筛,把命中记录对应的原图拉出来,这一步能把数据量从百万级压到万级。第二步是人工过一遍,剔掉非车尾角度、严重遮挡、运动模糊、标志被货物遮挡的图,保留标志清晰可辨的样本。第三步是按类别和场景做均衡,避免某一类车在某个月份扎堆。
最终形成的数据集规模是 10000 张危化品运输车辆背面图像,全部来自真实道路监控场景,覆盖不同照度、不同天气、不同点位。这个量级对 23 类目标检测来说偏紧,尤其是稀有类,所以后面的增强和阈值策略必须配合上,不能指望模型自己学会。
注意:样本筛选阶段就要按“日期 + 点位 + 时段”三个维度做记录,不是为了好看,而是后面划分数据集时唯一能防止数据泄漏的依据。
3.3 标注规范:labelimg、xml 字段与边界框口径
标注工具用 labelimg,输出 PASCAL VOC 格式的 xml,和原图同名,一个 xml 对应一张图,里面记录每个目标的位置和类别。格式本身很简单,难的是口径统一:到底框到哪里、倾斜多少度还算、遮挡多少算不可标、一块牌上有两个图案算一个框还是两个框。
<annotation> <filename>hwy_0412_000317.jpg</filename> <size> <width>4096</width> <height>2160</height> <depth>3</depth> </size> <object> <name>flammable_liquid</name> <!-- 必须与冻结后的类别表逐字一致 --> <difficult>0</difficult> <!-- 严重遮挡或极模糊标 1,训练时可降权 --> <bndbox> <xmin>2871</xmin><ymin>1204</ymin> <xmax>3082</xmax><ymax>1419</ymax> </bndbox> </object> </annotation>口径上我一般会定死几条:只框标志牌本体,不把车体、支架、反光条框进来;标志倾斜但整体可辨的照标,框取外接矩形;遮挡超过一半且关键图案不可辨的,标difficult=1或者直接不标;同一车尾贴多块标志的全部标出,不允许只标最清楚的那块。这几条写在标注手册第一页,新标注员先标 100 张做交叉复核,通过率够了再放量。
name字段最容易出问题。多人协作时会出现flammable_liquid、Flammable-Liquid、易燃液体三种写法混用的情况,训练脚本按字符串匹配,结果就是同一类被拆成三类,每类样本数都变少,模型学得稀里糊涂。解决办法只有一个:类别表先冻结,写成一份独立的配置文件,标注和训练都从这份文件读,脚本里做一次全量校验。
3.4 类别体系:5 类属性加 18 类分项的双层标签设计
类别体系不是拍脑袋定的,它跟着危险货物的分类标准走。按主要特性和运输要求,危险货物分为 9 个大类,往下的细分项很多。直接上几十类会带来两个问题:样本不够分,类别间视觉差异极小。所以这里做成两层。
第一层是危险化学品属性分类,5 类,粒度粗,标注框 8419 个,平均每类接近 1700 个,样本相对充足,模型容易学稳。这一层的价值在应急端:救援队伍第一时间要知道的是“能不能用水”“要不要防爆装备”“需不需要防毒面具”,粗粒度属性直接对应处置动作。
第二层是危险化学品分类,18 类,标注框 13755 个,平均每类七百多,实际分布相当不均。这一层服务管理端:统计某路段某月运输的货物构成、追溯特定品名的通行记录。
| 层级 | 类别数 | 标注框数 | 平均每类 | 主要用途 |
|---|---|---|---|---|
| 属性层 | 5 | 8419 | 约 1684 | 应急处突、救援决策 |
| 细分层 | 18 | 13755 | 约 764 | 通行统计、溯源查询 |
| 合计 | 23 | 22174 | 约 964 | — |
两层合计 23 种标注类别、22174 个标注框。平均数是骗人的,真实分布里头部类别可能占两成以上,尾部类别不足百分之一。这个不均衡是后面所有训练策略的前提,也是避坑那一章要重点讲的问题。
3.5 划分与统计:按日期和点位切,不按随机数切
很多检测项目在这里栽跟头:把 10000 张图随机打乱,8:1:1 分给训练、验证、测试,跑出来的指标很漂亮,一上线掉一大截。原因是同一辆车在同一个卡口会被连续抓拍多张,这些图几乎一模一样,随机划分会把它们分散到训练集和验证集里,等于让模型在验证时“见过原题”。
正确的切法按时间或点位切:用前几个月的图做训练,后一个月做验证,再留一个从没出现过的卡口做测试。这样验证集上的指标才能反映模型面对新场景时的真实水平。如果数据量允许,还可以再按白天/夜间分层,保证每个子集里都有一定比例的弱光样本。
import os import xml.etree.ElementTree as ET from collections import Counter def count_boxes(label_dir): """统计每个类别的标注框数量,用于发现长尾类别""" counter = Counter() for fn in os.listdir(label_dir): if not fn.endswith(".xml"): continue root = ET.parse(os.path.join(label_dir, fn)).getroot() for obj in root.findall("object"): counter[obj.find("name").text.strip()] += 1 return counter stat = count_boxes("./annotations") total = sum(stat.values()) for name, n in stat.most_common(): # 占比低于 1% 的类,后续要单独做过采样或阈值调整 print(f"{name:<24}{n:>7}{n / total:>8.2%}")这段脚本跑一遍就能拿到类别分布,输出里占比低于 1% 的类要单独列出来。它们不是“顺手标一下”的附属品,而是决定验收时能不能过关的关键类别——真出事故的往往就是这些小概率品类。
4. 训练与调参:300 轮、动量 0.9 背后的配置清单
训练环节最忌讳的是“默认配置跑一遍,指标不行再瞎调”。这一章把超参、配置文件字段、曲线判读、推理后处理四件事按顺序讲,每个参数都说清它管什么、改它会带来什么连锁反应。
4.1 训练环境与超参设置:动量 0.9、权重衰减 0.0005、300 轮
训练在带 GPU 的深度学习服务器上完成。动量设 0.9,作用是让参数更新带一点惯性,缓解批次噪声带来的震荡;权重衰减设 0.0005,做 L2 正则,防止模型在万级样本上过拟合;总训练周期 300 轮,在总周期的 70% 和 90% 两个节点把学习率衰减到原来的 0.1 倍。初始学习率一般取 0.001,配合阶梯衰减。
| 超参 | 取值 | 说明 |
|---|---|---|
| 动量 | 0.9 | 抑制批次间震荡 |
| 权重衰减 | 0.0005 | L2 正则,防过拟合 |
| 训练周期 | 300 | 万级样本的常见量级 |
| 学习率衰减点 | 70%、90% | 衰减为原来的 0.1 倍 |
| 初始学习率 | 0.001 | 配合阶梯衰减使用 |
| 输入尺寸 | 608×608 | 小目标场景下的折中 |
输入尺寸这个参数要单独说。416×416 推理快,但经过 32 倍下采样后特征图只剩 13×13,画面里只有几十像素的小标志在那个尺度上基本消失;608×608 能把小目标多保住一点,代价是显存占用和推理耗时大约翻倍。在边缘盒子算力有限的前提下,608 是“精度还能看、时延还没爆”的折中点,如果硬件更强可以再往上试,如果时延超标就得退回去,同时补上切图推理之类的补救手段。
4.2 配置文件关键字段:classes 与 filters 的联动改法
检测模型的配置文件里,最容易改错的是类别数和卷积核数量。两者是绑定关系,改一个不改另一个,训练能启动但结果全是错的,而且不一定报错,属于典型的静默失败。
[net] width=608 height=608 batch=16 subdivisions=4 # 实际批大小 = 16/4 = 4,显存不够就调大 subdivisions momentum=0.9 decay=0.0005 learning_rate=0.001 max_batches=60000 # 按 300 轮 × 每轮批次数折算 [yolo] mask = 6,7,8 classes = 23 # 与冻结后的类别表一致 num = 9 # 先验框总数,三个尺度各用 3 个 [convolutional] size=1 stride=1 pad=1 filters=84 # (classes + 5) × 3 = (23 + 5) × 3 = 84 activation=linearfilters的算法是(类别数 + 5) × 先验框数,5 指的是中心点两个、宽高两个、置信度一个。三个尺度的预测层前面各有一个这样的卷积层,三处都要改,漏改一处就会出现维度不匹配或者输出错位。subdivisions是显存调节阀,实际批大小等于batch / subdivisions,显存报 OOM 时优先调它而不是直接砍batch,因为砍batch会连带影响批归一化的统计稳定性。
4.3 训练曲线判读:三个阶段和四种异常形态
300 轮训练大致分三段。前 20 轮损失快速下降,这一段主要在学习“哪里有目标”,损失曲线陡是正常的。中期进入平台期,损失下降变缓,这时候别只盯损失,要同时看验证集上的平均精度,因为损失还在降但精度不涨,往往意味着模型在拟合训练集的标注噪声。70% 和 90% 两个衰减点之后,损失会有小台阶式下降,这是学习率降低后模型在做精细调整。
四种异常形态要认得。第一种是损失突然变 NaN,绝大多数是学习率过大或者标注框坐标越界(比如 xmin 大于 xmax、坐标超出图像尺寸),先用脚本校验一遍标注文件。第二种是训练损失降、验证精度也降,典型过拟合,减小模型规模、加数据增强、提高权重衰减都能缓解。第三种是损失几乎不降,先检查类别数和 filters 是否匹配、类别名是否和标注文件逐字一致,这是最常见的静默失败。第四种是某一类始终学不会,去看这一类的样本数和标注质量,八成是长尾或者标注口径出了问题。
提示:每 10 轮存一次权重,别只留最后一轮。小数据集上最后一轮未必是最好的,很多时候最优模型出现在 80% 到 90% 之间,没有中间权重就只能重跑。
4.4 推理侧阈值与后处理:分类别阈值加非极大值抑制
训练完只是拿到权重,真正决定线上表现的是推理侧的后处理参数。全局统一的置信度阈值在长尾分布下必然出错:阈值调高,稀有类全漏;阈值调低,常见类误报爆炸。可行的做法是按类别分别设阈值,稀有类下调、常见类上调。
# 推理后处理:分类别阈值 + NMS(伪代码,与框架无关) PER_CLASS_CONF = { "flammable_liquid": 0.45, # 头部长尾分布里的头部类,阈值可以高一些 "oxidizer": 0.25, # 尾部类下调阈值,优先保召回 "toxic": 0.30, } def postprocess(raw_boxes, iou_thres=0.45): kept = [] for cls_id, boxes in group_by_class(raw_boxes): thres = PER_CLASS_CONF.get(cls_id, 0.40) # 未配置的类走默认值 boxes = [b for b in boxes if b.score >= thres] kept += nms(boxes, iou_thres) return kept # iou_thres 取 0.45 是因为同一车尾可能贴两块相邻标志, # 阈值压得太低会把紧挨着的第二块牌当成重复框删掉,造成漏检iou_thres这个参数在别的检测任务里通常设到 0.5 甚至 0.6,这里反而要往下压一点,原因是车尾多标志共存的情况真实存在,而且两块牌经常挨得很近。这属于场景特性带来的参数偏移,不能照抄通用配置。
5. 避坑与排查:五类最容易翻车的问题及处置
下面五条是这个项目里最容易出问题的地方,每一条都按“现象、原因、解决”写,遇到对应现象可以直接对照排查。
5.1 验证指标虚高:随机划分导致的数据泄漏
现象是验证集上平均精度能到 0.9 以上,一上线掉到 0.5 左右,且错误集中在同一批卡口。原因是随机划分把同一辆车在相邻几秒内的连拍图分到了训练集和验证集,这些图背景、光照、角度几乎一致,模型在验证时等于做原题。解决是改成按日期或点位切分,训练用早期数据、验证用后期数据、测试用一个完全没参与训练的卡口,同时记录划分清单,后续每次迭代沿用同一套切分,保证指标可比。
5.2 长尾类别学不会:稀有类样本占比过低
现象是头部几类精度很高,尾部类别召回率个位数,混淆矩阵里尾部类大量落到背景。原因是标注框分布极度不均,平均每类七百多,但实际尾部类可能只有几十个框,模型在损失里几乎感受不到它们。解决分三层:数据层对稀有类做过采样和小角度旋转、亮度扰动增强;损失层提高正样本权重或引入焦点损失,压一压易分样本的贡献;推理层给稀有类单独下调置信度阈值,用精度换召回。三管齐下还不行的类别,考虑合并到属性层,先保住粗粒度可用。
5.3 夜间与逆光性能断崖:成像分布偏了
现象是白天场景召回率正常,夜间和清晨掉三成以上,误报也明显变多。原因是训练样本的时段分布严重偏白天,模型没怎么见过红外补光下的低饱和图像;同时标志表面反光在强补光下会糊掉图案。解决是按时段分层抽样,强制每个训练批次里都有一定比例的夜间样本;增强里加入亮度、对比度、伽马变换和运动模糊,模拟不同曝光条件;评估时单独出一份夜间子集指标,不和白天混在一起算平均值,否则问题会被平均掉。
5.4 远距离小目标漏检:下采样把标志抹没了
现象是近处车辆识别正常,画面远端或大车遮挡后的车辆整块漏掉,日志里连低置信度的框都没有。原因是输入缩放后,远处标志在特征图上只剩十几个像素,经过多次下采样后响应基本消失。解决有三个方向:提高输入尺寸,代价是时延;做两级检测,先定位车辆尾部区域,把该区域裁剪放大后再送一次识别;或者对原图做切片推理,把大图分块送入再合并结果。工程上通常选第二种,因为它的算力增量可控,而且车辆检测本身可以复用已有的卡口算法。
5.5 标注口径漂移:同一类被拆成好几个名字
现象是训练日志里某一类样本数比预期少很多,混淆矩阵出现两个语义相近的类别互相混。原因是多人标注时类别名写法不统一,或者框选范围一个人框牌、一个人框牌加支架。解决是先把类别表冻结成独立文件,标注工具和训练脚本都从它读;写一页标注手册,用正例反例图说明边界;上线前跑一次全量校验脚本,检查类别名合法性、坐标越界、宽高为零的异常框。这套动作做一遍要半天,不做的话后面返工是几周。
6. 验收与答辩:用什么指标证明这套识别真的能用
验收这件事,最怕拿一个总体平均精度交差。评审只要问一句“夜间怎么样”“尾部类别召回多少”,总体指标就撑不住了。我的做法是把验收拆成一张分层指标表,每一项都写清口径和门槛,答辩时按这张表讲,比讲网络结构有说服力得多。
| 验收项 | 指标口径 | 建议门槛 |
|---|---|---|
| 整体精度 | 全部类别平均精度(IoU 0.5) | 按基线版本对比,要求不降 |
| 属性层召回 | 5 类粗粒度,漏检优先 | 高于细分层,应急场景以不漏为主 |
| 细分层召回 | 18 类分项,含尾部类别单列 | 尾部类别单独给数,不混平均值 |
| 单帧时延 | 边缘设备端到端,含前后处理 | 控制在预算内并留抖动余量 |
| 分场景表现 | 白天、夜间、雨雾分别统计 | 各场景差距控制在可接受区间 |
| 连续试运行 | 连续 7 天在线,统计误报与漏报 | 误报按点位归因,漏报逐条复盘 |
这张表里有两个设计意图值得说明。一是把属性层和细分层的门槛分开,属性层服务于应急处置,漏检的代价远大于误报,所以召回门槛要压得住;细分层服务于统计查询,可以适当让精度优先。二是要求尾部类别单独给数,不接受“平均值达标”这种说法,因为平均值会被头部类别拉高,恰恰掩盖了最需要关注的稀有品类。
答辩的讲述顺序也有讲究,我一般按四段走。第一段讲场景和代价:为什么要在卡口识别货物类型,识别错了会带来什么后果,这一分钟决定评委愿不愿意继续听。第二段讲数据怎么来:抓拍参数、筛选流程、标注口径、类别体系为什么分两层,这一段最能体现工作量,也最容易被追问细节,提前把标注手册和类别分布图准备好。第三段讲模型和训练:为什么选单阶段、四个尺度的输出怎么设计、损失四项各自的掩码和权重、超参怎么定的,讲取舍而不是讲公式。第四段讲指标和失败案例:把上面那张表放出来,主动讲一两个没解决好的场景,比如夜间反光导致的误判,以及打算怎么补。
主动交代失败案例这一步,很多人不敢做,其实是加分项。全部指标都漂亮的方案反而让人怀疑数据划分有问题,能说清“哪一类现在还不稳、原因是什么、下一步怎么改”,才像真的做过这个项目。
从那以后我每次做检测类项目,都强制把所有验证集的划分方式写在方案第一页,标注口径和类别表冻结之后才允许开训,指标表里必须留一行给最差的那个类别。这三条替我挡掉了好几次上线前的返工。这套从抓拍、数据集、训练到验收的完整链路整理在文末的资源里,做毕设或课设时按章节顺序对着改就行,希望帮到你。
本文还有配套的精品资源,点击获取