
简介labelImg-master图像标注工具源码包面向计算机视觉方向的研究人员、算法工程师与需要自建数据集的学生用于快速完成图像边界框与多边形区域标注输出PASCAL VOC、COCO等数据集常用的XML标注文件。压缩包共118个文件约6.95MB以png界面图标、py核心源码、pyc编译缓存、svg矢量图与sh脚本为主另含cfg、in、qrc、license等工程配置与许可文件完整保留了项目的构建与打包结构。其中主程序实现图形界面与标注交互逻辑配置与安装脚本负责环境搭建和依赖管理资源文件支撑界面显示整体目录清晰便于按模块阅读与二次开发。已有706人学习下载。通过梳理源码与配置读者可理解标注工具的工作流程掌握从加载图像、绘制标注框到保存结果的实现思路并在此基础上定制类别、快捷键或导出格式满足特定场景下的标注需求。1. 拿到 labelImg-master 压缩包之后它到底解决什么问题如果你手头有一批图片要喂给目标检测模型却卡在“框从哪来”这一步labelImg 大概率是你绕不开的第一个工具。它就是一个用 Python Qt 写的轻量级图像标注客户端核心工作只有一件事在图片上画矩形框、打上类别标签然后导出成模型能直接读的标注文件。你拿到的labelImg-master压缩包本质是它的源码目录不是装好就能双击运行的 exe。这意味着你需要自己补 Python 环境和依赖好处是版本可控、能改源码、能嵌进自己的流水线。这篇笔记面向两类人刚接触图像标注、想用最短路径跑通第一份数据集的新手以及已经用过但被路径、格式、中文类别名反复折腾的老手。下面从环境搭建讲到格式转换再到批量标注的提速技巧每一步都给出可复现的命令和参数。2. 把 labelImg-master 跑起来环境、依赖与启动命令2.1 为什么推荐源码方式而不是直接找打包好的 exe网上能搜到不少 labelImg 的免安装包双击就能用但我不建议在正式项目里这么干。原因有三个第一打包版往往锁死了某个旧版本导出格式和快捷键可能和你查到的教程对不上第二你无法确认打包者有没有夹带别的东西第三源码方式能让你在libs目录里改默认类别、改框的颜色、甚至加自定义导出格式。labelImg-master这个目录名说明你拿到的是 GitHub 上的源码快照解压后应该能看到labelImg.py、libs/、requirements/这些条目。先确认目录结构再动手装环境。2.2 用 conda 隔离环境并安装依赖labelImg 对 PyQt5 和 lxml 的版本比较敏感直接往系统 Python 里装很容易和已有包打架。我一般用 conda 开一个干净环境# 创建独立环境Python 版本选 3.9兼容性最稳 conda create -n labelimg python3.9 -y conda activate labelimg # 进入解压后的源码目录 cd labelImg-master # 安装依赖requirements 目录下按平台分文件 pip install -r requirements/requirements-linux-python3.txt如果你在 Windows 上把最后一行换成requirements/requirements-windows-py3.txt。装完后用pip list | grep -i pyqt确认 PyQt5 在位。这里有个细节某些系统里requirements文件写的是pyqt55.15.4这类固定版本别自作主张升级到 6.xQt6 的 API 变动会让 labelImg 启动直接报cannot import name错误。2.3 启动命令与首次运行要设的三个参数依赖装好后在源码根目录执行# 直接以模块方式启动避免路径问题 python labelImg.py窗口弹出来后别急着画框先点左侧工具栏的PascalVOC按钮把它切换成YOLO或保持 VOC取决于你的训练框架。然后按CtrlU或点Open Dir选中你的图片文件夹。首次运行必须确认三件事保存目录Change Save Dir是否指向你想要的标注输出位置默认类别文件Predefined Classes是否加载了你准备好的classes.txt自动保存模式View菜单里的Auto Save mode是否打开。这三项没设好画一百个框也可能白干。提示classes.txt每行一个类别名不要有空行不要用中文逗号分隔编码用 UTF-8。类别名里带空格是允许的但带斜杠或冒号会在某些导出格式里出问题。3. 标注格式怎么选VOC、YOLO、CreateML 的差异与转换3.1 三种导出格式的字段对照labelImg 支持 Pascal VOC XML、YOLO TXT、CreateML JSON 三种主流格式。选错格式不会让标注白做但会让你在训练脚本里多写一层转换。下面这张表把关键字段列清楚格式单张标注文件坐标表示类别存储适用框架Pascal VOC.xml左上右下绝对像素文件内name标签MMDetection、Detectron2YOLO.txt归一化中心点宽高独立classes.txt按行号索引YOLOv5/v8、DarknetCreateML.json左上右下绝对像素文件内label字段Apple CreateMLYOLO 格式最容易踩的坑是类别索引它不存类别名只存一个数字这个数字对应classes.txt里的行号从 0 开始。如果你中途改了classes.txt的顺序之前所有标注的类别就全错位了。所以我的习惯是项目一开始就定死classes.txt整个标注周期内只增不改新增类别一律追加到末尾。3.2 从 VOC 转 YOLO 的脚本与边界处理很多人的流程是先用 VOC 标再转 YOLO 训练。转换脚本网上一搜一大把但能正确处理边界情况的没几个。下面这个版本我用了两年处理了空标注文件、坐标越界、图片尺寸读取失败三种情况import os import xml.etree.ElementTree as ET from PIL import Image # voc_dir: VOC xml 所在目录img_dir: 对应图片目录out_dir: YOLO txt 输出目录 def voc_to_yolo(voc_dir, img_dir, out_dir, classes): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(voc_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(voc_dir, xml_file)) root tree.getroot() # 用 PIL 读真实尺寸不信任 xml 里的 size 字段 img_path os.path.join(img_dir, xml_file.replace(.xml, .jpg)) try: with Image.open(img_path) as im: w, h im.size except FileNotFoundError: print(f图片缺失跳过: {img_path}) continue lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: print(f未知类别 {cls_name}跳过该框) continue cls_id classes.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 裁剪到图像范围内防止标注时手抖画出界 x1, x2 max(0, x1), min(w, x2) y1, y2 max(0, y1), min(h, y2) # YOLO 需要归一化的中心点坐标和宽高 cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) # 即使没有目标也写空文件YOLO 训练时把空文件当负样本 with open(os.path.join(out_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明先读图片真实尺寸而不是 XML 里的size因为 labelImg 在某些缩放状态下写入的尺寸可能不准。坐标裁剪那两行是后悔药标注时鼠标拖出图像边界是常事不裁的话归一化后会出现负数或大于 1 的值YOLO 训练时直接报invalid label。空文件必须保留否则那些没有目标的背景图会被训练脚本当成“未标注”而忽略负样本就丢了。参数说明classes是一个列表顺序必须和classes.txt完全一致out_dir要和图片目录同级YOLO 默认按images/和labels/平行查找。3.3 转换后怎么验证没出错转完别直接开训先做两件事。第一随机抽三张图用cat看对应的 txt确认类别 id 和框数量对得上。第二写个十行脚本把 YOLO 框画回图片上肉眼比对import cv2 def draw_yolo(img_path, txt_path, classes): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cid, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, classes[int(cid)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check.jpg, img)跑一遍看check.jpg框的位置和类别名都对才算转换成功。这一步花两分钟能省掉训练时对着 loss 曲线发呆的两小时。4. 标注效率与常见翻车现场排查4.1 快捷键与批量操作的正确姿势labelImg 的效率全在快捷键上。W画框A上一张D下一张CtrlS保存Del删除选中框。但真正提速的是两个隐藏操作一是CtrlShiftR批量重命名当前图片的标注文件适合图片编号混乱时统一二是按住Ctrl滚动滚轮缩放图片比点工具栏按钮快得多。我一般会把View里的Auto Save mode打开这样切图时自动保存不用每张都按CtrlS。但注意自动保存只在标注文件已存在或保存目录已设置时生效第一次标注某张图仍需手动保存一次来创建文件。4.2 避坑五条血泪经验现象一启动报ModuleNotFoundError: No module named libs.resources。原因在错误的目录下执行了python labelImg.py或者源码目录不完整。 解决cd到包含labelImg.py的那一层再执行确认libs/目录存在且里面有resources.py。现象二画完框切到下一张回来发现框没了。原因没设保存目录或者保存目录设成了只读路径。 解决点Change Save Dir选一个可写目录确认该目录下生成了对应的.xml或.txt文件。现象三YOLO 格式导出后训练时报Label class 3 is not in the models class list。原因classes.txt的行数和训练配置里的nc不一致或者类别顺序变了。 解决核对classes.txt行数等于nc且顺序与训练时一致。改过顺序的话所有标注文件都要重新生成。现象四中文类别名在 VOC XML 里显示正常转 YOLO 后变成乱码或索引错位。原因classes.txt用了 GBK 编码Python 读取时按 UTF-8 解码失败。 解决用file classes.txt确认编码统一转成 UTF-8。类别名建议用英文或拼音省去编码麻烦。现象五图片很多时labelImg 打开目录卡死。原因目录下混入了非图片文件或者图片尺寸过大比如 8000 像素以上。 解决标注前把图片统一缩放到 2000 像素以内并把目录里的.DS_Store、.txt等非图片文件移走。labelImg 会尝试加载目录下所有文件非图片文件会让它反复报错拖慢速度。4.3 多人协作时怎么合并标注两个人同时标一个数据集合并时最容易出的问题是图片重名和类别定义不一致。我的做法是按图片编号分段A 标 0001-0500B 标 0501-1000各自用同一份classes.txt。合并时直接把两个输出目录的标注文件拷到一起图片也拷到一起重名冲突自然避免。如果已经标重了用md5sum比对图片内容去重再决定保留哪份标注。合并后务必跑一遍 3.3 节的可视化验证抽检比例不低于 5%。5. 把 labelImg 嵌进流水线预标注与自动化的进阶玩法5.1 用模型预标注减少手画框的工作量纯手工画框在几百张图上还能忍上千张就是折磨。我的做法是先用一个粗糙的模型跑一遍推理把预测框写成 VOC XML再用 labelImg 打开这些 XML 做人工修正。labelImg 会自动加载同目录下同名的 XML 文件你只需要删掉错框、补上漏框、改掉错类别比从零画快三到五倍。预标注模型不需要很准mAP 0.3 以上就能显著减少工作量。下面是把模型输出转成 VOC XML 的最小代码import os from xml.etree.ElementTree import Element, SubElement, ElementTree def write_voc_xml(out_path, img_name, img_w, img_h, boxes, classes): # boxes: [(cls_name, x1, y1, x2, y2), ...] root Element(annotation) SubElement(root, filename).text img_name size SubElement(root, size) SubElement(size, width).text str(img_w) SubElement(size, height).text str(img_h) SubElement(size, depth).text 3 for cls_name, x1, y1, x2, y2 in boxes: obj SubElement(root, object) SubElement(obj, name).text cls_name bbox SubElement(obj, bndbox) SubElement(bbox, xmin).text str(int(x1)) SubElement(bbox, ymin).text str(int(y1)) SubElement(bbox, xmax).text str(int(x2)) SubElement(bbox, ymax).text str(int(y2)) ElementTree(root).write(out_path, encodingutf-8, xml_declarationTrue)逻辑说明VOC XML 的size字段必须和图片真实尺寸一致否则 labelImg 打开时框会偏移。depth写 3 表示三通道灰度图写 1。生成的 XML 文件名必须和图片文件名不含扩展名完全一致labelImg 靠这个匹配。参数说明boxes里的坐标是绝对像素值不是归一化值classes参数在这个函数里没用到但保留是为了和前面的转换脚本接口一致。5.2 用命令行参数跳过 GUI 做格式转换labelImg 本身有个隐藏的命令行模式可以在不打开窗口的情况下批量转换格式# 把 VOC 目录批量转成 YOLO不启动 GUI python labelImg.py --help实际用下来它的命令行转换功能比较弱不如自己写脚本灵活。但--help里能看到--output和--format参数适合在 CI 流水线里做轻量转换。我一般只在 Docker 构建阶段用它做一次格式校验正式转换还是用 3.2 节的脚本。5.3 一个我坚持了三年的习惯每次标注完一个数据集我会在目录里留一个README.md写清楚四件事classes.txt的类别顺序、标注总图片数、标注人、标注日期。这个习惯救过我两次——一次是三个月后要加新类别翻出 README 确认了原顺序避免了全量重标另一次是交接给同事时对方五分钟就理解了数据组织方式。labelImg 本身不生成任何元数据这些信息只能靠人记而人是最不可靠的存储介质。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取