Appearance
车牌识别
使用YOLO26 Pose模型直接检测车牌的四个角点坐标
YOLO26-Pose 模型,是 Ultralytics 推出的新一代实时姿态估计模型,提供关键点检测功能。
1.对比常用方案
| 方案 | 目标检测 | YOLO26-Pose 关键点检测 |
|---|---|---|
| 车牌定位 | 车牌锚框坐标 | 车牌四个角点坐标 |
| 车牌矫正 | 拉伸形变 | ✅ 透视变换 |
| OCR 识别 | 较低 | ✅ 较高 |
- 目标检测 步骤
- 车牌检测 -> 车牌边界框 (bbox)
- 根据 bbox 裁剪车牌区域
- 拉伸车牌区域 到固定尺寸(提升识别率)
- OCR识别 -> 车牌识别结果
- YOLO26-Pose 步骤
- 车牌检测 -> 车牌关键点坐标
- 将用4个角点计算,任意角度车牌变换为正向标准矩形
- OCR 识别(矫正后的车牌) -> 车牌识别结果
2.模型选择
| 模型 | 参数量 | 模型大小 | 推理速度(ms) | mAP |
|---|---|---|---|---|
| YOLO26n-pose | 3.3M | 6.3MB | 6 | 52.1 |
| YOLO26s-pose | 11.6M | 22.6MB | 12 | 62.5 |
| YOLO26m-pose | 26.4M | 49.7MB | 28 | 67.3 |
| YOLO26l-pose | 44.8M | 84.0MB | 50 | 69.8 |
选择建议:
- 🚀 实时性要求高 → YOLO26n-pose
- ⚖️ 平衡性能 → YOLO26s-pose (推荐)
- 🎯 高精度要求 → YOLO26l-pose
3.数据集
3.1.中文车牌数据集(主流开源)
- CCPD(最常用,USTC,MIT 商用)
- 全称:Chinese City Parking Dataset
- 规模:50 万 +(CCPD2020+Green 新能源)
- 场景:停车场 / 道路,蓝牌、绿牌、黄牌
- 标注:车牌框、4 个角点、字符、倾斜 / 模糊 / 光照等属性
- 特点:免费商用、难度高、最权威
- 官网:https://github.com/detectRecog/CCPD
- CRPD(多目标 + 角点,补充 CCPD)
- 全称:Chinese Road Plate Dataset
- 规模:约 20 万,含多车牌同图
- 标注:车牌框、4 角点、字符、类型
- 特点:真实卡口场景、适合YOLO 检测 + 关键点
- 官网:https://github.com/yxgong0/CRPD
- CBLPRD-330k(均衡 + 合成,GAN 增强)
- 规模:33 万,蓝 / 黄 / 绿 / 白牌均衡
- 特点:GAN 生成 + 真实混合、解决样本不平衡
- 地址:https://gitcode.com/gh_mirrors/cb/CBLPRD-330k
3.2.关键点定义
对于车牌,我们定义4个关键点 + 置信度
- 右下、左下、左上、右上
- onfidence(置信度)
3.3.CCPD数据转换为YOLO格式
CCPD2020 转 YOLO 核心就两件事:从文件名解析出车牌框 → 转成归一化的 class x y w h 并按 YOLO 目录结构存放。下面给你完整流程和可直接跑的代码。
CCPD2020 标注规则(文件名即标签)
plaintext
025-95_113-154&383_386&473-386&473_177&454_154&383_363&402-0_0_22_27_27_33_16-37-15.jpg每个名字可以被拆分为七个字段。这些场的解释如下。
- 面积 :车牌面积与整个画面区域的面积比值。
- 倾斜度 :水平倾斜度和垂直倾斜度。
- 边界框坐标 :左上顶点和右下顶点的坐标。
- 四个顶点位置 :整个图像中 LP 四个顶点的精确(x, y)坐标。这些坐标从右下顶点开始。
- 车牌号
按 - 分割为多段,第 4 段 = 车牌四个角点 格式:p1_p2_p3_p4,每个点 x&y 顺序:右下、左下、左上、右上(车牌顺时针 / 逆时针四点)
输出 YOLOv8 Pose 关键点格式(外接框 + 四点)
标签格式:cls cx cy bw bh k1x k1y k2x k2y k3x k3y k4x k4y
解释:
- cls:类别ID,车牌为0
- cx cy bw bh:车牌边界框坐标
- k1x k1y k2x k2y k3x k3y k4x k4y:车牌四个角点坐标, 右下、左下、左上、右上(车牌顺时针)
- 安装
bash
pip install opencv-python tqdm- 转换 yolo_conversion.py
python
import os
import cv2
import shutil
from tqdm import tqdm
# ==================== 配置区 ====================
ROOT_SRC = "/Users/ly/Downloads/dataset/CCPD2020/ccpd_green"
ROOT_DST = "/Users/ly/Downloads/dataset/ccpd_green_pose"
CLASS_ID = 0
# =================================================
sub_sets = ["train", "val", "test"]
# 创建目录
for subset in sub_sets:
os.makedirs(os.path.join(ROOT_DST, "images", subset), exist_ok=True)
os.makedirs(os.path.join(ROOT_DST, "labels", subset), exist_ok=True)
def parse_bbox(file_name):
"""解析外接框 xyxy"""
parts = file_name.split("-")
bbox_str = parts[2]
p1, p2 = bbox_str.split("_")
x1, y1 = map(int, p1.split("&"))
x2, y2 = map(int, p2.split("&"))
return x1, y1, x2, y2
def parse_four_points(file_name):
"""解析四点原始坐标"""
parts = file_name.split("-")
pts_str = parts[3]
pts = []
for p in pts_str.split("_"):
x, y = map(int, p.split("&"))
pts.append(x)
pts.append(y)
return pts
# 遍历数据集
for subset in sub_sets:
src_img_dir = os.path.join(ROOT_SRC, subset)
dst_img_dir = os.path.join(ROOT_DST, "images", subset)
dst_label_dir = os.path.join(ROOT_DST, "labels", subset)
img_list = [f for f in os.listdir(src_img_dir) if f.lower().endswith(".jpg")]
print(f"\n正在处理 {subset},共 {len(img_list)} 张图片")
for img_name in tqdm(img_list):
src_path = os.path.join(src_img_dir, img_name)
img = cv2.imread(src_path)
h, w = img.shape[:2]
# 1. 外接框转YOLO格式
x1, y1, x2, y2 = parse_bbox(img_name)
cx = (x1 + x2) / 2.0 / w
cy = (y1 + y2) / 2.0 / h
bw = (x2 - x1) / w
bh = (y2 - y1) / h
# 2. 四点归一化
raw_pts = parse_four_points(img_name)
norm_pts = []
for idx, val in enumerate(raw_pts):
if idx % 2 == 0:
norm_pts.append(f"{val / w:.6f}")
else:
norm_pts.append(f"{val / h:.6f}")
# 复制图片
shutil.copy(src_path, os.path.join(dst_img_dir, img_name))
# 写入标签
txt_name = os.path.splitext(img_name)[0] + ".txt"
txt_path = os.path.join(dst_label_dir, txt_name)
line = f"{CLASS_ID} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f} {' '.join(norm_pts)}\n"
with open(txt_path, "w", encoding="utf-8") as f:
f.write(line)
# 生成Pose专用yaml
yaml_content = f"""path: {ROOT_DST}
train: images/train
val: images/val
test: images/test
nc: 1
names: ["license_plate"]
kpt_shape: [4, 2] # 4个关键点,每个点x/y
"""
yaml_path = os.path.join(ROOT_DST, "data.yaml")
with open(yaml_path, "w", encoding="utf-8") as f:
f.write(yaml_content.strip())
print("\n✅ YOLOv8 Pose 四点关键点格式转换完成!")4.模型训练代码
- yolo_pose_plate.py
python
"""
车牌关键点检测训练脚本 - Pose任务
基于YOLO26 Pose模型
用于检测车牌的四个角点位置
"""
import os
from pathlib import Path
from ultralytics import YOLO
def train_model(data_yaml_path, task='pose'):
"""训练YOLO26 Pose模型"""
print("="*50)
print("开始加载模型...")
# 加载预训练Pose模型
model = YOLO("yolo26n-pose.pt")
print("模型加载完成")
# 开始训练
print("="*50)
print("开始训练...\n")
results = model.train(
data=data_yaml_path, # 数据集配置
task=task, # 任务类型
epochs=10, # 训练轮数
imgsz=640, # 输入图像尺寸
batch=-1, # 批次大小
device="mps", # 设备:0(GPU)、cpu、mps(Mac)、0,1(多 GPU)
workers=4,
project="./results",
name="plate_detection",
exist_ok=True,
amp=True, # 混合精度训练 (推荐)
patience=20, # 早停机制
save=True,
save_period=10,
cache=True,
verbose=True,
# 数据增强参数
flipud=0.5, # 纵向翻转
fliplr=0.5, # 横向翻转
degrees=10, # 旋转
hsv_h=0.015, # 色度调整
hsv_s=0.7, # 饱和度调整
hsv_v=0.4, # 亮度调整
translate=0.1, # 平移
scale=0.5, # 缩放
mosaic=1.0, # Mosaic增强
mixup=0.0, # Mixup增强
close_mosaic=10, # 最后10个epoch关闭Mosaic
weight_decay=0.0005, # 权重衰减
lr0=0.001, # 初始学习率
lrf=0.01, # 最终学习率系数
)
return results
def main():
print("=" * 50)
print("车牌关键点检测模型训练")
print("任务: 检测4个角点 (Pose)")
print("=" * 50)
data_yaml_path = "/Users/ly/Downloads/dataset/ccpd_green_pose/data.yaml"
if not Path(data_yaml_path).exists():
print(f"\n错误: 配置文件不存在,请检查路径: {data_yaml_path}")
return
print(f"\n使用配置文件: {data_yaml_path}\n")
# 开始训练
results = train_model(data_yaml_path, task='pose')
print("\n" + "=" * 50)
print("训练完成!")
print(f"最优模型: ./results/plate_detection/weights/best.pt")
print(f"最后模型: ./results/plate_detection/weights/last.pt")
print("=" * 50)
if __name__ == "__main__":
main()一次训练结果:
| 指标分类 | 指标名称 | 数值 | 指标含义 |
|---|---|---|---|
| 基础统计 | 验证图片数量 | 1001 | 验证集总图片数 |
| 基础统计 | 目标实例数量 | 1001 | 验证集总车牌目标数 |
| 边界框检测 | 精确率(P) | 0.976 | 预测为正例中真实正例的比例 |
| 边界框检测 | 召回率(R) | 0.967 | 真实正例中被正确预测的比例 |
| 边界框检测 | mAP50 | 0.993 | IoU阈值0.5时的平均精度 |
| 边界框检测 | mAP50-95 | 0.817 | IoU从0.5到0.95区间的平均精度 |
| 关键点检测 | 精确率(P) | 0.977 | 关键点检测的精确率 |
| 关键点检测 | 召回率(R) | 0.968 | 关键点检测的召回率 |
| 关键点检测 | mAP50 | 0.993 | IoU阈值0.5时关键点的平均精度 |
| 关键点检测 | mAP50-95 | 0.988 | IoU从0.5到0.95区间关键点的平均精度 |
5.推理
5.1 单张图片推理
- yolo_pose_plate_inference.py
python
from ultralytics import YOLO
import cv2
import numpy as np
def detect_plate_keypoints(image_path, model_path):
"""
检测图像中的车牌和关键点
输出格式:结果对象中包含:
- boxes(边界框坐标、置信度)
- keypoints(归一化/像素级的4个车牌角点坐标)、速度统计等字段,可直接用于后续车牌透视矫正等业务流程。
Returns:
results: YOLO26检测结果
detections: 解析后的检测结果 [{
'bbox': [x_min, y_min, x_max, y_max],
'keypoints': [(x1,y1,conf1), (x2,y2,conf2), ...],
'confidence': float
}, ...]
"""
# 加载模型
model = YOLO(model_path)
# 执行推理
results = model(image_path)
# 解析结果
detections = []
count = 0
for result in results:
result.show() # 显示结果图片
for box, kpts in zip(result.boxes, result.keypoints):
count += 1
# 边界框
cls_id = int(box.cls[0]) # 类别ID
confidence = float(box.conf[0]) # 置信度
x1, y1, x2, y2 = box.xyxy[0].tolist() # 边界框坐标
bbox = [x1, y1, x2, y2] # 边界框坐标
# bbox = box.xyxy[0].cpu().numpy()
# conf = box.conf[0].item()
# 关键点
keypoints = []
detections.append({
'bbox': bbox,
'keypoints': kpts.xy[0],
'confidence': confidence
})
print(f"检测到 {count} 个车牌")
return results, detections
# 使用
if __name__ == "__main__":
image_path = "./test_image2.jpeg"
model_path = "./runs/pose/results/plate_detection/weights/best.pt"
results, detections = detect_plate_keypoints(image_path, model_path)
for i, det in enumerate(detections):
print(f"车牌 {i+1}:")
print(f" 置信度: {det['confidence']:.2f}")
print(f" 关键点: {det['keypoints']}")