带过几届毕业设计的答辩,安全帽佩戴识别这个题目几乎每届都会出现一两份。它的诱惑力在于“看起来闭环”:一个目标检测模型、一段视频流、屏幕上画出几个框,demo 就能跑起来。但真正动手的人很快会发现,基于深度学习的安全帽佩戴识别系统,难点从来不在模型本身——模型是现成的,权重是开源的,训练脚本复制粘贴就能跑。真正卡人的是数据定义、场景适配和工程闭环这三件事,而这三件事恰恰是答辩老师最爱追问的地方。这篇内容我按实际做一个能交付、能演示、能被追问的系统来拆,从任务边界定义、数据集的坑、模型训练选型,到部署加速和业务逻辑,全部给到可复现的细节。适合正在做这个题的学生,也适合刚接手工地视频分析类需求、想快速摸清门道的开发者。看完你至少能避开我当年踩过的那几个最费时间的坑。
1. 先把题目拆开:这个系统究竟要识别什么
1.1 三类目标的定义边界决定了后面所有工作
很多人一上手就去下载数据集、改配置文件、开训。三天后发现指标上不去,回头一看,是类别定义从一开始就是糊的。安全帽场景里至少存在四种状态:规范佩戴(帽带扣好)、戴了但没扣帽带、完全没戴、手里拿着或者挂在脖子/胳膊上。这四种在业务上的处置完全不同,但在图像上却高度相似。
我的建议是毕设阶段只做两类:helmet(头部区域存在安全帽)和head(头部区域无安全帽)。不要贪多去做“帽带是否扣紧”,这个任务在 1080p 远景里根本没有任何可辨识的像素信息,标出来的标签噪声比信号还大,最后指标上不去还找不到原因。至于“手持安全帽”这种情况,本质上属于head类别,业务上应该报警——人确实没戴。如果你把它单独列一个holding类,模型会被大量边界模糊的样本搞崩,因为手和头的空间关系在低分辨率下极其难分。
类别数少还有一个隐性好处:单类正样本数量翻倍,收敛更快,毕设周期内更容易调到能看的指标。
1.2 为什么分类模型走不通,必须上目标检测
答辩时被问得最多的一个问题是“为什么不用分类网络”。答案很直接:分类网络的前提是“图里已经裁好了目标”。而工地监控画面是一整幅 1920×1080 的广角图,里面有十几个人、脚手架、塔吊、材料堆。你没法直接用分类器判断“这张图里有没有人没戴帽子”,因为一张图里可能同时存在戴和不戴的人,输出一个标量标签在逻辑上就是错的。
所以必须是目标检测:先定位每一个头部,再对每个头部判类别。这也是为什么这个题目天然适合 YOLO 这类单阶段检测器——它一次前向就同时给出框和类别,延迟可控,工程上链条最短。
还有一个变通方案是先用人检测模型框出人,再对每个人框上部做分类。这条路在论文里看着合理,实际落地会多一次推理、多一层误差传递,人框裁不准头就废了。除非你的算力极其紧张,否则不推荐。
1.3 毕设的评分点在哪里,别把力气用错地方
我见过太多人把 80% 的时间花在调参上,想从 0.82 的 mAP 冲到 0.87,结果系统演示时视频卡成幻灯片。实际上这类题目的评分维度大致是三块:工作量(数据集自建、系统完整度)、技术含量(模型选型与优化有依据)、可演示性(实时跑通、界面可用)。指标只要在合理区间内、有对比实验支撑,就已经够了。
所以我给的时间分配建议是:数据 50%,训练调优 25%,系统工程化 25%。工程化那 25% 才是拉开差距的地方——能不能实时、能不能处理多路、误报怎么抑制、告警怎么留证,这些是老师一眼能看出来“这孩子真动手做过”的东西。
注意:不要为了指标好看而在验证集上反复挑模型。答辩时老师只要让你现场换一段视频跑一下,虚高的指标立刻现原形。
2. 数据集:占七成工作量的隐形地基
2.1 数据从哪来,三条路的成本对比
这个题目最大的陷阱是“网上一搜有现成数据集”。确实有,而且是公开的、标注质量不错的。但直接用会有两个问题:一是场景单一,多为晴天白天的固定机位,你的模型会严重过拟合到某几种安全帽颜色和背景;二是同源泄漏,如果训练集和验证集来自同一批视频的相邻帧,指标会虚高十几个点,你以为是模型强,其实是记住了背景。
我的做法是三源混合:公开数据集打底占 60%,自采占 30%,剩下 10% 用困难样本补充。自采不一定要去真实现场,校园里的施工围挡、装修楼层、实验室安全演练都能凑出可用素材。拿手机在不同时段、不同角度拍,用视频抽帧(每秒 1 帧,避免大量重复帧)就能拿到几百张。
抽帧这一步有个细节:先按视频切分,再在各自视频内抽帧,绝不能先抽帧再随机划分。这个顺序错了,前面所有评估都是假的。
2.2 标注规范:框人头还是框帽子,这是第一个分水岭
这是我认为整个项目里最有价值的一个经验点。绝大多数人会本能地“框安全帽”——看见帽子就画框,没戴帽子的画一个头。听起来合理,实际会出大问题:两类目标的尺度分布完全不一样。安全帽的框通常比人头框小一圈,模型学到的是“小物体是 helmet、大物体是 head”这种伪特征,换个摄像头焦距变了,直接崩。
正确做法是统一框头部区域:无论戴没戴,都把“人的头部(含安全帽)”整体框出来,类别分别标helmet和head。这样两类的先验尺度一致,模型只能靠外观特征区分,泛化能力立刻上一个台阶。
具体标注细则我一般写这么几条,贴在标注工具旁边:
- 框体包含完整头部和帽檐,帽檐外扩 2~4 像素即可,不要框到肩膀。
- 侧脸、背脸只要能判断是否戴帽就标,判断不了的直接丢弃,不要猜。
- 头部像素小于 12×12 的远景目标丢弃,这类样本只会贡献噪声。
- 严重遮挡(遮挡超过 60%)丢弃,轻微遮挡按可见部分框。
- 一个人同一帧只标一个头框,不要重复。
标注工具用 LabelImg 或 CVAT 都行,导出 YOLO 的 txt 格式,一行一个目标class cx cy w h,坐标是归一化到 0~1 的。
2.3 数据清洗与划分:避免同源泄漏
清洗主要干三件事:去重(用感知哈希或简单的像素差比对,抽帧造成的近重复帧必须去掉)、去模糊(拉普拉斯方差低于阈值的直接删)、查错标(随机抽 5% 人工复核,重点看有没有把黄色头盔和黄色安全标语牌搞混)。
划分比例按 8:1:1 走训练/验证/测试。这里的关键是“按源分组划分”——同一段视频、同一个摄像头、同一天的素材必须整体进同一个集合。我一般用文件名前缀做分组标识,比如siteA_cam1_00123.jpg,按siteA_cam1这个前缀分组,随机打乱组而不是打乱文件。
这个细节带来的指标差异有多大?我实测过一次,同一份数据,随机划分能到 0.91,分组划分只有 0.84。差的这 7 个点全是泄漏。你拿着 0.91 去答辩,老师让你换个场地跑,掉到 0.7 就尴尬了。
2.4 类别不平衡怎么处理
未戴安全帽的样本天然少,工地里大部分人还是守规矩的。实测比例经常是 5:1 甚至 10:1。处理手段按优先级排:
第一优先是补数据,去公开数据集里专门挑head类多抽一些,或者对现有head样本做水平翻转、随机裁剪、色彩抖动。第二优先是在损失里给类别加权,但权重别给太狠,超过 2.0 容易导致大量误报。第三是调整采样策略,让含head的图像在每个 batch 里占比不低于 40%。
提示:翻转变换对安全帽场景是安全的,但如果你的数据里有明显左右区分的场景元素(比如工牌、编号),翻转会引入错误标签,得酌情关闭。
3. 模型与训练策略:为什么这么选
3.1 为什么坚定的选单阶段检测器
两阶段检测器精度略高,但推理慢,对毕设这种要实时演示的题目不划算。单阶段检测器一次前向出结果,结构简单,导出部署链路成熟,遇到问题好排查。这个题目里目标尺寸中等偏小、密集度中等、实时性要求高,单阶段是最优解。
具体选哪个尺度?我建议从 n 或 s 起步。理由很实际:你的数据量大概率在 3000~8000 张这个区间,训一个 m 或 l 尺度会严重过拟合,而且单卡显存吃紧,batch size 只能开到 8,BN 层统计不稳定,loss 曲线抖得没法看。小尺度模型在你这个数据量下反而更容易收敛到稳定状态。
如果最后精度确实不够,再往上换,同时把数据量补上去。顺序不能反。
3.2 输入分辨率的取舍,以及一笔算力账
分辨率是这个任务里最敏感的参数。工地上的人距离摄像头 15~30 米很常见,1080p 画面里一个头部可能只有 20×20 像素。你把输入缩到 416,头部就剩 7×7,模型根本看不见特征。
算一笔账。输入 640×640 时,s 尺度模型的乘加运算量大约在 8~9 GFLOPs 量级;输入提到 1280×1280,像素数变成 4 倍,运算量也大致翻 4 倍,到 30 GFLOPs 以上。在一张中端显卡上,640 输入能跑到 100+ FPS,1280 输入掉到 30 FPS 左右。如果你的系统要接 4 路视频,每路 25 FPS,总需求就是 100 FPS,1280 输入直接不够用。
实际方案我是这么定的:单路高清场景用 960 或 1280 输入,保证小目标;多路场景用 640 输入配合“先粗筛后细看”两段式——全画面低分辨率跑一遍,检出疑似未佩戴的头部区域,把该区域放大到高分辨率再判一次。这个思路本质上是切片推理的简化版,代价可控,对小目标提升明显。
另一种做法是切片推理:把 1920×1080 切成 2×2 或 3×3 的重叠块,每块单独送进 640 输入,再合并结果。缺点是推理次数翻 4~9 倍,且块边界处的目标容易被切断,需要靠重叠区和 NMS 补救。毕设演示可以用,生产环境得掂量算力。
3.3 损失构成与难例挖掘
单阶段检测器的损失一般是三部分:分类损失(二值交叉熵)、框回归损失(IoU 系列或其变体)、置信度/质量损失。调参时最容易犯的错是乱改损失权重,看到分类 loss 不降就把分类权重调高,结果框回归崩了,mAP 反而掉。
我的经验是权重基本别动,默认值就是大量实验调出来的。真要提升,把力气花在难例上:训练中期用当前模型跑一遍训练集,把置信度在 0.3~0.6 之间的样本挑出来人工复核,错标的改掉,漏标的补上。这一轮“模型自己找错题”的操作,通常能带来 2~4 个点 mAP 的提升,比调一个月参有用得多。
3.4 小目标和密集场景的三条路
小目标的问题我在上面提了分辨率,这里补两条。第一条是数据侧:在训练时加入马赛克增强和随机缩放,让模型在训练阶段就见过各种尺度,特别是把小目标放大后再喂进去,效果相当直接。第二条是推理侧的置信度阈值下探:默认 0.25 对小目标偏高,降到 0.15 能捞回一部分,代价是误报增加,需要靠后处理去抖来抵消。
密集场景是另一个坑。几十个人挤在一起时,NMS 会把挨得近的正确框当成重复框滤掉。可以把 NMS 的 IoU 阈值从 0.45 调到 0.6,或者直接换成按距离加权的软抑制方案。判断标准很简单:拿一段密集场景的视频跑,看同一帧里检出的头部数量是不是明显少于肉眼数的,少了就调。
4. 从零跑通一次训练:配置与监控
4.1 环境与目录结构
环境不追求最新,追求稳。Python 3.9 或 3.10,深度学习框架选主流的 PyTorch 2.x,CUDA 版本跟驱动对齐即可。显卡 8G 显存起步,够跑 s 尺度 640 输入。
目录我习惯这么组织,后面写论文和排查都方便:
project/ ├── datasets/ │ ├── images/{train,val,test}/ │ └── labels/{train,val,test}/ ├── configs/ │ ├── helmet_s_640.yaml │ └── helmet_m_960.yaml ├── weights/ ├── runs/ └── tools/ ├── split_by_group.py ├── check_labels.py └── export_onnx.pycheck_labels.py是关键,作用是把所有标签读一遍,检查有没有越界坐标、有没有类别 id 超范围、有没有空标签文件、有没有宽高为 0 的框。这份脚本能在开训前省掉你两三个小时的瞎猜。
4.2 配置文件里几个真正要动的项
大部分配置保持默认,只有这几项需要按你的数据来定:
# helmet_s_640.yaml path: ../datasets train: images/train val: images/val nc: 2 names: ["helmet", "head"] imgsz: 640 batch: 16 epochs: 150 workers: 8 cache: ram # 增强 mosaic: 1.0 mixup: 0.0 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 0.0 translate: 0.1 scale: 0.5 fliplr: 0.5几个说明。cache: ram在数据量小于 1 万张时开着,能显著加快训练,数据太大就关掉否则内存爆。mixup我默认关掉,工地场景里两张图叠在一起会产生物理上不存在的组合,比如半透明的人头,对收敛没好处。degrees也关掉,因为摄像头基本是俯视或水平视角,大角度旋转会让模型学到无用的不变性。scale: 0.5保留,尺度变化是真实存在的。
4.3 训练过程怎么看,哪些指标才作数
开训以后别只盯着 loss 下降。判断训练是否健康,我一般看四个信号:
第一,分类 loss 和框回归 loss 应该是同步下降的。如果分类降得很快而框回归不降,说明类别特征好学但位置不准,多半是标注框质量差。第二,mAP50 在 30 轮内应该出现明显上升,如果 60 轮还在 0.3 以下,直接停,回去查数据和标签格式。第三,验证集的 loss 应该在 50 轮左右开始趋于平稳,如果一路下降到接近 0 而验证 mAP 不动,就是过拟合了。第四,看验证集的可视化结果,这个比任何曲线都直观。
可视化这块我建议每 10 轮就抽 20 张验证图跑一次推理,把预测框和真实框叠在同一张图上对比颜色。很多问题——比如类别系统性颠倒、框整体偏移几个像素——在曲线里完全看不出来,在图上是一眼的事。
4.4 推理后处理的参数该怎么定
后处理主要就三个参数:置信度阈值、NMS 的 IoU 阈值、最大检出数。
置信度阈值不是拍脑袋定的,要画 P-R 曲线找拐点。安全帽场景里我更看重召回,因为漏报(有人没戴没被发现)比误报代价大,所以阈值一般压到 0.2~0.25 之间。但压得太低会导致背景里的圆形物体(安全出口标志、黄色反光锥)被误判,需要靠后面的业务逻辑去抖来兜底。
import cv2 import numpy as np def postprocess(pred, conf_thres=0.25, iou_thres=0.55, max_det=100): pred = pred[pred[:, 4] >= conf_thres] if pred.shape[0] == 0: return np.empty((0, 6), dtype=np.float32) boxes = np.concatenate( [pred[:, :2] - pred[:, 2:4] / 2, pred[:, :2] + pred[:, 2:4] / 2], axis=1 ) scores = pred[:, 4] classes = pred[:, 5] keep = [] for c in np.unique(classes): idx = np.where(classes == c)[0] idx = idx[np.argsort(-scores[idx])] while idx.size > 0: i = idx[0] keep.append(i) if idx.size == 1: break rest = idx[1:] xx1 = np.maximum(boxes[i, 0], boxes[rest, 0]) yy1 = np.maximum(boxes[i, 1], boxes[rest, 1]) xx2 = np.minimum(boxes[i, 2], boxes[rest, 2]) yy2 = np.minimum(boxes[i, 3], boxes[rest, 3]) w = np.maximum(0.0, xx2 - xx1) h = np.maximum(0.0, yy2 - yy1) inter = w * h area_i = (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1]) area_r = (boxes[rest, 2] - boxes[rest, 0]) * (boxes[rest, 3] - boxes[rest, 1]) iou = inter / (area_i + area_r - inter + 1e-9) idx = rest[iou <= iou_thres] return np.concatenate([boxes[keep], scores[keep, None], classes[keep, None]], axis=1)[:max_det]这段代码我标配在项目里,方便脱离检测框架直接调参对比,也方便你理解框架内部到底做了什么。
5. 工程化落地:从 demo 到能演示的系统
5.1 系统架构怎么搭最省事
毕设演示系统的架构不用复杂,够用就好。我推荐三段式:视频接入层、推理层、展示层。
视频接入层负责把 RTSP 流或者本地视频解码成帧,这里坑最多。1080p 的流用 OpenCV 直接读经常卡顿,原因是解码在主线程。解决办法是把解码放到独立线程,用队列缓冲 3~5 帧,主线程只取帧不阻塞。如果有硬件解码能力(比如设备自带解码单元),优先打开,帧率能翻倍。
推理层做批量组帧,把几帧拼成一个 batch 送进模型,提高显存利用率。注意别为了凑 batch 而无限等待,设置一个 10~20 毫秒的等待上限,超时就单帧推理,保证延迟可控。
展示层用 Web 就行,后端 FastAPI 起一个服务,前端拉流显示,检测框和告警记录通过 WebSocket 推过去。这套组合上手快、部署简单,答辩时手机连同一网络就能看,效果很不错。
5.2 模型压缩与加速:什么时候才需要
很多人一上来就搞量化剪枝,结果精度掉了一大截,性能也没提升多少。我的判断顺序是:先用 FP16 半精度推理,几乎无损,速度提升 1.5 倍左右;还不够再上 ONNX 导出配合推理引擎优化;最后才考虑 INT8 量化。
INT8 量化的关键是校准集。别随便拿几十张图凑数,校准集必须覆盖你的实际场景分布——白天、傍晚、夜间、晴天、雨天各来一些,总量 300~500 张。校准集和实际场景偏移,量化后的精度会掉得很厉害。
# 导出 ONNX,注意输入尺寸和动态 batch python tools/export_onnx.py \ --weights weights/best.pt \ --imgsz 640 \ --batch 1 \ --dynamic \ --simplify导出后必须做一次数值对齐:用同一张图分别跑原始权重和 ONNX 模型,比较两者的输出差异。我遇到过导出后类别顺序颠倒的情况,原因是导出脚本里做了算子融合但没更新输出结构。这类问题不看输出数值根本发现不了。
设备上的实测数据我这里给个参考量级:在带独立显卡的机器上,s 尺度 640 输入 FP16 能跑到 80~120 FPS;在嵌入式计算盒上,同样的模型大概 20~30 FPS;纯 CPU 跑推理则只有个位数。你做多路接入之前,先按这个量级估算一下,别等系统搭好了发现只能带一路。
5.3 业务逻辑:报警去抖是决定系统“能不能用”的关键
模型输出是每帧的检测框,业务需要的是“某个人从 10 点 05 分开始未佩戴安全帽,持续 42 秒”。中间这个转换过程,是整个系统里最容易被忽略、却最影响体验的部分。
第一步是目标跟踪。用轻量的多目标跟踪算法给每个头部一个临时 ID,框在相邻帧之间用 IoU 匹配。这样同一顶帽子在 30 帧里只算一个人,而不是 30 次报警。
第二步是时间维度的去抖。规则我一般这么设:连续 15 帧(约 0.6 秒)同一 ID 都被判为head才触发预警;持续 60 帧(约 2.4 秒)才升级为正式告警并截图留证。这样能过滤掉绝大部分因为遮挡、转身、低头造成的瞬时误判。
class AlertFSM: def __init__(self, warn_frames=15, alarm_frames=60, cooldown=300): self.state = {} # track_id -> dict self.warn_frames = warn_frames self.alarm_frames = alarm_frames self.cooldown = cooldown # 同一 id 报警后冷却帧数 def update(self, track_id, is_no_helmet, frame_idx): s = self.state.setdefault( track_id, {"count": 0, "last_alarm": -10**9, "fired": False} ) if is_no_helmet: s["count"] += 1 else: s["count"] = 0 s["fired"] = False return None if s["count"] >= self.alarm_frames and not s["fired"]: if frame_idx - s["last_alarm"] > self.cooldown: s["last_alarm"] = frame_idx s["fired"] = True return "alarm" elif s["count"] >= self.warn_frames: return "warn" return None这段状态机代码不长,但它决定了系统的可用性。没有冷却机制的系统,一个人站着不戴帽子能被报几百次,值班的人第一天就会把告警关掉。
第三步是留证。告警触发时保存三样东西:当前帧原图、画了框的标注图、一段前后各 5 秒的短视频片段。文件名带上时间戳和摄像头编号。数据库里存一条记录,包括时间、位置、持续时长、截图路径。这部分是答辩时最能加分的——老师问“报警之后怎么追溯”,你能直接点开一条记录给他看。
5.4 多路接入时的资源分配
如果要接多路,别用“一路一个进程”的朴素做法,显存会被吃干净。更稳的方式是单进程内做模型实例共享,多路共用一个推理队列,推理层按帧入队、按序出结果。调度上让路与路之间轮转,保证每路都能拿到帧,避免某一路一直排队。
帧率控制也要有。很多场景不需要 25 FPS 全部推理,抽到 5~8 FPS 完全够用,检测结果在中间帧做简单插值显示即可。这一刀砍下去,算力需求直接降到三分之一,是性价比最高的优化。
6. 踩坑实录与排查速查表
6.1 指标很好看,实际一跑全是误报
这是最高频的问题。原因通常有五个,我按出现概率排:
第一,验证集和训练集同源泄漏,前面说过,按组划分就能解决。第二,负样本不足,你的训练集里几乎每张图都有人,模型没见过“干净的工地背景”,于是把脚手架扣件、黄色圆形标志、反光锥都当成安全帽。解决办法是往训练集里塞 10%~15% 的纯背景图。第三,标注框过于宽松,把脖子和肩膀都框进去了,模型学到的是“衣服颜色”而不是帽子。第四,类别权重给太高,导致模型倾向于把任何圆形头部特征都判成head。第五,验证集场景太单一,全是晴天白天的固定机位。
排查顺序建议从可视化开始:挑 20 张典型误报图,把预测框画出来,人眼看一遍,基本上一分钟就能定位是哪种原因。
6.2 训练不收敛的排查路径
我整理过一张排查表,按这个顺序走基本能覆盖 95% 的情况:
| 现象 | 最可能原因 | 处理方式 |
|---|---|---|
| loss 一直是 nan | 学习率过大或标签坐标异常 | 降学习率到 1e-4,跑标签检查脚本 |
| 分类 loss 不降 | 标签类别 id 与配置的 names 顺序不对 | 打印一批标签核对类别分布 |
| 框回归 loss 不降 | 标注框坐标未归一化 | 检查是否存在大于 1 的坐标值 |
| mAP 卡在 0.2 左右 | 数据量太少或类别严重失衡 | 补数据,加背景图,调采样 |
| 训练集好验证集差 | 过拟合或数据泄漏 | 加增强,按源分组重新划分 |
| 训练中期突然崩 | 学习率调度不合适 | 换余弦退火,加 warmup |
| 显存溢出 | batch 或输入尺寸过大 | 降 batch,开梯度累积 |
表格里最容易被忽略的是第二行。类别 id 定义的顺序和你配置文件里写的名字顺序必须严格一致,helmet是 0 还是 1,一旦反了,模型照样收敛,指标看着还行,但所有预测的类别全是反的。这个问题在可视化的时候一眼能看出来——人戴着帽子被标成未佩戴,说明类别反了。
6.3 部署后精度掉了好几个点
导出一致性问题。三个常见来源:一是预处理不一致,训练时用的是信箱填充(保持长宽比,短边补齐),推理时用了直接拉伸,几何形变直接毁掉精度。二是归一化参数不一致,训练用 0~1 归一化,推理忘了除 255。三是颜色通道顺序反了,训练是 RGB,OpenCV 读进来是 BGR,没转换。
我的做法是写一个“对齐验证”脚本:固定一张测试图,分别用训练框架的推理接口和部署引擎的推理接口跑,把两组输出框打印出来对比,IoU 低于 0.95 就说明有问题。这个脚本花十分钟写,能省你一整天。
6.4 夜间和逆光场景几乎全瞎
这不是调参能解决的,是数据问题。夜间画面噪点多、色彩失真,模型在白天数据上学到的特征完全失效。解决方案只有补数据:夜间素材至少占到总量的 15%,如果实在采不到,用亮度扰动、加噪声、模拟低照度做数据增强,效果能打个折扣但聊胜于无。
逆光场景更麻烦,人变成剪影,安全帽和头的区别只剩轮廓。这种情况我建议在业务层加一条规则:置信度低于某个阈值时不上报告警,只记录。宁可漏报也不要误报,因为误报会让人失去对系统的信任。
6.5 几个容易被追问的细节
答辩老师常问“你的模型多大、多少参数、推理一次多久”。这些数据要提前测好写在论文里,别现场卡壳。参数量、FLOPs、单帧延迟、FPS,四个数一组,不同尺度模型各来一组,做成对比表,说服力很强。
还会问“误报率多少”。这个概念要转化成人能懂的口径:每小时每路摄像头产生多少次误报。这个指标比单纯的精确率更有说服力。我实测调好的系统大概能做到每路每小时 1~2 次误报,调之前是几十次。
最后一个常见追问是“如果工人把帽子拿在手里算不算违规”。这个问题的价值在于展示你对业务的理解:算,因为人没戴;但模型只能判断头部区域,手里拿帽子的样本会被判为head,这在业务上是正确结果,在标注上要注意别标成helmet。
我自己的体会是,这个题目真正难的地方不在深度学习,而在于你得像一个真正要交付系统的人那样去思考:数据怎么来、误报怎么忍、告警怎么追溯、算力怎么省。把这几件事想清楚,模型那部分反而是最省心的。如果时间还有富余,可以往两个方向延展:一是把检测和跟踪做得更紧,用跟踪结果反过来修正检测的瞬时抖动;二是加一个小程序端,让现场管理人员能直接在手机上收到告警推送,这一块工作量不大,但演示效果拔群。