Files
vision-anti-pinch/README.md
T
2026-09-30 17:04:29 +08:00

221 lines
15 KiB
Markdown
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.
# Vision Anti-Pinch
C++ / TensorRT 实时摄像头风险推理,算法来自最新 run_risk.py。
持续取图、推理并更新内存中的 FrameResult;实时推理、录制和回灌默认开启可视化。
相机每次取帧默认等待 **2000 ms**,连续超时 **6 次**才停止流水线(纯超时约 **12 秒**);成功取得有效帧后清零超时计数。单次等待时间由 `config/camera.json` 的 `camera.timeout_ms` 配置。运行中的坏帧、取流错误共用该重试期限;设备失效时尝试重新打开相机,恢复有效帧后继续推理。GPU 错误仍按原有异常处理退出。
## Orin 直接运行
适用环境:Jetson AGX Orin、JetPack 5.1.2 / L4T R35.4.1、CUDA 11.4、TensorRT 8.5.2、OpenCV 4.5.4。
仓库已包含该板端编译的程序和重新构建的 TensorRT 引擎。
```bash
git clone https://git.kendeji.fun/zmv/vision-anti-pinch.git
cd vision-anti-pinch/orin
./scripts/initialize_camera.sh # 开机后初始化 /dev/video5 摄像头链路
./run.sh # 持续推理并显示预览,结果只保存在内存
```
默认显示本地预览,叠加门区、检测框、手臂/手部关键点及风险信息。需要关闭预览时:
```bash
./run.sh --no-visualize
```
`--visualize` 仍可显式开启;没有 DISPLAY / WAYLAND_DISPLAY 时自动跳过窗口并继续推理。
Ctrl+C / SIGTERM 停止程序。
关闭预览窗口或按 q/Esc 只关闭预览,推理继续。
摄像头为 /dev/video5,1920×1536 UYVY,30 FPS。
当前 door mask 是指定的 constant_door_mask.json 副本,标定状态为 false;装车后应更换与实际画面匹配的门区并更新标定状态。
## Door mask 标注工具
先停止正在占用摄像头的推理程序,再运行:
```bash
./annotate_door_mask.sh --camera --output config/door_masks/driver-side.json
```
工具按 `config/camera.json` 抓取一张参考画面并打开标注窗口。左键依次添加门区多边形顶点,右键或 `U` 撤销一个点,`R` 清空,`S` 或 Enter 保存,Esc 或 `Q` 取消。保存时生成 x-anylabeling JSON,并在同一目录保存 `<名称>_reference.jpg`;已有文件默认不会覆盖,需要覆盖时显式添加 `--force`。
也可以使用已有图片,不打开摄像头:
```bash
./annotate_door_mask.sh --image /path/to/frame.jpg --output config/door_masks/driver-side.json
```
输入已经采集的视频时,工具会先打开帧选择窗口。可拖动滑块,使用 `A/D` 逐帧移动,或使用 `J/L` 前后跳过 30 帧;按 `S` 或 Enter 选择当前帧并进入标注:
```bash
./annotate_door_mask.sh --video /path/to/video.mp4 --output config/door_masks/driver-side.json
```
已知帧号时可以跳过帧选择窗口,帧号从 0 开始:
```bash
./annotate_door_mask.sh --video /path/to/video.mp4 --frame 244 --output config/door_masks/driver-side.json
```
`--image` 和 `--video` 模式不会打开或检查摄像头。视频生成的 JSON 会额外记录 `sourceVideo` 和 `sourceFrameIndex`。
该工具仅生成 mask 文件,暂不改变 `camera.json` 或推理程序当前使用的默认 mask。
## 下游获取结果
main.cpp 注释中也包含以下示例。下游通过自己的调用位置或线程读取:
```cpp
auto result = pipeline.latest();
if (result) {
const auto& risk = result->risk;
const auto& people = result->persons;
// result->frame_index / track_ids / boxes
// result->image / door_mask_pixels / capture / timing
// people[i].selected / smoothed / temporal:各 133 个关键点
}
```
latest() 返回 shared_ptr<const FrameResult>;首帧尚未完成时为空。
公共定义位于 include/camera_pipeline.hpp 和 include/frame_result.hpp。
## 从源码构建
```bash
./scripts/build.sh
./scripts/initialize_camera.sh
./run_camera.sh
```
源码构建要求 CMake 3.24+、CUDA/NPP、TensorRT 和 OpenCV 开发依赖。
构建脚本会优先使用 Orin 上已有的 ~/miniconda3/envs/yolo/bin/cmake。
源码配置直接使用 orin/engines/ 中的板端引擎。该引擎仅适用于上述 Orin 环境;仓库不包含 ONNX 或模型转换脚本。
独立库目标为 camera_risk_core;可视化在 camera_risk_visualizer。
## 目录
- src/、include/:采集、推理、结果变量及可选绘制。
- config/:源码构建程序的配置和固定门区。
- scripts/:构建和摄像头初始化脚本。
- orin/:已编译程序、TensorRT 引擎、配置、当前 GMSL 驱动及启动脚本。
- third_party/:nlohmann JSON 及其许可证。
NPP 用于 UYVY 转换、YOLO 缩放填充、RTMW 仿射裁剪;CUDA 用于通道转换与归一化。
风险几何计算仍包含 CPU/OpenCV 操作;算法内部的临时 JSON 对象只在内存中使用。
当前摄像头驱动启动时偶发首帧 ERROR,应用会丢弃并计数;持续错误、采集超时或推理错误会明确报错并退出。
现有 GMSL 驱动停止采集时可产生 videobuf2 缓冲区告警,v4l2-ctl 同样复现;需在驱动侧完成量产修复和验证。
## 可选保存(默认全部关闭)
三个独立参数可以单独使用,也可以组合:
```bash
./run_camera.sh --save-original --save-annotated --save-results
# 独立包:./orin/run.sh --save-original --save-annotated --save-results
```
开启任一项时,程序自动创建可执行文件所属工程的 `output/` 及三个子目录:
- `original/`:`--save-original` 保存未叠加的原图,JPEG 质量 95。
- `annotated/`:`--save-annotated` 保存带框、关键点及风险文字的 JPEG,无需开启可视化窗口。
- `results/`:`--save-results` 每个完成推理的帧保存一个 JSON,包含帧号、检测框、跟踪 ID、关键点及置信度、风险和耗时;有 CAN 的分支还包含相应状态。
同一帧三类文件同名(扩展名不同),文件名包含运行标识和帧号,多次运行不覆盖。JSON 的 `images` 提供相对于 `output/` 的图片路径,未选择保存的图片为 null。源码入口写入工程 `output/`;独立包入口写入 `orin/output/`。默认不创建目录、不保存文件。
保存对象是完成推理的帧;惰性分支暂停时不新增文件。开启保存会增加编码和磁盘 I/O 开销。写入失败时报告错误并停止,不静默丢失文件。
相机超时回归测试:`./tests/run_camera_timeout.sh`;加 `--wall-clock` 验证默认 6 次超时约 12 秒。测试直接编译实际相机代码,使用模拟采集和转换器,无需摄像头、GPU 或 OpenCV;不覆盖视频回放路径。
板端真实摄像头 / TensorRT 恢复与停止测试记录见 [Orin 测试报告](tests/ORIN_TEST_REPORT.md)。
## 推理过程录制与视频回灌
当前 `inference-only` 分支支持 H.264/MP4 会话;默认关闭,不再为会话逐帧保存图片。
```bash
# 录制实际完成推理的帧,Ctrl+C / SIGTERM 正常停止并封装视频
./run_camera.sh --record-session output/sessions/drive-001
# 视频解码后重新运行 YOLO、RTMW、融合与风险推理;文件结束自动退出
./run_camera.sh --replay-session output/sessions/drive-001 --save-results
# 同时保存回灌后的新视频及推理过程,便于对比
./run_camera.sh --replay-session output/sessions/drive-001 \
--record-session output/sessions/drive-001-replay
```
独立包使用同样参数,例如 `./orin/run.sh --record-session output/sessions/drive-001`。
会话路径相对于当前工作目录;录制目录必须尚不存在,避免覆盖原始记录。
会话内容:
- `frames.mp4`:H.264 有损压缩视频,按完成推理的顺序编码,尺寸与原图一致。
- `frames/*.json`:对应视频帧序号、原推理帧号、完整采集元数据、检测框、原始/隔离关键点、融合记录、最终结果与耗时。
- `frames.txt`:视频帧与 JSON 的顺序索引。
- `session.json`、`door_mask.json`:录制时的推理参数、分辨率、名义帧率、门区副本、模型路径与大小。
- `complete.json`:正常停止后写入的帧数与视频大小,用于检查会话是否完成。
回灌不打开摄像头。它恢复会话中的分辨率、检测阈值、人数上限、风险阈值和门区,使用 `--config` 指定的当前 TensorRT 引擎,按顺序尽快推理,不按墙钟时间限速。视频使用配置的固定名义帧率;真实采集间隔和断流间隔以 JSON 中的纳秒时间戳为准。原始采集时间在再次录制时保存为 `source_capture`,回灌耗时使用本次运行的单调时钟。
编码使用 OpenCV 的 FFmpeg H.264 编码器;需 OpenCV `videoio` 与可用的 H.264 编码支持,宽高必须为正偶数。不支持时明确报错。当前使用软件编解码,会增加 CPU、编码延迟和磁盘开销;保存对象是完成推理的帧,并非相机全部原始帧。H.264 有损压缩和不同引擎可能改变检测、关键点与风险结果,不承诺逐位一致。模型只记录来源和大小,不复制引擎,也不保证大小相同即模型相同;精确对比应使用同一组引擎。
Ctrl+C / SIGTERM 会封装已写入的视频,`CameraPipeline::join()` 返回前完成收尾。强制杀进程、断电或写入失败可能留下 `frames.pending.mp4`;缺少完成标记的会话拒绝回灌。编码器封装后检查帧数,回灌遇到缺帧、缺失 JSON、损坏索引或解码错误会报错,不静默跳帧。录制没有自动限时或磁盘轮转,请按可用空间控制录制时长。
编解码及会话回归:`./tests/run_session.sh`,无需摄像头或 TensorRT。真实摄像头与 TensorRT 验证脚本:`python3 tests/orin_session_test.py --binary build/camera_risk --config config/camera.json --output /tmp/new-session-test`;输出目录必须不存在,脚本不会停止已有部署进程。
本次实机结果与未覆盖范围见 [H.264 会话测试报告](tests/ORIN_SESSION_REPORT.md)。
## CAN/CANFD 收发与四门位置显示
实时运行默认打开参考工程 `/home/orin/vision-anti-pinch` 使用的 USBCANFD(设备类型 33、设备索引 0),启用 CH0 和 CH1:60 MHz 时钟、仲裁域 500 kbps、数据域 2 Mbps。一个接收线程只接收 CH0,所有发送只使用 CH1;两个通道默认启用终端电阻。可在 `config/camera.json` 的 `can` 中设置 `enabled`、`device_index`、`timeout_ms`(默认 1000 ms)和 `termination`;`--no-can` 显式关闭实时 CAN。设备不可用时显示 UNAVAILABLE,摄像头推理继续;接收 API 失败显示 ERROR,需检查设备后重启程序。
首次使用 USB-CAN 时,若节点为 root:root 且普通用户不可写,可运行 `./scripts/enable_can_access.sh`(需要 sudo)。它只为 `3068:0009` 适配器安装 `plugdev` 组读写规则并刷新设备权限;运行用户须属于 `plugdev`,新加组后需重新登录。无需以 root 运行整个推理程序。
`0x391` 四个信号按参考工程注释中的四个连续字节解析:
| 字节(从 0 开始) | 预览标签 | 信号 |
|---|---|---|
| 0 | LF | 左前门实际位置反馈 |
| 1 | RF | 右前门实际位置反馈 |
| 2 | LR | 左后门实际位置反馈 |
| 3 | RR | 右后门实际位置反馈 |
显示值是无符号原始字节 0–255,尚无物理单位、缩放或无效值定义,因此不标为百分比、不把 255 擅自视为无效。CH0 接收 CAN/CANFD 数据帧,过滤远程帧、错误帧和发送回显;少于 4 字节的 `0x391` 不更新信号。修正了参考代码 `get_bit_value(0/1/2/3, 8)` 导致的位域重叠,字节起始位应为 0/8/16/24。
默认预览显示 CH0 四门实际位置反馈、LIVE/STALE/WAIT、新鲜度及帧数,并显示总 RX/TX 和错误计数。尚未接收到值时显示 `--`,超时保留最后值但标记 STALE;CH1 专用于发送,不接收也不在实时预览中显示。即使相机正在重试或暂无新推理帧,CAN 预览仍约每 100 ms 刷新。CAN 只提供状态显示和下游数据,本次没有新增基于门位置的推理启停或风险门控。
下游通过 `pipeline.canStatus()` 读取当前状态;`pipeline.latest()->can` 是该推理结果生成时的快照。按需发送使用线程安全的 `pipeline.sendCan()`:
```cpp
cms::CanFrame frame;
frame.channel = 1; // 发送仅使用 CH1
frame.id = 0x123; // 按实际协议设置
frame.data = {0x01, 0x02}; // 示例数据,应替换为实际报文
bool sent = pipeline.sendCan(frame);
// CANFD:frame.fd = true;可按协议设置 extended、bitrate_switch。
// CAN 数据长度 0–8;CANFD 长度 0–8、12、16、20、24、32、48、64。
// 标准 ID 最大 0x7FF,扩展 ID 最大 0x1FFFFFFF。
```
发送通道不是 CH1,或 ID/长度/标志非法时抛出 `std::invalid_argument`;设备未就绪或 SDK 发送失败返回 false,发送计数以 SDK 返回为准。`sendCan()` 接口不自动填充/截断数据。发送调用与设备关闭互斥;CH0 接收保持独立线程运行。
实时推理首次取得风险状态时发送一次,此后仅在二值风险发生变化时,由后台 CAN 发送线程通过 CH1 发送一帧标准 CAN 数据报文 `0x1F6`,DLC 固定为 8。`data[0]` 的低四位(bit0–3)和高四位(bit4–7)同时填写相同的二值风险:`risk_level` 为 `clear`、`watch` 或 `unavailable` 时各为 0,`warning`、`danger` 以及未知状态时各为 1;后七字节全部为 0。即 `clear`、`watch` 和无人或证据不足导致的 `unavailable` 发送 `00 00 00 00 00 00 00 00`,其余发送 `11 00 00 00 00 00 00 00`(十六进制)。
发送按报文二值状态变化触发:`clear/watch/unavailable ↔ warning/danger/未知状态` 才产生新报文;相同编码的等级切换不重复发送。没有独立周期,风险不变时不重发;发送失败计数后也不因后续相同状态自动重试,等待新的二值状态变化。SDK 发送等待不占用推理线程;后台最多保留一个待发结果,发送变慢时用最新结果替换尚未发送的结果,避免积压旧风险;等待期间若从 A 变为 B 又回到正在发送的 A,会取消冗余待发报文,因此不保证每个短暂中间状态都上总线。CH1 发送失败通过现有 TX 错误计数呈现;结果中的 CAN 快照记录采样时已完成的发送计数,不保证包含当前帧的发送。停止时丢弃待发结果,并等待已经进入 SDK 的调用返回后释放设备。`--no-can` 或 `can.enabled=false` 禁用自动发送,视频回灌始终不发送。按需 `sendCan()` 仍为同步 API。
结果 JSON 和录制会话自动包含每帧 `can` 快照。视频回灌不打开 USB-CAN,不发送报文,直接恢复录制时的状态并标记 `source=replay`;旧会话没有 CAN 字段时显示 NOT_RECORDED。回灌调用 `sendCan()` 会明确拒绝。
依赖 `third_party/zlg/zcan.h` 和 `orin/lib/` 中的参考工程 SDK 库(含 SONAME 链接),独立包已包含它们。纯模拟收发回归:`./tests/run_can.sh`;会话回归同时覆盖 CAN 快照保存。模拟测试不代表物理总线收发验证。
本次检查范围与延期项目见 [CAN 实现记录](tests/ORIN_CAN_REPORT.md);CH0 实机排查与单通道修复见 [0x391 实际位置反馈检查](tests/ORIN_391_REPORT.md)。
新增 `0x1F6` 二值风险发送的检查范围及部署状态见 [风险发送检查记录](tests/RISK_CAN_REPORT.md)。
风险发送现已改为首次发送及二值变化触发,验证结果见 [风险变化触发发送检查](tests/ORIN_RISK_CHANGE_REPORT.md)。
CAN 通道现为 CH0 接收、CH1 发送,板端检查与未覆盖项目见 [CH1 发送切换检查](tests/ORIN_CH1_TX_REPORT.md)。