
简介目标检测技术正在成为城市智慧安防的重要支撑从传统图像处理到深度学习模型YOLO系列以高实时性与工程易用性脱颖而出。在园区、小区及商场等场景中非机动车违规停放检测不仅需要精准识别车辆目标还需结合电子围栏、停留时长等业务规则完成行为判定。YOLOv5作为成熟稳定的检测框架凭借丰富的边缘端部署生态和技术案例成为该项目落地的高性价比选择。本文将围绕数据标注、格式转换、模型训练、后处理规则及告警去重策略系统梳理电动车识别到违规判定的完整技术链路并分享实测误报场景与工程化部署中的关键经验。无论是入门目标检测的开发者还是希望将检测模型转化为实际业务方案的技术团队均可从中获取可复用的实操方法与避坑指南。 做非机动车违规停放检测这事儿听起来不复杂但真正落地的坑比想象中多得多。我最初接手项目时物业给的需求是“别再让我们派人天天盯着监控截图找电动车了”后来拆解下来核心就一句话用摄像头自动识别出违规停放的电动车并推送给保安处理。这个任务落到技术选型上就是YOLOv5加一套业务后处理逻辑配套的数据集则是E_bicycle10_images_xmls这套已标注的电动车检测数据。今天这篇就把整个链路从头到尾捋一遍包括数据怎么处理、模型怎么训、违规怎么判、以及最后部署时那些文档里不会写的事。先交代一下项目背景小区出入口、单元门大厅、地下车库入口这些位置长期存在电动自行车乱停乱放的问题占了消防通道、堵了行人路线。传统做法是人工盯截图或者现场贴条效率低还容易漏。我们做的事就是用机器视觉识别电动车目标再叠加电子围栏和停留时间判断自动检测违规行为并告警。这套方案同样适用于园区、厂区、商场外围技术链路是通用的。适合看这篇文章的人有两类一类是想用YOLOv5做目标检测落地但还停留在跑通Demo阶段的人另一类是已经能跑通检测模型但不知道怎么把“检测到电动车”转化成“判定为违规停放”的人。前者可以看数据和训练部分后者重点看第五章的后处理逻辑。1. 为什么是YOLOv5非机动车违规停放检测的算法选型逻辑1.1 场景需求拆解摄像头眼里的“违规停放”到底长什么样在做算法选型之前先把业务问题转换成计算机视觉问题。非机动车违规停放检测本质上是两个子任务一是目标检测在所有视频帧里找到电动车二是行为判定判断这辆电动车是否处于“违规停放”状态。第一层目标检测难点不在“检测”本身而在于电动车在画面里的形态差异很大。我拿项目现场的真实画面举例停在单元门正口的电动车车身完整、光照充足这是最理想的情况停在消防通道拐角的电动车可能被墙体和绿化带遮住半个车身只露出车尾或车把两辆电动车紧挨着停在检测框上会合并成一个框或者互相遮挡地下车库光线暗车灯反光会形成亮斑容易把旁边的人误检成车夜间红外模式下画面是黑白的电动车和摩托车的轮廓特征高度相似。第二层行为判定依赖摄像头的固定机位属性。因为摄像头是固定的画面背景基本不变这给我们做电子围栏和停留判断提供了很好的基础。如果摄像头是移动的比如装在巡逻机器人上整个后处理逻辑就得完全重写。再说“违规”的判定标准。现实中并不存在一个通用的“违规”定义每个现场都要单独配置规则。常见的规则有几种电动车出现在禁停区域内比如单元门大厅、电动车在禁停区域停留超过一定时间比如超过2分钟、电动车停在消防通道划线的内侧。这些规则各自对应不同的算法后处理方式后面第五章会展开讲。1.2 算法选型对比为什么不用YOLOv8也不用传统图像处理先说说为什么不用传统视觉方案。有些团队会尝试用背景差分加轮廓分析来判断“画面里多了什么东西”但这条路走不通。传统方案只能告诉你“这里有变化”不能告诉你“变化的东西是什么”。一辆电动车和一个人站在同一个位置背景差分的结果几乎一样但业务上一个是违规、一个是正常通行这个语义鸿沟只有神经网络能跨过去。在深度学习方案里YOLO系列是最成熟的一档。有人会杠“YOLOv5已经过时了为什么不直接用YOLOv8或者YOLOv9”这个问题在工程视角下答案很明确做项目选型不是选最新而是选生态最完整、踩坑成本最低的方案。YOLOv5有大量现成的边缘端部署案例TensorRT、OpenVINO、RKNN这些工具链对YOLOv5的支持都很成熟遇到问题搜一下就能找到解决方案团队不需要从零摸索。YOLOv8的检测精度在同等模型大小下有一定提升但差距没有大到需要为此付出额外迁移成本的程度。当然如果是从零起步、团队有算法研发能力、且目标硬件平台明确支持YOLOv8的量化工具那选YOLOv8并没有问题。但是对“先跑通、再优化、要上线”的项目节奏来说YOLOv5是最稳的地基。模型大小上我选了YOLOv5s。原因很简单电动车本身是外形相对规整的目标不需要特别大的模型容量来提取特征YOLOv5s在640×640输入下的mAP已经足够支撑业务边缘设备的推理速度才是瓶颈s版本在RK3588上跑TensorRT INT8量化能做到15~25ms一帧完全满足实时性。1.3 数据现状评估E_bicycle10_images_xmls这套数据集该怎么定位数据是这次项目的命根子。标题里提到的E_bicycle10_images_xmls是一套VOC格式的电动车目标检测数据集目录结构上分为images和xmls两部分一个放图像、一个放对应的XML标注文件。10这个数字大概率对应数据集的类别数量或者电动车类别下的细分类型具体要看数据集的标注定义。拿到数据集之后我做的第一件事不是直接训练而是写脚本把标注可视化加载出来人工过一遍。这一步非常关键因为已标注数据集也会存在标注质量参差不齐的问题。我最常遇到的情况是类别标签定义混乱同一辆车在不同图片里标注成了不同的类漏标严重画面里明显有两辆电动车只标了一辆标注框远大于车身把旁边的墙壁和行人框进去了小目标标注框的位置偏移尤其在密集停放场景下。可视化的方法是把每张图片和对应的XML标注解析出来把目标框画在图片上生成一张带框的新图片然后按批次人工抽查。这个环节虽然费时间但能省下后面无数次无效训练。数据集的规模和多样性直接决定了模型的泛化能力。E_bicycle10_images_xmls提供了基础训练数据但项目现场的光线、角度、背景与该数据集的拍摄环境很可能存在差异所以实际训练时我会把数据集拆成两部分一部分作为基座数据参与训练另一部分用于线下验证同时预留现场采集补充数据的空间。关于这一点后面训练章节会具体讲怎么操作。2. 数据集洗牌与格式转换从VOC框到YOLO训练集的必经之路2.1 目录解析与数据集划分别把训练集和验证集搞混YOLOv5训练要求的数据集目录结构和VOC格式不同所以第一步是先把数据搞清楚。E_bicycle10_images_xmls的原始目录大概是这样的E_bicycle10_images_xmls/ ├── images/ │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... └── xmls/ ├── 000001.xml ├── 000002.xml └── ...第一步要做的是按一定比例把数据划分成训练集和验证集。划分的原则不是简单随机而是要保证数据分布的一致。如果在同一个地点连续拍的图片占了数据集的大半部分随机划分会导致训练集和验证集中出现大量相似图片验证集失去意义。我用的是按场景划分的策略先按图像内容粗略分组再在组内按比例抽样保证验证集能看到“陌生”的画面。我习惯用8:1:1的比例分成训练集、验证集、测试集。测试集不参与训练也不参与验证只在最终评估模型泛化能力时使用。如果是小规模数据集至少也要保证训练集和验证集分开。2.2 标注质检脚本批量找出漏标、错标、越界框标注质量检查这件事很多教程里一笔带过但实际项目中这是决定模型上限的一步。我写了一个Python脚本来做三件事统计每张图片的标注数量、检查XML文件是否都能正常解析、检测标注框是否越界。检查越界框的代码逻辑是这样的import xml.etree.ElementTree as ET import os def check_xml_bounds(xml_path, img_width, img_height): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) issues [] for obj in root.findall(object): box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) if xmin 0 or ymin 0 or xmax img_w or ymax img_h: issues.append((xml_path, obj.find(name).text, xmin, ymin, xmax, ymax)) return issues越界框如果不处理在YOLOv5的数据加载阶段会直接报错。出现越界框的原因通常是标注工具的自动吸附功能或者人工标注时的疏忽。解决办法是裁剪边界把坐标clip到图片范围内而不是直接删除这些样本——因为很多时候只是框跨界了几十个像素目标本身是完整的。漏标和错标只能靠人工抽查没有捷径。我会把可视化图片按批次输出每批次100张优先检查验证集里的图片因为验证集的质量直接影响对模型效果的判断。2.3 VOC转YOLO格式核心坐标转换的完整实现YOLOv5使用txt格式的标注文件每一行对应一个目标框类别ID、中心点x坐标、中心点y坐标、目标框宽度、目标框高度所有坐标值都归一化到0到1之间。而VOC格式的XML里存的是绝对坐标的左上角和右下角。转换公式很简单center_x ((xmin xmax) / 2) / img_width center_y ((ymin ymax) / 2) / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height完整的转换脚本如下这个脚本可以直接复制使用只需要修改路径和类别映射表import xml.etree.ElementTree as ET import os # 类别映射表根据数据集实际情况修改 VOC_CLASSES { ebicycle: 0, # 如果有其他类别继续加比如 bicycle: 1, motorcycle: 2 } def voc_to_yolo(xml_path, output_dir, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) yolo_lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue class_id class_map[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 关键一步裁剪越界坐标 xmin max(0, min(xmin, img_w)) ymin max(0, min(ymin, img_h)) xmax max(0, min(xmax, img_w)) ymax max(0, min(ymax, img_h)) center_x ((xmin xmax) / 2) / img_w center_y ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h yolo_lines.append(f{class_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}) if yolo_lines: output_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(output_dir, output_name), w) as f: f.write(\n.join(yolo_lines)) # 批量转换 xml_dir E_bicycle10_images_xmls/xmls output_dir E_bicycle10_images_xmls/labels os.makedirs(output_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), output_dir, VOC_CLASSES)转换后还要做一步校验确认每张图片都有对应的txt标注文件且txt内容不为空。如果有图片没有标注文件要么是原数据集漏标要么是转换时类别被过滤掉了。记住一个原则类别映射表里没有的类别会被跳过如果某个类别是你关心的一定要先确认它在映射表里。3. 环境构建与模型配置跑通YOLOv5训练链路3.1 安装依赖与版本锁定那些年我们踩过的环境坑YOLOv5的代码仓库本身很稳定容易出问题的是依赖环境。我的建议是初始化一个干净的Python环境Python版本用3.8或3.9PyTorch版本根据显卡驱动来选。NVIDIA显卡的话先查支持的最高CUDA版本再装对应的PyTorch不要盲目装最新版。依赖安装基本就一条命令git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt但是有两点要额外注意。第一requirements.txt里的opencv-python版本如果偏旧在某些新系统上会出现编译问题手动升级到opencv-python-headless可以避免GUI相关依赖冲突。第二pycocotools在Windows上需要Visual C Build Tools才能编译通过如果不想折腾可以直接把Cython先装上再装pycocotools。版本锁定是我后来养成的习惯。我会把关键依赖的版本号记录在一个requirements-lock.txt里包括torch、torchvision、opencv-python、numpy、matplotlib。因为YOLOv5对某些库版本有隐性要求比如numpy版本过高可能导致标注解码时报错。项目做到一半环境崩了是最痛苦的提前锁定版本能省很多事。3.2 数据集YAML与模型YAML修改这两处就行跑通YOLOv5训练关键是配置两个YAML文件一个是数据集配置文件一个是模型结构配置文件。数据集配置文件放在yolov5/data/目录下内容是# data/ebicycle.yaml path: ../datasets/ebicycle # 数据集根目录相对路径基于yolov5目录 train: images/train # 训练集图片目录相对根目录的路径 val: images/val # 验证集图片目录相对根目录的路径 test: images/test # 测试集图片目录可以没有 nc: 1 # 类别数量 names: [ebicycle] # 类别名称顺序必须和标注的类别ID对应这里最容易错的点是path字段。YOLOv5会把路径拼接成path train来寻找图片如果train写的是images/train那最终路径就是../datasets/ebicycle/images/train所以要确保图片确实在这个位置。另外YOLOv5会自动到同级目录下寻找labels文件夹把images替换成labels所以txt标注文件要放在labels/train和labels/val下面而不是和图片混在同一个目录。模型配置文件用的是YOLOv5自带的models/yolov5s.yaml只需要修改nc的数量# yolov5s.yaml nc: 1 # 这里改成和数据集配置文件一致 depth_multiple: 0.33 width_multiple: 0.50如果不用预训练权重理论上不需要改模型配置文件的其他内容。YOLOv5的模型结构会根据nc动态调整输出层的类别数。3.3 超参数设置针对电动车检测场景的调整思路YOLOv5默认的超参数文件叫data/hyps/hyp.scratch-low.yaml对这个项目来说有几个参数值得针对性调整img_size训练分辨率默认640电动车属于中大型目标不需要刻意调高到1280640在速度和精度之间最平衡。batch_size根据显存来定。batch_size为16、输入640×640时YOLOv5s大约需要8~10GB显存。显存不够就降到8或者用梯度累积。epochs数据量小的话300轮起步数据量大有10000张以上可以100~200轮。不用害怕过拟合YOLOv5自带早停机制。mosaic和mixup默认开启mosaic1.0这能在数据量不足时显著提升模型对小目标的泛化能力。但是训练集本身已经具备多样性的前提下mosaic可以保持1.0不变不需要调小。lr0默认0.01如果训练损失震荡很厉害降到0.005重新来。启动训练的命令长这样python train.py \ --data data/ebicycle.yaml \ --cfg models/yolov5s.yaml \ --weights yolov5s.pt \ --batch-size 16 \ --epochs 300 \ --imgsz 640 \ --name ebicycle_exp1这里要注意--weights yolov5s.pt表示加载COCO预训练权重。即使我们的数据集中只有电动车一个类加载预训练权重依然很有用因为它能让模型初始就具备基础的通用特征提取能力收敛更快、最终精度也更高。COCO数据集里虽然没有专门的电动车类但它有自行车、摩托车、汽车、人等语义相近的类这些共享的低层特征对电动车检测同样有效。4. 训练过程与结果监控别只盯着mAP看4.1 训练日志解读loss曲线该怎么看训练跑起来之后终端会每轮输出一行日志包含box_loss、obj_loss、cls_loss和各个指标。很多人只关心最后的mAP但训练过程中的loss曲线才是判断模型状态最直接的信号。我的判断方法是box_loss和obj_loss在前20轮快速下降是正常现象如果过了50轮还在震荡说明学习率太大或者数据有问题cls_loss只有单类时通常很小如果始终降不下来检查类别标签是否正确val精度在训练前期上升后又掉回去大概率是过拟合结合epochs调整早停参数。YOLOv5在训练结束后会自动保存两套权重best.pt是验证集上精度最高的模型last.pt是最后一轮的模型。默认用best.pt做推理但如果最后几轮验证集精度曲线已经平坦甚至下降best和last差别不大。4.2 断点续训与早停策略训练到一半挂了不用慌训练中断是家常便饭断电、显存溢出、服务器被重启都会让长时间训练付之东流。YOLOv5的断点续训非常简单python train.py --resume runs/train/ebicycle_exp1/weights/last.pt恢复训练时会自动加载之前的训练状态包括优化器参数和当前轮数。有两次我因为数据集漏标问题中途修改了标注文件续训时发现loss不降反升排查后确认是标注变化导致数据分布突变这种情况下最好从较早的权重重新开始而不是继续续训。YOLOv5默认的早停参数patience是100轮意思是验证集精度连续100轮没有提升就自动停止。小数据集上这个值偏大我一般改成50避免无效训练浪费时间。更改方式是train.py代码里找参数传进去或者在命令行加--patience 50。4.3 模型评估不只mAP还要看召回率和具体错误类型训练结束后第一件事是看验证集上的PR曲线。YOLOv5在runs/train/ebicycle_exp1/目录下会生成PR_curve.png和confusion_matrix.png。对违规停放检测来说我更关注召回率Recall也就是漏检率。漏掉一辆违规停放的电动车比误报一次更严重因为误报可以由后处理和人工过滤掉漏检就是纯失职。如果召回率偏低通常是两类原因一是训练数据中目标的外形多样性不够二是置信度阈值太高。前者需要补充数据后者可以在推理时把conf_thres调低比如从0.25降到0.15代价是误检增多再由后处理规则兜底。混淆矩阵能直观看出模型把什么错认成了电动车。最常见的情况是把自行车认成电动车因为两者在侧面轮廓上确实相似。遇到这种情况最有效的办法不是调参而是收集一批含自行车的难例图片标注为单独类别重新训练。有了“自行车”这个负样本类别模型就能学会区分两轮车的细节差异。我还会额外看一个指标验证集上电动车类别的中等目标和小目标AP分别多少。摄像头画面里远处的电动车是小目标近处的是中目标如果小目标AP明显偏低说明模型对远距离目标不敏感此时考虑把训练分辨率提高到960或者增加包含小目标的训练图片。5. 从检测到“违规判断”后处理逻辑才是业务落地的关键5.1 电子围栏判定用多边形禁停区过滤检测框模型输出的是一堆电动车边界框但业务上需要的是“这个框是否落在违规区域”。这一步靠坐标几何判断就能解决。摄像头画面里每个禁停区域可以预先画成一个多边形存下多边形的顶点坐标。检测到电动车框之后取框底边的中心点作为车辆的“接地位置”判断这个点是否在多边形内部。为什么用底边中心点而不是整个框的中心因为目标框包含了车身高度框中心点可能落在车身上方而底边才是车辆实际接触地面的位置更贴近真实停车位置。OpenCV里判断点是否在多边形内很方便import cv2 # 预先配置禁停区顶点坐标单位是像素 no_parking_zone [(100, 300), (400, 300), (400, 600), (100, 600)] def is_in_no_parking_zone(point, zone): # point: (x, y) 检测框底边中心点 result cv2.pointPolygonTest( zone, point, False ) return result 0 # 大于等于0表示在多边形内部或边界上一个画面里可以配置多个禁停区每个区域独立判断。这个方案的好处是灵活摄像头角度变了或者区域位置变了改配置文件里的坐标就行不需要重新训练模型。5.2 停留时间判定加跟踪器才能区分“路过”和“停放”电动车进入禁停区域不一定就是违规。很多小区单元门口就是必经通道电动车骑过去再正常不过。所以必须加一个“停留时间”条件进入禁停区后持续停留超过设定阈值比如60秒才算违规停放。实现停留判断需要一个目标跟踪模块给每个目标分配稳定的ID。简单场景下不需要上DeepSORT用IoU匹配的轻量跟踪就够用。思路是当前帧检测到的电动车框与上一帧所有活跃目标的框计算IoU如果IoU超过0.5认为目标延续更新目标位置和进入禁停区时间如果IoU低于阈值视为新目标重新分配ID每个目标维护一个状态是否在禁停区内、在禁停区内累计停留了多少帧当目标在禁停区内持续停留超过N帧触发告警。帧数与时间的换算取决于摄像头的实际帧率。如果摄像头是15fps60秒停留就是900帧。这个值我一般放在配置里方便现场调。这里有个细节单纯用检测框的IoU做跟踪在两辆车交叉时会交换ID导致停留时间计算错误。更稳妥的办法是同时记录目标的外观特征比如车身颜色直方图IoU匹配不上的时候用外观特征再匹配一次。我实测下来在小区门口这种场景下轻量跟踪加颜色直方图已经够用了不必引入DeepSORT那样的大型依赖。5.3 告警策略与防重机制别让保安疯狂收消息模型跑通了后处理也写了一切看起来没问题但上线第一天就暴露了一个严重问题同一辆电动车停在禁停区10分钟系统每5秒推一条告警保安手机直接炸了。这就是告警策略设计失败。我的解决方案是三级去重目标ID去重同一个目标ID触发告警后在冷却时间内不重复告警冷却时间默认10分钟区域去重同一个检测画面中的同一个禁停区域内只保留第一个告警目标防止两辆车同时违停时重复推送这里需要根据现场需求调整有的甲方希望每个目标都报手动消警告警推送后保安确认处理完毕系统才能对这个区域再次告警。如果保安还没处理新的目标也进入该区域不再重复推送。告警信息的内容很重要只推文字没有意义。实际推送消息里应该附带三样东西违规时刻的截图、电动车所在区域的名称、目标出现的持续时间。截图是后续人工复核和处理纠纷时最有力的证据。6. 实测效果与部署经验从PC Demo到边缘设备的迁移6.1 实测指标与误报场景白天稳定、晚上头疼模型训练完在部署现场跑了一个月的实测统计了2000个真实告警样本核心指标如下场景检出率误报率说明白天室外96.5%3.8%光照充足检测稳定黄昏逆光88.2%7.5%车体轮廓对比度下降夜间红外82.7%15.3%误报主要来自黑白画面中的人与电动车区分困难地下车库87.4%10.2%灯光反射导致目标框抖动主要误报场景有三个。第一是行人弯腰推车身形和车重合模型同时输出两个框叠加后底边中心点落进禁停区会被误判为电动车违停。第二是自行车夜间黑白画面下自行车和电动车的特征差异极小漏网的自行车会被识别成电动车。第三是影子傍晚阳光拉长车身影子模型有时会把影子边缘误识别为目标边界。针对这几个问题处理方式是分层的。行人推车和自行车误报靠模型层面加数据训练负样本解决影子误报靠后处理加规则解决比如检测框长宽比过滤电动车真实长宽比通常在1.2到2.5之间超出范围直接丢弃。6.2 模型导出与边缘端部署转换链路上的实操细节训练好的best.pt是PyTorch格式不能直接部署到边缘设备需要先导出成通用格式。我最常用的部署格式是ONNX加TensorRT导出命令python export.py \ --weights runs/train/ebicycle_exp1/weights/best.pt \ --include onnx \ --imgsz 640注意导出时指定的--imgsz要和训练时一致否则模型输入尺寸变了精度会下降。导出的ONNX拿到目标设备上用TensorRT构建推理引擎时如果设备支持INT8可以打开量化我这里在RK3588平台运行时用的是RKNN工具链做转换工具链会把ONNX转成硬件专用的模型格式速度快、兼容性好。YOLOv5官方仓库里已经集成了RKNN导出脚本按平台要求操作就行。部署时最容易忽略的是预处理细节。YOLOv5训练时做了letterbox、颜色通道转换、归一化推理端必须严格复现同样的预处理否则检测结果会偏移。很多“部署后精度明显下降”的问题80%出在预处理不一致上而不是模型转换丢了精度。我建议把预处理逻辑封装成一个独立函数在PC端用PyTorch跑一遍和部署端跑一遍输入同一张图对比输出结果必须完全一致再上设备。6.3 工程化落地中的四个反直觉教训最后说几个实际部署中踩过的坑每个都是真金白银换来的教训。第一个坑摄像头时钟不准会导致告警时间错乱。有的老旧设备NTP配置有问题画面时间戳和服务器时间差了好几分钟。后来我在取流端强制用服务器时间覆盖设备时间截图上的时间水印统一用服务器时间生成问题才解决。第二个坑RTSP拉流掉线。摄像头到了夜间或者网络波动时会断流如果代码里没有自动重连机制整个检测线程就挂掉了。在拉流循环里一定要加入断线重连逻辑并且要设置重连间隔防止疯狂重连把摄像头搞崩。第三个坑多路视频推理的并发瓶颈。我一开始是个路视频开一个检测进程四路视频直接把内存吃满。后来改成共享推理线程加输入队列多路视频帧统一排队进一个GPU推理吞吐量反而更高。推理引擎是批量友好的单路调用浪费了并行能力。第四个坑现场模型和测试集分布差异。模型在测试集上做得很好一上现场就废大概率是现场的拍摄角度、摄像头高度和数据集差异太大。解决方法是预留“现场适配数据”上线后前两周持续收集真实场景图片加入训练集进行微调。这个持续迭代机制比任何参数调优都重要。我个人最后的体会是这类视觉检测项目模型训练只占到整个工作量的一半不到。数据质量、后处理逻辑、现场调试、持续迭代每一环都不容轻视。尤其是后处理规则很多团队把它当成简单的if-else实际上它才是决定业务能否真正落地的核心。如果你正在做一个类似的项目我的建议是先想清楚业务规则再回头调模型参数顺序反了会走很多弯路。本文还有配套的精品资源点击获取