yolo笔记3:部署 YOLOv8 自动标注后端 (ML Backend)

在第一篇中,我们部署了 Label Studio;在第二篇中,我们清洗数据并训练了 YOLOv8 模型。

现在,我们要完成“闭环”:将训练好的 YOLO 模型部署为 Label Studio 的后端服务,实现 AI 辅助自动标注。

这意味着:你上传新图片后,AI 会自动帮你画好框,你只需要微调即可。效率提升神器!


简介

纯手工标注是枯燥且低效的。Label Studio 最强大的功能之一就是 "Human-in-the-loop"(人机协同):

  1. 预标注:模型先预测一遍。
  2. 人工修正:人类只需调整不准的框。
  3. 循环迭代:修正后的数据再次训练模型,模型越来越准。

本文将教你如何使用 Docker 编写并部署一个自定义的 YOLOv8 ML Backend,并将其连接到 Label Studio。


架构原理

Label Studio 本身不运行复杂的深度学习模型,它通过 HTTP 协议 与外部的 "ML Backend" 通信。

  • Label Studio: "喂,这里有一张图片的 URL,帮我看看里面有什么?"
  • ML Backend: (下载图片 -> 运行 YOLO -> 生成坐标) "发现了 2 只猫,坐标是 [...]。"
  • Label Studio: 收到结果,在前端渲染出框。

第一步:准备 ML Backend 目录

我们需要创建一个独立的文件夹(例如 yolo_backend),结构如下:

yolo_backend/
├── best.pt            # 你在上一篇教程中训练好的模型文件
├── docker-compose.yml # 容器编排
├── Dockerfile         # 镜像构建文件
├── requirements.txt   # 依赖库
└── model.py           # 核心逻辑代码

请确保将你训练好的 best.pt 复制到这个文件夹中。

第二步:编写核心代码

1. requirements.txt

label-studio-ml==1.0.9
ultralytics
torch
torchvision

2. Dockerfile

# 使用轻量级 Python 镜像
FROM python:3.9-slim

WORKDIR /app

# 安装依赖(利用缓存加速)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制当前目录代码到容器
COPY . .

# 环境变量:指定后端使用的类
ENV LABEL_STUDIO_ML_BACKEND_CLASS=model.YOLOMLBackend

# 启动服务,默认端口 9090
CMD ["label-studio-ml", "start", ".", "--port", "9090"]

3. model.py (核心逻辑)

这是最关键的部分。我们需要继承 LabelStudioMLBase 并重写 predict 方法。

import os
from label_studio_ml.model import LabelStudioMLBase
from label_studio_ml.utils import get_image_size
from ultralytics import YOLO

class YOLOMLBackend(LabelStudioMLBase):
    def __init__(self, project_id=None, model_path='best.pt', **kwargs):
        super(YOLOMLBackend, self).__init__(**kwargs)
        
        # 加载你的 YOLO 模型
        print(f"🚀 Loading YOLO model from {model_path}...")
        self.model = YOLO(model_path) 

    def predict(self, tasks, **kwargs):
        predictions = []
        
        for task in tasks:
            # 1. 获取图片 URL
            # 注意:Label Studio 传过来的可能是本地路径或 http 链接
            # 这里的逻辑是让 label-studio-ml SDK 自动处理下载或路径解析
            image_url = task['data']['image']
            
            # 2. 获取原图尺寸 (用于坐标归一化)
            # get_image_size 是 SDK 自带的工具,能自动下载并读取头部信息
            original_width, original_height = get_image_size(image_url)
            
            # 3. YOLO 推理
            # 提示:如果图片需要鉴权,这一步可能需要额外处理,这里假设是公开或本地可读
            results = self.model.predict(image_url, conf=0.4) # 设置置信度阈值 0.4
            
            for result in results:
                for box in result.boxes:
                    # 获取 YOLO 坐标 [x1, y1, x2, y2]
                    x1, y1, x2, y2 = box.xyxy[0].tolist()
                    conf = box.conf[0].item()
                    cls_id = int(box.cls[0].item())
                    
                    # 获取类别名称 (确保 data.yaml 里的 names 和这里一致)
                    label_name = self.model.names[cls_id]

                    # 4. 坐标转换 (关键!)
                    # Label Studio 需要 0-100 的相对百分比
                    x = x1 / original_width * 100
                    y = y1 / original_height * 100
                    w = (x2 - x1) / original_width * 100
                    h = (y2 - y1) / original_height * 100

                    # 5. 构造 Label Studio 标准 JSON 响应
                    predictions.append({
                        "result": [{
                            "from_name": "label",      # 对应 XML 配置里的 <Labels name="label">
                            "to_name": "image",        # 对应 XML 配置里的 <Image name="image">
                            "type": "rectanglelabels",
                            "value": {
                                "rectanglelabels": [label_name],
                                "x": x,
                                "y": y,
                                "width": w,
                                "height": h,
                                "rotation": 0
                            },
                            "score": conf
                        }],
                        "score": conf,
                        "model_version": "v1.0"
                    })
        
        return predictions

4. docker-compose.yml

version: "3.9"
services:
  ml_backend:
    build: .
    ports:
      - "9090:9090"
    environment:
      - MODEL_PATH=best.pt
    # ⚠️ 如果你第一篇使用的是本地文件挂载 (Local Storage)
    # 你必须把图片目录也挂载给 ML Backend,否则它读不到图片!
    volumes:
      - ./mydata/images:/data/images # 修改为你实际的图片路径

第三步:启动后端服务

yolo_backend 目录下终端运行:

docker-compose up --build -d

查看日志,确保没有报错,且显示服务已启动:

docker-compose logs -f

第四步:在 Label Studio 中连接

  1. 打开 Label Studio 网页,进入你的项目。
  2. 点击 Settings -> Machine Learning
  3. 点击 Add Model
  4. 填写 URL (这是最大的坑)
    • 情况 A (Docker Desktop): 填 http://host.docker.internal:9090
    • 情况 B (Linux 服务器): 填 http://172.17.0.1:9090 (Docker 网桥 IP) 或服务器内网 IP。
    • 切记不要填 localhost,因为那是指向 Label Studio 容器自己。
  5. 开启 Interactive Preannotations (交互式预标注)。
  6. 点击 Validate and Save
    • 如果变为绿色 Connected,恭喜你,成功了!
    • 如果报错 Error,请检查上面的 URL 和防火墙。

第五步:享受自动标注

现在有两种方式使用它:

  1. 打开新任务
    在 Data Manager 中点击一个状态为 New 的任务。进入标注界面后,稍等 1-2 秒,YOLO 预测的框就会自动浮现。你只需要微调位置,然后点击 Submit。
  2. 批量预测
    在任务列表勾选所有图片 -> 点击上方 Actions -> Retrieve predictions。刷新页面后,你会发现任务状态变成了 Predicted,框已经全部打好了。

常见问题排查

Q: 为什么显示的类别不对?
A:
Label Studio 里的标签名称(如 <Label value="Cat">)必须和 YOLO 模型训练时的 names(如 cat完全一致(区分大小写)。如果模型返回 "cat" 但你的项目里只有 "Cat",框会被忽略。

Q: 模型连上了,但没有框出现?
A:

  1. 检查 model.py 里的置信度阈值 conf=0.4 是否太高。
  2. 网络问题:ML Backend 容器必须能下载或读取到图片。如果使用本地挂载,确保 ML 容器也能访问到那个挂载路径。

Q: 坐标偏移很大?
A:
这通常是原图尺寸 original_width 获取错误导致的。YOLO 返回的是绝对像素,必须除以正确的原图长宽。