Files
operating-room-monitor-server/backend/algorithm_subprocesses/5.15/README.md
2026-05-21 15:48:03 +08:00

382 lines
19 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 手术室耗材监控系统
基于 **手术室主视角录像MP4****术中商品 Excel**,对术中耗材可能出现的时段进行端到端推断:先做 **Phase 1 — 全局时序切分**VideoSwin 视频特征 + ActionFormer再对每个时段做 **Phase 2 — 细粒度段内推断**Ultralytics YOLO手部 ROI、好坏帧、耗材 Top-K、撕膜模型门控可选地在全部段落跑完后执行 **撕膜相邻段合并**`tear_merge`),最终将每个 ActionFormer 段对应的主结果写成 **TSV**
本项目定位为 **离线推理包**:配置集中在 YAMLPython 源码中不写死业务路径;`src/` 负责解析配置与编排,`code/` 为算法脚本与第三方子树的导出目录。
---
## 目录
- [能力与适用场景](#能力与适用场景)
- [流水线概览](#流水线概览)
- [环境与依赖](#环境与依赖)
- [安装](#安装)
- [准备权重与输入](#准备权重与输入)
- [运行方式](#运行方式)
- [配置文件逐项说明](#配置文件逐项说明)
- [输出文件与列含义](#输出文件与列含义)
- [目录结构](#目录结构)
- [与上游开发仓库的关系](#与上游开发仓库的关系)
- [ActionFormer 子项目](#actionformer-子项目)
- [常见问题与排错](#常见问题与排错)
- [许可证](#许可证)
---
## 能力与适用场景
- **输入**单路术中主视角视频Excel 中含商品名称等与视频画面中文案/条码对应的字段,流程会用它构建「名称 → 商品编码」映射;可选 JSON 白名单限制耗材分类的输出类别集合。
- **输出**:对每个 ActionFormer 时段一行,给出耗材 **Top 1/2/3** 名称与置信度,以及映射后的 **product_id**;默认不写出撕膜类名列(见下文)。
- **典型用途**:手术视频质检、耗材使用回溯、与院内物资系统对接前的中间结果导出。
不在本 README 范围内的内容:模型训练脚本、数据采集规范、院内部署与鉴权——这些通常在全量开发仓库中维护。
---
## 流水线概览
### Phase 1时段提案ActionSegmenter
1. 在工作目录 `work/input/` 下放一份视频的副本文件名与stem一致
2. 调用 `run_feature_extraction`:用 **torchvision Swin3D-T** 预训练 backbone 抽取视频特征,写出 `features/*.npy``meta.json`
3. 生成单视频推理所需的 JSON/YAML`infer_single.json` / `infer_single.yaml`)。
4. 子进程调用 **ActionFormer**`eval.py`,载入 `weights.actionformer` 的检查点,得到 `eval_results.pkl`
5. 从 pkl 解析时段列表,依次应用:
- 分数阈值 `phase1.af_min_score`
- 互斥贪心合并(与同仓库逻辑一致)
- 最短时长过滤 `phase1.af_min_seg_seconds`
### Phase 2段内细粒度推断
对每个存活时段 `(start_sec, end_sec)`
- 用 OpenCV `VideoCapture` 在区间内按 `phase2.frame_stride` 抽样帧。
- **手部检测**`weights.hand`,置信度 `phase2.det_conf`,检测框经 `phase2.pad_ratio` 等与 `HandRoiGrouper` 合并规则处理。
- **FineGrainedClassifier**:在同一 ROI 链路中依次调用好坏帧、耗材分类、撕膜模型;阈值由 `classification.*` 与重试逻辑控制;耗材输出会按 Excel/JSON **白名单** 过滤允许的类别索引。
若某段推断失败(如无法得到有效手部/分类结果),仍会输出一行,`top1_name` 等字段会填入 **可读原因字符串**(见「输出文件」)。
### 可选:撕膜相邻段合并(`tear_merge`
`tear_merge.merge_adjacent_tear: true` 时,在全部段初步写表后:
- 用撕膜模型对相邻段是否在「撕膜」行为上达成一致做门控;可用 `tear_merge_weights` 指定与推理阶段不同的权重。
- 合并后的段落可能 **复用已缓存的细胞格**(同起止时间与 key若不存在缓存则会对合并窗 **全量再推理一次**(日志会有 `[tear_merge] 合并窗段全量重推理`)。
### 简要数据流(概念图)
```mermaid
flowchart LR
V[MP4 + Excel] --> P1[VideoSwin 特征]
P1 --> AF[ActionFormer 提案时段]
AF --> P2[YOLO 手/好坏帧/耗材/撕膜]
P2 --> TM{撕膜合并?}
TM -->|是| OUT[TSV]
TM -->|否| OUT
```
---
## 环境与依赖
| 组件 | 说明 |
|------|------|
| **操作系统** | Linux 为主(开发验证环境);若在 Windows/macOS 上跑,需注意路径与子进程脚本兼容性。 |
| **Python** | 建议 **3.10+**(与 PyTorch 2.x、第三方库的常见组合一致。 |
| **CUDA / PyTorch** | 需 **与你的显卡驱动匹配的** PyTorch / torchvision。**务必先**按 [PyTorch 官方指引](https://pytorch.org/get-started/locally/) 安装 `torch``torchvision`,再 `pip install -r requirements.txt`,避免 pip 拉上不匹配的 cu 版本。 |
| **磁盘** | 工作目录中会暂存特征 `.npy`、中间 json/yaml、pkl长视频与工作目录设置在固定路径时注意预留空间。 |
`requirements.txt` 中的 **Python 包**(节选含义):
- `torch` / `torchvision`:特征与后端(版本行仅作下限,实际应与你的 CUDA 环境一致)。
- `ultralytics`YOLOv8 系 API加载 `.pt` 手部与分类权重。
- `opencv-python`:读视频、`seek`/抽帧。
- `pandas``openpyxl`:读取 Excel。
- `PyYAML`:解析 `configs/*.yaml`
此外还有 **editable 安装的本地包**
- `code/actionformer_release/libs/utils`:含 **NMS 等 C++/CPU 扩展**ActionFormer `eval.py` 依赖;必须在仓库根目录执行 `pip install -e ...`(见下文)。
---
## 安装
```bash
git clone <你的仓库 URL>
cd <仓库目录>
# 推荐使用 venv / conda以下为 venv 示例
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 1先安装与 CUDA 匹配的 torch / torchvision见官网
# 2再安装本仓库列出的其余依赖
pip install -r requirements.txt
# 3编译安装 ActionFormer 侧 NMS 扩展(在仓库根目录执行)
pip install -e code/actionformer_release/libs/utils
```
要点:
- **`pip install -e`** 的安装目录必须是 **`code/actionformer_release/libs/utils`**(该目录下有 `setup.py`),且在 **已从仓库根 `import torch` 无报错** 的前提下执行。
- 若升级了 PyTorch有时需要对该扩展 **重新安装**
---
## 准备权重与输入
### 权重(放入 `weights/`,或由 YAML 指到任意绝对路径)
本流程需要 **5 个** Ultralytics/YOLO 或 ActionFormer 检查点文件:
| YAML 配置键 | 建议文件名 | 作用 |
|-------------|------------|------|
| `weights.actionformer` | `actionformer_epoch_045.pth.tar` | Phase 1 ActionFormer 推理 |
| `weights.hand` | `hand_detect.pt` | 手部检测 |
| `weights.goodbad` | `goodbad_frame.pt` | 好坏帧二分类 |
| `weights.haocai` | `haocai_classify.pt` | 耗材多类分类 |
| `weights.tear` | `tear_classify.pt` | 撕膜相关(段内门控 + 可选合并阶段) |
**VideoSwin** 使用 torchvision 自带的 **Swin3D-T** ImageNet/Kinetics 侧预训练权重,**不**要求用户再提供单独权重文件。
若你仍能从完整开发仓库拷贝文件,可参考以下 **相对路径**(来自内部交接文档,仅供找回文件时对照;完整仓库不一定随本 pack 分发):
| 配置键 | 建议文件名 | 开发仓库中参考路径(若存在) |
|--------|------------|------------------------------|
| `weights.actionformer` | `actionformer_epoch_045.pth.tar` | `code/video_clip_cls/runs/actionformer_ckpt/haocai_main_perspective_videoswin_haocai_main_perspective_videoswin/epoch_045.pth.tar` |
| `weights.hand` | `hand_detect.pt` | `code/hand_detection/runs/hand_det_y11s_multiframe-better/weights/best.pt` |
| `weights.goodbad` | `goodbad_frame.pt` | `code/goodORbad_frame/runs/goodbad_frame_y11m_e50/weights/best.pt` |
| `weights.haocai` | `haocai_classify.pt` | `code/haocai_classify/runs/haocai_cls_41cls_goodframe_721_e50_p8-0.96/weights/best.pt` |
| `weights.tear` | `tear_classify.pt` | `code/tear_classify/runs/tear_cls_bicls_y11m_e50_ds0511/weights/best.pt` |
### Excel 与白名单
- **`io.excel`**:用于 `e2e.load_product_code_map` 等逻辑构建 **耗材名称 ↔ 编码**;表格结构需与你的训练/线上约定一致。
- **`io.whitelist_json: null`**:启用 **从 Excel 第一张工作表的 C 列**读取一串「允许出现的商品名称」,再映射到分类 head 的允许类别索引(与线上一致)。
- **`io.whitelist_json`** 若为路径JSON 形如 `{"allowed_names":["名称A","名称B",...]}`
### 视频路径建议
配置里 **`io.video` / `io.excel` / `io.out`** 既可写 **相对路径**(相对 **仓库根**,即包含 `main.py` 的那一层),也可写绝对路径。
上传至 GitHub 的仓库中建议 **不要将个人机上的绝对路径** 提交进 `configs/default_config.yaml`;可使用 `input/xxx.mp4` 占位,每台机器本地化修改或使用独立 `configs/local.yaml`(并加入 `.gitignore`)。
---
## 运行方式
```bash
# 默认读取 configs/default_config.yaml
python main.py
# 指定配置文件
python main.py --config configs/my_run.yaml
```
**命令行接口**`main.py`
| 参数 | 含义 |
|------|------|
| `--config` | YAML 配置文件路径;默认 `configs/default_config.yaml`。 |
运行前,`config.py` 会将 YAML 中相对路径一律 **-resolve 为基于仓库根的绝对路径**`runtime.python``null` 时使用 **`sys.executable`** 调用子进程(特征提取与 ActionFormer eval从而在 venv 内保持一致解释器。
**环境变量**(入口已设默认):
- `OPENCV_FFMPEG_LOGLEVEL`:降低 OpenCV FFmpeg 后端日志冗长度;可按需在外层 shell 覆盖。
---
## 配置文件逐项说明
主模板见 [`configs/default_config.yaml`](configs/default_config.yaml)。以下与 `src/config.py` 的映射一致。
### `io`
| 键 | 类型 | 说明 |
|----|------|------|
| `video` | 路径 | 输入 MP4 |
| `excel` | 路径 | 输入 Excel |
| `out` | 路径 | 输出 TSV父目录会自动创建 |
| `whitelist_json` | `null` 或路径 | `null` 表示从 Excel 第一表 C 列读白名单;否则为 JSON 文件路径 |
### `weights`
五个模型文件路径,缺失时进程会在校验阶段报错并打印「缺少 xxx」。
### `runtime`
| 键 | 说明 |
|----|------|
| `work_dir` | `null`:使用系统临时目录(进程结束默认删除);非 `null`:固定中间结果目录 |
| `keep_work_dir` | `true` 且无 `work_dir` 时,使用 `tempfile.mkdtemp`**保留** 目录(日志会打印路径) |
| `python` | `null`/`""`:子进程使用当前解释器;否则填绝对路径可用于多环境隔离 |
### `device`
| 键 | 说明 |
|----|------|
| `type` | `cuda``cpu`CPU 仅能验证逻辑,速度慢) |
| `half` | `true` 时向 Ultralytics `predict` 传入半精度以降低显存(依 GPU 支持情况而定) |
### `phase1`ActionFormer + 特征)
| 键 | 说明 |
|----|------|
| `af_min_score` | 保留提案段的分数下限 |
| `af_min_seg_seconds` | 段长(秒)下限;`<=0` 可关闭最短段过滤 |
| `feat_batch_size` | VideoSwin 特征批大小;显存紧张时可设为 `1` |
### `phase2`(解码与检测)
| 键 | 说明 |
|----|------|
| `seek_margin_sec` | 打开段落时在起止两侧的 seek 留白,减轻关键帧不齐带来的边界效应 |
| `frame_stride` | 抽帧步长,`>=1` |
| `det_conf` | 手部检测置信度阈值 |
| `pad_ratio` | 手部框缩放/填充比例(与 `_pad_box` 等一致) |
| `imgsz_det` | YOLO 检测输入分辨率 |
| `merge_iou_gt` | ROI 合并IoU 相关阈值 |
| `merge_center_dist_max_px` | 合并:中心距离像素上限,`null` 表示不用该项 |
| `merge_center_dist_max_frac_diag` | 合并:中心距离相对对角线比例上限,`null` 表示不用 |
### `classification`
| 键 | 说明 |
|----|------|
| `imgsz_cls` | 分类支路输入尺寸 |
| `good_top1_conf_threshold` | 「好帧」Top1 置信度门槛 |
| `good_top1_retry_threshold` | 重试时使用的好帧阈值(与段内 retry 逻辑配合) |
| `haocai_min_conf` | 耗材 Top1 主阈值 |
| `haocai_min_conf_retry` | 第二次尝试的耗材阈值;**`<=0` 关闭**二段重试;若 **`haocai_min_conf`** 则配置会被视为等价于关闭重试 |
| `empty_cache_every` | 每隔若干步 `torch.cuda.empty_cache()``0` 表示不调用 |
### `tear_merge`
| 键 | 说明 |
|----|------|
| `merge_adjacent_tear` | 是否在写表前做相邻撕膜导向的合并 |
| `tear_merge_weights` | `null` 则用 `weights.tear`;否则指定合并专用权重路径 |
| `tear_merge_class` | 撕膜类别名(与 `tear` 模型 `names` 对齐),默认 `"tearing"` |
| `tear_merge_head_sec` | 合并判定时观察的段首秒数 |
| `tear_merge_prob` | 撕膜概率阈值 |
| `tear_merge_min_frames` | 最少帧数要求 |
| `tear_merge_verbose` | 是否打印冗余日志 |
| `tear_merge_full_frame` | `true` 时合并路径用全帧而非手部分支(按需实验) |
### `output`
| 键 | 说明 |
|----|------|
| `legacy_12_col_only` | `true`(默认):**12 列** TSV**不含** `tear_top1_name``tear_top2_name``false`14 列,保留撕膜 Top2 名称列 |
### `doctor_identity`
| 键 | 说明 |
|----|------|
| `enabled` | 是否启用医生身份识别后处理;`true` 时会在结果文件末尾追加 `医生信息:...` |
| `checkpoint` | 医生识别模型权重路径,默认 `doctor_identity_package/doctor_info.pth` |
| `labels_csv` | `person_id -> 医生姓名` 映射表,默认 `doctor_identity_package/labels.csv` |
| `middle_seconds` | 取视频中间窗口长度(秒),默认 `10.0` |
| `sample_fps` | 中间窗口采样帧率,默认 `3.0` |
| `pad_frac` | 人体框外扩比例,默认 `0.15` |
---
## 输出文件与列含义
- **分隔符**:制表符 **Tab**
- **编码**UTF-8。
- **表头**:与 `legacy_12_col_only` 一致。
**12 列(默认)**`rank``start_sec``end_sec``product_id_top1``top1_name``top1_conf``product_id_top2``top2_name``top2_conf``product_id_top3``top3_name``top3_conf`
**14 列**:在末尾增加 `tear_top1_name``tear_top2_name`
说明:
- `product_id_*` 由 Excel商品表映射若耗材名存在但表中无映射仍会输出名称编码为空_stderr 可能出现 **「商品表无名称…」** 警告。
- 推断失败行仍会占用一行:`top1_name`(及对应 conf 为空)常为 **可读失败原因**,便于离线筛查。
- 文件末尾会额外追加一行医生识别结果,格式固定为:`医生信息XXX`。例如:`医生信息:付玉峰 (id=24503, conf=0.9969)`
---
## 目录结构
```
.
├── main.py # CLI读 YAML调用编排
├── requirements.txt
├── README.md # 本文件
├── README_handoff.md # 简短交接备忘(可与本 README 互为补充)
├── configs/
│ └── default_config.yaml # 默认配置模板
├── src/
│ ├── __init__.py
│ ├── paths.py # 将 code/video_clip_cls/scripts 等加入 sys.path
│ ├── config.py # YAML -> argparse.NamespaceSimpleNamespace
│ ├── pack_utils.py # Excel 白名单加载等共用工具
│ ├── orchestrator.py # PipelineManager主流程
│ └── actionformer_utils.py # Phase1 ActionSegmenter
├── weights/ # 用户提供 .pth.tar / .pt勿提交大文件时用 .gitignore
├── input/
├── output/
├── data/
└── code/ # 算法导出树(一般不改)
├── repo_root.py
├── dataset.py
├── video_clip_cls/ # VideoSwin 提特征、e2e、pipeline 脚本…
│ └── ...
└── actionformer_release/ # ActionFormer 上游 + 本项目 yaml / train / eval
├── README.md
├── INSTALL.md
└── LICENSE
```
`src/paths.py` 会校验 `code/repo_root.py` 存在,并依次 prepend`video_clip_cls/infer_single_0506``video_clip_cls/scripts``code` 根,以便 `import run_haocai_actionformer_consumables_e2e``import pipeline.segment_processor` 等板块与原版脚本一致。
---
## 与上游开发仓库的关系
-**`code/`** 目录常为从更大研发仓库 **剪出的子树****编排入口**已从散落的 argparse 收口为:**YAML → `src/config.py``src/orchestrator.py`**。
- 若在段内推断或合并逻辑上与历史行为不一致,可对照 **`code/video_clip_cls/scripts/main_pipeline.py`**(同一 `PipelineManager` 思想)。
- 更偏「给谁解压就能跑」的清单式说明可见 [`README_handoff.md`](README_handoff.md)。
---
## ActionFormer 子项目
[`code/actionformer_release/`](code/actionformer_release/) 基于 ActionFormer 公开代码演进而来:
- **训练 / 数据集 / FAQ**:见其 [`README.md`](code/actionformer_release/README.md)、[`FAQ.md`](code/actionformer_release/FAQ.md)、[`INSTALL.md`](code/actionformer_release/INSTALL.md)。
- **许可证**[`LICENSE`](code/actionformer_release/LICENSE)。
本 README 不重述上游论文公式与训练指令;仅强调 **推理路径**依赖 `nms_1d_cpu` 的可编辑安装。
---
## 常见问题与排错
| 现象 | 可能原因与处理 |
|------|----------------|
| Import / `nms` 相关错误eval 阶段) | 未安装扩展:在仓库根执行 `pip install -e code/actionformer_release/libs/utils`;确认先能 `python -c "import torch"`。 |
| `找不到视频` / `找不到 Excel` | 检查 YAML 路径;相对路径是相对 **仓库根**,不是当前 shell 工作目录层级错误。 |
| `缺少手部检测` 等 | `weights/` 下文件名与 YAML 不一致,或拷贝不完整。 |
| CUDA OOM | 减小 `feat_batch_size`;设 `device.half: true`;或缩短试运行视频长度。 |
| CPU 太慢 | Phase1 特征 + AF 在长视频上单核/单进程极慢属预期;仅建议短 clip 冒烟。 |
| OpenCV 无法打开 MP4 | 检查文件是否损坏、编码是否罕见;必要时用 ffmpeg 重封装为 H.264 MP4。 |
| 商品编码总为空 | 检查 Excel 表结构是否与 `load_product_code_map` 假设一致;名称字符串是否与模型输出完全一致(空格、别名)。 |
---
## 许可证
- **ActionFormer 相关代码**:以 [`code/actionformer_release/LICENSE`](code/actionformer_release/LICENSE) 为准。
- **本仓库其余部分**:若在 GitHub 公开,建议在仓库根 **自行添加** `LICENSE`(公司内部可保留专有许可),并与 **PyTorch / Ultralytics / OpenCV** 等依赖的开源许可证要求自行核对后再分发模型权重。
若需英文版 README、Dockerfile 或示例 `configs/ci_smoke.yaml`,可以单独提出要求再补。