This commit is contained in:
Kevin
2026-05-21 15:48:03 +08:00
commit c869fcc6b9
261 changed files with 45423 additions and 0 deletions

View File

@@ -0,0 +1,381 @@
# 手术室耗材监控系统
基于 **手术室主视角录像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`,可以单独提出要求再补。