news 2026/10/7 6:33:40

2022-RoLabelImg旋转框标注实战:角度约定、格式转换与OBB训练对接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2022-RoLabelImg旋转框标注实战:角度约定、格式转换与OBB训练对接

简介:2022-RoLabelImg 是一款面向计算机视觉与机器学习研发者的图像标注工具,尤其适合从事目标检测、自动驾驶等方向的研究人员与开发者使用。该版本针对 Windows 系统做了专门优化,修复了以往安装与运行中可能出现的兼容性问题,解压后即可直接使用,降低了环境配置门槛。资源包共包含 121 个文件,以 36 个 png 界面截图、17 个 py 源码脚本、12 个 pyc 编译文件及 13 个 sample 示例为主,另含 svg 图标、sh 脚本、xml 配置与 rst 文档等,整体约 32.62MB,结构完整便于二次开发与查阅。工具支持矩形、多边形、圆形、点与线等多种标注类型,可批量加载图像,并导出 PASCAL VOC XML、YOLO YAML、COCO JSON 等主流格式,兼容 TensorFlow、PyTorch 等框架,同时具备进度保存恢复与自定义快捷键能力。目前已有 781 人学习下载,适合需要高效完成数据标注与预处理的中高级用户参考使用。

1. 2022-RoLabelImg:旋转框标注这件事,为什么值得你重新捡起来

如果你做过遥感影像、工业质检或者任意带角度的目标检测,大概率经历过这样一个场景:一张图里的飞机、舰船、文本行或者零件,明明是斜着的,你只能用水平框把它框住,框里塞满了背景,模型学得一脸懵。2022-RoLabelImg 就是冲着这个痛点来的——它是 roLabelImg 在 2022 年前后仍在被广泛使用的一个维护/分发版本,核心能力是在标注界面里直接画带角度的旋转框,并导出带角度信息的标注文件。它解决的不是"能不能标"的问题,而是"标出来的角度信息能不能被下游训练框架直接吃进去"的问题。适合谁?适合手里有一批带方向的目标数据、又不想自己从零写标注工具的目标检测从业者。这一章先把它的定位、输出格式和适用边界讲清楚,后面几章再动手。

roLabelImg 的定位很明确:它是 labelImg 的旋转框分支。labelImg 本身只支持水平矩形框,而 roLabelImg 在它的基础上加了旋转框的绘制、编辑和角度存储。2022 这个时间点之所以被单独拎出来,是因为那几年旋转目标检测(OBB,Oriented Bounding Box)在遥感、场景文本、工业缺陷几个方向集中爆发,而标注工具链却长期停留在水平框阶段,很多人回头去找这个分支,发现它虽然老,但依然是能跑通、能出活的选择。你要清楚它的边界:它是个标注工具,不是训练框架,不负责数据增强、不负责格式转换、不负责模型训练,它只干一件事——把带角度的框和类别,准确地落到文件里。

2. 旋转框标注的坐标系与角度约定:先把"玄学"讲明白

2.1 为什么角度约定是第一个坑

旋转框标注最容易翻车的地方,不是工具会不会用,而是角度到底怎么定义。同一个"斜着的框",在不同工具、不同框架里有完全不同的表示:有的用角度制,有的用弧度制;有的以水平轴为 0 度逆时针为正,有的以垂直轴为起点;有的存的是框的中心点加宽高加角度,有的存的是四个角点。roLabelImg 用的是中心点 + 宽 + 高 + 角度的表示,角度以弧度存储,这一点如果你不提前确认,后面转格式时会发现框全歪了,还找不到原因。

我一般会建议在动手标注之前,先拿一张图、画一个框、导出文件、打开看数值,用这个最小闭环把角度约定钉死。具体做法是:画一个明显倾斜 45 度左右的框,导出 XML,找到<robndbox>节点,看里面的cx、cy、w、h、angle五个值。cx、cy是框中心在图像坐标系下的像素坐标,w、h是框的宽高(注意这里的宽高是相对于框自身坐标系的,不是外接矩形),angle是弧度值。你把这个框在图上量一下,就能反推出它的角度正方向到底是顺时针还是逆时针。

2.2 角度正方向与取值范围

roLabelImg 的角度通常落在[-π/2, π/2)或者[-π, π)区间内,具体取决于版本和你的操作方式。常见做法是:角度以水平向右为 0,逆时针为正,范围限制在[-π/2, π/2),因为一个矩形旋转超过 90 度后,宽高互换就能等价表示,所以工具会做归一化。这个归一化本身是好事,但它带来的副作用是:你标注时如果习惯性地把框"转过去",工具可能悄悄帮你换算了宽高和角度,导致你以为标的是 30 度,存下来变成 -60 度加宽高互换。这不是 bug,是约定,但如果你不知道,就会在可视化时看到框"跳"了一下。

验证方法很简单:写一个几行的 Python 脚本,把 XML 里的cx、cy、w、h、angle读出来,用 OpenCV 的cv2.boxPoints或者自己算四个角点,画回原图,看是否和标注时一致。如果一致,说明你理解了约定;如果不一致,先别急着改代码,回去检查角度单位和正方向。

import math import xml.etree.ElementTree as ET import cv2 import numpy as np def parse_robndbox(xml_path): tree = ET.parse(xml_path) root = tree.getroot() objs = [] for obj in root.findall('object'): name = obj.find('name').text robnd = obj.find('robndbox') cx = float(robnd.find('cx').text) cy = float(robnd.find('cy').text) w = float(robnd.find('w').text) h = float(robnd.find('h').text) angle = float(robnd.find('angle').text) # 单位:弧度 objs.append((name, cx, cy, w, h, angle)) return objs def draw_rotated_box(img, cx, cy, w, h, angle): # 以中心点构造旋转矩形,angle 为弧度,逆时针为正 rect = ((cx, cy), (w, h), math.degrees(angle)) box = cv2.boxPoints(rect) box = np.int0(box) cv2.drawContours(img, [box], 0, (0, 0, 255), 2) return img img = cv2.imread('test.jpg') for name, cx, cy, w, h, angle in parse_robndbox('test.xml'): img = draw_rotated_box(img, cx, cy, w, h, angle) cv2.imwrite('vis.jpg', img)

这段代码做了两件事:解析 roLabelImg 导出的 XML,把旋转框画回原图。关键参数是angle,它从 XML 里读出来是弧度,传给cv2.boxPoints之前用math.degrees转成角度,因为 OpenCV 的旋转矩形接口吃的是角度制。如果你发现画出来的框和标注时方向相反,大概率是正方向约定不同,把angle取负再试一次就能确认。这个脚本我建议每个新数据集都跑一遍,花两分钟,省掉后面几小时的排查。

2.3 与主流框架的格式对接

roLabelImg 导出的 XML 是 Pascal VOC 风格的扩展,多了<robndbox>节点。但下游训练框架,比如 PaddleDetection、MMRotate、YOLOv8-OBB,通常吃的是 DOTA 格式、COCO 格式或者它们自定义的 txt。DOTA 格式是x1 y1 x2 y2 x3 y3 x4 y4 category difficult,四个角点加类别和难度标记。从 roLabelImg 的cx, cy, w, h, angle转到 DOTA 的四个角点,是标注流程里必须自己写的一步,没有现成工具能完全替你搞定,因为角度约定和角点顺序每个框架要求不同。

转换的核心逻辑是:以(cx, cy)为中心,w为宽,h为高,旋转angle弧度,算出四个角点。注意角点顺序要符合目标框架的要求,DOTA 一般要求从左上角开始顺时针或逆时针排列,顺序错了训练时框会乱。我一般会写一个转换脚本,转完之后随机抽几十张可视化检查,确认角点顺序和方向都对,再批量转。

3. 从安装到导出:2022-RoLabelImg 的最小可复现流程

3.1 环境准备与启动

roLabelImg 是 Python + Qt 的老项目,依赖 PyQt5 和 lxml。2022 年前后能跑通的常见环境是 Python 3.6 到 3.8,PyQt5 用 5.15 左右的版本。Python 3.9 以上有时会遇到 PyQt5 兼容问题,不是不能跑,但需要自己调。我一般会单独建一个虚拟环境,避免和系统里的其他 Qt 项目打架。

conda create -n rolabel python=3.8 -y conda activate rolabel pip install PyQt5==5.15.4 lxml # 假设你已经拿到 roLabelImg 的源码目录 cd roLabelImg python roLabelImg.py

启动后你会看到一个和 labelImg 很像的界面,左边是文件列表,中间是画布,上面有打开目录、打开文件、保存、下一张这些按钮。区别在于工具栏里多了旋转框相关的按钮,快捷键通常是R或者通过右键菜单切换。如果你启动时报No module named 'libs.resources',说明资源文件没编译,跑一下pyrcc5 -o libs/resources.py resources.qrc就行。这个报错在老项目里很常见,不是代码坏了,是 Qt 资源需要重新生成。

3.2 标注操作与快捷键

标注流程本身不复杂:打开图片目录,按W或者点击创建框,在目标上拉一个水平框,然后按R或者拖动框的旋转手柄把它转到贴合目标的角度。roLabelImg 的旋转操作有两种模式:一种是在框上直接拖拽旋转,一种是通过快捷键微调角度。我一般用拖拽粗调,再用快捷键细调,因为纯拖拽很难精确到一两度。

这里有个血泪经验:标注时先把图片放大到合适比例再画框,因为旋转框的角点在小图上很难对准。另外,框的宽高是相对于框自身坐标系的,你旋转之后如果发现框"变胖"了,不是工具的问题,是你旋转前框就没贴紧目标。建议每标完一张就按Ctrl+S保存,roLabelImg 没有自动保存,崩一次可能丢一批标注。

3.3 导出 XML 与批量检查

保存后每张图对应一个同名 XML,结构是 VOC 的扩展。你可以用前面那段解析脚本批量检查。批量检查我一般看三件事:框的数量对不对、角度值有没有落在合理区间、有没有框的宽高为 0 或者负数。宽高为 0 通常是误操作画了一个点,这种框训练时会报错,必须在标注阶段清掉。

import os import xml.etree.ElementTree as ET def check_dataset(xml_dir): issues = [] for fname in os.listdir(xml_dir): if not fname.endswith('.xml'): continue path = os.path.join(xml_dir, fname) tree = ET.parse(path) root = tree.getroot() for obj in root.findall('object'): robnd = obj.find('robndbox') if robnd is None: issues.append((fname, 'missing robndbox')) continue w = float(robnd.find('w').text) h = float(robnd.find('h').text) angle = float(robnd.find('angle').text) if w <= 0 or h <= 0: issues.append((fname, f'bad w/h: {w}, {h}')) if abs(angle) > 3.15: issues.append((fname, f'angle out of range: {angle}')) return issues for item in check_dataset('./Annotations'): print(item)

这段脚本遍历标注目录,把宽高异常和角度越界的框揪出来。参数上,角度阈值我设的是 3.15,略大于 π,因为有些版本可能存到 π 附近。如果你发现大量角度越界,先确认工具的角度单位是不是弧度,有些改版会存角度制,那就得整体换算。这个检查脚本我一般会在标注中期就跑一次,不要等全部标完再查,越早发现约定问题,返工成本越低。

4. 避坑与排查:旋转框标注里最容易翻车的 5 件事

4.1 现象:导出的框在可视化时整体旋转了 90 度

原因:角度正方向约定和可视化代码不一致。roLabelImg 存的是逆时针为正,但你的可视化代码可能按顺时针处理,或者 OpenCV 的boxPoints对角度正方向的理解和工具不同。解决:拿一个已知角度的框,分别用正负角度画一次,看哪个和标注时一致,然后把可视化代码的角度符号固定下来。不要两边都改,改一边,另一边跟着对齐。

4.2 现象:宽高互换,框看起来"转了个方向"

原因:角度归一化。当一个框旋转超过 90 度时,工具会把角度减 90 度、宽高互换,用等价表示存下来。这是正常行为,但如果你下游的转换脚本没考虑这一点,就会把框画错。解决:在转换脚本里统一处理,先把所有框的角度归一到[-π/2, π/2),宽高按需互换,再算角点。归一化逻辑写一次,所有转换都走这个函数。

4.3 现象:标注文件里出现robndbox和bndbox混用

原因:roLabelImg 支持水平框和旋转框两种模式,操作时可能不小心切换了模式,导致同一批数据里两种框混在一起。解决:标注前统一模式,标注后用脚本检查,把bndbox的框找出来,要么重新标成旋转框,要么单独处理。混用的数据集直接拿去训练,轻则精度下降,重则框架报错。

4.4 现象:图片和 XML 文件名对不上,训练时找不到标注

原因:roLabelImg 保存时默认用图片名加.xml,但如果你中途改了图片名或者移动了文件,XML 不会自动跟着改。解决:标注完成后跑一次文件名匹配检查,把没有对应 XML 的图片和没有对应图片的 XML 都列出来,手动对齐。这个检查用os.listdir加集合运算就能做,几行代码的事。

4.5 现象:角度值出现nan或者极大值

原因:旋转操作时框的宽高被拖成了 0,或者角度计算除了零。这种框在 XML 里可能存成nan或者一个很大的数。解决:用前面的检查脚本把异常值筛出来,定位到具体图片,重新标注那个框。不要试图用代码修复nan,修复出来的框位置大概率是错的,重新标更快。

5. 批量转换与训练对接:把 roLabelImg 的 XML 喂给 OBB 框架

5.1 转 DOTA 格式的完整脚本

DOTA 格式是旋转目标检测里最通用的中间格式之一,很多框架都支持从 DOTA 转自己的格式。下面这个脚本把 roLabelImg 的 XML 转成 DOTA 的 txt,每行一个目标,八个坐标加类别加难度。

import math import os import xml.etree.ElementTree as ET def rotbox_to_corners(cx, cy, w, h, angle): # 角度归一化到 [-pi/2, pi/2) while angle >= math.pi / 2: angle -= math.pi w, h = h, w while angle < -math.pi / 2: angle += math.pi w, h = h, w # 计算四个角点,顺序:左上、右上、右下、左下(逆时针) dx = w / 2 dy = h / 2 corners = [] for sx, sy in [(-dx, -dy), (dx, -dy), (dx, dy), (-dx, dy)]: rx = cx + sx * math.cos(angle) - sy * math.sin(angle) ry = cy + sx * math.sin(angle) + sy * math.cos(angle) corners.append((rx, ry)) return corners def xml_to_dota(xml_path, out_path): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text robnd = obj.find('robndbox') if robnd is None: continue cx = float(robnd.find('cx').text) cy = float(robnd.find('cy').text) w = float(robnd.find('w').text) h = float(robnd.find('h').text) angle = float(robnd.find('angle').text) corners = rotbox_to_corners(cx, cy, w, h, angle) coords = ' '.join(f'{x:.2f} {y:.2f}' for x, y in corners) lines.append(f'{coords} {name} 0') with open(out_path, 'w') as f: f.write('\n'.join(lines)) for fname in os.listdir('./Annotations'): if fname.endswith('.xml'): xml_to_dota(os.path.join('./Annotations', fname), os.path.join('./dota_labels', fname.replace('.xml', '.txt')))

这段代码的关键在rotbox_to_corners:先做角度归一化,保证角度落在[-π/2, π/2),同时按需互换宽高;然后用旋转矩阵把框的四个角点从框坐标系转到图像坐标系。角点顺序我按逆时针排,这是 DOTA 常见的顺序,但不同框架可能要求不同,转完一定要可视化抽查。参数上,w和h是框自身的宽高,不是外接矩形,别搞混。

5.2 转 COCO 格式与 YOLOv8-OBB 的注意点

如果你的下游是 COCO 系或者 YOLOv8-OBB,格式又不一样。COCO 的旋转框通常用[cx, cy, w, h, angle]加多边形分割两种方式存,YOLOv8-OBB 用的是归一化的八个坐标加类别。转换逻辑和 DOTA 类似,区别在归一化和坐标顺序。我一般会先把 XML 转成 DOTA,再从 DOTA 转目标格式,因为 DOTA 是中间格式,转一次检查一次,比直接从 XML 转目标格式更容易排查。

YOLOv8-OBB 要求坐标归一化到[0, 1],所以转的时候要除以图片宽高。图片宽高从 XML 的<size>节点里读,别用错。另外 YOLOv8-OBB 的类别是整数索引,不是类别名,转的时候要维护一个类别映射表,把name转成class_id。这个映射表建议单独存一个 json,训练和推理都用同一份,避免类别对不上。

5.3 转换后的验证方法

转完不要直接开训,先做两件事:一是随机抽 20 张图,把转换后的框画回原图,和 roLabelImg 里看到的对比;二是统计每个类别的框数量,和标注时的预期对比。这两步能拦住大部分格式错误。如果可视化发现框整体偏移,检查cx、cy是不是被当成了左上角;如果框大小不对,检查宽高有没有被归一化两次;如果框方向反了,检查角度正方向。这些错误我都犯过,每次都是靠可视化抽查抓出来的。

6. 一个提高标注效率的进阶技巧:预标注 + 人工修正

纯手工标旋转框,一天标不了多少张,尤其是密集小目标场景。我后来固定下来的做法是:先用一个已经在类似数据上训过的 OBB 模型跑一遍推理,把预测结果转成 roLabelImg 能读的 XML,导入工具里作为预标注,人工只做修正和增删。这样效率能翻几倍,而且模型越用越准。

具体操作分三步。第一步,用 MMRotate 或者 YOLOv8-OBB 在无标注数据上推理,输出 DOTA 格式的 txt。第二步,写一个 DOTA 转 roLabelImg XML 的脚本,把四个角点转回cx, cy, w, h, angle。这里要注意,从角点反算中心点和角度时,角度要归一到 roLabelImg 的约定,否则导入后框会歪。第三步,把生成的 XML 和图片放同一目录,用 roLabelImg 打开,工具会自动加载已有标注,你只需要拖动调整。

import math def corners_to_rotbox(corners): # corners: [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] 逆时针 xs = [p[0] for p in corners] ys = [p[1] for p in corners] cx = sum(xs) / 4 cy = sum(ys) / 4 # 用相邻两边算宽高和角度 dx1 = corners[1][0] - corners[0][0] dy1 = corners[1][1] - corners[0][1] dx2 = corners[3][0] - corners[0][0] dy2 = corners[3][1] - corners[0][1] w = math.hypot(dx1, dy1) h = math.hypot(dx2, dy2) angle = math.atan2(dy1, dx1) # 归一化到 [-pi/2, pi/2) while angle >= math.pi / 2: angle -= math.pi w, h = h, w while angle < -math.pi / 2: angle += math.pi w, h = h, w return cx, cy, w, h, angle

这个反算函数的坑在角点顺序:如果 DOTA 的角点顺序和函数假设的不一致,算出来的宽高和角度会错。我一般会先用一个已知框做往返测试——XML 转角点,角点再转回 XML,看数值是否一致。一致了再批量跑。预标注的框质量取决于模型,模型没训过的类别可能全是误检,所以导入后还是要人工过一遍,别直接信。

这套流程我用了挺长时间,最大的体会是:工具本身不复杂,复杂的是数据格式和角度约定。把这两件事用脚本固定下来,每次新数据集都跑一遍验证,后面就很少翻车了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 6:33:36

OpenCV手势识别系统实战:从肤色分割到凸包缺陷数手指

简介&#xff1a;这份资源是基于OpenCV与Mediapipe实现的手势识别系统完整源码&#xff0c;面向计算机相关专业正在准备大作业、毕业设计的学生&#xff0c;以及需要项目实战练习的学习者。项目经导师指导并认可通过&#xff0c;评审分98分&#xff0c;难度适中&#xff0c;源码…

作者头像 李华
网站建设 2026/10/7 6:33:35

DeepSeek大模型实战:从API调用到私有化部署全指南

1. 从一张白纸开始的DeepSeek学习路线很多人第一次接触DeepSeek大模型&#xff0c;脑子里冒出来的第一个问题不是"这玩意儿怎么用"&#xff0c;而是"我该从哪儿下手"。我特别理解这种感觉——打开官方文档&#xff0c;满屏的API参数、模型版本号、Token计费…

作者头像 李华
网站建设 2026/10/7 6:33:02

RAG落地最脏最累的活:图文与PDF解析全攻略

1. 为什么图文与 PDF 解析是 RAG 落地最脏最累的活做过 RAG 项目的人都有一个共识&#xff1a;检索效果差&#xff0c;八成不是模型不行&#xff0c;而是数据没洗干净。尤其是当你的知识库里混进了扫描件、产品手册、带图表的技术文档、合同 PDF 之后&#xff0c;纯文本抽取那一…

作者头像 李华
网站建设 2026/10/7 6:33:01

Java短链接生成工具实战:Base62编码、Redis缓存与布隆过滤器

简介&#xff1a;这是一套基于Java实现的短链接生成工具完整源码&#xff0c;面向具备一定Java与前端基础的开发者&#xff0c;可用于学习链接管理、访问统计与AB测试等典型业务场景。项目融合Java、Vue、JavaScript、CSS与HTML等技术栈&#xff0c;核心能力包括将原始网页链接…

作者头像 李华
网站建设 2026/10/7 6:32:59

金融信贷AI智能体搭建实战:基于华为云AgentArts的RAG与工作流编排经验

金融信贷这个场景&#xff0c;做AI智能体跟做通用问答完全是两码事。通用场景下模型答错一句话&#xff0c;用户顶多觉得"这AI不太聪明"&#xff1b;但在信贷审批、贷后管理、合规质检这些环节里&#xff0c;一次错误的判断可能直接对应一笔坏账或者一次监管问责。我…

作者头像 李华
网站建设 2026/10/7 6:32:06

用 WorkBuddy 和开源 Hypit 搭建 AI 短视频爆款复刻工作流

最近这一两个月&#xff0c;我身边做短视频的朋友几乎都在聊同一个话题&#xff1a;腾讯 WorkBuddy 和开源 Hypit 搭在一起&#xff0c;能不能真做到"一句话复刻爆款视频"。我自己运营着三个账号&#xff0c;一个偏知识口播&#xff0c;一个偏产品测评。以前看到爆款…

作者头像 李华