☰
共享单车检测实战:VOC格式转YOLO目标检测训练与避坑指南
2026/9/26 2:45:05 网站建设 项目流程

简介:YOLO目标检测-共享单车检测数据集面向计算机视觉学习者与开发者,可用于YOLO系列模型的共享单车识别、定位与计数,服务于城市交通监管及共享单车调度场景。压缩包共272个文件,包含136张JPG图片与136个VOC格式的XML标注文件,标注了边界框坐标和类别信息,包体约90.08MB,规模适中,方便快速试验。目前已有107人学习使用,适合作为目标检测实战训练与效果对比的基准数据。图片采集自不同城市环境,覆盖多种光照、视角和背景,共享单车的颜色、品牌及使用状态也较为多样,有助于提高模型泛化能力。基于该数据集可完成数据加载、模型训练、参数优化和效果评估等流程,训练所得模型可部署于固定或车载摄像头,支撑共享单车实时监测与高效调度。

1. 共享单车检测数据集:为什么说它是 YOLO 目标检测里最典型的单类落地项目

打开 .rar 之前,先回答一个问题:共享单车检测在 YOLO 目标检测里属于什么难度档位?答案是入门偏上一点点。类别只有一个,不像城市道路检测那样需要同时区分行人、汽车、卡车;但目标角度变化大,俯拍、侧拍、斜拍都有,车辆堆叠遮挡严重,这又比纯单类物体检测难不少。这份数据集的价值在于把最麻烦的数据准备环节压缩成两件事:给你图片和 VOC 格式标签,然后由你把标签转成 YOLO 能吃的格式并跑通训练闭环。VOC 的 XML 标注是很多老项目沉淀下来的标准格式,而 YOLO 训练读的是归一化 txt,这中间那道转换工序,决定了后面 pytorch 训练到底是顺风顺水还是连环翻车。适合谁看,就是准备做乱停放检测、特定车辆识别、或第一次拿真实标注数据跑通 yolov5 / yolov8 训练流程的开发者。

2. 解开 .rar 先看目录:VOC 标签与 YOLO 训练之间的那道转换工序

拿到压缩包第一件事不是解压就训练,而是先把目录结构看明白。共享单车检测这类单类项目,目录一般长这样:images 目录放原图,annotations 目录放同名 XML,XML 就是 VOC 格式标签。YOLO 训练时读取的是一个 txt 标签加一张图片的配对关系,所以中间隔着一道转换工序。这道工序本身不难,但百分之八十的问题都发生在转换前后的数据检查上,而不是训练命令写错。

2.1 共享单车检测的标注特点:单类目标为什么仍然要小心标注

共享单车在检测任务里属于中等尺寸目标,比人脸大,比卡车小,通常占图片面积的 2% 到 15%。正因为类别单一,很多人容易忽视一个事实:单类检测的难度集中在形态变化和遮挡上。

同一辆共享单车,侧视图是一横条,俯视图是一个接近方形的轮廓,斜 45 度拍的时候车把、车座、车轮互相重叠。把这些图合在一起训练,模型实际上学到的是“像共享单车的几何结构”这个抽象特征,而不是某个固定宽高比的模板。所以 VOC 标签里的 bndbox 是否紧贴车体,直接决定了模型学到的特征里有多少背景噪声。框大了,把路面、绿化带、其他车辆都包进来,YOLO 的损失函数会努力去拟合一个不干净的边界;框小了,车把或后轮被切掉,模型学到的特征不完整。

从算法角度看,YOLO 把图像划分成网格,每个网格负责预测目标中心落在自己格子里的框。它回归的是中心点坐标和宽高,不是像素分割。这意味着标签里的坐标精度会被模型直接学习并放大。标签差几个像素,最终预测框也会差几个像素,放在乱停放检测场景里可能无所谓,但如果后续要做停车区域判断,框偏了就容易造成误判。

2.2 VOC 格式的 XML 字段:bndbox 四个像素坐标是一切转换的基准

VOC 格式来自 Pascal VOC 数据集,后来被大量标注工具沿用下来。打开一个 XML 文件,内容通常长这样:

<annotation> <folder>images</folder> <filename>shared_bike_001.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>bicycle</name> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>356</xmin> <ymin>189</ymin> <xmax>674</xmax> <ymax>511</ymax> </bndbox> </object> </annotation>

重点看三块。第一,size 字段里的 width 和 height,这是图片的真实尺寸,后面做归一化全靠它。第二,object 里的 name,这就是类别名,不同标注员可能写成 bicycle、SharedBike、共享单车,转换前必须统一。第三,bndbox 里的 xmin、ymin、xmax、ymax,这四个值是像素坐标,单位是像素,不是归一化数值。

YOLO 的标签格式则完全不同:每一行是一类目标,依次是类别 id、归一化后的中心点 x、中心点 y、归一化宽度 w、归一化高度 h。比如0 0.402344 0.486111 0.248438 0.447222。这里的 0.402344 是把像素坐标除以图片宽度得到的,0.486111 是除以高度得到的。二者之间就是一个简单的除法关系,但无数人在这里踩坑,因为一旦图片被 resize 过而 XML 没同步更新,转换出来的标签就全部错位。

2.3 展开压缩包后先做的三件事:数量统计、缺失检查、类名统一

解压后第一件事不是直接写转换脚本,而是先统计这份共享单车数据集的实际状态。我一般会跑一个小脚本,把图片和 XML 的对应关系列出来:

import os from pathlib import Path data_root = Path("共享单车数据集") images = list(data_root.rglob("*.jpg")) + list(data_root.rglob("*.png")) xmls = list(data_root.rglob("*.xml")) img_names = {p.stem for p in images} xml_names = {p.stem for p in xmls} print("图片数量:", len(images)) print("XML 数量:", len(xmls)) print("缺少 XML 的图片:", len(img_names - xml_names))

逻辑说明:用 Path.rglob 递归找到所有 jpg、png 和 xml 文件,再按文件主名求差集。图片主名和 XML 主名一一对应,是 VOC 格式最基本的约定。如果打印出来的图片数量和 XML 数量对不上,先别急着转换,把缺失的那些图片筛出来看一眼。常见情况是爬取的图片里混入了一部分没有标注的样本,这时候要么删掉,要么补标注,放进去会让训练集出现大量“有图无标签”的负样本,干扰模型对正样本的判断。

第二步是检查类别名分布:

import xml.etree.ElementTree as ET from collections import Counter counter = Counter() for xml_path in xmls: root = ET.parse(xml_path).getroot() for obj in root.iter("object"): counter[obj.findtext("name")] += 1 print(counter)

这段代码把所有 XML 里的 object name 收集起来统计频次。如果打印结果里有多个不同的名字,比如{'bicycle': 1520, '共享单车': 340, 'SharedBike': 120},说明这批标注来自不同标注员或不同来源,转换时必须合并成一个类别。不合并的话,YOLO 会把这三种名字当成三个类别去训练,而你的数据集 YAML 里只写了 nc=1,训练时会直接报类别数不匹配,或者强行按 3 类跑出一个没法用的模型。

第三步是可视化抽查。随机挑几张图,把 XML 里的框画回原图:

import cv2 import xml.etree.ElementTree as ET for xml_path in xmls[:5]: root = ET.parse(xml_path).getroot() img_path = str(xml_path.parent / root.findtext("filename")) img = cv2.imread(img_path) for obj in root.iter("object"): box = obj.find("bndbox") x1, y1 = int(box.findtext("xmin")), int(box.findtext("ymin")) x2, y2 = int(box.findtext("xmax")), int(box.findtext("ymax")) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(f"check_{xml_path.stem}.jpg", img)

注意这里有个坑:XML 里的 filename 字段不一定对得上实际文件名。有些数据集在整理时重命名了图片,但 XML 里还留着旧文件名,直接用 root.findtext("filename") 拼接路径会读不到图。更稳妥的做法是用 XML 自己的主名去找同名图片,也就是img_path = xml_path.with_suffix(".jpg"),找不到再尝试 .png。画框结果保存成单独文件,人工翻一遍,确认框的位置是否合理,尤其是车头车尾有没有被切掉。

这三步做完,数据集的底细基本摸清了,再进入转换阶段就有了底。

3. VOC 转 YOLO 格式:最小转换脚本与三个必调参数

VOC 转 YOLO 的方法网上有一堆现成工具,但我更建议自己写一个 30 行的脚本。原因很简单:共享单车数据集这种单类小项目,现成工具往往带了多余的处理逻辑,比如自动划分训练集、自动增强、自动过滤空标注,出了问题反而不好排查。自己写脚本,每一步都看得见。

3.1 归一化坐标的计算方式与转换脚本

核心转换函数不复杂,就是把 bndbox 的四个像素值转成归一化的中心点和宽高。下面这个脚本是通用的,也适合其他 VOC 格式数据集:

import xml.etree.ElementTree as ET from pathlib import Path class_mapping = { "bicycle": 0, "shared_bike": 0, "共享单车": 0, "SharedBike": 0, } def voc_to_yolo(xml_path, out_path, img_width, img_height): root = ET.parse(xml_path).getroot() lines = [] for obj in root.iter("object"): name = obj.findtext("name") if name not in class_mapping: continue class_id = class_mapping[name] box = obj.find("bndbox") x1 = float(box.findtext("xmin")) y1 = float(box.findtext("ymin")) x2 = float(box.findtext("xmax")) y2 = float(box.findtext("ymax")) x_center = ((x1 + x2) / 2) / img_width y_center = ((y1 + y2) / 2) / img_height w = (x2 - x1) / img_width h = (y2 - y1) / img_height # 防止标注越界导致训练报错,把数值夹到 0~1 之间 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines))

逻辑说明:先从 XML 里找到所有 object,读取 name 和 bndbox。class_mapping 字典的作用是把多种写法统一映射到类别 0。中心点坐标用(x1 + x2) / 2求得像素中心,再分别除以图片宽和高完成归一化。宽和高直接做差后除以图片尺寸。最后的 clamp 操作是必须的,因为部分标注框会超出图片边界,如果不处理,YOLO 训练时可能因为坐标超出 0~1 范围而报错或产生 nan loss。

调用时注意,img_width 和 img_height 必须读 XML 里 size 字段的值,不能自己去读图片,因为 XML 里的标注坐标就是依据这张图当时的分辨率标出来的。如果 XML 写的是 1280x720,而实际图片被压缩成 800x450,以实际图片宽高做归一化,所有标签都会偏。

3.2 三个必调参数:类名映射表、数据集划分、路径前缀

第一个必调参数是类名映射表,也就是 class_mapping。不同来源的共享单车数据集,标注名字五花八门,不统一的必须在这里合并。合并完之后可以用一个小循环把整个 annotations 目录转换掉:

label_root = Path("labels") label_root.mkdir(exist_ok=True) for xml_path in xmls: root = ET.parse(xml_path).getroot() size = root.find("size") img_w = int(size.findtext("width")) img_h = int(size.findtext("height")) out_path = label_root / f"{xml_path.stem}.txt" voc_to_yolo(xml_path, out_path, img_w, img_h)

这里的参数要点是输出文件主名与 XML 主名保持一致,这样每个 txt 都能通过主名找到对应的图片。如果输出名和原图对不上,后面训练时图片路径和标签路径匹配不上,会直接报错。

第二个必调参数是训练集和验证集的划分比例。共享单车数据集如果图片数量在几千张级别,8:1:1 或 9:1 都合理。我的做法是固定随机种子,避免每次跑出来划分结果不同:

import random from pathlib import Path random.seed(42) image_files = list(Path("images").glob("*.jpg")) + list(Path("images").glob("*.png")) random.shuffle(image_files) n = len(image_files) train_ratio, val_ratio = 0.8, 0.1 train_files = image_files[: int(n * train_ratio)] val_files = image_files[int(n * train_ratio): int(n * (train_ratio + val_ratio))] def write_list(file_list, list_path): with open(list_path, "w", encoding="utf-8") as f: for p in file_list: f.write(str(p.resolve()) + "\n") write_list(train_files, "train.txt") write_list(val_files, "val.txt")

random.seed(42) 保证每次运行划分结果一致。这里有一个容易忽略的点:如果这份共享单车数据集的图片来自多个不同的拍摄地点,按文件列表随机划分可能把同一地点的图片同时分进训练集和验证集,导致验证指标虚高。更严格的做法是按目录或按拍摄批次划分,先把同一来源的图片分到同一组,再从中切分。对于乱停放检测这种场景,同一街道同一角度拍的照片高度相似,同源数据跨训练验证集,mAP 会高得离谱,但换到新场景立刻失灵。

第三个必调参数是 train.txt 和 val.txt 里的路径。YOLO 训练时有两种方式组织数据:一种是直接用目录结构,train 和 val 各自建 images 和 labels 子目录;另一种是提供 train.txt 和 val.txt,每行写图片绝对路径,训练时自动找同主名的 txt 标签。我推荐后者,因为共享单车数据集可能来自多个压缩包,合并后目录结构不一定规整。路径写成绝对路径,训练时少一层纠结。

3.3 转换后的自查:画框验证与类别统计

转换脚本跑完不代表转换成功。检查分三层:数量层、格式层、可视化层。

数量层检查 labels 目录下的 txt 数量是否与 XML 数量一致,有没有输出空文件。空 txt 文件说明 XML 里没有有效 object,也许是被 class_mapping 过滤掉了,也许是原有标注就是空的。

格式层检查随便head几个 txt:

head -3 labels/shared_bike_001.txt

正常输出应该是五行中的前几行,每行由 5 个数字组成,类别 id 是 0,后面四位小数在 0 到 1 之间。如果出现负数或大于 1 的值,说明转换前的 clamp 没加上。

可视化层把 YOLO txt 画回图片:

import cv2 from pathlib import Path for txt_path in Path("labels").glob("*.txt"): img_path = txt_path.with_suffix(".jpg") if not img_path.exists(): img_path = txt_path.with_suffix(".png") if not img_path.exists(): continue img = cv2.imread(str(img_path)) for line in txt_path.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: continue cls_id, xc, yc, w, h = [float(v) for v in parts] x1 = int((xc - w / 2) * img.shape[1]) y1 = int((yc - h / 2) * img.shape[0]) x2 = int((xc + w / 2) * img.shape[1]) y2 = int((yc + h / 2) * img.shape[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("verify_" + txt_path.stem + ".jpg", img)

这个脚本是转换过程的逆运算:把归一化的中心点和宽高乘回图片尺寸,得到像素框画出来。和 VOC 阶段画框不同的是,这一步验证的是 YOLO 标签能否被正确解析。建议抽查 30 到 50 张,重点关注三类图:车辆密集的、严重遮挡的、光照很暗的。这些图最容易暴露标签错位和漏标问题。

做完自查,数据端就算准备好了。

4. 配置 YOLO 环境并训练共享单车检测:yolov5 与 yolov8 都适用的最小闭环

数据准备好之后,进入训练环节。现在的 YOLO 生态里,yolov8 是主力,yolov5 还有大量老项目在用。两者在数据组织方式上一致,都是图片加 txt 标签,只是环境配置和命令略有差异。下面按 yolov8 为主讲,顺带给出 yolov5 的对应命令。

4.1 anaconda 环境配置与预训练权重准备

训练 YOLO 系列模型,最省心的方式是建一个独立的 conda 环境,避免和系统 Python 或其他项目的 torch 版本冲突。命令如下:

conda create -n yolo python=3.10 -y conda activate yolo pip install ultralytics

参数说明:Python 3.10 是目前兼容性比较好的版本,torch 和 ultralytics 都支持。pip install ultralytics 会同时装上 torch、torchvision、opencv、pandas 等依赖。如果机器上已经有其他项目的 torch,不要直接在这个环境里复用,版本不一致会出现算子不匹配之类的诡异报错。

安装完成后确认 GPU 可用:

python -c "import torch; print(torch.cuda.is_available())"

输出 True 说明能用 GPU。只有 CPU 也能训练,但速度会慢很多。建议把数据集 YAML 里的路径改成绝对路径,然后把 yolov8n.pt 预训练权重提前放到当前目录。没有也没关系,ultralytics 在训练时会自动下载,前提是运行环境能访问外网。预训练权重的作用是让模型从 COCO 数据集学到的特征作为初始值,对于共享单车这种单类检测任务,可以显著减少训练轮数和过拟合风险。

4.2 写数据集 YAML 并启动训练

数据集 YAML 是 YOLO 训练读取数据配置的唯一入口。创建 shared_bike.yaml:

# 共享单车检测数据集配置 path: /home/user/shared_bike_dataset # 改成数据集解压后的绝对路径 train: train.txt val: val.txt nc: 1 names: ["bicycle"]

train 和 val 这里写的是相对 path 的文件名,也可以直接写绝对路径。nc 是类别数量,单类就是 1。names 列表里的名字要和 labels txt 里的类别 id 一一对应,只有一个类别时就是 0 对应 bicycle。

启动训练:

yolo detect train model=yolov8n.pt data=shared_bike.yaml epochs=100 imgsz=640 batch=16 device=0

参数说明:model 指定预训练权重,data 指定数据集配置,epochs 是训练轮数,imgsz 是输入图片尺寸,batch 是批量大小,device=0 表示用第一块 GPU。共享单车在常见图片尺寸下占图比例不算小,640 够用。如果摄像头画面里共享单车离得远、像素占得少,可以提到 960,代价是训练时间变长。

yolov5 的对应命令写法不同,但逻辑一样:

python train.py --data shared_bike.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100

训练开始后,终端会打印每一轮的 box_loss、cls_loss、dfl_loss、recall、mAP50 等指标。第一次跑建议把 epochs 设到 100,然后开早停。数据量小或者单类目标,通常到 60 到 80 轮就收敛了。

另一个影响结果的关键参数是 batch。显存够大就往上加,训练速度更快,BN 层的统计也更准。共享单车图片分辨率普遍在 720p 以上,batch=16 需要 8GB 显存左右,如果报 CUDA out of memory,降到 8:

yolo detect train model=yolov8n.pt data=shared_bike.yaml epochs=100 imgsz=640 batch=8 device=0

4.3 训练过程怎么看:损失函数下降与 mAP 指标

训练跑起来之后,不要只盯着终端滚动看。ultralytics 会把训练过程写入 runs/detect/train 目录下的 results.csv,用 pandas 读出来更直观:

import pandas as pd df = pd.read_csv("runs/detect/train/results.csv") print(df.columns.tolist()) print(df[["train/box_loss", "train/cls_loss", "train/dfl_loss", "metrics/mAP50(B)"]].tail(5))

yolov8 的损失函数分成三块。box_loss 负责回归框的位置和宽高,如果它下降缓慢,说明标签坐标噪声大,或者学习率不合适。cls_loss 是分类损失,单类模型也要关注,因为模型本质上在区分“共享单车”和“背景”。dfl_loss 是分布焦点损失,控制框的边界精确度。三项都在下降但不一定同步,属正常现象。

mAP50 是 IoU 阈值 0.5 下的平均精度,单类项目主要看它。mAP50 能达到 0.8 以上,说明模型已经能稳定检出大部分目标。mAP50-95 会更严格,它考察不同 IoU 阈值下的综合表现,这个值偏低不用太慌,可能是标注框边界画得不紧,也可能目标本身遮挡太严重,模型只能给出大概范围。

训练完成后,权重文件保存在 runs/detect/train/weights/ 下,best.pt 是验证集上表现最好的权重,last.pt 是最后一轮的权重。部署时用 best.pt,不要用 last.pt,除非你确认最后几轮没有过拟合。

5. 避坑:共享单车数据集训练全流程的五个高频翻车点

整理五个我在这类单类目标检测数据集上反复遇到的坑。每一条都是真实出现过的现象、原因、解决方式,照单排查能省不少时间。

5.1 现象:检测框整体偏移,框完全不在共享单车上

原因:图片在整理阶段被统一 resize 过,但 XML 里的 bndbox 坐标没有同步缩放。比如原图是 1920x1080,标注坐标也是按这个尺寸标定的,但数据集发布时把图片压成了 960x540,XML 里的 size 字段却还写着 1920x1080。转换脚本读 size 做归一化后,标签仍然和原图坐标匹配,但实际图片已经缩水,训练时模型看到的框和内容对不上。

解决:转换前用 Python 读实际图片的宽高,和 XML size 字段对比。不一致时,以实际图片为准重新计算坐标。如果图片已经丢失原始数据,就只能根据缩放比例去换算标注坐标,比如 x 坐标乘 0.5、y 坐标乘 0.5。

5.2 现象:loss 降得很快,但 mAP 上不去

原因:标注框过大,把大量背景包了进来。共享单车数据集里常见的标注习惯是把车筐到后轮整个框住,这没问题,但有些框连停车区域白线、旁边的树影都包进去了。YOLO 回归的是整个框的内容,背景占比越高,模型特征越模糊,检测框就会忽大忽小。

解决:回到可视化抽查环节,把训练集里预测置信度低于 0.5 的图片集中看一遍,框明显偏大的重新标注。如果数据集量太大没法重标,可以写脚本把每个框向内收缩 5% 再训练,相当于强制让模型关注更核心的目标区域。我试过对共享单车这类目标,收缩 5% 到 8% 边界后 mAP 能提升 3 个点左右。

5.3 现象:训练时提示类别数不匹配,或者训练完成后预测出多个类别名

原因:数据集 YAML 里 nc=1,但 labels 目录下生成的 txt 里出现了多个不同的类别 id。这种情况通常不是转换脚本写错,而是类名映射不完全。比如原始 XML 里有 bicycle、SharedBike、共享单车、共享电单车四种名字,class_mapping 只映射了前三种,共享电单车被过滤掉了,但如果某个 XML 里共享电单车的名字被写成了 “电动车”,它不在映射表中,会被直接跳过,于是图片和标签错位,训练数据里出现“有图无标签”的文件。

解决:转换脚本不要用 continue 跳过未知类名,而是把所有出现过的 name 打出来,确认每一类都能对应到正确类别。共享单车数据集最怕混入共享电单车,两者外形相似但属于不同目标,如果业务上需要把电单车也检出来,就应该在映射表里给它单独的类别 id,而不是过滤掉。

5.4 现象:训练中途报错,坐标值超出图片范围,或者 loss 变成 nan

原因:XML 里有越界标注,bndbox 的坐标超出了图片宽高。这种标签通常来自半自动标注工具,少量框没人工修正,比如 xmax 大于图片 width,ymin 为负数。转换后归一化数值小于 0 或大于 1,训练进程计算 anchor 匹配时就会出错。

解决:转换脚本里的 clamp 已经能把数值限制在 0~1 之间,但更严谨的做法是把越界严重的样本单独挑出来。如果 xmax 只比宽度大十几像素,clamp 修正后基本不影响结果;如果整个框有一半在图片外,说明标注质量有问题,直接删掉这张图比强行修正更合理。加一个过滤条件是基本操作:

box_width = x2 - x1 box_height = y2 - y1 if box_width < 10 or box_height < 10: continue

过滤掉过小的框,能避免训练时数值不稳定。10 像素这个阈值按图片分辨率调,720p 以下取 5 也行。

5.5 现象:验证集 mAP 接近 0.98,但换到真实场景视频里漏检严重

原因:数据集划分时没有按拍摄来源分组。共享单车图片往往按街道、小区、停车场分批次采集,同一批次里同一角度连续拍摄的图片高度相似。如果随机划分,训练集和验证集里会出现大量近似重复的图片,模型其实记住了这些特定场景,而不是学会检测共享单车本身。

解决:回去检查图片的文件名或目录结构。一般情况下数据集发布时会按批次分目录,划分 train 和 val 时先按目录分组,再把整个目录分到一边,避免同场景图片同时出现在两边。即使目录信息不完整,也可以用图片文件名前缀或者拍摄时间字段做分组。验证时再用一段新场景视频测试,mAP 虚高的假象会立刻暴露。

6. 验证共享单车检测结果的三个技巧:置信度门限与坏例回填

训练跑完只完成一半,真正决定能不能用的是验证环节。我通常不满足于测试集上的 mAP,而是拿三段不同时间、不同地点、不同光线条件的视频跑一遍检测。命令很简单:

yolo detect predict model=runs/detect/train/weights/best.pt source=test_video.mp4 conf=0.25

source 可以是图片、视频或目录,conf 是置信度门限。这里就牵出第一个技巧:门限不是拍脑袋定的。共享单车检测的误检来源主要是电动车、摩托车和行人。电动车和共享单车同为两轮交通工具,在侧面轮廓和车轮特征上极其相似,conf 太低会把电动车当共享单车检出来。我的做法是先用 conf=0.05 跑一遍完整视频,导出所有检测框和目标数量,然后逐步提高到 0.1、0.25、0.5,观察检出的目标数量何时出现断崖式下降。如果某个门限下目标数量骤减,说明大量真实目标被卡掉了,门限要回退一档。

第二个技巧是专门挑坏例回填训练集。把所有置信度在 0.3 到 0.6 之间的框抽出来,人工看一眼。那些明明是正确的共享单车但模型不敢置信的图,copy 到训练集中重新训练一轮,比盲目增加训练轮数效果好得多。共享单车数据集的坏例主要集中在密集堆放场景,两辆车靠在一起时模型经常只检出一辆,这类样本补充 100 到 200 张,效果立竿见影。

第三个技巧是框的范围要和业务场景对齐。如果最终目标是检测乱停放,标注时应该只框车身,不框车前的停车区域;如果最终目标是统计某个路口的单车流量,框可以稍微宽松一些,减少漏检。YOLO 学到的是框内的特征分布,框松紧直接影响后续评估。我自己做单车项目时曾为了省事让标注员把框画得随意,后来花了一周时间返工,从那以后每次转换完标签都先画几十张图肉眼确认。希望帮到你。

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

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

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

立即咨询