数据标注格式错?cv_resnet18_ocr-detection训练集验证脚本分享
1. 为什么需要这个验证脚本?
你是不是也遇到过这样的情况:
辛辛苦苦标注了上百张图片,准备开始训练,结果cv_resnet18_ocr-detection模型一跑就报错——不是路径找不到,就是坐标读取失败,再一看日志里满屏ValueError: could not convert string to float或者IndexError: list index out of range……
别急,这大概率不是模型的问题,而是训练集标注格式不合规。
cv_resnet18_ocr-detection要求严格遵循ICDAR2015 标注规范:每行必须是x1,y1,x2,y2,x3,y3,x4,y4,文本内容的8个坐标+1个字符串,且所有坐标必须为整数、按顺时针/逆时针顺序排列、不能越界、不能为空字段。但人工标注、工具导出、跨平台复制时,极易混入空格、制表符、中文逗号、引号、BOM头,甚至把“文本内容”里的英文逗号误当分隔符切开——这些细微错误,模型不会友好提示,只会静默崩溃或训练发散。
这篇分享的,就是一个专为cv_resnet18_ocr-detection训练集设计的轻量级验证脚本。它不依赖训练环境,纯 Python 实现,5分钟内就能跑完千张数据,精准定位每一处格式问题,并生成修复建议。它不是通用OCR校验器,而是为你手头这个模型量身定制的“标注体检报告”。
2. 验证脚本核心能力一览
2.1 它能发现哪些典型问题?
基础结构错误
- 标注文件(
.txt)为空或只有一行 - 行数与
train_list.txt中声明的图片数量不一致 - 某行字段数 ≠ 9(8个数字+1个文本)
- 标注文件(
坐标数值异常
- 坐标含非数字字符(如
x1=100.5、y2=abc、x3= 200带空格) - 坐标为负数或小数(ICDAR2015 要求整数像素坐标)
- 四点不构成有效凸四边形(如三点共线、自相交)
- 坐标值超出图片宽高范围(需结合图片实际尺寸校验)
- 坐标含非数字字符(如
文本内容陷阱
- 文本字段为空(
x1,y1,...,y4,后无内容) - 文本中包含未转义的英文逗号(导致
split(',')切错) - 文本首尾含不可见字符(BOM、零宽空格、换行符)
- 文本字段为空(
文件系统级问题
train_list.txt中路径拼写错误(如train_gts/1.txt实际为train_gts/001.txt)- 图片文件缺失或格式不支持(非 JPG/PNG/BMP)
- 同名标注文件存在编码冲突(UTF-8 with BOM vs ANSI)
2.2 它怎么帮你快速修复?
脚本不只报错,更提供可直接执行的修复方案:
- 对坐标空格问题:自动
strip()并提示原始行; - 对小数坐标:给出四舍五入后的合法值及修改命令(
sed -i 's/100.5/101/g' 1.txt); - 对文本逗号:建议用
|替代,并生成安全的csv导出模板; - 对BOM头:明确指出文件并推荐
iconv -f UTF-8 -t UTF-8 -c file.txt > clean.txt; - 最终输出一份
fix_summary.md,按严重等级排序所有问题,附带一键修复脚本片段。
3. 快速上手:三步完成验证
3.1 准备工作:确认你的数据集结构
确保你的训练集严格符合cv_resnet18_ocr-detection要求的目录结构:
/root/custom_data/ ├── train_list.txt # 必须存在,每行:train_images/xxx.jpg train_gts/xxx.txt ├── train_images/ # 存放所有训练图片(JPG/PNG/BMP) │ ├── 1.jpg │ └── 2.png ├── train_gts/ # 存放对应标注文件(纯文本,UTF-8无BOM) │ ├── 1.txt │ └── 2.txt └── test_list.txt # (可选)测试集列表,结构同 train_list.txt注意:
train_list.txt中的路径必须是相对路径,且与实际文件位置完全一致;train_gts/*.txt文件必须是Unix换行符(LF),无BOM头。
3.2 运行验证脚本(无需安装任何包)
将以下脚本保存为validate_ocr_dataset.py,放在/root/custom_data/目录下(与train_list.txt同级):
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ cv_resnet18_ocr-detection 训练集格式验证器 功能:检查 ICDAR2015 格式合规性,定位坐标/文本/路径错误 作者:科哥 | 适配 cv_resnet18_ocr-detection v1.2+ """ import os import sys import csv import json import time from pathlib import Path from PIL import Image import numpy as np def load_train_list(list_path): """加载 train_list.txt,返回 (img_path, gt_path) 列表""" pairs = [] with open(list_path, 'r', encoding='utf-8') as f: for i, line in enumerate(f, 1): line = line.strip() if not line: continue parts = line.split() if len(parts) != 2: print(f" [行{i}] {list_path.name} 格式错误:期望2字段,得到{len(parts)}个 → '{line}'") continue img_rel, gt_rel = parts[0], parts[1] pairs.append((Path(img_rel), Path(gt_rel))) return pairs def validate_gt_file(gt_path, img_path): """验证单个 .txt 标注文件""" errors = [] warnings = [] # 检查文件是否存在 if not gt_path.exists(): errors.append(f"❌ 标注文件缺失:{gt_path}") return errors, warnings # 读取图片尺寸(用于坐标越界检查) try: with Image.open(img_path) as img: img_w, img_h = img.size except Exception as e: errors.append(f"❌ 无法读取图片 {img_path}:{e}") return errors, warnings # 逐行解析标注 with open(gt_path, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n\r') for line in f if line.strip()] if not lines: errors.append(f"❌ 标注文件为空:{gt_path}") return errors, warnings for idx, line in enumerate(lines, 1): # 分割字段(严格按英文逗号,不允许空格干扰) fields = line.split(',') if len(fields) < 9: errors.append(f"❌ [第{idx}行] 字段数不足9个(得{len(fields)})→ '{line}'") continue if len(fields) > 9: # 文本内容可能含逗号,只取前8个为坐标,剩余合并为文本 coords_str = fields[:8] text_content = ','.join(fields[8:]).strip() else: coords_str = fields[:8] text_content = fields[8].strip() # 验证8个坐标 coords = [] for i, c in enumerate(coords_str): c_clean = c.strip() if not c_clean: errors.append(f"❌ [第{idx}行] 第{i+1}个坐标为空 → '{line}'") continue try: val = int(float(c_clean)) # 先转float防"100.0",再转int coords.append(val) except ValueError: errors.append(f"❌ [第{idx}行] 第{i+1}个坐标非数字 → '{c_clean}' in '{line}'") continue if len(coords) != 8: continue # 检查坐标是否越界 x1, y1, x2, y2, x3, y3, x4, y4 = coords for i, (x, y) in enumerate([(x1,y1), (x2,y2), (x3,y3), (x4,y4)], 1): if x < 0 or x >= img_w or y < 0 or y >= img_h: errors.append(f"❌ [第{idx}行] 第{i}点({x},{y}) 超出图片尺寸({img_w}×{img_h}) → '{line}'") # 检查文本内容 if not text_content: warnings.append(f" [第{idx}行] 文本内容为空 → '{line}'") if ',' in text_content and not text_content.startswith('"'): warnings.append(f" [第{idx}行] 文本含未转义英文逗号,建议用引号包裹 → '{text_content}'") # 检查四点是否构成合理四边形(简化:检查是否全部不同) points = [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] if len(set(points)) < 4: warnings.append(f" [第{idx}行] 四点不全不同,可能为退化矩形 → '{line}'") return errors, warnings def main(): if len(sys.argv) != 2: print("用法:python validate_ocr_dataset.py <数据集根目录>") print("示例:python validate_ocr_dataset.py /root/custom_data") sys.exit(1) root_dir = Path(sys.argv[1]) train_list = root_dir / "train_list.txt" if not train_list.exists(): print(f"❌ 错误:未找到 {train_list},请确认路径正确") sys.exit(1) print(f" 开始验证数据集:{root_dir}") print(f" 加载训练列表:{train_list}") start_time = time.time() all_errors = [] all_warnings = [] # 加载列表 pairs = load_train_list(train_list) if not pairs: print("❌ train_list.txt 无有效条目") sys.exit(1) print(f" 加载 {len(pairs)} 个图像-标注对") # 逐对验证 for i, (img_rel, gt_rel) in enumerate(pairs, 1): img_path = root_dir / img_rel gt_path = root_dir / gt_rel print(f" [{i}/{len(pairs)}] 验证 {img_rel} ←→ {gt_rel}", end="\r") if not img_path.exists(): all_errors.append(f"❌ 图片缺失:{img_path}") continue errors, warnings = validate_gt_file(gt_path, img_path) all_errors.extend(errors) all_warnings.extend(warnings) # 输出汇总 elapsed = time.time() - start_time print(f"\n⏱ 验证完成,耗时 {elapsed:.1f} 秒") print(f"\n 汇总报告:") print(f" • 错误(需立即修复):{len(all_errors)} 处") print(f" • 警告(建议优化):{len(all_warnings)} 处") if all_errors: print(f"\n🚨 严重错误详情:") for err in all_errors[:10]: # 只显示前10个,避免刷屏 print(f" {err}") if len(all_errors) > 10: print(f" ... 还有 {len(all_errors)-10} 处错误(详见 full_report.json)") # 生成完整报告 report = { "summary": { "total_pairs": len(pairs), "errors_count": len(all_errors), "warnings_count": len(all_warnings), "duration_sec": round(elapsed, 1) }, "errors": all_errors, "warnings": all_warnings } report_path = root_dir / "validation_report.json" with open(report_path, 'w', encoding='utf-8') as f: json.dump(report, f, ensure_ascii=False, indent=2) print(f"\n 完整报告已保存:{report_path}") if not all_errors: print(f"\n 恭喜!数据集通过全部格式校验,可直接用于训练。") if __name__ == "__main__": main()3.3 执行验证并解读结果
在终端中运行:
cd /root/custom_data python validate_ocr_dataset.py .典型输出解读:
开始验证数据集:/root/custom_data 加载训练列表:/root/custom_data/train_list.txt 加载 127 个图像-标注对 [127/127] 验证 train_images/105.jpg ←→ train_gts/105.txt ⏱ 验证完成,耗时 4.2 秒 汇总报告: • 错误(需立即修复):3 处 • 警告(建议优化):12 处 🚨 严重错误详情: ❌ [第3行] 字段数不足9个(得8)→ '10,20,30,40,50,60,70,80' ❌ 标注文件缺失:/root/custom_data/train_gts/042.txt ❌ [第1行] 第1个坐标非数字 → 'x1' in 'x1,y1,x2,y2,x3,y3,x4,y4,文本'- 错误(❌):必须修复,否则训练必然失败;
- 警告():不影响运行,但可能降低检测精度(如文本含逗号易被切错);
- 报告末尾的
validation_report.json包含所有细节,可导入 Excel 排序分析。
4. 常见问题修复指南(附命令)
4.1 修复“字段数不足9个”
原因:标注行末尾漏写文本,或用空格代替逗号分隔。
修复命令(Linux/macOS):
# 将所有末尾无文本的行,补上占位符"UNK" sed -i '/^[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*,[^,]*$/s/$/,UNK/' train_gts/*.txt # 将空格分隔改为逗号分隔(谨慎使用,先备份) sed -i 's/ \+/,/g' train_gts/*.txt4.2 修复“坐标含小数或空格”
原因:标注工具导出带小数,或手动编辑留空格。
修复命令(Python 一行流):
# 对所有 .txt 文件:去除空格,四舍五入坐标 for f in train_gts/*.txt; do python3 -c " import re,sys with open('$f') as f: s=f.read() s = re.sub(r'(\d+\.\d+)', lambda m: str(round(float(m.group(1)))), s) s = re.sub(r'\s+', '', s) with open('$f','w') as f: f.write(s) " done4.3 修复“文本含未转义英文逗号”
安全方案:用双引号包裹文本字段
# 将每行末尾的文本内容用双引号包裹(假设文本不含双引号) sed -i 's/\(.*,\)\(.*\)$/\1"\2"/' train_gts/*.txt提示:修复后务必重新运行验证脚本,确认错误数归零。
5. 进阶技巧:让验证融入工作流
5.1 训练前自动触发(CI/CD 友好)
将验证脚本加入训练启动流程,在start_training.sh中添加:
#!/bin/bash # ... 其他初始化 ... echo "🧪 正在验证训练集格式..." python /root/custom_data/validate_ocr_dataset.py /root/custom_data if [ $? -ne 0 ]; then echo "❌ 数据集验证失败,终止训练" exit 1 fi echo " 开始训练..." python train.py --data_dir /root/custom_data ...5.2 生成可视化报告(HTML)
利用validation_report.json,用以下脚本生成网页版报告:
# gen_html_report.py import json from pathlib import Path with open("validation_report.json") as f: report = json.load(f) html = f"""<!DOCTYPE html> <html><head><meta charset="utf-8"><title>OCR数据集验证报告</title> <style>body{{font-family:Arial,sans-serif;margin:40px}}.error{{color:red}}.warn{{color:#e67e22}}</style> </head><body><h1>OCR数据集验证报告</h1> <p><strong>总耗时:</strong>{report['summary']['duration_sec']}秒</p> <p><strong>错误数:</strong><span class="error">{report['summary']['errors_count']}</span></p> <p><strong>警告数:</strong><span class="warn">{report['summary']['warnings_count']}</span></p> <h2>详细错误</h2><ul>""" for err in report["errors"]: html += f"<li class='error'>{err}</li>" html += "</ul></body></html>" Path("validation_report.html").write_text(html, encoding="utf-8") print(" HTML报告已生成:validation_report.html")运行python gen_html_report.py,双击打开即可查看彩色报告。
6. 总结:别让格式问题拖慢你的OCR项目
一个合格的 OCR 训练集,70% 的时间花在数据上,而其中一半又消耗在格式纠错上。cv_resnet18_ocr-detection是一款优秀的轻量级文字检测模型,但它对输入数据的“洁癖”程度远超想象。本文分享的验证脚本,不是万能的黑盒工具,而是一份可审计、可定制、可嵌入流水线的工程实践手册。
它教会你的不仅是“怎么修”,更是“为什么这样修”——比如 ICDAR2015 为何强制整数坐标(为后续二值化、ROI Pooling 做准备),为何禁止文本字段含逗号(因底层 C++ 解析器用strtok硬切)。当你理解这些设计约束,标注、清洗、调试的效率会指数级提升。
现在,就去你的custom_data/目录下,运行那行python validate_ocr_dataset.py .吧。5分钟后,你会收到一份清晰的“健康诊断书”。如果一切正常,恭喜你,离第一个可用的 OCR 模型只剩一步之遥;如果发现问题,也别焦虑——每个错误背后,都藏着一个让你离专业更近的契机。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。