权重目录与 doctor_info.pth 纳入版本库;样本视频、Excel、构建缓存仍保持 ignore。 Co-authored-by: Cursor <cursoragent@cursor.com>
手术室耗材监控系统
基于 手术室主视角录像(MP4) 与 术中商品 Excel,对术中耗材可能出现的时段进行端到端推断:先做 Phase 1 — 全局时序切分(VideoSwin 视频特征 + ActionFormer),再对每个时段做 Phase 2 — 细粒度段内推断(Ultralytics YOLO:手部 ROI、好坏帧、耗材 Top-K、撕膜模型门控);可选地在全部段落跑完后执行 撕膜相邻段合并(tear_merge),最终将每个 ActionFormer 段对应的主结果写成 TSV。
本项目定位为 离线推理包:配置集中在 YAML,Python 源码中不写死业务路径;src/ 负责解析配置与编排,code/ 为算法脚本与第三方子树的导出目录。
目录
能力与适用场景
- 输入:单路术中主视角视频;Excel 中含商品名称等与视频画面中文案/条码对应的字段,流程会用它构建「名称 → 商品编码」映射;可选 JSON 白名单限制耗材分类的输出类别集合。
- 输出:对每个 ActionFormer 时段一行,给出耗材 Top 1/2/3 名称与置信度,以及映射后的 product_id;默认不写出撕膜类名列(见下文)。
- 典型用途:手术视频质检、耗材使用回溯、与院内物资系统对接前的中间结果导出。
不在本 README 范围内的内容:模型训练脚本、数据采集规范、院内部署与鉴权——这些通常在全量开发仓库中维护。
流水线概览
Phase 1:时段提案(ActionSegmenter)
- 在工作目录
work/input/下放一份视频的副本(文件名与stem一致)。 - 调用
run_feature_extraction:用 torchvision Swin3D-T 预训练 backbone 抽取视频特征,写出features/*.npy与meta.json。 - 生成单视频推理所需的 JSON/YAML(
infer_single.json/infer_single.yaml)。 - 子进程调用 ActionFormer 的
eval.py,载入weights.actionformer的检查点,得到eval_results.pkl。 - 从 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] 合并窗段全量重推理)。
简要数据流(概念图)
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 官方指引 安装 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 扩展,ActionFormereval.py依赖;必须在仓库根目录执行pip install -e ...(见下文)。
安装
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)。
运行方式
# 默认读取 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。以下与 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.Namespace(SimpleNamespace)
│ ├── 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。
ActionFormer 子项目
code/actionformer_release/ 基于 ActionFormer 公开代码演进而来:
- 训练 / 数据集 / FAQ:见其
README.md、FAQ.md、INSTALL.md。 - 许可证:
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为准。 - 本仓库其余部分:若在 GitHub 公开,建议在仓库根 自行添加
LICENSE(公司内部可保留专有许可),并与 PyTorch / Ultralytics / OpenCV 等依赖的开源许可证要求自行核对后再分发模型权重。
若需英文版 README、Dockerfile 或示例 configs/ci_smoke.yaml,可以单独提出要求再补。