ml_backend/ 是一个 label-studio-ml 后端:Label Studio 打开任务时调 /predict, 后端跑 YOLO 并把框转成 Label Studio 的百分比坐标返回,人工只需修正。 - model.py:FilmDefectDetector。权重懒加载且只加载一次(Flask 多线程,加锁); 控件名从项目的 labeling config 读取,不写死;model_version 带权重的修改时间, 换权重后 Label Studio 能区分新旧预测。 - convert.py:坐标转换,刻意不 import label_studio_ml,好在主环境里单测。 - fit():每条标注累加计数,达到 RETRAIN_AFTER 就起子进程执行 RETRAIN_COMMAND 并立刻返回——训练几十分钟,同步做会卡住 Label Studio。 - _wsgi.py 由官方模板生成,ruff 跳过,便于跟随上游。 必须用独立虚拟环境:label-studio-ml 会把 jinja2 降到 3.0.3,而 label-studio 1.23 需要 3.1,装进主环境会直接弄坏 Label Studio。PyPI 上的 1.0.9 是 2023 年的旧版, 要装 GitHub 上的现行版本。 本地用 COCO 权重端到端验证过:/health、/predict(bus.jpg 出 10 个框、坐标换算正确)、 /webhook(第 3 条标注触发重训命令并清零计数)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""YOLO 结果 <-> Label Studio 结果格式的互转。
|
|
|
|
单独成一个模块、不 import label_studio_ml,这样主环境里就能直接测:
|
|
ML backend 跑在自己的虚拟环境里(见 ml_backend/README.md),主环境装不了它。
|
|
|
|
Label Studio 的框用**百分比**表示左上角与宽高(0~100),YOLO 用归一化的中心点与宽高。
|
|
`original_width` / `original_height` 必须是图片的真实像素尺寸,Label Studio 据此换算。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from collections.abc import Sequence
|
|
|
|
|
|
def box_to_result(
|
|
class_name: str,
|
|
xyxyn: Sequence[float],
|
|
width: int,
|
|
height: int,
|
|
from_name: str,
|
|
to_name: str,
|
|
score: float | None = None,
|
|
) -> dict:
|
|
"""一个归一化 xyxy 框 -> 一条 Label Studio 的 rectanglelabels 结果。"""
|
|
x1, y1, x2, y2 = (min(max(float(v), 0.0), 1.0) for v in xyxyn)
|
|
item = {
|
|
"id": uuid.uuid4().hex[:10],
|
|
"type": "rectanglelabels",
|
|
"from_name": from_name,
|
|
"to_name": to_name,
|
|
"original_width": int(width),
|
|
"original_height": int(height),
|
|
"image_rotation": 0,
|
|
"value": {
|
|
"x": x1 * 100,
|
|
"y": y1 * 100,
|
|
"width": (x2 - x1) * 100,
|
|
"height": (y2 - y1) * 100,
|
|
"rotation": 0,
|
|
"rectanglelabels": [class_name],
|
|
},
|
|
}
|
|
if score is not None:
|
|
item["score"] = float(score)
|
|
return item
|
|
|
|
|
|
def to_prediction(
|
|
boxes: Sequence[tuple[str, Sequence[float], float]],
|
|
width: int,
|
|
height: int,
|
|
from_name: str,
|
|
to_name: str,
|
|
model_version: str,
|
|
) -> dict:
|
|
"""一张图的若干框 -> 一条 Label Studio 预测。
|
|
|
|
Args:
|
|
boxes: (类别名, 归一化 xyxy, 置信度) 的序列。
|
|
|
|
预测整体的 `score` 取所有框里最高的置信度:Label Studio 用它给任务排序,
|
|
取最大值才能让"模型最有把握的图"排在前面;没有框时为 0,表示这张图没找到缺陷。
|
|
"""
|
|
results = [
|
|
box_to_result(name, xyxyn, width, height, from_name, to_name, score)
|
|
for name, xyxyn, score in boxes
|
|
]
|
|
return {
|
|
"model_version": model_version,
|
|
"score": max((score for _, _, score in boxes), default=0.0),
|
|
"result": results,
|
|
}
|