尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
LabelMe转PaddleOCR格式转换脚本实践:从JSON到TXT标注全攻略
1. 为什么需要格式转换OCR数据标注的常见困境接触过OCR训练流程的人应该都经历过这个阶段图片收集了一大批标注工具选定LabelMe一个框一个框画了好几宿终于把几百张图标完。兴冲冲去跑PaddleOCR训练脚本结果第一步整理数据就卡住了——它压根不认LabelMe的JSON。这个不认体现在两方面。一方面PaddleOCR检测模型需要的标注是TXT文件里一行一张图图片路径和标注内容用Tab分隔另一方面标注内容需要的是key为transcription和points的JSON数组结构。而LabelMe存的是嵌套的shapes结构label、points、shape_type散落在各自的字段里没有一个能和目标格式直接对齐。我见过不少人想走捷径用Excel把JSON打开硬拼或者写一堆正则替换两个框还能应付一旦有几百个文件、上千个文本框这就成了噩梦。比较现实的做法是写一个小巧的转换脚本把LabelMe的shapes逐条拆出来重新组装成PaddleOCR要求的结构。这篇文章面向两类人一类是已经用LabelMe标完数据、准备训练PaddleOCR检测模型的开发者另一类是刚开始接触OCR数据标注、想知道格式怎么流转的初学者。我会把实际转换过程中踩过的坑、脚本的设计思路、以及转换之后怎么验证结果都讲清楚流程可以直接照搬。顺带说一句背景PaddleOCR官方文档其实提过LabelMe标注与训练格式的转换关系但给的是零散说明和几个独立脚本实际用起来你会发现有大量边角情况没覆盖——label字段为空、shape_type是rectangle、画了七八个点的多边形、图片路径带中文、标注框出现交叉……这些在官方示例里都不存在真实标注数据里却全都可能出现。这篇文章补的正是这些部分。2. LabelMe与PaddleOCR的字段对照理解两套格式才能正确搬运先冷静拆一下两边的数据结构别急着写转换代码。格式转换的本质是把A结构的字段映射到B结构的字段映射关系没梳理清楚写出来的代码一定会有漏网之鱼。2.1 LabelMe JSON到底存了什么一个典型的LabelMe标注文件内容大致如下{ version: 5.2.1, flags: {}, shapes: [ { label: 姓名, points: [[23, 45], [120, 45], [120, 80], [23, 80]], group_id: null, shape_type: polygon, flags: {} }, { label: 身份证号, points: [[50, 90], [300, 95], [298, 125], [48, 120]], group_id: 1, shape_type: polygon, flags: {} } ], imagePath: idcard_001.jpg, imageData: null }需要关注的字段其实只有这几个imagePath是原始图片的文件名可能带路径前缀也可能是纯文件名shapes是一个数组里面每个元素代表一次标注操作每个shape里的label字段就是框对应的文本内容points字段是标注时画的点按顺序排列的二维坐标数组用多边形工具画出来就是个列表用矩形工具画就只存左上和右下两个点shape_type则是标注工具类型polygon、rectangle、circle都有可能。很多人看这个JSON觉得简单其实问题都藏在细节里。比如imagePath在LabelMe里保存成什么形式取决于你打开图片的方式再比如shape_type如果是rectangle那points里只有两个点如果标注员手一抖本来想画矩形最后点了七八个点生成了一个多边形points会非常长。写转换脚本之前把这些情况列出来比写完之后疯狂打补丁舒服得多。2.2 PaddleOCR要求的目标格式长什么样PaddleOCR检测模型的标准训练标注是TXT文件一行对应一张图片train_images/idcard_001.jpg\t[{transcription: 姓名, points: [[23,45],[120,45],[120,80],[23,80]]}, {transcription: 身份证号, points: [[50,90],[300,95],[298,125],[48,120]], difficult: false}]结构拆开看就三个部分图片路径、Tab制表符、JSON数组。数组里每个对象描述一个文本框transcription是框内文本内容也就是你希望模型识别出来的字符串points是框的顶点坐标二维数组每个元素是[x, y]difficult是可选项标记样本是否困难不写程序一般会默认处理但为了严谨建议显式写入。这里有一个很多人第一次会忽略的点目标格式里文本内容直接放在transcription字段里而LabelMe的label字段就是文本内容所以搬运关系很直接。但要注意PaddleOCR后面构建字典、计算损失都依赖TXT里的编码一致性如果文本里有Tab字符、换行符或者特殊符号转换时最好清洗掉否则构建字符表的时候会非常难受。2.3 两套格式的字段映射关系把对应关系整理成一张表信息含义LabelMe字段转换后目标图片文件名imagePath行首的图片路径文本框内容shape.labeltranscription字段框顶点坐标shape.pointspoints字段标注类型shape.shape_type需转为统一的点数组分组信息shape.group_id一般丢弃样本难度无difficult可手工指定这张表就是整个转换脚本的设计蓝图。对照着写代码基本不会漏字段。唯一需要额外处理的就是pointsLabelMe矩形只给了两个点必须补算成四个角点多边形点超过四个的可以直接保留PaddleOCR本身支持多点坐标但后续有些工具链只认四点框到时候你可能还需要做最小外接矩形之类的收敛操作这个在第4节详细说。3. 转换脚本完整实现从单文件到批量目录一次搞定这块我直接给可跑的代码但更关键的是解释每一段为什么这么写。3.1 先写单文件转换函数import json import os def convert_single_labelme(json_path, image_dir_prefix, out_lines): 把单个LabelMe JSON转换为一串PaddleOCR标注行 json_path: LabelMe JSON文件路径 image_dir_prefix: 输出标注中图片路径的前缀目录 out_lines: 收集结果的行列表 with open(json_path, r, encodingutf-8) as f: data json.load(f) img_path data.get(imagePath, ) # 统一把路径里的斜杠转成正斜杠Windows下尤其重要 img_path img_path.replace(\\, /) full_img_path os.path.join( image_dir_prefix, os.path.basename(img_path) ).replace(\\, /) paddle_anns [] for shape in data.get(shapes, []): label str(shape.get(label, )).strip() # 空标签直接跳过避免空框污染训练数据 if not label or label in {__ignore__, _background_}: continue points shape.get(points, []) shape_type shape.get(shape_type, polygon) if shape_type rectangle: # 矩形只存左上和右下两点需要补齐成四边形 if len(points) 2: x1, y1 points[0] x2, y2 points[1] # 归一化对角点保证四点顺序正确、不交叉 x_min, x_max min(x1, x2), max(x1, x2) y_min, y_max min(y1, y2), max(y1, y2) points [ [x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max], ] elif shape_type circle: # 圆形标注在文本框场景中几乎不会出现跳过并提示 print(f跳过圆形标注{json_path} - {label}) continue # 坐标统一转成整数四舍五入取整 int_points [[int(round(x)), int(round(y))] for x, y in points] if len(int_points) 3: print(f警告框点数少于3个跳过 {label}) continue ann { transcription: label, points: int_points, difficult: False, } paddle_anns.append(ann) if paddle_anns: line full_img_path \t json.dumps(paddle_anns, ensure_asciiFalse) out_lines.append(line)这段代码有几个设计上的细节值得说明。第一是坐标取整。LabelMe里拖拽标注时可能产生小数坐标PaddleOCR训练数据解析用的是整形坐标保留小数短期看不出问题但后续缩放、裁剪、可视化的时候容易出奇奇怪怪的像素偏移统一round一下最稳妥。第二是rectangle的处理。LabelMe的矩形工具只保存对角线上两个点如果直接把这个两点数组塞给PaddleOCR模型解析的时候会默认按四边形顶点顺序计算面积和归一化两个点的数组会让数据加载直接报错或者loss乱飞。所以我在这里把两个对角点补成顺时针方向的四个角点而且用min/max做了归一化这样无论标注员是从左上往右下拖还是从右下往左上拖最终得到的四点顺序都不会交叉。第三是空标签和非法形状的处理。真实标注的时候总有人会把不该标的背景框也画出来label可能是个空格、一个点甚至是误操作画出来的小圆。这些数据如果不过滤直接进训练集轻则浪费训练样本重则让模型学出一堆检测框里没有任何文本的错误模式。所以我在最前面就把空标签和几个LabelMe默认的忽略标签排掉了。3.2 批量遍历整个标注目录单文件函数准备好之后批量处理就很自然了def batch_convert(labelme_dir, image_dir_prefix, output_txt): labelme_dir: 存放所有LabelMe JSON的文件夹 image_dir_prefix: 输出标注中图片路径的前缀 output_txt: 输出的标注文件路径 out_lines [] json_files [ os.path.join(labelme_dir, f) for f in os.listdir(labelme_dir) if f.lower().endswith(.json) ] # 排序保证输出顺序稳定多次生成结果可对比 json_files.sort() for jf in json_files: convert_single_labelme(jf, image_dir_prefix, out_lines) with open(output_txt, w, encodingutf-8) as f: f.write(\n.join(out_lines) \n) print(f转换完成共处理 {len(json_files)} 个JSON生成 {len(out_lines)} 条标注记录)这段逻辑里有一个我很坚持的习惯把json_files排序后再遍历。原因不是追求形式而是输出文件的行顺序如果每次都不一样你后面做训练集和验证集划分、做diff对比、排查这次实验结果怎么和上次不一样的时候会非常痛苦。固定顺序让整个过程可复现。还有一个容易踩的细节有些目录里的JSON是LabelMe自动生成的备份或临时文件文件名可能是xxx.json~或者隐藏文件批量遍历时要记得过滤掉。我的习惯是只保留以.json结尾的常规文件并且过滤掉文件名以.开头的隐藏文件。3.3 关于图片路径的处理方式图片路径这个坑值得单独说。LabelMe里imagePath字段存的是什么跟你打开图片的方式强相关有人把图片放在和JSON同级目录存的是纯文件名有人用的较新版本存的是完整路径还有人在Windows上操作存的是反斜杠。三种情况混在一起的时候简单用os.path.basename处理是保底方案但它会丢掉你有意组织的子目录结构。我的建议是转换脚本里强制使用前缀目录 纯文件名的方式组装输出路径代码里的image_dir_prefix就是你在PaddleOCR训练配置里填的图片根路径。这样无论LabelMe里imagePath长什么样最后输出都是统一风格的路径后面做train/val划分都不用再二次修正。如果你确实需要保留子目录结构也可以改成保留imagePath的第一层子目录再拼前缀总之选择一种规则并全量统一绝不要让同一个TXT里出现两种路径风格。4. 多边形坐标与标签清洗实际标注数据里的坑与修正逻辑即便转换脚本写出来了真正跑批量数据的时候你会发现大部分标注文件并不是标准样本。这一节说的是真实标注数据里最常出现的三类问题以及相应的处理逻辑。4.1 超过四个点的多边形怎么处理LabelMe的多边形工具可以随便点十几二十个点都画得出来。这种数据直接进PaddleOCR理论上可以因为它的检测标注本身就支持任意多点多边形。但实际训练中很多下游处理比如后续的透视矫正、随机裁剪增强是按照四边形来假设的点多之后增强模块算外接框、算旋转角度的时候会自动往四边形上靠反而容易产生意想不到的形变。对于超过四点的标注我建议在转换时做一次收敛成四边形的操作。收敛方法有两种直接取多边形的最小外接矩形好处是稳定坏处是会让倾斜文本的外接框变大引入背景噪声。取多边形点集在四个主方向上最外侧的点拟合近似外接四边形好处是能保留一定倾斜角度坏处是实现稍复杂。我个人的经验是如果标注多边形整体形状接近矩形大多数文本框都是最小外接矩形就够了只有那种倾斜角度比较大的长文本框才值得做四点拟合。你可以在转换脚本里加一个开关控制这个行为默认收敛成矩形需要时再切换。另外用cv2.minAreaRect可以拿到带角度的最小外接矩形如果不想自己写拟合逻辑这是一个现成方案。4.2 label文本的清洗规则LabelMe的label字段既然要直接变成transcription就意味着它会被用于构建字典和损失计算。所以文本清洗的优先级非常高。我常用的清洗规则有三条第一去首尾空白把空格、换行、制表符全去掉第二把字符串里的Tab和换行替换成空格因为目标格式本身用Tab做分隔符如果文本里混入Tab解析的时候整行都会乱掉第三把空字符串、_background_、__ignore__这类LabelMe默认标签过滤掉。还有一类特殊标签值得注意有些人标注的时候会用N/A或###来表示这里确实有文字但我不认识。这类标签如果直接进字典模型会被迫学习一个无意义的字符。常见做法是转换时把它替换成具体描述或者标记difficult为True也可以直接跳过。具体策略取决于你的业务需求但不管怎么选都要在转换脚本里显式写出来而不是靠事后清洗。4.3 difficult字段该什么时候设PaddleOCR的标注数组里difficult字段是可选的大部分训练配置对difficult样本会做加权或者部分采样。实际标注场景里模糊文本、残缺文本、极小文本都属于典型的difficult样本。如果你在标注时用特殊前缀标记了困难样本比如在文本前面加hard:转换时就可以解析这个前缀把difficult设为True同时把前缀剥掉避免字典里出现奇怪字符。更省事的做法是转换后单独跑一遍统计脚本根据检测框面积、文本长度等指标做一次自动标注。不过自动标注的阈值需要根据你的数据集来调没有万能参数我建议是从面积小于某个百分位数和文本长度比平均值短很多两个维度同时筛选宁可少标也别误标。5. 转换结果校验肉眼核对与自动化检查双管齐下转换完不是直接拿去过训练至少要校验一遍。这一步很多人偷懒结果训练到一半发现数据错位返工的代价远大于校验的几分钟。5.1 自动化检查脚本会告诉你哪里不对先写一个简单的读取校验脚本把TXT的每一行解析成图片路径和标注数组逐项检查import json import os def verify_annotation(txt_path): with open(txt_path, r, encodingutf-8) as f: lines f.readlines() error_count 0 total_boxes 0 for idx, line in enumerate(lines): line line.rstrip(\n) if \t not in line: print(f第{idx 1}行缺少Tab分隔符) error_count 1 continue img_path, ann_str line.split(\t, 1) if not os.path.exists(img_path): print(f第{idx 1}行图片不存在: {img_path}) error_count 1 try: anns json.loads(ann_str) except json.JSONDecodeError as e: print(f第{idx 1}行JSON解析失败: {ann_str[:80]}) error_count 1 continue for ann in anns: total_boxes 1 pts ann.get(points, []) if len(pts) 3: print(f第{idx 1}行出现点数不足的框: {pts}) error_count 1 # 图片尺寸合规检查可以在这里做 # if not check_points_in_image(pts, img_path): # print(f第{idx 1}行坐标超出图片范围) # error_count 1 print(f校验完成错误{error_count}处有效文本框{total_boxes}个)这个脚本检查三个最常出错的点Tab分隔符是否丢失、JSON结构对不对、框顶点数是否合法。图片是否真实存在也建议加进去我实操中遇到过因为大小写不对或者文件名带空格导致图片路径对不上的情况这类错误跑训练时才会暴露尽早查出来能省一晚上。5.2 可视化校验把标注画回图片上自动化检查只能保证格式正确不能保证标注内容对得上。最直观的方法是选几十张图把TXT里的标注框用OpenCV画回原图人眼扫一遍。可视化代码不复杂读取一行解析图片把每个框的points用cv2.polylines画出来再在框上方写上transcription文本最后拼成一张大图或逐张显示。我习惯随机抽样10%的图做可视化生成到一个check目录里翻一遍心里就有底了。这一步能发现两类自动化检查查不出来的问题一是坐标错位比如矩形补点的时候顺序填反导致框变成交叉线我见过好几次虽然脚本里用min/max归一化能避免但如果你改过代码或者接了别人的数据还是得复核二是标签串号比如标注员把姓名的框和身份证号的内容对应错了这个只能靠肉眼看出来。5.3 常见错误的根因对照把实操中容易出的问题整理成一个对照表排查时对着找现象大概率原因修复方向转换脚本某一行崩溃某个JSON缺少shapes字段或字段类型异常单文件函数里加try/except并打印文件名图片路径在训练时找不到imagePath混入绝对路径或反斜杠统一basename 前缀目录组装标注框画出来是交叉线矩形补点顺序写错检查四点是否按顺时针排列大量空transcriptionlabel字段为空被保留转换前过滤空标签文本含乱码字符label中混入转义符清洗Tab、换行、控制字符这里最核心的经验是所有错误都要带着文件名打印。批处理脚本里一定要把当前处理的JSON路径拼进报错信息不然几百个文件里有一个坏的你连找都找不到。我在自己的脚本里统一用print(f[{json_path}] ...)的格式输出所有日志配合排序后的遍历顺序定位问题文件基本就是瞄一眼的事。6. 接入PaddleOCR训练流程前的收尾工作与扩展思路最后一步就是把转换出来的TXT接入PaddleOCR的数据准备环节这里有几个收尾动作和扩展想法值得多说一句。6.1 训练集与验证集的划分PaddleOCR训练时不强制要求单独提供验证集文件但建议在转换时就顺手把数据分成train和val两份。划分逻辑最简单的就是按文件名hash或者按比例随机切分但我更推荐按图片目录来切比如同一个业务场景的图尽量放在同一个集合里避免训练集和验证集高度相似导致验证指标虚高。这个在转换脚本里加一个参数就能实现比训练到一半再重新整理数据轻松得多。划分完别忘了检查两边的标注规模是否接近如果某类非常特殊的样本只在验证集里出现那就是边界情况最好手动调整一下。6.2 把转换流程固化下来整个转换脚本跑完其实它已经变成了你数据流水线的一部分。建议把转换、校验、划分这些步骤串成一个脚本用命令行参数控制输入输出目录这样整个团队复现数据准备工作只需要一条命令。我在实际项目里就把这条流水线固化了下来后续换了数据集、加了新标注从头到尾重建训练数据都只要几分钟而不是半天的重复劳动。命令行接口可以参考这样的风格python labelme2paddle.py \ --labelme_dir ./annotations \ --image_dir_prefix ./train_images \ --output_txt ./train.txt \ --val_ratio 0.15 \ --val_output ./val.txt6.3 后续可能需要的扩展能力如果你后续要做的不仅是文本检测还有文本识别也就是PaddleOCR里的rec那转换脚本可以顺势扩展多输出一版从检测框里按裁剪顺序排列的文本内容清单对应识别模型的输入。裁剪文本框可以用检测模型的坐标直接切图写进识别的标注文件里这样检测和识别的数据一次转换全搞定省去二次标注。另外如果标注里用了group_id关联同一行的多个文本框比如一张发票里项目名称和金额分属不同框可以在转换时把group_id一并读出来写进一个辅助的映射表后续做表格结构还原或者版面分析时直接用不用重新翻原始JSON。最后再分享一个小技巧转换脚本里可以加一个--debug_sample参数随机抽几张图把原始LabelMe框和转换后的框同时画出来对比如果两套框架重合度很高说明坐标搬运没有问题。这个对比图最好输出成PNG存下来数据变更后翻旧图就能一眼看出差异。我一向建议把格式转换这件事当成数据工程的一个正规环节而不只是一个临时脚本。把结构梳理清楚、异常处理做全、校验步骤补齐这套流程在多个不同的标注工具和目标格式之间都能复用。今天讲的是LabelMe到PaddleOCR理解了映射逻辑之后换成其他标注工具和其他OCR框架唯一变化的也就是字段名和拼装规则而已。
RELATED

相关推荐

Link prefetch预加载实战:H5性能优化从首屏到下一屏

Link prefetch预加载实战:H5性能优化从首屏到下一屏

做H5页面性能优化这几年,我自己踩过最深的坑,是把首屏调得飞快,结果用户点进下一屏还是白白等了两三秒。H5的Link prefetch这项预加载技术,解决的就是这种“下一屏慢”的问题:浏览器在当前页面空闲的时候,提…

📅 2026/10/15 23:01:12
R语言dplyr核心函数group_by:分组聚合、分组残留与实战用法详解

R语言dplyr核心函数group_by:分组聚合、分组残留与实战用法详解

接手一份订单明细表的时候,我最先做的不是急着算汇总,而是先把 dplyr 包加载进来,然后对数据集执行 group_by() 分组。这个习惯是踩了无数次坑之后换来的。说白了,group_by() 的核心作用就是把整个数据集按照给定变量切成逻辑上的…

📅 2026/10/15 23:01:12
电力行业国产化ERP成本管理方案:从核算到管控的落地实践

电力行业国产化ERP成本管理方案:从核算到管控的落地实践

电力行业的ERP建设,一直是个“看着标准化、落地极复杂”的领域。特别是成本管理模块,发、输、配、售四个环节的业务逻辑差异巨大,资产密集程度在全部工业门类里都排在最前列,加上电价监管对成本分摊的精细化要求,传统通…

📅 2026/10/15 23:01:12
MORE NEWS

更多资讯

📰

Web自动化测试环境搭建指南:Selenium与Python实战

1. 为什么Web自动化测试的环境搭建总让人卡在第一步做过Web自动化测试的人都有一个共同感受:写测试脚本本身可能只占三成时间,剩下七成全耗在环境搭建和调试上。浏览器驱动版本对不上、依赖包装完跑不起来、元素定位一会儿找得到一会儿找不到——这些问题…

📰

滚动时域最优视角规划:面向真实场景的3D探索决策系统

1. 这不是“找角度”的玄学,而是一套可计算、可落地的3D空间探索决策系统你有没有试过让一个机器人在陌生的地下管道里自主前进?它面前不是一条笔直的走廊,而是不断分叉、塌陷、堆满杂物的三维迷宫。激光雷达扫出一片点云,但只覆盖…

📰

华为单板硬件机考实战指南:电源检查、信号诊断与故障注入法

简介:本资源是面向华为硬件开发/设计工程师岗位应聘者的2024届单板硬件机考专项复习资料,聚焦高频考点与易错点解析,助力求职者高效突破硬件基础理论瓶颈。内容覆盖RC定时电路电容选型、二极管温度特性、电解电容低温行为、IGBT原理、信号抖动…

📰

华为硬件机考实操指南:供电验证、信号完整性与故障定位

简介:本资源是面向华为硬件开发/设计工程师岗位应聘者的2024届单板硬件机考专项复习资料,聚焦高频考点与易错点解析,助力应届生高效备考、精准突破。内容覆盖RC定时电路电容选型、二极管温度特性、电解电容低温行为、IGBT原理、信号抖动与匹配…

📰

HAPS原型验证调试全指南:从信号注入到Deep Trace实战

简介:本资源是Synopsys官方发布的《HAPS调试软件protocompiler debugger用户手册》(2021年9月版),面向数字电路原型验证工程师、FPGA开发人员及SoC硬件验证从业者,聚焦HAPS高性能原型验证平台的底层调试能力建设。手册…

📰

632流程变革框架:从分层架构到落地执行的全链路指南

简介:这份PPT资源聚焦美的集团632项目流程变革框架规划设计方案,面向企业流程管理、IT规划与数字化转型从业者,以及需要研究大型集团流程重构案例的学习者。内容围绕六大运营系统与三大管理平台展开,涵盖EPF企业流程框架的L1至L4设…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬