
本文详解如何正确解析yolo格式的归一化标注文件(.txt),将中心坐标+宽高转换为像素级裁剪框,避免常见坐标误算导致的车牌/头盔等目标截断问题。
本文详解如何正确解析yolo格式的归一化标注文件(.txt),将中心坐标+宽高转换为像素级裁剪框,避免常见坐标误算导致的车牌/头盔等目标截断问题。
在计算机视觉任务中,尤其在车牌识别、头盔检测等场景下,需从原始图像中精确裁剪出标注目标区域。但许多开发者在使用YOLO格式标注文件(每行形如 class x_center y_center width height,所有值均归一化到 [0, 1] 区间)时,常因坐标转换逻辑错误,导致裁剪结果严重偏移或目标不完整——例如仅截取到车牌一角,或头盔被截去顶部。根本原因在于:误将归一化中心坐标直接当作左上角使用,或未正确处理归一化宽高的像素换算。
以下代码已全面修复原始实现中的关键缺陷,并增强鲁棒性:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
import os
import cv2
import numpy as np
def crop_image_with_yolo_annotation(image_path, annotation_path, output_folder, padding_ratio=0.0):
"""
基于YOLO格式标注文件(.txt)精确裁剪图像目标区域
Args:
image_path: 原图路径(支持.jpg/.jpeg/.png)
annotation_path: 对应标注文件路径(每行: class x_c y_c w h,归一化)
output_folder: 输出目录
padding_ratio: 可选边距比例(如0.05表示在原bbox四周扩展5%)
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
print(f"❌ 错误:无法加载图像 {image_path}")
return
h, w = img.shape[:2]
# 读取并解析标注
try:
with open(annotation_path, 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
except FileNotFoundError:
print(f"⚠️ 警告:标注文件不存在 {annotation_path},跳过")
return
if not lines:
print(f"⚠️ 警告:标注文件为空 {annotation_path},跳过")
return
# 逐行处理多目标(支持单图多框)
for idx, line in enumerate(lines):
values = line.split()
if len(values) < 4:
print(f"⚠️ 警告:标注格式异常(字段数<4),跳过该行: {line}")
continue
try:
# 兼容含类别标签(5值)或无类别(4值)格式
if len(values) == 5:
_, x_c_norm, y_c_norm, w_norm, h_norm = map(float, values)
else: # len == 4
x_c_norm, y_c_norm, w_norm, h_norm = map(float, values)
# ✅ 关键修正:归一化 → 像素坐标(中心+宽高 → 左上+右下)
x_c = int(x_c_norm * w)
y_c = int(y_c_norm * h)
w_px = int(w_norm * w)
h_px = int(h_norm * h)
# 计算左上角(x1, y1)和右下角(x2, y2)
x1 = max(0, x_c - w_px // 2)
y1 = max(0, y_c - h_px // 2)
x2 = min(w, x_c + w_px // 2)
y2 = min(h, y_c + h_px // 2)
# ✅ 可选:添加安全边距(提升小目标识别鲁棒性)
if padding_ratio > 0:
pad_w = int((x2 - x1) * padding_ratio)
pad_h = int((y2 - y1) * padding_ratio)
x1 = max(0, x1 - pad_w)
y1 = max(0, y1 - pad_h)
x2 = min(w, x2 + pad_w)
y2 = min(h, y2 + pad_h)
# 裁剪并保存
cropped = img[y1:y2, x1:x2]
if cropped.size == 0:
print(f"⚠️ 警告:裁剪区域为空(坐标越界),跳过第{idx+1}个框")
continue
# 构造输出路径(支持多框:_0, _1...)
name, ext = os.path.splitext(os.path.basename(image_path))
output_path = os.path.join(
output_folder,
f"{name}_crop{idx+1}{ext}"
)
cv2.imwrite(output_path, cropped)
print(f"✅ 已保存裁剪图:{output_path} | 尺寸: {cropped.shape[1]}×{cropped.shape[0]}")
except ValueError as e:
print(f"⚠️ 警告:标注数值解析失败 {line},错误: {e}")
continue
# --- 批量处理主流程 ---
input_folder = r"C:\Users\Desktop\crop"
output_folder = r"C:\Users\Desktop\crop\new"
os.makedirs(output_folder, exist_ok=True)
for filename in os.listdir(input_folder):
if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
continue
image_path = os.path.join(input_folder, filename)
txt_path = os.path.join(input_folder, os.path.splitext(filename)[0] + ".txt")
crop_image_with_yolo_annotation(
image_path=image_path,
annotation_path=txt_path,
output_folder=output_folder,
padding_ratio=0.05 # 推荐对车牌/头盔等小目标加5%边距
)⚠️ 关键注意事项与最佳实践
- 坐标系统必须明确:YOLO格式严格采用 归一化中心点 + 宽高(非左上角+右下角),这是所有计算的起点;
- 整数截断要谨慎:int() 向零取整可能导致1像素偏差,对高精度任务建议用 round() 或保留浮点计算后转 np.clip;
- 多目标支持:示例中已支持单图多行标注(如您提供的含4个目标的 .txt 文件),自动为每个目标生成独立裁剪图;
- 边界防护不可少:max(0, ...) 和 min(img_dim, ...) 防止坐标越界导致 cv2 报错或黑边;
- 边距增强实用性:设置 padding_ratio=0.05 可有效缓解因标注框紧贴目标边缘导致的特征丢失(尤其利于OCR或分类模型);
- 调试建议:首次运行时,在裁剪前用 cv2.rectangle() 在原图上绘制bbox验证位置准确性(可临时添加可视化代码)。
通过以上修正,您的脚本将能稳定、准确地从任意YOLO标注数据集中提取完整车牌、双人骑行者及对应头盔区域,为后续识别任务提供高质量输入。

















