Files
ModelTrainEval/ml_backend
Shaun ZhouandClaude Opus 5 1b8c8d7cac chore: 忽略 ml_backend 的运行时缓存
cache.db 是 label-studio-ml 用来存 model_version、标注计数的本地缓存,
上一提交误把它带了进去。顺带忽略后端的独立虚拟环境 .venv-ml/。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 17:36:25 +08:00
..

Label Studio ML Backend:用训练好的 YOLO 做预标注

把 runs/detect/<name>/weights/best.pt 接进 Label Studio,打开任务时自动给出框, 人工只需修正而不是从零画。标注攒够数量还能自动触发重训,形成"边标边训"的闭环。

为什么要单独的虚拟环境

label-studio-ml 会把 jinja2 降到 3.0.3、带进 Flask 1.1.2,而 label-studio 1.23 需要 jinja2 3.1。装进主环境会直接弄坏 Label Studio。它本来就是个独立服务,单独建环境最省事。

另外 PyPI 上的 label-studio-ml 是 2023 年的 1.0.9,不要用,要装 GitHub 上的现行版本 (本文档基于 2.0.1.dev0 验证)。

uv venv .venv-ml --python 3.12
VIRTUAL_ENV=.venv-ml uv pip install \
    "label-studio-ml @ git+https://github.com/HumanSignal/label-studio-ml-backend.git" \
    ultralytics

启动

cd ml_backend
YOLO_WEIGHTS=$PWD/../runs/detect/film/weights/best.pt \
YOLO_DEVICE=cuda \
LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT=/home/deep/labeling \
../.venv-ml/bin/python _wsgi.py -p 9090

用 python _wsgi.py 而不是 label-studio-ml start .:后者用 PATH 上的 python 起子进程, 在独立环境里会 ModuleNotFoundError。

LABEL_STUDIO_LOCAL_FILES_DOCUMENT_ROOT 必须和启动 Label Studio 时的值一致——任务里的图片是 /data/local-files/?d=...,后端要靠它把 URL 还原成磁盘路径。图片若来自上传或云存储, 则改为设置 LABEL_STUDIO_URL 与 LABEL_STUDIO_API_KEY,后端会自行下载。

健康检查:

curl http://localhost:9090/health
# {"model_class":"FilmDefectDetector","status":"UP"}

接入项目

Label Studio 里:Settings → Model → Connect Model,URL 填 http://localhost:9090。

  • 勾选 Interactive preannotations 后,打开任务就会实时调用模型;
  • 也可以在 Data Manager 里选中若干任务 → Actions → Retrieve Predictions 批量拉取。

类别名必须和 labeling config 里的 <Label value="..."> 完全一致。 后端用的是权重里的 model.names(也就是训练时 data.yaml 的 names),对不上的话 Label Studio 会拒绝这条预测。 我们的模型是 9 类:black、blackdot、grease、mosquito、normal、obstacle、ripple、uneven、wrinkle。

环境变量

变量 默认 说明
YOLO_WEIGHTS yolo26n.pt 权重路径。版本号里带它的修改时间,换权重后 Label Studio 能区分新旧预测
YOLO_CONF 0.25 置信度阈值。预标注可以调低些,宁可多给几个框让人删
YOLO_IMGSZ 640 推理尺寸,与训练保持一致
YOLO_DEVICE cuda cuda / cpu / mps
RETRAIN_AFTER 0 累计多少条新标注触发重训,0 = 不触发
RETRAIN_COMMAND 空 触发时执行的命令

边标边训

Label Studio 每产生一条标注就会 POST /webhook,后端在 fit() 里累加计数, 达到 RETRAIN_AFTER 就起一个子进程执行 RETRAIN_COMMAND 并立刻返回—— 训练要几十分钟,绝不能在这个请求里同步做,否则会把 Label Studio 卡住。

RETRAIN_AFTER=200 \
RETRAIN_COMMAND="/home/deep/ModelTrainEval/scripts/retrain.sh" \
../.venv-ml/bin/python _wsgi.py -p 9090

retrain.sh 需要自己写,大致是:导出项目 → build_yolo_dataset.py 装配 → detect.py 训练 → 把新权重放到 YOLO_WEIGHTS 指向的位置 → 重启本服务(或让它下次启动时加载新权重)。 目前权重只在进程启动时加载一次,重训完要重启服务才会生效。

已验证的行为

本地用 COCO 预训练权重实测过:

  • /health 返回 UP;
  • /predict 用 Label Studio 的真实请求体(tasks + label_config + params)调用, bus.jpg 返回 10 个框,坐标换算正确(百分比、original_width/height 与原图一致);
  • 图片无检出时返回一条空 result 的预测,表示"这张图没问题";
  • /webhook 连发 3 条 ANNOTATION_CREATED,在第 3 条触发 RETRAIN_COMMAND 并把计数清零。

转换逻辑的单元测试在 tests/test_ml_backend_convert.py,跑在主环境里 (convert.py 刻意不 import label_studio_ml,就是为了能这样测)。