ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

VisDrone数据集转COCO格式:从数据解析到JSON生成的完整实践

VisDrone数据集转COCO格式:从数据解析到JSON生成的完整实践 简介本资源是VisDrone数据集转换为标准COCO格式的标注文件包面向目标检测方向的算法工程师、计算机视觉学习者及YOLOX等主流框架使用者解决原始VisDrone数据集不兼容COCO训练流程的问题。压缩包共4个文件10.18MB含2个标准COCO JSON标注文件train2017/val2017、1个图片路径说明txt及1张下载指引jpg结构精简、开箱即用其中JSON文件已剔除训练集中10张存在标注错误的样本确保数据质量可靠。目前已有764人学习下载所有标注均经YOLOX实测训练验证可直接用于模型训练与评估并支持导入百度EasyDL等平台交叉验证格式合规性。用户仅需从VisDrone官网下载对应图片并放入指定目录即可无缝接入COCO工作流显著降低数据预处理门槛与排错成本。1. 项目概述从VisDrone到COCO一份JSON文件背后的数据工程如果你正在计算机视觉特别是目标检测领域摸爬滚打那么“VisDrone”和“COCO”这两个名字对你来说一定不陌生。VisDrone是一个极具挑战性的无人机视角数据集专注于交通场景下的车辆、行人检测其视角独特、目标密集、尺度变化大是检验算法鲁棒性的绝佳试金石。而COCOCommon Objects in Context数据集凭借其统一的标注格式和庞大的数据规模早已成为目标检测、实例分割等任务事实上的“标准答案”和“通用货币”。今天要聊的就是如何将VisDrone这座“富矿”的原始标注熔炼成COCO格式的JSON文件。这看似只是一个格式转换的“脏活累活”实则贯穿了数据理解、格式解析、脚本编写和结果验证的完整数据工程流程。对于任何希望利用VisDrone数据在主流检测框架如MMDetection、Detectron2、YOLO等上进行训练和评估的研究者或工程师来说这都是必须跨越的一道坎。本文将带你深入这个转换过程的每一个细节不仅告诉你“怎么做”更会剖析“为什么这么做”并分享我踩过的坑和总结的经验让你能高效、准确地得到那份关键的instances_train2017.json或instances_val2017.json。2. VisDrone与COCO两种数据哲学的碰撞与融合2.1 VisDrone数据格式深度解析VisDrone数据集的标注文件通常以.txt格式存储每个图像对应一个同名的TXT文件。打开一个典型的标注文件你会看到类似这样的内容54, 312, 22, 30, 1, 1, 1, 1 472, 210, 42, 22, 1, 1, 1, 1 ...每一行代表一个目标实例包含8个用逗号分隔的数值。这8个字段的含义是VisDrone数据逻辑的核心边界框左上角x坐标目标边界框左上角在图像中的列坐标从0开始。边界框左上角y坐标目标边界框左上角在图像中的行坐标从0开始。边界框宽度目标边界框的像素宽度。边界框高度目标边界框的像素高度。目标类别一个整数代表目标的类别。VisDrone定义了10个类别例如0忽略区域、1行人、2人、3自行车、4汽车、5货车、6卡车、7三轮车、8遮阳三轮车、9巴士、10摩托车。特别注意类别0是“忽略区域”在训练时通常需要特殊处理或过滤掉。截断标志0表示目标未被截断1表示目标被截断部分在图像外。这个信息对于评估很重要但在COCO格式中通常没有直接对应字段需要我们在转换时决定是保留通过其他方式还是舍弃。遮挡标志0表示无遮挡1表示被遮挡。同样这是评估指标的一部分COCO格式不直接支持。其他或称为“可信度”在VisDrone的某些版本中这个字段可能表示标注的可信度或其他属性。注意VisDrone的类别索引是从1开始的行人1但我们在编程处理时尤其是在转换为COCO格式其category_id通常从1开始连续时需要特别注意映射关系避免出现0类别忽略区域被错误引入。VisDrone的这种格式非常紧凑适合存储但缺乏结构化的描述信息比如整个数据集的类别列表、图像信息等都是分散在各个TXT和图像文件中的。2.2 COCO数据格式标准剖析COCO格式则是一种高度结构化的JSON标注格式。一个完整的COCO JSON文件如instances_train2017.json是一个庞大的字典主要包含以下几个顶级字段info: 描述数据集的基本信息如年份、版本、描述等。licenses: 许可证信息列表。images: 一个列表列表中的每个元素是一个字典描述一张图像的信息。这是关键包含id: 图像的唯一ID整数。必须全局唯一。file_name: 图像文件名如0000001.jpg。height: 图像高度像素。width: 图像宽度像素。可选license,coco_url,flickr_url等。annotations: 一个列表列表中的每个元素是一个字典描述一个目标实例的标注。这是核心包含id: 标注的唯一ID整数。必须全局唯一。image_id: 该标注所属图像的ID与images列表中的id对应。category_id: 该实例的类别ID与categories列表中的id对应。bbox: 边界框格式为[x, y, width, height]其中(x, y)是边界框左上角的坐标。这里有一个关键点COCO的bbox是[x, y, width, height]而VisDrone的原始格式也是[x, y, width, height]看起来可以直接对应。但必须验证坐标原点是否一致通常都是左上角为原点并且要确保转换后bbox的每个值都是浮点数在JSON中。area: 边界框的面积width * height。这个字段很重要COCO评估API会用它来划分小、中、大目标。segmentation: 实例分割的多边形点集对于检测任务如果只有框可以设为空列表[]或[[]]。iscrowd: 是否为拥挤人群标注0表示单个实例1表示一群实例crowd。VisDrone中通常都是0。categories: 一个列表列表中的每个元素是一个字典描述一个类别。包含id: 类别的唯一ID整数通常从1开始。name: 类别的名称字符串如“person”。supercategory: 父类别字符串如“vehicle”可以为空。对比两者你会发现核心的映射关系在于将VisDrone每行8个字段的文本拆解并填充到COCO JSON的images、annotations和categories这三个核心结构中。同时我们需要为整个数据集生成info和licenses可以简单填写并确保所有ID的唯一性和关联的正确性。3. 转换脚本的核心设计与实现要点理解了两种格式我们就可以开始设计转换脚本。我将使用Python进行演示因为它有丰富的库如json,os,PIL/opencv来处理文件和图像。3.1 整体转换流程设计一个健壮的转换流程应该包含以下步骤我将其绘制成一个清晰的逻辑流程图来展示flowchart TD A[开始转换流程] -- B[步骤1: 初始化COCO数据结构] B -- C[步骤2: 定义类别映射brVisDrone - COCO] C -- D[步骤3: 遍历VisDrone图像文件夹] D -- E{对于每张图片} E -- F[步骤4: 读取图像尺寸brPIL/OpenCV] F -- G[步骤5: 构建image信息字典brid, file_name, height, width] G -- H[步骤6: 查找对应标注TXT文件] H -- I{文件存在?} I -- 是 -- J[步骤7: 逐行解析TXT标注] I -- 否 -- K[步骤8: 跳过无标注图像] J -- L{对于每个标注行} L -- M[步骤9: 解析8个字段] M -- N[步骤10: 过滤无效标注br如忽略区域、无效框] N -- O[步骤11: 映射类别ID] O -- P[步骤12: 构建annotation字典brid, image_id, category_id, bbox, area, iscrowd] P -- Q[步骤13: 添加到annotations列表] Q -- R[步骤14: 将image信息添加到images列表] R -- S[步骤15: 继续处理下一张图片] K -- S S -- D D -- T[步骤16: 所有图片处理完毕] T -- U[步骤17: 组装完整COCO JSON字典] U -- V[步骤18: 写入JSON文件] V -- W[转换完成]这个流程图清晰地展示了从初始化到最终输出的完整过程。接下来我们将深入流程中的几个关键环节看看具体的代码实现和需要注意的细节。3.2 关键环节一类别映射与过滤策略VisDrone有10个可识别类别1-10和一个忽略区域0。COCO的categories列表需要我们自己定义。通常我们会将VisDrone的类别映射到COCO风格的名字并建立一个映射字典。# 定义COCO格式的类别列表 # 注意id通常从1开始连续与VisDrone的原始id不同。 coco_categories [ {id: 1, name: pedestrian, supercategory: person}, {id: 2, name: people, supercategory: person}, {id: 3, name: bicycle, supercategory: vehicle}, {id: 4, name: car, supercategory: vehicle}, {id: 5, name: van, supercategory: vehicle}, {id: 6, name: truck, supercategory: vehicle}, {id: 7, name: tricycle, supercategory: vehicle}, {id: 8, name: awning-tricycle, supercategory: vehicle}, {id: 9, name: bus, supercategory: vehicle}, {id: 10, name: motor, supercategory: vehicle}, ] # 建立VisDrone原始类别id到COCO category_id的映射 # VisDrone的‘ignored regions’(0) 将被过滤掉。 visdrone_to_coco_id { 1: 1, # pedestrian - 1 2: 2, # people - 2 3: 3, # bicycle - 3 4: 4, # car - 4 5: 5, # van - 5 6: 6, # truck - 6 7: 7, # tricycle - 7 8: 8, # awning-tricycle - 8 9: 9, # bus - 9 10: 10, # motor - 10 }过滤策略在解析每一行标注时首先要检查category_id原始第一个字段后的第五个字段。如果它是0代表忽略区域我们应该直接跳过这个标注不将其加入COCO的annotations列表。这是保证训练数据纯净度的关键一步。3.3 关键环节二图像信息获取与ID管理COCO格式要求为每张图像生成一个唯一的image_id并且每个标注的image_id要正确关联。一个简单可靠的方法是使用一个自增的计数器。import os from PIL import Image image_id 1 # 起始ID for img_file in sorted(os.listdir(images_dir)): # 按文件名排序保证可复现 if not img_file.lower().endswith((.jpg, .jpeg, .png)): continue # 获取图像尺寸 img_path os.path.join(images_dir, img_file) try: with Image.open(img_path) as img: width, height img.size except Exception as e: print(fWarning: Could not open image {img_file}: {e}. Skipping.) continue # 构建image信息字典 image_info { id: image_id, file_name: img_file, height: height, width: width, # 可以添加更多信息如 license: 1, } coco_images.append(image_info) # 接下来处理这张图的标注... # annotation中的 image_id 要设置为当前的 image_id image_id 1 # ID自增注意事项务必使用PIL或OpenCV读取图像的实际尺寸而不是假设一个固定值。因为VisDrone数据集中图像的尺寸可能不完全一致。将height和width填错会导致后续计算area、评估时计算IoU等全部出错。3.4 关键环节三标注解析与边界框处理这是转换的核心。对于每个图像对应的TXT文件我们需要逐行读取并解析。annotation_id 1 # 标注ID也需要全局唯一 for img_info in coco_images: base_name os.path.splitext(img_info[file_name])[0] txt_path os.path.join(annotations_dir, base_name .txt) if not os.path.exists(txt_path): # 有些图像可能没有标注文件跳过 continue with open(txt_path, r) as f: lines f.readlines() for line in lines: line line.strip() if not line: continue parts line.split(,) if len(parts) 8: # 确保有足够字段 continue # 解析字段注意转换为整数 x, y, w, h map(int, parts[:4]) cls_id int(parts[4]) truncation int(parts[5]) occlusion int(parts[6]) # alpha int(parts[7]) # 可能不需要 # 1. 过滤忽略区域 if cls_id 0: continue # 2. 过滤无效边界框 (可选但推荐) if w 0 or h 0: print(fWarning: Invalid bbox (w{w}, h{h}) in {txt_path}. Skipping.) continue # 3. 类别映射 if cls_id not in visdrone_to_coco_id: print(fWarning: Unknown category id {cls_id} in {txt_path}. Skipping.) continue coco_cls_id visdrone_to_coco_id[cls_id] # 4. 构建COCO annotation # COCO的bbox要求是 [x, y, width, height]且x,y是左上角坐标。VisDrone格式一致。 # 但COCO官方评估脚本要求bbox是float类型。 bbox [float(x), float(y), float(w), float(h)] area float(w * h) ann { id: annotation_id, image_id: img_info[id], category_id: coco_cls_id, bbox: bbox, area: area, segmentation: [], # 检测任务分割为空 iscrowd: 0, # VisDrone通常不是crowd标注 } coco_annotations.append(ann) annotation_id 1重要细节数据类型bbox和area在COCO JSON中通常是浮点数float尽管坐标和尺寸是整数。使用float()进行转换可以避免一些解析库的潜在问题。边界框验证务必添加对w和h的检查防止出现负值或零值框这种无效标注会导致训练时损失计算出现NaN。截断与遮挡信息VisDrone中的truncation和occlusion信息在COCO格式中没有直接位置存放。如果你希望保留这些信息用于后续分析或特殊训练策略如对遮挡目标赋予不同权重一个常见的做法是将其作为annotation字典的自定义字段加入例如visdrone_attr: {truncation: truncation, occlusion: occlusion}。但请注意大多数标准训练代码会忽略这些额外字段。4. 完整脚本示例与结果验证4.1 一个完整的转换脚本骨架将上述环节组合起来并添加数据集信息等形成一个完整的脚本import os import json from PIL import Image from tqdm import tqdm # 用于显示进度条 def convert_visdrone_to_coco(visdrone_img_dir, visdrone_ann_dir, output_json_path): 将VisDrone数据集转换为COCO格式的JSON文件。 参数: visdrone_img_dir: VisDrone图像文件夹路径 visdrone_ann_dir: VisDrone标注TXT文件夹路径 output_json_path: 输出的COCO JSON文件路径 # 1. 初始化COCO数据结构 coco_data { info: { year: 2023, version: 1.0, description: Converted from VisDrone dataset, contributor: , url: , date_created: 2023-01-01 }, licenses: [{id: 1, name: VisDrone License, url: }], images: [], annotations: [], categories: [] } # 2. 定义类别 (同上此处省略...) coco_categories [...] visdrone_to_coco_id {...} coco_data[categories] coco_categories # 3. 遍历图像 image_id 1 annotation_id 1 img_files [f for f in os.listdir(visdrone_img_dir) if f.lower().endswith((.jpg, .jpeg, .png))] for img_file in tqdm(sorted(img_files), descProcessing Images): img_path os.path.join(visdrone_img_dir, img_file) # 获取图像尺寸 try: with Image.open(img_path) as img: width, height img.size except Exception as e: print(fSkipping {img_file}: {e}) continue # 添加图像信息 image_info { id: image_id, file_name: img_file, height: height, width: width, } coco_data[images].append(image_info) # 处理对应标注 base_name os.path.splitext(img_file)[0] txt_path os.path.join(visdrone_ann_dir, base_name .txt) if os.path.exists(txt_path): with open(txt_path, r) as f: lines f.readlines() for line in lines: line line.strip() if not line: continue parts line.split(,) if len(parts) 8: continue x, y, w, h map(int, parts[:4]) cls_id int(parts[4]) # 过滤忽略区域和无效框 if cls_id 0 or w 0 or h 0: continue if cls_id not in visdrone_to_coco_id: continue # 或记录日志 coco_cls_id visdrone_to_coco_id[cls_id] bbox [float(x), float(y), float(w), float(h)] area float(w * h) ann { id: annotation_id, image_id: image_id, category_id: coco_cls_id, bbox: bbox, area: area, segmentation: [], iscrowd: 0, } coco_data[annotations].append(ann) annotation_id 1 # else: 如果没有标注文件这张图只有image_info没有annotation image_id 1 # 4. 保存为JSON文件 with open(output_json_path, w) as f: json.dump(coco_data, f, indent2) # indent参数使JSON文件更易读 print(fConversion completed!) print(f Total images: {len(coco_data[images])}) print(f Total annotations: {len(coco_data[annotations])}) print(f Saved to: {output_json_path}) if __name__ __main__: # 使用示例 convert_visdrone_to_coco( visdrone_img_dir./VisDrone2019-DET-train/images, visdrone_ann_dir./VisDrone2019-DET-train/annotations, output_json_path./instances_train2017.json )4.2 转换结果的验证与排查生成JSON文件后绝不能直接用于训练。必须进行严格验证。1. 基础完整性检查使用Python加载尝试用json.load()加载文件确保JSON格式正确。检查关键字段确认images、annotations、categories三个列表都存在且非空。检查ID唯一性编写简单脚本检查所有image_id和annotation_id是否唯一。检查关联性随机抽查几个annotation确保其image_id能在images列表中找到category_id能在categories列表中找到。2. 可视化验证强烈推荐这是最直观有效的方法。写一个简单的可视化脚本读取COCO JSON和原图将边界框和类别标签画上去。import json import cv2 import random def visualize_coco_json(json_path, img_dir, num_samples5): with open(json_path, r) as f: data json.load(f) # 建立类别id到名字的映射 cat_id_to_name {cat[id]: cat[name] for cat in data[categories]} # 随机选择几张图片查看 sample_images random.sample(data[images], min(num_samples, len(data[images]))) for img_info in sample_images: img_id img_info[id] img_file img_info[file_name] img_path os.path.join(img_dir, img_file) # 读取图像 img cv2.imread(img_path) if img is None: print(fCould not read image: {img_path}) continue # 找到该图的所有标注 anns [ann for ann in data[annotations] if ann[image_id] img_id] print(fImage: {img_file}, Annotations: {len(anns)}) for ann in anns: bbox ann[bbox] # [x, y, w, h] cat_id ann[category_id] cat_name cat_id_to_name.get(cat_id, fUnknown({cat_id})) # 将浮点数bbox转换为整数像素坐标 x, y, w, h [int(coord) for coord in bbox] # 画矩形和标签 color (0, 255, 0) # 绿色 cv2.rectangle(img, (x, y), (xw, yh), color, 2) cv2.putText(img, cat_name, (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) # 显示图像 cv2.imshow(Visualization, img) key cv2.waitKey(0) # 按任意键查看下一张 if key ord(q): # 按q键退出 break cv2.destroyAllWindows() # 调用函数 visualize_coco_json(./instances_train2017.json, ./VisDrone2019-DET-train/images)通过可视化你可以立即发现以下问题边界框位置是否严重偏移类别标签是否正确是否漏掉了某些标注图像尺寸读取是否正确框是否与图像大小匹配3. 使用COCO API进行格式验证PyCOCOtools是官方的COCO数据集处理工具。安装后pip install pycocotools可以用它来加载你生成的JSON这是一个非常严格的格式检查。from pycocotools.coco import COCO coco COCO(./instances_train2017.json) print(coco.dataset[info]) print(fNumber of images: {len(coco.imgs)}) print(fNumber of annotations: {len(coco.anns)}) print(fNumber of categories: {len(coco.cats)})如果能成功加载而不报错说明你的JSON文件基本符合COCO格式规范。5. 常见问题与实战经验总结在实际操作中你几乎一定会遇到下面这些问题。这里是我总结的“避坑指南”。5.1 坐标系统与边界框溢出问题转换后边界框的一部分画在了图像外面。原因与解决坐标原点确认VisDrone和COCO都使用图像左上角(0,0)作为原点这一点通常一致。但有些数据集如VOC使用(1,1)。确认你的理解无误。边界框越界处理VisDrone的标注框有时可能部分在图像外x0或y0或xw img_width。COCO格式允许浮点数但一个越界的框在训练时可能引发问题。一个稳健的做法是在转换时进行“裁剪”clampx max(0, min(x, img_width - 1)) y max(0, min(y, img_height - 1)) w min(w, img_width - x) h min(h, img_height - y) if w 0 or h 0: continue # 如果裁剪后框无效则丢弃但请注意裁剪会改变框的真实位置。另一种策略是保留原始越界框相信数据集的原始标注。我建议在转换脚本中记录下越界框的数量和位置评估其影响后再决定是否裁剪。5.2 类别映射的歧义与处理问题VisDrone的“pedestrian”1和“people”2在COCO中可能都想映射到“person”。如何处理方案这取决于你的任务目标。方案A细粒度保留所有原始类别如前面示例所示。这样模型可以学习区分行人和人群。方案B合并如果你只关心“人”这个大类可以在映射字典中将1和2都映射到同一个category_id例如1并在categories列表中只定义一个{id: 1, name: person}。务必同步修改categories列表方案C选择性使用只使用你关心的部分类别。例如只检测车辆汽车、巴士、卡车等则在映射时过滤掉行人和人类类别。关键categories列表必须与annotations中实际出现的category_id完全对应。转换后务必检查categories中的每个id是否都在标注中被用到以及标注中用到的每个id是否都在categories中有定义。5.3 图像与标注文件匹配错误问题转换后发现有些图像的标注数量为0或者可视化时框不对应。原因图像文件扩展名不匹配如.jpg vs .JPG。标注TXT文件名与图像文件名不完全一致如123.jpg对应00123.txt。某些图像可能确实没有标注对象空标注。VisDrone的空标注TXT文件可能是空的或者只有一行0,0,0,0,0,0,0,0。解决在脚本中统一处理文件名如使用os.path.splitext去除扩展名后再匹配。在处理前打印一下找不到标注文件的图像名手动检查原因。对于空标注确保你的脚本能正确处理空的TXT文件或全零行不产生错误即可。5.4 性能与内存优化问题当处理数万张图像时脚本可能运行缓慢或内存占用高。优化技巧使用tqdm显示进度如上例所示直观了解进度。避免在内存中累积过大的列表如果数据集极大可以考虑流式处理分批写入JSON或使用ijson等库增量生成。但对于VisDrone的规模约万张一次性处理通常没问题。图像尺寸读取优化使用PIL.Image.open然后img.size比用OpenCV的cv2.imread读取整个图像矩阵要快得多内存占用也小。并行处理对于更大量的数据可以考虑用multiprocessing模块并行处理不同图像文件夹。但要注意写入JSON时的线程安全通常建议并行解析最后再合并结果列表。5.5 与训练框架的对接问题生成的JSON文件在MMDetection或Detectron2中加载失败。排查步骤首先用COCO API验证如上节所述能通过COCO()加载是第一步。检查文件路径训练配置中指定的ann_file路径是否正确是绝对路径还是相对路径检查数据集注册在MMDetection中你需要确保自定义数据集的classes与JSON中categories的name字段顺序一致或通过metainfo指定。一个常见错误是classes列表的顺序与category_id没有正确对应。最好在数据集的配置中通过metainfodict(classes[‘pedestrian‘, ‘people‘, ...])显式指定类别列表确保万无一失。检查标注过滤有些框架在加载时可能会自动过滤area过小如area 1的标注。如果你的area计算有误比如用了整数计算导致为0可能会导致标注被全部过滤。确保area是float且大于0。转换一份VisDrone-COCO格式的JSON文件远不止是跑通一个脚本。它要求你对两种数据格式的细节有深刻理解对可能出现的边界情况有充分的预案并且养成用可视化等手段严格验证结果的习惯。这份生成的JSON文件将是连接高质量无人机数据和强大检测模型的可靠桥梁。希望这份详尽的拆解能让你在搭建这座桥时每一步都走得扎实、稳健。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表