ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

2022-RoLabelImg旋转框标注实战:从环境搭建到YOLO-OBB格式转换

2022-RoLabelImg旋转框标注实战:从环境搭建到YOLO-OBB格式转换 简介2022-RoLabelImg 是一款面向计算机视觉与机器学习研发者的图像标注工具尤其适合从事目标检测、自动驾驶等方向的研究人员与学生使用。该版本针对 Windows 环境做了专门优化修复了以往安装与运行中可能出现的兼容性故障解压后即可直接使用省去繁琐的配置环节。资源包共收录 121 个文件整体约 32.62MB以 png 界面截图、py 源码脚本、pyc 编译文件、sample 示例数据及 svg 图标为主另含 sh 脚本、xml 配置与 rst 说明文档目录结构完整便于二次开发与功能查阅。工具支持矩形、多边形、圆形、点与线等多种标注形式可批量加载图像并导出 PASCAL VOC XML、YOLO YAML、COCO JSON 等主流格式兼容 TensorFlow、PyTorch 等框架同时提供进度保存恢复与自定义快捷键。目前已有 779 人学习下载适合需要高效完成数据标注、加速项目迭代的开发者参考使用。1. 旋转框标注的刚需2022-RoLabelImg 到底解决了什么做遥感影像目标检测的同行大概率都经历过这个场景一张航拍图里停着几十架飞机每架飞机的朝向都不一样用水平矩形框去标框里塞满了背景像素模型学出来的定位精度怎么调都上不去。2022-RoLabelImg 就是冲着这个痛点来的——它是经典标注工具 LabelImg 的一个旋转框分支版本允许你画出带角度的矩形框OBBOriented Bounding Box把目标的朝向信息一并标进去。如果你手头的数据集是 DOTA、HRSC2016、UCAS-AOD 这类遥感或航拍数据或者工业质检里需要标注斜向排列的零件这个工具值得花半小时搭起来。它不依赖 GPUPython 环境跑起来就能用标注结果直接输出 XML和 PASCAL VOC 格式兼容后续转 YOLO-OBB 或 DOTA 格式都有成熟脚本。适合谁做旋转目标检测的数据标注员、算法工程师、以及需要快速验证 OBB 方案可行性的小团队。2. 把 2022-RoLabelImg 跑起来环境、依赖与最小启动命令2.1 为什么选它而不是 LabelImg 原版或 CVATLabelImg 原版只支持水平框这是硬伤。CVAT 功能全但部署重标注旋转框的交互路径长小团队用起来时间成本高。2022-RoLabelImg 的定位很清晰在 LabelImg 的轻量交互基础上加了旋转框的绘制、调整和序列化。它的标注文件里会多出robndbox节点包含cx、cy、w、h、angle五个参数角度单位是弧度范围在[-π/2, π/2]之间。这个格式后来被不少 OBB 检测框架直接读取省去了自己写解析器的麻烦。选型时要注意这个分支版本在 2022 年前后有若干次更新不同 commit 对 Python 版本和 PyQt5 的兼容性有差异。我一般会锁定 Python 3.8 或 3.9PyQt5 用 5.15.x避免踩到 PyQt6 的 API 断裂。2.2 从零搭建四条命令跑通标注界面假设你用的是 Ubuntu 20.04 或 Windows 10 以上Python 环境已经就绪。下面是我在本地和服务器上都验证过的最小启动流程# 1. 创建独立环境避免和系统 PyQt 冲突 conda create -n rolabel python3.8 -y conda activate rolabel # 2. 安装核心依赖PyQt5 指定 5.15 系列 pip install PyQt55.15.9 lxml # 3. 获取 2022-RoLabelImg 源码从你手头的仓库或镜像 git clone 你的仓库地址 RoLabelImg cd RoLabelImg # 4. 启动标注工具指定图片目录和预定义类别 python labelImg.py ./images ./classes.txt启动后界面和 LabelImg 几乎一样区别在工具栏多了一个旋转框按钮。快捷键W画水平框E画旋转框画完后拖动四个角点可以调整角度和尺寸。classes.txt里一行一个类别名比如plane、ship、vehicle。参数说明labelImg.py第一个参数是图片文件夹路径第二个是类别文件。如果图片和标注要在不同目录用--save_dir指定 XML 输出位置。默认保存格式是 PASCAL VOC但旋转框会写成robndbox不是bndbox。这一点在后续写数据加载器时要特别注意。2.3 标注文件结构robndbox 五个参数怎么读画完一个旋转框XML 里长这样object nameplane/name poseUnspecified/pose truncated0/truncated difficult0/difficult robndbox cx512.3/cx cy384.7/cy w120.5/w h45.2/h angle0.785/angle /robndbox /objectcx、cy是旋转框中心点像素坐标w是长边h是短边angle是长边与水平轴的夹角弧度制。这里有个容易翻车的点不同版本的 RoLabelImg 对 angle 的定义可能差一个 π/2 的偏移有的以长边为基准有的以 x 轴正方向为基准。拿到标注后第一件事是可视化验证别直接喂给模型。3. 从 XML 到训练格式旋转框转换的四个边界坑3.1 转 DOTA 格式四角点顺序决定成败DOTA 格式一行是x1 y1 x2 y2 x3 y3 x4 y4 category difficult四个点按顺时针或逆时针排列不能交叉。从robndbox转四角点的公式import math def robndbox_to_dota(cx, cy, w, h, angle): # 计算未旋转时的四个角点相对于中心 dx w / 2 dy h / 2 corners [(-dx, -dy), (dx, -dy), (dx, dy), (-dx, dy)] # 旋转矩阵 cos_a math.cos(angle) sin_a math.sin(angle) points [] for x, y in corners: rx x * cos_a - y * sin_a cx ry x * sin_a y * cos_a cy points.append((rx, ry)) # 按顺时针输出从左上角开始近似 return points逻辑说明先把旋转框还原成以中心为原点的水平矩形再套旋转矩阵最后平移回图像坐标系。参数angle直接取自 XML但如果你发现可视化时框的方向反了大概率是 angle 的正负号约定不一致取反即可。输出顺序建议统一为顺时针从最靠近左上角的点开始这样后续转 YOLO-OBB 时不用再排序。3.2 转 YOLO-OBB归一化和角度范围裁剪YOLO-OBB 的标签格式是class_index cx cy w h angle全部归一化到[0,1]角度通常要求[0, π/2)。转换时有两个坑一是w和h要除以图像宽高但w对应的是长边归一化时分别除以宽和高二是角度如果落在[-π/2, 0)要加 π/2 转到正值区间。def dota_to_yolo_obb(points, img_w, img_h, class_idx): xs [p[0] for p in points] ys [p[1] for p in points] cx sum(xs) / 4 / img_w cy sum(ys) / 4 / img_h # 计算旋转框的宽高和角度 edge1 math.hypot(xs[1]-xs[0], ys[1]-ys[0]) edge2 math.hypot(xs[2]-xs[1], ys[2]-ys[1]) w max(edge1, edge2) / img_w h min(edge1, edge2) / img_h angle math.atan2(ys[1]-ys[0], xs[1]-xs[0]) if angle 0: angle math.pi / 2 if angle math.pi / 2: angle - math.pi / 2 return f{class_idx} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f} {angle:.6f}参数说明points是上一步输出的四角点img_w、img_h从图片读取。角度归一化到[0, π/2)是多数 YOLO-OBB 实现的要求但如果你用的框架文档写了别的范围以框架为准。这个函数我一般会跑一遍全量数据统计角度分布确认没有异常值。3.3 批量转换脚本多进程加速与错误日志几千张图用单进程转太慢我一般用multiprocessing.Pool并行import os import glob from multiprocessing import Pool from xml.etree import ElementTree as ET def convert_one(xml_path): try: tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text rb obj.find(robndbox) if rb is None: continue cx float(rb.find(cx).text) cy float(rb.find(cy).text) w float(rb.find(w).text) h float(rb.find(h).text) angle float(rb.find(angle).text) points robndbox_to_dota(cx, cy, w, h, angle) line dota_to_yolo_obb(points, img_w, img_h, class_map[name]) lines.append(line) out_path xml_path.replace(.xml, .txt).replace(annotations, labels) with open(out_path, w) as f: f.write(\n.join(lines)) except Exception as e: return f{xml_path}: {str(e)} return None if __name__ __main__: xml_files glob.glob(./annotations/*.xml) with Pool(8) as p: errors p.map(convert_one, xml_files) for err in errors: if err: print(err)逻辑说明每个进程独立解析一个 XML转换后写到对应的labels目录。错误不中断整体流程最后统一打印。class_map是类别名到索引的字典从classes.txt生成。跑完后检查错误日志常见问题是 XML 里缺少robndbox节点标注时误用了水平框或图片尺寸字段缺失。3.4 可视化验证别跳过这一步转完格式后我习惯用 OpenCV 画一遍框确认角度和位置没跑偏import cv2 import numpy as np def draw_obb(img_path, label_path): img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() cls, cx, cy, bw, bh, angle int(parts[0]), *map(float, parts[1:]) cx * w; cy * h; bw * w; bh * h rect ((cx, cy), (bw, bh), math.degrees(angle)) box cv2.boxPoints(rect).astype(np.int32) cv2.drawContours(img, [box], 0, (0, 255, 0), 2) cv2.imwrite(vis.jpg, img)这一步能抓出 90% 的转换错误角度偏移、宽高颠倒、归一化除错。别省这几分钟。4. 避坑与排查标注和转换中最容易翻车的五件事4.1 现象画完旋转框XML 里只有 bndbox 没有 robndbox原因启动时没有切换到旋转框模式或者快捷键按成了W而不是E。2022-RoLabelImg 默认还是水平框模式需要手动点工具栏的旋转框图标。解决标注前先确认工具栏旋转框按钮处于按下状态。如果已经标了一批只能重新标没有后悔药。建议在classes.txt同目录放一个说明文件提醒标注员先切模式。4.2 现象转换后的 YOLO-OBB 标签训练时 loss 不降原因角度范围没对齐。你的框架可能要求[-π/4, 3π/4)或[-π/2, π/2)而脚本输出的是[0, π/2)。角度差一个常数模型学起来就是玄学。解决查框架的dataset.py里对 angle 的处理逻辑把转换脚本的输出范围改成和它一致。最稳妥的办法是拿一张图手动算一遍期望值和脚本输出对比。4.3 现象可视化时框的位置对但方向反了原因robndbox的 angle 定义和你的旋转矩阵方向不一致。有的版本 angle 是顺时针为正有的是逆时针。解决在robndbox_to_dota里把sin_a取反重新可视化。如果对了就在脚本里固定这个符号约定别每次改。4.4 现象批量转换时部分图片报 “width/height not found”原因XML 里size节点缺失或图片损坏。LabelImg 在某些异常退出情况下会写出不完整的 XML。解决在转换脚本里加 try-except把报错文件路径记下来单独用xmllint检查。缺失的尺寸可以从对应图片用cv2.imread补读。4.5 现象标注界面卡顿画框延迟高原因图片分辨率太高比如 8000×6000 的遥感图PyQt 渲染压力大。解决标注前把大图切成 1024×1024 的切片或者用--resize参数在启动时缩放显示。注意缩放只影响显示不影响标注坐标XML 里存的还是原图坐标。切图方案更彻底但切图时要保证目标不被切断边缘留 overlap。5. 进阶技巧用脚本预标注减少一半工作量纯手工画旋转框一天标不了几百个。我的习惯是先用一个粗糙的 OBB 模型跑一遍推理把预测结果写成robndboxXML再用 2022-RoLabelImg 打开微调。这样标注员只需要拖动角点修正不用从零画框。具体做法用任意 OBB 检测框架比如基于 YOLOv8-OBB 或 MMrotate 的模型对训练集推理输出cx, cy, w, h, angle然后写一个反向转换脚本生成和 RoLabelImg 兼容的 XML。注意类别名要和classes.txt一致置信度低于 0.3 的框直接丢弃避免干扰。def yolo_obb_to_robndbox_xml(img_path, detections, classes, out_xml): # detections: list of (cls_idx, cx, cy, w, h, angle) 归一化值 img cv2.imread(img_path) h, w img.shape[:2] root ET.Element(annotation) ET.SubElement(root, filename).text os.path.basename(img_path) size ET.SubElement(root, size) ET.SubElement(size, width).text str(w) ET.SubElement(size, height).text str(h) ET.SubElement(size, depth).text 3 for cls_idx, cx, cy, bw, bh, angle in detections: obj ET.SubElement(root, object) ET.SubElement(obj, name).text classes[cls_idx] rb ET.SubElement(obj, robndbox) ET.SubElement(rb, cx).text str(cx * w) ET.SubElement(rb, cy).text str(cy * h) ET.SubElement(rb, w).text str(bw * w) ET.SubElement(rb, h).text str(bh * h) ET.SubElement(rb, angle).text str(angle) tree ET.ElementTree(root) tree.write(out_xml)参数说明detections里的角度要和 RoLabelImg 的约定一致不一致就先转换。生成的 XML 直接放到annotations目录用 RoLabelImg 打开图片目录就能看到预标注框。标注员微调后保存覆盖原 XML。这个流程我用了大半年标注效率大概提升 40% 到 60%具体取决于预标注模型的精度。模型越准人工修正越少。但别完全依赖预标注低置信度的框一定要人工过一遍否则错误会固化到训练集里。还有一个细节预标注 XML 里的angle如果和 RoLabelImg 的显示不一致打开后框会乱转。我的做法是先用 10 张图做小批量验证确认角度对齐后再全量生成。这个验证习惯帮我省过好几次返工。最后说一个我踩过的坑RoLabelImg 保存 XML 时会覆盖同名文件如果预标注和人工标注混在一起容易丢数据。我现在的做法是预标注文件加_auto后缀人工确认后再重命名覆盖。多一步操作但安全。希望帮到你。本文还有配套的精品资源点击获取
返回列表