# Vision Anti-Pinch C++ / TensorRT 实时摄像头风险推理,算法来自最新 run_risk.py。 持续取图、推理并更新内存中的 FrameResult;实时推理、录制和回灌默认开启可视化。 相机每次取帧默认等待 **2000 ms**,连续超时 **6 次**才停止流水线(纯超时约 **12 秒**);成功取得有效帧后清零超时计数。单次等待时间由 `config/camera.json` 的 `camera.timeout_ms` 配置。运行中的坏帧、取流错误共用该重试期限;设备失效时尝试重新打开相机,恢复有效帧后继续推理。GPU 错误仍按原有异常处理退出。 原始画面在 YOLO 前经过低照度门控。启动前 5 帧不输出有效风险;明显极暗连续 15 帧后暂停人体、姿态和风险推理,亮度满足恢复阈值连续 15 帧后自动恢复。低照度结果标记 `risk_valid=false`、`risk_level=unavailable`,不会转换成 `clear`。阈值和 ROI 可在 `brightness_gate` 配置段调整,`enabled=false` 可关闭门控。阈值依据研发原始视频验证说明设置,适用边界仍包括黄昏、局部强光和镜头污染等未覆盖场景。 ## 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;首帧尚未完成时为空。 公共定义位于 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)。