ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深度学习车辆检测全链路:模型选型、训练调参、NMS与TensorRT部署

深度学习车辆检测全链路:模型选型、训练调参、NMS与TensorRT部署 简介这是一份面向智能交通与计算机视觉方向学习者的深度学习车辆检测参考文献适合从事环境感知、目标检测研究的本科生、研究生及工程技术人员阅读参考。文中围绕城区道路场景中智能车辆对其他车辆的感知需求使用Velodyne64E激光雷达采集三维点云将其栅格化并投影到二维平面再结合区域候选网络构建单个2D端到端的全卷积网络完成车辆目标与边框检测在KITTI数据集上实现了城市道路每帧1.24秒的检测速度并讨论了该方法在智能交通环境感知与目标检测、图像分割等领域的应用空间。资源包体为1个PDF文件约1.03MB内容含中英文摘要、检测方法、网络结构与实验分析方便直接查阅与引用。目前已有449人学习适合作为课程学习、课题调研或方法复现的参考材料。1. 夜间路口的一帧画面为什么传统车辆检测会交白卷城市快速路的晚高峰前车遮挡了三分之二车身摄像机逆光画面里还有雨滴和路面反光。用 HOGSVM 或背景差分做车辆检测这一帧大概率直接漏掉换成背景建模车流一停一走背景模型自己先乱掉。这类场景逼着大家把特征工程交给卷积网络也正是「基于深度学习的车辆检测方法」要处理的问题。这个标题覆盖的不是某一个模型而是一条完整链路标注规范、backbone 与检测头选型、anchor 与输入分辨率、损失函数与训练策略、NMS 后处理、mAP 口径下的评估直到 ONNX/TensorRT 的部署加速。适合做智能交通、自动驾驶感知、安防视频结构化的人也适合要复现相关论文方法的学生。先给一个反直觉的结论多数车辆检测项目卡住的地方不是模型不够新而是标注框贴合标准不统一、anchor 尺度和实际车辆像素尺寸对不上、验证集切分时把同一条视频的相邻帧拆到了训练和验证两侧。2. 车辆检测的网络结构选型与指标口径2.1 Faster R-CNN、SSD 与 YOLO 在车辆场景下的取舍两阶段检测器把「哪里可能有车」和「这是什么车」分成两步。Faster R-CNN 先用 RPN 生成候选框再对每个候选框做 ROIAlign 池化和分类回归精度稳、对小目标和重叠目标相对友好代价是推理慢帧率通常撑不住多路高清视频。它在车辆场景里的合理位置是离线抽帧审核、卡口单帧抓拍、以及给自动标注做预标注。单阶段检测器把定位和分类压在一次前向里。SSD 在多个尺度的特征图上直接回归结构简单但浅层特征语义弱对远小车辆召回偏低实际项目里已经很少单独用。YOLO 系列是当前车辆检测的主力anchor-based 与 anchor-free 两代都有成熟工程实现导出 ONNX、量化、TensorRT 部署的链路完整社区坑位基本被踩平了。DETR 这类集合预测方案不需要 NMS端到端干净但收敛慢小目标和密集车流上通常还需要额外设计。方案类别代表结构车辆场景优势主要短板典型落点两阶段Faster R-CNN FPN遮挡、重叠车辆精度高单帧耗时长显存占用大离线审核、自动预标注单阶段 anchor-basedYOLO 系列、RetinaNet生态成熟、易部署密集车流 NMS 敏感实时路侧感知单阶段 anchor-freeFCOS、CenterNet超参少、尺度泛化好中心点重叠时易冲突中低密度车流集合预测DETR 类无需 NMS、结构简洁训练轮次多、小目标弱研究验证、固定视角2.2 Backbone、Neck 与 Head真正影响召回的位置Backbone 决定特征表达能力ResNet-50、CSPDarknet 这类结构在车辆检测里都够用换成更轻的移动端结构主要是换速度不是换精度上限。真正决定远处小车能不能被检出的是 Neck。FPN 把高层语义往下传PAN 再把浅层定位信息往上回浅层高分辨率特征图上的那个 8 倍下采样输出才是 20×40 像素级别车辆的最后一根救命稻草。如果 Neck 只做单向融合或者把最浅层直接砍掉小目标召回会掉得很明显。Head 上常见做法是分类和回归解耦各走一条小分支。车辆检测里还有一个容易忽略的点分类头不需要太强的语义区分能力但回归头对框的宽容度要求高因为车的长宽比跨度大——从轿车接近 1:1 的俯视到卡车 3:1 的侧视都在同一个数据集里。输入分辨率是最直接的杠杆。640 输入下一辆占原图 60 像素宽的远处车辆进网络后只剩 15 像素左右特征图上就是几个点。把输入提到 1280小目标召回通常能明显改善代价是显存和耗时大约翻倍。这个取舍必须结合业务的最小可接受车辆像素尺寸来定而不是照着别人的配置抄。2.3 IoU、mAP0.5 与尺寸分层统计的正确读法评价指标必须先对齐口径否则两个团队的「mAP 85」根本没法比较。IoU 衡量预测框和真值框的交并比通常阈值取 0.5mAP0.5 是在 IoU0.5 下的各类平均精度比较宽松适合看召回趋势。mAP0.5:0.95 把 IoU 从 0.5 到 0.95 每隔 0.05 算一次再平均对框的贴合度非常敏感标注稍微松一点这个指标就掉。车辆检测还要额外做尺寸分层统计把真值框按面积分成小、中、大三档分别算 AP。很多项目整体 mAP 看着漂亮小目标 AP 只有 0.2而这恰恰是路侧场景最关心的部分。下面的代码给出 IoU 与贪心 NMS 的最小实现用来在验证脚本里自己核对指标避免被不同框架的实现差异误导。import numpy as np def iou_matrix(boxes_a, boxes_b): boxes 格式 [x1, y1, x2, y2]返回 len(a) x len(b) 的 IoU 矩阵 a boxes_a[:, None, :] # 广播成 (N,1,4) b boxes_b[None, :, :] # 广播成 (1,M,4) inter_x1 np.maximum(a[..., 0], b[..., 0]) inter_y1 np.maximum(a[..., 1], b[..., 1]) inter_x2 np.minimum(a[..., 2], b[..., 2]) inter_y2 np.minimum(a[..., 3], b[..., 3]) inter np.clip(inter_x2 - inter_x1, 0, None) * np.clip(inter_y2 - inter_y1, 0, None) area_a (a[..., 2] - a[..., 0]) * (a[..., 3] - a[..., 1]) area_b (b[..., 2] - b[..., 0]) * (b[..., 3] - b[..., 1]) return inter / (area_a area_b - inter 1e-9) def nms(boxes, scores, iou_thres0.5): 贪心 NMS按分数降序逐个剔除 IoU 超阈值的框 order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) if order.size 1: break ious iou_matrix(boxes[i:i 1], boxes[order[1:]])[0] order order[1:][ious iou_thres] # 保留未被抑制的候选 return np.array(keep)iou_matrix用广播避免双重循环在几千个候选框上也能秒出结果nms的iou_thres是核心参数密级车流调高、稀疏场景调低这一项在下文调参表里还会再出现。注意它只做类内抑制跨类抑制比如公交车和卡车同时被检出需要另写逻辑或者交给模型自己区分。3. 车辆数据集准备与检测模型训练3.1 标注格式组织与 VOC 转 YOLO 的转换脚本标注规范要在动手前定死框贴合车身外沿还是留两像素余量、被截断超过多少比例的车标不标、远处小于多少像素的车算忽略区域。这三条不统一后面所有指标都不可比。目录上建议按视频源切分训练/验证集同一条视频的相邻帧只能进同一侧否则数据泄漏会让验证 mAP 虚高一截。import xml.etree.ElementTree as ET from pathlib import Path CLASSES [car, bus, truck, van, motorbike] # 类别顺序固定训练配置必须一致 def voc_to_yolo(xml_path, out_dir, img_w1920, img_h1080): 把 VOC XML 转成 YOLO txtcls cx cy w h全部归一化到 0-1 root ET.parse(xml_path).getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASSES: continue if obj.find(difficult) is not None and obj.find(difficult).text 1: continue # difficult 标为忽略不参与损失 b obj.find(bndbox) x1, y1 float(b.find(x1).text), float(b.find(y1).text) x2, y2 float(b.find(x2).text), float(b.find(y2).text) x1, x2 max(0, x1), min(img_w, x2) y1, y2 max(0, y1), min(img_h, y2) cx, cy (x1 x2) / 2 / img_w, (y1 y2) / 2 / img_h w, h (x2 - x1) / img_w, (y2 - y1) / img_h if w 0 or h 0: continue lines.append(f{CLASSES.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) Path(out_dir, xml_path.stem .txt).write_text(\n.join(lines), encodingutf-8) return len(lines)转换里最容易出问题的是坐标越界和宽高为零的框上面用 clip 加判空挡掉了。img_w和img_h必须和真实图片尺寸一致很多数据集里分辨率不是统一的硬写 1920×1080 会让框全部错位——常见做法是读图后取img.shape再传进来。数据配置文件里把train、val、nc、names写清路径推荐用绝对路径或相对 data 文件所在目录的相对路径两种混用是新手最常见的报错来源。3.2 Anchor、输入分辨率与训练超参怎么设Anchor 不必手算训练前跑一次聚类即可k-means 在标注框的宽高上做簇数等于每个特征层的 anchor 数乘层数。关键是让 anchor 覆盖到实际最小车辆尺寸如果聚类出来最小 anchor 还是比远处车辆大一圈那个尺度的正样本永远匹配不上召回会一直上不去。参数建议起步值说明img-size640 / 1280最小车辆像素宽低于 30 时优先上 1280batch16单卡 16G 显存显存不够先降 batch 再考虑降分辨率epochs100300车辆场景类别少通常 150 轮内收敛初始学习率0.01SGD/ 0.001Adam用预训练权重时取小值学习率衰减cosine 或 linear线性衰减到初始值的 0.01anchork-means 聚类 9 组必须覆盖最小车辆尺度mosaic / mixup开启遮挡与小目标收益明显末 10 轮关闭权重衰减5e-4抑制过拟合配合早停使用增强策略上马赛克拼接对车辆检测的收益比其他任务更明显因为它天然制造了小车和遮挡样本。但在训练最后阶段要关掉让模型回到真实图像分布上收敛否则验证指标会跳。HSV 抖动和随机缩放可以保留翻转要慎重水平翻转会让车辆朝向分布改变对只关心位置的检测问题影响不大但如果下游要做方向判断就不能翻。3.3 训练命令与损失曲线判读以常见的 YOLO 系列工程为例训练入口通常是这样一条命令# 单卡训练指定数据配置、预训练权重、输入尺寸与批大小 python train.py \ --data data/vehicle.yaml \ # 数据集路径、类别数与类别名 --weights yolov5s.pt \ # 预训练权重从头训容易初期发散 --img 1280 \ # 输入分辨率直接影响小目标召回 --batch 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ # 学习率、增强、损失权重 --device 0 \ --name vehicle_v1体重权重、类别数和数据路径这三项没对上报错通常发生在第一次迭代之前属于最好排查的一类问题。真正难的是训练开始之后的判断box loss 持续下降但 mAP 不再涨一般是过拟合看训练集和验证集的损失分叉box loss 抖动剧烈先查学习率是否过大、batch 是否过小分类损失低但 mAP 也低多半是定位不准回头查标注框贴合度和 anchor 匹配。跑完训练后务必用验证集导出 PR 曲线把每个类别的 AP 单独看一遍。公交和卡车混检、夜间车辆漏检、远处小车漏检这三类问题在总体 mAP 里被稀释掉了只有分类别看曲线才暴露得出来。4. 推理加速与后处理调参4.1 导出 ONNX 与构建 TensorRT 引擎训练完的权重直接跑 PyTorch 推理帧率通常只有部署要求的几分之一。标准流程是先导出 ONNX再用 TensorRT 构建引擎中间做 FP16 或 INT8 量化。# 导出 ONNX固定 opset、简化计算图、静态 shape 便于 TensorRT 优化 yolo export modelruns/train/vehicle_v1/weights/best.pt \ formatonnx opset12 simplifyTrue dynamicFalse imgsz1280 # 构建 FP16 引擎workspace 单位 MB按显存余量给 trtexec --onnxbest.onnx --saveEnginebest_fp16.engine \ --fp16 --workspace4096 --minShapesimages:1x3x1280x1280 \ --optShapesimages:4x3x1280x1280 --maxShapesimages:8x3x1280x1280opset选太高会让某些推理后端不支持选 11 或 12 通常最稳。dynamicFalse意味着引擎只认一个输入尺寸好处是优化更彻底、速度更快坏处是换分辨率就得重建引擎。用minShapes/optShapes/maxShapes可以构建支持动态 batch 的引擎代价是显存占用按 maxShapes 预留。INT8 量化需要校准集直接从验证集里抽几百张有代表性的图即可但要注意夜间和白天样本都要覆盖否则量化误差会集中在某一类场景上。4.2 NMS 阈值与置信度阈值的联合调参后处理参数对最终指标的影响经常比换模型还大。置信度阈值调高误检少但漏检多NMS 的 IoU 阈值调低重叠车辆会被误抑制密集车流直接掉框。这两个参数必须一起调只调一个容易陷入局部最优。参数默认值密集车流稀疏/高速场景调参信号置信度阈值0.250.350.450.200.25误检多则调高NMS IoU 阈值0.450.550.650.400.45相邻车被吞则调高最大检出数300500100堵车画面框数不够则调高最小框尺寸0812 像素0大量碎框则抬高调参方法是拿一段典型视频跑一遍把置信度从 0.15 到 0.6 扫一遍记录 F1 最高点同时人工看十几帧确认没有明显漏检。注意最大检出数这一项在堵车画面里非常关键默认 300 个框在六车道路口会直接截断尾部目标的框全丢。4.3 远景小目标切图推理与多尺度融合输入分辨率提到 1280 之后还是漏说明车辆像素本身太小这时候常见做法是切片推理把原图按固定重叠率切成若干块逐块推理再把结果映射回原图坐标做一次全局 NMS。重叠率一般取 20% 到 25%正好覆盖一辆车跨两块边界的宽度。代价是耗时按块数近似线性增长所以只对关心远处目标的画面开这个开关。多尺度推理是另一种补法同一张图跑 640 和 1280 两遍把两批框合并后统一 NMS。它的收益不如切图明显但实现简单、不引入块边界伪影在算力允许时可以当作兜底。切图方案要特别小心边界处的重复检出映射回原图后必须重新做一次类内 NMS否则同辆车会出现两个框。5. 用错误分析把车辆检测指标再抬一档调完参数就该做错误分析了这一步比继续换模型更有性价比。做法是把验证集所有预测和真值按 IoU 匹配分成三类正确匹配、误检预测框无对应真值、漏检真值无对应预测。然后把这三类按车辆尺寸、是否夜间、是否被遮挡、所属视频源分组统计哪一组异常集中问题就在哪。具体操作上写个脚本把漏检框和误检框分别画回原图按尺寸分档存成图片墙。看几十张之后通常会浮现几种模式漏检集中在图像边缘三分之一处说明增强里的随机裁剪做得不够漏检集中在小尺寸档说明 anchor 或分辨率问题误检集中在公交车和卡车的跨类边界说明类别定义本身有歧义需要回标注环节统一。硬样本挖掘是接下来的一招。把高置信度误检和低置信度漏检的样本挑出来加上常见误检场景比如车尾广告牌、路边停放的工程车、后视镜里的迷你车影一起补充标注下一轮训练里提高这批样本的采样权重。车辆检测的数据分布高度长尾一次挖掘通常能带来一两个点的 mAP 提升比换 backbone 划算。验证环节建议固定一段人工标注过的高密度视频作为回归集每次改模型或改后处理都跑一遍记录每类的 AP、漏检率和平均耗时。指标拆到「白天高速」「夜间市区」「雨天路口」三个子集上分别看比一个总体 mAP 更能反映上线之后会发生什么。最后提醒一句mAP0.5:0.95 的提升往往来自标注框贴合度的改善而不是模型变强——改完模型发现这个指标没动先回头看看标注框和真值之间是不是本来就有两三个像素的系统性偏差。本文还有配套的精品资源点击获取
返回列表