☰
VisDrone2019 DET与VID标注转COCO格式实战指南
2026/10/13 11:20:28 网站建设 项目流程

简介:这份资源面向计算机视觉初学者与需要处理无人机视角数据的研究者,解决VisDrone2019数据集在目标检测与视频跟踪任务中格式不兼容的问题。它提供将DET(目标检测)与VID(视频目标检测与跟踪)两类标注转换为COCO格式的完整方案,便于直接接入主流检测与跟踪框架进行训练和评测。压缩包共3个文件,包含2个Python脚本与1份Markdown说明文档,整体仅约3KB,其中脚本承担核心转换逻辑,说明文档则交代了需要修改的路径位置,只需按提示调整路径即可运行,对新手十分友好。目前已有2210人学习下载,说明该方案在同类数据预处理需求中具备一定参考价值。读者可借此快速完成VisDrone到COCO的标注映射,省去自行解析原始标注的时间,同时理解DET与VID两种标注结构的差异,为后续模型训练、视频目标跟踪实验及数据管线搭建提供可直接复用的工具脚本与排错思路。

1. 无人机视角下的数据格式困局:为什么 VisDrone 的 DET 和 VID 总让人卡在第一步

做无人机视觉项目的人多半遇到过这个场景:好不容易拿到 VisDrone2019 数据集,兴冲冲想用主流检测框架跑一版 baseline,结果发现标注文件是 txt 格式,一行行逗号分隔,跟 COCO 的 JSON 结构完全对不上。更麻烦的是,VisDrone 同时提供了 DET(静态图像检测)和 VID(视频序列跟踪)两套标注,两者的目录组织、标注字段含义、甚至坐标定义都有细微差别。很多人在这里就翻车了——要么直接放弃换数据集,要么手写脚本转了一半发现类别映射错了,训练时 loss 不降反升,排查半天才发现是 category_id 从 0 开始还是从 1 开始的问题。

这篇笔记要解决的就是这个卡点:把 VisDrone2019 的 DET 和 VID 两种标注格式,完整、正确地转换成 COCO 格式。COCO 之所以值得转,是因为它几乎是目标检测和跟踪领域的事实标准,主流框架的 dataloader 默认就吃这套结构,转完之后你能直接复用大量现成的训练配置、评估脚本和可视化工具。适合正在做无人机检测、多目标跟踪,或者想拿 VisDrone 做预训练再迁移到其他场景的从业者。下面从格式差异讲起,一步步落到可复现的转换脚本和参数说明。

2. 先搞清楚 VisDrone 的 DET 和 VID 到底差在哪

动手写转换脚本之前,必须先把两套标注的字段含义和目录结构吃透。很多人转换失败不是因为代码写得差,而是因为对源格式的理解有偏差,尤其是 VID 的帧间关联信息,如果直接按 DET 的方式处理,跟踪任务就没法做了。

2.1 DET 标注的字段定义与目录组织

VisDrone2019-DET 的目录结构通常是这样的:VisDrone2019-DET-train/下面有images/和annotations/两个子目录,图像和标注文件一一对应,命名规则是0000001.jpg对应0000001.txt。标注文件每一行代表一个目标实例,字段用逗号分隔,依次是:

bbox_left, bbox_top, bbox_width, bbox_height, score, object_category, truncation, occlusion

这里有几个容易踩坑的点。第一,score字段在检测任务里通常表示标注置信度,值为 1 表示有效标注,0 表示忽略区域,转换时一般只保留 score=1 的样本。第二,object_category的取值是 0 到 11,其中 0 是忽略类,1 到 10 是实际类别,11 是其他。第三,truncation和occlusion是 0/1 标志位,COCO 格式里没有直接对应字段,但可以塞进attributes或直接丢弃。坐标是左上角加宽高的绝对像素值,这点和 COCO 的[x, y, width, height]一致,不需要额外转换。

2.2 VID 标注的帧间关联与序列结构

VisDrone2019-VID 面向视频序列,目录组织是sequences/下每个视频一个文件夹,里面是连续帧图像,标注文件在annotations/下同名 txt。字段比 DET 多两个:

frame_index, target_id, bbox_left, bbox_top, bbox_width, bbox_height, score, object_category, truncation, occlusion

frame_index表示当前帧号,target_id是跨帧跟踪的目标 ID。这两个字段是 VID 的核心价值——它们让同一目标在不同帧之间建立了关联。转换成 COCO 时,如果只做检测,可以忽略target_id,按帧拆成独立图像;如果要做跟踪,就需要保留target_id,并在 COCO 的annotations里额外记录,或者转成 COCO 的 video 扩展格式。常见做法是:检测任务按帧展开,跟踪任务保留序列信息,用video_id和track_id字段扩展。

2.3 为什么 COCO 是下游任务的最优中转格式

COCO 的 JSON 结构分三大块:images记录图像元信息(id、文件名、宽高),annotations记录每个实例(id、image_id、category_id、bbox、area、iscrowd),categories记录类别映射。这种结构的优势在于:一是自描述,所有信息在一个文件里,不用再去解析目录;二是生态成熟,几乎每个框架都有现成的 COCO 解析器;三是扩展性好,加字段不影响原有解析。对于 VisDrone 这种多类别、多任务的数据集,转成 COCO 后可以无缝接入检测、跟踪、甚至分割的 pipeline。代价是 JSON 文件会比 txt 大不少,但这点存储开销换来的是工程上的便利,值得。

3. 手写转换脚本:从 txt 到 COCO JSON 的完整实现

理解了格式差异,接下来就是落地。这一章给出一个可复用的转换脚本,同时处理 DET 和 VID 两种输入,输出标准 COCO JSON。脚本用 Python 写,只依赖标准库和Pillow读图像尺寸,不引入重型依赖。

3.1 读取 VisDrone txt 并构建中间数据结构

先定义一个解析函数,把 txt 读成结构化列表。注意要处理空文件和异常行,实际数据里偶尔会有格式不规范的行。

import os import json from PIL import Image # VisDrone 类别映射:0 是忽略类,1-10 是实际类别,11 是其他 VISDRONE_CATEGORIES = { 0: "ignored", 1: "pedestrian", 2: "people", 3: "bicycle", 4: "car", 5: "van", 6: "truck", 7: "tricycle", 8: "awning-tricycle", 9: "bus", 10: "motor", 11: "others" } def parse_visdrone_txt(txt_path, is_vid=False): """解析 VisDrone 标注文件,返回实例列表""" instances = [] if not os.path.exists(txt_path): return instances with open(txt_path, "r") as f: for line in f: line = line.strip() if not line: continue parts = line.split(",") # VID 有 10 个字段,DET 有 8 个 expected = 10 if is_vid else 8 if len(parts) != expected: continue # 跳过格式异常行 if is_vid: frame_idx, target_id, x, y, w, h, score, cat, trunc, occ = map(int, parts) else: x, y, w, h, score, cat, trunc, occ = map(int, parts) frame_idx, target_id = 0, -1 # 只保留有效标注,忽略 score=0 和忽略类 if score == 0 or cat == 0: continue instances.append({ "frame_index": frame_idx, "target_id": target_id, "bbox": [x, y, w, h], "category_id": cat, "truncation": trunc, "occlusion": occ }) return instances

这段代码的关键点:expected字段数校验防止解析错位;score == 0和cat == 0的过滤逻辑对应 VisDrone 的忽略区域定义;VID 模式下保留frame_index和target_id,为后续跟踪任务留数据。参数is_vid控制解析分支,调用时根据数据集类型传入。

3.2 构建 COCO 的 images、annotations、categories 三段结构

COCO 的核心是三个列表的组装。images需要每张图的 id、文件名、宽高;annotations需要全局唯一的 id、关联的 image_id、category_id、bbox、area;categories直接从类别映射生成。

def build_coco(instances_by_image, image_dir, is_vid=False): """将按图像分组的实例列表组装成 COCO 字典""" coco = { "info": {"description": "VisDrone converted to COCO"}, "images": [], "annotations": [], "categories": [] } # 类别从 1 开始,0 留给背景 for cid, name in VISDRONE_CATEGORIES.items(): if cid == 0: continue coco["categories"].append({"id": cid, "name": name, "supercategory": "object"}) ann_id = 1 img_id = 1 for img_name, insts in instances_by_image.items(): img_path = os.path.join(image_dir, img_name) with Image.open(img_path) as im: width, height = im.size coco["images"].append({ "id": img_id, "file_name": img_name, "width": width, "height": height }) for inst in insts: x, y, w, h = inst["bbox"] coco["annotations"].append({ "id": ann_id, "image_id": img_id, "category_id": inst["category_id"], "bbox": [x, y, w, h], "area": w * h, "iscrowd": 0, "truncation": inst["truncation"], "occlusion": inst["occlusion"] }) ann_id += 1 img_id += 1 return coco

area字段是 COCO 评估时计算 mAP 用的,必须填。iscrowd对 VisDrone 一般设 0,因为它的忽略区域已经通过 score 过滤掉了。truncation和occlusion是额外加的字段,标准 COCO 解析器会忽略它们,但自定义分析时能用上。

3.3 DET 与 VID 的分支处理与输出

DET 和 VID 的主要差异在图像组织方式:DET 是扁平目录,VID 是序列目录。下面这个函数根据类型走不同分支。

def convert_visdrone(root_dir, output_json, is_vid=False): """主转换入口""" instances_by_image = {} if is_vid: seq_dir = os.path.join(root_dir, "sequences") ann_dir = os.path.join(root_dir, "annotations") for seq_name in sorted(os.listdir(seq_dir)): seq_path = os.path.join(seq_dir, seq_name) if not os.path.isdir(seq_path): continue ann_path = os.path.join(ann_dir, seq_name + ".txt") insts = parse_visdrone_txt(ann_path, is_vid=True) # 按帧号分组,映射到具体图像文件名 frames = {} for inst in insts: frames.setdefault(inst["frame_index"], []).append(inst) for frame_idx, frame_insts in frames.items(): img_name = f"{seq_name}_{frame_idx:07d}.jpg" instances_by_image[img_name] = frame_insts image_dir = seq_dir # 实际读取时需要拼序列路径 else: img_dir = os.path.join(root_dir, "images") ann_dir = os.path.join(root_dir, "annotations") for ann_file in sorted(os.listdir(ann_dir)): if not ann_file.endswith(".txt"): continue img_name = ann_file.replace(".txt", ".jpg") insts = parse_visdrone_txt(os.path.join(ann_dir, ann_file), is_vid=False) instances_by_image[img_name] = insts image_dir = img_dir coco = build_coco(instances_by_image, image_dir, is_vid) with open(output_json, "w") as f: json.dump(coco, f) print(f"Saved {len(coco['images'])} images, {len(coco['annotations'])} annotations")

VID 分支里图像文件名需要按序列和帧号重新构造,实际使用时要把image_dir改成能定位到具体帧的路径。DET 分支直接按文件名对应。输出 JSON 后,可以用pycocotools加载验证。

4. 转换过程中最容易翻车的五个坑

转换脚本写起来不复杂,但实际跑数据时总会有各种意外。这一章记录几个我踩过的坑,按现象、原因、解决三段式写清楚。

4.1 类别 ID 从 0 还是 1 开始导致训练 loss 异常

现象:转换完 JSON 直接拿去训练,模型 loss 一直不降,或者评估时 mAP 为 0。原因:VisDrone 的类别从 0 开始,但 0 是忽略类,实际类别是 1 到 10。如果直接把原始 category_id 写进 COCO,而框架默认类别从 0 开始索引,就会错位。解决:在build_coco里显式跳过 0,类别从 1 开始,并在训练配置里确认num_classes设为 10 而不是 11。

4.2 VID 帧号与图像文件名对不上

现象:VID 转换后images列表里的文件名在磁盘上找不到。原因:VisDrone 的 VID 图像命名不一定是序列名_帧号.jpg,不同批次的命名规则可能有差异。解决:先ls一个序列目录看实际命名,再调整img_name的构造逻辑。稳妥做法是遍历图像目录建立帧号到文件名的映射,而不是硬编码格式。

4.3 空标注文件和全忽略帧的处理

现象:某些图像没有任何有效标注,转换后这些图在images里但annotations为空。原因:VisDrone 里确实存在无目标的帧,或者所有目标都是忽略类。解决:这本身不是错误,COCO 允许图像没有标注。但如果框架的 dataloader 不支持空标注,需要在转换时过滤掉这些图,或者保留但在训练时跳过。

4.4 坐标越界与负值

现象:pycocotools加载时报 bbox 越界警告。原因:部分标注的 x、y 或宽高可能超出图像边界,或者出现负值。解决:在build_coco里加裁剪逻辑,把 bbox 限制在[0, 0, width, height]范围内,宽高为负的直接丢弃。

4.5 JSON 文件过大导致加载慢

现象:VID 数据集转完后 JSON 几百 MB,加载要几十秒。原因:VID 帧数多,标注量大,JSON 文本冗余。解决:如果只做检测,按序列拆成多个 JSON;如果做跟踪,考虑用 COCO 的 video 扩展或改用更紧凑的格式。另外json.dump时加separators=(',', ':')能减小体积。

5. 转换后的验证与进阶用法

转完不是终点,验证一遍才能放心用。这一章讲怎么确认转换正确,以及几个能提升效率的技巧。

5.1 用 pycocotools 做加载与统计验证

最直接的验证方式是加载 JSON 并打印统计信息。下面这段代码检查图像数、标注数、类别分布,还能可视化几张图确认 bbox 位置对不对。

from pycocotools.coco import COCO import matplotlib.pyplot as plt import matplotlib.patches as patches coco = COCO("visdrone_det_train.json") print(f"Images: {len(coco.imgs)}, Annotations: {len(coco.anns)}") # 类别分布 cats = coco.loadCats(coco.getCatIds()) for cat in cats: ids = coco.getAnnIds(catIds=[cat["id"]]) print(f"{cat['name']}: {len(ids)}") # 可视化第一张图 img_ids = coco.getImgIds() img_info = coco.loadImgs(img_ids[0])[0] ann_ids = coco.getAnnIds(imgIds=[img_info["id"]]) anns = coco.loadAnns(ann_ids) fig, ax = plt.subplots(1) ax.imshow(plt.imread(img_info["file_name"])) for ann in anns: x, y, w, h = ann["bbox"] ax.add_patch(patches.Rectangle((x, y), w, h, fill=False, edgecolor="red")) plt.show()

统计信息能快速暴露类别映射错误,可视化能确认坐标没有偏移。如果 bbox 整体偏移或缩放,多半是坐标转换时除了什么问题。

5.2 从 COCO 反推跟踪任务的数据组织

如果原始数据是 VID 且要做跟踪,转换时保留的target_id就派上用场了。可以在 COCO 的annotations里加track_id字段,然后按video_id和frame_id组织成跟踪格式。常见做法是:每个序列一个 JSON,images按帧排序,annotations里带track_id,评估时用 MOT 的指标脚本。这样既复用了 COCO 的解析生态,又保留了跟踪所需的时序关联。

5.3 批量转换多个 split 的脚本组织

实际项目里通常要处理 train、val、test-dev、test-challenge 多个 split。建议写一个配置文件或命令行参数,把 root 目录、输出路径、是否 VID 作为参数传入,循环调用convert_visdrone。这样换数据集或换 split 时不用改代码。另外转换完记得校验每个 JSON 的images数量是否和磁盘文件数一致,差太多说明文件名映射有问题。

我自己的习惯是:每次转换完先跑一遍统计脚本,把类别分布和官方论文里的数字对一下,偏差超过 5% 就回去查解析逻辑。这个习惯帮我省了很多事后排查的时间。希望帮到你。

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

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

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

立即咨询