简介面向使用 Label Studio 搭建半自动标注流水线的算法工程师与标注团队这份资源提供 YOLOv8 旋转框OBB目标检测模型接入 Label Studio ML 后端所需的 Model.py 实现文件用于打通模型推理与标注平台之间的交互减少人工逐框绘制的工作量。压缩包内共 1 个文件为单个 py 脚本体积约 2KB属于轻量级后端适配代码便于直接放入 Label Studio ML backend 目录中按需修改与调试。目前已有 832 人学习下载说明该方向在遥感、航拍等旋转目标标注场景中具有实际需求。读者可从中获得 OBB 模型与 Label Studio 对接的核心逻辑参考理解预测结果如何转换为平台可识别的标注格式并以此为起点替换为自己的权重与类别配置快速验证半自动标注流程是否跑通同时为后续排错和二次开发提供可复用的代码基础。1. 半自动标注落地从 Label Studio ML 后端接上 YOLOv8-OBB标注这件事做目标检测的同行都懂——纯手工框一天下来眼睛发直尤其是遥感图像、航拍图那种带旋转角度的目标水平框根本套不住得用带方向的 OBBOriented Bounding Box。Label Studio 本身是个好用的标注前端但它默认只负责“人画框”不会自己猜。真正让它变成半自动标注流水线的是 Label Studio ML Backend 这套后端机制而 YOLOv8-OBB 的接入点就落在后端项目里那个model.py文件上。这份资源给的就是这个model.py——一个把 YOLOv8-OBB 推理能力包装成 Label Studio 可调用预测接口的适配层。它解决的核心问题很具体你有一批待标图像已经训好了一个 OBB 模型想让模型先跑一遍把预标注框吐到画布上人工只做修正和确认。适合谁适合已经跑通 YOLOv8-OBB 训练、正在用 Label Studio 做标注、想省掉重复劳动的目标检测开发者。下面从它怎么跑、model.py里到底写了什么、怎么改一层层拆。2. 环境与目录把 ML 后端跑起来的最小闭环2.1 先看清 Label Studio ML Backend 的运行模型Label Studio ML Backend 不是个库它是个独立进程的 HTTP 服务。Label Studio 前端在打开一条任务时会向这个后端发预测请求后端拿到图像 URL 或本地路径跑模型返回符合 Label Studio 标注格式的 JSON前端把结果渲染成预标注。所以整条链路是Label Studio前端 主服务→ ML BackendFlask 服务→ 你的model.py推理逻辑→ 返回预测。理解这一点很关键因为很多人第一次接的时候会以为model.py是被 Label Studio 直接 import 的其实不是——它是被 ML Backend 的框架加载的。框架约定后端项目里要有一个继承自基类的模型类实现predict和fit等方法model.py就是放这个类的地方。常见做法是后端项目根目录下有个model.py里面定义一个YOLO或类似命名的类框架启动时按配置实例化它。2.2 目录结构与依赖清单一个能跑起来的后端项目目录大致长这样不同版本框架略有差异但骨架一致ml-backend/ ├── model.py # 核心模型类predict/fit 实现 ├── Dockerfile # 容器化启动可选 ├── docker-compose.yml # 本地编排可选 ├── requirements.txt # 依赖 └── README.md依赖这块除了 ML Backend 框架本身关键就是 ultralyticsYOLOv8 的官方包OBB 任务在它里面、以及图像读取相关的 pillow、numpy。requirements 里我一般会钉版本因为 ultralytics 迭代快OBB 的输出结构在小版本间有过调整不钉版本容易在某次重装后预测格式对不上。示例# requirements.txt 关键项版本按你训练环境对齐 ultralytics8.1.0 label-studio-ml1.0.9 pillow numpy安装和启动的典型命令# 安装依赖 pip install -r requirements.txt # 启动 ML 后端指定端口和项目目录 label-studio-ml start ./ml-backend -p 9090-p 9090是后端监听端口Label Studio 主服务默认 8080两者别撞。启动后访问http://localhost:9090/health应返回健康状态这一步是排查“连不上”的第一道关卡。2.3 在 Label Studio 里挂上后端后端跑起来只是半边还得让 Label Studio 知道它。进入项目设置 → Model → Connect Model填后端地址http://localhost:9090保存后点 “Validate and Save”能拉到模型信息就说明握手成功。之后打开任意一条任务点 “Retrieve predictions” 或开启自动预标注模型结果就会出现在画布上。这里有个容易忽略的点Label Studio 的标注配置Labeling Config里OBB 对应的标签控件必须是Rectangle且带旋转支持或者用Polygon近似。配置和model.py返回的from_name、to_name必须严格对应否则预测能返回但画布上不显示——这是后面避坑章要展开的典型翻车点。3. model.py 核心拆解一个 OBB 预测请求的完整链路3.1 模型类的骨架与初始化model.py的主体是一个模型类框架通过它调用推理。骨架大致如下我按实际项目里最常见的写法还原from label_studio_ml.model import LabelStudioMLBase from ultralytics import YOLO import numpy as np class YOLOOBB(LabelStudioMLBase): def __init__(self, **kwargs): super().__init__(**kwargs) # 加载训练好的 OBB 权重路径按实际部署调整 self.model YOLO(weights/best.pt) # 置信度阈值低于此值的预测直接丢弃 self.conf_threshold 0.25 # 从标注配置里解析标签名映射保证输出 label 与前端一致 self.from_name, self.to_name, self.value, self.labels_in_config \ self.get_label_config()逐段说。LabelStudioMLBase是框架基类继承它才能拿到get_label_config、preload_task_data这些工具方法。__init__里做两件事加载权重、读配置。YOLO(weights/best.pt)是 ultralytics 的标准加载方式OBB 模型和普通检测模型加载方式一样区别在推理输出结构。conf_threshold是业务阈值不是模型参数调它等于调“预标注的激进程度”——调低召回高但噪声多调高干净但漏标多这个值我一般从 0.25 起步再按实际标注体验微调。get_label_config()是基类提供的方法返回标注配置里解析出的控件名和标签集合。这一步的意义在于模型输出的类别名必须和前端配置里的标签名对得上否则前端认不出。很多“预测返回了但画布空白”的问题根子就在这。3.2 predict 方法从图像到 OBB 结果的转换predict是框架调用的入口入参是任务列表出参是预测结果列表。核心逻辑是把每张图喂给模型再把 ultralytics 的输出翻译成 Label Studio 的标注 JSONdef predict(self, tasks, **kwargs): predictions [] for task in tasks: # 拿到图像路径或 URL框架已处理好本地/远程差异 image_path task[data][image] # 跑推理OBB 任务输出带角度信息 results self.model(image_path, confself.conf_threshold) result results[0] obb result.obb # OBB 结果对象含 xywhr、cls、conf regions [] if obb is not None: # xywhr: 中心点x,y、宽、高、旋转角(弧度) xywhr obb.xywhr.cpu().numpy() cls obb.cls.cpu().numpy().astype(int) conf obb.conf.cpu().numpy() for i in range(len(xywhr)): cx, cy, w, h, angle xywhr[i] label self.labels_in_config[cls[i]] \ if cls[i] len(self.labels_in_config) else str(cls[i]) regions.append({ from_name: self.from_name, to_name: self.to_name, type: rectanglelabels, value: { x: float((cx - w / 2) / result.orig_shape[1] * 100), y: float((cy - h / 2) / result.orig_shape[0] * 100), width: float(w / result.orig_shape[1] * 100), height: float(h / result.orig_shape[0] * 100), rotation: float(np.degrees(angle)), rectanglelabels: [label] }, score: float(conf[i]) }) predictions.append({result: regions, model_version: yolov8-obb}) return predictions这段是全文最需要看懂的地方几个参数逐个说清obb.xywhrOBB 的核心输出五个值——中心点 x、中心点 y、宽、高、旋转角弧度。普通检测是xyxy或xywhOBB 多了角度这一维这正是它能套住斜向目标的原因。result.orig_shape原图的高宽用来把像素坐标归一化成百分比。Label Studio 的rectanglelabels用的是 0~100 的相对坐标不归一化会全画到画布外。rotationLabel Studio 的旋转单位是角度ultralytics 给的是弧度必须np.degrees转换。这一处漏转框会全部歪成水平或乱转是血泪经验级的坑。score置信度前端会显示也用于按分数筛选。注意x、y的算法是(cx - w/2)即左上角坐标因为 Label Studio 的矩形以左上角为锚点。如果直接用cx框会整体偏移半个宽高。3.3 fit 方法主动学习那点事框架还要求实现fit用于主动学习场景——把人工修正后的标注回传微调模型。多数接入 OBB 的项目其实用不上在线微调常见做法是留个空实现或只做日志def fit(self, event, data, **kwargs): # 主动学习回调event 为标注事件data 含任务与标注 # 简单场景下不实现在线训练仅记录便于后续离线迭代 print(freceived event: {event}, task count: {len(data.get(annotations, []))}) return {}如果确实要做增量训练这里可以收集data里的标注转成 YOLO 格式落盘再触发离线训练脚本。但要注意在线 fit 会阻塞预测请求生产环境一般拆成异步任务别在fit里直接跑训练。3.4 标注配置与预测字段的对应关系from_name、to_name来自标注配置配置长这样View Image nameimage value$image/ RectangleLabels namelabel toNameimage Label valueship background#FFA39E/ Label valuevehicle background#D4380D/ /RectangleLabels /ViewRectangleLabels的name就是from_nametoName就是to_name。model.py里get_label_config()解析出的labels_in_config顺序必须和模型训练时的类别顺序一致否则标签会张冠李戴——模型说是 ship前端显示 vehicle。这个顺序对齐是接入阶段最该先验证的一件事。4. 避坑与排查OBB 预标注最容易翻车的五处4.1 预测返回了但画布空白现象后端日志显示推理成功、返回了 regions但 Label Studio 画布上什么都没有。原因通常是from_name/to_name与标注配置不匹配或者type写成了rectangle而非rectanglelabels。解决对照配置逐字核对控件名type必须是rectanglelabelsvalue里必须带rectanglelabels数组。改完重启后端再 Validate 一次。4.2 框全部歪掉或角度错乱现象框的位置对但旋转角明显不对斜向目标被画成水平或反向。原因基本是弧度没转角度或者坐标系方向不一致图像坐标 y 向下某些角度定义 y 向上。解决确认rotation用了np.degrees并拿一张已知角度的图单独验证若整体反向对角度取负或加 180 度补偿具体看模型训练时的角度约定。4.3 框整体偏移半个宽高现象框的大小对但位置系统性偏移。原因x、y用了中心点而非左上角。解决x (cx - w/2)、y (cy - h/2)再归一化。这个错误很隐蔽因为小目标上偏移不明显大目标上一眼就歪。4.4 连不上后端或 Validate 失败现象Label Studio 填了地址点 Validate 报错。原因多为端口不通、后端没起、或容器网络隔离后端在容器里localhost指的不是宿主机。解决先curl http://localhost:9090/health确认后端活着容器场景下用宿主机可达的地址而非localhost检查防火墙和端口映射。4.5 大批量任务时预测超时现象单张预测正常一次拉几十条任务时前端转圈直到超时。原因predict是串行逐张推理任务一多就慢。解决控制单次请求任务数或在predict里做批处理ultralytics 支持传列表批量推理再按任务拆分结果GPU 环境下确认模型确实在 GPU 上别默默跑在 CPU 上。5. 二次开发把 model.py 改成你自己的预标注引擎5.1 三个最值得先改的点拿到这份model.py真正要动的地方通常就三处。第一是权重路径和类别映射把weights/best.pt换成你的模型把labels_in_config的顺序和训练类别对齐这是能不能用的前提。第二是阈值策略conf_threshold可以做成按类别不同——小目标类别调低、易混淆类别调高比全局一个值体验好很多。第三是输出后处理比如按面积过滤掉过小的框、按 IoU 做 NMS 二次去重减少人工删框的工作量。改造时我习惯先写一个离线验证脚本不经过 Label Studio直接拿几张图跑model.py里的转换逻辑把生成的 JSON 画回图上肉眼比对。这样能把“坐标归一化、角度转换”这类纯计算错误在接入前就摁死而不是在画布上反复猜。5.2 用一张对照表管住字段二次开发最容易在字段上翻车我一般维护一张小对照表改代码前先对一遍模型输出含义Label Studio 字段转换要点xywhr[0]中心 xvalue.x减 w/2 后归一化xywhr[1]中心 yvalue.y减 h/2 后归一化xywhr[2]宽value.width除以原图宽乘 100xywhr[3]高value.height除以原图高乘 100xywhr[4]弧度角value.rotationnp.degrees 转角度cls类别索引rectanglelabels映射到配置标签名conf置信度score直接透传这张表看着朴素但每次改完代码对着它核一遍能省掉大量“框歪了但不知道哪一步错”的排查时间。5.3 验证方法与一个具体技巧验证分两层。第一层是离线拿 5~10 张覆盖不同角度、不同尺寸的图跑转换逻辑把 JSON 用 PIL 画回原图重点看斜向目标的角度和边界。第二层是线上在 Label Studio 里开一条任务拉预测确认画布渲染、标签名、分数显示都对。两层都过再批量放开。一个具体技巧在predict里加一个可配置的debug_dir把每张图的原始推理结果和转换后的 JSON 各存一份。线上出问题时直接翻这个目录就能定位是模型输出错了还是转换错了不用靠猜。这个习惯是我被“画布上框时有时无”折磨过之后养成的——那次排查了两小时最后发现是某类别的角度在边界值上转换溢出。从那以后我每次接新模型都强制走一遍离线画图验证加 debug 落盘再上线。希望帮到你。本文还有配套的精品资源点击获取