680 lines
25 KiB
Markdown
680 lines
25 KiB
Markdown
|
|
# 汽车环车视频旧伤检测 Skill 设计
|
|||
|
|
|
|||
|
|
> 日期:2026-05-04
|
|||
|
|
> 状态:Draft
|
|||
|
|
> 范围:新增一个业务级 compute-entry Skill,用于从环车视频中抽帧、检测漆面旧伤、定位标注损伤区域并筛选最佳证据帧。
|
|||
|
|
> 部署约束:生产形态是 Windows 安装包,本设计必须遵守后端 `dataDir`、bundled skills 复制、可写目录和托盘运行时边界。
|
|||
|
|
|
|||
|
|
## 1. 背景与目标
|
|||
|
|
|
|||
|
|
RZYX_AI_MONOREPO 当前以 NetaClaw Skill Runtime 为核心承载可扩展业务能力。Skill 已支持 `SKILL.md`、`skill.config.yaml`、skill scoped secrets、`execute_skill` 和 stdin/stdout JSON 协议。
|
|||
|
|
|
|||
|
|
本设计新增一个独立业务 Skill:**汽车环车视频旧伤检测**。它不依赖旧 AI_flow 后端的 `BaseSkill`、LangChain tool、Midway service 注入或旧 progress emitter,而是按新项目 Skill Runtime 的 compute-entry 机制实现。
|
|||
|
|
|
|||
|
|
目标:
|
|||
|
|
|
|||
|
|
- 输入一个环车视频文件,输出结构化旧伤检测结果。
|
|||
|
|
- 自动抽帧并保留每帧时间戳。
|
|||
|
|
- 分批调用多模态模型识别车身漆面旧伤。
|
|||
|
|
- 对候选损伤调用支持 grounding/bbox 的视觉模型定位区域。
|
|||
|
|
- 在图片上绘制标注框,筛选每处损伤的最佳证据帧。
|
|||
|
|
- 所有模型、API Key、API URL、workspace 根目录等运行配置都从 `skill.config.yaml` env/schema 或输入参数读取,不在代码里硬编码密钥。
|
|||
|
|
- Windows 安装包生产环境下,所有运行产物只能写入后端 `dataDir` 下的可写目录,不能写入安装目录、源码目录或 bundled skill 原始目录。
|
|||
|
|
|
|||
|
|
非目标:
|
|||
|
|
|
|||
|
|
- 第一版不做前端专用结果卡片。
|
|||
|
|
- 第一版不做实时进度推送。
|
|||
|
|
- 第一版不训练 YOLO/SAM 等专用模型。
|
|||
|
|
- 第一版不生成正式检测报告 PDF/HTML。
|
|||
|
|
- 第一版不做人工复核工作流。
|
|||
|
|
|
|||
|
|
## 2. 总体形态
|
|||
|
|
|
|||
|
|
采用 **一个对外大 Skill + 内部模块化流水线** 的结构。
|
|||
|
|
|
|||
|
|
Agent 只调用一次:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"name": "vehicle-damage-inspection",
|
|||
|
|
"input": {
|
|||
|
|
"videoUrl": "/upload/20260504/car.mp4",
|
|||
|
|
"fps": 3,
|
|||
|
|
"topN": 1
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Skill 内部固定按协议执行:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
输入视频
|
|||
|
|
-> frame_extractor 抽帧
|
|||
|
|
-> damage_detector 环车旧伤候选检测
|
|||
|
|
-> damage_grounding 损伤 bbox 定位和画框
|
|||
|
|
-> best_frame_selector 去重和最佳帧筛选
|
|||
|
|
-> 输出最终 JSON
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
选择大 Skill 的原因:
|
|||
|
|
|
|||
|
|
- 视频旧伤检测是垂直业务流程,不是通用 Agent 工具。
|
|||
|
|
- 让 Agent 连续调用四个 Skill 并传递 `taskId` 会增加失败点。
|
|||
|
|
- 固定流水线更稳定,便于测试、重跑和后续替换局部模块。
|
|||
|
|
- 内部模块保持独立,未来仍可拆出单独 Skill。
|
|||
|
|
|
|||
|
|
## 3. Skill 目录结构
|
|||
|
|
|
|||
|
|
参考 `packages/backend/skills/minimax-pdf` 的组织方式,但本 Skill 以 compute-entry 为主,额外保留 `make.sh` 方便人工调试。
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
packages/backend/skills/vehicle-damage-inspection/
|
|||
|
|
├── SKILL.md
|
|||
|
|
├── skill.config.yaml
|
|||
|
|
├── README.md
|
|||
|
|
├── prompts/
|
|||
|
|
│ ├── damage_detect.md
|
|||
|
|
│ ├── grounding.md
|
|||
|
|
│ ├── dedup.md
|
|||
|
|
│ └── best_frame.md
|
|||
|
|
└── scripts/
|
|||
|
|
├── index.cjs
|
|||
|
|
├── make.sh
|
|||
|
|
├── setup.ps1
|
|||
|
|
└── lib/
|
|||
|
|
├── workspace.cjs
|
|||
|
|
├── frame_extractor.cjs
|
|||
|
|
├── vision_client.cjs
|
|||
|
|
├── damage_detector.cjs
|
|||
|
|
├── damage_grounding.cjs
|
|||
|
|
├── best_frame_selector.cjs
|
|||
|
|
├── image_marker.cjs
|
|||
|
|
└── json_utils.cjs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
职责边界:
|
|||
|
|
|
|||
|
|
| 文件 | 职责 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `SKILL.md` | 面向 Agent 的能力说明、触发场景、输入输出摘要 |
|
|||
|
|
| `skill.config.yaml` | runtime、entrypoint、依赖、env、接口 schema |
|
|||
|
|
| `scripts/index.cjs` | stdin/stdout JSON 入口,编排完整流水线 |
|
|||
|
|
| `scripts/make.sh` | Git Bash / POSIX 本地 `check/run/demo` 调试入口 |
|
|||
|
|
| `scripts/setup.ps1` | Windows 安装器 setup 入口;当前 installer 在 win32 下使用 `powershell -File` 执行 setup |
|
|||
|
|
| `workspace.cjs` | 创建 task workspace、读写 JSON、解析 `/upload/` 路径 |
|
|||
|
|
| `frame_extractor.cjs` | ffmpeg/ffprobe 抽帧与视频元信息 |
|
|||
|
|
| `vision_client.cjs` | OpenAI-compatible vision API 调用、重试、JSON/bbox 解析 |
|
|||
|
|
| `damage_detector.cjs` | 分批读取帧,识别候选旧伤 |
|
|||
|
|
| `damage_grounding.cjs` | 读取候选旧伤,找邻近帧,调用 grounding 模型 |
|
|||
|
|
| `image_marker.cjs` | 用 sharp 绘制 bbox 和标签 |
|
|||
|
|
| `best_frame_selector.cjs` | 损伤去重、最佳帧模型筛选、启发式兜底 |
|
|||
|
|
|
|||
|
|
## 4. skill.config.yaml
|
|||
|
|
|
|||
|
|
第一版使用 Node runtime,避免 Python venv 要求;依赖通过 skill 的 `dependencies.node.packages` 声明。生产 Windows 安装包应在打包或安装阶段预置 Node 依赖,不把现场 `npm install` 作为正常运行路径;`setup` 只作为安装/修复入口。
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
runtime: node
|
|||
|
|
entrypoint: scripts/index.cjs
|
|||
|
|
timeout: 900000
|
|||
|
|
|
|||
|
|
dependencies:
|
|||
|
|
system:
|
|||
|
|
- name: ffmpeg
|
|||
|
|
check: "ffmpeg -version"
|
|||
|
|
- name: ffprobe
|
|||
|
|
check: "ffprobe -version"
|
|||
|
|
- name: node
|
|||
|
|
check: "node --version"
|
|||
|
|
node:
|
|||
|
|
packages:
|
|||
|
|
- axios
|
|||
|
|
- sharp
|
|||
|
|
- fluent-ffmpeg
|
|||
|
|
- "@ffmpeg-installer/ffmpeg"
|
|||
|
|
- "@ffprobe-installer/ffprobe"
|
|||
|
|
|
|||
|
|
setup:
|
|||
|
|
posix: scripts/make.sh fix
|
|||
|
|
win32: scripts/setup.ps1
|
|||
|
|
|
|||
|
|
env:
|
|||
|
|
- name: ARK_API_KEY
|
|||
|
|
required: true
|
|||
|
|
description: 火山方舟/豆包 OpenAI-compatible API Key
|
|||
|
|
- name: ARK_API_URL
|
|||
|
|
required: false
|
|||
|
|
default: https://ark.cn-beijing.volces.com/api/v3/chat/completions
|
|||
|
|
description: OpenAI-compatible chat completions endpoint
|
|||
|
|
- name: DAMAGE_DETECT_MODEL
|
|||
|
|
required: true
|
|||
|
|
default: doubao-seed-2-0-pro-260215
|
|||
|
|
description: 用于环车旧伤候选检测的多模态模型 ID
|
|||
|
|
- name: DAMAGE_GROUNDING_MODEL
|
|||
|
|
required: true
|
|||
|
|
default: doubao-seed-2-0-pro-260215
|
|||
|
|
description: 支持 bbox/grounding 输出的视觉模型 ID;当前默认豆包模型已支持 bbox 和定位
|
|||
|
|
- name: BEST_FRAME_MODEL
|
|||
|
|
required: false
|
|||
|
|
default: doubao-seed-2-0-pro-260215
|
|||
|
|
description: 用于损伤去重和最佳帧筛选的模型 ID;未配置时复用 DAMAGE_DETECT_MODEL
|
|||
|
|
- name: RZYX_AI_WORKSPACE_ROOT
|
|||
|
|
required: false
|
|||
|
|
description: 检测产物 workspace 根目录;生产环境由宿主注入,默认应为 dataDir/workspace/vehicle-damage-inspection
|
|||
|
|
- name: RZYX_AI_UPLOAD_ROOT
|
|||
|
|
required: false
|
|||
|
|
description: 后端 /upload 路径对应的本地 uploads 根目录;生产环境由宿主注入,默认应为 dataDir/uploads
|
|||
|
|
- name: RZYX_AI_DATA_DIR
|
|||
|
|
required: false
|
|||
|
|
description: 后端运行时 dataDir;由 Windows 安装包/runtime 注入,skill 可据此推导 workspace/uploads 默认路径
|
|||
|
|
|
|||
|
|
interface:
|
|||
|
|
input:
|
|||
|
|
videoUrl:
|
|||
|
|
type: string
|
|||
|
|
required: true
|
|||
|
|
description: 视频本地路径或 /upload/... 路径;第一版不支持 http(s) URL
|
|||
|
|
taskId:
|
|||
|
|
type: string
|
|||
|
|
required: false
|
|||
|
|
description: 可选任务 ID;未传则自动生成
|
|||
|
|
fps:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "3"
|
|||
|
|
description: 抽帧帧率
|
|||
|
|
quality:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "90"
|
|||
|
|
description: 输出 JPG 质量,范围 1-100
|
|||
|
|
batchSize:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "12"
|
|||
|
|
description: 每批发送给检测模型的帧数
|
|||
|
|
concurrency:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "2"
|
|||
|
|
description: 多模态 API 并发数
|
|||
|
|
groundingWindow:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "1.5"
|
|||
|
|
description: 每个损伤时间点前后取帧窗口,单位秒
|
|||
|
|
topN:
|
|||
|
|
type: number
|
|||
|
|
required: false
|
|||
|
|
default: "1"
|
|||
|
|
description: 每处损伤返回的最佳证据帧数量
|
|||
|
|
mode:
|
|||
|
|
type: string
|
|||
|
|
required: false
|
|||
|
|
default: full
|
|||
|
|
description: full | frames-only | detect-only,用于调试或重跑部分流程
|
|||
|
|
output:
|
|||
|
|
success:
|
|||
|
|
type: boolean
|
|||
|
|
taskId:
|
|||
|
|
type: string
|
|||
|
|
workspacePath:
|
|||
|
|
type: string
|
|||
|
|
summary:
|
|||
|
|
type: object
|
|||
|
|
damages:
|
|||
|
|
type: array
|
|||
|
|
artifacts:
|
|||
|
|
type: object
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
模型配置原则:
|
|||
|
|
|
|||
|
|
- 代码不写死 API Key、模型 ID 或业务密钥。
|
|||
|
|
- `skill.config.yaml env` 是模型和密钥 schema 的单一来源。宿主需要把 `skill.config.yaml` 中的 env 声明同步到 `NetaClawSkillEntity.envSchema`,这样管理页、默认模型和 `SkillSecretService.resolveEnv()` 使用同一份配置;不要要求在 `SKILL.md metadata.env` 中重复声明。
|
|||
|
|
- 当前默认模型为 `doubao-seed-2-0-pro-260215`。该豆包模型支持多模态理解、bbox 和定位能力,因此第一版可同时作为候选旧伤检测、损伤 grounding 和最佳帧筛选模型使用。
|
|||
|
|
- `DAMAGE_DETECT_MODEL` 负责从视频帧中识别候选旧伤,默认 `doubao-seed-2-0-pro-260215`。
|
|||
|
|
- `DAMAGE_GROUNDING_MODEL` 负责输出 bbox,默认 `doubao-seed-2-0-pro-260215`;它应返回 `<bbox>x1 y1 x2 y2</bbox>` 或等价结构化定位。
|
|||
|
|
- `BEST_FRAME_MODEL` 可选,未配置时复用检测模型。
|
|||
|
|
- 所有模型调用使用同一个 `ARK_API_URL` 和 `ARK_API_KEY`。第一版用一个豆包模型即可跑通;后续如需要按阶段切换模型,再通过 env 覆盖模型 ID。
|
|||
|
|
|
|||
|
|
运行目录配置原则:
|
|||
|
|
|
|||
|
|
- 生产环境必须由宿主运行时注入 `RZYX_AI_DATA_DIR`,并由 Skill 推导:
|
|||
|
|
- `RZYX_AI_WORKSPACE_ROOT = {RZYX_AI_DATA_DIR}/workspace/vehicle-damage-inspection`
|
|||
|
|
- `RZYX_AI_UPLOAD_ROOT = {RZYX_AI_DATA_DIR}/uploads`
|
|||
|
|
- 管理端仍允许用 skill secrets 覆盖这些路径,但默认不要求用户手填。
|
|||
|
|
- 开发态没有 `RZYX_AI_DATA_DIR` 时,才允许 fallback 到 skill 目录下 `.workspace`。
|
|||
|
|
|
|||
|
|
## 5. 输入输出协议
|
|||
|
|
|
|||
|
|
### 5.1 输入
|
|||
|
|
|
|||
|
|
`scripts/index.cjs` 从 stdin 读取 JSON:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"videoUrl": "/upload/20260504/car.mp4",
|
|||
|
|
"taskId": "optional-task-id",
|
|||
|
|
"fps": 3,
|
|||
|
|
"quality": 90,
|
|||
|
|
"batchSize": 12,
|
|||
|
|
"concurrency": 2,
|
|||
|
|
"groundingWindow": 1.5,
|
|||
|
|
"topN": 1,
|
|||
|
|
"mode": "full"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 输出
|
|||
|
|
|
|||
|
|
stdout 只输出一个 JSON 对象:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"success": true,
|
|||
|
|
"taskId": "20260504-abc",
|
|||
|
|
"workspacePath": "C:/ProgramData/RZYX-AI/data/workspace/vehicle-damage-inspection/20260504-abc",
|
|||
|
|
"summary": {
|
|||
|
|
"duration": 42.3,
|
|||
|
|
"resolution": "1920x1080",
|
|||
|
|
"frameCount": 127,
|
|||
|
|
"candidateDamageCount": 5,
|
|||
|
|
"mergedDamageCount": 3,
|
|||
|
|
"bestFrameCount": 3
|
|||
|
|
},
|
|||
|
|
"damages": [
|
|||
|
|
{
|
|||
|
|
"id": "dmg_001",
|
|||
|
|
"part": "左前门",
|
|||
|
|
"type": "划痕",
|
|||
|
|
"severity": "轻微",
|
|||
|
|
"description": "左前门中部可见细长白色划痕",
|
|||
|
|
"timestamps": [12.4, 12.7],
|
|||
|
|
"bestFrames": [
|
|||
|
|
{
|
|||
|
|
"timestamp": 12.4,
|
|||
|
|
"path": "C:/.../marked_frames/dmg_001_12.40s.jpg",
|
|||
|
|
"relativePath": "marked_frames/dmg_001_12.40s.jpg",
|
|||
|
|
"bbox": { "x1": 120, "y1": 340, "x2": 280, "y2": 390 }
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"artifacts": {
|
|||
|
|
"videoInfo": "video_info.json",
|
|||
|
|
"damageCandidates": "damage_candidates.json",
|
|||
|
|
"damageAnnotations": "damage_annotations.json",
|
|||
|
|
"bestFrames": "best_frames.json"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
失败时:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"success": false,
|
|||
|
|
"error": "视频文件不存在: ...",
|
|||
|
|
"taskId": "..."
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
stderr 可输出人类可读日志,但不能输出最终 JSON,以免 `SkillExecutorService` 解析 stdout 失败。
|
|||
|
|
|
|||
|
|
## 6. Workspace 协议
|
|||
|
|
|
|||
|
|
每次运行创建或复用一个 task workspace:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
workspace/{taskId}/
|
|||
|
|
├── source/
|
|||
|
|
│ └── input.mp4
|
|||
|
|
├── frames/
|
|||
|
|
│ └── frame_000001_0.33s.jpg
|
|||
|
|
├── marked_frames/
|
|||
|
|
│ └── dmg_001_12.40s.jpg
|
|||
|
|
├── video_info.json
|
|||
|
|
├── damage_candidates.json
|
|||
|
|
├── damage_annotations.json
|
|||
|
|
├── best_frames.json
|
|||
|
|
└── run_summary.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
生产环境 workspace 根目录必须位于后端 dataDir 下:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
{dataDir}/workspace/vehicle-damage-inspection/{taskId}/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Windows 安装包场景下,后端 dataDir 解析顺序由 `comm/data-dir.ts` 决定:外部 `config.yaml` 的 `data.dir`、`NETA_DATA_DIR`、pkg 模式 `<exe-dir>/data`。本 Skill 不直接 import 后端 `pDataPath()`,而是通过宿主注入的 env 使用该目录。
|
|||
|
|
|
|||
|
|
开发态 fallback:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
packages/backend/skills/vehicle-damage-inspection/.workspace
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
该 fallback 仅用于本地调试。生产运行如果既没有 `RZYX_AI_DATA_DIR`,也没有 `RZYX_AI_WORKSPACE_ROOT`,Skill 应返回配置错误,而不是默默写入 skill 目录。
|
|||
|
|
|
|||
|
|
`/upload/...` 路径解析策略:
|
|||
|
|
|
|||
|
|
- 如果 `videoUrl` 是存在的本地路径,直接使用。
|
|||
|
|
- 如果 `videoUrl` 匹配 `/upload/<relative>`,生产环境使用 `RZYX_AI_UPLOAD_ROOT/<relative>` 解析。
|
|||
|
|
- 开发态未配置 `RZYX_AI_UPLOAD_ROOT` 时,可从 `RZYX_AI_DATA_DIR/uploads` 或 `RZYX_AI_WORKSPACE_ROOT/../../uploads` 推导;生产环境不能依赖推导。
|
|||
|
|
- 第一版明确不支持 http(s) URL。遇到 http(s) 输入时返回 `success:false` 和明确错误;如果后续要支持远程 URL,必须新增下载模块,把视频下载到 `source/input.mp4`,并实现协议白名单、大小限制、超时、重定向限制和审计日志。
|
|||
|
|
|
|||
|
|
### 6.1 Windows 安装包运行时约束
|
|||
|
|
|
|||
|
|
Windows 安装包部署时,后端 `backend.exe`、托盘程序、配置文件和 bundled skills 都在安装目录附近;真正可写的业务数据必须在 dataDir。
|
|||
|
|
|
|||
|
|
本 Skill 需要遵守以下约束:
|
|||
|
|
|
|||
|
|
- bundled skill 目录只作为模板来源。首次启动时,后端会把 bundled skills 复制到 `{dataDir}/skills`;运行时加载和执行的是 `{dataDir}/skills/vehicle-damage-inspection`。
|
|||
|
|
- Skill 代码、prompt、脚本可以位于 `{dataDir}/skills/...`,但检测产物不得写入该目录,避免升级、重装、依赖安装或清理时误删业务数据。
|
|||
|
|
- workspace、下载视频副本、抽帧图片、标注图片、JSON artifact 都写入 `{dataDir}/workspace/vehicle-damage-inspection/{taskId}`。
|
|||
|
|
- 上传视频来源优先解析为 `{dataDir}/uploads/...`。
|
|||
|
|
- 日志只写 stderr 或 `{dataDir}/logs` 下由宿主管理的日志,不在安装目录创建日志。
|
|||
|
|
- 如需清理过期 workspace,应按 dataDir 下的 workspace mtime/manifest 执行,不扫描 skill 代码目录。
|
|||
|
|
- `sharp`、`fluent-ffmpeg`、`@ffmpeg-installer/ffmpeg`、`@ffprobe-installer/ffprobe` 等运行依赖应在打包或安装阶段准备好。生产运行时不依赖联网执行 `npm install`;`setup.ps1` 只用于安装器或人工修复。
|
|||
|
|
|
|||
|
|
### 6.2 宿主运行时需要补充的 env 注入
|
|||
|
|
|
|||
|
|
当前 `SkillExecutorService` 只注入 env 白名单和 skill secrets。为减少 Windows 安装包配置成本,宿主应在执行 compute-entry skill 时额外注入:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
RZYX_AI_DATA_DIR={pDataPath()}
|
|||
|
|
RZYX_AI_WORKSPACE_ROOT={pDataPath()}/workspace/vehicle-damage-inspection
|
|||
|
|
RZYX_AI_UPLOAD_ROOT={pUploadPath()}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这些不是密钥,不应要求管理员在 Skill secrets 中手动配置。Skill secrets 只负责模型 API Key、模型 ID 和可选覆盖项。
|
|||
|
|
|
|||
|
|
如果暂时不改 `SkillExecutorService`,MVP 实现必须把 `RZYX_AI_WORKSPACE_ROOT` 和 `RZYX_AI_UPLOAD_ROOT` 标记为部署必填配置,并在 Windows 安装文档里写清楚。
|
|||
|
|
|
|||
|
|
## 7. 流水线设计
|
|||
|
|
|
|||
|
|
### 7.1 抽帧
|
|||
|
|
|
|||
|
|
`frame_extractor.cjs` 负责:
|
|||
|
|
|
|||
|
|
- 检查视频存在。
|
|||
|
|
- 用 ffprobe 获取 duration、fps、分辨率、视频流信息。
|
|||
|
|
- 用 ffmpeg 按 `fps` 抽帧到 `frames/`。
|
|||
|
|
- 文件名包含序号和时间戳。
|
|||
|
|
- 写出 `video_info.json`。
|
|||
|
|
|
|||
|
|
抽帧输出:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"videoPath": "...",
|
|||
|
|
"videoInfo": {
|
|||
|
|
"duration": 42.3,
|
|||
|
|
"videoFps": 29.97,
|
|||
|
|
"resolution": "1920x1080",
|
|||
|
|
"extractFps": 3,
|
|||
|
|
"extractedFrames": 127
|
|||
|
|
},
|
|||
|
|
"frames": [
|
|||
|
|
{
|
|||
|
|
"index": 0,
|
|||
|
|
"timestamp": 0,
|
|||
|
|
"relativePath": "frames/frame_000001_0.00s.jpg",
|
|||
|
|
"path": "..."
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
第一版不做复杂视频质量过滤。可以保留轻量重复帧/模糊帧过滤接口,但默认关闭,避免误删证据帧。
|
|||
|
|
|
|||
|
|
### 7.2 环车旧伤候选检测
|
|||
|
|
|
|||
|
|
`damage_detector.cjs` 负责:
|
|||
|
|
|
|||
|
|
- 读取 `video_info.json` 和 `frames/`。
|
|||
|
|
- 按 `batchSize` 分批组装多模态请求。
|
|||
|
|
- 每张图前插入时间戳文本。
|
|||
|
|
- 调用 `DAMAGE_DETECT_MODEL`。
|
|||
|
|
- 要求模型只返回 JSON。
|
|||
|
|
- 合并各批结果并写出 `damage_candidates.json`。
|
|||
|
|
|
|||
|
|
检测 prompt 重点:
|
|||
|
|
|
|||
|
|
- 只检测车身漆面/外观旧伤:划痕、凹陷、掉漆、裂纹、锈蚀等。
|
|||
|
|
- 反光、污渍、阴影、压缩噪声不应直接判定为损伤。
|
|||
|
|
- 不确定结果标记 `confidence: "low"` 或 `uncertain: true`。
|
|||
|
|
- 输出位置、类型、严重程度、时间点和证据描述。
|
|||
|
|
|
|||
|
|
候选输出:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"totalFrames": 127,
|
|||
|
|
"batches": [
|
|||
|
|
{
|
|||
|
|
"batch": 1,
|
|||
|
|
"frameStart": 0,
|
|||
|
|
"frameEnd": 11,
|
|||
|
|
"status": "success"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"candidates": [
|
|||
|
|
{
|
|||
|
|
"id": "cand_001",
|
|||
|
|
"timestamp": 12.4,
|
|||
|
|
"part": "左前门",
|
|||
|
|
"type": "划痕",
|
|||
|
|
"severity": "轻微",
|
|||
|
|
"description": "左前门中部可见细长白色划痕",
|
|||
|
|
"confidence": "medium"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
并发控制:
|
|||
|
|
|
|||
|
|
- 默认 `concurrency=2`,避免视觉模型请求体过大导致限流。
|
|||
|
|
- 对 429/TooManyRequests 做指数退避重试。
|
|||
|
|
- API 请求超时默认 10 分钟。
|
|||
|
|
|
|||
|
|
### 7.3 损伤定位标注
|
|||
|
|
|
|||
|
|
`damage_grounding.cjs` 负责:
|
|||
|
|
|
|||
|
|
- 读取 `damage_candidates.json` 和 `video_info.json`。
|
|||
|
|
- 对每个 candidate 取 `timestamp ± groundingWindow` 的帧。
|
|||
|
|
- 调用 `DAMAGE_GROUNDING_MODEL`。
|
|||
|
|
- 解析 bbox。
|
|||
|
|
- 用 `image_marker.cjs` 画红框和标签。
|
|||
|
|
- 写出 `damage_annotations.json`。
|
|||
|
|
|
|||
|
|
bbox 坐标约定:
|
|||
|
|
|
|||
|
|
- 模型输出优先使用 0-1000 归一化坐标。
|
|||
|
|
- 内部统一转换为图片像素坐标后画框。
|
|||
|
|
- 输出 JSON 同时保存 normalized 和 pixel 坐标。
|
|||
|
|
|
|||
|
|
标注输出:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"annotations": [
|
|||
|
|
{
|
|||
|
|
"candidateId": "cand_001",
|
|||
|
|
"damageId": "dmg_001",
|
|||
|
|
"part": "左前门",
|
|||
|
|
"type": "划痕",
|
|||
|
|
"severity": "轻微",
|
|||
|
|
"markedFrames": [
|
|||
|
|
{
|
|||
|
|
"timestamp": 12.4,
|
|||
|
|
"sourceRelativePath": "frames/frame_000038_12.40s.jpg",
|
|||
|
|
"markedRelativePath": "marked_frames/dmg_001_12.40s.jpg",
|
|||
|
|
"bbox": {
|
|||
|
|
"normalized": { "x1": 120, "y1": 315, "x2": 280, "y2": 380 },
|
|||
|
|
"pixel": { "x1": 230, "y1": 340, "x2": 538, "y2": 410 }
|
|||
|
|
},
|
|||
|
|
"groundingRaw": "<bbox>120 315 280 380</bbox>"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果 grounding 模型未返回 bbox:
|
|||
|
|
|
|||
|
|
- 仍复制原图到 `marked_frames/`。
|
|||
|
|
- `bbox` 为空。
|
|||
|
|
- 标记 `groundingStatus: "no_bbox"`,后续最佳帧筛选降低评分。
|
|||
|
|
|
|||
|
|
### 7.4 最佳帧筛选
|
|||
|
|
|
|||
|
|
`best_frame_selector.cjs` 负责:
|
|||
|
|
|
|||
|
|
- 读取 `damage_annotations.json`。
|
|||
|
|
- 对候选损伤做去重合并。
|
|||
|
|
- 对每处损伤的标注帧选择最佳 `topN`。
|
|||
|
|
- 写出 `best_frames.json` 和最终 summary。
|
|||
|
|
|
|||
|
|
去重策略:
|
|||
|
|
|
|||
|
|
- 第一优先:调用 `BEST_FRAME_MODEL`,输入候选损伤摘要,让模型输出合并分组。
|
|||
|
|
- 兜底:位置文本相似、损伤类型一致、时间相近的候选合并。
|
|||
|
|
- 去重后生成稳定 `dmg_001`、`dmg_002` 编号。
|
|||
|
|
|
|||
|
|
最佳帧策略:
|
|||
|
|
|
|||
|
|
- 如果某处损伤候选独立帧数小于等于 3,按启发式评分选择每个时间点最好的标注版本。
|
|||
|
|
- 如果候选帧较多,调用模型选择 `topN * 3` 候选,再按原始时间戳去重,最终保留 `topN`。
|
|||
|
|
- 模型失败时使用启发式评分。
|
|||
|
|
- 第一版实现必须真实接入 `BEST_FRAME_MODEL` 的去重/筛选调用,不能只保留 env 而完全不用;启发式逻辑只作为失败兜底和少量候选快速路径。
|
|||
|
|
|
|||
|
|
启发式评分因素:
|
|||
|
|
|
|||
|
|
- 有 bbox 的帧优先。
|
|||
|
|
- bbox 面积适中优先。
|
|||
|
|
- bbox 不贴边优先。
|
|||
|
|
- 同一时间戳只保留评分最高的标注版本。
|
|||
|
|
|
|||
|
|
## 8. 错误处理
|
|||
|
|
|
|||
|
|
| 场景 | 行为 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| 输入视频不存在 | 返回 `success:false`,不创建后续 artifact |
|
|||
|
|
| 输入 http(s) URL | 第一版返回 `success:false`,提示仅支持本地路径和 `/upload/...` |
|
|||
|
|
| ffmpeg/ffprobe 缺失 | `make.sh check` 报错;运行时返回可诊断错误 |
|
|||
|
|
| 抽帧为空 | 返回失败,提示视频无法抽帧 |
|
|||
|
|
| 模型配置缺失 | 返回失败,明确指出缺少 env 名称 |
|
|||
|
|
| 生产环境缺少 dataDir/workspace/upload 运行目录 | 返回失败,提示宿主未注入 `RZYX_AI_DATA_DIR` 或必要路径 |
|
|||
|
|
| 某批检测失败 | 记录 batch error,继续处理其他批次 |
|
|||
|
|
| 全部检测批次失败 | 返回失败,保留 `video_info.json` 和错误摘要 |
|
|||
|
|
| 未发现损伤 | 返回 `success:true`,damageCount 为 0 |
|
|||
|
|
| grounding 部分失败 | 保留无 bbox 标注帧,后续筛选降权 |
|
|||
|
|
| best frame 模型失败 | 使用启发式兜底 |
|
|||
|
|
| stdout JSON 生成异常 | `index.cjs` 捕获后输出标准失败 JSON |
|
|||
|
|
|
|||
|
|
## 9. 与 Agent Runtime 的关系
|
|||
|
|
|
|||
|
|
本 Skill 通过当前 Skill Runtime 执行:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Agent -> execute_skill({ name, input })
|
|||
|
|
-> SkillExecutorService
|
|||
|
|
-> node scripts/index.cjs
|
|||
|
|
-> stdout JSON
|
|||
|
|
-> tool_result
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
ToolResolver 只需要在 Agent 绑定了 `vehicle-damage-inspection` 且工具治理允许 `execute_skill` 时注入执行工具。Agent 不需要看到内部四个模块,也不需要调用 `bash`。
|
|||
|
|
|
|||
|
|
为适配 Windows 安装包生产形态,`SkillExecutorService` 需要在执行该 Skill 时提供运行时目录 env。该改动属于宿主能力补强,不改变 `execute_skill` 对 Agent 暴露的 schema。
|
|||
|
|
|
|||
|
|
第一版不修改 `SkillExecutorService` 的流式协议。由于视频检测耗时较长,后续可以扩展:
|
|||
|
|
|
|||
|
|
- 子进程 stderr 输出 JSONL progress。
|
|||
|
|
- `SkillExecutorService` 识别 progress 事件并转成 `tool_result_streamed`。
|
|||
|
|
- 前端对 `execute_skill` 增加视频检测进度展示。
|
|||
|
|
|
|||
|
|
该扩展不阻塞 MVP。
|
|||
|
|
|
|||
|
|
## 10. 测试策略
|
|||
|
|
|
|||
|
|
### 10.1 单元测试
|
|||
|
|
|
|||
|
|
优先测试纯函数和模块边界:
|
|||
|
|
|
|||
|
|
- `parseJSON()`:兼容 markdown code fence 和文本包裹 JSON。
|
|||
|
|
- `parseBboxes()`:解析 `<bbox>x1 y1 x2 y2</bbox>`。
|
|||
|
|
- `workspace`:创建 task、写读 JSON、路径安全。
|
|||
|
|
- `syncSkillConfigEnvSchema()`:从 `skill.config.yaml env` 同步默认模型和密钥 schema 到 DB,而不是依赖 `SKILL.md metadata.env`。
|
|||
|
|
- `best_frame_selector`:去重兜底、启发式评分、同时间戳去重。
|
|||
|
|
- `image_marker`:给一张 fixture 图片画框,确认输出文件存在。
|
|||
|
|
|
|||
|
|
### 10.2 集成测试
|
|||
|
|
|
|||
|
|
使用小型测试视频:
|
|||
|
|
|
|||
|
|
- `make.sh check` 验证依赖。
|
|||
|
|
- `make.sh run --video fixtures/car.mp4 --mode frames-only` 验证抽帧。
|
|||
|
|
- mock `vision_client` 验证完整流水线不依赖真实 API。
|
|||
|
|
- 有真实模型配置时,手动运行完整视频检测。
|
|||
|
|
|
|||
|
|
### 10.3 验收标准
|
|||
|
|
|
|||
|
|
MVP 完成的判断:
|
|||
|
|
|
|||
|
|
- 后端 skill loader 能识别该 Skill 为 `compute-entry`。
|
|||
|
|
- Agent 绑定该 Skill 后能通过 `execute_skill` 调起。
|
|||
|
|
- 输入视频后能生成 `video_info.json`、`damage_candidates.json`、`damage_annotations.json`、`best_frames.json`。
|
|||
|
|
- 有损伤视频能输出至少一张红框标注图。
|
|||
|
|
- 无损伤视频能返回 `success:true` 且损伤数为 0。
|
|||
|
|
- 未配置模型密钥时返回明确错误,不出现硬编码密钥。
|
|||
|
|
|
|||
|
|
## 11. 实施顺序
|
|||
|
|
|
|||
|
|
1. 补强 Skill 配置加载:从 `skill.config.yaml env` 同步 `envSchema`,确保默认模型和管理页一致。
|
|||
|
|
2. 创建 Skill 目录、`SKILL.md`、`skill.config.yaml`、`README.md`。
|
|||
|
|
3. 补强 `SkillExecutorService`:注入 `RZYX_AI_DATA_DIR`、`RZYX_AI_WORKSPACE_ROOT`、`RZYX_AI_UPLOAD_ROOT` 这三个非密钥运行时 env。
|
|||
|
|
4. 实现 `scripts/index.cjs` stdin/stdout 协议和统一错误包装。
|
|||
|
|
5. 实现 workspace 和路径解析,生产路径固定落在 dataDir 下,并明确拒绝 http(s) URL。
|
|||
|
|
6. 实现 ffmpeg 抽帧。
|
|||
|
|
7. 实现 vision client、prompt 文件和模型配置读取。
|
|||
|
|
8. 实现候选损伤检测。
|
|||
|
|
9. 实现 grounding 和图片标注。
|
|||
|
|
10. 实现 `BEST_FRAME_MODEL` 参与的去重/最佳帧筛选,以及启发式兜底。
|
|||
|
|
11. 增加 `make.sh check/run/demo` 和 Windows `setup.ps1`。
|
|||
|
|
12. 增加必要测试或 mock 验证脚本。
|
|||
|
|
13. 用 pkg/Windows 安装包路径模拟验证:确认 workspace/uploads/logs 都不写入安装目录或 skill 代码目录。
|
|||
|
|
14. 在技能管理页配置模型 env secrets 后进行真实视频手测。
|
|||
|
|
|
|||
|
|
## 12. 风险与后续扩展
|
|||
|
|
|
|||
|
|
### 12.1 主要风险
|
|||
|
|
|
|||
|
|
- 多模态模型容易把反光、污渍、阴影误判为旧伤。
|
|||
|
|
- 一次发送过多帧会降低模型注意力并增加请求失败概率。
|
|||
|
|
- grounding 模型能力不稳定时,bbox 可能偏移或为空。
|
|||
|
|
- 长视频会导致抽帧和模型调用成本高。
|
|||
|
|
- 当前 `execute_skill` 不支持流式进度,用户体验可能是长时间等待。
|
|||
|
|
|
|||
|
|
### 12.2 缓解策略
|
|||
|
|
|
|||
|
|
- 默认低 fps 和小 batch。
|
|||
|
|
- prompt 明确要求不确定就标记 uncertain,不强行报损伤。
|
|||
|
|
- 对 bbox 失败保留原图和状态,避免流程中断。
|
|||
|
|
- 保留 `mode` 参数支持分阶段调试。
|
|||
|
|
- 输出完整 artifact,方便人工复核和后续重跑。
|
|||
|
|
|
|||
|
|
### 12.3 后续扩展
|
|||
|
|
|
|||
|
|
- 增加 progress JSONL 协议。
|
|||
|
|
- 增加前端专用结果卡片。
|
|||
|
|
- 增加人工复核和修正 bbox。
|
|||
|
|
- 增加报告生成 Skill 或复用文档生成 Skill。
|
|||
|
|
- 累积标注数据后训练专用漆面损伤检测模型。
|