新闻详情

新闻详情

首页 / 资讯中心 / 详情

VisDrone2019 DET与VID转COCO格式:解析、转换与验证

发布时间:2026/9/29 17:42:06来源:尧图网络
VisDrone2019 DET与VID转COCO格式:解析、转换与验证
简介这是一份面向计算机视觉初学者与算法工程师的数据格式转换工具包用于将 VisDrone2019 数据集中的 DET目标检测与 VID视频目标检测/跟踪标注转换为 COCO 格式。作者已提前在代码中标注全部需修改的路径使用者只需按说明文档指引调整本地路径即可运行省去手动解析标注的繁琐步骤。资源压缩包约 3KB共 3 个文件包含 2 个 Python 脚本与 1 个 Markdown 说明文档脚本分别承担核心转换与示例调用功能说明文档则详细梳理环境配置、参数含义与运行流程整体结构精简、上手门槛低。目前该资源已有 2208 人学习下载适合正在备战相关竞赛或需要快速完成 VisDrone 数据适配 COCO 训练管线的开发者直接参考复用。1. 一次 VisDrone2019 的 DET 与 VID 格式转换为什么会卡住一整天先讲一个场景你从 VisDrone2019 的 DET 数据集里拿到了 10 类无人机视角标注准备拿来训 YOLOv8打开数据目录发现全是 txt每个 txt 里一行一行的数字。等你好不容易把 DET 训通又想做跟踪实验发现 VID 任务的数据结构和 DET 不一样是按序列组织的于是又得写一套解析。这个项目标题「CV 数据格式转换Visdrone2019 中的 DET 和 VID 转换为 coco」背后其实是很多 CV 从业者的日常VisDrone2019 的 DET 和 VID 用同一套 txt 标注风格但目录结构、帧组织、时序语义完全不同想把它们喂给 mmdetection、Detectron2 或 COCO API 生态的训练与评估管线必须先统一成 COCO JSON。适合谁读手里有 VisDrone2019 标注、想在 YOLO/MMDet 系框架里跑检测或跟踪的工程师。核心思路一句话先读懂原始标注的字段再设计一个能同时吃掉 DET 和 VID 的转换器最后用可视化验证转换没有丢框、没有错位。2. 先读懂 VisDrone2019 的 DET 与 VID 原始标注10 列字段和目录结构2.1 DET 的 annotations同名 txt 与 10 列标注的字段含义VisDrone2019 的 DET 任务目录一般长这样VisDrone2019-DET-train下面有images和annotations两个目录images里是一张张 JPEGannotations里是文件名完全相同的.txt。比如一张0000001_00777_d_0000001.jpg对应标注文件就是0000001_00777_d_0000001.txt。这里的_d_代表这是 DET 数据集里的图片帧命名规则在 DET 里基本不影响解析但在 VID 里帧号含义完全不同。每个 txt 每行描述一个目标。VisDrone 官方给出的字段顺序并不统一不同发布版本里出现过两种常见排列一种是 bbox 的左上角 x、左上角 y、宽、高接着是类别编号另一种是左上角和右下角两点坐标后跟类别、截断、遮挡、忽略标志。我见过很多人在这一步翻车拿到 txt 不做验证就开始写转换脚本结果把宽高当成了右下角坐标画框直接歪出目标。稳妥做法是先打开一个 txt 看一眼确认前四列数值的实际含义再用一张标注好的原图做交叉验证。字段大致可以归纳成下面这张表。列位常见含义取值说明1-4bbox 四值常见为左上角 x、y、宽、高或两点坐标5score / 置信度部分版本存在DET 通常为 06类别编号1-10 为有效目标0 表示 ignore 区域7截断比例0-2 或 0-1越大越不完整8遮挡比例0-3 或 0-1越大被挡越多9ignore 标志0 正常参与训练1 建议忽略10附加属性部分 txt 里有多数场景用不到VisDrone 的类别规则是固定 10 类pedestrian、people、bicycle、car、van、truck、tricycle、awning-tricycle、bus、motor分别编号 1 到 10。编号 0 通常是不可见或难以分辨的标注区域很多代码会把这类目标忽略掉转换时要单独处理。2.2 VID 的序列目录藏在帧文件名里的时间顺序VID 任务的目录组织方式和 DET 完全不同。VID 数据一般以VisDrone2019-VID-train/sequences为根下面是一个个序列目录每个序列目录里按顺序排列帧图像图像命名类似0000001.jpg、0000002.jpg。有的版本还有配套的annotations目录镜像了序列结构每个序列下放同名 txt也有的版本把 txt 直接和 jpg 放在同一级目录。不管哪种核心信息都很明确同一序列中的帧之间有时间先后顺序帧号就是时间轴。很多人第一次转 VID 时惯性沿用 DET 的转换思路只把每张图当成独立样本丢掉序列关系。这在小规模检测场景里看不出问题一旦要跑跟踪类任务、或者要用 VID 来补充检测训练并保持时序一致性就会吃亏。转换时至少要把序列名和帧号保留下来最直接的办法是把序列映射成整数索引再把帧排序后的序号和序列索引拼成全局唯一的 image_id。这样 COCO JSON 里每张图片既有一个稳定的全局编号又能通过命名或额外字段反查出它属于哪个序列的第几帧。VID 还有一个值得注意的细节序列清单。官方一般会提供划分好的序列列表train 用的序列和 val 用的序列是分开的转换时按序列粒度切分而不是按帧随机切分能避免同一段连续视频被拆进训练集和验证集评估 mAP 时会乐观不少。2.3 哪些字段值得带进 COCOcategory、truncation 与 ignore 的处理COCO 格式本身没有 truncation 和 occlusion 的官方字段但 VisDrone 里这两个属性对实际训练有影响。常见做法有两种。第一种是直接丢弃只保留 bbox、category_id、image_id 这三个核心字段实现最简单COCO API 完全兼容第二种是塞进 annotation 的自定义字段比如在每条 annotation 里加truncation和occlusion训练时如果你的检测头支持额外属性可以拿来辅助训练或做数据过滤。我一般倾向第二种成本不高而且后续分析失败案例时能查出来一个框是因为遮挡还是截断导致的误检。ignore 标志则更关键。VisDrone 里 ignore1 的目标处在遮挡严重、目标过小或边界不完整的位置直接参与训练反而会干扰模型。转换脚本里我会保留这些框但默认过滤掉同时留一个keep_ignore开关。这么设计的原因有两个一是过滤后训练集更干净二是某些分析场景下需要知道原始标注里有多少被忽略目标用来评估数据质量分布。3. 将 DET 与 VID 转换为 COCO JSON一个可复用脚本与参数设置3.1 COCO 格式的三个顶层数组与字段映射表COCO 标注的顶层结构就三个数组images、annotations、categories。每个 image 元素必须有id、file_name、width、height每个 annotation 必须有id、image_id、category_id、bbox、area、iscrowd每个 category 必须有id、name一般再加一个supercategory。bbox必须是[x, y, width, height]单位是像素数值用 float 还是 int 取决于你的下游框架常见做法是统一转成 int 因为检测框坐标不需要亚像素精度但 area 最好保留 float避免部分评估代码对零面积报错。VisDrone 到 COCO 的字段映射我一般按下面这张表处理。VisDrone 原始字段映射目标说明bbox 四值annotation.bbox若原始是两点坐标先转成左上角加宽高类别编号category_id0 过滤1-10 按序保留truncationannotation.truncation自定义字段可保留可丢弃occlusionannotation.occlusion自定义字段ignore过滤依据keep_ignoreFalse时跳过帧图像路径image.file_name存相对路径避免换机器后路径失效categories 数组要特别注意COCO 的 category_id 不需要从 0 开始但需要连续且稳定。VisDrone 的类别从 1 开始正好契合把 0 和 11 这类额外编号过滤掉就能直接用。如果你想把 pedestrian 合并成 person或者把 car、van、truck 合并成 vehicle务必在转换脚本里维护一个显式的映射字典而不是在 JSON 里改 id。3.2 DET 转 COCO保持坐标精度且不丢 ignore 的转换函数先写一个通用的 VisDrone txt 解析函数它能同时兼容逗号分隔和空格分隔的 txt并把前四列的 bbox 格式做成可配置项。import json from pathlib import Path def parse_visdrone_txt(txt_path, bbox_fmtltwh): 解析 VisDrone txt 标注返回记录列表。 bbox_fmt: ltwh 表示左上角 x, 左上角 y, 宽, 高 ltrb 表示左上角 x, 左上角 y, 右下角 x, 右下角 y。 records [] with open(txt_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts [p for p in line.replace(,, ).split() if p.strip()] # 按列位解析前四列是 bbox values [float(p) for p in parts[:9]] # 取前 9 列 if len(values) 8: continue x1, y1 values[0], values[1] x2, y2 values[2], values[3] if bbox_fmt ltwh: bw, bh x2, y2 x1, y1, x2, y2 x1, y1, x1 bw, y1 bh category_id int(values[4]) if len(values) 6 else 0 truncation int(values[5]) if len(values) 7 else 0 occlusion int(values[6]) if len(values) 8 else 0 ignore int(values[7]) if len(values) 9 else 0 records.append({ bbox: [x1, y1, x2, y2], category_id: category_id, truncation: truncation, occlusion: occlusion, ignore: ignore, }) return records解析函数里我取了前 9 列而不是死板地取 10 列因为部分 txt 末尾多出来的列并不参与训练。前四列的ltwh意味着文件里存的是左上角加宽高ltrb意味着存的是两点坐标两者只影响解析阶段进入 COCO 前都统一成[x, y, width, height]。类别、截断、遮挡、忽略字段的取位依赖你对手里 txt 的确认脚本里留了清晰的序号注释改起来很快。接着写 DET 的转换函数。from PIL import Image CATEGORY_MAP { 0: None, # ignore 区域过滤掉 1: pedestrian, 2: people, 3: bicycle, 4: car, 5: van, 6: truck, 7: tricycle, 8: awning-tricycle, 9: bus, 10: motor, } def det_to_coco(images_dir, anns_dir, out_path, keep_ignoreFalse): images_dir Path(images_dir) anns_dir Path(anns_dir) images, annotations [], [] category_dict {} image_id, ann_id 1, 1 # COCO 惯例id 从 1 开始 txt_list sorted(anns_dir.glob(*.txt)) for txt_path in txt_list: img_path images_dir / (txt_path.stem .jpg) if not img_path.exists(): continue with Image.open(img_path) as img: width, height img.size image_id 1 images.append({ id: image_id, file_name: img_path.name, width: width, height: height, }) records parse_visdrone_txt(txt_path) for obj in records: cat_id obj[category_id] if cat_id not in CATEGORY_MAP: continue if CATEGORY_MAP[cat_id] is None: continue if obj[ignore] and not keep_ignore: continue x1, y1, x2, y2 obj[bbox] bw max(0, x2 - x1) bh max(0, y2 - y1) if bw 0 or bh 0: continue ann_id 1 annotations.append({ id: ann_id, image_id: image_id, category_id: cat_id, bbox: [x1, y1, bw, bh], area: bw * bh, iscrowd: 0, truncation: obj[truncation], occlusion: obj[occlusion], }) categories [] for cid, name in CATEGORY_MAP.items(): if name is not None: categories.append({id: cid, name: name, supercategory: object}) coco { images: images, annotations: annotations, categories: categories, } with open(out_path, w, encodingutf-8) as f: json.dump(coco, f, ensure_asciiFalse, indent1) print(fsaved {len(images)} images, {len(annotations)} anns to {out_path})这个函数里我特意用PIL.Image.open读了一遍图片尺寸而不是写死1920x1080。VisDrone 里大部分帧是 2720x1530但有些区域裁剪帧尺寸并非常见的 16:9不读真实尺寸会导致 bbox 越界时无法判断。image_id 和 ann_id 都从 1 开始递增避免某些框架默认 id 从 1 起步造成 index 错位。过滤逻辑分两层第一层过滤 CATEGORY_MAP 里不存在的类别第二层按 ignore 标志过滤。keep_ignore开关在调试数据集质量时非常有用建议保留默认 False。3.3 VID 转 COCO用帧号构造全局唯一 image_idVID 转换的核心差异在于图片组织。给定一个序列根目录先按序列切分再把每个序列内部的帧排序最后用seq_index和frame_index构造全局唯一的 image_id。def vid_to_coco(seq_root, seq_list, out_path, keep_ignoreFalse): seq_root Path(seq_root) images, annotations [], [] category_dict {} image_id, ann_id 1, 1 # seq_list 是官方提供的 train_seq.txt / val_seq.txt 内容按行列出序列名 seq_names [s.strip() for s in seq_list if s.strip()] for seq_idx, seq_name in enumerate(seq_names): seq_dir seq_root / seq_name if not seq_dir.exists(): continue jpg_paths sorted(seq_dir.glob(*.jpg)) for frame_idx, jpg_path in enumerate(jpg_paths): txt_path jpg_path.with_suffix(.txt) with Image.open(jpg_path) as img: width, height img.size image_id 1 images.append({ id: image_id, file_name: str(jpg_path.relative_to(seq_root)), width: width, height: height, seq_id: seq_idx, frame_id: frame_idx, }) if not txt_path.exists(): continue # 保留空帧但不加 annotation records parse_visdrone_txt(txt_path) for obj in records: cat_id obj[category_id] if cat_id not in CATEGORY_MAP or CATEGORY_MAP[cat_id] is None: continue if obj[ignore] and not keep_ignore: continue x1, y1, x2, y2 obj[bbox] bw max(0, x2 - x1) bh max(0, y2 - y1) if bw 0 or bh 0: continue ann_id 1 annotations.append({ id: ann_id, image_id: image_id, category_id: cat_id, bbox: [x1, y1, bw, bh], area: bw * bh, iscrowd: 0, seq_id: seq_idx, frame_id: frame_idx, }) with open(out_path, w, encodingutf-8) as f: json.dump({images: images, annotations: annotations, categories: categories}, f, ensure_asciiFalse, indent1)这段代码的关键是jpg_path.with_suffix(.txt)直接在同级目录找同名标注如果 txt 和 jpg 不在同一目录改成annotations_dir / seq_name / (jpg_path.stem .txt)即可。空帧没有 txt 时我选择保留 image 记录但跳过标注这样跟踪类任务里图片数量完整检测类任务里也不会因为 image_id 断号出问题。seq_id和frame_id被放进 image 和 annotation 的自定义字段下游想按序列抽样或者统计时序关系时能直接读。3.4 运行参数类别编号、属性过滤与 train/val/test 划分脚本最后加一个 main 入口把 DET 和 VID 的分支统一起来。我会用 argparse 把路径和开关暴露出来方便后续切换不同数据集版本。python convert_visdrone.py \ --task det \ --images_dir /data/VisDrone2019-DET-train/images \ --anns_dir /data/VisDrone2019-DET-train/annotations \ --out /data/visdrone_det_train.json \ --keep_ignore Falseif __name__ __main__: import argparse parser argparse.ArgumentParser() parser.add_argument(--task, choices[det, vid], requiredTrue) parser.add_argument(--images_dir, typestr, defaultNone) parser.add_argument(--anns_dir, typestr, defaultNone) parser.add_argument(--seq_root, typestr, defaultNone) parser.add_argument(--seq_list, typestr, defaultNone) parser.add_argument(--out, typestr, requiredTrue) parser.add_argument(--keep_ignore, typebool, defaultFalse) args parser.parse_args() if args.task det: det_to_coco(args.images_dir, args.anns_dir, args.out, args.keep_ignore) else: seq_list Path(args.seq_list).read_text(encodingutf-8).splitlines() vid_to_coco(args.seq_root, seq_list, args.out, args.keep_ignore)参数设计上有两个点值得注意。keep_ignore前面已经说过默认过滤但保留开关。seq_list明确要求由外部提供不建议在脚本内部去扫全部序列目录因为 VID 的划分标准掌握在数据发布方手里扫目录会把测试序列一起转进去训练评估就脏了。如果你自己在做非官方划分就按 8:2 在序列粒度上划分tricycle 这类稀有类别最好保证每个序列都被完整保留不要按帧切。4. 转换后常见问题排查五条影响训练结果的踩坑记录4.1 现象训练加载时报IndexError或 mAP 始终为 0原因category_id 不连续。VisDrone 里很多转换工具会把 0 保留成背景导致 categories 里出现[{id:0,...}]而检测框架在构造分类头时要求 id 从 1 连续地映射到输出通道0 一旦作为有效类别出现输出层和目标索引就错位了。解决转换前把 CATEGORY_MAP 里值为 None 的 id 全部过滤categories 只保留 1 到 10如果使用了 forked 标注务必打印一次 JSON 的 categories 数组确认没有 0 和跳跃编号。4.2 现象画框全部偏移宽高明显不对但程序不报错原因bbox 格式混用。VisDrone 某些发布版本的 txt 里存的是左上角和右下角你按宽高解析后把 x2、y2 当成了宽和高画出来就是超大的畸形框反过来的情况是存的是宽高你却当成了右下角坐标画框贴住目标左上角但大小不对。解决解析前随机挑三张标注图把原始 txt 数值叠画在图上肉眼比对确认前四列含义后再设置bbox_fmt。我习惯在 parse 函数里加一个坐标合法性检查若检测到x2 x1或y2 y1就打印警告这类错误越早暴露越好。4.3 现象VID 转换后图片数量远多于预期且训练集验证集重复大量图片原因没有按序列划分而是把每个帧独立随机分箱。同一序列相邻帧内容高度相似拆进训练集和验证集后验证集等同于看过了训练数据mAP 虚高跟踪任务几乎失去意义。解决划分只发生在序列级。转换前先读官方序列清单train_seq 全量转成训练 JSONval_seq 全量转成验证 JSON如果没有官方清单按序列目录名做哈希分桶同一序列名落入同一桶不要用文件级随机抽样。4.4 现象大尺寸图像上目标非常小loss 正常但检测结果全是小碎片原因过滤 ignore 后框仍然太小且加入了下游数据增强的随机裁剪。VisDrone 里小型目标的宽高经常只有二三十像素COCO 转换建议保留这些小目标因为它们才是无人机视角的低空检测难点但如果你选择的训练管线默认用了 min_size 过滤小框会被当成背景丢掉。解决转换脚本里不要按像素阈值过滤小框把过滤逻辑留给训练配置文件如果你发现模型完全不关心小目标可以增加一个--min_wh参数只在转换阶段保留宽高大于阈值的标注阈值建议从 8 像素开始试而不是直接取 32。4.5 现象JSON 文件几百 MB加载一次要十几秒训练前处理反复卡死原因VID 全量帧数多而且脚本把自定义字段重复存进了 image 和 annotation 里数据膨胀严重。解决第一自定义字段用简短名字比如sid、fid避免seq_id、frame_id这类长篇字符串在几十万条记录里反复出现第二装箱时把数值全部转成 int减少浮点序列化体积第三如果你只是做检测而不关心时序VID 转 COCO 时直接从 file_name 里解析帧号不额外存 custom 字段性能提升最明显。还有一种常见做法是输出两份一份完整 JSON 用于跟踪一份精简 JSON 用于检测。5. 转换完先验证三分钟可视化检查一套转换是否可靠拿到 COCO JSON 后不要急着开训。我会先跑一个极简验证脚本随机抽 20 张图把原始 txt 的框和 COCO JSON 里的框分别画出来叠在同一张图上对看。如果两者完全重合说明 bbox 解析、坐标系、过滤逻辑全部正确如果不重合逐张定位是解析问题还是过滤问题。这个验证步骤十分钟内能完成但能帮你省下后面训练三天排错的时间。import json, random from PIL import Image, ImageDraw coco_path visdrone_det_train.json with open(coco_path, r, encodingutf-8) as f: coco json.load(f) img_idx random.sample(range(len(coco[images])), 5) for idx in img_idx: img_info coco[images][idx] img Image.open(img_info[file_name]) draw ImageDraw.Draw(img) for ann in coco[annotations]: if ann[image_id] ! img_info[id]: continue x, y, w, h ann[bbox] draw.rectangle([x, y, x w, y h], outlinered, width3) img.save(fcheck_{idx}.jpg)这段脚本只依赖 PIL不需要装 pycocotools。运行时把file_name换成你的图片相对路径根目录如果 img 打开失败多半是 JSON 里存的路径和你当前工作目录不一致回头检查相对路径的基准点。输出文件里红色矩形如果刚好框住车辆、行人和自行车转换基本可信。做完可视化我还会做一次数据统计COCO JSON 里images的数量、annotations的数量以及每个类别的框数分布和原始 txt 逐目录统计的框数做对比。两边总数对不上时优先检查 ignore 过滤和空 txt 帧是不是被一并丢掉了。这个数字不需要完全一致因为忽略框本身就被设计为不参与训练但如果少了十分之一以上就要回到过滤条件去查。以后每次换数据源我第一件事就是写这个验证脚本。格式转换的坑是固定那几个但每次坑的位置略有不同。先花二十分钟把框画出来比任何日志和断言都直观。这个习惯帮我避免过好几次低级失误希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

拆解爆火的 JEV(上):10 分钟看懂这款 Agent 的高速“协处理器”。 2026/9/29 20:34:27

拆解爆火的 JEV(上):10 分钟看懂这款 Agent 的高速“协处理器”。

AI 圈从来不缺少新玩意。最近,硅谷又爆了一款新模型 — JEV。有趣的是, JEV 并非我们熟知的大语言模型(LLM),更不是出图和视频的模型。这款来自 TypeSafe 公司的产品不写文章也不聊天,却让无数 Agent 开发者…

阅读更多 →
基于php的摄影门户交流网站-附源码 2026/9/29 20:34:27

基于php的摄影门户交流网站-附源码

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

阅读更多 →
OpenGame API配置详解:如何为4大AI模态设置Provider与密钥? 2026/9/29 20:34:27

OpenGame API配置详解:如何为4大AI模态设置Provider与密钥?

OpenGame API配置详解:如何为4大AI模态设置Provider与密钥? 【免费下载链接】OpenGame OpenGame: Open Agentic Coding for Games 项目地址: https://gitcode.com/gh_mirrors/op/OpenGame OpenGame 是一个开源的游戏 Agent 框架,靠调用…

阅读更多 →
可控硅触发技术:移相与过零触发电路设计及工程实战 2026/9/29 20:34:27

可控硅触发技术:移相与过零触发电路设计及工程实战

我估计每个做过温度控制、电机调速或者工业调光的人,都跟可控硅打过交道。以前我调试一台三相电热烘箱,温度总是冲过头,PID给了很弱的信号,加热管还是"哐哐"地全功率猛干。后来换成可控硅调压器,把触发方式从…

阅读更多 →
Kimi K3 重磅发布冲进前三!TaoToken 统一 Key 接入保姆级配置教程 2026/9/29 20:34:26

Kimi K3 重磅发布冲进前三!TaoToken 统一 Key 接入保姆级配置教程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
半导体芯片企业落地AI:从产线数据到TaoToken统一API通道的配置实战 2026/9/29 20:34:20

半导体芯片企业落地AI:从产线数据到TaoToken统一API通道的配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉