BIT-Vehicle数据集详解:YOLO/VOC/COCO格式转换实战
搞交通监控、自动驾驶或者智慧城市项目的朋友对BIT-Vehicle这个车辆车型检测数据集肯定不陌生。做目标检测训练数据格式是绕不过去的一道坎网上能下载到的公开版本基本都是原始标注直接扔给YOLO或者跑COCO的脚本根本读不了每换一个框架就要重新写一遍解析逻辑非常耽误时间。这篇文章就把BIT-Vehicle数据集的完整内容、下载方式、目录结构说清楚再给你一套可以直接跑的Python脚本把原始标注转换成YOLO训练格式、VOC格式和COCO格式顺便把每个环节里我踩过的坑也整理出来。我自己前前后后在这个数据集上折腾过好几轮从最早的VOC格式手写XML到后来用Ultralytics YOLO训练自己的车辆检测模型中间踩了不少格式转换的坑。这篇文章既适合刚接触目标检测、想拿开源数据练手的新手也适合已经在做项目、需要快速把BIT-Vehicle接入现有训练流程的工程师。不管你是准备做车辆计数、车型识别还是只用它来验证自己的检测算法这篇文章都能帮你省下两三天折腾标注格式的时间。1. BIT-Vehicle数据集到底是什么适合做什么任务1.1 数据来源与基础信息BIT-Vehicle数据集由北京理工大学发布全称是BIT Vehicle Dataset采集自真实城市交通监控场景。整套数据包含9600张车辆图像图像分辨率统一为1600×1200覆盖白天、夜晚、逆光、阴天等多种光线条件车与车之间也存在不同程度的重叠和遮挡。和很多实验室环境下拍摄的数据集不同BIT-Vehicle里的车辆是真实出现在路口、路段中的车辆姿态、光照、拍摄角度都不一样拿它训练出来的模型更接近实际落地场景。图像中的车辆一共有6个类别但不同资料里的类别命名略有差异我拿到过的标注文件里出现过bus、truck、van、sedan、suv、microbus这组命名也有资料写作客车、卡车、面包车、轿车、SUV、微型客车。这里提醒一下写转换脚本之前先打开标注文件看一眼实际出现的类别名不要直接拿网上的映射表套不同发布版本的标注内容存在差别。1.2 这个数据集的核心价值与适用场景BIT-Vehicle在车辆检测领域使用频率很高核心原因有三个。第一是量大且标注完整9600张图像全部带有车辆级BBox标注用作训练集规模合适。第二是场景真实图像来自实际道路监控对模型泛化能力的要求更高训练出来的结果更有参考意义。第三是分辨率高1600×1200的原始图像保留了大量车辆细节既可以做检测任务也可以裁剪出来做车辆ReID或者车型分类。适用场景常见的有这么几个城市交通视频中的车辆检测与计数道路交通监控场景下的车型分类作为预训练数据先在这个数据集上训练车辆检测模型再迁移到自有业务场景对比验证不同检测算法在监控视角下的表现有一点要说清楚BIT-Vehicle的标注是BBox矩形框没有实例分割掩码也没有关键点标注。如果你要做的是实例分割或者细粒度车辆部件检测这个数据集帮不上忙需要找其他带分割标注的数据集。但做2D车辆检测它的性价比非常高。2. 从申请到下载BIT-Vehicle数据集获取全流程2.1 官方下载路径与实际操作BIT-Vehicle的官方下载方式和很多学术数据集类似不是直接甩一个链接出来而是需要填申请表确认用途后由数据集维护方把下载地址发到邮箱。以我实际操作为例整个过程大概分三步。第一步找到北京理工大学复杂网络智能实验室发布的数据集主页页面上有数据集的介绍、论文引用信息和下载说明。第二步下载并填写申请表一般包括姓名、单位、邮箱、用途说明这几项用途写“学术研究”或者“算法验证”都可以说明尽量具体一些比如“用于车辆检测算法研究”比一个模糊的“学习用”通过率高很多。第三步把填好的申请表发到页面给出的邮箱等对方回复下载链接。通常一两天内能收到回复。如果着急用或者等不到回复也可以试试国内技术社区上的一些镜像分享。我自己在CSDN和GitHub上都见过热心网友搬运的网盘版本。不过这里要给个提醒镜像版本可能经过二次处理包括改过目录结构、重命名过文件甚至部分图像缺失。如果你要做严谨的学术实验尽量走官方渠道如果是自己学习练手镜像版本也能用但一定要检查一下图片数量和标注文件是否对应。2.2 拿到压缩包后的目录结构官方发布版本解压后目录结构大致是这样的BIT-Vehicle/ ├── 图片文件夹/ │ ├── 00001.jpg │ ├── 00002.jpg │ ├── ... ├── VehicleInfo/ │ ├── 00001.txt (或 .mat) │ ├── 00002.txt (或 .mat) │ ├── ... └── readme.txt (或说明文档)图片文件的命名通常是连续的纯数字编号对应关系靠文件名一一匹配。这个设计看起来简单但实际操作时会遇到一个隐蔽的坑有些图片有标注有些没有配置文件不对齐就会导致解析中断或者label错位。不同发布版本里标注文件的格式不太统一我见过两种。第一种是.mat格式Matlab数据文件需要用scipy.io.loadmat读取里面通常存了一个结构体记录了每辆车在图像中的位置信息。第二种是纯文本TXT格式每一行代表一辆车行内用空格或Tab分隔类别和坐标。TXT格式读起来直观下面讲格式解析也围绕这个来展开。3. 核心前置解析BIT-Vehicle原始标注搞懂每一项数值的坐标系3.1 从TXT标注文件里读出BBox坐标拿到一个标注文件先别急着写转换脚本先把内容打开看一眼。我见过的一个典型TXT版本每一行是这样的bus 480 620 560 700 sedan 900 580 1040 690每一行从左到右是类别名、BBox左上角x坐标、BBox左上角y坐标、BBox右下角x坐标、BBox右下角y坐标。也就是说BBox的形式是[xmin, ymin, xmax, ymax]坐标原点在图像的左上角x轴向右y轴向下。这和VOC、YOLO的坐标系是同一个方向所以转换时只需要做坐标形式的变换不需要做镜像翻转。如果你拿到的是.mat版本读取方式稍微绕一点。用scipy.io.loadmat把文件读进来之后数据通常存放在一个以VehicleInfo命名的结构里里面每个元素包含一个4维或者5维的向量前几维是坐标信息。具体字段名每个版本不一样稳妥的做法是用Python打印出.dtype和.names看到了实际结构再写解析逻辑。3.2 类别ID映射的逻辑以及为什么会踩坑YOLO和COCO的标注文件不直接使用类别名字符串而是使用整数ID。因此转换时需要一个类别到ID的映射表。比如常见的映射可以设置成bus- 0truck- 1van- 2sedan- 3suv- 4microbus- 5这个映射看起来简单但有个陷阱很容易被忽视。YOLO训练时类别ID是从0开始连续编号的如果你的标注文件里类别数目不是6个或者多了一个没见过的类别名直接用固定映射表会强行把未知类别映射成错误ID或者直接抛KeyError。所以解析脚本里一定要先对标注文件里的全部类别做一次去重统计打印出来确认和你的映射表一致再继续往下走。我在一次转换中就遇到过TXT版本里出现了mini_truck这种额外类别当时没检查训练时类别全都乱了排查了很久才发现是转换时丢了类别。3.3 解析脚本先把标注统一读成Python字典为了方便后续三种格式的转换我习惯先把原始标注解析成一个统一的中间结构一个字典键是图片文件名值是该图像中所有目标的列表每个目标是一个字典包含category和bbox两个字段bbox统一存成左上角加宽高的形式[x, y, w, h]。这样后面无论转YOLO、VOC还是COCO都是从同一个数据源取数代码逻辑清晰也不容易出错。import os import glob def parse_txt_annotation(txt_path, image_width1600, image_height1200): 解析单张图片的TXT标注文件 返回[{category: sedan, bbox: [x, y, w, h]}, ...] bbox格式统一为左上角坐标 宽高 objects [] with open(txt_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.split() if len(parts) 5: continue category parts[0] xmin float(parts[1]) ymin float(parts[2]) xmax float(parts[3]) ymax float(parts[4]) # 做一次坐标合法性保护 xmin max(0, min(xmin, image_width)) xmax max(0, min(xmax, image_width)) ymin max(0, min(ymin, image_height)) ymax max(0, min(ymax, image_height)) x xmin y ymin w xmax - xmin h ymax - ymin objects.append({category: category, bbox: [x, y, w, h]}) return objects坐标合法性保护这一步非常重要。监控图像里偶尔会出现标注框超出图像边界的情况不加保护的话后续YOLO归一化坐标会出现大于1的值轻则训练时警告重则直接导致loss异常。4. YOLO格式转换实战目录划分、归一化坐标计算与TXT生成4.1 YOLO训练数据应该怎么组织无论是用Ultralytics YOLOv8/YOLOv11还是之前的YOLOv5训练数据的目录结构基本都长这样dataset/ ├── images/ │ ├── train/ │ │ ├── 00001.jpg │ │ ├── ... │ └── val/ │ ├── 00100.jpg │ ├── ... ├── labels/ │ ├── train/ │ │ ├── 00001.txt │ │ ├── ... │ └── val/ │ ├── 00100.txt │ ├── ... └── dataset.yamlimages和labels的文件夹名字要严格对应而且同一张图片的jpg和txt文件名必须完全一致。转换成YOLO格式的核心工作只有三件事划分训练集和验证集、计算归一化坐标、写TXT文件。坐标计算方法如下假设原始BBox是[x, y, w, h]左上角坐标加宽高图像宽度为W高度为H那么YOLO需要的归一化坐标是x_center (x w / 2) / Wy_center (y h / 2) / Hwidth w / Wheight h / H归一化之后所有值都在0到1之间和图像实际尺寸无关。这也是YOLO能灵活适配不同分辨率输入的原因——它关心的是目标相对整张图的比例位置。4.2 训练验证集划分的比例与细节划分训练集和验证集时比例通常取8:2或者9:1。BIT-Vehicle一共9600张图按8:2划分就是7680张训练、1920张验证。这里有一个实际建议划分时使用随机种子固定随机数否则每次运行转换脚本生成的划分结果都不一样同一个实验前后对不上后期复现就麻烦了。还有一个值得注意的点BIT-Vehicle的图像文件全部在一个文件夹里如果直接按图集切片随机划分很容易出现连续编号的图片全在训练集或全在验证集的情况。由于相邻编号的图片大概率是同一时间、同一地点拍摄的序列前后内容非常相似这样的划分会导致验证集和训练集分布重叠度过高评估结果虚高。更合理的方式是先打乱图片列表再做划分保证验证集里的图片在场景分布上更有代表性。4.3 完整YOLO格式转换脚本下面这个脚本是完整可运行的不依赖任何第三方库只有os、random、glob这些标准库。运行之前把image_dir、label_dir、output_dir改成自己机器上的实际路径就行。import os import glob import random from collections import Counter CATEGORY_MAPPING { bus: 0, truck: 1, van: 2, sedan: 3, suv: 4, microbus: 5, } def parse_txt_annotation(txt_path, image_width1600, image_height1200): objects [] with open(txt_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.split() if len(parts) 5: continue category parts[0] if category not in CATEGORY_MAPPING: print(f[警告] 未知类别: {category} 文件: {txt_path}) continue xmin float(parts[1]) ymin float(parts[2]) xmax float(parts[3]) ymax float(parts[4]) xmin max(0, min(xmin, image_width)) xmax max(0, min(xmax, image_width)) ymin max(0, min(ymin, image_height)) ymax max(0, min(ymax, image_height)) objects.append({ category: category, bbox: [xmin, ymin, xmax - xmin, ymax - ymin] }) return objects def convert_to_yolo(image_dir, label_dir, output_dir, val_ratio0.2, seed42): random.seed(seed) images sorted(glob.glob(os.path.join(image_dir, *.jpg))) random.shuffle(images) val_count int(len(images) * val_ratio) val_images set(os.path.basename(p) for p in images[:val_count]) os.makedirs(os.path.join(output_dir, images, train), exist_okTrue) os.makedirs(os.path.join(output_dir, images, val), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, train), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, val), exist_okTrue) for img_path in images: img_name os.path.basename(img_path) label_path os.path.join(label_dir, os.path.splitext(img_name)[0] .txt) if not os.path.exists(label_path): print(f[跳过] 未找到标注文件: {label_path}) continue objects parse_txt_annotation(label_path) if not objects: print(f[跳过] 标注为空: {img_path}) continue sub_dir val if img_name in val_images else train dest_img os.path.join(output_dir, images, sub_dir, img_name) # 这里根据实际需要选择移动还是复制推荐复制保留原始数据 os.system(fcp {img_path} {dest_img}) lines [] for obj in objects: cat_id CATEGORY_MAPPING[obj[category]] x, y, w, h obj[bbox] # 计算YOLO归一化坐标 x_center (x w / 2) / 1600.0 y_center (y h / 2) / 1200.0 w_norm w / 1600.0 h_norm h / 1200.0 lines.append(f{cat_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) dest_label os.path.join(output_dir, labels, sub_dir, os.path.splitext(img_name)[0] .txt) with open(dest_label, w) as f: f.write(\n.join(lines)) # 统计类别数量确认划分合理性 print(转换完成验证集图片数量:, len([p for p in images if os.path.basename(p) in val_images]))脚本运行完output_dir下面就是标准的YOLO数据格式。再写一个dataset.yaml内容大致如下把path改成output_dir的路径即可path: /your/path/to/dataset train: images/train val: images/val nc: 6 names: [bus, truck, van, sedan, suv, microbus]有个细节容易被忽略上面cp命令用的是系统命令复制图片速度比Python的shutil.copy慢。数据量小没感觉数量大了建议换成shutil.copy2。另外整个流程只转换了有标注的图片没有标注的图片不会出现在最终数据集里这一点要在心里有数因为原始数据集的图片数量和你最终文件夹里的图片数量很可能对不上。5. VOC与COCO格式转换两种主流格式的逐项拆解5.1 VOC格式XML文件结构与生成方法Pascal VOC格式是很多老检测框架如Faster R-CNN、SSD使用的标准格式核心是一个XML文件夹每个XML对应一张图片记录图片路径、尺寸和所有目标框的位置与类别。生成VOC格式时需要在读取原始标注后再用ElementTree或字符串拼接技术生成XML。VOC XML的核心结构是这样的annotation folderBIT-Vehicle/folder filename00001.jpg/filename size width1600/width height1200/height depth3/depth /size object namesedan/name bndbox xmin900/xmin ymin580/ymin xmax1040/xmax ymax690/ymax /bndbox /object /annotation生成XML时我踩过一个坑filename标签只写文件名不要写完整路径。很多框架比如老版本的VOC工具包会拿着这个字段去拼图片路径如果存了绝对路径或者Windows风格的反斜杠路径在Linux训练机上一定会出问题。为了保险可以顺便把path字段也写进去但filename保持纯文件名。下面是转换脚本的简化版本import os import glob import xml.etree.ElementTree as ET def write_voc_xml(img_path, objects, xml_path): img_name os.path.basename(img_path) root ET.Element(annotation) folder ET.SubElement(root, folder) folder.text BIT-Vehicle filename ET.SubElement(root, filename) filename.text img_name path ET.SubElement(root, path) path.text img_path size ET.SubElement(root, size) width ET.SubElement(size, width) width.text 1600 height ET.SubElement(size, height) height.text 1200 depth ET.SubElement(size, depth) depth.text 3 for obj in objects: object_elem ET.SubElement(root, object) name ET.SubElement(object_elem, name) name.text obj[category] bndbox ET.SubElement(object_elem, bndbox) xmin ET.SubElement(bndbox, xmin) xmin.text str(int(obj[bbox][0])) ymin ET.SubElement(bndbox, ymin) ymin.text str(int(obj[bbox][1])) xmax ET.SubElement(bndbox, xmax) xmax.text str(int(obj[bbox][0] obj[bbox][2])) ymax ET.SubElement(bndbox, ymax) ymax.text str(int(obj[bbox][1] obj[bbox][3])) ET.indent(root) tree ET.ElementTree(root) tree.write(xml_path, encodingutf-8, xml_declarationTrue)5.2 COCO格式JSON结构与三张表的关系COCO格式和VOC、YOLO都不一样它不是每个文件对应一个标注文件而是把所有标注汇总到一个大的JSON文件里。一个标准的COCO JSON里包含三个主要字段images一个列表每个元素描述一张图片的id、file_name、width、heightannotations一个列表每个元素描述一个目标框的信息包括id、image_id、category_id、bbox、area、iscrowdcategories一个列表每个元素是类别ID对应的类别名和超类别关键点来了COCO的bbox格式是[x, y, width, height]和YOLO不同它不做归一化保留的是原始像素坐标。但这一点也和VOC不同VOC用的是[xmin, ymin, xmax, ymax]两个角点坐标COCO用的是左上角坐标加宽高。转换时很多人习惯从VOC逻辑直接搬过来结果就是把xmax、ymax直接填进了COCO的bbox里导致所有框的位置和大小都错了。area字段在检测任务里通常直接算成width * heightiscrowd一般写0。如果后续要做COCO评估指标area很重要因为COCO的AP是按小目标、中目标、大目标分桶计算的area算错会直接影响评估结果的分类。5.3 完整COCO JSON生成脚本import os import glob import json from collections import Counter CATEGORY_MAPPING { bus: 0, truck: 1, van: 2, sedan: 3, suv: 4, microbus: 5, } def parse_txt_annotation(txt_path, image_width1600, image_height1200): objects [] with open(txt_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue parts line.split() if len(parts) 5: continue category parts[0] if category not in CATEGORY_MAPPING: print(f[警告] 未知类别: {category} 文件: {txt_path}) continue xmin float(parts[1]) ymin float(parts[2]) xmax float(parts[3]) ymax float(parts[4]) xmin max(0, min(xmin, image_width)) xmax max(0, min(xmax, image_width)) ymin max(0, min(ymin, image_height)) ymax max(0, min(ymax, image_height)) objects.append({ category: category, bbox: [xmin, ymin, xmax - xmin, ymax - ymin] }) return objects def convert_to_coco(image_dir, label_dir, output_json): images sorted(glob.glob(os.path.join(image_dir, *.jpg))) coco_images [] coco_annotations [] coco_categories [] for cat_id, cat_name in CATEGORY_MAPPING.items(): coco_categories.append({ id: cat_id, name: cat_name, supercategory: vehicle }) ann_id 1 for image_id, img_path in enumerate(images, start1): img_name os.path.basename(img_path) coco_images.append({ id: image_id, file_name: img_name, width: 1600, height: 1200 }) label_path os.path.join(label_dir, os.path.splitext(img_name)[0] .txt) if not os.path.exists(label_path): continue objects parse_txt_annotation(label_path) for obj in objects: x, y, w, h obj[bbox] coco_annotations.append({ id: ann_id, image_id: image_id, category_id: CATEGORY_MAPPING[obj[category]], bbox: [x, y, w, h], area: w * h, iscrowd: 0 }) ann_id 1 coco_data { images: coco_images, annotations: coco_annotations, categories: coco_categories } os.makedirs(os.path.dirname(output_json), exist_okTrue) with open(output_json, w, encodingutf-8) as f: json.dump(coco_data, f, indent2) print(fCOCO JSON已生成: {output_json}) print(f图片数量: {len(coco_images)}, 标注数量: {len(coco_annotations)})生成JSON之后建议再用Python读一遍打印几行样本做目检确认bbox的数值量级是对的。比如1600×1200的图像里车辆框的x坐标通常在200到1400之间宽度通常是几十到几百像素如果发现xwidth超过1600或者y超出1200说明原始标注里有可能存在超出边界的框需要回溯原始解析逻辑。6. 转换过程中的常见问题与排查实录6.1 文件名匹配失败导致的标注缺失最常见的问题是标签文件夹和图片文件夹里的文件名对不上。BIT-Vehicle官方版本的命名规则比较规整但某些镜像版本在搬运过程中给文件名加了前缀或改了后缀比如把00001.jpg变成了img_00001.jpg而标注文件还是00001.txt直接用os.path.splitext(img_name)[0]去拼标注路径就会全部失败。排查方法是打印几张图片名对应的标注路径肉眼确认一下规律。一旦发现有前缀差异写脚本时先统一做一次格式化比如去掉img_前缀或者把文件名统一补零到5位。这个逻辑虽然简单但放在转换脚本的最前面会省掉后面大量的调试时间。6.2 未知类别导致的标注丢失我前面提到过在某个镜像版本里遇到过TXT文件中出现mini_truck这个类别而固定映射表里根本没有这个键。如果脚本里直接使用CATEGORY_MAPPING[category]而不做判断Python会直接抛KeyError。所以解析函数里一定要加一个跳过加打印的逻辑把所有未知类别打印出来。拿到了真实的类别列表再决定是扩展映射表还是忽略这些目标。这里还建议大家转换后统计一下每个类别的目标数量方法很简单遍历所有生成的label文件统计每一行的第一个整数。目标数量直接反映数据分布情况比如BIT-Vehicle中轿车数量非常多卡车、客车相对较少训练YOLO时如果发现某些小类别的mAP明显偏低先回来看看是不是训练样本本身就不够。6.3 归一化坐标出现大于1的值YOLO的归一化坐标理论上必须在0到1之间但原始标注里一旦出现超出图像边界的框计算出的归一化坐标很可能超过1。很多训练框架在数据加载阶段会做严格检查超界的坐标直接跳过或者报错。解决办法是在解析阶段就对坐标做裁剪也就是我在前面代码里写的max(0, min(...))操作。这个操作看起来简单但确实是从实际训练报错里逼出来的教训一批数据里只要有一张图的标注超界整个训练都可能崩掉而问题往往出在几张不起眼的图片上。6.4 训练时类别ID对不上导致指标异常还有一次我帮朋友排查训练问题发现loss能正常下降但验证集mAP几乎是0。最后定位到的问题是转换脚本里类别ID从1开始编号而YOLO的类别文件里ID从0开始导致所有类别错了一位。YOLO、COCO这类格式要求类别ID从0开始连续编号这个从0开始的要求非常严格少一个类别或者多一个类别都不行。转换完成后有一个很简单的验证技巧随机挑3到5张图片和对应的label文件手工画框检查。不需要写复杂脚本用OpenCV在图片上把bbox画出来再和原始标注对比一下位置这样能一次性发现坐标错位、归一化错误、ID偏移等多类问题。6.5 转换问题速查表现象可能原因解决思路label文件为空或缺失文件名匹配失败、原始标注里没有目标检查文件名前缀、后缀使用统一的格式化函数训练时报KeyError原始标注出现未知类别先用Counter统计全部类别再去调整映射表归一化坐标大于1原始BBox超出图像边界在解析阶段做坐标裁剪、强制限定范围类别ID错位映射表起始ID不是0确认类别ID从0开始连续编号损失正常但mAP为0标签和图像内容不匹配、验证集图片重复手工画框抽查检查划分逻辑是否打乱COCO面积异常area计算错误、bbox格式写错复查bbox是否[x,y,w,h]格式6.6 一个小技巧先转换一个小样本集验证全流程在我自己的流程里正式转换整个数据集之前一定会先建一个只包含20张图片的小样本目录跑一遍完整转换流程然后用Ultralytics YOLO训练1到2个epoch确保整个链路都通了再对全部9600张图执行转换。这样做有几个好处小样本训练速度快几分钟就能看到结果如果格式有隐性错误在20张图上更容易暴露最关键的是训练能帮你验证label路径和图片路径的对应关系是否正确。很多人在大转换完成后才发现问题比如训练一跑报一堆路径错误、类别缺失然后回来重新生成全部数据白等好几个小时。先小样本验证再全量转换这个顺序对我来说已经是从多次踩坑中总结出的标准流程了。另外再分享一个跟数据相关的经验BIT-Vehicle图片分辨率相对较高且很多车辆在画面中占比不大直接用640×640输入训练小目标的检测效果会打折扣。我在实际项目中通常先用这个数据集训练一个基础模型再配合SAHI之类的切片推理工具做推理针对监控场景的小车辆效果提升非常明显。如果你用的是YOLOv8/YOLOv11这类框架训练时把imgsz调到960甚至1280也可以显存够的话大分辨率输入对这类监控场景的收益很直接。数据格式转换这种事情看起来只是写几个脚本的体力活但每次都容易在一些小细节上翻车。能用标准格式尽量别自己在中间环节创造新格式对后面接任何训练框架都会省心很多。希望这篇基于BIT-Vehicle数据集的转换实战笔记能帮你少走一些弯路。