实例分割数据标注实战:自研轻量在线COCO标注工具

发布时间:2026/8/31 3:47:47
实例分割数据标注实战:自研轻量在线COCO标注工具 简介这是一款基于Python开发的轻量级在线图像标注工具专为深度学习实例分割任务如Mask R-CNN训练设计支持矩形框、多边形、点、线等多种标注类型适用于计算机、人工智能、自动化等专业学生课程设计、毕设开发及模型数据准备场景零基础小白亦可快速上手。资源包共54个文件包含15张PNG/JPG示例图与GIF动图用于界面演示5个核心JS脚本含webAnnotate.js主逻辑、4类CSS样式文件含Font Awesome图标库与响应式布局、HTML入口页及详细README.md说明文档整体压缩包仅11.01MB结构清晰、开箱即用。已有143人下载学习项目源自高校课程设计经完整测试运行验证答辩平均分96分附带远程答疑支持用户可直接部署使用也可基于源码二次开发拓展功能是兼顾教学性、实用性与工程规范性的高质量标注工具实践案例。 做深度学习项目尤其是实例分割这种任务最绕不开的环节就是数据标注。你以为模型在比赛谁跑得快实际上大家都在拼标注质量。我第一次跑 Mask R-CNN 用公开的垃圾图片数据集效果还不错换成自己拍的一批工业零件图像以后Loss 直接起飞训练完 mAP 只有个位数。排查了整三天最后才发现是标注 JSON 里有个多边形点坐标落到了图像外沿模型学了一堆根本不存在的轮廓。从那之后我就彻底想明白一个道理标注工具不是锦上添花而是训练流程里最容易被忽略的瓶颈。所以后来我花了两周时间从头写了一套轻量的在线图像标注工具支持矩形框、多边形还带源码头和配套文档。这篇文章就把这个项目的完整思路、核心代码、踩坑过程和文档结构都摆出来希望能帮到正在为实例分割准备数据的人尤其是刚接触 Mask R-CNN、YOLO 这类模型的朋友。1. 为什么我决定自己写一个在线图像标注工具1.1 从一次 Mask R-CNN 训练翻车说起先交代个背景。当时项目目标是用 Mask R-CNN 做工业零件检测要求不只是框出物体还要把每个零件的轮廓抠出来。用了市面上常见的标注工具界面挺漂亮的导出的 JSON 格式也主流但真正开始训练时问题一个接一个。最大的问题是标注体验和模型需求脱节。Mask R-CNN 要的 mask 是像素级轮廓最合适的标注方式是多边形但有些工具默认给的是矩形或者需要付费功能才能解锁多边形。还有团队几个人同时标注文件传来传去版本对不上今天标的人覆盖了昨天的结果训练时发现有的图片标注文件缺失有的类别 id 重复。另外一个更隐蔽的问题是标注导出的数据里带有各种无意义的干扰字段。很多图形界面工具为了兼容自家生态往 JSON 里塞了一堆自定义属性模型训练脚本解析的时候很容易被带偏。所以磨了几天之后我决定按训练需求倒推开发一个只服务实例分割的在线标注工具上传图像、画矩形、画多边形、打标签、导出 COCO 格式。功能做到极简但每一步都对标训练脚本的实际输入。1.2 市面上的工具到底哪里不够用有现成工具为什么还要自己写这话要先放在桌面上讲清楚。常见的开源工具有 Labelme、LabelImg、CVAT它们各有各的定位但当你真正投入一个训练项目时不少需求它们覆盖不到。LabelImg 适合快速画矩形框对目标检测友好但多边形支持比较弱面对实例分割场景就有点拉胯。Labelme 支持多边形也接近我需要的功能但它是桌面软件多人协作时要自己折腾环境导出格式虽然也走 JSON但严格说和 COCO 标准还有细节差异。CVAT 功能很全在线协作也成熟但部署起来偏重配置权限、组织任务、安装依赖小团队和单人项目用起来杀鸡用牛刀。更关键的一点我对标注工作流有特殊诉求。比如我希望某种场景下允许标注助手半自动生成预标注框希望导出时自动过滤掉面积过小的多边形希望标注中途可以自动保存到服务器防止丢失。这些用现成工具要么改源码要么加一堆复杂配置还不如自己写一个贴合需求的轻量方案。如果你的项目只是随便标注几百张图做 demo 验证完全可以选现成工具但如果你要正经训练一个实例分割模型手里有个自己可控的标注工具很多意外问题都能主动排查掉而不是在别人的代码里猜。1.3 这个工具到底帮你解决什么问题说白了这个工具解决的是从原始图片到可直接送入模型训练的数据集这一段路。核心解决的问题就三个。第一是统一格式。不管你是画矩形还是多边形最终导出的 JSON 严格按 COCO 格式组织包含 info、licenses、images、annotations 和 categories 五个部分训练 Mask R-CNN 时直接用 pycocotools 读取就行。第二是多人协同。通过 Web 服务部署任何浏览器都能打开标注结果实时回传到服务器只用一套标签文件避免本地文件互相覆盖的问题。第三是数据校验。这个功能是最容易被忽视的。工具会在导出前校验每一条标注框是否超出图像边界多边形面积是否为零类别名称是否合法id 是否有重复。等于在进入训练之前先做一轮数据清洗。2. 整体架构设计与核心原理2.1 技术选型为什么用 Web 而不是桌面先解释一下为什么定位成在线工具。我之前也纠结过桌面应用毕竟 Electron 可以做但后来发现标注这个动作天然就适合 Web 化。你需要把图片放在服务器上后端统一管理目录前端只管绘图数据往 API 一交就算完。浏览器天然跨平台Windows、macOS、Linux 都能用不用装 Python 环境也不用装一堆依赖。团队场景下成员打开浏览器就能干活权限和任务分配都在后端处理。架构上我用的是 Flask 后端加原生 HTML/CSS/JavaScript 前端。Flask 选型的原因很简单项目规模不大不需要 Django 这种全家桶而 Flask 结构清晰路由灵活做文件上传和 JSON 接口最顺手。前端没有引入 React 或 Vue直接用 Canvas 2D 画图。标注界面其实就两块一块是图片显示画布一块是侧边属性面板用原生的 canvas 反而更灵活不用被框架约束状态更新的时序问题。有意思的是很多类似项目会为了显得专业强上前后端分离结果开发复杂度高了一截标注场景性能也没有质的提升。我这个项目走的是务实的单人开发路线所有代码能少则少能简则简每个文件都有明确职责。2.2 数据格式为什么最终落成 COCO JSON这里要展开说一下格式问题因为这是训练一个通用实例分割模型最容易踩坑的地方。COCO 数据集的标注格式是业界事实标准Mask R-CNN 的官方实现、Detectron2、MMDetection 这些框架默认都能读 COCO JSON。一个标准的 COCO JSON 长这样{ info: { description: Example Dataset, version: 1.0, year: 2024 }, images: [ { id: 1, file_name: image_001.jpg, width: 1920, height: 1080 } ], annotations: [ { id: 1, image_id: 1, category_id: 1, bbox: [x, y, width, height], area: 123456.5, segmentation: [ [x1, y1, x2, y2, ...] ], iscrowd: 0 } ], categories: [ { id: 1, name: bolt, supercategory: component } ] }几个关键点要提醒一下都是训练时会爆炸的地方bbox 必须是 [左上角 x左上角 y宽度 width高度 height]不是中心点坐标也不是右下角坐标。area 是掩码区域的像素面积图像分割训练时这个值有时会影响 mask 的损失权重必须算准。segmentation 是多个数组嵌套的结构多边形点坐标要按顺序排首尾不用闭合框架会自动闭合。我自己第一次写导出脚本时bbox 用了中心点加宽高结果训练时的目标框全对不上损失曲线的收敛方向都是错的后来对着 COCO 官方文档一条一条核对才修正过来。这个项目好就好在把格式问题封装在导出模块里你画完多边形后端自动算面积、自动算 bbox、自动转成 COCO 数组不用你操心。2.3 标注过程的几何计算原理标注看起来只是沿着物体边缘点一圈点但背后的几何逻辑很直接。每张图像的标注信息本质上是一个从像素坐标集合到物体类别标签的映射。矩形标注很简单就是用户按下鼠标左键的点作为左上角拖动到松开鼠标的点作为右下角记录下这两个点就能得到 bbox。但实际画框时会遇到一个问题用户可能从右下角往左上角拖也可能从中心往外拖。为了统一工具里会在鼠标松开后立即做一次坐标归一化保证 x1 x2 且 y1 y2。多边形标注要稍微复杂一点本质是维护一个顶点数组。用户每次点击就往数组里追加一个点双击或点击闭合按钮就把首尾相连形成一个闭合多边形。在导出时需要额外计算两样东西面积用鞋带公式对多边形所有顶点做叉积累加再除以 2。bbox遍历所有顶点取 x 最小值和最大值、y 最小值和最大值就能算出外接矩形。鞋带公式的代码很短我直接贴出来def polygon_area(points): area 0.0 n len(points) for i in range(n): x1, y1 points[i] x2, y2 points[(i 1) % n] area x1 * y2 - x2 * y1 return abs(area) / 2.0这个面积值直接作为 COCO JSON 里 annotation 的 area 字段是 mask 面积的一阶近似。如果标注对象是规则物体这个值基本等于真实像素面积如果是复杂形状可能会有小偏差但对训练影响不大。3. 核心功能实现与实操操作3.1 一个矩形标注从点击到保存的完整链路这部分是整个工具的使用主线我把每一步拆开讲。打开首页后第一步是上传图片。工具支持一次上传多张图片后端会把文件保存到服务器指定目录同时往图片列表里追加记录。图片列表会显示缩略图点击缩略图进入标注页面。标注页面最核心的是画布操作。工具栏左侧有矩形、多边形、选择/编辑、删除四个按钮。选择矩形模式后在画布上按下鼠标左键会看到实时画出一个半透明蓝色矩形拖动时对角线会跟着鼠标移动。松开左键后矩形自动吸附固定同时右侧属性面板会出现一个类别下拉框你可以给这个框选一个类别。保存的逻辑是这样的前端把当前图片的所有标注对象存在一个内存数组里每个对象的结构是{ id: annotation_1, type: rect, category: bolt, points: [x1, y1, x2, y2] }点击保存本张按钮后前端把这个数组通过 POST 请求发到后端后端解析后更新全局数据集的 JSON 文件并返回当前图片的标注 id。你看这个过程其实很朴素没有花哨的拖拽组件但稳定可靠而且完全围绕训练数据需求设计。3.2 多边形标注的实现与交互细节多边形标注是实例分割最核心的能力操作细节上我做了不少打磨。进入多边形模式后在物体边缘依次单击每一个点击都会生成一个实心小圆点表示顶点位置。如果点错了可以右键点击顶点直接删除如果想撤销上一步按 Backspace 会回退到上一个顶点。多边形闭合有两种方式一种是双击鼠标左键工具自动把最后一个顶点和第一个顶点连接起来另一种是点击工具栏的闭合按钮。闭合之后多边形内部会填充半透明颜色方便看清覆盖范围。因为很多标注对象形状不规则偶尔会出现多边形顶点绕了一圈把自己打结的情况。为避免这个问题工具在闭合时做了一次简易自交检测如果发现线段交叉会弹出警告提示用户手动调整顶点顺序。这个检测不是 100% 精确但能拦截大多数低级失误。在多边形绘制时还要处理一个很容易被忽略的问题图像缩放。用户用滚轮缩放图像后画在 canvas 上的像素坐标和原始图像坐标之间有一个缩放比例。我们内部统一用图像原始坐标系存储前端展示时再根据 scale 值换算。换算公式就一句话const rawX (mouseX - canvasOffsetX panOffsetX) / scale; const rawY (mouseY - canvasOffsetY panOffsetY) / scale;所有保存到后端的点都是原始坐标不会随着缩放级别变化漂移。这个细节如果处理不好就会出现标注时看着很准导出的坐标全偏了的问题。3.3 标注数据导出与训练集划分标注完所有图片后点击导出 COCO JSON按钮后端会遍历所有图片和标注记录生成一个符合 COCO 标准的 JSON 文件供下载。这里一定要说清楚COCO JSON 的 images 数组和 annotations 数组的 id 必须是一一对应的连续整数。很多自己写工具的开发者会在这里踩坑annotation 的 image_id 指向的图像不存在或者 images 数组里的 id 有重复训练脚本读取时报 KeyError。我在导出模块里加了一套自动编号逻辑先给 images 排 id再给 annotations 排 id同时用 image_id 关联。这个顺序永远是固定过程所以导出的数据不会出现引用问题。训练集划分我放在导出之后单独处理。工具会提供一个划分数据集按钮按默认的 8:1:1 比例把图片随机分成 train、val、test 三个子集并在每个子集里生成对应的 COCO JSON。这样拿到压缩包后直接能喂给训练脚本跑不用再写额外的数据拆分代码。4. 项目代码结构与关键模块解读4.1 后端 API 设计要点整个项目后端代码规模不大核心文件就三个app.py、dataset.py、coco_exporter.py。app.py负责所有路由和请求处理主要接口如下接口路径方法功能/api/uploadPOST上传图片保存到 images 目录/api/imagesGET获取图片列表/api/images/image_idGET获取单张图片信息及已有标注/api/annotations/image_idPOST保存某张图片的全部标注/api/export/cocoGET导出 COCO JSON 文件/api/splitGET按比例划分训练集、验证集、测试集接口设计遵循一个原则前端无状态后端统一管理所有标注数据。哪怕浏览器意外关闭重新打开时也能从后端把所有已保存的标注拉回来不会丢数据。每次保存接口都做全量保存而不是增量保存这样避免了并发写冲突导致标注错乱的问题。4.2 前端 Canvas 绘制与事件处理前端就一个main.js但里面做的事情比较密集。主要分三块画布初始化、鼠标事件处理、标注对象渲染。画布初始化要注意两点。一是设置 canvas 的宽度和高度与图片实际尺寸保持一致不然绘制会模糊二是要处理好图片加载的异步事件图片没加载完就绑定鼠标事件会导致画布坐标对不上。鼠标事件的核心是模式分发。用一个mode变量标记当前是rect、polygon还是select模式。在 mousedown、mousemove、mouseup 里分别判断当前模式执行相应的逻辑。多边形模式下mousedown 追加顶点矩形模式下mousedown 记录起点mouseup 记录终点并生成矩形对象。渲染逻辑相对常规但有一个提升体验的小技巧绘制半透明遮罩时会在标注对象上方再画一层描边让轮廓更清晰。比如矩形选择后边框用亮蓝色 2px 描边多边形闭合后内部填充 20% 透明度的蓝色外轮廓画 2px 实线。这样用户一眼就能看清当前标注的对象范围减少视觉误判。4.3 标注数据持久化方案标注数据是项目里最珍贵的资产所以持久化方案我选了最简单的方案JSON 文件加目录存储。具体来说后端维护一个dataset/目录里面结构如下dataset/ ├── images/ │ ├── image_001.jpg │ └── image_002.jpg ├── annotations.json └── categories.jsonannotations.json是全部图片标注的汇总外层用图片 id 做键。保存接口收到前端数据后会更新对应图片的标注列表然后立即写回文件。这个方案的好处是显而易见的存储无状态、备份简单、版本管理也方便用 Git 也能追踪标注文件的变更。有人会问为什么不用数据库对于几千张图片、几万个标注对象的体量JSON 文件完全够用读写都非常快而且零配置。相比引入 MongoDB 或者 PostgreSQLJSON 文件方式在单人开发和中小团队场景下反而最不容易出错。我的观点永远是能用简单方案解决的事情就不要让项目复杂度失控。5. 踩坑实录标注质量如何影响模型训练5.1 错误标注真的会让 Loss 降不下来吗回到开头那个问题。很多人会问错误标注真的会导致模型训练集 loss 降不下来吗答案是一定会而且表现形式非常迷惑。Loss 降不下来的本质是模型在拟合一个内部不一致的训练集。你让模型去学一个螺栓的轮廓结果有些标注框把螺母也框进去了有些多边形只框了一半模型在不同迭代里看到的目标形状和位置都不一样它无法找到一个稳定的映射关系梯度更新方向混乱loss 自然就卡在一个高位上下波动。我印象最深的一次翻车是标注时不小心把一个类别名称拼错了。比如图片里的零件明明是 bolt但有一部分标注写成了 Bolt另一部分写成了 bolt。训练脚本把这两个当成两个不同的类别Mask R-CNN 需要额外学一个类别但数据里这两个类别的标注又互相重叠。结果整个模型训练了 60 个 epoch 后 mAP 还是没超过 10%后来查数据才发现问题是类别命名不统一。所以我在工具里做了类别池机制所有标注类别必须先在后端注册前端只能从下拉框里选择不能手动输入自由文本。这个限制虽然看着繁琐但能从根本上杜绝不同人写出来不同类别名的问题。5.2 常见问题速查表这块我整理成一张表遇到问题直接对照排查。问题现象可能原因排查与解决导出的 COCO JSON 解析失败annotations 里有 image_id 对应不上检查 images 与 annotations 的 id 是否一致用代码批量校验训练时 loss 超高且不下降标注边界严重超出图像、类别名不一致用校验脚本检测超出边界的点统一类别命名mask 和 bbox 对不齐多边形的 bbox 计算错误重新生成 bbox用多边形顶点取 min/max多人标注后数据丢失本地文件互相覆盖改用在线工具统一保存避免多个客户端维护副本图片无法加载文件路径含中文或空格统一用英文文件名后端存储时自动重命名多边形自交导致 mask 异常绘制时顶点顺序错乱闭合时做自交检测手动调整顶点顺序5.3 我的几条实用建议最后分享几条我自己长期干这活攒下来的经验。第一每完成一批标注立刻导出一次 COCO JSON并用一个小脚本做完整性校验检查图片和标注数量是否一致、每个多边形面积是否大于零、所有点坐标是否都在图像范围内。这些校验虽然基础但能省下你后面至少一天的排查时间。第二多边形顶点的数量不是越多越好。有些人标注复杂物体时恨不得每个像素都加一个点结果多边形文件巨大训练时 mask 编码也变慢。建议一条边控制在 20 到 30 个点以内既保证轮廓精度又不至于过度拟合标注噪声。过度细致的轮廓反而会限制模型的泛化能力。第三标注时可以把同一个物体的不同角度、不同光照条件都覆盖到。实例分割模型对形变的敏感度比目标检测高很多如果只标注了物体正面的轮廓训练出来的模型遇到侧面的物体就识别不好。多搜集实际场景里的形态比在标注界面上追求完美多边形更重要。第四如果你要训练的领域比较特殊比如遥感图像建议把标注任务分成几轮。第一轮先用矩形粗筛找出所有疑似目标第二轮针对矩形框内的目标精细勾画多边形。这种方式能明显提高效率避免一开始就在大图上仔细抠轮廓抠完才发现目标选错了。另外还有一个细节我自己实际使用中发现在在线工具里标注大的遥感图时会特别卡因为 canvas 要同时绘制底图和大量标注层。解决办法是把底图分成瓦片加载标注层只绘制当前视野范围。这个优化我在后续版本里会补上但如果你只是标注普通尺寸的图片完全够用。6. 后续值得继续扩展的方向工具做到目前这个程度已经能满足单人或小团队从零构建实例分割数据集的全部需求。但如果你的项目规模再大一点或者场景更特殊有几个方向值得继续扩展。一是加入预标注功能。用现成的检测模型跑一遍目标框出来再让标注员修正也就是人工复核 机器预标注的工作流。这一步能大幅缩短从零标注的时间让标注员把精力放在修正边界上。二是支持 YOLO 格式导出。我的工具目前主打 COCO但如果你想先训练一个 YOLO 检测模型做候选框生成再配合 Mask R-CNN那额外的导出格式就特别有用。格式转换无非是坐标系从像素转成归一化值代码量不大但能覆盖更多训练框架。三是增加团队权限管理。现在工具默认所有可以使用的人都能编辑全部数据。如果团队人多可以给不同成员分配任务范围比如甲负责前 100 张图乙负责后 100 张图后台统一收口数据质量。这个功能实现起来也不难思路就是给用户表加一个任务 ID 字段。四是自动备份。标注数据一旦丢了就很惨所以可以在服务器上配置定时任务每天压缩dataset/目录到另一个存储路径。我自己的项目就加了一行 cron 命令每天凌晨三点打包虽然简单但心里踏实多了。说了这么多我最想表达的一点是标注工具本身不是目的它只是连接现实物体和模型参数之间的桥。桥搭得稳训练才能顺利桥上一块木板松了后面所有环节都会跟着摇摆。这套工具虽然开发时间不长设计也不算花哨但每一处代码都是根据实际训练需求倒推出来的。如果你也正在为实例分割数据头痛不妨按这个思路做一个属于自己的工具或者直接借鉴我这里的架构和踩坑经验让你接下来的训练少走点弯路。本文还有配套的精品资源点击获取