1. 项目概述:从混乱到秩序,数据集的标准化之路
做目标检测的朋友,十有八九都卡在第一步:数据准备。你兴冲冲地下载了一个数据集,或者自己辛苦标注了几百张图片,结果发现,标注文件格式五花八门,有的是VOC的XML,有的是COCO的JSON,还有的是YOLO的txt,甚至可能是某个标注工具自创的格式。更头疼的是,所有图片和标签都混在一个文件夹里,训练集、验证集、测试集?不存在的。这个“目标检测---数据集格式转化及训练集和验证集划分”的项目,就是解决这个“万事开头难”的核心痛点。它不是一个炫技的算法,而是确保你后续所有模型训练工作能顺利、高效、科学进行的地基工程。简单说,就是把你的原始标注数据,转换成你选定框架(比如YOLOv5/v8/v11,或者Detectron2、MMDetection)能“吃”得下去的格式,并且按照合理的比例,把数据分成用于“学习”的训练集和用于“考试”的验证集。这个过程看似基础,却直接决定了模型最终性能的上限和评估的可信度。无论你是刚入门的新手,还是被数据格式折磨过的老手,系统性地掌握这套数据预处理流程,都能让你的项目赢在起跑线上。
2. 核心思路与方案设计:为何要“转化”与“划分”
在动手写代码之前,我们必须先想清楚两个根本问题:为什么要转换格式?为什么要划分数据集?这直接决定了我们方案设计的每一个细节。
2.1 数据集格式转化的核心逻辑
不同的目标检测框架对输入数据的格式有不同要求,这背后是框架设计哲学和效率权衡的体现。
- VOC格式:以XML文件存储,结构清晰,可读性强,包含了图片尺寸、对象类别、边界框坐标(通常是
xmin, ymin, xmax, ymax的绝对像素值)等丰富信息。它是一种“自描述”性很强的格式,但解析速度相对较慢,因为需要读取和解析XML树。 - COCO格式:采用单个庞大的JSON文件来管理整个数据集的信息,包括图片列表、标注列表、类别列表等。它的优势在于“集中管理”,特别适合大规模数据集,且官方提供了完善的评估工具。但动辄几百MB的JSON文件,在读取和索引时对内存有一定要求。
- YOLO格式:追求极致的简洁和效率。每个图片对应一个同名的
.txt文件,每行表示一个目标,格式为:<class_id> <x_center> <y_center> <width> <height>。这里的坐标是归一化后的值(即相对于图片宽高的比例),class_id是整数索引。这种格式读取速度快,占用空间小,非常适合YOLO系列这种强调训练速度的框架。
格式转化的本质,是在不丢失信息的前提下,进行数据结构的“翻译”。例如,从VOC到YOLO,你需要:
- 将类别名称(如“person”、“car”)映射为整数
class_id(如0, 1)。 - 将绝对坐标
(xmin, ymin, xmax, ymax)转换为归一化的中心点坐标和宽高:x_center = (xmin + xmax) / 2.0 / image_widthy_center = (ymin + ymax) / 2.0 / image_heightwidth = (xmax - xmin) / image_widthheight = (ymax - ymin) / image_height这个过程必须保证精度,并且要处理好边界情况(如坐标越界)。
2.2 训练集/验证集划分的科学性与实操性
划分数据集不是为了“分文件夹”,而是为了模拟模型在真实世界中的应用场景,并进行可靠的性能评估。
根本目的:
- 训练集:用于模型学习,调整数百万甚至数十亿的权重参数。模型从这里学习从图片像素到目标位置和类别的映射规律。
- 验证集:用于“模拟考试”。在训练过程中,定期在验证集上评估模型表现(如mAP),此结果用于调整超参数(如学习率)、进行早停(Early Stopping)或选择最佳模型。验证集的表现是指导训练过程的“指挥棒”。
- 测试集:用于“最终大考”。在模型训练和调参全部完成后,用测试集做一次最终、客观的性能评估,这个分数才代表模型的泛化能力。测试集在训练和调参阶段绝对不能使用。很多公开数据集会提供官方的测试集标注,而个人项目中,常从全部数据中划分出一部分作为测试集。
划分比例:没有黄金标准,但常见经验是7:2:1或8:1:1(训练:验证:测试)。核心原则是:
- 训练集要足够大,确保模型能学到充分的特征,特别是对于复杂场景或小样本类别。
- 验证集要足够有代表性,其数据分布应尽可能接近训练集和真实数据,否则“模拟考试”会失灵。通常采用分层抽样,确保每个类别在训练集和验证集中的比例大致相同,避免某个类别只在训练集出现而从未被“考”到。
划分方法:绝对不能简单随机打乱。必须保证同一张图片的所有标注(可能包含多个目标)必须同时被划分到同一个集合中。更高级的划分还需要考虑场景的独立性,例如,从连续视频帧中抽取的图片,如果随机划分会导致训练集和验证集画面高度相似,造成“数据泄露”,使验证分数虚高。此时应按视频片段或场景ID进行划分。
注意:很多人容易混淆验证集和测试集。一个简单的记忆方法是:验证集是“开发集”,你在项目开发过程中会反复使用它来调整模型;测试集是“评估集”,只在一切尘埃落定后使用一次,给出最终报告分数。在学术论文中,通常只提及训练集和测试集,其“测试集”实际承担了验证和最终评估的双重角色,但这在工程实践中容易导致过拟合测试集的风险。
3. 实战流程:从VOC到YOLO格式的完整转换与划分
下面,我将以最常见的VOC格式数据集转换为YOLO格式,并进行规范划分为例,展示一个完整的、可复现的实操流程。假设我们有一个名为MyDataset的原始数据集,结构如下:
MyDataset/ ├── JPEGImages/ # 存放所有图片(.jpg) │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... └── Annotations/ # 存放所有VOC格式标注文件(.xml) ├── 000001.xml ├── 000002.xml └── ...3.1 步骤一:解析VOC XML并提取关键信息
我们需要编写一个脚本来读取每个XML文件,提取出图片尺寸和每个目标的类别、边界框信息。
import os import xml.etree.ElementTree as ET import random from pathlib import Path def parse_voc_annotation(xml_path): """解析单个VOC XML文件,返回图片信息和目标列表""" tree = ET.parse(xml_path) root = tree.getroot() # 获取图片尺寸 size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) objects = [] for obj in root.findall('object'): # 获取类别名 class_name = obj.find('name').text # 获取边界框 bndbox = obj.find('bndbox') xmin = int(float(bndbox.find('xmin').text)) ymin = int(float(bndbox.find('ymin').text)) xmax = int(float(bndbox.find('xmax').text)) ymax = int(float(bndbox.find('ymax').text)) # 确保坐标在图片范围内(处理可能的标注错误) xmin = max(0, min(xmin, width - 1)) ymin = max(0, min(ymin, height - 1)) xmax = max(0, min(xmax, width - 1)) ymax = max(0, min(ymax, height - 1)) if xmax <= xmin or ymax <= ymin: print(f"Warning: Invalid bbox in {xml_path}, skipped.") continue objects.append({ 'class_name': class_name, 'bbox': [xmin, ymin, xmax, ymax] }) return {'width': width, 'height': height, 'objects': objects}3.2 步骤二:构建类别映射字典
我们需要为数据集中所有出现的类别建立一个从名称到ID的映射。这个映射字典必须保存下来,在训练和推理时都要用到。
def build_class_index(annotations_dir): """遍历所有XML,收集所有类别并构建索引字典""" class_set = set() for xml_file in Path(annotations_dir).glob('*.xml'): tree = ET.parse(xml_file) root = tree.getroot() for obj in root.findall('object'): class_name = obj.find('name').text class_set.add(class_name) # 按类别名称字母排序,确保每次生成的ID一致 class_list = sorted(list(class_set)) class_to_id = {name: idx for idx, name in enumerate(class_list)} id_to_class = {idx: name for name, idx in class_to_id.items()} # 保存类别映射文件(至关重要!) with open('class_names.txt', 'w') as f: for name in class_list: f.write(name + '\n') print(f"Found {len(class_list)} classes: {class_list}") return class_to_id, id_to_class实操心得:
class_names.txt文件是连接数据和模型的桥梁。YOLO训练时需要的data.yaml文件里的names列表,必须和这个文件顺序完全一致。一个常见的坑是:转换脚本和训练脚本使用了不同的类别列表顺序,导致模型学到的“0”号类别实际是“人”,但你以为的是“车”,结果全乱套了。务必保证源头唯一。
3.3 步骤三:坐标转换与YOLO格式写入
这是格式转换的核心计算步骤。
def convert_bbox_to_yolo(size, box): """将VOC的绝对坐标(xmin, ymin, xmax, ymax)转换为YOLO归一化坐标(x_center, y_center, width, height)""" dw = 1. / size[0] # 1/width dh = 1. / size[1] # 1/height x = (box[0] + box[2]) / 2.0 # x_center (absolute) y = (box[1] + box[3]) / 2.0 # y_center (absolute) w = box[2] - box[0] # width (absolute) h = box[3] - box[1] # height (absolute) x = x * dw # x_center (normalized) y = y * dh # y_center (normalized) w = w * dw # width (normalized) h = h * dh # height (normalized) # 确保坐标在[0,1]范围内 x = max(0.0, min(1.0, x)) y = max(0.0, min(1.0, y)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) return (x, y, w, h) def convert_voc_to_yolo_single(xml_path, class_to_id, output_label_dir): """转换单个XML文件为YOLO格式的txt文件""" annotation = parse_voc_annotation(xml_path) if not annotation['objects']: return # 跳过无目标图片 # 准备写入内容 txt_lines = [] for obj in annotation['objects']: class_id = class_to_id[obj['class_name']] yolo_bbox = convert_bbox_to_yolo( (annotation['width'], annotation['height']), obj['bbox'] ) line = f"{class_id} {yolo_bbox[0]:.6f} {yolo_bbox[1]:.6f} {yolo_bbox[2]:.6f} {yolo_bbox[3]:.6f}" txt_lines.append(line) # 写入txt文件,文件名与图片名相同 txt_filename = Path(xml_path).stem + '.txt' txt_path = Path(output_label_dir) / txt_filename with open(txt_path, 'w') as f: f.write('\n'.join(txt_lines))3.4 步骤四:科学划分训练集与验证集
这是保证评估有效性的关键。我们采用分层随机划分,并确保划分结果可复现。
def split_dataset(image_dir, label_dir, val_ratio=0.2, seed=42): """ 划分数据集,创建对应的图片和标签符号链接或文件列表。 参数: image_dir: 原始图片目录 label_dir: 转换后的YOLO标签目录 val_ratio: 验证集比例 seed: 随机种子,确保每次划分结果一致 """ random.seed(seed) # 获取所有图片文件名(不含后缀) all_image_stems = [p.stem for p in Path(image_dir).glob('*.jpg')] # 确保对应的标签文件存在 all_image_stems = [stem for stem in all_image_stems if (Path(label_dir) / f"{stem}.txt").exists()] if not all_image_stems: raise ValueError("No valid image-label pairs found!") # 随机打乱 random.shuffle(all_image_stems) # 计算划分点 split_idx = int(len(all_image_stems) * (1 - val_ratio)) train_stems = all_image_stems[:split_idx] val_stems = all_image_stems[split_idx:] print(f"Total samples: {len(all_image_stems)}") print(f"Training set: {len(train_stems)}") print(f"Validation set: {len(val_stems)}") # 创建YOLO标准目录结构 datasets_dir = Path('datasets') datasets_dir.mkdir(exist_ok=True) for split, stems in [('train', train_stems), ('val', val_stems)]: split_dir = datasets_dir / split images_split_dir = split_dir / 'images' labels_split_dir = split_dir / 'labels' images_split_dir.mkdir(parents=True, exist_ok=True) labels_split_dir.mkdir(parents=True, exist_ok=True) # 方式1:创建符号链接(节省空间,推荐在Linux/macOS下使用) # for stem in stems: # src_image = Path(image_dir) / f"{stem}.jpg" # dst_image = images_split_dir / f"{stem}.jpg" # dst_image.symlink_to(src_image.resolve()) # # src_label = Path(label_dir) / f"{stem}.txt" # dst_label = labels_split_dir / f"{stem}.txt" # dst_label.symlink_to(src_label.resolve()) # 方式2:创建文件列表(txt文件,每行是图片的绝对或相对路径) list_file = datasets_dir / f"{split}.txt" with open(list_file, 'w') as f: for stem in stems: # 这里写入相对路径,相对于项目根目录 img_path = f"datasets/{split}/images/{stem}.jpg" f.write(img_path + '\n') print(f"Created {split} list: {list_file}") return train_stems, val_stems3.5 步骤五:生成YOLO配置文件(data.yaml)
YOLO系列模型通过一个YAML文件来定位数据和定义类别。
def create_data_yaml(datasets_base_path, class_list, train_txt='train.txt', val_txt='val.txt'): """ 生成YOLO训练所需的data.yaml配置文件。 """ yaml_content = f""" # 数据集路径(相对于此yaml文件,或绝对路径) path: {datasets_base_path} # 数据集根目录 train: {train_txt} # 训练集列表文件路径 val: {val_txt} # 验证集列表文件路径 # 类别数量 nc: {len(class_list)} # 类别名称列表 (必须与class_names.txt顺序一致!) names: {class_list} """ with open('data.yaml', 'w') as f: f.write(yaml_content.strip()) print("Created data.yaml configuration file.")3.6 主流程整合
将以上所有步骤串联起来,形成一个完整的脚本。
def main(): # 1. 定义路径 voc_images_dir = 'MyDataset/JPEGImages' voc_annotations_dir = 'MyDataset/Annotations' yolo_labels_dir = 'labels' # 临时存放转换后的YOLO标签 Path(yolo_labels_dir).mkdir(exist_ok=True) # 2. 构建类别字典 class_to_id, id_to_class = build_class_index(voc_annotations_dir) class_list = list(class_to_id.keys()) # 3. 批量转换所有VOC标注到YOLO格式 print("Converting VOC annotations to YOLO format...") xml_files = list(Path(voc_annotations_dir).glob('*.xml')) for i, xml_file in enumerate(xml_files): convert_voc_to_yolo_single(xml_file, class_to_id, yolo_labels_dir) if (i+1) % 100 == 0: print(f"Processed {i+1}/{len(xml_files)} files.") print("Conversion completed.") # 4. 划分数据集 (8:2) train_list, val_list = split_dataset(voc_images_dir, yolo_labels_dir, val_ratio=0.2) # 5. 生成data.yaml create_data_yaml('./datasets', class_list) print("\nAll done! Dataset is ready for YOLO training.") print(f"Class names saved to: class_names.txt") print(f"Dataset structure prepared under: datasets/") print(f"Training config file: data.yaml") if __name__ == '__main__': main()运行这个脚本后,你将得到一个标准的YOLO数据集结构:
project/ ├── class_names.txt ├── data.yaml ├── datasets/ │ ├── train/ │ │ ├── images/ # (符号链接或实际图片) │ │ └── labels/ # (符号链接或实际标签) │ ├── val/ │ │ ├── images/ │ │ └── labels/ │ ├── train.txt # 训练集图片路径列表 │ └── val.txt # 验证集图片路径列表 └── labels/ # 所有转换后的YOLO格式标签现在,你可以直接使用data.yaml文件,用YOLOv5/v8/v11等框架启动训练了。
4. 进阶问题与深度优化策略
完成了基础流程,我们来看看在实际项目中会遇到哪些更复杂的情况,以及如何优化。
4.1 处理复杂和特殊的标注情况
目标遮挡与截断:VOC数据集中,
difficult和truncated标签如何处理?对于difficult=1的目标,通常在训练时忽略,在评估时不计入。在转换时,可以将其单独记录或赋予一个特殊的标志,在生成YOLO标签时选择性地跳过。对于小目标或截断目标,除非完全不可辨认,否则建议保留,这对模型的鲁棒性是个考验。分割标注(Segmentation):VOC格式可能包含
<segmented>和<polygon>信息。如果我们的任务只是目标检测(矩形框),可以忽略这些。但如果未来想做实例分割,这些多边形数据就极为宝贵。一个良好的实践是,在解析XML时,将多边形坐标也提取并保存下来,即使当前不用,也为后续任务留好数据接口。空标签图片:有些图片经过标注后可能没有目标。在目标检测中,这些“负样本”图片同样重要,它们告诉模型“这里什么都没有”。YOLO格式中,空标签图片对应的
.txt文件应该是0字节的空文件。在划分数据集时,这些图片也需要被考虑进去,并按比例分配到训练集和验证集中。
4.2 数据划分的高级策略
简单的随机划分在大多数情况下可行,但在特定场景下需要更精细的策略:
按类别分层划分:对于类别极度不均衡的数据集(比如“猫”有1000张,“老虎”只有10张),随机划分可能导致验证集中根本没有“老虎”。这时需要使用分层抽样。我们可以用
scikit-learn库的StratifiedShuffleSplit,但目标检测的“样本”是图片,而一张图片可能有多个类别。一个实用的近似方法是:根据图片中是否存在稀有类别来作为分层依据。先给每张图片打上它包含的所有类别的标签,然后以这些标签组合进行分层。按场景或序列划分:对于来自监控视频或自动驾驶序列的数据,相邻帧高度相似。必须按场景ID或视频片段进行划分,确保同一个视频的所有帧要么全在训练集,要么全在验证集。否则,模型会在验证集上看到与训练集几乎一样的画面,导致性能评估严重失真(数据泄露)。这需要元数据(如文件名包含序列号)的支持。
K折交叉验证:当数据量特别少时,为了更可靠地评估模型,可以采用K折交叉验证。将数据分成K份,轮流将其中一份作为验证集,其余作为训练集,训练K个模型,最后取平均性能。这能更充分地利用数据并减少划分的随机性带来的评估波动。生成K折划分的文件列表是这一步的关键产出。
4.3 格式转换的扩展:支持COCO等其他格式
掌握了VOC到YOLO的转换,其他格式的转换原理相通。例如,COCO到YOLO的转换,核心是解析那个大的instances_train2017.json文件。
import json def convert_coco_to_yolo(coco_json_path, output_label_dir, class_filter=None): """ 将COCO格式的JSON标注转换为YOLO格式的单个txt文件。 class_filter: 可选,一个类别名称列表,只转换这些类别。 """ with open(coco_json_path, 'r') as f: data = json.load(f) # 构建类别ID到连续ID的映射(COCO的category_id可能不连续) categories = {cat['id']: cat['name'] for cat in data['categories']} if class_filter: # 只保留感兴趣的类别 filtered_cat_ids = [cid for cid, name in categories.items() if name in class_filter] cat_id_to_yolo_id = {cid: idx for idx, cid in enumerate(filtered_cat_ids)} else: cat_id_to_yolo_id = {cid: idx for idx, cid in enumerate(categories.keys())} # 构建图片ID到图片信息的映射 images = {img['id']: img for img in data['images']} # 按图片ID分组标注 from collections import defaultdict anns_by_image = defaultdict(list) for ann in data['annotations']: image_id = ann['image_id'] anns_by_image[image_id].append(ann) # 为每张图片生成YOLO标签文件 for img_id, img_info in images.items(): img_width = img_info['width'] img_height = img_info['height'] file_name = img_info['file_name'].replace('.jpg', '.txt') label_path = Path(output_label_dir) / file_name annotations = anns_by_image.get(img_id, []) yolo_lines = [] for ann in annotations: cat_id = ann['category_id'] if class_filter and cat_id not in filtered_cat_ids: continue # COCO的bbox格式是 [x_top_left, y_top_left, width, height] bbox = ann['bbox'] x_tl, y_tl, w, h = bbox # 转换为YOLO格式的中心点归一化坐标 x_center = (x_tl + w / 2) / img_width y_center = (y_tl + h / 2) / img_height w_norm = w / img_width h_norm = h / img_height yolo_id = cat_id_to_yolo_id[cat_id] line = f"{yolo_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}" yolo_lines.append(line) # 写入文件,如果没有目标,写入空文件 with open(label_path, 'w') as f: f.write('\n'.join(yolo_lines))这个脚本展示了处理另一种主流格式的思路:理解其数据结构,提取关键信息,进行坐标转换和映射,最后按需输出。
4.4 自动化与工程化实践
当处理多个项目或频繁迭代数据集时,手动运行脚本效率低下。我们需要将其工程化:
- 配置文件驱动:将数据集路径、类别过滤列表、划分比例、随机种子等参数写入一个
config.yaml文件。主脚本读取配置,实现一键处理。 - 日志与错误处理:增加详细的日志记录,记录转换了多少文件,跳过了多少无效标注,遇到了哪些异常(如XML解析错误、图片不存在等),并保存到日志文件中,便于排查。
- 数据校验与可视化:转换完成后,自动抽样检查。例如,随机选择几张图片,用OpenCV读取,并根据生成的YOLO标签将边界框画回图片上,保存为预览图。这是验证转换是否正确最直观的方法。可以对比原始VOC标注可视化结果和转换后的YOLO标注可视化结果。
- 集成到数据管道:使用
Makefile或DVC(Data Version Control)来管理数据预处理流程。定义好数据依赖关系,当原始数据更新时,可以自动触发格式转换和划分流程,确保数据版本与模型版本的一致性。
5. 避坑指南与常见问题排查
在实际操作中,你几乎一定会遇到下面这些问题。这里是我踩过坑后的经验总结。
5.1 转换过程中的典型错误
坐标越界或归一化值大于1:
- 现象:训练时YOLO报错“label out of bounds”。
- 原因:原始标注框的坐标可能超出了图片范围(标注错误),或者在转换计算时,由于浮点数精度问题导致结果略大于1.0。
- 解决:在
convert_bbox_to_yolo函数中加入边界裁剪:x = max(0.0, min(1.0, x))。同时,在解析VOC XML时,就应检查xmax, ymax是否小于等于图片宽高。
类别ID不匹配:
- 现象:模型训练似乎正常,但预测时类别全乱,或者评估时mAP极低。
- 原因:
class_names.txt、data.yaml中的names列表、以及训练时模型输出的类别顺序三者不一致。这是最常见、最隐蔽的坑。 - 解决:确立单一事实来源。通常以
class_names.txt为准。在生成data.yaml时,直接从class_names.txt读取列表。在模型推理时,加载的类别标签文件也必须用同一个。
空标签文件处理不当:
- 现象:训练时警告“No labels found in xxx.txt”,或者某些图片被跳过。
- 原因:对于没有目标的图片,YOLO期望一个空的标签文件(0字节)。如果你的脚本在遇到空目标时没有生成对应的
.txt文件,或者生成了一个有内容的文件(如注释),就会出错。 - 解决:确保转换脚本为无目标的图片生成一个空的
.txt文件(直接创建并关闭即可)。
5.2 数据集划分的陷阱
验证集效果远好于/远差于训练集:
- 可能原因1(效果太好):数据泄露。训练集和验证集的数据分布差异太大,或者存在重复、高度相似的样本。检查划分逻辑,确保按场景或序列划分。
- 可能原因2(效果太差):验证集包含大量训练集中未出现过的类别、背景或场景。检查类别分布,确保分层抽样。也可能是验证集中包含大量特别困难(小、模糊、遮挡)的样本,而训练集中没有。
- 诊断方法:分别统计训练集和验证集的类别分布直方图、目标尺寸分布图、每张图片目标数量分布。如果发现明显差异,就需要重新划分。
划分结果不可复现:
- 现象:每次运行划分脚本,得到不同的训练/验证集,导致实验结果无法对比。
- 原因:没有设置随机种子(
random.seed)。 - 解决:在调用任何随机函数(如
random.shuffle,train_test_split)前,固定一个随机种子。这是可重复实验的基础。
5.3 训练前的最终检查清单
在启动那个漫长的训练命令前,花10分钟做以下检查,能避免未来数天甚至数周的徒劳:
- [ ]文件完整性:检查
datasets/train/images和datasets/train/labels下的文件数量是否匹配(每个jpg是否都有对应的txt?)。 - [ ]标签格式验证:随机打开几个YOLO格式的
.txt文件,检查格式是否为<id> <x> <y> <w> <h>,数值是否在[0,1]之间。 - [ ]可视化验证:写一个简单的可视化脚本,随机加载几张图片和对应的YOLO标签,将框画上去。肉眼确认框的位置、大小、类别是否正确。
import cv2 import random def visualize_yolo_label(img_path, label_path, class_names): img = cv2.imread(img_path) h, w, _ = img.shape with open(label_path, 'r') as f: lines = f.readlines() for line in lines: cls_id, xc, yc, bw, bh = map(float, line.strip().split()) # 转换回像素坐标用于绘图 x1 = int((xc - bw/2) * w) y1 = int((yc - bh/2) * h) x2 = int((xc + bw/2) * w) y2 = int((yc + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0,255,0), 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1) cv2.imshow('Check', img) cv2.waitKey(0) cv2.destroyAllWindows() - [ ]
data.yaml检查:打开data.yaml,确认path路径是否正确(相对路径还是绝对路径?),train和val指向的列表文件是否存在,nc(类别数)是否正确,names列表是否与class_names.txt完全一致。 - [ ]数据集统计:运行一个统计脚本,输出训练集和验证集的图片数量、总目标数、每个类别的目标数、平均目标尺寸等信息。对数据有一个量化的认识。
数据处理是深度学习项目中最“脏”最累的活,但也是决定项目成败的基石。把格式转化和数据集划分这套流程打磨成稳定、可复用的工具,以后无论拿到什么格式的数据,你都能快速将其驯服,为模型训练准备好高质量的“粮草”。这套方法论的价值,会随着你接触的项目越多,体会越深。