☰
YOLOv5非机动车违停检测:数据集转换与训练实战
2026/10/1 1:47:19 网站建设 项目流程

简介:这是一份面向机器视觉与目标检测场景的已标注非机动车自行车子数据集,专用于YOLOv5等模型的非机动车违规停放识别训练。包内共1896个文件,包含951张jpg原图与945个xml标注文件,压缩包整体大小138.26MB。该子集取自完整十类自行车数据中的第三类,图像覆盖城市道路、小区出入口、商圈周边等常见违规停放场景,xml标注文件与图片一一对应,标注框已框出自行车目标,可直接用于YOLOv5训练,或按需转换为COCO等格式。目前已有275人学习下载,适合算法学习者、安防项目开发者及智慧城市相关从业人员,借助这份数据集可快速构建非机动车违规停放识别模型,免去自行采集与标注的繁琐流程,大幅提升开发效率。

1. 把 YOLOv5 用到非机动车违停检测:先搞清楚这份资源到底解决什么问题

做机器视觉落地的人,十有八九都碰过这个场景:小区消防通道、商场门口、地铁站周边,自行车和电动车横七竖八一停,监控画面拍得清清楚楚,但后端就是没有一套能自动报警的系统。YOLOv5 本身是个成熟的目标检测框架,但真正要落地到「非机动车违规停放」这个细分任务上,缺的不是模型结构,而是两点:一是符合业务场景的已标注数据集,二是把检测结果转成管理动作的流程。这份「yolov5+非机动车违规停放+已标注数据集+机器视觉识别+自行车 bicycles2_images_xmls」资源,核心价值在于它把数据、标注、训练基线一次给齐了,尤其适合那些想在自有场景里做违停检测验证,又不想从零开始标几千张图的工程师。

资源里最实在的部分是 bicycles2_images_xmls 这个数据集包,图片是实拍场景下的自行车/电动车样本,标注格式是 XML(VOC 格式),这意味着你可以直接用 YOLOv5 的 VOC 转 YOLO 脚本做预处理,也可以丢进 Detectron2、MMDetection 这类框架里做对比实验。本篇就把整套东西拆开讲清楚:数据集长什么样、转化脚本怎么写、训练参数怎么调、哪些坑最容易翻车,以及最后怎么把模型接到真实场景里做验证。

2. 选型逻辑与数据集解构:为什么用 YOLOv5,以及 bicycles2_images_xmls 里面到底有什么

2.1 为什么这个场景选 YOLOv5 而不是 YOLOv8 或 Faster R-CNN

非机动车违停检测有一个特殊性:它是典型的「固定相机、固定视角、目标形态有限」的监控场景,不像自动驾驶那样需要处理极端尺度变化和遮挡。用 YOLOv5 在这个场景里有两个非常现实的理由。

第一,YOLOv5 的工程生态最成熟。训练脚本、导出脚本、TensorRT 部署、ONNX 转换,这些在 YOLOv5 仓库里都是开箱即用的,社区踩坑记录也最多。你搜「yolov5 训练自己的数据集」能找到大量可复现的教程,这对于做落地验证来说,比模型精度绝对值更重要。第二,YOLOv5 的 CPU/边缘设备部署方案齐全。非机动车违停检测通常要跑在现场的 NVR 或小盒子(比如树莓派 5、Jetson Nano)上,YOLOv5s 的参数量在 7M 左右,FP16 推理在 Jetson 上能做到 30FPS 以上,这个性价比是 Faster R-CNN 给不了的。而 YOLOv8 虽然新,但它的核心改进集中在训练策略和 Anchor-Free 头,对于「区分自行车/电动车/行人」这种粗粒度任务,提升并没有那么显著,反而增加了部署兼容性的不确定。

从数据集适配性来看,VOC XML 标注本身就是 YOLOv5 官方支持的数据格式,voc2yolo.py脚本可以直接用。如果你的目标是快速验证算法的可行性,而不是刷论文指标,YOLOv5 是投入产出比最高的方案。

提示:如果你的项目已经有大量 polygon 标注或者旋转框标注,那么 YOLOv8 的 OBB 支持会更有优势;但 bicycles2_images_xmls 是 axis-aligned 的 VOC 框,用 YOLOv5 是零损耗的。

2.2 数据集目录结构与标注格式解析

拿到资源包后,第一件事不是急着训练,而是先把目录结构摸清楚。bicycles2_images_xmls 的设计遵循了 VOC 数据集的经典布局,展开后大致是这样:

bicycles2_images_xmls/ ├── images/ │ ├── bicycles2_001.jpg │ ├── bicycles2_002.jpg │ └── ... ├── xmls/ │ ├── bicycles2_001.xml │ ├── bicycles2_002.xml │ └── ... └── classes.txt

images目录存放原始图片,xmls目录是每张图片对应的 PASCAL VOC 标注文件,classes.txt是类别清单。每个 XML 文件内部的结构是标准的 VOC 格式:

<annotation> <folder>images</folder> <filename>bicycles2_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>bicycle</name> <bndbox> <xmin>412</xmin> <ymin>258</ymin> <xmax>678</xmax> <ymax>491</ymax> </bndbox> </object> </annotation>

注意两点。第一,这里的<name>是bicycle,说明类别名不一定叫bicycle,也可能是electric_bicycle或non_motor_vehicle,具体以资源里的classes.txt为准,这个信息决定了后续转换脚本里的 class list,不能搞错。第二,图片尺寸是 1280x720,这就是监控视频帧的典型分辨率,不是网上爬下来的正方形图片,这意味着训练时的imgsz参数可以直接沿用 640 或 1280,不需要额外的 resize 逻辑。

2.3 类别分布与训练前检查清单

在动手之前,花十分钟做一次数据体检能省掉后面大量排错时间。检查清单如下:

检查项具体做法常见问题
图片与 XML 对应遍历 xmls,检查每个 filename 是否能找到对应的 jpg文件名缺号、大小写不一致
框坐标合法性检查 xmin < xmax, ymin < ymax,且不超出图片边界标注软件误操作导致的负坐标
类别一致性统计 xml 里所有 name 的集合,与 classes.txt 比对混入了 person、car 等无关类别
空标签检查统计没有任何 object 的 xmlYOLOv5 训练时背景图片过多会拉低 mAP

我一般会写一段快速校验脚本来处理前两项,用 Python 的xml.etree.ElementTree把全部标注读一遍,有异常直接打印文件名和具体报错:

import os import xml.etree.ElementTree as ET xml_dir = "bicycles2_images_xmls/xmls" img_dir = "bicycles2_images_xmls/images" for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() img_name = root.find("filename").text # 检查图片是否存在 if not os.path.exists(os.path.join(img_dir, img_name)): print(f"[ERROR] Missing image: {img_name}") # 检查坐标合法性 for obj in root.iter("object"): box = obj.find("bndbox") xmin = int(box.find("xmin").text) ymin = int(box.find("ymin").text) xmax = int(box.find("xmax").text) ymax = int(box.find("ymax").text) if xmin >= xmax or ymin >= ymax: print(f"[ERROR] Invalid bbox in {xml_file}: {xmin},{ymin},{xmax},{ymax}")

这段脚本的逻辑很简单但很实用:对每个 XML 先核对filename对应的图片是否存在于 images 目录,避免训练时因为找不到图片而报错;再对每个object的bndbox做坐标合法性校验。YOLOv5 的datasets.py在加载标签时会对越界框做警告,但有些警告只打印不报错,会悄悄影响训练质量,不如在源头上就拦掉。

第 3 点类别一致性检查容易忽略。如果 XML 里有name字段标成了motorbike而不是bicycle,转换脚本会把未知类别直接跳过或识别为背景,那这批数据就等于作废了。稳妥的做法是把所有 XML 的<name>去重打印出来,和classes.txt手工比对一遍,确认完全一致再进入下一环节。

3. 把 VOC XML 转成 YOLOv5 格式:转换脚本与数据划分的正确姿势

3.1 标准转换流程:从 XML 到 TXT 标签

YOLOv5 训练需要的标签格式不是 XML,而是每个图片对应一个同名 TXT 文件,每行内容为class_id center_x center_y width height,坐标值全部归一化到 0~1。这个转换过程是数据准备阶段绕不过去的一步,常见做法是用xml2yolo之类的开源脚本,或者自己写一段一次性脚本。这里给出我自己常用的转换逻辑:

import os import xml.etree.ElementTree as ET from glob import glob # 配置路径 xml_dir = "bicycles2_images_xmls/xmls" image_dir = "bicycles2_images_xmls/images" label_dir = "bicycles2_images_xmls/labels" os.makedirs(label_dir, exist_ok=True) # 类别列表(务必和 classes.txt 保持一致) class_names = ["bicycle", "electric_bicycle"] for xml_path in glob(os.path.join(xml_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() img_name = root.find("filename").text img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) # 解析所有目标框,格式转换为 YOLO 归一化坐标 lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in class_names: continue # 跳过未知类别 cls_id = class_names.index(cls_name) box = obj.find("bndbox") xmin = int(box.find("xmin").text) ymin = int(box.find("ymin").text) xmax = int(box.find("xmax").text) ymax = int(box.find("ymax").text) # 归一化计算 center_x = ((xmin + xmax) / 2) / img_w center_y = ((ymin + ymax) / 2) / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{cls_id} {center_x:.6f} {center_y:.6f} {width:.6f} {height:.6f}") # 写出与图片同名的标签文件 label_path = os.path.join(label_dir, img_name.replace(".jpg", ".txt")) with open(label_path, "w") as f: f.write("\n".join(lines)) print("转换完成,标签文件数量:", len(glob(os.path.join(label_dir, "*.txt"))))

这里有三个细节值得注意。第一是类别过滤的写法:if cls_name not in class_names: continue,当你发现某些类别的目标框被丢弃时,重新检查 class_names 和 XML 里的实际 name 是否一致。第二是归一化精度保留 6 位小数,1280x720的图上这个精度足够,再长的小数位不仅没用,还会让标签文件变大。第三是输出路径结构和图片目录是平行关系,后面训练配置里train.txt和val.txt引用的就是这套路径。

3.2 训练集/验证集划分:不要碰测试集

数据集划分是个看着简单但容易翻车的步骤。很多初学者把数据随机分成 7:3,然后两个集合都在训练时用,这等于把验证集泄漏进了训练过程,得到的高 mAP 没有说服力。对于违停检测这个场景,我建议划分方式是:8:1:1,训练集 80%,验证集 10%,测试集 10%。但请注意,测试集在这个资源包场景里不是必须的——如果你只是想训练模型用于现场 demo,那么验证集就足够了。真正的测试集应该从现场采集的、完全不参与训练的数据中去留。

划分脚本的核心逻辑是打乱顺序后按比例切分,同时生成 YOLOv5 需要的train.txt和val.txt文件,里面是图片的绝对路径列表:

import os import random img_dir = "bicycles2_images_xmls/images" train_txt = "bicycles2_images_xmls/train.txt" val_txt = "bicycles2_images_xmls/val.txt" imgs = [os.path.join(img_dir, f) for f in os.listdir(img_dir) if f.endswith(".jpg")] random.seed(42) # 固定随机种子,保证每次划分结果一致 random.shuffle(imgs) val_count = int(len(imgs) * 0.1) val_imgs = imgs[:val_count] train_imgs = imgs[val_count:] with open(train_txt, "w") as f: f.write("\n".join(train_imgs)) with open(val_txt, "w") as f: f.write("\n".join(val_imgs)) print(f"train: {len(train_imgs)}, val: {len(val_imgs)}")

随机种子seed(42)这个细节很重要。不设种子的话,每次运行脚本数据的划分结果都不一样,你复现别人的训练结果时,mAP 波动会很大。固定种子后,同一份数据在任何机器上划分结果一致,这是可复现性的基础。

3.3 YOLOv5 训练配置文件怎么改

数据准备好后,需要修改 YOLOv5 项目里的 data yaml 文件。这个文件通常长这样:

# 文件路径:yolov5/data/bicycles.yaml train: /absolute/path/to/bicycles2_images_xmls/train.txt val: /absolute/path/to/bicycles2_images_xmls/val.txt test: # 可选,测试集路径留空即可 nc: 2 # 类别数量,必须和 class_names 长度一致 names: ['bicycle', 'electric_bicycle'] # 类别名称,必须和 classes.txt 一致

这里有一个新手最容易踩的坑:train和val的路径建议写绝对路径。YOLOv5 解析相对路径时,是相对于当前工作目录的,如果你在项目根目录下启动训练没问题,但如果你在别的目录下执行python train.py,相对路径就会失效,报FileNotFoundError。这也是为什么很多教程里强调路径前要加/absolute/path/to/的原因。

另一个容易被忽略的点是:names列表的顺序必须和转换脚本里 class_names 的顺序保持一致。如果转换时是按["bicycle", "electric_bicycle"]生成的标签,但 yaml 里的 names 写成了['electric_bicycle', 'bicycle'],那训练时 pork 不会报错,但模型学到的类别语义会错乱,推理结果完全没法用。

4. YOLOv5 训练实操:超参数设置、断点续训与训练结果判读

4.1 训练命令与关键超参数说明

训练命令是整套流程里最需要耐心调的部分。这里给出一条经过实际验证的基线命令,并解释每个关键参数的来由:

cd yolov5 python train.py \ --data /path/to/bicycles.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --cache \ --device 0

--weights yolov5s.pt是预训练权重,在 COCO 上训过的模型。用预训练权重做 fine-tune 比从头训练收敛快得多,通常前 20 个 epoch loss 就能降到可接受范围。--img 640是训练分辨率。你的数据本身是 1280x720,缩到 640 训练是 YOLOv5 的默认策略,原因有两点:一是 640 是 COCO 预训练的标准分辨率,迁移效果最好;二是训练速度差不多是 1280 的 4 倍。等模型在 640 上收敛后,再拿测试集用--img 1280做推理,检测精度会有一个明显提升,这个技巧叫 Test-Time Augmentation 的简化版。

--batch-size 16需要根据显存来定。如果你用的是 8GB 显存的卡,16 可能是上限了;如果是 24GB 的 3090/4090,可以尝试--batch-size 32。注意 YOLOv5 的默认学习率 0.01 是基于 batch-size 16 校准过的,如果你强行加大 batch-size 而不调整学习率,训练初期 loss 可能会出现震荡。

--cache参数值得单独说一句。它会把图片预先加载到内存中,加速每个 epoch 的数据读取。数据量小的时候体验不明显,但当数据集超过 5GB 时,这个参数能省下大量磁盘 I/O 等待时间。代价是占用内存,如果你的机器同时跑着其他服务,可以不加。

4.2 训练过程中看什么:loss 曲线的正确读法

训练一旦开始,train.py会在终端持续打印 loss 信息,同时在runs/train/exp目录下输出 results.csv 和 results.png。很多新人盯着box_loss和obj_loss两个指标,觉得它们降得越快越好,这是一个很典型的误读。

正确的读法是:box_loss代表边界框回归的误差,obj_loss代表目标存在性的置信度误差,两个值都在下降说明模型在同时学习「目标在哪」和「目标是什么」。但如果obj_loss降得很快而box_loss停滞,说明你的数据里框的标注质量有问题——可能坐标不准确,也可能框的大小分布极不均匀。另外要注意val/box_loss和val/obj_loss这两条曲线:如果训练开始后 train loss 持续下降,但 val loss 在某个 epoch 后开始回升,就是标准的过拟合信号,这时候应该提前停止训练,而不是硬跑完 100 个 epoch。

YOLOv5 会自动保存最后一个 epoch 的权重和 best.pt 权重。best.pt 的依据是验证集上的 mAP 综合表现,不是简单看 loss 最低点。所以在判断训练结果时,直接看eval阶段打印的mAP@0.5和mAP@0.5:0.95两个指标:前者代表粗略定位的准确率,后者代表精确定位的准确率。对于违停检测场景,mAP@0.5 更重要,因为管理端只需要一个大致框位置来触发报警,不需要像素级分割精度。

4.3 断点续训与新增数据微调

训练到一半机器断电,或者你在别的机器上想接着训练,这是工程里一定会遇到的事。YOLOv5 原生支持断点续训:

python train.py \ --data /path/to/bicycles.yaml \ --weights runs/train/exp/weights/last.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --resume

关键点是--resume参数。加上它之后,train.py 会读取runs/train/exp/weights/last.pt里保存的训练状态,包括当前 epoch 数、optimizer 的动量参数、学习率调度器的位置,从断点处无缝继续。如果不加--resume,即使你传了last.pt,它也只是把这个文件当作预训练权重,学习率从头开始,效果会差很多。

新增数据的微调场景也类似。假设现场又采集了 200 张新图,补标完成后,推荐的做法是把新数据合并进原始训练集,然后重新走一遍数据划分和转换流程,再用last.pt做起点继续训练。这时候不要动原数据集,只把新数据加进去重训,能最大限度保留模型已学到的特征。

4.4 标注数据量不够怎么办:数据增强与迁移学习的边界

如果你的数据量只有 200~300 张,这是最常见的尴尬局面。YOLOv5 自带的 Mosaic 数据增强、MixUp、HSV 扰动能显著扩充有效样本量,但它的增强参数是面向 COCO 数据集调好的,不一定适合你的场景。可以在 train.py 里通过--hyp传入一个自定义的 hyperparameter 文件来调整:

# hyp.custom.yaml(在 yolov5/data/ 下新建) mosaic: 0.8 # 降低 mosaic 概率,原默认 1.0 mixup: 0.2 # 开启 mixup,但概率调低 hsv_h: 0.02 # 色调扰动,监控画面色彩相对稳定 hsv_s: 0.6 # 饱和度扰动幅度适当加大 hsv_v: 0.4 # 明度扰动 fliplr: 0.5 # 左右翻转对自行车来说有意义

对于违停场景,hsv_h建议调小到 0.02 以下,因为监控相机的白平衡通常稳定,色相大幅扰动反而会让模型学到对光线变化的错误依赖。fliplr保持默认 0.5 就行,自行车左右对称,翻转不会产生语义错误。Mosaic 增强在这一场景很有用,它能把四张图拼在一起训练,增强模型对遮挡和小目标的鲁棒性,但概率调低到 0.8 是为了避免过于频繁的拼接让小目标过度失真。

5. 避坑与常见问题排查:从环境配置到部署的 5 个实战踩坑记录

5.1 环境配置翻车:conda 安装后 torch 版本与 CUDA 不匹配

现象:严格按照网上的教程执行conda install pytorch torchvision torchaudio cudatoolkit=11.3 -c pytorch后,运行train.py报错,提示AssertionError: Torch not compiled with CUDA enabled。

原因:conda 的默认 channel 有时候会解析到 CPU 版本的 PyTorch(Linux 上包名相同但 build 标志不同),尤其是在没有指定pytorch-cuda的情况下。检查方式是打开 Python 终端执行import torch; print(torch.cuda.is_available()),返回 False 就说明装成了 CPU 版。

解决:不要用 conda 默认源,直接用 PyTorch 官方提供的安装命令,它会根据你的 CUDA 版本自动匹配:pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118。装完后重新验证torch.cuda.is_available(),同时检查torch.version.cuda是否与系统的驱动兼容。血泪经验:先跑通一个最简单的torch.zeros(1).cuda()再往下走,别浪费一下午在数据准备上。

5.2 训练时报错:图片与标签数量不匹配

现象:启动训练后,train.py很快报AssertionError: train: No labels in ...或者提示found X images and Y labels,两个数字相差悬殊。

原因:图片和转换后的 TXT 标签没有放在 YOLOv5 期望的目录里。YOLOv5 的标签目录是labels文件夹,与图片目录平行,而且标签文件名必须与图片名完全一致(后缀不同)。还有一种可能是转换脚本里类别过滤逻辑写错了,把全部目标都 skip 掉了,生成了空标签文件。

解决:按第 3.1 节的转换脚本重新生成标签,确保labels目录和images目录在同一级。然后跑一次数据校验:统计 labels 目录下非空 TXT 的数量,应该和 images 目录下图片数量一致。另一个常见做法是检查生成的 TXT 文件第一行内容,确认是0 0.5 0.5 0.2 0.3这样的格式,而不是空文件。

5.3 训练很快但 mAP 极低:疑似标签类别顺序错乱

现象:训练 50 个 epoch 后,mAP@0.5 只有 0.2 左右,明显低于预期。终端里打印的类别概率分布很混乱。

原因:这是本节前面提到的class_names顺序问题。转换脚本里 class id 是按["bicycle", "electric_bicycle"]分配的,但训练用的 yaml 文件里 names 顺序不同,导致模型学到的是「bicycle 的特征对应 class_id 1」这类错误映射。因为 YOLOv5 的训练过程中把类别标签当作纯数值,它不会去校验类别名称是否匹配。

解决:打开转换脚本里定义 class_names 的位置,把它和 yaml 文件里的 names 列表逐项对齐。最好的做法是把两份文件都改成从同一个classes.txt读取,保证单一数据源。用脚本动态生成 yaml 的 names 字段,而不是手动敲。

5.4 部署时推理结果全画在人身上:类别语义混淆

现象:训练好的模型部署到现场后,行人从自行车旁边经过时,模型把行人识别成自行车并触发误报。

原因:训练数据里自行车/电动车都是在静态场景下拍摄的,且行人与自行车同时出现的情况很少。模型真正学到的不是「自行车」这个抽象概念,而是「画面中静止的路边物体」这个特征模式。行人直立行走的姿态和站立状态下的自行车在轮廓上相似,导致误检。

解决:数据层面,补充带行人和自行车同时入镜的负样本,并把这些框标注成person类别,让模型学会区分。策略层面,在应用端加一个逻辑:目标框的中心点位如果在过去 N 帧内持续变化超过阈值,判定为移动目标,过滤掉非静止目标的报警。这两者结合才能把误报率压到可接受范围。

5.5 树莓派 5 上部署推理慢:没有用对模型导出方式

现象:在树莓派 5 上用python detect.py --weights best.pt跑推理,只有 2~3 FPS,完全达不到实时监控的要求。

原因:PyTorch 原生推理在 ARM 设备上没有做算子优化,还占用了大量内存。直接加载.pt文件推理是性能最差的方式。

解决:先用export.py导出成 ONNX 格式,再用 ONNX Runtime 推理。实测在树莓派 5 上能把 FPS 提升到 8~10,如果能用 NCNN 或 RKNN 的话还能进一步优化。但对多数场景来说,更现实的方案是把推理服务推到边缘盒子(Jetson 或 x86 主机)上,树莓派只负责图像采集和结果回传。从那以后我给客户做方案时,会先确认部署机的 CPU 算力再谈模型选型,不在树莓派上硬扛 PyTorch 推理。

6. 模型推理与现场验收:置信度阈值、误报率和部署脚本的最后一公里

6.1 推理脚本的工程化写法:从 detect.py 到自己的服务

YOLOv5 自带的detect.py适合做效果验证,但接进实际业务系统时不推荐直接用,因为它的输出是图片/视频,没有结构化结果。在实际项目里我习惯写一个最小的推理封装:

import torch import cv2 import numpy as np # 加载模型 model = torch.hub.load("yolov5", "custom", path="best.pt", source="local", force_reload=True) model.conf = 0.35 # 置信度阈值,现场场景建议 0.3~0.5 model.iou = 0.45 # NMS IoU 阈值 model.classes = [0, 1] # 只保留自行车和电动车类别 cap = cv2.VideoCapture("rtsp://your-camera-stream") while True: ret, frame = cap.read() if not ret: break results = model(frame, size=1280) # 推理分辨率升到 1280 dets = results.xyxy[0].cpu().numpy() # 每行: x1 y1 x2 y2 conf cls for det in dets: x1, y1, x2, y2, conf, cls = det if conf < 0.35: continue # 计算目标宽度和画面占比 w = x2 - x1 h = y2 - y1 area_ratio = (w * h) / (frame.shape[0] * frame.shape[1]) if area_ratio > 0.3: # 目标过大,可能是行人近距离路过,跳过 continue print(f"detected class {int(cls)} conf={conf:.2f} box=({int(x1)},{int(y1)},{int(x2)},{int(y2)})") cv2.imshow("preview", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break

这段代码里有几个参数值得讨论。model.conf = 0.35是置信度阈值,它和误报率直接相关。阈值调高到 0.5,误报减少但召回率下降;调低到 0.2,能检测出更多远距离小目标,但行人和静态物体的误检也会大幅增加。对于违停管理场景,我倾向 0.35~0.4 之间做平衡。size=1280是用高分辨率做推理的关键,训练虽然用 640,但推理时用 1280 能显著改善小目标的检出率,这就是前面提到的分辨率后处理技巧。

area_ratio > 0.3这个过滤条件是我在实际项目里加的:监控画面中,正常停放的自行车不会占据超过 30% 的画面面积,如果目标框过大,很可能是行人近距离经过被误检成自行车。它是应用层的规则过滤,不依赖模型能力。

6.2 现场验收:用视频段回放代替单帧抽检

模型训练完,不能只看几张测试图的检测效果就下结论。我习惯的验收方式是录一段 10 分钟的现场视频,包含白天、傍晚、夜间三个时段,把视频段丢给推理脚本,统计三个时段各自的检测帧率、误报率、漏报率。一套可用的验收标准是:误报率低于 5%(每小时不超过 5 次误报),漏报率低于 10%(车停在那里但没被检测出来的情况不超过十分之一),单帧处理速度满足现场监控帧率要求。

这个验收过程往往会暴露训练数据没有覆盖的问题:比如夜间逆光时自行车轮廓不完整,雨天下车体反光导致检测框抖动。这些都要回到数据标注环节去补充样本,然后重新训练。真正做工程的人都明白,目标检测模型从来不是一次训练就能交付的,它是数据、模型、阈值三个环节的迭代闭环。从那以后我给任何客户交模型,都强制要求先跑满一周的现场视频回放,把误报、漏报统计清楚再谈验收签字,这条流程也分享给所有做类似项目的同行,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询