尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

VisDrone转YOLOv5:小目标检测的格式转换与训练避坑指南

VisDrone转YOLOv5:小目标检测的格式转换与训练避坑指南 简介一套来自VisDrone场景、面向无人机低空俯视视角的车辆与行人目标检测YOLO格式数据集适合目标检测算法研究、无人机视觉应用开发及边缘智能场景验证尤其适合需要低空俯瞰样本的训练任务。压缩包共2000个文件以1887个txt标注、112张jpg原图和1个data.yaml配置为主整体约960.43MBtxt采用YOLO格式存储目标框归一化坐标jpg为无人机实拍画面data.yaml中声明了car、person两个类别。目录已按train/valid/test划分完毕解压后可在YOLOv5、YOLOv7、YOLOv8等框架下直接指定路径启动训练免去格式转换与手动划分步骤同时保留图像与txt标签的对应关系方便逐张检查标注质量、补充目标框或进行数据增广也适合迁移学习与算法对比。已有1325人学习浏览可作为车辆与行人检测、无人机视觉等任务的基准数据集。1. 无人机俯视视角检测为什么预训练权重在VisDrone上“失灵”了一个很有意思的现场拿YOLOv5s在COCO上的预训练权重直接去推理无人机俯视画面里的车辆和行人效果往往差到让你怀疑权重是不是下错了。原因不在于YOLOv5本身而在于视角和尺度发生了剧烈变化——地面道路场景里目标占画面比例大、多为侧面视角到了VisDrone这类无人机俯视画面里一辆轿车可能只有三四十个像素宽行人和车辆大量堆叠、互相遮挡迁移学习的domain gap比想象中大得多。标题里这个数据集做的事情就是把VisDrone-DET整理成YOLOv5能直接消费的数据包让你跳过“找数据、对齐标注格式、重新做label”这些重复劳动直接进入训练和部署环节。适合谁做航道巡检、交通监控、无人机落地的算法工程师和研究生以及想在板载设备上跑通实时检测的开发者。这篇笔记就沿着“格式对齐→转换脚本→训练参数→避坑→推理进阶”这条线把我实际操作中的做法和踩过的坑一次讲清楚。2. 先算清楚格式账VisDrone的原始标注与YOLOv5之间的坐标和类别鸿沟拿到任何数据集包第一件事不是急着训练而是把标注格式一条条读明白。VisDrone和YOLOv5之间的格式差异比大多数人预想的要大尤其是类别编号和坐标系的处理错一位就全盘皆输。2.1 VisDrone原始标注长什么样一个容易读错的txtVisDrone-DET的原始标注是“图片同名txt”的方式存放每一行是一条目标记录字段顺序固定bounding box的左上角x、左上角y、框宽、框高然后是score、目标类别、截断程度和遮挡程度。里面最容易被忽略的是类别编号体系0是忽略区域1到11才是真实类别对应car、van、truck、bus、pedestrian、person、cyclist、tricycle、awning-tricycle、others以及额外类型。换句话说它的类别编号从1开始而不是大多数目标检测框架习惯的从0开始。另一个关键点是score字段。训练集里score为1表示这是一个正常参与训练的目标score为0表示该框属于“忽略区域”——这些区域不是负样本也不该参与loss计算。很多人拿到原始txt直接用pandas读进来不做过滤就转格式结果把大量score为0的忽略框当成真实目标喂进模型训练出来的模型在背景区域疯狂输出鬼影框。2.2 YOLOv5期望的标签格式归一化坐标系与类别重编号YOLOv5的标签格式是完全另一套规则每个txt文件与图片同名每一行由class、x_center、y_center、width、height五个字段组成class是整数且从0开始后面四个坐标值全部归一化到0到1之间。这里的x_center和y_center是目标中心点坐标width和height是框的宽高全部除以图片宽高做归一化。所以转换要做的事情很清晰读原始行→过滤score为0和ignored区域→类别重编号→把“左上角宽高”换算成“中心点宽高”→除以图片宽高完成归一化→写出新txt。整个过程不复杂但细节容易出错。类别重编号时我习惯用一张显式的映射表而不是在代码里做算术偏移因为VisDrone的类别号有跳号比如编号10在有些不完全标注里是空的一张dict比加减法可靠得多。VisDrone原始类别号含义转YOLOv5后类别号0ignored regions丢弃1car02van13truck24bus35pedestrian46person57cyclist68tricycle79awning-tricycle811others9注意编号10在官方标注里对应的是“未知类别”多数整理版本选择直接丢弃如果想保留我会把它与others合并到编号9这样训练时类别不至于过于稀疏。这是个人选择不影响整体流程。2.3 写一个能直接用的visdrone.yaml数据集的目录结构和配置文件是训练前的最后一道准备。YOLOv5要求yaml文件里给出train和val的图片路径以及类别名列表。我一般会先把数据整理成如下结构再写配置visdrone-yolo/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/对应的visdrone.yamltrain: visdrone-yolo/images/train val: visdrone-yolo/images/val test: visdrone-yolo/images/test nc: 10 names: 0: car 1: van 2: truck 3: bus 4: pedestrian 5: person 6: cyclist 7: tricycle 8: awning-tricycle 9: others这段配置里最容易出问题的是路径。YOLOv5读取yaml里的train和val路径时如果是相对路径会相对于当前工作目录解析。我习惯把yaml放在visdrone-yolo目录外一层路径写成相对路径或绝对路径都行但要保证在同一个目录下执行训练命令。另外nc的值必须和names列表长度一致多一个少一个都会在训练时报shape mismatch。关于标题里压缩包名带“-2”这个后缀我倾向于理解成整理后的第二个版本具体文件组织和是否已经划分好train/val/test以压缩包里的README为准。不管它怎么组织上面这套格式账是必须算清楚的。3. 把VisDrone标注转成YOLOv5格式转换脚本与四个边界坑格式账算清楚了接下来就是动手写转换脚本。这一节给出一个可以直接跑的Python脚本并重点讲四个我在实际转换中踩过的边界坑。每一步都给出代码和参数说明方便你对照自己的数据包调整。3.1 转换前先做两件事确认图片分辨率和统计类别分布在写转换脚本之前我一般会先跑一段快速统计确认两件事一是图片分辨率是否统一VisDrone的原始图片大约是2000x1500但有些场景可能有裁剪分辨率不统一会导致归一化时除错二是每个类别的样本数量这决定了后面训练时是否需要调整类别损失权重。统计脚本很简单用OpenCV读图取shape再用Counter统计txt里每行的类别字段。这里不贴完整代码重点说结论如果发现某些类别比如awning-tricycle样本特别少后面训练时要考虑focal loss参数或者类别权重否则模型会倾向于把这类目标预测成背景。确认完这两件事再开始转换。转换脚本的输入是VisDrone原始标注目录输出是YOLOv5的labels目录中间不修改原文件。3.2 核心转换脚本过滤、映射、归一化一步到位import cv2 from pathlib import Path SRC_ANNO Path(VisDrone2019-DET-train/annotations) SRC_IMG Path(VisDrone2019-DET-train/images) DST_LABEL Path(visdrone-yolo/labels/train) DST_IMG Path(visdrone-yolo/images/train) CAT_MAP { 1: 0, # car 2: 1, # van 3: 2, # truck 4: 3, # bus 5: 4, # pedestrian 6: 5, # person 7: 6, # cyclist 8: 7, # tricycle 9: 8, # awning-tricycle 11: 9, # others } DST_LABEL.mkdir(parentsTrue, exist_okTrue) DST_IMG.mkdir(parentsTrue, exist_okTrue)for anno_path in SRC_ANNO.glob(*.txt): img_path SRC_IMG / f{anno_path.stem}.jpg if not img_path.exists(): print(f[跳过] 找不到图片: {img_path}) continue h, w cv2.imread(str(img_path)).shape[:2] out_lines [] for line in anno_path.read_text().strip().splitlines(): parts line.strip().split(,) if len(parts) 7: continue left, top, box_w, box_h map(float, parts[:4]) score float(parts[4]) cat_id int(parts[5]) if score 0 or cat_id not in CAT_MAP: continue # 坐标越界保护VisDrone部分标注框边缘会超出图像 left min(max(left, 0), w - 1) top min(max(top, 0), h - 1) box_w min(box_w, w - left) box_h min(box_h, h - top) if box_w 1 or box_h 1: continue cx (left box_w / 2) / w cy (top box_h / 2) / h bw box_w / w bh box_h / h # 归一化后越界保护浮点误差允许回钳 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) out_lines.append(f{CAT_MAP[cat_id]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if out_lines: (DST_LABEL / f{anno_path.stem}.txt).write_text(\n.join(out_lines) \n) else: # 空txt表示该图无目标YOLOv5可正常处理 (DST_LABEL / f{anno_path.stem}.txt).write_text() # 同时把原图拷贝到训练目录避免训练时路径不一致 img_bytes img_path.read_bytes() (DST_IMG / img_path.name).write_bytes(img_bytes)这段脚本的逻辑分成四步读原始txt的每一行按逗号切分前四位是坐标第五位是score第六位是类别号filter掉score为0的忽略框用CAT_MAP重映射类别把左上角坐标换算成中心点坐标后除以图片宽高。最后写出的每行格式是“类别 中心x 中心y 宽 高”。参数说明min(max(...))是数值钳制用来防止浮点误差导致归一化坐标略小于0或大于1。box_w 1的过滤条件不是随便写的——VisDrone里存在极少量标注框宽度只有1到2像素的目标这类目标经过resize后在训练图上几乎不可见留着只会变成噪声标签。如果某些画面里这种超小目标数量特别多可以把这个阈值放宽到3但我一般不建议因为小目标检测的收益远小于噪声带来的损失。3.3 转换后必须做的校验别信转换脚本的print看统计数据转换完成后立刻训练是很多新手会做的事但我强烈建议先做两步校验。第一步是算一下转换前后的样本量是否对得上方法很简单数一下原始txt总行数再数一下转换后txt的总行数过滤比例如果超过5%说明原始标注里score为0的忽略框占比不小这是正常的如果过滤比例超过30%就要回头检查是不是把score字段和类别字段解析反了。第二步是抽样可视化用OpenCV把转换后的坐标画回原图人眼确认框的位置和类别是否匹配。这一步看起来很笨却能发现大量自动化脚本发现不了的问题包括类别映射错误、归一化后框漂移、宽高颠倒。我经历过一次把宽和高写反的情况训练出来的模型mAP掉到0.3以下最后就是靠可视化才定位到问题。4. 训练参数怎么设图像尺寸、anchor重算和超参别照抄COCO格式准备好之后训练阶段有三个参数直接决定VisDrone这类小目标数据集的最终效果输入图像尺寸、锚框是否重算、损失函数的超参数。这三样如果照搬COCO的配置结果一般不太好看。4.1 图像尺寸选640还是1024这是小目标检测的第一个分水岭VisDrone的原始图片分辨率在2000x1500左右目标尺寸普遍偏小。统计下来相当一部分车辆的标注框边长只有20到50像素在1024分辨率下还能保留30到50像素勉强够YOLOv5的检测头识别如果直接resize到640这些目标缩到20像素以内特征图上的信息几乎被压没了漏检是必然的。输入尺寸显存占用b16, s6.9m小目标召回率训练速度640约6GB低快1024约14GB中中1280约22GB高慢如果显存只够跑640我的建议是训练时开启YOLOv5自带的--multi-scale让模型在训练过程中随机接触不同尺度的输入提升小目标的泛化能力。但要注意multi-scale只对训练有帮助推理时还是要固定到一个尺度。有一个常见的误区是训练用1024推理用640因为推理要跑实时。这个做法在小目标场景下问题很大训练和推理的尺度不一致会导致精度大幅下降。要么训练和推理都用1024要么都用640不要混着来。4.2 autoanchor重算让锚框适配俯视视角的尺寸分布YOLOv5在COCO上的默认锚框是适配行人、车辆在水平视角下的常见尺寸的到了VisDrone俯视场景目标尺寸分布完全不同。YOLOv5训练时默认会跑autoanchor根据你的数据集重新计算anchor形状。但自动重算不是万能的我在实际使用中遇到过训练日志里没有“anchors updated”提示的情况说明当前anchor在新数据集上还算是可用的损失值也正常但这种“可用”往往只是不报错而已不代表效果就好。我一般的做法是在训练前手动跑一次anchor计算python tools/run.py --dataset visdrone.yaml --anchor-calc如果不想额外跑脚本直接看训练日志前几行YOLOv5会打印当前anchor与重新计算anchor的k-means聚类结果的对比。发现两者差异较大时手动把模型配置文件里的anchor替换成重算后的值再关闭autoanchor效果通常比完全交给自动重算更稳定。还有一个细节值得注意YOLOv5的anchor数量和特征图数量绑定分别是P3、P4、P5三张特征图各三个anchor。VisDrone里超小目标特别多时可以考虑把P3特征图上的anchor整体往小尺寸方向偏移。这个操作需要改模型配置不建议新手一开始就动先把图像尺寸从640提到1024收益更直接。4.3 训练命令和关键超参hyp文件里的三个调节开关训练命令本身不复杂关键是几个参数配合。python train.py \ --data visdrone.yaml \ --weights yolov5s.pt \ --img 1024 \ --batch-size 16 \ --epochs 150 \ --hyp hyp.scratch-low.yaml \ --multi-scale--weights用COCO预训练权重没问题但要知道这个预训练效果的迁移是有限的预训练权重主要帮模型先学会底层纹理特征俯视视角下的语义特征还是要靠新数据集自己学。--hyp参数值得专门说hyp.scratch-low.yaml里有三个和VisDrone最相关的开关。fl_gamma是focal loss的gamma值默认通常是0.0。VisDrone的车辆和行人存在明显的类别不均衡awning-tricycle这类类别样本少建议把fl_gamma调到0.5到1.0让模型难例挖掘更主动。cls_pw是分类损失的权重系数默认1.0样本少的类别可以单独加大这一类的loss权重但YOLOv5的hyp文件是全局生效的所以我一般用fl_gamma而不是单独调类别权重。训练过程中要看两个曲线box_loss和cls_loss。box_loss如果一开始就非常低然后长时间不降说明锚框和图像尺寸设置有问题模型在“用默认anchor硬凑标注框”这时候先停下来重新检查anchor而不是盲目加epoch。cls_loss如果震荡剧烈说明类别不均衡严重需要加大fl_gamma。4.4 训练中断与续训一个容易翻车的小细节训练到一半断了是常事YOLOv5的断点续训有个坑恢复训练时如果输入--weights runs/train/exp/weights/last.pt优化器状态会一起恢复但hyp配置文件也要和中断时保持一致否则learning rate会突变。我习惯在训练脚本里固定用一个hyp文件不要中途切换。注意VisDrone数据集的类别名称里同时有pedestrian和person很多人会想当然把它们合并成一个“行人”类。我在实际项目中试过合并效果并不理想因为VisDrone里pedestrian是站立静止的人person是姿态多变的人比如骑车、弯腰它们的表观特征差异较大分开训练能让模型对姿态更敏感。除非你的业务场景明确不区分否则建议保留原始10类的划分。5. 常见翻车点与排查我在VisDrone上踩过的五个坑这一节是全文最值得保存的部分。五个问题全部来自实际训练和部署中的现象记录按“现象→原因→解决”来写。这些坑如果不遇到看再多理论都是纸上谈兵遇到了这一节能帮你省下一整天排查时间。5.1 现象类别索引错位地面上的车全被识别成行人训练完跑验证集发现所有检测框的类别都向后错了一位car变成vanvan变成truckpedestrian变成person而且错得非常整齐。原因几乎是确定的转换脚本里用了算术偏移比如new_cls old_cls - 1没有处理VisDrone里编号0的ignored区域和编号10的空缺。旧编号1car减去1变成0YOLO的第一个类别car看起来没错但旧编号2van减1变成1在YOLOv5里1对应的是类别列表里的第二个名字如果你把最后一个类别放在了列表中更后面的类别全乱了。更隐蔽的情况是数据集中根本没有编号10的样本但你写了个特殊规则跳过了它其他人拿到代码时完全看不出这个意图。解决使用显式的类别映射dict不要做算术偏移。映射表写清楚每个原始编号对应YOLO的哪个新编号缺失的编号比如10明确标注“丢弃”或“归入others”。这是我强调用CAT_MAP字典而不是加减法的根本原因。5.2 现象loss掉得很快val指标却不升反降训练到第10个epoch训练集loss就已经降到很低看起来一切正常但验证集mAP一直不动甚至往下掉典型的过拟合前兆。原因有两个方向一是数据集划分有泄漏train和val里有重复图片。VisDrone原始数据集本身不带官方train/val划分很多整理版本是把同一条航线连续帧的图片一刀切导致train和val里有大量近乎相同的画面。二是训练集图片太少VisDrone某个序列可能只有几十张图模型把这些图背下来了。解决检查val集和train集是否有来自同一个视频序列的相邻帧如果有重新划分按“场景”为单位划分而不是按“帧”为单位。训练时也要控制epoch数VisDrone这种中规模数据集yolov5s在150个epoch后继续训练收益会明显下降我一般到120到150个epoch就停了。5.3 现象验证mAP有0.5但实际照片里小车漏检严重这是最让人头疼的情况指标看起来不错落地画面里漏检得厉害。查了几个原因后发现验证集本身也存在大量小目标漏检只是mAP被大目标的正确检测拉高了。VisDrone的评估指标是每个类别的mAP平均vehicle是大目标检测很准car是中等目标也还行但person和pedestrian这类小目标漏检严重总体mAP被大类别“平均”掉了。解决不要只看总mAP分类别看AP。YOLOv5训练结束后会输出每个类别的AP重点关注person、pedestrian、bicycle这几个目标小、形态复杂的类别。如果这些小类别AP明显低于car说明模型对小目标学习不足优先调整输入尺寸、anchor重算然后再考虑换更大模型。5.4 现象每一帧都检测出几百个框画面里全是“鬼影”模型训练收敛了指标正常但推理时画面上出现大量小方框每个框的置信度还不低一帧图检测出几百个目标。原因VisDrone标注里的ignored regionscore为0的框没有被正确过滤转换时把它们当成了正常目标模型学到了“这个区域的低置信度框也能得分”。另一个原因是NMS阈值太低默认的IoU阈值0.45在俯视密集场景下过于激进大量重叠框被保留下来。解决转换脚本里score为0的框必须丢弃这是底线。推理时如果图片尺寸用1024且目标密集建议把NMS阈值从0.45调到0.6置信度阈值从0.25调到0.45。这个方法能快速压制鬼影但治本还是要把训练数据里的ignore区域处理干净。5.5 现象显存不够切片训练后目标被切断检测框横跨两个切片想用更高的分辨率训练但单卡显存不够于是把图片切成小块分别训练和推理。切小图后检测效果反而变差经常出现一个目标被切成两半检测框横跨两个切片的边界。原因切图时没有做重叠切片。如果一个目标恰好落在切片边界上两个切片各分到一半两边的模型都看不到完整目标自然检测不出。解决推理时使用重叠切片左右各剪裁出15%到30%的重叠区两次推理结果按IoU合并。实际项目中这个方法很常见把1280分辨率的大图切成两个1024的切片配合重叠漏检率明显下降。提示切片推理不是万能的。目标特别密集时切片之间重复检测很多合并逻辑如果处理不好框的数量会失控。我项目里面对密集交通场景时优先用1024整图推理只有显存真不够才切成两块且保证重叠区不低于20%。6. 推理阶段的三个进阶操作切片、TTA与板载部署的取舍训练完只是第一步无人机场景下模型要跑在巡检软件里、地面站或者板载设备上。VisDrone数据的实际落地我一般会做三个层面的增强和优化按性价比从高到低排序。第一个是TTA测试时增强。YOLOv5的detect.py里自带--augment参数推理时对输入做左右翻转和尺度缩放再把多组结果的框合并。别小看这个开关在VisDrone这类小目标密集场景下TTA通常能带来2到4个点的mAP提升尤其在person这种形态复杂的类别上提升最明显。代价是推理时间变为原来的4倍左右。如果做离线批处理或者对实时性要求不高的场景开着TTA很划算但要做实时视频流这个方案不现实。第二个是切片推理。这个方法在第5.5节提过这里说具体配置我会把原始大图先缩放一次到目标尺寸再按有重叠的方式切成左右两块分别推理后用NMS合并。合并时IoU阈值设在0.5置信度阈值比整图略高用来抑制重复框。切片推理能解决显存瓶颈但合并逻辑要做好否则在密集场景下框的数量会爆炸。第三个是板载部署。如果最终目标是跑在树莓派5这类边缘设备上第一步是把PyTorch模型导出成TensorRT或ONNX。树莓派5上我实际跑过自己的yolov5模型导出时最有价值的是FP16量化速度提升明显且精度损失很小。如果想进一步压榨性能可以尝试INT8量化但VisDrone的小目标本身就脆弱INT8量化后漏检率会上升需要用小目标较多的验证集单独评估不要只看整体mAP。最后一个习惯是我踩坑踩出来的每次推理前先拿几张训练时从未见过的真机画面跑一遍看框是否稳定。VisDrone训练出来的模型有一个共性问题——对俯视角度敏感训练集里多是垂直视角如果实际飞行的云台角度偏斜超过30度模型的表现会大幅缩水。我现在的固定做法是在模型交付前专门拍摄不同角度、不同高度的画面做一次红队测试把所有漏检和误检的案例收集起来再决定是重新采集数据还是用数据增强去补齐。这套流程走下来VisDrone数据集的正确打开方式基本就清晰了格式转换别抄捷径训练参数按小目标场景调避坑靠可视化验证落地前做足红队测试。希望帮到你。本文还有配套的精品资源点击获取
返回列表