共计 2566 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:为什么需要自动化标注转换?
刚开始接触 CCPD 数据集时,我对着那些.txt 文件发呆了半天。原始标注文件长这样:

车牌图片路径 四点坐标 (x1,y1,x2,y2,x3,y3,x4,y4) 倾斜角度 车牌号码
但 YOLO 需要的格式却是:
类别序号 中心点 x 中心点 y 宽度 高度 (均需归一化)
手动转换?我试过处理 100 张图就花了半小时,还总把坐标算错。更别提数据集动辄几万张的规模了 …
技术方案拆解
1. 解析 CCPD 的标注结构
每个 txt 行包含三大关键信息:
– 四点坐标 :定义车牌在图像中的四边形区域
– 倾斜角度 :处理旋转车牌的必备参数
– 车牌号码 :可用于分类任务(本文暂不展开)
2. 坐标系转换核心算法
关键步骤:
- 从四边形到矩形:通过四点坐标计算最小外接矩形
- 坐标归一化:将像素坐标转换为 0 - 1 之间的相对值
- 格式适配:不同框架需要不同格式(YOLO/PASCAL VOC 等)
转换公式示例(YOLO 格式):
中心点 x = (x_min + x_max) / 2 / 图像宽度
中心点 y = (y_min + y_max) / 2 / 图像高度
宽度 = (x_max - x_min) / 图像宽度
高度 = (y_max - y_min) / 图像高度
3. 多格式输出设计
建议采用工厂模式,通过统一接口支持多种输出格式:
class AnnotationConverter:
@staticmethod
def to_yolo(points, img_size):
# 实现 YOLO 格式转换
...
@staticmethod
def to_voc(points, img_size):
# 实现 PASCAL VOC 格式转换
...
代码实现(带完整异常处理)
import os
import numpy as np
from multiprocessing import Pool
class CCPDParser:
def __init__(self, annotation_dir):
self.annotation_dir = annotation_dir
def parse_single_file(self, txt_path):
"""解析单个标注文件"""
try:
with open(txt_path, 'r') as f:
line = f.readline().strip()
parts = line.split()
if len(parts) < 9: # 基本校验
raise ValueError(f"Invalid annotation format in {txt_path}")
# 解析四点坐标(注意转换为整数)points = np.array([float(x) for x in parts[1:9]]).reshape(4, 2)
return {'image_path': parts[0],
'points': points,
'angle': float(parts[9]),
'plate_number': ' '.join(parts[10:])
}
except Exception as e:
print(f"Error parsing {txt_path}: {str(e)}")
return None
class CoordinateConverter:
@staticmethod
def quadrilateral_to_yolo(points, img_size):
"""将四边形转换为 YOLO 格式"""
x_coords = points[:, 0]
y_coords = points[:, 1]
x_min, x_max = min(x_coords), max(x_coords)
y_min, y_max = min(y_coords), max(y_coords)
# 归一化处理
width, height = img_size
x_center = ((x_min + x_max) / 2) / width
y_center = ((y_min + y_max) / 2) / height
w = (x_max - x_min) / width
h = (y_max - y_min) / height
return [x_center, y_center, w, h]
# 使用示例
if __name__ == "__main__":
parser = CCPDParser("CCPD2020/annotations")
sample_data = parser.parse_single_file("sample.txt")
if sample_data:
img_size = (1920, 1080) # 假设图像尺寸
yolo_coords = CoordinateConverter.quadrilateral_to_yolo(sample_data['points'], img_size
)
print(f"YOLO 格式坐标: {yolo_coords}")
避坑指南
1. 倾斜车牌处理
当遇到旋转角度大的车牌时,简单取 min/max 会丢失区域信息。解决方案:
- 使用 cv2.minAreaRect 计算旋转矩形
- 或保留原始四边形作为多边形标注(需框架支持)
2. 多线程批处理
def process_batch(annotation_files):
with Pool(processes=4) as pool: # 4 进程并行
results = pool.map(parse_single_file, annotation_files)
return [r for r in results if r is not None]
注意:
– 避免直接写同一文件
– 使用队列管理 IO 密集型任务
3. 数据泄漏预防
在划分训练 / 验证集时:
– 按车牌号码哈希分桶(防止同一车牌出现在两个集合)
– 使用 sklearn 的 StratifiedKFold 保持类别分布
扩展思考
- 跨数据集适配 :如何修改代码以支持 CRPD 等其它车牌数据集?
-
关键点是抽象解析逻辑,通过继承实现不同数据源的适配
-
格式对比实验 :YOLO 格式与 COCO 格式在训练效率上有何差异?
-
建议用相同数据分别训练,比较 mAP 和训练速度
-
自动化验证 :如何自动检查标注转换的正确性?
- 可开发可视化工具叠加显示原图与转换后的 bbox
结语
处理完第一批数据时,看着自动生成的几千个标注文件,终于不用再熬夜手动改坐标了。建议大家在实践中思考:
- 如果车牌被严重遮挡,四点坐标还可靠吗?
- 如何扩展支持车牌颜色分类?
- 当处理 100 万 + 数据时,如何优化内存使用?
希望本指南能帮你跳过那些我踩过的坑。完整代码已放在 GitHub(示例链接),欢迎提交改进建议!
正文完
