Trim use-can to buildable runtime sources and standalone Orin package

This commit is contained in:
zmv
2026-09-23 19:17:17 +08:00
parent 9aadf9cbda
commit 0f609612a6
108 changed files with 2365 additions and 35388 deletions
+56 -170
View File
@@ -1,205 +1,91 @@
# vision-anti-pinch-0923
# Vision Anti-Pinch — use-can
以 Orin `~/vision-anti-pinch` 的 V4L2 MMAP 摄像头及 USBCANFD 模块为基线,接入 CMS v9.10 `run_risk.py` 的完整风险流程。模型及 GPU 推理代码来自远端 `~/vision-anti-pinch-0920`。
V4L2 摄像头 + CAN 门状态 + YOLO/RTMW TensorRT 推理 + CMS 风险判断。
默认持续推理,将最新完整结果保存在内存;可视化和文件保存均按需开启。
```
基线 V4L2 MMAP → NPP UYVY 转 BGR → 最新帧槽位
→ YOLO11s TensorRT → RTMW-L TensorRT(含小人遮挡隔离推理)
→ Python 原版轨迹 / 手臂手部融合 / 全局手关联
→ 骨骼主导的前后门归属 → 固定 mask 风险 → 逐帧 CAN 活跃门过滤
→ 内存中的最新只读结果(可选 JSONL / 浏览器预览)
```
## Orin 直接运行
## 仓库分支与 Orin 直接运行包
- `main`:原 CMS v9.10 Python/ONNX 部署包。
- `inference-only`:保留原 main 的纯 C++ TensorRT 推理版本。
- `use-can`(本分支):0923 CAN 集成版本。
本分支已提供可单独复制的 `orin/` 运行包,包含预编译库和 TensorRT 模型:
适用:Jetson AGX Orin,JetPack 5.1.2 / L4T R35.4.1、CUDA 11.4、TensorRT 8.5.x、系统 OpenCV 4.5.4;Python 3.8+,numpy、cv2。
`orin/` 包含编译库、TensorRT 引擎、CAN 库、摄像头驱动、Python 运行代码和标定文件,可以单独复制到其他目录。
```bash
git clone --branch use-can https://git.kendeji.fun/zmv/vision-anti-pinch.git
cd vision-anti-pinch/orin
./scripts/initialize_camera.sh # 开机后需要初始化摄像头时执行
./run.sh # 默认无可视化,结果保存在内存
./run.sh --visualize # 可选预览
cd orin
./scripts/initialize_camera.sh # 开机后无 /dev/video5 时初始化,需要 sudo
./run.sh # 默认无可视化,结果保存在内存
# 或开启浏览器预览:http://设备IP:8765
./run.sh --visualize
```
`orin/run.sh` 通过相对目录定位运行库、模型与标定,使用系统 Python,无需现场编译。目标环境及下游调用说明见 `orin/README.md`。
源码变更后,在目标 Orin 构建并执行 `scripts/package_orin.sh` 重新生成运行包;`ENGINE_DIR` 和 `DRIVER_DIR` 可指定模型/驱动目录。
每次只运行一个采集进程;Ctrl+C / SIGTERM 停止。关闭浏览器不会停止推理。
系统 CUDA/TensorRT/OpenCV/Python 运行环境由目标系统提供,不随包重复分发。TensorRT 引擎不能直接用于 Thor 或其他 JetPack/GPU 环境。
## 两端目录和环境
## 从源码编译
- 本机:`/home/thor/文档/vision-anti-pinch-0923`
- Orin:`/home/orin/vision-anti-pinch-0923`
- 目标推理环境:Orin,JetPack R35.4.1、CUDA 11.4、系统 Python 3.8、原生 TensorRT。
- 本机保存完整源码、测试报告、模型副本;本机是 Thor / R38,未发现 `/dev/video*`。Orin 的序列化 engine 不能直接作为 Thor 的本机模型使用,需在 Thor 上另行转换。本次实时采集/推理在 Orin,浏览器可在本机查看。
- 远端 `models/engines` 软链接到 `../../vision-anti-pinch-0920/orin/engines`,避免重复占用有限磁盘。不要删除被引用的 0920 模型。
- 原有远端 `vision-anti-pinch` / `vision-anti-pinch-0920` 源码均未修改。测试时已停止占用 `/dev/video5` 的旧 0920 演示进程,0923 可独立启动。
## 编译和运行(在 Orin)
在目标 Orin 上执行,依赖 CMake 3.24+、CUDA/NPP、TensorRT、OpenCV 开发库:
```bash
cd ~/vision-anti-pinch-0923
./scripts/build.sh
# 开机后如 /dev/video5 不存在,加载已部署的 0920 摄像头驱动:
./scripts/initialize_camera.sh
# 编译也可使用 make
./run_camera.sh --active-door can --can-side left
# 等价入口:python3 run_risk.py --active-door can --can-side left
./scripts/build.sh # 或 make
./scripts/initialize_camera.sh # 摄像头尚未初始化时执行
./run_camera.sh # 使用新编译的 build/libvision_native.so
```
默认只在内存中发布结果,不创建结果目录,不绘制或编码图像,不启动 HTTP 服务。
后台启动、停止:
源码入口复用 `orin/models/engines/` 和 `orin/lib/`,不依赖旧版工程。
修改源码后刷新直接运行包:
```bash
./scripts/start.sh # 无可视化
./scripts/stop.sh
./scripts/start.sh --visualize # 可选浏览器预览
./scripts/package_orin.sh # 或 make package
cd orin && sha256sum -c SHA256SUMS
```
默认从 `/dev/video5` 采集 UYVY 1920×1536,驱动当前为 30 FPS。驱动不支持 `VIDIOC_G_PARM`,程序会打印警告并继续采集,命令行 fps 不代表驱动一定支持修改。
打包会替换运行代码,清除已删除的旧 Python 模块,并更新文件校验值。
- 开启 `--visualize` 后,预览:[http://192.168.195.100:8765](http://192.168.195.100:8765)
- 开启 `--visualize` 后,最新结果:[http://192.168.195.100:8765/result.json](http://192.168.195.100:8765/result.json)
- 开启 `--visualize` 后,最新图像:[http://192.168.195.100:8765/frame.jpg](http://192.168.195.100:8765/frame.jpg)
- 后台进程日志:`logs/live.log`,PID:`logs/camera.pid`;加保存开关后,后台结果写入 `outputs/live/`。
- `--visualize` 开启预览;仅设置 `--preview-port` 不会开启预览。监听地址默认 `0.0.0.0:8765`,可用 `--preview-host 127.0.0.1` 限制为本机。可视化在独立观察线程中绘制和编码,只保留最新待绘制帧。端口占用或绘制失败不会终止推理;关闭浏览器也不会停止推理。
## 下游读取结果
摄像头限定时长、模拟门状态:
```bash
./run_camera.sh --no-can --active-door both --duration 30 --save-results --output outputs/demo
```
有人视频回放(同一 TensorRT/融合/风险链路):
```bash
./run_camera.sh --video tests/person-test.mp4 --no-can --active-door both \
--max-frames 150 --save-results --save-pose-records --output outputs/replay
```
`tests/person-test.mp4` 来自用户提供的 `segment_009.mp4` 第 10 秒起的 150 帧片段。
## 下游直接读取结果变量
参考 0920 的 `LatestResult` / `CameraPipeline.latest()`,0923 提供同进程 Python 接口:
在同一个 Python 进程中创建流水线,然后读取单个最新只读快照:
```python
from run_camera import CameraPipeline, parse_args
args = parse_args([]) # 默认 CAN 模式;不打开可视化,不保存文件
with CameraPipeline(args) as pipeline:
result = pipeline.wait_for_result(timeout=10) # 等待第一帧,可选
# 在下游自己的循环/线程中读取当前完整快照:
with CameraPipeline(parse_args([])) as pipeline:
result = pipeline.wait_for_result(timeout=10)
# 下游自己的循环/线程中读取;首帧前为 None:
result = pipeline.latest_result # 等价于 pipeline.latest()
if result is not None:
risk = result["risk"]
door_risks = result["door_risks"]
skeletons = result["skeletons"]
can = result["can"]
print(result["frame_index"], risk["risk_type"], risk["risk_score"])
print(result['frame_index'], result['risk'])
door_risks = result['door_risks']
skeletons = result['skeletons']
```
完整持续读取示例:`examples/read_latest.py`,在 Orin 执行。示例会自己启动推理并占用摄像头,运行前先停止已有后台进程:
结果字段:`frame_index`、`track_ids`、`boxes`、`skeletons`(每人 133 点)、`door_assignments`、`active_door`、`door_risks`、`risk`、`ungated_risk`、`can`、`capture`、`capture_to_result_ms`、`calibration_verified`。
```bash
./scripts/stop.sh
python3 examples/read_latest.py
# 或开启可视化:
python3 examples/read_latest.py --visualize
```
- 读取不消费结果;多个读者可以持有同一个不可变快照,旧快照不会被下一帧改写。
- 只保留最新结果,慢读者可以跳过帧;`wait_for_result(last_frame_index, timeout)` 可等待更新。
- `request_stop()` 请求停止,`join()` 等待退出并传播异常;`with` 自动回收资源。
- 停止后仍保留最后快照;下游应检查运行状态和帧时间,避免使用过期结果。
- `result_to_dict(result)` 生成可修改、可序列化的副本。变量属于当前进程,不是跨进程共享内存。
- `latest_result` 是单个最新结果变量,首帧前为 `None`。每帧完成风险计算后发布,早于任何可选绘制或文件写入。
- 快照深度只读:字典使用只读映射,数组使用 tuple;字段和索引含义与原 JSON 一致。旧快照不会随下一帧变化,多个读取线程互不消费或修改结果。
- 只保留最新一帧,没有结果队列;下游处理慢可跳过中间帧。需要每帧历史时显式开启 `--save-results`。
- `pipeline.wait_for_result(after_frame_index, timeout)` 是可选等待接口,返回更新的最新结果;超时或结束且无新帧时返回 `None`。
- `pipeline.request_stop()` 请求停止,`pipeline.join()` 等待退出并向调用方传播推理异常;停止后可读取最后快照及 `pipeline.summary`。使用 `with` 自动停止并回收资源。
- 停止后的快照只是最后结果;持续控制时应同时检查 `pipeline.running()` 及帧序号/时间戳,避免使用旧结果。
- 需要修改副本或 JSON 序列化时使用 `from run_camera import result_to_dict`,再调用 `result_to_dict(result)`。
- 变量属于创建 `CameraPipeline` 的 Python 进程。另一个进程或 C++ 程序不能直接 import 读取后台服务的变量,需要另行使用 IPC/HTTP 接口;当前没有新增 C++ 风险结果 ABI。
## CAN、风险与输出选项
## 可选输出开关
`0x391` 的 data[0..3] 依次为左前、右前、左后、右后。默认 `--can-side left`,右侧使用 `--can-side right`。值为 0 表示对应门活跃;报文缺失或超过默认 1000 ms 时,输出 `CAN_UNKNOWN`、分数 null。当前接口不发送防夹执行器指令。
| 参数 | 行为 |
优先用融合后的有效手部区域判断风险,无有效手部时使用人体框兜底;最终读取 `risk` / `door_risks`。`ungated_risk` 仅供诊断,`unknown` 不能当作 clear。
标定文件来自研发机位,默认 `calibration_verified=false`,换机位须重新标定。
| 参数 | 作用 |
|---|---|
| 无输出参数 | 只发布内存结果,不绘制、不保存结果文件 |
| `--visualize` | 开启独立可视化线程和 HTTP 预览 |
| `--save-results` | 保存逐帧 JSONL 及约每 10 秒的 latest.json |
| `--save-snapshots` | 约每 10 秒绘制并保存 latest.jpg,无 HTTP 服务 |
| `--save-pose-records` | 保存诊断姿态 JSONL |
| `--write-video` | 绘制并保存叠加 MP4,无需打开预览 |
| `--visualize` | 开启独立绘制线程及 HTTP 预览;默认不绘制、不编码、不监听端口 |
| `--save-results` | 保存逐帧 JSONL 及 latest.json,默认不保存 |
| `--save-snapshots` | 每约 10 秒保存叠加图像 |
| `--write-video` | 保存叠加视频,可配合 `--duration` 限时运行 |
| `--output PATH` | 指定保存目录,本身不启用保存 |
| `--no-can --active-door both` | 显式模拟门状态 |
输出开关可组合。`--output` 仅指定保存目录,不会自动开启保存。未开启保存时,旧输出文件保留但不会更新。任一保存开关开启后,停止时额外保存 summary.json;默认内存模式的统计保存在 `pipeline.summary` 并打印到终端。
运行 `./run_camera.sh --help` 或 `orin/run.sh --help` 查看全部选项。JSONL 每 32 MiB 轮换,保留两个历史文件;视频不轮换。开启可视化后可读取 `/result.json` 和 `/frame.jpg`。
恢复此前的预览与文件输出行为:
## 目录
```bash
./run_camera.sh --visualize --save-results --save-snapshots --output outputs/demo
```
## CAN 约定
按用户确认的 `0x391` 数据布局:
| 字节 | 车门 |
|---|---|
| data[0] | 左前 |
| data[1] | 右前 |
| data[2] | 左后 |
| data[3] | 右后 |
- `--can-side left` 使用 data[0]/data[2];`right` 使用 data[1]/data[3]。默认 left,请按摄像头安装侧选择。
- 值为 0 时对应门活跃,每帧读取最新 CAN 快照。有效报文至少需要 4 字节。
- 未收到有效报文或超过 `--can-timeout-ms`(默认 1000 ms),整体输出 `CAN_UNKNOWN`,门级输出 `FRONT_UNKNOWN/REAR_UNKNOWN`,分数为 null。
- 此时 `active_door=none` 仅为内部无有效活跃门输入,业务必须读取 `risk` 和 `can.valid`,不能将其解释为已确认两门关闭。
- 显式 `--active-door front/rear/both/none` 是模拟模式,输出 `can.source=simulation`,预览显示 CAN SIMULATION。`--no-can` 要求显式选择模拟状态。
- 本项目提供最终风险结果;基线未提供防夹执行器的 CAN 指令协议,因此没有自行添加执行器报文。
- 本次 USB-CAN `VCI_OpenDevice` 返回失败,`lsusb` 中未见 USB-CAN 设备;真实 0x391 报文链路尚未验证。连接适配器后重新启动程序;字节映射及门状态组合通过软件测试。
## 风险行为与机位标定
风险算法、默认 warning=0.30 / release=0.45、133 点骨骼、track_id、前后门归属、transition→unknown、无有效手部时人体框兜底均沿用原版。详见 `README-risk-upstream.md`。
`annotations/constant_door_mask.json` 和 `door_zone_multifeature.json` 原样复制,属于原研发机位。当前室内摄像头位置与其不一致,默认输出 `calibration_verified=false`,预览显示 CALIBRATION UNVERIFIED。实车验证需更换标定文件;`--calibration-verified` 仅标记经人工验证的标定,不会自动标定。
当前实时场景没有人,约 29 FPS 是该场景的实测值,不能当作多人满负载帧率。
## 优化与输出
- 重用 0920 的常驻 TensorRT engine、CUDA/NPP 预处理、显存及锁页缓存、GPU UYVY 转换。
- 独立取帧线程只保留最新帧,主动丢弃旧帧,避免积压;不像 0920 那样单独并行检测线程,完整 Python 融合阶段按结果顺序执行。
- 风险 mask / 距离变换按原版缓存;开启预览时默认每 3 帧提交一次待绘制图像;开启快照保存时每 10 秒一次。
- 长期运行清理过期轨迹,计时统计仅保留最近 3000 帧。
- 开启 `--save-results` 后,`causal_results.jsonl` 默认每 32 MiB 轮换,保留 `.1` 和 `.2`;`--jsonl-max-mb 0` 可禁用轮换。读取全部历史时按 `.2 → .1 → 当前文件` 顺序处理。
- `--save-pose-records` 额外保存同样轮换的诊断姿态记录;`--write-video` 录制叠加 MP4(视频不轮换,建议配合 `--duration`)。
- 文件输出遵循上面的独立开关;关闭可视化仍可完整读取内存结果。
- 新增 `capture`、`can`、`capture_to_result_ms`、`calibration_verified` 字段,原版核心 JSON 字段保留。
- 驱动原始时间戳存在时钟偏移,保存在 `capture.driver_timestamp_ms` 供诊断。延迟采用主机单调时钟在 DQBUF 后取样,表示“主机取帧到风险结果”,不包含曝光到驱动交付的时间。
## 文件关系与测试
- `main.cpp`, `v4l2_capture.*`, `can_comm.*`, `include/zcan.h`, `lib/`:基线工程。新工程修正错误 V4L2 buffer 处理;旧 main 同步修正 CAN 字节解析。
- `native/native_api.cpp`:采集线程、CAN 快照、TensorRT 的 ctypes C 接口。
- `accelerator/`:远端 0920 源码快照,当前构建只选取 TensorRT/预处理/检测/姿态源文件。
- `pipeline/`:原版 Python 算法,推理适配在 `native_backend.py`;ONNX/Ultralytics 的可选导入移到旧离线入口,原版风险公式不变。
- `Makefile.baseline` 和 `make baseline`:旧基线演示程序的编译方式;默认 `make` 编译新的原生共享库。
- `pipeline/latest_result.py`:线程安全的单结果发布槽位及不可变快照。
- `examples/read_latest.py`:同进程下游读取示例。
- `tests/test_latest_result.py`:结果所有权、线程生命周期、可选输出、独立可视化及异常传播测试。
- `tests/test_risk_integration.py`:CAN 左右门组合、失效状态、手部/人体兜底、transition、轨迹回收、日志轮换测试。
- `TEST_REPORT.md`:实测结果和限制。
```bash
python3 -m unittest discover -s tests -v
```
本机重新部署源码(会使用 SSH 交互认证,不保存密码):
```bash
./scripts/deploy.sh orin@192.168.195.100
```
重新编译共享库前应先停止 0923 运行进程,再启动新进程加载新库。
- `native/`:实际参与编译的摄像头/CAN、CUDA/NPP、TensorRT 源码及头文件。
- `pipeline/`:运行时姿态融合、跟踪、遮挡处理、风险、可视化和结果变量。
- `annotations/`:源码运行时的标定文件。
- `scripts/`:构建、打包、摄像头初始化。
- `orin/`:可单独复制并直接执行的运行包。