1. 为什么要在 Ubuntu 18.04 上折腾 roLabelImg
如果你做过目标检测相关的标注工作,大概率绕不开 LabelImg 这个工具。它轻量、上手快、支持 VOC 和 YOLO 格式导出,几乎是入门标注的标配。但当你真正进入工业级场景,尤其是处理遥感影像、航拍图、文档版面分析、工业质检这类数据时,会发现一个很尴尬的问题:标准 LabelImg 只能画水平矩形框,遇到倾斜目标就彻底歇菜了。
roLabelImg 就是在这个背景下出现的。它是 LabelImg 的一个分支版本,核心增强点在于支持**旋转矩形框(Rotated Bounding Box)**的标注,输出格式兼容 DOTA 数据集规范,也能导出成带角度信息的 XML。简单说,它能让你在图上画一个带旋转角度的框,把斜着的飞机、船舰、路牌、文本行准确地框出来。
这个工具本身并不复杂,但它在 Ubuntu 18.04 上的部署和使用,坑比想象中多。Ubuntu 18.04 自带的 Python 版本、Qt 库版本、PyQt5 与 Python 的绑定关系,再加上 roLabelImg 本身代码里一些历史遗留问题,导致很多人第一次跑起来就报错,然后卡在环境配置上出不来。我前后在三台不同配置的 Ubuntu 18.04 机器上部署过这个工具,踩过的坑包括但不限于:libQt5Core.so版本冲突、sip模块缺失、canvas.py里的浮点运算报错、中文路径读取失败、标注文件角度值异常等等。
这篇文章面向的是需要在 Ubuntu 18.04 上使用 roLabelImg 做旋转框标注的从业者,不管你是做遥感解译、OCR 数据准备,还是工业缺陷检测,只要涉及倾斜目标标注,这套流程都能直接参考。我会从环境准备讲起,把依赖关系、安装步骤、参数配置、实操标注流程、常见报错排查全部拆开讲清楚,最后再分享一些我在实际标注中总结出来的效率技巧和避坑经验。内容偏实操,不堆理论,能直接抄作业。
2. 环境准备与依赖关系梳理
2.1 Ubuntu 18.04 的 Python 环境现状
Ubuntu 18.04 默认自带 Python 3.6.9,这个版本本身不算太老,但问题在于系统级的 Python 环境被大量系统工具依赖,你如果直接用sudo pip3 install往系统环境里装包,很容易把系统工具搞崩。我见过有人装完 PyQt5 之后,系统自带的软件更新器直接打不开了,就是因为 PyQt5 的版本和系统 Qt 库产生了冲突。
所以第一步,强烈建议用虚拟环境隔离。Python 3.6 自带的venv模块就够用了,不需要额外装 virtualenv。创建虚拟环境的命令很直接:
sudo apt update sudo apt install python3-venv python3-pip -y python3 -m venv ~/rolabelimg_env source ~/rolabelimg_env/bin/activate激活之后,你的终端提示符前面会出现(rolabelimg_env)字样,后续所有 pip 安装都只影响这个虚拟环境,不会污染系统。这一步看起来简单,但它是后面所有操作不出问题的前提。
2.2 系统级依赖库的安装
roLabelImg 基于 PyQt5 构建界面,而 PyQt5 在 Linux 上运行时需要一批系统级的 Qt 库支持。这些库不能用 pip 装,必须用 apt。具体需要哪些,取决于你的 Ubuntu 18.04 是最小化安装还是桌面完整版。如果你装的是桌面版,大部分库已经有了;如果是服务器版或者最小化安装,需要手动补齐。
我整理了一份实测可用的依赖清单:
sudo apt install -y libqt5gui5 libqt5core5a libqt5widgets5 \ libqt5xml5 libqt5svg5 libqt5opengl5 \ python3-pyqt5 python3-pyqt5.qtsvg \ libxcb-xinerama0 libxcb-icccm4 libxcb-image0 \ libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \ libxcb-shape0 libxcb-xfixes0 libxkbcommon-x11-0这里面的libxcb-*系列库特别容易被忽略。PyQt5 在启动时会通过 xcb 插件连接显示服务,如果缺少这些库,程序会直接报could not load the Qt platform plugin "xcb"然后退出。这个报错非常常见,很多人以为是 PyQt5 装错了,其实是系统库没补齐。
提示:如果你是在纯命令行环境下操作,没有图形界面,roLabelImg 是跑不起来的,因为它本质上是一个 GUI 工具。你需要确保操作环境有可用的显示服务,或者通过远程桌面等方式接入图形界面。
2.3 Python 包依赖的版本选择
roLabelImg 的原始仓库里有一个requirements.txt,但里面的版本约束比较宽松,直接装最新版反而容易出问题。根据我的实测,下面这套版本组合在 Ubuntu 18.04 + Python 3.6.9 下最稳定:
| 包名 | 推荐版本 | 说明 |
|---|---|---|
| PyQt5 | 5.12.3 | 与系统 Qt 5.9.5 兼容性最好 |
| lxml | 4.5.2 | 解析 XML 标注文件 |
| numpy | 1.19.5 | 最后支持 Python 3.6 的稳定版 |
安装命令:
pip install PyQt5==5.12.3 lxml==4.5.2 numpy==1.19.5为什么不装最新版?因为 PyQt5 5.15 之后的版本对 Python 3.6 的支持开始减弱,而且和 Ubuntu 18.04 系统自带的 Qt 库版本差距太大,容易出现符号冲突。numpy 1.20 之后也放弃了对 Python 3.6 的支持。所以这套版本组合不是随便选的,是在兼容性和功能性之间找到的平衡点。
2.4 roLabelImg 源码的获取与目录结构
roLabelImg 的源码可以从公开的代码托管平台获取。下载下来之后,先别急着运行,花两分钟看一下目录结构,对后面排查问题很有帮助:
roLabelImg/ ├── labelImg.py # 主入口 ├── canvas.py # 画布逻辑,旋转框的核心实现 ├── shape.py # 形状定义,包含旋转矩形的数据结构 ├── libs/ # 工具库 │ ├── labelFile.py # 标注文件读写 │ ├── yolo_io.py # YOLO 格式导出 │ └── ... ├── resources/ # 图标等资源 └── requirements.txt其中canvas.py和shape.py是旋转框功能的核心。如果你后续想改快捷键、改默认角度步长、改导出格式,基本都在这两个文件里动手。我建议在正式标注之前,先把这个目录备份一份,因为标注过程中如果误改了源码,恢复起来很麻烦。
3. 安装部署与首次启动的完整流程
3.1 从源码启动的正确姿势
进入 roLabelImg 源码目录,在虚拟环境激活的状态下执行:
cd ~/roLabelImg python labelImg.py如果一切正常,会弹出一个图形界面窗口。但根据我的经验,第一次启动大概率不会这么顺利。下面我按报错出现的频率从高到低排列,逐个说明排查方法。
3.2 常见启动报错与即时修复
报错一:ModuleNotFoundError: No module named 'libs'
这个问题的根源是 Python 的模块搜索路径没有包含当前目录。解决方法有两种:一是用python -m方式运行,二是手动设置 PYTHONPATH。
export PYTHONPATH=$PWD:$PYTHONPATH python labelImg.py报错二:qt.qpa.plugin: Could not load the Qt platform plugin "xcb"
前面提到的 xcb 库缺失问题。先确认库是否装全:
ldd ~/rolabelimg_env/lib/python3.6/site-packages/PyQt5/Qt/plugins/platforms/libqxcb.so | grep "not found"这条命令会列出所有缺失的动态库。根据输出结果,用 apt 安装对应的包即可。常见的缺失包括libxcb-xinerama0、libxkbcommon-x11-0等。
报错三:TypeError: arguments did not match any overloaded call
这个报错通常出现在canvas.py里,原因是 PyQt5 版本差异导致某些方法签名不匹配。比如QPainter.drawText()在某些版本里不接受浮点数坐标。修复方法是把相关代码里的浮点数强制转成整数:
# 修改前 painter.drawText(x, y, text) # 修改后 painter.drawText(int(x), int(y), text)具体改哪一行,报错信息里会给出文件名和行号,照着改就行。
报错四:中文界面乱码或中文路径读取失败
Ubuntu 18.04 的默认 locale 可能是C.UTF-8或者en_US.UTF-8,导致中文显示异常。检查当前 locale:
locale如果LANG不是zh_CN.UTF-8或en_US.UTF-8,建议设置成 UTF-8:
export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8另外,标注文件的路径里尽量避免中文和空格,虽然理论上支持,但实际用下来,中文路径在某些导出环节还是会出问题。
3.3 首次启动后的界面配置
程序启动后,先别急着打开图片。有几个配置项需要提前设好,否则后面标注效率会大打折扣。
点击左侧工具栏的PascalVOC按钮,切换到PascalVOC格式(roLabelImg 默认就是这个,但确认一下更稳妥)。然后勾选View菜单里的Auto Save mode,这样每次画完一个框、切换到下一张图时,标注会自动保存,不用手动按 Ctrl+S。这个习惯能帮你省下大量重复操作。
还有一个关键设置是Save目录。默认情况下,标注文件会保存在图片同级目录。如果你的图片和标注需要分开存放,可以在File菜单里设置Change Save Dir,指定一个专门的标注输出目录。我个人的习惯是图片放images/,标注放annotations/,两个目录平级,方便后续训练脚本读取。
4. 旋转框标注的核心操作与参数详解
4.1 旋转矩形的数据结构长什么样
在讲操作之前,先搞清楚 roLabelImg 输出的旋转框到底是怎么表示的。这直接关系到你后续怎么解析标注文件、怎么转换成训练框架需要的格式。
roLabelImg 的标注文件是 XML 格式,一个典型的旋转框节点长这样:
<object> <name>vehicle</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <robndbox> <cx>512.0</cx> <cy>384.0</cy> <w>120.0</w> <h>60.0</h> <angle>0.785</angle> </robndbox> </object>和标准 VOC 格式最大的区别在于,bndbox变成了robndbox,里面用cx, cy, w, h, angle五个参数来描述一个旋转矩形。cx, cy是矩形中心的像素坐标,w, h是矩形的宽和高,angle是旋转角度,单位是弧度,范围是[-π/2, π/2]。
这里有一个非常容易踩的坑:angle 的参考方向。roLabelImg 里,角度是相对于水平方向的旋转量,逆时针为正。但不同版本的代码在这个定义上可能有细微差异,有的版本是顺时针为正。我建议你在正式标注之前,先画一个明显倾斜的框,导出 XML 看一眼角度值的正负,确认清楚再批量操作。否则标了几百张之后发现角度方向反了,返工成本极高。
4.2 画旋转框的完整操作流程
打开一张图片后,画旋转框的操作和普通矩形框类似,但多了旋转步骤。具体流程如下:
- 按
W键或者点击左侧的Create RectBox按钮,进入画框模式。 - 在目标左上角按下鼠标左键,拖到右下角松开,先画出一个水平矩形。
- 此时矩形四个角会出现控制点。把鼠标移到矩形边线中间的控制点上,光标会变成旋转样式。
- 按住鼠标左键拖动,矩形就会绕中心旋转。转到和目标方向对齐后松开。
- 在弹出的类别输入框里输入标签名,回车确认。
这里有个细节:旋转的控制点是在边的中点,不是在角上。角上的控制点是用来缩放矩形的。很多人第一次用的时候找不到旋转入口,就是因为一直在角上找。
注意:旋转的时候,矩形的宽高会随着旋转发生变化。如果你先画了一个很扁的矩形再旋转,旋转后宽高可能会互换。建议先画一个大致覆盖目标的框,旋转对齐后再微调宽高。
4.3 快捷键体系与效率提升
roLabelImg 的快捷键和 LabelImg 基本一致,但有几个在旋转框场景下特别有用:
| 快捷键 | 功能 | 使用场景 |
|---|---|---|
| W | 创建新框 | 开始标注新目标 |
| A | 上一张图 | 回看检查 |
| D | 下一张图 | 快速推进 |
| Ctrl+S | 保存 | 手动保存当前标注 |
| Ctrl+R | 修改当前框类别 | 标错类别时快速修正 |
| Delete | 删除选中框 | 删除误标的框 |
| 方向键 | 微调框位置 | 精细调整 |
我个人的习惯是左手放在 W、A、D 三个键上,右手握鼠标,形成"画框-切图-画框"的流水线节奏。熟练之后,一张有十几个目标的图,标注时间可以控制在两分钟以内。
另外,Edit菜单里有一个Rotate功能,可以对选中的框进行精确角度旋转。如果你需要标注的角度非常精确,比如文本行标注要求角度误差小于1度,用鼠标拖拽很难做到,这时候就用Rotate功能手动输入角度值。
4.4 类别管理与预定义标签
如果你的标注任务涉及多个类别,每次都手动输入标签名效率很低,而且容易打错字。roLabelImg 支持预定义标签列表。在源码目录下创建一个predefined_classes.txt文件,每行一个类别名:
vehicle ship aircraft storage_tank harbor然后在界面里勾选View菜单下的Use default label,之后画框时就会弹出下拉列表让你选,不用再手动输入。这个功能在多类别标注任务里能显著降低出错率。
5. 标注数据的导出、转换与质量检查
5.1 导出格式的选择与差异
roLabelImg 支持多种导出格式,但在旋转框场景下,真正可用的其实只有 PascalVOC 格式(也就是带robndbox的 XML)。YOLO 格式的导出在旋转框支持上并不完整,导出的还是水平框的坐标,角度信息会丢失。所以如果你要做旋转目标检测训练,标注阶段就用 PascalVOC 格式,后续再写脚本转换成训练框架需要的格式。
这一点很重要,我见过有人标完之后直接导出 YOLO 格式,结果角度全丢了,几百张标注白做。记住:roLabelImg 的旋转框信息只完整保存在 PascalVOC XML 里。
5.2 从 XML 到训练格式的转换思路
不同训练框架对旋转框的格式要求不一样。常见的几种表示方式包括:
- DOTA 格式:
x1 y1 x2 y2 x3 y3 x4 y4 category difficult,四个角点的坐标。 - COCO 旋转格式:
[cx, cy, w, h, angle],和 roLabelImg 的输出基本一致。 - 四边形格式:直接给四个顶点坐标。
从 roLabelImg 的cx, cy, w, h, angle转换成四个角点坐标,核心就是旋转矩阵运算。下面是一段可直接用的 Python 转换代码:
import math def rolabelimg_to_dota(cx, cy, w, h, angle): # 计算四个角点相对于中心的偏移 dx = w / 2.0 dy = h / 2.0 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.extend([rx, ry]) return points这段代码的逻辑很直白:先把矩形四个角相对于中心的偏移算出来,然后用旋转矩阵旋转,最后平移回中心坐标。注意 angle 的正负方向要和你的标注定义一致,如果发现转出来的框方向反了,把sin_a的符号取反即可。
5.3 标注质量的批量检查方法
标注做完之后,一定要做质量检查。人工一张张看效率太低,我通常写一个脚本做自动化检查,重点查以下几类问题:
第一类:坐标越界。检查cx, cy是否在图片尺寸范围内,w, h是否为正数。如果出现负数宽高或者中心点跑到图片外面,说明标注时出了问题。
第二类:角度异常。正常角度范围是[-π/2, π/2],如果出现超出这个范围的值,可能是旋转操作时出了问题。
第三类:类别名拼写。统计所有出现过的类别名,和预定义列表对比,找出拼写不一致的。
第四类:空标注文件。有些图片可能确实没有目标,但也要确认不是漏标。
下面是一个检查脚本的框架:
import os import xml.etree.ElementTree as ET import math def check_annotation(xml_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() issues = [] for obj in root.findall('object'): name = obj.find('name').text robndbox = obj.find('robndbox') if robndbox is None: issues.append(f"缺少robndbox: {name}") continue cx = float(robndbox.find('cx').text) cy = float(robndbox.find('cy').text) w = float(robndbox.find('w').text) h = float(robndbox.find('h').text) angle = float(robndbox.find('angle').text) if w <= 0 or h <= 0: issues.append(f"宽高异常: {name} w={w} h={h}") if cx < 0 or cx > img_w or cy < 0 or cy > img_h: issues.append(f"中心越界: {name} cx={cx} cy={cy}") if abs(angle) > math.pi / 2: issues.append(f"角度异常: {name} angle={angle}") return issues这个脚本可以批量跑整个标注目录,把有问题的文件列出来,然后针对性修复。比人工翻看效率高得多。
6. 实操中遇到的典型问题与排查实录
6.1 标注文件保存失败或保存为空
这个问题我遇到过好几次,表现是:画完框之后按 Ctrl+S,看起来保存了,但打开 XML 文件发现里面是空的,或者只有图片基本信息没有 object 节点。
排查下来,原因通常有两个。第一个是保存目录没有写权限。如果你把保存目录设到了一个需要 root 权限的位置,程序写入时会静默失败。解决方法就是chmod给写权限,或者换一个用户目录下的路径。
第二个原因是图片文件名包含特殊字符。比如文件名里有#、&、空格等,在某些版本的代码里会导致 XML 写入异常。建议在标注之前,先把图片文件名批量规范化,只保留字母、数字、下划线和短横线。
# 批量重命名示例 for f in *.jpg; do newname=$(echo "$f" | sed 's/[^a-zA-Z0-9._-]/_/g') mv "$f" "$newname" done6.2 旋转框显示异常或拖拽卡顿
在标注大尺寸图片(比如 4000x3000 以上的遥感图)时,roLabelImg 可能会出现拖拽卡顿、框显示错位的情况。这主要是 Qt 的渲染性能问题,不是程序 bug。
我的处理办法是:先把大图切分成小块再标注。比如把 4000x3000 的图切成 1000x1000 的瓦片,标注完再合并。这样不仅标注流畅,而且后续训练时也需要切图,一举两得。切图可以用 GDAL 或者 OpenCV 写个简单脚本:
import cv2 def split_image(img_path, output_dir, tile_size=1000, overlap=100): img = cv2.imread(img_path) h, w = img.shape[:2] idx = 0 for y in range(0, h, tile_size - overlap): for x in range(0, w, tile_size - overlap): tile = img[y:y+tile_size, x:x+tile_size] cv2.imwrite(f"{output_dir}/tile_{idx}.jpg", tile) idx += 1overlap 设置为 100 像素是为了避免目标被切断。切图之后标注,最后再把坐标映射回原图,这是遥感标注的标准流程。
6.3 角度值在转换后出现偏差
有朋友反馈说,在 roLabelImg 里看着框是对齐的,但转成 DOTA 格式之后,框的方向偏了。这个问题十有八九是角度定义不一致导致的。
roLabelImg 内部的角度定义、XML 里保存的角度值、以及你转换脚本里用的角度定义,这三者必须统一。我的建议是:拿一张有明显倾斜目标的图,在 roLabelImg 里标一个框,导出 XML,然后手动用转换脚本算一遍四个角点,把角点画回图上,看是否和原框重合。如果不重合,就调整转换脚本里的角度符号或旋转方向,直到完全对齐。这个验证步骤花不了十分钟,但能避免后面几千张标注全部返工。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 启动报 xcb 插件加载失败 | 系统缺少 xcb 相关库 | apt 安装 libxcb-* 系列库 |
| 画框后无法旋转 | 没找对控制点位置 | 鼠标移到边线中点,不是角点 |
| 保存的 XML 为空 | 目录无写权限或文件名特殊字符 | 检查权限,规范化文件名 |
| 角度转换后方向反了 | 角度定义不一致 | 验证并调整转换脚本符号 |
| 大图标注卡顿 | Qt 渲染性能瓶颈 | 切图后标注,再合并坐标 |
| 中文标签显示乱码 | locale 设置问题 | 设置 LANG=en_US.UTF-8 |
| 导出的 YOLO 格式没有角度 | 格式本身不支持旋转框 | 用 PascalVOC 格式,后续转换 |
7. 提升标注效率的实战经验
7.1 建立自己的标注规范文档
在开始批量标注之前,花半小时写一份标注规范,把以下内容定清楚:类别定义和边界、旋转框的贴合标准(是紧贴目标还是留一点余量)、最小可标注目标尺寸、遮挡目标的处理方式、角度标注的精度要求。
这份文档看起来是额外工作,但实际用下来,它能帮你省下大量返工时间。尤其是多人协作标注时,没有规范文档,不同人标出来的框风格差异很大,后续训练效果会受影响。
7.2 分批标注与交叉检查
不要一次性标完所有数据再检查。我的做法是:每标 100 张,就停下来做一次抽查,随机抽 10 张检查质量。如果发现系统性问题(比如某个类别定义不清、角度方向标反了),及时修正,避免错误累积。
抽查的时候,可以用前面提到的检查脚本先跑一遍自动化检查,再人工看几张。自动化检查能抓住坐标越界、角度异常这类硬错误,人工检查则关注框的贴合度和类别正确性。
7.3 利用预标注减少重复劳动
如果你的数据里同类目标比较多,可以考虑先用一个粗略训练的模型做预标注,然后导入 roLabelImg 里微调。roLabelImg 支持读取已有的 XML 标注文件,你可以把模型输出的旋转框转成 roLabelImg 格式的 XML,然后人工只需要微调角度和边界,不用从零画框。
这个流程的前提是你已经有一个能输出旋转框的模型。如果没有,也可以先用水平框模型做预标注,导入后手动旋转调整。虽然还是要人工旋转,但至少省去了画框和输入类别的步骤。
7.4 定期备份标注文件
标注数据是真正的劳动成果,丢了就没了。我建议每天标注结束后,把标注目录打包备份一次。可以用简单的tar命令:
tar -czf annotations_backup_$(date +%Y%m%d).tar.gz annotations/如果标注量大,可以配合rsync同步到另一块硬盘或者网络存储。这个习惯看起来不起眼,但真出问题的时候能救命。我见过有人标了两周的数据,因为硬盘故障全没了,只能从头再来。
8. 从标注到训练的数据流转建议
标注只是第一步,标注数据最终要喂给训练框架。这里简单说一下数据流转的链路,帮你把整个流程串起来。
roLabelImg 输出 PascalVOC XML,这是标注的原始格式,建议永久保存。然后写一个转换脚本,把 XML 转成训练框架需要的格式。如果你用的是基于 DOTA 的旋转检测框架,就转成 DOTA 格式的 txt;如果用 COCO 风格的框架,就转成 COCO JSON 的旋转框扩展格式。
转换之后,建议做一次可视化验证:把转换后的框画回原图,和原标注对比,确认没有偏差。这个验证步骤可以用 OpenCV 写个简单脚本完成:
import cv2 import numpy as np def draw_rotated_box(img, cx, cy, w, h, angle, color=(0, 255, 0)): rect = ((cx, cy), (w, h), np.degrees(angle)) box = cv2.boxPoints(rect) box = np.int0(box) cv2.drawContours(img, [box], 0, color, 2) return img把转换后的参数传进去,画出来的框如果和原图目标对齐,说明转换没问题。这个验证做一次就够了,确认脚本正确之后,后续批量转换都可以放心用。
数据集的划分也要在转换阶段完成。通常按 8:1:1 划分训练集、验证集、测试集。划分时要注意同一场景的图片尽量分到同一个集合,避免数据泄漏。比如同一张遥感大图切出来的瓦片,应该全部放在训练集或者全部放在测试集,不能混着放。
最后再分享一个小技巧:在标注目录里放一个README文件,记录标注规范、类别列表、标注人员、标注日期、使用的 roLabelImg 版本。过几个月再回头看这批数据,这些信息能帮你快速回忆起当时的标注背景,避免因为时间久了记不清而误用数据。