第一次接触目标检测时,我被各种数据格式搞得晕头转向。特别是当下载了COCO数据集准备用YOLO训练时,发现标注格式完全不兼容——COCO用JSON存储边界框信息,而YOLO需要的是TXT文件。更让人崩溃的是,网上找到的转换脚本要么报错,要么生成的标注文件格式不对。经过多次尝试和调试,我终于总结出一套稳定可靠的转换方法,现在分享给同样遇到这个问题的你。
在开始转换之前,我们需要清楚地理解两种格式的区别。COCO数据集使用JSON文件存储标注信息,每个边界框用[x, y, width, height]表示,其中:
x和y是边界框中心点的坐标width和height是边界框的宽度和高度而YOLO格式使用TXT文件存储标注,每行对应一个物体,格式为class_id x_center y_center width height,其中:
class_id是类别索引(从0开始)x_center、y_center、width和height都是相对于图像宽度和高度的归一化值(0到1之间)关键转换公式如下:
python复制def convert(size, box):
# size是图像的(width, height)
# box是COCO格式的[x, y, width, height]
dw = 1. / size[0]
dh = 1. / size[1]
x = box[0] + box[2] / 2.0 # 计算中心点x坐标
y = box[1] + box[3] / 2.0 # 计算中心点y坐标
w = box[2] # 宽度
h = box[3] # 高度
# 归一化
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return (x, y, w, h)
下面是一个经过优化的完整转换脚本,解决了常见问题如路径错误、类别ID不连续等:
python复制import os
import json
from tqdm import tqdm
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='Convert COCO format to YOLO format')
parser.add_argument('--json_path', required=True, help='Path to COCO JSON annotation file')
parser.add_argument('--save_path', default='labels', help='Directory to save YOLO format labels')
parser.add_argument('--image_dir', help='Directory containing images for relative path generation')
return parser.parse_args()
def convert_bbox(size, box):
"""Convert COCO bbox format to YOLO format"""
dw = 1. / size[0]
dh = 1. / size[1]
x = (box[0] + box[2] / 2.0) * dw
y = (box[1] + box[3] / 2.0) * dh
w = box[2] * dw
h = box[3] * dh
return (x, y, w, h)
def main():
args = parse_args()
# 创建保存目录
os.makedirs(args.save_path, exist_ok=True)
# 加载COCO标注
with open(args.json_path, 'r') as f:
data = json.load(f)
# 创建类别映射(解决COCO ID不连续问题)
id_map = {cat['id']: idx for idx, cat in enumerate(data['categories'])}
# 保存类别文件
with open(os.path.join(args.save_path, 'classes.txt'), 'w') as f:
for cat in data['categories']:
f.write(f"{cat['name']}\n")
# 创建图像路径列表文件
list_file_path = os.path.join(args.save_path, 'images.txt')
with open(list_file_path, 'w') as list_file:
for img in tqdm(data['images'], desc='Processing images'):
img_id = img['id']
filename = img['file_name']
img_width = img['width']
img_height = img['height']
# 生成对应的TXT文件名
txt_name = os.path.splitext(filename)[0] + '.txt'
txt_path = os.path.join(args.save_path, txt_name)
# 写入YOLO格式标注
with open(txt_path, 'w') as f_txt:
for ann in data['annotations']:
if ann['image_id'] == img_id:
class_id = id_map[ann['category_id']]
bbox = convert_bbox((img_width, img_height), ann['bbox'])
f_txt.write(f"{class_id} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n")
# 写入图像路径(相对或绝对)
img_path = os.path.join(args.image_dir, filename) if args.image_dir else filename
list_file.write(f"{img_path}\n")
if __name__ == '__main__':
main()
提示:使用
tqdm可以显示进度条,处理大型数据集时非常有用。如果不需要,可以移除相关代码。
在实际使用中,你可能会遇到以下问题:
exist_ok=True参数避免目录已存在时报错COCO数据集的类别ID不是连续的(如1,2,3,4,...90),而YOLO通常期望从0开始的连续ID。我们的脚本通过创建映射表解决了这个问题:
python复制id_map = {cat['id']: idx for idx, cat in enumerate(data['categories'])}
浮点数精度可能导致训练时出现微小差异。我们使用.6f格式保证足够精度:
python复制f_txt.write(f"{class_id} {bbox[0]:.6f} {bbox[1]:.6f} {bbox[2]:.6f} {bbox[3]:.6f}\n")
YOLO训练时需要知道图像路径。我们的脚本会生成一个包含所有图像路径的文本文件,方便直接用于训练配置。
转换完成后,你需要配置YOLO的训练YAML文件。以下是一个典型的coco.yaml示例:
yaml复制# COCO数据集配置
path: /path/to/coco
train: images.txt # 我们脚本生成的图像列表
val: images.txt # 验证集列表(可根据需要修改)
test: # 测试集(可选)
# 类别信息
names:
0: person
1: bicycle
2: car
# ...其他类别
关键步骤:
labels目录放在与图像相同的父目录下code复制/path/to/coco/
├── images/
│ ├── train2017/ # 存放训练图像
│ └── val2017/ # 存放验证图像
└── labels/
├── train2017/ # 训练标注
└── val2017/ # 验证标注
处理大型数据集时,可以考虑以下优化:
对于COCO这样的大型数据集,可以使用Python的multiprocessing加速处理:
python复制from multiprocessing import Pool
def process_image(args):
img, data, id_map, save_path = args
# 处理单张图像的代码...
if __name__ == '__main__':
# ...其他代码...
with Pool(processes=os.cpu_count()) as pool:
args = [(img, data, id_map, args.save_path) for img in data['images']]
pool.map(process_image, args)
对于特别大的JSON文件,可以使用ijson库流式处理,避免一次性加载整个文件:
python复制import ijson
def parse_large_json(json_path):
with open(json_path, 'rb') as f:
# 流式处理JSON
images = ijson.items(f, 'images.item')
for img in images:
# 处理每张图像...
转换完成后,建议可视化检查一些样本,确保标注正确:
python复制import cv2
import random
def visualize_sample(image_path, label_path):
img = cv2.imread(image_path)
h, w = img.shape[:2]
with open(label_path, 'r') as f:
for line in f:
class_id, x, y, w, h = map(float, line.split())
# 转换回像素坐标
x1 = int((x - w/2) * w)
y1 = int((y - h/2) * h)
x2 = int((x + w/2) * w)
y2 = int((y + h/2) * h)
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow('Preview', img)
cv2.waitKey(0)
# 随机检查几个样本
sample_images = random.sample(image_list, 5)
for img_path in sample_images:
label_path = img_path.replace('images', 'labels').replace('.jpg', '.txt')
visualize_sample(img_path, label_path)
COCO有多个版本(2014、2017等),主要区别在于:
| 版本 | 训练图像数量 | 验证图像数量 | 主要变化 |
|---|---|---|---|
| 2014 | 82,783 | 40,504 | 初始版本 |
| 2017 | 118,287 | 5,000 | 增加图像 |
在使用转换脚本时,需要注意:
instances_train2017.json vs instances_train2014.json)转换后的YOLO格式数据可以方便地用于各种深度学习框架:
python复制from torch.utils.data import Dataset
class YOLODataset(Dataset):
def __init__(self, img_list, label_dir, transform=None):
self.img_list = img_list
self.label_dir = label_dir
self.transform = transform
def __getitem__(self, idx):
img_path = self.img_list[idx]
img = cv2.imread(img_path)
label_path = os.path.join(self.label_dir,
os.path.basename(img_path).replace('.jpg', '.txt'))
# 读取和处理标签...
return img, labels
python复制import tensorflow as tf
def load_yolo_data(img_path, label_path):
img = tf.io.read_file(img_path)
img = tf.image.decode_jpeg(img, channels=3)
# 读取和处理标签...
return img, labels
dataset = tf.data.Dataset.from_tensor_slices((img_paths, label_paths))
dataset = dataset.map(load_yolo_data)
在一次工业质检项目中,我们需要检测产品表面的缺陷。客户提供的标注是COCO格式,但我们的训练框架基于YOLOv5。使用这个转换脚本,我们成功:
关键成功因素:
为了完全自动化数据准备流程,可以考虑:
一个简单的自动化示例:
bash复制#!/bin/bash
# 自动下载COCO数据集并转换
DATASET_DIR="./coco"
TRAIN_JSON="instances_train2017.json"
VAL_JSON="instances_val2017.json"
# 创建目录
mkdir -p $DATASET_DIR
# 下载训练集
wget http://images.cocodataset.org/annotations/annotations_trainval2017.zip -P $DATASET_DIR
unzip $DATASET_DIR/annotations_trainval2017.zip -d $DATASET_DIR
# 转换训练集
python coco2yolo.py --json_path $DATASET_DIR/annotations/$TRAIN_JSON --save_path $DATASET_DIR/labels/train
# 转换验证集
python coco2yolo.py --json_path $DATASET_DIR/annotations/$VAL_JSON --save_path $DATASET_DIR/labels/val
根据具体需求,你可能需要调整转换脚本:
python复制# 在convert_bbox函数中添加类别过滤
target_classes = ['person', 'car'] # 只转换这些类别
if cat['name'] not in target_classes:
continue
如果使用支持旋转边界框的YOLO变体:
python复制def convert_rotated_bbox(size, box, angle):
# 添加旋转角度处理逻辑
pass
COCO还包含分割标注,如果需要转换为YOLO分割格式:
python复制def convert_segmentation(size, segmentation):
# 将COCO的分割多边形转换为YOLO格式
pass
经过多个项目的实践验证,这套转换方案已经处理了超过100万张图像的转换任务,稳定性和可靠性都得到了充分验证。