Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a35e4a1754 | |||
| 713bfd3672 | |||
| d24de544cb | |||
| e1e7fbe2fb | |||
| 0f609612a6 | |||
| 9aadf9cbda |
+4
-5
@@ -1,5 +1,4 @@
|
||||
*.sh text eol=lf
|
||||
*.engine -text
|
||||
*.ko -text
|
||||
/orin/** -text
|
||||
/config/door_mask.json -text
|
||||
*.engine binary
|
||||
*.onnx binary
|
||||
*.so binary
|
||||
*.ko binary
|
||||
|
||||
+20
-4
@@ -1,7 +1,23 @@
|
||||
/build/
|
||||
/engines/
|
||||
/logs/
|
||||
*.tmp
|
||||
# Generated build/cache/runtime files
|
||||
/build*/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.o
|
||||
*.a
|
||||
.venv/
|
||||
venv/
|
||||
outputs/
|
||||
logs/
|
||||
*.log
|
||||
*.pid
|
||||
*.mp4
|
||||
*.avi
|
||||
/models/
|
||||
*.onnx
|
||||
# Local credentials
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Optional per-frame output, including standalone Orin runs
|
||||
output/
|
||||
|
||||
+6
-8
@@ -1,19 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES 87)
|
||||
endif()
|
||||
project(camera_risk LANGUAGES CXX CUDA)
|
||||
project(vision_anti_pinch LANGUAGES CXX CUDA)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CUDA_STANDARD 17)
|
||||
set(CMAKE_CUDA_ARCHITECTURES 87)
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs highgui)
|
||||
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs highgui videoio)
|
||||
find_package(Threads REQUIRED)
|
||||
find_path(TENSORRT_INCLUDE_DIR NvInfer.h PATH_SUFFIXES aarch64-linux-gnu x86_64-linux-gnu REQUIRED)
|
||||
find_path(TENSORRT_INCLUDE_DIR NvInfer.h PATH_SUFFIXES aarch64-linux-gnu REQUIRED)
|
||||
find_library(TENSORRT_LIBRARY nvinfer REQUIRED)
|
||||
add_library(camera_risk_core
|
||||
src/trt_engine.cpp src/pose_engine.cu src/gpu_preprocess.cu src/uyvy_converter.cu
|
||||
src/detector.cpp src/door_risk.cpp src/camera.cpp src/camera_pipeline.cpp src/serialization.cpp src/output_writer.cpp src/result_renderer.cpp src/config.cpp src/lazy_gate.cpp src/can_input.cpp src/can_comm.cpp)
|
||||
src/detector.cpp src/v4l2_capture.cpp src/camera.cpp src/can_comm.cpp src/can_state.cpp
|
||||
src/door_risk.cpp src/door_zone.cpp src/camera_pipeline.cpp src/output_writer.cpp src/result_renderer.cpp src/config.cpp src/serialization.cpp)
|
||||
target_include_directories(camera_risk_core PUBLIC include third_party ${TENSORRT_INCLUDE_DIR} ${OpenCV_INCLUDE_DIRS})
|
||||
target_link_libraries(camera_risk_core PUBLIC ${TENSORRT_LIBRARY} ${OpenCV_LIBS} Threads::Threads CUDA::cudart CUDA::nppc CUDA::nppig CUDA::nppidei CUDA::nppicc
|
||||
"${CMAKE_SOURCE_DIR}/orin/lib/libusbcanfd.so" "${CMAKE_SOURCE_DIR}/orin/lib/libusb-1.0.so")
|
||||
@@ -22,5 +21,4 @@ add_library(camera_risk_visualizer src/visualizer.cpp)
|
||||
target_link_libraries(camera_risk_visualizer PUBLIC camera_risk_core)
|
||||
add_executable(camera_risk src/main.cpp)
|
||||
target_link_libraries(camera_risk PRIVATE camera_risk_visualizer)
|
||||
|
||||
set_target_properties(camera_risk PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN/../orin/lib;/usr/local/cuda/lib64")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.PHONY: all run package clean
|
||||
all:
|
||||
./scripts/build.sh
|
||||
run: all
|
||||
./run_camera.sh
|
||||
package: all
|
||||
./scripts/package_orin.sh
|
||||
clean:
|
||||
cmake --build build --target clean
|
||||
@@ -1,81 +1,99 @@
|
||||
# Vision Anti-Pinch — lazy-inference
|
||||
# Vision Anti-Pinch — use-can
|
||||
|
||||
从 `inference-only` 分支派生的纯 C++ 惰性推理工程。保留该分支的 YOLO / RTMW / 时序融合 / mask 风险流程,在执行推理前加入 CAN 门状态控制。模型启动时加载、预热一次,暂停期间保持 TensorRT 引擎、上下文及显存,不反复加载。
|
||||
相机默认每 **2000 ms** 无有效帧记一次失败,连续 **6 次**才停止流水线(纯超时约 **12 秒**);成功取得有效帧后重新计数。每个等待周期内仍以 100 ms 间隔检查停止请求,CAN 接收不受重试影响。取流错误或坏帧也计入重试期限;设备失效时尝试重开相机,恢复后继续推理。单次等待时间由 `config/camera.json` 的 `camera.timeout_ms` 配置。
|
||||
|
||||
## 触发规则
|
||||
## 编译与运行
|
||||
|
||||
接收 CAN / CAN FD `0x391`,`data[0..3]` 按百分比解释,顺序为左前、右前、左后、右后。当前仅使用左前和左后,任一满足即可检测。
|
||||
|
||||
| 状态 | 推理行为 |
|
||||
|---|---|
|
||||
| 左前或左后开度 **<15%**,且正在增加(开门) | 检测并计算风险 |
|
||||
| 左前或左后开度 **<15%**,且正在减小(关门) | 检测并计算风险 |
|
||||
| 恰好 15%、大于 15%,或两门均静止 | 暂停模型推理 |
|
||||
| 从未收到有效报文、报文超时或左门开度值无效 | 开启检测 |
|
||||
| CAN 恢复且不满足运动条件 | 停止检测 |
|
||||
|
||||
开度不变的重复报文不会立即抹掉运动方向。默认最后一次开度变化后 **300 ms** 仍沿用方向,超过该时间判定静止,避免整数百分比报文导致频繁启停。到达 0% 后也保留这段检测时间。首次报文及超时后的首次报文只建立基线,不将断线前后的差值当作运动。
|
||||
|
||||
CAN 默认 **1000 ms** 超时。上述参数可在 `config/camera.json` 的 `lazy_inference` 中调整;15% 使用严格小于。USB CAN 缺失时后台持续重试,恢复后自动接收。当前不发送执行器指令。
|
||||
|
||||
相机每次取帧默认等待 **2000 ms**,连续超时 **6 次**才停止流水线(纯超时约 **12 秒**);成功取得有效帧后清零超时计数。重试期间 CAN 继续接收。单次等待时间由 `camera.timeout_ms` 配置;运行中的坏帧、取流错误共用该重试期限;设备失效时尝试重新打开相机,恢复有效帧后继续推理。GPU 错误仍按原有异常处理退出。
|
||||
|
||||
## 编译和运行
|
||||
|
||||
目标设备:Orin / JetPack 5.1.2 / CUDA 11.4 / TensorRT 8.5 / OpenCV 4.5,构建需 CMake 3.24+。
|
||||
目标环境:Jetson AGX Orin、JetPack 5.1.2 / L4T R35.4.1、CUDA 11.4、TensorRT 8.5.x、OpenCV 4.5.4。
|
||||
源码编译另需 CMake 3.24+ 和相应开发库。
|
||||
|
||||
```bash
|
||||
./scripts/build.sh
|
||||
./scripts/initialize_camera.sh # 摄像头尚未初始化时
|
||||
./run_camera.sh
|
||||
./run_camera.sh --visualize # 可选本地 OpenCV 窗口
|
||||
./run_camera.sh --duration 30 # 运行 30 秒后正常退出
|
||||
./scripts/build.sh # 或 make
|
||||
./scripts/initialize_camera.sh # 开机后摄像头尚未初始化时执行
|
||||
./run_camera.sh # 执行 build/camera_risk
|
||||
./run_camera.sh --visualize # 可选 OpenCV 本地窗口
|
||||
```
|
||||
|
||||
Orin 独立包可单独复制运行:
|
||||
预编译 Orin 包:
|
||||
|
||||
```bash
|
||||
cd orin
|
||||
./scripts/initialize_camera.sh
|
||||
./run.sh
|
||||
./run.sh # 脚本直接 exec C++ ELF 程序
|
||||
# 也可以直接执行:
|
||||
./build/camera_risk --config config/camera.json
|
||||
```
|
||||
|
||||
`orin/build/camera_risk` 是 ARM64 C++ 可执行程序。运行不依赖 Python。修改源码后,在 Orin 编译并运行 `./scripts/package_orin.sh` 更新独立包。`cd orin && sha256sum -c SHA256SUMS` 检查文件完整性。
|
||||
默认无窗口、不保存文件;Ctrl+C / SIGTERM 停止。`--visualize` 使用 Orin 的图形桌面,关闭窗口仅停止显示、推理继续。没有桌面环境时保持无窗口推理。
|
||||
|
||||
## 流水线和下游结果
|
||||
## 流水线与结果变量
|
||||
|
||||
摄像头持续采集、CAN 持续接收。启用时运行采集、YOLO、RTMW/融合/风险三阶段流水线;暂停时继续取帧以保持画面新鲜,但不执行 YOLO、RTMW 或风险计算。暂停前已提交的 GPU 工作可以完成,其结果会被丢弃。状态切换会清空待处理帧,恢复时重置跟踪与时序融合,不使用暂停前的历史人物状态。
|
||||
```
|
||||
V4L2 MMAP 采集 / NPP UYVY 转换(线程 1)
|
||||
→ YOLO TensorRT 检测(线程 2)
|
||||
→ RTMW TensorRT / 隔离推理 / C++ 时序融合(线程 3)
|
||||
→ 前后门多特征归属 / 因果中值 / mask 风险 / CAN 过滤
|
||||
→ shared_ptr<const FrameResult> 最新结果变量
|
||||
```
|
||||
|
||||
摄像头使用有界队列并丢弃积压旧帧;可视化独立读取最新结果。
|
||||
下游直接链接 `camera_risk_core`,在自己的线程中读取:
|
||||
|
||||
```cpp
|
||||
#include "camera_pipeline.hpp"
|
||||
#include "config.hpp"
|
||||
|
||||
cms::CameraPipeline pipeline(cms::loadConfig("config/camera.json"));
|
||||
pipeline.start();
|
||||
// 下游线程中:
|
||||
auto state = pipeline.inferenceStatus();
|
||||
// state.models_ready / enabled / can_valid / reason
|
||||
// state.positions[0]:左前;state.positions[2]:左后
|
||||
// state.direction[0..1]:左前/左后,-1 关门,0 静止,+1 开门
|
||||
|
||||
auto result = pipeline.latest(); // shared_ptr<const FrameResult>
|
||||
// 下游自己的循环中读取;首帧前 result 为空。
|
||||
auto result = pipeline.latest();
|
||||
if (result) {
|
||||
const auto& risk = result->risk;
|
||||
const auto& people = result->persons;
|
||||
const auto& front = result->door_risks.at("front");
|
||||
const auto& rear = result->door_risks.at("rear");
|
||||
const auto& people = result->persons; // 每人 selected/smoothed/temporal 各 133 点
|
||||
const auto& assignments = result->door_assignments;
|
||||
const auto& can = result->can;
|
||||
// risk.risk_type / risk.risk_level / risk.risk_score(std::optional<double>)
|
||||
}
|
||||
// 启动、暂停或等待恢复后的首帧时,result 为空;不能解释为风险 clear。
|
||||
// 退出:pipeline.requestStop(); pipeline.join();
|
||||
// 退出时:
|
||||
pipeline.requestStop();
|
||||
pipeline.join();
|
||||
```
|
||||
|
||||
`latest()` 不返回旧运行周期的风险;调用者已持有的旧快照不会被修改,应重新读取状态和结果。`FrameResult::inference` 保存生成结果时的 CAN 状态和 generation。状态变化后的下一次读取会反映新的周期。`running()` 表示整个服务在运行,暂停推理时仍为 true。
|
||||
`latest()` 返回拥有数据的只读快照,读者互不消费结果,旧引用不会被新帧修改。慢读者可以跳过中间帧;异常通过 `join()` 传播。停止后的快照只代表最后结果,持续控制应检查运行状态和时间戳。
|
||||
公共结构定义见 `include/frame_result.hpp`,接口见 `include/camera_pipeline.hpp`。
|
||||
|
||||
默认结果仅保存在内存变量,不保存视频或逐帧文件。标准输出记录启停原因和累计推理次数;`stats()` 提供 detector_runs / pose_runs / skipped_idle 等计数。可视化暂停时显示 IDLE,不继续显示旧风险。
|
||||
## CAN 与风险
|
||||
|
||||
## 目录
|
||||
- 0x391 的 data[0..3]:左前、右前、左后、右后。
|
||||
- 默认左侧,`--can-side right` 切到右侧;值为 0 时对应门活跃。
|
||||
- 缺失或过期(默认 1000 ms)报文输出 `CAN_UNKNOWN`,分数为空,不能解释为 clear。
|
||||
- `--no-can --active-door both` 显式模拟门状态;也可选择 front/rear/none。
|
||||
- 优先使用有效手部凸包,无有效手部时用人体框兜底;transition 保留 unknown 语义。
|
||||
- 最终使用 `risk` 或 `door_risks`;`ungated_risk` 仅用于诊断。当前只接收门状态,不发送执行器指令。
|
||||
- mask 和前后门参数来自研发机位,默认 calibration_verified=false;装车后需要重新标定。
|
||||
|
||||
- `src/`、`include/`:C++ / CUDA 源码及公共接口。
|
||||
- `config/`:门 mask、摄像头及惰性推理配置。
|
||||
- `orin/`:模型、可执行程序、CAN 库、相机驱动及配置。
|
||||
- `scripts/`:编译、初始化、打包;`third_party/`:JSON 依赖。
|
||||
配置位于 `config/camera.json`,模型位于 `orin/models/engines/`。详细参数:
|
||||
|
||||
模型和推理算法沿用纯推理基线。当前门 mask 为研发机位配置,装车需按实际机位标定。
|
||||
```bash
|
||||
./run_camera.sh --help
|
||||
./run_camera.sh --duration 30
|
||||
./run_camera.sh --save-results /path/results.jsonl
|
||||
```
|
||||
|
||||
显式保存时每帧写一行 JSON,核心字段保留 frame_index、track_ids、boxes、skeletons、door_assignments、door_risks、risk、ungated_risk、can;文件由用户指定路径管理。`--video` 可使用同一 C++ 流程处理视频文件,尺寸需匹配配置。
|
||||
|
||||
## 目录与打包
|
||||
|
||||
- `src/`:C++ / CUDA 源文件及 main.cpp。
|
||||
- `include/`:头文件及下游数据结构。
|
||||
- `third_party/nlohmann/`:C++ JSON 依赖及许可证。
|
||||
- `config/`、`annotations/`:运行配置与标定。
|
||||
- `scripts/`:编译、摄像头初始化、打包。
|
||||
- `orin/`:可单独复制的 ELF 程序、模型、驱动、CAN 库和配置。
|
||||
|
||||
修改源码后,在 Orin 重新编译并执行 `./scripts/package_orin.sh`,或执行 `make package`。运行、打包均不使用 Python。打包后可在 orin 目录运行 `sha256sum -c SHA256SUMS`。
|
||||
|
||||
## 可选保存(默认全部关闭)
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": "v9.10-research-calibration-1",
|
||||
"description": "Temporary-camera calibration from seg_01 and seg_08; recalibrate for the production camera.",
|
||||
"front_probability_min": 0.72,
|
||||
"rear_probability_max": 0.28,
|
||||
"minimum_person_height": 0.4,
|
||||
"minimum_pose_features": 4,
|
||||
"history_frames": 5,
|
||||
"stale_track_frames": 120,
|
||||
"features": {
|
||||
"shoulder_center_x": {
|
||||
"rear": 0.53,
|
||||
"front": 0.64,
|
||||
"weight": 1.2
|
||||
},
|
||||
"shoulder_center_y": {
|
||||
"rear": 0.54,
|
||||
"front": 0.46,
|
||||
"weight": 1.3
|
||||
},
|
||||
"shoulder_width": {
|
||||
"rear": 0.14,
|
||||
"front": 0.2,
|
||||
"weight": 1.4
|
||||
},
|
||||
"torso_height": {
|
||||
"rear": 0.35,
|
||||
"front": 0.47,
|
||||
"weight": 1.5
|
||||
},
|
||||
"hip_center_y": {
|
||||
"rear": 0.89,
|
||||
"front": 0.93,
|
||||
"weight": 0.6
|
||||
},
|
||||
"box_center_x": {
|
||||
"rear": 0.48,
|
||||
"front": 0.58,
|
||||
"weight": 0.35
|
||||
},
|
||||
"box_height": {
|
||||
"rear": 0.7,
|
||||
"front": 0.82,
|
||||
"weight": 0.45
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-25
@@ -1,27 +1,6 @@
|
||||
{
|
||||
"camera": {
|
||||
"device": "/dev/video5",
|
||||
"width": 1920,
|
||||
"height": 1536,
|
||||
"fps": 30,
|
||||
"buffers": 4,
|
||||
"timeout_ms": 2000
|
||||
},
|
||||
"inference": {
|
||||
"engine_directory": "../orin/engines",
|
||||
"detector_confidence": 0.3,
|
||||
"max_people": 3,
|
||||
"queue_capacity": 2
|
||||
},
|
||||
"door": {
|
||||
"mask": "door_mask.json",
|
||||
"warning_relative": 0.3,
|
||||
"release_relative": 0.45,
|
||||
"calibration_verified": false
|
||||
},
|
||||
"lazy_inference": {
|
||||
"threshold_percent": 15,
|
||||
"can_timeout_ms": 1000,
|
||||
"motion_timeout_ms": 300
|
||||
}
|
||||
"camera": {"device":"/dev/video5","width":1920,"height":1536,"fps":30,"buffers":4,"timeout_ms":2000},
|
||||
"inference": {"engine_directory":"../orin/models/engines","detector_confidence":0.30,"max_people":3,"queue_capacity":2},
|
||||
"door": {"mask":"../annotations/constant_door_mask.json","zone_config":"../annotations/door_zone_multifeature.json","warning_relative":0.30,"release_relative":0.45,"calibration_verified":false},
|
||||
"can": {"enabled":true,"side":"left","timeout_ms":1000}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,9 @@ public:
|
||||
bool pushLatest(T item) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if(closed_)return false;
|
||||
if(items_.size()==capacity_)items_.pop_front();
|
||||
if(items_.size()>=capacity_)items_.pop_front();
|
||||
items_.push_back(std::move(item));changed_.notify_all();return true;
|
||||
}
|
||||
void clear() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);items_.clear();changed_.notify_all();
|
||||
}
|
||||
std::optional<T> pop() {
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
changed_.wait(lock,[&]{return closed_||!items_.empty();});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
namespace cms {
|
||||
struct CameraConfig {
|
||||
std::string device{"/dev/video5"};
|
||||
std::string video;
|
||||
int width{1920},height{1536},fps{30},buffers{4},timeout_ms{2000};
|
||||
};
|
||||
struct CapturedFrame {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "camera.hpp"
|
||||
#include "frame_result.hpp"
|
||||
#include "output_writer.hpp"
|
||||
#include "lazy_gate.hpp"
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
@@ -10,8 +9,12 @@ namespace cms {
|
||||
struct PipelineConfig {
|
||||
CameraConfig camera;
|
||||
OutputOptions output;
|
||||
LazyGateConfig lazy_gate;
|
||||
std::filesystem::path engines,door_mask;
|
||||
std::filesystem::path engines,door_mask,door_zone_config;
|
||||
bool can_enabled{true};
|
||||
std::string can_side{"left"},active_door{"can"};
|
||||
double can_timeout_ms{1000};
|
||||
std::uint64_t max_frames{};
|
||||
std::filesystem::path results_path,pose_records_path;
|
||||
double detector_confidence{0.30};
|
||||
int max_people{3};
|
||||
double warning_relative{0.30},release_relative{0.45};
|
||||
@@ -21,10 +24,9 @@ struct PipelineConfig {
|
||||
struct PipelineStats {
|
||||
std::uint64_t captured{},published{},driver_sequence_gaps{},discarded_error_frames{};
|
||||
double mean_capture_to_result_ms{};
|
||||
std::uint64_t detector_runs{},pose_runs{},skipped_idle{};
|
||||
};
|
||||
// One lifecycle per instance. start() warms engines before starting capture.
|
||||
// Captures and receives CAN continuously. Engines stay warm while inference is idle.
|
||||
// Runs continuously until requestStop() or a worker failure.
|
||||
// latest() reads the immutable result variable without removing or acknowledging it.
|
||||
// No result queue, consumer registration or frame-count termination.
|
||||
// Stop is safe from any thread;
|
||||
@@ -38,7 +40,6 @@ public:
|
||||
void start();
|
||||
FrameResultPtr latest() const;
|
||||
bool running() const noexcept;
|
||||
InferenceStatus inferenceStatus() const;
|
||||
void requestStop() noexcept;
|
||||
void join();
|
||||
PipelineStats stats() const;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
#include "lazy_gate.hpp"
|
||||
#include <memory>
|
||||
namespace cms {
|
||||
// Receives continuously, including while inference is idle; retries missing USB CAN.
|
||||
class CanInput {
|
||||
public:
|
||||
explicit CanInput(LazyGateConfig config);
|
||||
~CanInput();
|
||||
CanInput(CanInput const&)=delete;
|
||||
CanInput& operator=(CanInput const&)=delete;
|
||||
InferenceStatus snapshot();
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <string>
|
||||
namespace cms {
|
||||
struct CanSnapshot {
|
||||
std::array<int,4> positions{{-1,-1,-1,-1}}; // LF, RF, LR, RR
|
||||
double age_ms{-1};
|
||||
bool valid{false};
|
||||
std::string source{"hardware"},side{"left"};
|
||||
};
|
||||
class CanInput {
|
||||
public:
|
||||
explicit CanInput(bool enabled);
|
||||
~CanInput();
|
||||
CanInput(CanInput const&)=delete;
|
||||
CanInput& operator=(CanInput const&)=delete;
|
||||
CanSnapshot snapshot(std::string const& side,double timeoutMs) const;
|
||||
private:
|
||||
bool opened_{};
|
||||
};
|
||||
std::string activeDoor(CanSnapshot const& state);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "door_risk.hpp"
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cms {
|
||||
class DoorZoneAssigner {
|
||||
public:
|
||||
explicit DoorZoneAssigner(std::filesystem::path const& configuration);
|
||||
Json update(int frame, Json const& record, cv::Size size);
|
||||
private:
|
||||
std::string classify(double probability) const;
|
||||
Json features_;
|
||||
double frontMin_{},rearMax_{},minimumHeight_{};
|
||||
int minimumPoseFeatures_{},historyFrames_{},staleFrames_{};
|
||||
std::unordered_map<int,std::deque<double>> history_;
|
||||
std::unordered_map<int,int> lastSeen_;
|
||||
};
|
||||
struct DoorDecisions { Json doors; Json selected; };
|
||||
DoorDecisions buildDoorRisks(Json const& raw, Json const& assignments,
|
||||
std::string const& activeDoor, bool canValid);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
// Public downstream contract. Owns its data; no GPU, JSON or network handles.
|
||||
#include "image.hpp"
|
||||
#include "lazy_gate.hpp"
|
||||
#include "can_state.hpp"
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -23,6 +24,7 @@ struct RegionRisk {
|
||||
std::string risk_level;
|
||||
std::string decision;
|
||||
std::string source;
|
||||
std::string door_zone;
|
||||
int track_id{};
|
||||
std::string side;
|
||||
std::optional<std::string> state;
|
||||
@@ -32,6 +34,8 @@ struct RegionRisk {
|
||||
std::optional<double> region_scale_px, distance_relative;
|
||||
};
|
||||
struct RiskResult {
|
||||
std::string risk_type;
|
||||
std::optional<std::string> door_id;
|
||||
std::string status;
|
||||
std::optional<double> risk_score;
|
||||
std::string risk_level;
|
||||
@@ -63,12 +67,22 @@ struct HandRegion {
|
||||
struct StageTiming {
|
||||
double detector_ms{}, pose_ms{}, fusion_risk_ms{}, capture_to_result_ms{};
|
||||
};
|
||||
struct DoorAssignment {
|
||||
int track_id{}, person_index{}, pose_feature_count{}, history_frames{};
|
||||
std::string door_zone, raw_door_zone, evidence_source;
|
||||
double front_probability{}, smoothed_front_probability{}, confidence{};
|
||||
std::map<std::string,double> features, feature_frontness;
|
||||
};
|
||||
struct FrameResult {
|
||||
InferenceStatus inference;
|
||||
int frame_index{};
|
||||
std::vector<int> track_ids;
|
||||
std::vector<std::array<double,4>> boxes;
|
||||
RiskResult risk;
|
||||
RiskResult ungated_risk;
|
||||
std::map<std::string,RiskResult> door_risks;
|
||||
std::vector<DoorAssignment> door_assignments;
|
||||
std::string active_door;
|
||||
CanSnapshot can;
|
||||
CaptureMetadata capture;
|
||||
StageTiming timing;
|
||||
std::vector<PersonResult> persons;
|
||||
|
||||
@@ -11,7 +11,6 @@ public:
|
||||
if(!result)throw std::invalid_argument("Cannot publish a null result");
|
||||
std::atomic_store_explicit(&latest_,std::move(result),std::memory_order_release);
|
||||
}
|
||||
void clear() { std::atomic_store_explicit(&latest_,FrameResultPtr{},std::memory_order_release); }
|
||||
FrameResultPtr load() const {
|
||||
return std::atomic_load_explicit(&latest_,std::memory_order_acquire);
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace cms {
|
||||
struct LazyGateConfig {
|
||||
int threshold_percent{15};
|
||||
int can_timeout_ms{1000};
|
||||
int motion_timeout_ms{300};
|
||||
};
|
||||
struct InferenceStatus {
|
||||
bool models_ready{};
|
||||
bool enabled{true};
|
||||
bool can_valid{};
|
||||
bool left_front_trigger{},left_rear_trigger{};
|
||||
std::array<int,4> positions{{-1,-1,-1,-1}}; // LF, RF, LR, RR; percent
|
||||
std::array<int,2> direction{}; // LF, LR: -1 closing, 0 stationary, +1 opening
|
||||
std::uint64_t generation{1},sample_monotonic_ns{};
|
||||
double can_age_ms{-1};
|
||||
std::string reason{"CAN_UNAVAILABLE"};
|
||||
};
|
||||
// Pure state machine. Caller serializes access. Time is monotonic nanoseconds.
|
||||
class LazyGate {
|
||||
public:
|
||||
explicit LazyGate(LazyGateConfig config={});
|
||||
void receive(std::uint32_t id,std::uint8_t const* data,std::size_t size,std::uint64_t now);
|
||||
InferenceStatus snapshot(std::uint64_t now);
|
||||
private:
|
||||
LazyGateConfig config_;
|
||||
bool have_sample_{};
|
||||
std::uint64_t last_sample_{};
|
||||
std::array<std::uint64_t,2> last_change_{};
|
||||
InferenceStatus state_;
|
||||
};
|
||||
}
|
||||
@@ -2,33 +2,30 @@
|
||||
#include "frame_result.hpp"
|
||||
#include "door_risk.hpp"
|
||||
namespace cms {
|
||||
template<class T> inline std::optional<T> optionalField(Json const& value,char const* key) {
|
||||
auto it=value.find(key);return it==value.end()||it->is_null()?std::nullopt:std::optional<T>(it->get<T>());
|
||||
template<class T> inline std::optional<T> optionalField(Json const& j,char const* key) {
|
||||
auto it=j.find(key);return it==j.end()||it->is_null()?std::nullopt:std::optional<T>(it->get<T>());
|
||||
}
|
||||
inline FrameResult makeFrameResult(Json const& record,Json const& risk,
|
||||
CaptureMetadata capture) {
|
||||
FrameResult out;
|
||||
out.frame_index=record.at("frame_index").get<int>();
|
||||
out.track_ids=record.at("track_ids").get<std::vector<int>>();
|
||||
out.boxes=record.at("boxes").get<std::vector<std::array<double,4>>>();
|
||||
out.capture=capture;
|
||||
auto& r=out.risk;
|
||||
r.status=risk.at("status");r.risk_score=optionalField<double>(risk,"risk_score");
|
||||
r.risk_level=risk.at("risk_level");r.decision=risk.at("decision");
|
||||
r.source=optionalField<std::string>(risk,"source");r.track_id=optionalField<int>(risk,"track_id");r.side=optionalField<std::string>(risk,"side");
|
||||
auto const& d=risk.at("door_mask");
|
||||
r.door_mask={d.at("status"),d.at("annotation_index"),optionalField<std::string>(d,"path"),d.at("format"),d.at("area_px")};
|
||||
r.warning_distance_relative=risk.at("warning_distance_relative");r.release_distance_relative=risk.at("release_distance_relative");
|
||||
for(auto const& j:risk.at("regions")) {
|
||||
RegionRisk v;
|
||||
v.status=j.at("status");v.risk_score=optionalField<double>(j,"risk_score");
|
||||
v.risk_level=j.at("risk_level");v.decision=j.at("decision");v.source=j.at("source");
|
||||
v.track_id=j.at("track_id");v.side=j.at("side");v.state=optionalField<std::string>(j,"state");
|
||||
v.region_area_px=optionalField<int>(j,"region_area_px");v.door_overlap_px=optionalField<int>(j,"door_overlap_px");
|
||||
v.region_overlap_ratio=optionalField<double>(j,"region_overlap_ratio");v.min_distance_px=optionalField<double>(j,"min_distance_px");
|
||||
v.region_scale_px=optionalField<double>(j,"region_scale_px");v.distance_relative=optionalField<double>(j,"distance_relative");
|
||||
r.regions.push_back(std::move(v));
|
||||
inline RiskResult parseRisk(Json const& j) {
|
||||
RiskResult r;r.status=j.at("status");r.risk_score=optionalField<double>(j,"risk_score");r.risk_level=j.at("risk_level");r.decision=j.at("decision");
|
||||
r.risk_type=j.value("risk_type",std::string());r.door_id=optionalField<std::string>(j,"door_id");
|
||||
r.source=optionalField<std::string>(j,"source");r.track_id=optionalField<int>(j,"track_id");r.side=optionalField<std::string>(j,"side");
|
||||
if(j.contains("door_mask")) {auto const& d=j.at("door_mask");r.door_mask={d.at("status"),d.at("annotation_index"),optionalField<std::string>(d,"path"),d.at("format"),d.at("area_px")};}
|
||||
r.warning_distance_relative=j.value("warning_distance_relative",.30);r.release_distance_relative=j.value("release_distance_relative",.45);
|
||||
for(auto const& x:j.at("regions")) {
|
||||
RegionRisk v;v.status=x.at("status");v.risk_score=optionalField<double>(x,"risk_score");v.risk_level=x.at("risk_level");v.decision=x.at("decision");v.source=x.at("source");
|
||||
v.track_id=x.at("track_id");v.side=x.at("side");v.state=optionalField<std::string>(x,"state");v.door_zone=x.value("door_zone",std::string());
|
||||
v.region_area_px=optionalField<int>(x,"region_area_px");v.door_overlap_px=optionalField<int>(x,"door_overlap_px");
|
||||
v.region_overlap_ratio=optionalField<double>(x,"region_overlap_ratio");v.min_distance_px=optionalField<double>(x,"min_distance_px");
|
||||
v.region_scale_px=optionalField<double>(x,"region_scale_px");v.distance_relative=optionalField<double>(x,"distance_relative");r.regions.push_back(std::move(v));
|
||||
}
|
||||
return out;
|
||||
return r;
|
||||
}
|
||||
inline DoorAssignment parseAssignment(Json const& j) {
|
||||
DoorAssignment a;a.track_id=j.at("track_id");a.person_index=j.at("person_index");a.door_zone=j.at("door_zone");a.raw_door_zone=j.at("raw_door_zone");
|
||||
a.front_probability=j.at("front_probability");a.smoothed_front_probability=j.at("smoothed_front_probability");a.confidence=j.at("confidence");a.evidence_source=j.at("evidence_source");
|
||||
a.pose_feature_count=j.at("pose_feature_count");a.history_frames=j.at("history_frames");a.features=j.at("features").get<std::map<std::string,double>>();a.feature_frontness=j.at("feature_frontness").get<std::map<std::string,double>>();return a;
|
||||
}
|
||||
inline FrameResult makeFrameResult(Json const& record,Json const& raw,CaptureMetadata capture) {
|
||||
FrameResult r;r.frame_index=record.at("frame_index");r.track_ids=record.at("track_ids").get<std::vector<int>>();r.boxes=record.at("boxes").get<std::vector<std::array<double,4>>>();r.capture=capture;r.ungated_risk=parseRisk(raw);return r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include "frame_result.hpp"
|
||||
#include "door_risk.hpp"
|
||||
namespace cms { Json serializeResult(FrameResult const& result); }
|
||||
namespace cms {
|
||||
Json serializeResult(FrameResult const& result);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef V4L2_CAPTURE_H
|
||||
#define V4L2_CAPTURE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// V4L2 流式(MMAP)取图封装
|
||||
// 参考 quick_bring_up_64g.sh 中 v4l2src 的行为:
|
||||
// - 打开 /dev/videoN
|
||||
// - 协商 UYVY 像素格式与分辨率
|
||||
// - 设置相机控制(sensor_mode / trig_pin 等, 等价于 v4l2-ctl -c ...)
|
||||
// - MMAP 方式申请内核缓冲区并流式采集
|
||||
class V4l2Capture {
|
||||
public:
|
||||
// 单帧采集结果
|
||||
struct Frame {
|
||||
void *data = nullptr; // 指向 MMAP 缓冲区, 释放前有效
|
||||
size_t size = 0; // 实际帧字节数(bytesused)
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pixelformat = 0; // V4L2_PIX_FMT_* (如 V4L2_PIX_FMT_UYVY)
|
||||
uint32_t bytes_per_line = 0;
|
||||
uint32_t sequence = 0; // 驱动序号
|
||||
uint64_t timestamp_us = 0; // 单调时钟时间戳(us)
|
||||
int index = -1; // 内部缓冲区索引
|
||||
};
|
||||
|
||||
V4l2Capture() = default;
|
||||
~V4l2Capture();
|
||||
|
||||
V4l2Capture(const V4l2Capture &) = delete;
|
||||
V4l2Capture &operator=(const V4l2Capture &) = delete;
|
||||
|
||||
// 打开设备节点, 例如 /dev/video5
|
||||
bool open(const std::string &device);
|
||||
|
||||
// 关闭设备并释放所有资源
|
||||
void close();
|
||||
|
||||
bool isOpen() const { return fd_ >= 0; }
|
||||
bool needsReconnect() const { return reconnect_needed_; }
|
||||
|
||||
// 协商像素格式与分辨率. pixelformat 使用 V4L2_PIX_FMT_* 四字符码
|
||||
bool setFormat(uint32_t width, uint32_t height, uint32_t pixelformat);
|
||||
|
||||
// 设置帧率(通过 VIDIOC_S_PARM). fps 分子/分母均为 fps/1
|
||||
bool setFps(uint32_t fps);
|
||||
|
||||
// 设置 V4L2 控制, 等价 v4l2-ctl -c <name>=<value>. id 为 V4L2_CID_* 之四字符码
|
||||
// 例如 sensor_mode=0x009a2008, trig_pin=0x009a2084
|
||||
bool setControl(uint32_t id, int32_t value);
|
||||
|
||||
// 申请 MMAP 缓冲区, 默认 4 个
|
||||
bool initMmap(uint32_t buffer_count = 4);
|
||||
|
||||
// 开始 / 停止采集
|
||||
bool startStreaming();
|
||||
bool stopStreaming();
|
||||
|
||||
// 阻塞取一帧, 超时 timeout_ms 毫秒. 成功返回 true, 需配对调用 releaseFrame()
|
||||
bool getFrame(Frame &frame, int timeout_ms = 1000);
|
||||
|
||||
// 归还缓冲区给驱动队列, 必须在下次 getFrame 前调用
|
||||
void releaseFrame(const Frame &frame);
|
||||
|
||||
uint32_t width() const { return width_; }
|
||||
uint32_t height() const { return height_; }
|
||||
uint32_t pixelformat() const { return pixelformat_; }
|
||||
|
||||
private:
|
||||
struct MmapBuffer {
|
||||
void *start = nullptr;
|
||||
size_t length = 0;
|
||||
bool queued = false; // 是否在驱动队列中
|
||||
};
|
||||
|
||||
// 归还指定索引的缓冲区 (内部使用)
|
||||
void releaseFrameByIndex(int idx);
|
||||
|
||||
int fd_ = -1;
|
||||
uint32_t width_ = 0;
|
||||
uint32_t height_ = 0;
|
||||
uint32_t pixelformat_ = 0;
|
||||
uint32_t bytes_per_line_ = 0;
|
||||
bool streaming_ = false;
|
||||
bool reconnect_needed_ = false;
|
||||
std::vector<MmapBuffer> buffers_;
|
||||
int last_dequeued_ = -1; // 当前已出队但未归还的缓冲区索引
|
||||
};
|
||||
|
||||
#endif // V4L2_CAPTURE_H
|
||||
+443
-443
@@ -1,443 +1,443 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERN_C extern "C"
|
||||
#else
|
||||
#define EXTERN_C
|
||||
#endif
|
||||
|
||||
#define ZCAN_API
|
||||
#define INVALID_HANDLE_VALUE ((HANDLE)(-1))
|
||||
|
||||
#define CMD_CAN_FILTER 0x14 // 滤波
|
||||
#define CMD_CAN_TTX 0x16 // 定时发送
|
||||
#define CMD_CAN_TTX_CTL 0x17 // 使能定时发送
|
||||
#define CMD_CAN_TRES 0x18 // CAN终端电阻
|
||||
|
||||
#define ZCAN_CMD_SET_CHNL_RECV_MERGE 0x32 // 设置合并接收 0:不合并接收;1:合并接收
|
||||
#define ZCAN_CMD_GET_CHNL_RECV_MERGE 0x33 // 获取是否开启合并接收 0:不合并接收;1:合并接收
|
||||
|
||||
#define CMD_SET_SN 0x42 // 获取SN号
|
||||
#define CMD_GET_SN 0x43 // 设置SN号
|
||||
#define CMD_CAN_TX_TIMEOUT 0x44 // 发送超时
|
||||
|
||||
#define ZCAN_CMD_GET_SEND_QUEUE_SIZE 0x100 // 获取队列大小,uint32_t
|
||||
#define ZCAN_CMD_GET_SEND_QUEUE_SPACE 0x101 // 获取队列剩余空间, uint32_t
|
||||
#define ZCAN_CMD_SET_SEND_QUEUE_CLR 0x102 // 清空发送队列,1:清空
|
||||
#define ZCAN_CMD_SET_SEND_QUEUE_EN 0x103 // 开启发送队列,1:使能
|
||||
|
||||
typedef unsigned char U8;
|
||||
typedef unsigned short U16;
|
||||
typedef unsigned int U32;
|
||||
typedef unsigned long long U64;
|
||||
|
||||
enum eZCANDataDEF
|
||||
{
|
||||
//数据类型
|
||||
ZCAN_DT_ZCAN_CAN_DATA = 1, // CAN/CANFD数据
|
||||
ZCAN_DT_ZCAN_CANFD_DATA = 2, // CAN/CANFD数据
|
||||
ZCAN_DT_ZCAN_ERROR_DATA = 3, // CAN/CANFD错误数据
|
||||
ZCAN_DT_ZCAN_LIN_DATA = 4, // LIN数据
|
||||
ZCAN_DT_ZCAN_LIN_ERROR_DATA = 5, // LIN错误数据
|
||||
};
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef enum {
|
||||
ZCAN_TX_NORM = 0, /**< normal transmission */
|
||||
ZCAN_TX_ONCE = 1, /**< single-shot transmission */
|
||||
ZCAN_SR_NORM = 2, /**< self reception */
|
||||
ZCAN_SR_ONCE = 3, /**< single-shot transmission & self reception */
|
||||
} ZCAN_TX_MODE;
|
||||
|
||||
/** CAN filter configuration */
|
||||
typedef struct {
|
||||
U8 type; /**< 0-std_frame, 1-ext_frame */
|
||||
U8 pad[3];
|
||||
U32 sid; /**< start-id */
|
||||
U32 eid; /**< end-id */
|
||||
} ZCAN_FILTER;
|
||||
|
||||
typedef struct {
|
||||
U32 size;
|
||||
ZCAN_FILTER table[64];
|
||||
} ZCAN_FILTER_TABLE;
|
||||
|
||||
/** controller initialization */
|
||||
typedef struct {
|
||||
U32 clk; // 时钟频率
|
||||
U32 mode; /**< bit0-normal/listen_only, bit1-ISO/BOSCH */
|
||||
struct {
|
||||
U8 tseg1;
|
||||
U8 tseg2;
|
||||
U8 sjw;
|
||||
U8 smp;
|
||||
U16 brp;
|
||||
} aset;
|
||||
struct {
|
||||
U8 tseg1;
|
||||
U8 tseg2;
|
||||
U8 sjw;
|
||||
U8 smp;
|
||||
U16 brp;
|
||||
} dset;
|
||||
} ZCAN_INIT;
|
||||
|
||||
/** CAN message info */
|
||||
typedef struct {
|
||||
U32 txm : 4; /**< TX-mode, @see ZCAN_TX_MODE */
|
||||
U32 fmt : 4; /**< 0-CAN2.0, 1-CANFD */
|
||||
U32 sdf : 1; /**< 0-data_frame, 1-remote_frame */
|
||||
U32 sef : 1; /**< 0-std_frame, 1-ext_frame */
|
||||
U32 err : 1; /**< error flag */
|
||||
U32 brs : 1; /**< bit-rate switch */
|
||||
U32 est : 1; /**< error state */
|
||||
U32 tx : 1; /**< received valid, tx frame */
|
||||
U32 echo : 1; /**< tx valid, echo frame */
|
||||
U32 qsend_100us:1; /**< queue send delay unit, 1-100us, 0-ms */
|
||||
U32 qsend:1; /**< send valid, queue send frame */
|
||||
U32 pad : 15;
|
||||
} ZCAN_MSG_INF;
|
||||
|
||||
/** CAN message header */
|
||||
typedef struct {
|
||||
U32 ts; /**< timestamp */
|
||||
U32 id; /**< CAN-ID */
|
||||
ZCAN_MSG_INF inf; /**< @see ZCAN_MSG_INF */
|
||||
U16 pad; /**< send queue delay */
|
||||
U8 chn; /**< channel */
|
||||
U8 len; /**< data length */
|
||||
} ZCAN_MSG_HDR;
|
||||
|
||||
/** CAN2.0-frame */
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[8];
|
||||
} ZCAN_20_MSG;
|
||||
|
||||
/** CANFD-frame */
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[64];
|
||||
} ZCAN_FD_MSG;
|
||||
|
||||
// 错误帧
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[8];
|
||||
} ZCAN_ERR_MSG;
|
||||
|
||||
// 定时发送结构体
|
||||
typedef struct {
|
||||
U32 interval; // 定时发送周期,单位 100us
|
||||
U16 repeat; // 发送次数,0等于循环发
|
||||
U8 index; // 定时发送列表的帧索引号,也就是第几条定时发送报文
|
||||
U8 flags; // 0-此帧禁用定时发送,1-此帧使能定时发送
|
||||
ZCAN_FD_MSG msg; // CANFD帧结构体
|
||||
} ZCAN_TTX;
|
||||
|
||||
typedef struct {
|
||||
U32 size; // 实际生效的数组的长度(用来控制实际生效多少条定时发送帧结构体)
|
||||
ZCAN_TTX table[8]; // 最大设置8条
|
||||
} ZCAN_TTX_CFG;
|
||||
|
||||
// 设备信息
|
||||
typedef struct {
|
||||
U16 hwv; /**< hardware version */
|
||||
U16 fwv; /**< firmware version */
|
||||
U16 drv; /**< driver version */
|
||||
U16 api; /**< API version */
|
||||
U16 irq; /**< IRQ */
|
||||
U8 chn; /**< channels */
|
||||
U8 sn[20]; /**< serial number */
|
||||
U8 id[40]; /**< card id */
|
||||
U16 pad[4];
|
||||
} ZCAN_DEV_INF;
|
||||
|
||||
/** controller status */
|
||||
typedef struct {
|
||||
U8 IR; /**< not used(for backward compatibility) */
|
||||
U8 MOD; /**< not used */
|
||||
U8 SR; /**< not used */
|
||||
U8 ALC; /**< not used */
|
||||
U8 ECC; /**< not used */
|
||||
U8 EWL; /**< not used */
|
||||
U8 RXE; /**< RX errors */
|
||||
U8 TXE; /**< TX errors */
|
||||
U32 PAD;
|
||||
} ZCAN_STAT;
|
||||
|
||||
//lin begin
|
||||
typedef struct _VCI_LIN_INIT_CONFIG
|
||||
{
|
||||
U8 linMode; // 是否作为主机,0-从机,1-主机
|
||||
U8 chkSumMode; // 校验方式,1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||
U16 reserved; // 保留
|
||||
U32 linBaud; // 波特率,取值1000~20000
|
||||
}ZCAN_LIN_INIT_CONFIG, *PZCAN_LIN_INIT_CONFIG;
|
||||
|
||||
typedef struct tagZCANLINData
|
||||
{
|
||||
union{
|
||||
struct {
|
||||
U8 ID : 6; // 帧ID
|
||||
U8 Parity : 2; // 帧ID校验
|
||||
}unionVal;
|
||||
U8 rawVal; // 受保护的ID原始值
|
||||
} PID; // 受保护的ID
|
||||
struct
|
||||
{
|
||||
U64 timeStamp; // 时间戳,单位微秒(us)
|
||||
U8 dataLen; // 数据长度
|
||||
U8 dir; // 传输方向,0-接收 1-发送
|
||||
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||
U8 reserved[13]; // 保留
|
||||
U8 data[8]; // 数据
|
||||
}RxData; // 仅接收数据时有效
|
||||
U8 reserved[7]; // 保留
|
||||
}ZCANLINData;
|
||||
|
||||
typedef struct tagZCANLINErrData
|
||||
{
|
||||
U64 timeStamp; // 时间戳, 单位微秒(us)
|
||||
union {
|
||||
struct {
|
||||
U8 ID : 6; // 帧ID
|
||||
U8 Parity : 2; // 帧ID校验
|
||||
}unionVal;
|
||||
U8 rawVal; // 受保护的ID原始值
|
||||
} PID; // 受保护的ID
|
||||
U8 dataLen;
|
||||
U8 data[8];
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
U16 errStage : 4; // 错误阶段
|
||||
U16 errReason : 4; // 错误原因
|
||||
U16 reserved : 8; // 保留
|
||||
};
|
||||
U16 unionErrData;
|
||||
}errData;
|
||||
U8 dir; // 传输方向
|
||||
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||
U8 reserved[10]; // 保留
|
||||
}ZCANLINErrData;
|
||||
|
||||
typedef struct _VCI_LIN_MSG{
|
||||
U8 chnl; // 数据通道
|
||||
U8 dataType; // 数据类型, 0-LIN数据 1-LIN错误数据
|
||||
union
|
||||
{
|
||||
ZCANLINData zcanLINData; // LIN数据
|
||||
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||
U8 raw[46]; // RAW数据
|
||||
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||
}ZCAN_LIN_MSG, *PZCAN_LIN_MSG;
|
||||
|
||||
enum eZLINChkSumMode
|
||||
{
|
||||
DEFAULT = 0, // 默认,启动时配置
|
||||
CLASSIC_CHKSUM, // 经典校验
|
||||
ENHANCE_CHKSUM, // 增强校验
|
||||
AUTOMATIC, // 自动,设备自动识别校验方式(仅ZCAN_SetLINSubscribe时有效)
|
||||
};
|
||||
enum
|
||||
{
|
||||
MAX_LIN_ID_COUNT = 63,
|
||||
MIN_LIN_DLC = 1,
|
||||
MAX_LIN_DLC = 8,
|
||||
AUTO_LIN_DLC = 255,
|
||||
};
|
||||
typedef struct _VCI_LIN_PUBLISH_CFG
|
||||
{
|
||||
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||
U8 dataLen; // dataLen范围为1-8
|
||||
U8 data[8];
|
||||
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验(对应eZLINChkSumMode的模式)
|
||||
U8 reserved[5]; // 保留
|
||||
}ZCAN_LIN_PUBLISH_CFG, *PZCAN_LIN_PUBLISH_CFG;
|
||||
|
||||
typedef struct _VCI_LIN_SUBSCIBE_CFG
|
||||
{
|
||||
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||
U8 dataLen; // dataLen范围为1-8 当为255(0xff)则表示设备自动识别报文长度
|
||||
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||
U8 reserved[5]; // 保留
|
||||
}ZCAN_LIN_SUBSCIBE_CFG, *PZCAN_LIN_SUBSCIBE_CFG;
|
||||
|
||||
// 合并接收数据数据结构,支持CAN/CANFD/LIN/错误等不同类型数据
|
||||
typedef struct tagZCANDataObj
|
||||
{
|
||||
U8 dataType; // 数据类型, 参考eZCANDataDEF中 数据类型 部分定义
|
||||
U8 chnl; // 数据通道
|
||||
union{
|
||||
struct{
|
||||
U16 reserved : 16; // 保留
|
||||
}unionVal;
|
||||
U16 rawVal;
|
||||
}flag; // 标志信息, 暂未使用
|
||||
U8 extraData[4]; // 额外数据, 暂未使用
|
||||
union
|
||||
{
|
||||
ZCAN_20_MSG zcanCANData; // CAN数据
|
||||
ZCAN_FD_MSG zcanCANFDData; // CANFD数据
|
||||
ZCAN_ERR_MSG zcanErrData; // 错误数据
|
||||
ZCANLINData zcanLINData; // LIN数据
|
||||
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||
U8 raw[92]; // RAW数据
|
||||
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||
}ZCANDataObj;
|
||||
|
||||
|
||||
// UDS传输协议版本
|
||||
typedef U8 ZCAN_UDS_TRANS_VER;
|
||||
#define ZCAN_UDS_TRANS_VER_0 0 // ISO15765-2(2004版本)
|
||||
#define ZCAN_UDS_TRANS_VER_1 1 // ISO15765-2(2016版本)
|
||||
|
||||
// 帧类型
|
||||
typedef U8 ZCAN_UDS_FRAME_TYPE;
|
||||
#define ZCAN_UDS_FRAME_CAN 0 // CAN帧
|
||||
#define ZCAN_UDS_FRAME_CANFD 1 // CANFD帧
|
||||
#define ZCAN_UDS_FRAME_CANFD_BRS 2 // CANFD加速帧
|
||||
|
||||
// UDS填充模式
|
||||
#define FILL_MODE_SHORT 0 // 小于8字节填充至8字节,大于8字节时按DLC就近填充
|
||||
#define FILL_MODE_NONE 1 // 不填充
|
||||
#define FILL_MODE_MAX 2 // 填充至最大数据长度 (不建议)
|
||||
|
||||
// CAN UDS请求数据
|
||||
typedef struct _ZCAN_UDS_REQUEST
|
||||
{
|
||||
U32 req_id; // 请求事务ID,范围0~65535,本次请求的唯一标识
|
||||
U8 channel; // 设备通道索引 0~255
|
||||
ZCAN_UDS_FRAME_TYPE frame_type; // 帧类型
|
||||
U8 reserved0[2]; // 保留
|
||||
U32 src_addr; // 请求地址
|
||||
U32 dst_addr; // 响应地址
|
||||
U8 suppress_response; // 1:抑制响应
|
||||
U8 sid; // 请求服务id
|
||||
U8 reserved1[6]; // 保留
|
||||
struct {
|
||||
U32 timeout; // 响应超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||
U32 enhanced_timeout; // 收到消极响应错误码为0x78后的超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||
U8 check_any_negative_response:1; // 接收到非本次请求服务的消极响应时是否需要判定为响应错误
|
||||
U8 wait_if_suppress_response:1; // 抑制响应时是否需要等待消极响应,等待时长为响应超时时间
|
||||
U8 flag:6; // 保留
|
||||
U8 reserved0[7]; // 保留
|
||||
} session_param; // 会话层参数
|
||||
struct {
|
||||
ZCAN_UDS_TRANS_VER version; // 传输协议版本, VERSION_0, VERSION_1
|
||||
U8 max_data_len; // 单帧最大数据长度, can:8, canfd:64
|
||||
U8 local_st_min; // 本程序发送流控时用,连续帧之间的最小间隔, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||
U8 block_size; // 流控帧的块大小
|
||||
U8 fill_byte; // 无效字节的填充数据
|
||||
U8 ext_frame; // 0:标准帧 1:扩展帧
|
||||
U8 is_modify_ecu_st_min; // 是否忽略ECU返回流控的STmin,强制使用本程序设置的 remote_st_min
|
||||
U8 remote_st_min; // 发送多帧时用, is_ignore_ecu_st_min = 1 时有效, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||
U32 fc_timeout; // 接收流控超时时间(ms), 如发送首帧后需要等待回应流控帧
|
||||
U8 fill_mode; // 数据长度填充模式, FILL_MODE_SHORT, FILL_MODE_NONE, FILL_MODE_MAX
|
||||
U8 reserved0[3]; // 保留
|
||||
} trans_param; // 传输层参数
|
||||
U8 *data; // 数据数组(不包含SID)
|
||||
U32 data_len; // 数据数组的长度
|
||||
U32 reserved2; // 保留
|
||||
} ZCAN_UDS_REQUEST;
|
||||
|
||||
// UDS错误码
|
||||
typedef U8 ZCAN_UDS_ERROR;
|
||||
#define ZCAN_UDS_ERROR_OK 0 // 没错误
|
||||
#define ZCAN_UDS_ERROR_TIMEOUT 1 // 响应超时
|
||||
#define ZCAN_UDS_ERROR_TRANSPORT 2 // 发送数据失败
|
||||
#define ZCAN_UDS_ERROR_CANCEL 3 // 取消请求
|
||||
#define ZCAN_UDS_ERROR_SUPPRESS_RESPONSE 4 // 抑制响应
|
||||
#define ZCAN_UDS_ERROR_BUSY 5 // 忙碌中
|
||||
#define ZCAN_UDS_ERROR_REQ_PARAM 6 // 请求参数错误
|
||||
#define ZCAN_UDS_ERROR_OTHTER 100
|
||||
|
||||
typedef U8 ZCAN_UDS_RESPONSE_TYPE;
|
||||
#define ZCAN_UDS_RT_NEGATIVE 0 // 消极响应
|
||||
#define ZCAN_UDS_RT_POSITIVE 1 // 积极响应
|
||||
|
||||
// UDS响应数据
|
||||
typedef struct _ZCAN_UDS_RESPONSE
|
||||
{
|
||||
ZCAN_UDS_ERROR status; // 响应状态
|
||||
U8 reserved[6]; // 保留
|
||||
ZCAN_UDS_RESPONSE_TYPE type; // 响应类型
|
||||
union {
|
||||
struct {
|
||||
U8 sid; // 响应服务id
|
||||
U32 data_len; // 数据长度(不包含SID), 数据存放在接口传入的dataBuf中
|
||||
} positive;
|
||||
struct {
|
||||
U8 neg_code; // 固定为0x7F
|
||||
U8 sid; // 请求服务id
|
||||
U8 error_code; // 错误码
|
||||
} negative;
|
||||
U8 raw[8];
|
||||
};
|
||||
} ZCAN_UDS_RESPONSE;
|
||||
|
||||
// UDS控制类型
|
||||
typedef U32 ZCAN_UDS_CTRL_CODE;
|
||||
#define ZCAN_UDS_CTRL_STOP_REQ 0 // 停止UDS请求
|
||||
|
||||
// UDS控制请求
|
||||
typedef struct _ZCAN_UDS_CTRL_REQ
|
||||
{
|
||||
U32 reqID; // 请求事务ID,指明要操作哪一条请求
|
||||
ZCAN_UDS_CTRL_CODE cmd; // 控制类型
|
||||
U8 reserved[8]; // 保留
|
||||
} ZCAN_UDS_CTRL_REQ;
|
||||
|
||||
// UDS控制结果
|
||||
typedef U32 ZCAN_UDS_CTRL_RESULT;
|
||||
#define ZCAN_UDS_CTRL_RESULT_OK 0 // 成功
|
||||
#define ZCAN_UDS_CTRL_RESULT_ERR 1 // 失败
|
||||
|
||||
// UDS控制响应数据
|
||||
typedef struct _ZCAN_UDS_CTRL_RESP
|
||||
{
|
||||
ZCAN_UDS_CTRL_RESULT result; // 操作结果
|
||||
U8 reserved[8]; // 保留
|
||||
} ZCAN_UDS_CTRL_RESP;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
//
|
||||
EXTERN_C U32 ZCAN_API VCI_OpenDevice(U32 Type, U32 Card, U32 Reserved);
|
||||
EXTERN_C U32 ZCAN_API VCI_CloseDevice(U32 Type, U32 Card);
|
||||
EXTERN_C U32 ZCAN_API VCI_InitCAN(U32 Type, U32 Card, U32 Port, ZCAN_INIT *pInit);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadBoardInfo(U32 Type, U32 Card, ZCAN_DEV_INF *pInfo);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadErrInfo(U32 Type, U32 Card, U32 Port, ZCAN_ERR_MSG *pErr);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadCANStatus(U32 Type, U32 Card, U32 Port, ZCAN_STAT *pStat);
|
||||
EXTERN_C U32 ZCAN_API VCI_GetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||
EXTERN_C U32 ZCAN_API VCI_SetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||
EXTERN_C U32 ZCAN_API VCI_GetReceiveNum(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_ClearBuffer(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_StartCAN(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_ResetCAN(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_Transmit(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count);
|
||||
EXTERN_C U32 ZCAN_API VCI_TransmitFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count);
|
||||
EXTERN_C U32 ZCAN_API VCI_Receive(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count, U32 Time);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReceiveFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count, U32 Time);
|
||||
EXTERN_C U32 ZCAN_API VCI_Debug(U32 Debug);
|
||||
|
||||
// LIN
|
||||
EXTERN_C U32 VCI_InitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_INIT_CONFIG pLINInitConfig);
|
||||
EXTERN_C U32 VCI_StartLIN(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ResetLIN(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_TransmitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pSend, U32 Len);
|
||||
EXTERN_C U32 VCI_GetLINReceiveNum(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ClearLINBuffer(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ReceiveLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pReceive, U32 Len,int WaitTime);
|
||||
EXTERN_C U32 VCI_SetLINSubscribe(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_SUBSCIBE_CFG pSend, U32 nSubscribeCount);
|
||||
EXTERN_C U32 VCI_SetLINPublish(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_PUBLISH_CFG pSend, U32 nPublishCount);
|
||||
|
||||
// 合并接收
|
||||
EXTERN_C U32 VCI_TransmitData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count);
|
||||
EXTERN_C U32 VCI_ReceiveData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count, unsigned Time);
|
||||
|
||||
// UDS
|
||||
EXTERN_C U32 VCI_UDS_Request(unsigned Type, unsigned Card, const ZCAN_UDS_REQUEST *req, ZCAN_UDS_RESPONSE *resp, U8 *dataBuf, U32 dataBufSize);
|
||||
EXTERN_C U32 VCI_UDS_Control(unsigned Type, unsigned Card, const ZCAN_UDS_CTRL_REQ *ctrl, ZCAN_UDS_CTRL_RESP *resp);
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define EXTERN_C extern "C"
|
||||
#else
|
||||
#define EXTERN_C
|
||||
#endif
|
||||
|
||||
#define ZCAN_API
|
||||
#define INVALID_HANDLE_VALUE ((HANDLE)(-1))
|
||||
|
||||
#define CMD_CAN_FILTER 0x14 // 滤波
|
||||
#define CMD_CAN_TTX 0x16 // 定时发送
|
||||
#define CMD_CAN_TTX_CTL 0x17 // 使能定时发送
|
||||
#define CMD_CAN_TRES 0x18 // CAN终端电阻
|
||||
|
||||
#define ZCAN_CMD_SET_CHNL_RECV_MERGE 0x32 // 设置合并接收 0:不合并接收;1:合并接收
|
||||
#define ZCAN_CMD_GET_CHNL_RECV_MERGE 0x33 // 获取是否开启合并接收 0:不合并接收;1:合并接收
|
||||
|
||||
#define CMD_SET_SN 0x42 // 获取SN号
|
||||
#define CMD_GET_SN 0x43 // 设置SN号
|
||||
#define CMD_CAN_TX_TIMEOUT 0x44 // 发送超时
|
||||
|
||||
#define ZCAN_CMD_GET_SEND_QUEUE_SIZE 0x100 // 获取队列大小,uint32_t
|
||||
#define ZCAN_CMD_GET_SEND_QUEUE_SPACE 0x101 // 获取队列剩余空间, uint32_t
|
||||
#define ZCAN_CMD_SET_SEND_QUEUE_CLR 0x102 // 清空发送队列,1:清空
|
||||
#define ZCAN_CMD_SET_SEND_QUEUE_EN 0x103 // 开启发送队列,1:使能
|
||||
|
||||
typedef unsigned char U8;
|
||||
typedef unsigned short U16;
|
||||
typedef unsigned int U32;
|
||||
typedef unsigned long long U64;
|
||||
|
||||
enum eZCANDataDEF
|
||||
{
|
||||
//数据类型
|
||||
ZCAN_DT_ZCAN_CAN_DATA = 1, // CAN/CANFD数据
|
||||
ZCAN_DT_ZCAN_CANFD_DATA = 2, // CAN/CANFD数据
|
||||
ZCAN_DT_ZCAN_ERROR_DATA = 3, // CAN/CANFD错误数据
|
||||
ZCAN_DT_ZCAN_LIN_DATA = 4, // LIN数据
|
||||
ZCAN_DT_ZCAN_LIN_ERROR_DATA = 5, // LIN错误数据
|
||||
};
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef enum {
|
||||
ZCAN_TX_NORM = 0, /**< normal transmission */
|
||||
ZCAN_TX_ONCE = 1, /**< single-shot transmission */
|
||||
ZCAN_SR_NORM = 2, /**< self reception */
|
||||
ZCAN_SR_ONCE = 3, /**< single-shot transmission & self reception */
|
||||
} ZCAN_TX_MODE;
|
||||
|
||||
/** CAN filter configuration */
|
||||
typedef struct {
|
||||
U8 type; /**< 0-std_frame, 1-ext_frame */
|
||||
U8 pad[3];
|
||||
U32 sid; /**< start-id */
|
||||
U32 eid; /**< end-id */
|
||||
} ZCAN_FILTER;
|
||||
|
||||
typedef struct {
|
||||
U32 size;
|
||||
ZCAN_FILTER table[64];
|
||||
} ZCAN_FILTER_TABLE;
|
||||
|
||||
/** controller initialization */
|
||||
typedef struct {
|
||||
U32 clk; // 时钟频率
|
||||
U32 mode; /**< bit0-normal/listen_only, bit1-ISO/BOSCH */
|
||||
struct {
|
||||
U8 tseg1;
|
||||
U8 tseg2;
|
||||
U8 sjw;
|
||||
U8 smp;
|
||||
U16 brp;
|
||||
} aset;
|
||||
struct {
|
||||
U8 tseg1;
|
||||
U8 tseg2;
|
||||
U8 sjw;
|
||||
U8 smp;
|
||||
U16 brp;
|
||||
} dset;
|
||||
} ZCAN_INIT;
|
||||
|
||||
/** CAN message info */
|
||||
typedef struct {
|
||||
U32 txm : 4; /**< TX-mode, @see ZCAN_TX_MODE */
|
||||
U32 fmt : 4; /**< 0-CAN2.0, 1-CANFD */
|
||||
U32 sdf : 1; /**< 0-data_frame, 1-remote_frame */
|
||||
U32 sef : 1; /**< 0-std_frame, 1-ext_frame */
|
||||
U32 err : 1; /**< error flag */
|
||||
U32 brs : 1; /**< bit-rate switch */
|
||||
U32 est : 1; /**< error state */
|
||||
U32 tx : 1; /**< received valid, tx frame */
|
||||
U32 echo : 1; /**< tx valid, echo frame */
|
||||
U32 qsend_100us:1; /**< queue send delay unit, 1-100us, 0-ms */
|
||||
U32 qsend:1; /**< send valid, queue send frame */
|
||||
U32 pad : 15;
|
||||
} ZCAN_MSG_INF;
|
||||
|
||||
/** CAN message header */
|
||||
typedef struct {
|
||||
U32 ts; /**< timestamp */
|
||||
U32 id; /**< CAN-ID */
|
||||
ZCAN_MSG_INF inf; /**< @see ZCAN_MSG_INF */
|
||||
U16 pad; /**< send queue delay */
|
||||
U8 chn; /**< channel */
|
||||
U8 len; /**< data length */
|
||||
} ZCAN_MSG_HDR;
|
||||
|
||||
/** CAN2.0-frame */
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[8];
|
||||
} ZCAN_20_MSG;
|
||||
|
||||
/** CANFD-frame */
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[64];
|
||||
} ZCAN_FD_MSG;
|
||||
|
||||
// 错误帧
|
||||
typedef struct {
|
||||
ZCAN_MSG_HDR hdr;
|
||||
U8 dat[8];
|
||||
} ZCAN_ERR_MSG;
|
||||
|
||||
// 定时发送结构体
|
||||
typedef struct {
|
||||
U32 interval; // 定时发送周期,单位 100us
|
||||
U16 repeat; // 发送次数,0等于循环发
|
||||
U8 index; // 定时发送列表的帧索引号,也就是第几条定时发送报文
|
||||
U8 flags; // 0-此帧禁用定时发送,1-此帧使能定时发送
|
||||
ZCAN_FD_MSG msg; // CANFD帧结构体
|
||||
} ZCAN_TTX;
|
||||
|
||||
typedef struct {
|
||||
U32 size; // 实际生效的数组的长度(用来控制实际生效多少条定时发送帧结构体)
|
||||
ZCAN_TTX table[8]; // 最大设置8条
|
||||
} ZCAN_TTX_CFG;
|
||||
|
||||
// 设备信息
|
||||
typedef struct {
|
||||
U16 hwv; /**< hardware version */
|
||||
U16 fwv; /**< firmware version */
|
||||
U16 drv; /**< driver version */
|
||||
U16 api; /**< API version */
|
||||
U16 irq; /**< IRQ */
|
||||
U8 chn; /**< channels */
|
||||
U8 sn[20]; /**< serial number */
|
||||
U8 id[40]; /**< card id */
|
||||
U16 pad[4];
|
||||
} ZCAN_DEV_INF;
|
||||
|
||||
/** controller status */
|
||||
typedef struct {
|
||||
U8 IR; /**< not used(for backward compatibility) */
|
||||
U8 MOD; /**< not used */
|
||||
U8 SR; /**< not used */
|
||||
U8 ALC; /**< not used */
|
||||
U8 ECC; /**< not used */
|
||||
U8 EWL; /**< not used */
|
||||
U8 RXE; /**< RX errors */
|
||||
U8 TXE; /**< TX errors */
|
||||
U32 PAD;
|
||||
} ZCAN_STAT;
|
||||
|
||||
//lin begin
|
||||
typedef struct _VCI_LIN_INIT_CONFIG
|
||||
{
|
||||
U8 linMode; // 是否作为主机,0-从机,1-主机
|
||||
U8 chkSumMode; // 校验方式,1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||
U16 reserved; // 保留
|
||||
U32 linBaud; // 波特率,取值1000~20000
|
||||
}ZCAN_LIN_INIT_CONFIG, *PZCAN_LIN_INIT_CONFIG;
|
||||
|
||||
typedef struct tagZCANLINData
|
||||
{
|
||||
union{
|
||||
struct {
|
||||
U8 ID : 6; // 帧ID
|
||||
U8 Parity : 2; // 帧ID校验
|
||||
}unionVal;
|
||||
U8 rawVal; // 受保护的ID原始值
|
||||
} PID; // 受保护的ID
|
||||
struct
|
||||
{
|
||||
U64 timeStamp; // 时间戳,单位微秒(us)
|
||||
U8 dataLen; // 数据长度
|
||||
U8 dir; // 传输方向,0-接收 1-发送
|
||||
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||
U8 reserved[13]; // 保留
|
||||
U8 data[8]; // 数据
|
||||
}RxData; // 仅接收数据时有效
|
||||
U8 reserved[7]; // 保留
|
||||
}ZCANLINData;
|
||||
|
||||
typedef struct tagZCANLINErrData
|
||||
{
|
||||
U64 timeStamp; // 时间戳, 单位微秒(us)
|
||||
union {
|
||||
struct {
|
||||
U8 ID : 6; // 帧ID
|
||||
U8 Parity : 2; // 帧ID校验
|
||||
}unionVal;
|
||||
U8 rawVal; // 受保护的ID原始值
|
||||
} PID; // 受保护的ID
|
||||
U8 dataLen;
|
||||
U8 data[8];
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
U16 errStage : 4; // 错误阶段
|
||||
U16 errReason : 4; // 错误原因
|
||||
U16 reserved : 8; // 保留
|
||||
};
|
||||
U16 unionErrData;
|
||||
}errData;
|
||||
U8 dir; // 传输方向
|
||||
U8 chkSum; // 数据校验, 部分设备不支持校验数据的获取
|
||||
U8 reserved[10]; // 保留
|
||||
}ZCANLINErrData;
|
||||
|
||||
typedef struct _VCI_LIN_MSG{
|
||||
U8 chnl; // 数据通道
|
||||
U8 dataType; // 数据类型, 0-LIN数据 1-LIN错误数据
|
||||
union
|
||||
{
|
||||
ZCANLINData zcanLINData; // LIN数据
|
||||
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||
U8 raw[46]; // RAW数据
|
||||
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||
}ZCAN_LIN_MSG, *PZCAN_LIN_MSG;
|
||||
|
||||
enum eZLINChkSumMode
|
||||
{
|
||||
DEFAULT = 0, // 默认,启动时配置
|
||||
CLASSIC_CHKSUM, // 经典校验
|
||||
ENHANCE_CHKSUM, // 增强校验
|
||||
AUTOMATIC, // 自动,设备自动识别校验方式(仅ZCAN_SetLINSubscribe时有效)
|
||||
};
|
||||
enum
|
||||
{
|
||||
MAX_LIN_ID_COUNT = 63,
|
||||
MIN_LIN_DLC = 1,
|
||||
MAX_LIN_DLC = 8,
|
||||
AUTO_LIN_DLC = 255,
|
||||
};
|
||||
typedef struct _VCI_LIN_PUBLISH_CFG
|
||||
{
|
||||
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||
U8 dataLen; // dataLen范围为1-8
|
||||
U8 data[8];
|
||||
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验(对应eZLINChkSumMode的模式)
|
||||
U8 reserved[5]; // 保留
|
||||
}ZCAN_LIN_PUBLISH_CFG, *PZCAN_LIN_PUBLISH_CFG;
|
||||
|
||||
typedef struct _VCI_LIN_SUBSCIBE_CFG
|
||||
{
|
||||
U8 ID; // 受保护的ID(ID取值范围为0-63)
|
||||
U8 dataLen; // dataLen范围为1-8 当为255(0xff)则表示设备自动识别报文长度
|
||||
U8 chkSumMode; // 校验方式,0-默认,启动时配置 1-经典校验 2-增强校验 3-自动(对应eZLINChkSumMode的模式)
|
||||
U8 reserved[5]; // 保留
|
||||
}ZCAN_LIN_SUBSCIBE_CFG, *PZCAN_LIN_SUBSCIBE_CFG;
|
||||
|
||||
// 合并接收数据数据结构,支持CAN/CANFD/LIN/错误等不同类型数据
|
||||
typedef struct tagZCANDataObj
|
||||
{
|
||||
U8 dataType; // 数据类型, 参考eZCANDataDEF中 数据类型 部分定义
|
||||
U8 chnl; // 数据通道
|
||||
union{
|
||||
struct{
|
||||
U16 reserved : 16; // 保留
|
||||
}unionVal;
|
||||
U16 rawVal;
|
||||
}flag; // 标志信息, 暂未使用
|
||||
U8 extraData[4]; // 额外数据, 暂未使用
|
||||
union
|
||||
{
|
||||
ZCAN_20_MSG zcanCANData; // CAN数据
|
||||
ZCAN_FD_MSG zcanCANFDData; // CANFD数据
|
||||
ZCAN_ERR_MSG zcanErrData; // 错误数据
|
||||
ZCANLINData zcanLINData; // LIN数据
|
||||
ZCANLINErrData zcanLINErrData; // LIN错误数据
|
||||
U8 raw[92]; // RAW数据
|
||||
} data; // 实际数据, 联合体,有效成员根据 dataType 字段而定
|
||||
}ZCANDataObj;
|
||||
|
||||
|
||||
// UDS传输协议版本
|
||||
typedef U8 ZCAN_UDS_TRANS_VER;
|
||||
#define ZCAN_UDS_TRANS_VER_0 0 // ISO15765-2(2004版本)
|
||||
#define ZCAN_UDS_TRANS_VER_1 1 // ISO15765-2(2016版本)
|
||||
|
||||
// 帧类型
|
||||
typedef U8 ZCAN_UDS_FRAME_TYPE;
|
||||
#define ZCAN_UDS_FRAME_CAN 0 // CAN帧
|
||||
#define ZCAN_UDS_FRAME_CANFD 1 // CANFD帧
|
||||
#define ZCAN_UDS_FRAME_CANFD_BRS 2 // CANFD加速帧
|
||||
|
||||
// UDS填充模式
|
||||
#define FILL_MODE_SHORT 0 // 小于8字节填充至8字节,大于8字节时按DLC就近填充
|
||||
#define FILL_MODE_NONE 1 // 不填充
|
||||
#define FILL_MODE_MAX 2 // 填充至最大数据长度 (不建议)
|
||||
|
||||
// CAN UDS请求数据
|
||||
typedef struct _ZCAN_UDS_REQUEST
|
||||
{
|
||||
U32 req_id; // 请求事务ID,范围0~65535,本次请求的唯一标识
|
||||
U8 channel; // 设备通道索引 0~255
|
||||
ZCAN_UDS_FRAME_TYPE frame_type; // 帧类型
|
||||
U8 reserved0[2]; // 保留
|
||||
U32 src_addr; // 请求地址
|
||||
U32 dst_addr; // 响应地址
|
||||
U8 suppress_response; // 1:抑制响应
|
||||
U8 sid; // 请求服务id
|
||||
U8 reserved1[6]; // 保留
|
||||
struct {
|
||||
U32 timeout; // 响应超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||
U32 enhanced_timeout; // 收到消极响应错误码为0x78后的超时时间(ms)。因PC定时器误差,建议设置不小于200ms
|
||||
U8 check_any_negative_response:1; // 接收到非本次请求服务的消极响应时是否需要判定为响应错误
|
||||
U8 wait_if_suppress_response:1; // 抑制响应时是否需要等待消极响应,等待时长为响应超时时间
|
||||
U8 flag:6; // 保留
|
||||
U8 reserved0[7]; // 保留
|
||||
} session_param; // 会话层参数
|
||||
struct {
|
||||
ZCAN_UDS_TRANS_VER version; // 传输协议版本, VERSION_0, VERSION_1
|
||||
U8 max_data_len; // 单帧最大数据长度, can:8, canfd:64
|
||||
U8 local_st_min; // 本程序发送流控时用,连续帧之间的最小间隔, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||
U8 block_size; // 流控帧的块大小
|
||||
U8 fill_byte; // 无效字节的填充数据
|
||||
U8 ext_frame; // 0:标准帧 1:扩展帧
|
||||
U8 is_modify_ecu_st_min; // 是否忽略ECU返回流控的STmin,强制使用本程序设置的 remote_st_min
|
||||
U8 remote_st_min; // 发送多帧时用, is_ignore_ecu_st_min = 1 时有效, 0x00-0x7F(0ms~127ms), 0xF1-0xF9(100us~900us)
|
||||
U32 fc_timeout; // 接收流控超时时间(ms), 如发送首帧后需要等待回应流控帧
|
||||
U8 fill_mode; // 数据长度填充模式, FILL_MODE_SHORT, FILL_MODE_NONE, FILL_MODE_MAX
|
||||
U8 reserved0[3]; // 保留
|
||||
} trans_param; // 传输层参数
|
||||
U8 *data; // 数据数组(不包含SID)
|
||||
U32 data_len; // 数据数组的长度
|
||||
U32 reserved2; // 保留
|
||||
} ZCAN_UDS_REQUEST;
|
||||
|
||||
// UDS错误码
|
||||
typedef U8 ZCAN_UDS_ERROR;
|
||||
#define ZCAN_UDS_ERROR_OK 0 // 没错误
|
||||
#define ZCAN_UDS_ERROR_TIMEOUT 1 // 响应超时
|
||||
#define ZCAN_UDS_ERROR_TRANSPORT 2 // 发送数据失败
|
||||
#define ZCAN_UDS_ERROR_CANCEL 3 // 取消请求
|
||||
#define ZCAN_UDS_ERROR_SUPPRESS_RESPONSE 4 // 抑制响应
|
||||
#define ZCAN_UDS_ERROR_BUSY 5 // 忙碌中
|
||||
#define ZCAN_UDS_ERROR_REQ_PARAM 6 // 请求参数错误
|
||||
#define ZCAN_UDS_ERROR_OTHTER 100
|
||||
|
||||
typedef U8 ZCAN_UDS_RESPONSE_TYPE;
|
||||
#define ZCAN_UDS_RT_NEGATIVE 0 // 消极响应
|
||||
#define ZCAN_UDS_RT_POSITIVE 1 // 积极响应
|
||||
|
||||
// UDS响应数据
|
||||
typedef struct _ZCAN_UDS_RESPONSE
|
||||
{
|
||||
ZCAN_UDS_ERROR status; // 响应状态
|
||||
U8 reserved[6]; // 保留
|
||||
ZCAN_UDS_RESPONSE_TYPE type; // 响应类型
|
||||
union {
|
||||
struct {
|
||||
U8 sid; // 响应服务id
|
||||
U32 data_len; // 数据长度(不包含SID), 数据存放在接口传入的dataBuf中
|
||||
} positive;
|
||||
struct {
|
||||
U8 neg_code; // 固定为0x7F
|
||||
U8 sid; // 请求服务id
|
||||
U8 error_code; // 错误码
|
||||
} negative;
|
||||
U8 raw[8];
|
||||
};
|
||||
} ZCAN_UDS_RESPONSE;
|
||||
|
||||
// UDS控制类型
|
||||
typedef U32 ZCAN_UDS_CTRL_CODE;
|
||||
#define ZCAN_UDS_CTRL_STOP_REQ 0 // 停止UDS请求
|
||||
|
||||
// UDS控制请求
|
||||
typedef struct _ZCAN_UDS_CTRL_REQ
|
||||
{
|
||||
U32 reqID; // 请求事务ID,指明要操作哪一条请求
|
||||
ZCAN_UDS_CTRL_CODE cmd; // 控制类型
|
||||
U8 reserved[8]; // 保留
|
||||
} ZCAN_UDS_CTRL_REQ;
|
||||
|
||||
// UDS控制结果
|
||||
typedef U32 ZCAN_UDS_CTRL_RESULT;
|
||||
#define ZCAN_UDS_CTRL_RESULT_OK 0 // 成功
|
||||
#define ZCAN_UDS_CTRL_RESULT_ERR 1 // 失败
|
||||
|
||||
// UDS控制响应数据
|
||||
typedef struct _ZCAN_UDS_CTRL_RESP
|
||||
{
|
||||
ZCAN_UDS_CTRL_RESULT result; // 操作结果
|
||||
U8 reserved[8]; // 保留
|
||||
} ZCAN_UDS_CTRL_RESP;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
//
|
||||
EXTERN_C U32 ZCAN_API VCI_OpenDevice(U32 Type, U32 Card, U32 Reserved);
|
||||
EXTERN_C U32 ZCAN_API VCI_CloseDevice(U32 Type, U32 Card);
|
||||
EXTERN_C U32 ZCAN_API VCI_InitCAN(U32 Type, U32 Card, U32 Port, ZCAN_INIT *pInit);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadBoardInfo(U32 Type, U32 Card, ZCAN_DEV_INF *pInfo);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadErrInfo(U32 Type, U32 Card, U32 Port, ZCAN_ERR_MSG *pErr);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReadCANStatus(U32 Type, U32 Card, U32 Port, ZCAN_STAT *pStat);
|
||||
EXTERN_C U32 ZCAN_API VCI_GetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||
EXTERN_C U32 ZCAN_API VCI_SetReference(U32 Type, U32 Card, U32 Port, U32 Ref, void *pData);
|
||||
EXTERN_C U32 ZCAN_API VCI_GetReceiveNum(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_ClearBuffer(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_StartCAN(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_ResetCAN(U32 Type, U32 Card, U32 Port);
|
||||
EXTERN_C U32 ZCAN_API VCI_Transmit(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count);
|
||||
EXTERN_C U32 ZCAN_API VCI_TransmitFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count);
|
||||
EXTERN_C U32 ZCAN_API VCI_Receive(U32 Type, U32 Card, U32 Port, ZCAN_20_MSG *pData, U32 Count, U32 Time);
|
||||
EXTERN_C U32 ZCAN_API VCI_ReceiveFD(U32 Type, U32 Card, U32 Port, ZCAN_FD_MSG *pData, U32 Count, U32 Time);
|
||||
EXTERN_C U32 ZCAN_API VCI_Debug(U32 Debug);
|
||||
|
||||
// LIN
|
||||
EXTERN_C U32 VCI_InitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_INIT_CONFIG pLINInitConfig);
|
||||
EXTERN_C U32 VCI_StartLIN(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ResetLIN(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_TransmitLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pSend, U32 Len);
|
||||
EXTERN_C U32 VCI_GetLINReceiveNum(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ClearLINBuffer(U32 Type, U32 Card, U32 LinChn);
|
||||
EXTERN_C U32 VCI_ReceiveLIN(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_MSG pReceive, U32 Len,int WaitTime);
|
||||
EXTERN_C U32 VCI_SetLINSubscribe(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_SUBSCIBE_CFG pSend, U32 nSubscribeCount);
|
||||
EXTERN_C U32 VCI_SetLINPublish(U32 Type, U32 Card, U32 LinChn, PZCAN_LIN_PUBLISH_CFG pSend, U32 nPublishCount);
|
||||
|
||||
// 合并接收
|
||||
EXTERN_C U32 VCI_TransmitData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count);
|
||||
EXTERN_C U32 VCI_ReceiveData(unsigned Type, unsigned Card, unsigned Port, ZCANDataObj *pData, unsigned Count, unsigned Time);
|
||||
|
||||
// UDS
|
||||
EXTERN_C U32 VCI_UDS_Request(unsigned Type, unsigned Card, const ZCAN_UDS_REQUEST *req, ZCAN_UDS_RESPONSE *resp, U8 *dataBuf, U32 dataBufSize);
|
||||
EXTERN_C U32 VCI_UDS_Control(unsigned Type, unsigned Card, const ZCAN_UDS_CTRL_REQ *ctrl, ZCAN_UDS_CTRL_RESP *resp);
|
||||
|
||||
+14
-9
@@ -1,23 +1,28 @@
|
||||
# Orin 惰性推理运行包(C++)
|
||||
# Orin 纯 C++ 运行包
|
||||
|
||||
相机每次等待默认 2 秒,连续 6 次超时才退出(约 12 秒);有效帧恢复后重新计数。Ctrl+C / SIGTERM 主动停止不需要等待 6 次超时。
|
||||
|
||||
|
||||
在 JetPack 5.1.2 / TensorRT 8.5 / CUDA 11.4 / OpenCV 4.5 环境执行:
|
||||
适用 AGX Orin / JetPack 5.1.2 (R35.4.1)、CUDA 11.4、TensorRT 8.5.x、OpenCV 4.5.4。不依赖 Python。
|
||||
|
||||
```bash
|
||||
./scripts/initialize_camera.sh # 摄像头未初始化时
|
||||
./run.sh
|
||||
./run.sh --visualize # 可选窗口
|
||||
sha256sum -c SHA256SUMS
|
||||
./scripts/initialize_camera.sh # 开机后无 /dev/video5 时执行,需要 sudo
|
||||
./run.sh # exec build/camera_risk,默认无可视化
|
||||
./run.sh --visualize # 可选 Orin 本地图形窗口
|
||||
# 直接执行 ELF:
|
||||
./build/camera_risk --config config/camera.json
|
||||
```
|
||||
|
||||
`build/camera_risk` 为 ARM64 ELF 程序,无 Python 运行依赖。
|
||||
只运行一个摄像头进程。Ctrl+C / SIGTERM 停止;关闭可视化窗口不停止推理。无图形桌面时继续无窗口运行。
|
||||
|
||||
CAN 0x391 的 data[0..3] 为左前、右前、左后、右后百分比。左前或左后开度 <15% 且正在增加或减小时推理;其余有效 CAN 状态暂停。CAN 缺失或超时 1000 ms 时推理,恢复后重新判断。
|
||||
CAN 0x391 data[0..3] 为左前、右前、左后、右后。默认左侧,`--can-side right` 切换右侧。缺失或超时报文输出 CAN_UNKNOWN,风险分数为空。
|
||||
|
||||
模型只加载预热一次并常驻;相机和 CAN 始终运行。重复相同开度保留运动方向 300 ms,之后视为静止。参数见 config/camera.json 的 lazy_inference。默认结果保存在 C++ 内存,不写逐帧文件;暂停时 latest() 为空。标准输出记录启停状态与累计模型运行次数。
|
||||
`--no-can --active-door both` 模拟门状态;`--duration 30` 限时运行;`--save-results FILE.jsonl` 显式保存结果,默认不写文件。完整参数见 `./run.sh --help`。
|
||||
|
||||
文件完整性:`sha256sum -c SHA256SUMS`。
|
||||
最新结果保存在进程内的 `shared_ptr<const cms::FrameResult>`;C++ 下游调用 `pipeline.latest()` 读取。接口头文件位于仓库 include/,不是 Python 变量或跨进程共享内存。
|
||||
|
||||
模型、驱动和编译产物绑定目标 GPU、TensorRT 与内核环境。标定文件需匹配实际机位,默认 calibration_verified=false。系统 CUDA/TensorRT/OpenCV 库由 JetPack 提供。
|
||||
|
||||
## 可选保存(默认全部关闭)
|
||||
|
||||
|
||||
+10
-8
@@ -1,13 +1,15 @@
|
||||
b0110852effcec51d5e6b74f32a37fbb22997d6fa4f9f1e2a5dbc7cade00ab7b ./README.md
|
||||
836d9b85249866642f7f3ca7cfe4eec092f29fd3b44bd9fdbe1c2ba3ca3d68b0 ./README.md
|
||||
9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./annotations/constant_door_mask.json
|
||||
3544fb1c7b1b27b39db27d8f849d5e8652f5d09a950a59c5dd0c7703086b4f6b ./annotations/door_zone_multifeature.json
|
||||
6d5e874fd27fcfd75d303d7e35d9672028af71041f52dcc82a1e83676faefa65 ./bin/drivers/max9295.ko
|
||||
8a78d04ef8be44e9b62a5e4ee315d3d6d679e34aedff02a234bbf4d40e222933 ./bin/drivers/max9296.ko
|
||||
3da628587477cc35d5e3f8aee86e718e2ddf16808e893e00ba6e164b2307e843 ./bin/drivers/sgx-yuv-gmsl2.ko
|
||||
0d77d17626644fb50b663c33edbb1a09884b34b5410a1109fc56b4d3781651b1 ./build/camera_risk
|
||||
951c514ee76e10233a84746f25a884e0a1e8cbed84df57845a5aff66af9aecbd ./config/camera.json
|
||||
9ba768d559fd3b64e833b50c62c70f2adfd6c169620aea5fba32cfb1b4c5b0e9 ./config/door_mask.json
|
||||
ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./engines/rtmw_l_384.engine
|
||||
f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./engines/yolo11s.engine
|
||||
a2616e193ef9f006b49b4bb4ac43ae3ffc4cee1f1edf6d58ed6f7f9e5f933ad1 ./build/camera_risk
|
||||
718c43eba7c9a86178b272404b14e201445a3998dae81cd32b840f83018ba2b5 ./config/camera.json
|
||||
8dae8c557deadbe6bd7241c0dcd7025cb007077ee39a79b115e953a2b22cd9c1 ./lib/libusb-1.0.so
|
||||
ab8b7f7249d5fb73d1d33d14fa7532a522088ffb6c25f37ba334daf4c6b4d313 ./lib/libusbcanfd.so
|
||||
a0b083ad029fd6430941f5aff472150c441f7d5c4b6d7f201ed1999f0f86095a ./run.sh
|
||||
9122cd4ed17fb26da1cb3f9456f923508430a518eb346c5038919d4475d8d2e8 ./scripts/initialize_camera.sh
|
||||
ebcd09e8c2e35008adbb0c1d222e580b17e0df1c8093df3481ba3374cd9a8174 ./models/engines/rtmw_l_384.engine
|
||||
f6a65b926ac1144484f4aa93cd26d7997d32d62310ab93fe867f2bd4412222e9 ./models/engines/yolo11s.engine
|
||||
4e3bd58314a9d70b6b243f14c0e26ffd1b1c6dcf8bcbf4c44629f1767f72a5ec ./run.sh
|
||||
11b5fbc3c17c0610ec0514a0a26cf5d92588d15c0e50e7086d9db5a5c0306e99 ./scripts/initialize_camera.sh
|
||||
bce5a22d49633454ed2c6bc92b608edf3b718af2424c283ed8f56ba93987200a ./scripts/initialize_camera_impl.sh
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": "v9.10-research-calibration-1",
|
||||
"description": "Temporary-camera calibration from seg_01 and seg_08; recalibrate for the production camera.",
|
||||
"front_probability_min": 0.72,
|
||||
"rear_probability_max": 0.28,
|
||||
"minimum_person_height": 0.4,
|
||||
"minimum_pose_features": 4,
|
||||
"history_frames": 5,
|
||||
"stale_track_frames": 120,
|
||||
"features": {
|
||||
"shoulder_center_x": {
|
||||
"rear": 0.53,
|
||||
"front": 0.64,
|
||||
"weight": 1.2
|
||||
},
|
||||
"shoulder_center_y": {
|
||||
"rear": 0.54,
|
||||
"front": 0.46,
|
||||
"weight": 1.3
|
||||
},
|
||||
"shoulder_width": {
|
||||
"rear": 0.14,
|
||||
"front": 0.2,
|
||||
"weight": 1.4
|
||||
},
|
||||
"torso_height": {
|
||||
"rear": 0.35,
|
||||
"front": 0.47,
|
||||
"weight": 1.5
|
||||
},
|
||||
"hip_center_y": {
|
||||
"rear": 0.89,
|
||||
"front": 0.93,
|
||||
"weight": 0.6
|
||||
},
|
||||
"box_center_x": {
|
||||
"rear": 0.48,
|
||||
"front": 0.58,
|
||||
"weight": 0.35
|
||||
},
|
||||
"box_height": {
|
||||
"rear": 0.7,
|
||||
"front": 0.82,
|
||||
"weight": 0.45
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
+4
-25
@@ -1,27 +1,6 @@
|
||||
{
|
||||
"camera": {
|
||||
"device": "/dev/video5",
|
||||
"width": 1920,
|
||||
"height": 1536,
|
||||
"fps": 30,
|
||||
"buffers": 4,
|
||||
"timeout_ms": 2000
|
||||
},
|
||||
"inference": {
|
||||
"engine_directory": "../engines",
|
||||
"detector_confidence": 0.3,
|
||||
"max_people": 3,
|
||||
"queue_capacity": 2
|
||||
},
|
||||
"door": {
|
||||
"mask": "door_mask.json",
|
||||
"warning_relative": 0.3,
|
||||
"release_relative": 0.45,
|
||||
"calibration_verified": false
|
||||
},
|
||||
"lazy_inference": {
|
||||
"threshold_percent": 15,
|
||||
"can_timeout_ms": 1000,
|
||||
"motion_timeout_ms": 300
|
||||
}
|
||||
"camera": {"device":"/dev/video5","width":1920,"height":1536,"fps":30,"buffers":4,"timeout_ms":2000},
|
||||
"inference": {"engine_directory":"../models/engines","detector_confidence":0.30,"max_people":3,"queue_capacity":2},
|
||||
"door": {"mask":"../annotations/constant_door_mask.json","zone_config":"../annotations/door_zone_multifeature.json","warning_relative":0.30,"release_relative":0.45,"calibration_verified":false},
|
||||
"can": {"enabled":true,"side":"left","timeout_ms":1000}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root="$(cd "$(dirname "$0")" && pwd)"
|
||||
export LD_LIBRARY_PATH="$root/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
exec "$root/build/camera_risk" --config "$root/config/camera.json" "$@"
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
device=/dev/video5
|
||||
loaded() { test -d "/sys/module/$1"; }
|
||||
root_command() { if [[ $EUID -eq 0 ]]; then "$@"; else sudo -n "$@"; fi; }
|
||||
if [[ -c "$device" ]] && loaded max9295 && loaded max9296 && loaded sgx_yuv_gmsl2; then
|
||||
echo "Camera already initialized: $device"
|
||||
exit 0
|
||||
fi
|
||||
for driver in max9295 max9296 sgx-yuv-gmsl2; do
|
||||
file="$root/bin/drivers/$driver.ko"
|
||||
[[ -f "$file" ]] || { echo "Missing driver: $file" >&2; exit 1; }
|
||||
vermagic="$(modinfo -F vermagic "$file")"
|
||||
# Some supplied drivers omit this metadata; normal insmod still validates them.
|
||||
[[ -z "$vermagic" || "$vermagic" == "$(uname -r) "* ]] || { echo "Kernel mismatch: $file" >&2; exit 1; }
|
||||
done
|
||||
loaded max9295 || root_command insmod "$root/bin/drivers/max9295.ko"
|
||||
loaded max9296 || root_command insmod "$root/bin/drivers/max9296.ko"
|
||||
loaded sgx_yuv_gmsl2 || root_command insmod "$root/bin/drivers/sgx-yuv-gmsl2.ko" gmsl_mode=2,2,2,2
|
||||
for _ in {1..20}; do [[ -c "$device" ]] && break; sleep 0.25; done
|
||||
[[ -c "$device" ]] || { echo "$device was not created; verify camera/DTB" >&2; exit 1; }
|
||||
v4l2-ctl -d "$device" -c sensor_mode=1,trig_pin=0xffff0007
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
export DRIVER_DIR="$root/bin/drivers"
|
||||
exec "$root/scripts/initialize_camera_impl.sh" "$@"
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Initialize the camera using the packaged Orin drivers.
|
||||
set -euo pipefail
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
driver_dir="${DRIVER_DIR:-$root/orin/bin/drivers}"
|
||||
device="${1:-/dev/video5}"
|
||||
kernel="$(uname -r)"
|
||||
for driver in max9295 max9296 sgx-yuv-gmsl2; do
|
||||
module="${driver//-/_}"
|
||||
[[ -d "/sys/module/$module" ]] && continue
|
||||
file="$driver_dir/$driver.ko"
|
||||
[[ -f "$file" ]] || { echo "Missing driver: $file" >&2; exit 1; }
|
||||
vermagic="$(modinfo -F vermagic "$file")"
|
||||
if [[ -n "$vermagic" && "${vermagic%% *}" != "$kernel" ]]; then
|
||||
echo "Kernel mismatch: $file ($vermagic), running $kernel" >&2; exit 1
|
||||
fi
|
||||
if [[ -z "$vermagic" ]]; then
|
||||
echo "No vermagic metadata: $file; using normal insmod kernel validation."
|
||||
fi
|
||||
options=()
|
||||
[[ "$driver" == sgx-yuv-gmsl2 ]] && options=(gmsl_mode=2,2,2,2)
|
||||
sudo -n insmod "$file" "${options[@]}"
|
||||
done
|
||||
for _ in {1..20}; do [[ -c "$device" ]] && break; sleep .25; done
|
||||
[[ -c "$device" ]] || { echo "Camera not created: $device" >&2; exit 1; }
|
||||
v4l2-ctl -d "$device" -c sensor_mode=1,trig_pin=0xffff0007
|
||||
echo "Camera ready: $device"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
root="$(cd "$(dirname "$0")" && pwd)"
|
||||
export LD_LIBRARY_PATH="$root/orin/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
exec "$root/build/camera_risk" --config "$root/config/camera.json" "$@"
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cmake_bin="${CMAKE_COMMAND:-cmake}"
|
||||
if [[ -x "$HOME/miniconda3/envs/yolo/bin/cmake" ]]; then cmake_bin="${CMAKE_COMMAND:-$HOME/miniconda3/envs/yolo/bin/cmake}"; fi
|
||||
"$cmake_bin" -S "$root" -B "$root/build" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_COMPILER="${CUDACXX:-/usr/local/cuda/bin/nvcc}" "$@"
|
||||
"$cmake_bin" -S "$root" -B "$root/build" -DCMAKE_BUILD_TYPE=Release -DCMAKE_CUDA_COMPILER="${CUDACXX:-/usr/local/cuda/bin/nvcc}"
|
||||
"$cmake_bin" --build "$root/build" -j4
|
||||
|
||||
@@ -1,4 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Initialize the camera using the packaged Orin drivers.
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
exec "$root/orin/scripts/initialize_camera.sh" "$@"
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
driver_dir="${DRIVER_DIR:-$root/orin/bin/drivers}"
|
||||
device="${1:-/dev/video5}"
|
||||
kernel="$(uname -r)"
|
||||
for driver in max9295 max9296 sgx-yuv-gmsl2; do
|
||||
module="${driver//-/_}"
|
||||
[[ -d "/sys/module/$module" ]] && continue
|
||||
file="$driver_dir/$driver.ko"
|
||||
[[ -f "$file" ]] || { echo "Missing driver: $file" >&2; exit 1; }
|
||||
vermagic="$(modinfo -F vermagic "$file")"
|
||||
if [[ -n "$vermagic" && "${vermagic%% *}" != "$kernel" ]]; then
|
||||
echo "Kernel mismatch: $file ($vermagic), running $kernel" >&2; exit 1
|
||||
fi
|
||||
if [[ -z "$vermagic" ]]; then
|
||||
echo "No vermagic metadata: $file; using normal insmod kernel validation."
|
||||
fi
|
||||
options=()
|
||||
[[ "$driver" == sgx-yuv-gmsl2 ]] && options=(gmsl_mode=2,2,2,2)
|
||||
sudo -n insmod "$file" "${options[@]}"
|
||||
done
|
||||
for _ in {1..20}; do [[ -c "$device" ]] && break; sleep .25; done
|
||||
[[ -c "$device" ]] || { echo "Camera not created: $device" >&2; exit 1; }
|
||||
v4l2-ctl -d "$device" -c sensor_mode=1,trig_pin=0xffff0007
|
||||
echo "Camera ready: $device"
|
||||
|
||||
+11
-5
@@ -1,12 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Assemble the C++ runtime; no Python interpreter is used for packaging or execution.
|
||||
set -euo pipefail
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
[[ -x "$root/build/camera_risk" ]] || { echo 'Build on Orin first.' >&2; exit 1; }
|
||||
cp -p "$root/build/camera_risk" "$root/orin/build/"
|
||||
cp -p "$root/config/door_mask.json" "$root/orin/config/"
|
||||
sed 's|../orin/engines|../engines|' "$root/config/camera.json" > "$root/orin/config/camera.json"
|
||||
package="$root/orin"
|
||||
[[ -x "$root/build/camera_risk" ]] || { echo 'Compile on Orin with scripts/build.sh first.' >&2; exit 1; }
|
||||
mkdir -p "$package/build" "$package/config" "$package/annotations" "$package/scripts"
|
||||
cp -p "$root/build/camera_risk" "$package/build/"
|
||||
cp -p "$root/annotations/"*.json "$package/annotations/"
|
||||
# Source and standalone configurations differ only in the engine directory.
|
||||
sed 's|../orin/models/engines|../models/engines|' "$root/config/camera.json" > "$package/config/camera.json"
|
||||
cp -p "$root/scripts/initialize_camera.sh" "$package/scripts/initialize_camera_impl.sh"
|
||||
(
|
||||
cd "$root/orin"
|
||||
cd "$package"
|
||||
find . -type f ! -path './output/*' ! -path './outputs/*' ! -path './logs/*' ! -name SHA256SUMS -print0 |
|
||||
sort -z | xargs -0 sha256sum > SHA256SUMS
|
||||
)
|
||||
echo "C++ runtime ready: $package/build/camera_risk"
|
||||
|
||||
+60
-160
@@ -1,190 +1,90 @@
|
||||
#include "camera.hpp"
|
||||
#include "v4l2_capture.h"
|
||||
#include "uyvy_converter.hpp"
|
||||
#include <linux/videodev2.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <poll.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <thread>
|
||||
namespace cms {
|
||||
std::uint64_t monotonicNs() {
|
||||
timespec time{};
|
||||
if(clock_gettime(CLOCK_MONOTONIC,&time)!=0)throw std::runtime_error("clock_gettime failed");
|
||||
return std::uint64_t(time.tv_sec)*1000000000ULL+time.tv_nsec;
|
||||
}
|
||||
namespace {
|
||||
void checkedIoctl(int fd,unsigned long request,void* argument,char const* operation) {
|
||||
int status;
|
||||
do {status=ioctl(fd,request,argument);} while(status<0&&errno==EINTR);
|
||||
if(status<0)throw std::runtime_error(std::string(operation)+": "+std::strerror(errno));
|
||||
}
|
||||
}
|
||||
std::uint64_t monotonicNs(){return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();}
|
||||
struct Camera::Impl {
|
||||
struct Buffer {void* data{};std::size_t size{};};
|
||||
CameraConfig config;
|
||||
int fd{-1},stopFd{-1};bool streaming{};
|
||||
std::uint32_t stride{},lastSequence{};bool haveSequence{};
|
||||
std::uint64_t index{};
|
||||
std::uint32_t consecutiveErrors{};
|
||||
std::atomic<bool> stopped{false};
|
||||
std::vector<Buffer> buffers;
|
||||
V4l2Capture capture;
|
||||
cv::VideoCapture video;
|
||||
std::unique_ptr<UyvyConverter> converter;
|
||||
std::atomic<bool> stopped{false};
|
||||
std::uint64_t index{};
|
||||
std::uint32_t previousSequence{};
|
||||
bool haveSequence{};
|
||||
explicit Impl(CameraConfig c):config(std::move(c)) {
|
||||
if(config.width<=0||config.width>8192||config.height<=0||config.height>8192||config.width%2||config.fps<=0||config.fps>240||config.buffers<2||config.buffers>32||config.timeout_ms<1)
|
||||
throw std::invalid_argument("Invalid camera configuration");
|
||||
try {
|
||||
stopFd=eventfd(0,EFD_CLOEXEC|EFD_NONBLOCK);
|
||||
if(stopFd<0)throw std::runtime_error("Cannot create camera stop event");
|
||||
if(!config.video.empty()) {
|
||||
if(!video.open(config.video))throw std::runtime_error("Cannot open video: "+config.video);
|
||||
if(int(video.get(cv::CAP_PROP_FRAME_WIDTH))!=config.width||int(video.get(cv::CAP_PROP_FRAME_HEIGHT))!=config.height)throw std::runtime_error("Video dimensions must match camera configuration");
|
||||
} else {
|
||||
openCapture();
|
||||
} catch(...) {release();throw;}
|
||||
}
|
||||
}
|
||||
void openCapture() {
|
||||
try {
|
||||
fd=open(config.device.c_str(),O_RDWR|O_NONBLOCK|O_CLOEXEC);
|
||||
if(fd<0)throw std::runtime_error("Cannot open "+config.device+": "+std::strerror(errno));
|
||||
v4l2_capability capability{};checkedIoctl(fd,VIDIOC_QUERYCAP,&capability,"VIDIOC_QUERYCAP");
|
||||
auto caps=(capability.capabilities&V4L2_CAP_DEVICE_CAPS)?capability.device_caps:capability.capabilities;
|
||||
if(!(caps&V4L2_CAP_VIDEO_CAPTURE)||!(caps&V4L2_CAP_STREAMING))throw std::runtime_error("Camera does not support single-plane streaming capture");
|
||||
v4l2_format format{};format.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
format.fmt.pix.width=config.width;format.fmt.pix.height=config.height;
|
||||
format.fmt.pix.pixelformat=V4L2_PIX_FMT_UYVY;format.fmt.pix.field=V4L2_FIELD_NONE;
|
||||
checkedIoctl(fd,VIDIOC_S_FMT,&format,"VIDIOC_S_FMT");
|
||||
if(format.fmt.pix.pixelformat!=V4L2_PIX_FMT_UYVY||format.fmt.pix.width!=static_cast<unsigned>(config.width)||format.fmt.pix.height!=static_cast<unsigned>(config.height))
|
||||
throw std::runtime_error("Camera rejected the configured UYVY resolution; refusing mismatched mask coordinates");
|
||||
stride=format.fmt.pix.bytesperline;
|
||||
if(stride<static_cast<unsigned>(config.width*2))throw std::runtime_error("Invalid camera row stride");
|
||||
v4l2_streamparm params{};params.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if(ioctl(fd,VIDIOC_G_PARM,¶ms)==0&&(params.parm.capture.capability&V4L2_CAP_TIMEPERFRAME)) {
|
||||
params.parm.capture.timeperframe.numerator=1;params.parm.capture.timeperframe.denominator=config.fps;
|
||||
checkedIoctl(fd,VIDIOC_S_PARM,¶ms,"VIDIOC_S_PARM");
|
||||
auto const& interval=params.parm.capture.timeperframe;
|
||||
if(!interval.numerator||interval.denominator!=static_cast<unsigned>(config.fps)*interval.numerator)
|
||||
throw std::runtime_error("Camera rejected requested frame rate");
|
||||
}
|
||||
v4l2_requestbuffers request{};request.count=config.buffers;
|
||||
request.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;request.memory=V4L2_MEMORY_MMAP;
|
||||
checkedIoctl(fd,VIDIOC_REQBUFS,&request,"VIDIOC_REQBUFS");
|
||||
if(request.count<2)throw std::runtime_error("Insufficient V4L2 buffers");
|
||||
buffers.resize(request.count);
|
||||
for(unsigned i=0;i<request.count;++i) {
|
||||
v4l2_buffer buffer{};buffer.type=request.type;buffer.memory=request.memory;buffer.index=i;
|
||||
checkedIoctl(fd,VIDIOC_QUERYBUF,&buffer,"VIDIOC_QUERYBUF");
|
||||
if(buffer.length<std::size_t(stride)*config.height)throw std::runtime_error("Camera buffer smaller than negotiated frame");
|
||||
buffers[i].size=buffer.length;
|
||||
auto mapping=mmap(nullptr,buffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buffer.m.offset);
|
||||
if(mapping==MAP_FAILED)throw std::runtime_error("Camera mmap failed");
|
||||
buffers[i].data=mapping;checkedIoctl(fd,VIDIOC_QBUF,&buffer,"VIDIOC_QBUF");
|
||||
}
|
||||
if(!capture.open(config.device)||!capture.setFormat(config.width,config.height,V4L2_PIX_FMT_UYVY))throw std::runtime_error("Camera open/format failed");
|
||||
if(capture.width()!=unsigned(config.width)||capture.height()!=unsigned(config.height))throw std::runtime_error("Unexpected negotiated camera dimensions");
|
||||
capture.setFps(config.fps);
|
||||
if(!capture.initMmap(config.buffers)||!capture.startStreaming())throw std::runtime_error("Camera streaming failed");
|
||||
if(!converter)converter=std::make_unique<UyvyConverter>(config.width,config.height);
|
||||
auto type=V4L2_BUF_TYPE_VIDEO_CAPTURE;checkedIoctl(fd,VIDIOC_STREAMON,&type,"VIDIOC_STREAMON");streaming=true;
|
||||
} catch(...) {releaseCapture();throw;}
|
||||
}catch(...){capture.close();throw;}
|
||||
}
|
||||
~Impl(){release();}
|
||||
void releaseCapture() noexcept {
|
||||
if(streaming){auto type=V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(fd,VIDIOC_STREAMOFF,&type);streaming=false;}
|
||||
for(auto const& b:buffers)if(b.data)munmap(b.data,b.size);
|
||||
buffers.clear();
|
||||
if(fd>=0){close(fd);fd=-1;}
|
||||
}
|
||||
void release() noexcept {
|
||||
releaseCapture();
|
||||
if(stopFd>=0){close(stopFd);stopFd=-1;}
|
||||
}
|
||||
void stop() noexcept {
|
||||
stopped=true;std::uint64_t one=1;
|
||||
if(stopFd>=0){auto ignored=write(stopFd,&one,sizeof(one));(void)ignored;}
|
||||
}
|
||||
std::optional<CapturedFrame> readFrame() {
|
||||
auto windowStart=monotonicNs();unsigned failedWindows=0;
|
||||
auto windowNs=std::uint64_t(config.timeout_ms)*1000000;
|
||||
std::optional<CapturedFrame> read() {
|
||||
if(stopped)return {};
|
||||
if(video.isOpened()) {
|
||||
cv::Mat bgr;if(!video.read(bgr))return {};
|
||||
auto image=std::make_shared<BgrImage>();image->width=bgr.cols;image->height=bgr.rows;image->stride=bgr.cols*3;
|
||||
image->pixels.resize(std::size_t(image->stride)*image->height);
|
||||
for(int y=0;y<bgr.rows;++y)std::memcpy(image->pixels.data()+y*image->stride,bgr.ptr(y),image->stride);
|
||||
CaptureMetadata m{};m.frame_index=index;m.driver_sequence=index++;m.capture_monotonic_ns=m.received_monotonic_ns=monotonicNs();return CapturedFrame{m,image};
|
||||
}
|
||||
auto started=monotonicNs();unsigned misses=0,failedWindows=0;
|
||||
bool reconnect=false;
|
||||
std::string lastError="no camera frame";
|
||||
auto expired=[&](bool timedOut=false) {
|
||||
while(!stopped) {
|
||||
auto now=monotonicNs();
|
||||
if(timedOut||now-windowStart>=windowNs) {
|
||||
if((now-started)*1e-6>=config.timeout_ms) {
|
||||
if(++failedWindows>=6)
|
||||
throw std::runtime_error("Camera capture failed 6 consecutive times: "+config.device+": "+lastError);
|
||||
windowStart=now;
|
||||
started=now;
|
||||
}
|
||||
};
|
||||
auto pause=[&] {
|
||||
pollfd stopEvent{stopFd,POLLIN,0};
|
||||
// Back off transient driver errors while retaining immediate cancellation.
|
||||
poll(&stopEvent,1,20);
|
||||
};
|
||||
auto reconnect=[&] {
|
||||
releaseCapture();
|
||||
if(haveSequence){++index;haveSequence=false;}
|
||||
};
|
||||
while(!stopped.load()) {
|
||||
expired();
|
||||
if(fd<0) {
|
||||
try {openCapture();}
|
||||
catch(std::exception const& e){lastError=e.what();pause();continue;}
|
||||
if(reconnect) {
|
||||
try {openCapture();reconnect=false;haveSequence=false;}
|
||||
catch(std::exception const& e){lastError=e.what();std::this_thread::sleep_for(std::chrono::milliseconds(20));continue;}
|
||||
}
|
||||
if(stopped.load())return {};
|
||||
auto elapsed=monotonicNs()-windowStart;
|
||||
auto remaining=elapsed<windowNs?windowNs-elapsed:1;
|
||||
int waitMs=static_cast<int>((remaining+999999)/1000000);
|
||||
pollfd descriptors[2]={{fd,POLLIN,0},{stopFd,POLLIN,0}};
|
||||
int ready=poll(descriptors,2,waitMs);
|
||||
if(ready<0&&errno==EINTR)continue;
|
||||
if(stopped.load()||(descriptors[1].revents&POLLIN))return {};
|
||||
if(ready==0){expired(true);continue;}
|
||||
if(ready<0||(descriptors[0].revents&(POLLERR|POLLHUP|POLLNVAL))) {
|
||||
lastError="camera disconnected or poll failed";reconnect();pause();continue;
|
||||
V4l2Capture::Frame f;
|
||||
if(!capture.getFrame(f,100)) {
|
||||
if(stopped)return {};
|
||||
++misses;
|
||||
if(capture.needsReconnect()){lastError="camera device unavailable";capture.close();reconnect=true;}
|
||||
// Avoid spinning when the driver immediately reports an error.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
v4l2_buffer buffer{};buffer.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;buffer.memory=V4L2_MEMORY_MMAP;
|
||||
int status=ioctl(fd,VIDIOC_DQBUF,&buffer);
|
||||
if(status<0&&(errno==EAGAIN||errno==EINTR))continue;
|
||||
if(status<0) {
|
||||
lastError="VIDIOC_DQBUF: "+std::string(std::strerror(errno));
|
||||
// EIO can be a transient corrupt frame; a lost device needs reopening.
|
||||
if(errno!=EIO)reconnect();
|
||||
pause();continue;
|
||||
auto stride=f.bytes_per_line?f.bytes_per_line:unsigned(config.width*2);
|
||||
if(f.size<std::size_t(stride)*config.height) {
|
||||
lastError="incomplete camera frame";++misses;capture.releaseFrame(f);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;
|
||||
}
|
||||
if(buffer.index>=buffers.size()){lastError="invalid V4L2 buffer index";reconnect();pause();continue;}
|
||||
bool invalid=(buffer.flags&V4L2_BUF_FLAG_ERROR)||buffer.bytesused<std::size_t(stride)*config.height||buffer.bytesused>buffers[buffer.index].size;
|
||||
if(invalid) {
|
||||
++consecutiveErrors;lastError="corrupt or incomplete camera frame";
|
||||
if(ioctl(fd,VIDIOC_QBUF,&buffer)<0)reconnect();
|
||||
pause();continue;
|
||||
}
|
||||
CapturedFrame frame;
|
||||
auto& m=frame.metadata;
|
||||
m.discarded_error_frames=consecutiveErrors;
|
||||
m.driver_sequence=buffer.sequence;m.received_monotonic_ns=monotonicNs();
|
||||
m.driver_timestamp_ns=std::uint64_t(buffer.timestamp.tv_sec)*1000000000ULL+std::uint64_t(buffer.timestamp.tv_usec)*1000;
|
||||
m.driver_timestamp_monotonic=(buffer.flags&V4L2_BUF_FLAG_TIMESTAMP_MASK)==V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
|
||||
m.capture_monotonic_ns=m.received_monotonic_ns;
|
||||
if(haveSequence) {
|
||||
auto delta=static_cast<std::uint32_t>(buffer.sequence-lastSequence);
|
||||
if(delta==0||delta>0x7fffffffU) {
|
||||
lastError="camera sequence reset or repeated frame";
|
||||
reconnect();pause();continue;
|
||||
}
|
||||
m.skipped_driver_frames=delta-1;index+=delta;
|
||||
}
|
||||
m.frame_index=index;
|
||||
// GPU failures remain fatal; only capture failures consume the retry budget.
|
||||
try {frame.image=converter->convert(buffers[buffer.index].data,stride);}
|
||||
catch(...) {ioctl(fd,VIDIOC_QBUF,&buffer);throw;}
|
||||
if(ioctl(fd,VIDIOC_QBUF,&buffer)<0){lastError="VIDIOC_QBUF failed";reconnect();pause();continue;}
|
||||
lastSequence=buffer.sequence;haveSequence=true;consecutiveErrors=0;
|
||||
return frame; // The next read starts a fresh six-window retry budget.
|
||||
CaptureMetadata m{};m.frame_index=index++;m.driver_sequence=f.sequence;
|
||||
m.received_monotonic_ns=m.capture_monotonic_ns=monotonicNs();m.driver_timestamp_ns=f.timestamp_us*1000;m.discarded_error_frames=misses;
|
||||
if(haveSequence){auto delta=std::uint32_t(f.sequence-previousSequence);if(delta>0&&delta<0x80000000U)m.skipped_driver_frames=delta-1;}
|
||||
previousSequence=f.sequence;haveSequence=true;
|
||||
try {
|
||||
auto image=converter->convert(f.data,stride);capture.releaseFrame(f);return CapturedFrame{m,std::move(image)};
|
||||
}catch(...){capture.releaseFrame(f);throw;}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
Camera::Camera(CameraConfig config):impl_(std::make_unique<Impl>(std::move(config))){}
|
||||
Camera::Camera(CameraConfig c):impl_(std::make_unique<Impl>(std::move(c))){}
|
||||
Camera::~Camera()=default;
|
||||
std::optional<CapturedFrame> Camera::read(){return impl_->readFrame();}
|
||||
void Camera::requestStop() noexcept {impl_->stop();}
|
||||
std::optional<CapturedFrame> Camera::read(){return impl_->read();}
|
||||
void Camera::requestStop()noexcept{impl_->stopped=true;}
|
||||
}
|
||||
|
||||
+48
-49
@@ -5,7 +5,10 @@
|
||||
#include "door_risk.hpp"
|
||||
#include "records.hpp"
|
||||
#include "result_conversion.hpp"
|
||||
#include "can_input.hpp"
|
||||
#include "door_zone.hpp"
|
||||
#include "can_state.hpp"
|
||||
#include "serialization.hpp"
|
||||
#include <fstream>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
@@ -46,10 +49,9 @@ void attachPoses(FrameResult& result,fangjia::PoseFrameResult const& fused) {
|
||||
}
|
||||
}
|
||||
struct CameraPipeline::Impl {
|
||||
struct Pending {CapturedFrame frame;std::uint64_t generation;};
|
||||
struct Detected {CapturedFrame frame;std::vector<fangjia::Box> boxes;double elapsed_ms{};std::uint64_t generation;};
|
||||
struct Detected {CapturedFrame frame;std::vector<fangjia::Box> boxes;double elapsed_ms{};};
|
||||
PipelineConfig config;
|
||||
BoundedQueue<Pending> captures;
|
||||
BoundedQueue<CapturedFrame> captures;
|
||||
BoundedQueue<Detected> detections;
|
||||
LatestResult results;
|
||||
std::unique_ptr<OutputWriter> outputWriter;
|
||||
@@ -57,11 +59,11 @@ struct CameraPipeline::Impl {
|
||||
std::unique_ptr<Detector> detector;
|
||||
std::unique_ptr<fangjia::RtmwEngine> pose;
|
||||
std::unique_ptr<DoorRisk> risk;
|
||||
std::unique_ptr<DoorZoneAssigner> zone;
|
||||
std::unique_ptr<CanInput> can;
|
||||
std::ofstream output, poseOutput;
|
||||
std::shared_ptr<const BinaryMask> doorPixels;
|
||||
std::unique_ptr<Camera> camera;
|
||||
std::unique_ptr<CanInput> can;
|
||||
std::atomic<bool> modelsReady{false};
|
||||
std::atomic<std::uint64_t> detectorRuns{0},poseRuns{0},skippedIdle{0};
|
||||
std::thread captureThread,detectorThread,inferenceThread;
|
||||
mutable std::mutex stateMutex;
|
||||
std::exception_ptr failure;
|
||||
@@ -71,7 +73,10 @@ struct CameraPipeline::Impl {
|
||||
double totalLatency{};
|
||||
|
||||
explicit Impl(PipelineConfig c):config(std::move(c)),captures(config.queue_capacity),detections(config.queue_capacity) {
|
||||
LazyGate validateGate(config.lazy_gate);
|
||||
if(config.can_side!="left"&&config.can_side!="right")throw std::invalid_argument("CAN side must be left or right");
|
||||
if(config.active_door!="can"&&config.active_door!="front"&&config.active_door!="rear"&&config.active_door!="both"&&config.active_door!="none")throw std::invalid_argument("Invalid active door");
|
||||
if(!config.can_enabled&&config.active_door=="can")throw std::invalid_argument("Disabled CAN requires an explicit simulated active door");
|
||||
if(config.can_timeout_ms<=0)throw std::invalid_argument("CAN timeout must be positive");
|
||||
if(config.queue_capacity>8)throw std::invalid_argument("Queue capacity must be between 1 and 8");
|
||||
if(config.max_people<1||config.max_people>16||!std::isfinite(config.detector_confidence)||config.detector_confidence<=0||config.detector_confidence>=1)
|
||||
throw std::invalid_argument("Invalid detector configuration");
|
||||
@@ -84,7 +89,7 @@ struct CameraPipeline::Impl {
|
||||
if(inferenceThread.joinable())inferenceThread.join();
|
||||
}
|
||||
void stop() noexcept {
|
||||
stopped=true;active=false;results.clear();
|
||||
stopped=true;active=false;
|
||||
// Camera stays alive until all threads have joined. start()/destruction
|
||||
// are owner-thread operations; requestStop() only interrupts existing work.
|
||||
{std::lock_guard<std::mutex> lock(stateMutex);if(camera)camera->requestStop();}
|
||||
@@ -110,108 +115,102 @@ struct CameraPipeline::Impl {
|
||||
auto const& mask=risk->mask();pixels->pixels.resize(std::size_t(mask.cols)*mask.rows);
|
||||
for(int y=0;y<mask.rows;++y)std::memcpy(pixels->pixels.data()+std::size_t(y)*mask.cols,mask.ptr(y),mask.cols);
|
||||
doorPixels=std::move(pixels);
|
||||
zone=std::make_unique<DoorZoneAssigner>(config.door_zone_config);
|
||||
auto openOutput=[](std::ofstream& out,std::filesystem::path const& path) {
|
||||
if(path.empty())return;
|
||||
if(!path.parent_path().empty())std::filesystem::create_directories(path.parent_path());
|
||||
out.exceptions(std::ios::failbit|std::ios::badbit);out.open(path);
|
||||
};
|
||||
openOutput(output,config.results_path);openOutput(poseOutput,config.pose_records_path);
|
||||
Stream warmup;
|
||||
pose->warmup(warmup.value);
|
||||
detector->infer(cv::Mat(config.camera.height,config.camera.width,CV_8UC3,cv::Scalar()),warmup.value);
|
||||
{std::lock_guard<std::mutex> lock(stateMutex);camera=std::make_unique<Camera>(config.camera);if(stopped)camera->requestStop();}
|
||||
{std::lock_guard<std::mutex> lock(stateMutex);can=std::make_unique<CanInput>(config.lazy_gate);}
|
||||
modelsReady=true;
|
||||
can=std::make_unique<CanInput>(config.can_enabled);
|
||||
active=!stopped.load();
|
||||
captureThread=std::thread([this]{try{captureLoop();}catch(...){fail();}});
|
||||
detectorThread=std::thread([this]{try{detectorLoop();}catch(...){fail();}});
|
||||
inferenceThread=std::thread([this]{try{inferenceLoop();}catch(...){fail();}});
|
||||
}catch(...){stop();joinWorkers();throw;}
|
||||
}
|
||||
InferenceStatus status() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex);
|
||||
auto status=can?can->snapshot():InferenceStatus{};
|
||||
status.models_ready=modelsReady;
|
||||
if(!active||stopped){status.enabled=false;status.reason=stopped?"STOPPED":"STARTING";}
|
||||
return status;
|
||||
}
|
||||
bool eligible(std::uint64_t generation) const {auto s=status();return s.enabled&&s.generation==generation;}
|
||||
void captureLoop() {
|
||||
std::uint64_t generation=0;
|
||||
while(!stopped) {
|
||||
auto frame=camera->read();if(!frame)break;
|
||||
gaps+=frame->metadata.skipped_driver_frames;
|
||||
errorFrames+=frame->metadata.discarded_error_frames;
|
||||
bool queued=config.camera.video.empty()?captures.pushLatest(std::move(*frame)):captures.push(std::move(*frame));
|
||||
if(!queued)break;
|
||||
++captured;
|
||||
auto gate=status();
|
||||
if(gate.generation!=generation){captures.clear();detections.clear();results.clear();generation=gate.generation;}
|
||||
if(!gate.enabled){++skippedIdle;continue;}
|
||||
if(!captures.pushLatest({std::move(*frame),gate.generation}))break;
|
||||
}
|
||||
captures.close();
|
||||
}
|
||||
void detectorLoop() {
|
||||
Stream stream;
|
||||
while(!stopped) {
|
||||
auto pending=captures.pop();if(!pending)break;
|
||||
if(!eligible(pending->generation))continue;
|
||||
auto startedAt=monotonicNs();++detectorRuns;
|
||||
auto boxes=detector->infer(imageView(*pending->frame.image),stream.value);
|
||||
if(!eligible(pending->generation))continue;
|
||||
if(!detections.pushLatest({std::move(pending->frame),std::move(boxes),elapsedMs(startedAt),pending->generation}))break;
|
||||
auto frame=captures.pop();if(!frame)break;
|
||||
auto startedAt=monotonicNs();auto boxes=detector->infer(imageView(*frame->image),stream.value);
|
||||
Detected item{std::move(*frame),std::move(boxes),elapsedMs(startedAt)};
|
||||
bool queued=config.camera.video.empty()?detections.pushLatest(std::move(item)):detections.push(std::move(item));
|
||||
if(!queued)break;
|
||||
}
|
||||
detections.close();
|
||||
}
|
||||
void inferenceLoop() {
|
||||
Stream stream;fangjia::BoxTracker boxesTracker;fangjia::PoseFusionTracker fusion;
|
||||
std::uint64_t generation=0;
|
||||
Stream stream;fangjia::BoxTracker boxesTracker;fangjia::PoseFusionTracker fusion;std::uint64_t processed=0;
|
||||
while(!stopped) {
|
||||
auto detected=detections.pop();if(!detected)break;
|
||||
if(!eligible(detected->generation))continue;
|
||||
if(generation!=detected->generation){boxesTracker=fangjia::BoxTracker{};fusion=fangjia::PoseFusionTracker{};generation=detected->generation;}
|
||||
if(detected->frame.metadata.frame_index>static_cast<std::uint64_t>(std::numeric_limits<int>::max()))throw std::runtime_error("Frame index limit reached; restart pipeline");
|
||||
auto index=static_cast<int>(detected->frame.metadata.frame_index);
|
||||
if(processed>static_cast<std::uint64_t>(std::numeric_limits<int>::max()))throw std::runtime_error("Frame index limit reached; restart pipeline");
|
||||
auto index=static_cast<int>(processed++);
|
||||
auto& boxes=detected->boxes;
|
||||
auto frame=imageView(*detected->frame.image);
|
||||
boxesTracker.update(boxes,index,frame.cols,frame.rows);
|
||||
auto poseStart=monotonicNs();
|
||||
++poseRuns;
|
||||
auto raw=pose->infer(frame,boxes,stream.value);auto isolated=raw;
|
||||
for(std::size_t p=0;p<boxes.size();++p) {
|
||||
auto const& box=boxes[p];double area=std::max(double(box.x2-box.x1)*(box.y2-box.y1),1.0);
|
||||
bool needsIsolation=false;
|
||||
for(std::size_t q=0;q<boxes.size();++q)if(q!=p&&std::max(double(boxes[q].x2-boxes[q].x1)*(boxes[q].y2-boxes[q].y1),1.0)<0.5*area)needsIsolation=true;
|
||||
if(!eligible(generation))break;
|
||||
if(needsIsolation){auto masked=maskOtherPeople(frame,boxes,p);isolated[p]=pose->infer(masked,{box},stream.value).at(0);}
|
||||
}
|
||||
if(!eligible(generation))continue;
|
||||
auto poseElapsed=elapsedMs(poseStart);auto fusionStart=monotonicNs();
|
||||
auto fused=fusion.update(raw,isolated,index);
|
||||
auto record=makeRecord(index,raw,isolated,fused,false,false);
|
||||
auto record=makeRecord(index,raw,isolated,fused,poseOutput.is_open(),true);
|
||||
auto riskData=risk->evaluate(index,record);
|
||||
auto result=std::make_shared<FrameResult>(makeFrameResult(record,riskData,detected->frame.metadata));
|
||||
auto assignments=zone->update(index,record,frame.size());
|
||||
result->can=can->snapshot(config.can_side,config.can_timeout_ms);
|
||||
result->active_door=activeDoor(result->can);
|
||||
if(config.active_door!="can") {result->active_door=config.active_door;result->can.source="simulation";result->can.valid=true;}
|
||||
auto decisions=buildDoorRisks(riskData,assignments,result->active_door,result->can.valid);
|
||||
result->risk=parseRisk(decisions.selected);
|
||||
for(auto const& entry:decisions.doors.items())result->door_risks[entry.key()]=parseRisk(entry.value());
|
||||
for(auto const& a:assignments)result->door_assignments.push_back(parseAssignment(a));
|
||||
attachPoses(*result,fused);
|
||||
result->image=std::move(detected->frame.image);result->door_mask_pixels=doorPixels;
|
||||
result->door_mask_calibration_verified=config.door_mask_calibration_verified;
|
||||
result->timing={detected->elapsed_ms,poseElapsed,elapsedMs(fusionStart),elapsedMs(result->capture.capture_monotonic_ns)};
|
||||
auto gate=status();
|
||||
if(!gate.enabled||gate.generation!=generation)continue;
|
||||
result->inference=gate;
|
||||
FrameResultPtr immutable=result;
|
||||
results.publish(std::move(immutable));
|
||||
{std::lock_guard<std::mutex> lock(stateMutex);totalLatency+=result->timing.capture_to_result_ms;++published;}
|
||||
outputWriter->write(*result);
|
||||
if(output.is_open())output<<serializeResult(*result).dump()<<'\n';
|
||||
if(poseOutput.is_open())poseOutput<<record.dump()<<'\n';
|
||||
if(config.max_frames&&processed>=config.max_frames){stop();break;}
|
||||
}
|
||||
if(output.is_open())output.flush();
|
||||
if(poseOutput.is_open())poseOutput.flush();
|
||||
active=false;
|
||||
}
|
||||
};
|
||||
CameraPipeline::CameraPipeline(PipelineConfig config):impl_(std::make_unique<Impl>(std::move(config))){}
|
||||
CameraPipeline::~CameraPipeline()=default;
|
||||
void CameraPipeline::start(){impl_->start();}
|
||||
FrameResultPtr CameraPipeline::latest()const{
|
||||
auto result=impl_->results.load();auto gate=impl_->status();
|
||||
return result&&gate.enabled&&result->inference.generation==gate.generation?result:FrameResultPtr{};
|
||||
}
|
||||
InferenceStatus CameraPipeline::inferenceStatus()const{return impl_->status();}
|
||||
FrameResultPtr CameraPipeline::latest()const{return impl_->results.load();}
|
||||
bool CameraPipeline::running()const noexcept{return impl_->active.load();}
|
||||
void CameraPipeline::requestStop()noexcept{impl_->stop();}
|
||||
void CameraPipeline::join(){impl_->joinWorkers();{std::lock_guard<std::mutex> lock(impl_->stateMutex);impl_->camera.reset();impl_->can.reset();}impl_->rethrowFailure();}
|
||||
PipelineStats CameraPipeline::stats()const {
|
||||
std::lock_guard<std::mutex> lock(impl_->stateMutex);auto count=impl_->published.load();
|
||||
return {impl_->captured.load(),count,impl_->gaps.load(),impl_->errorFrames.load(),count?impl_->totalLatency/count:0,impl_->detectorRuns.load(),impl_->poseRuns.load(),impl_->skippedIdle.load()};
|
||||
return {impl_->captured.load(),count,impl_->gaps.load(),impl_->errorFrames.load(),count?impl_->totalLatency/count:0};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,6 @@ bool can_start()
|
||||
s_rx_ctx[i].total = 0;
|
||||
s_rx_ctx[i].stop = 0;
|
||||
|
||||
if (IsMerge && i != 0)continue;
|
||||
if (IsMerge && i == 0)
|
||||
{
|
||||
s_thread_started[i]=pthread_create(&s_rx_threads[i], NULL, rx_merge_thread, &s_rx_ctx[i])==0; // 合并接收线程
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "can_input.hpp"
|
||||
#include "can_comm.h"
|
||||
#include "camera.hpp"
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <stdexcept>
|
||||
namespace cms {
|
||||
struct CanInput::Impl {
|
||||
static std::mutex owner_mutex;
|
||||
static Impl* owner;
|
||||
std::mutex mutex;
|
||||
std::condition_variable wake;
|
||||
LazyGate gate;
|
||||
std::atomic<bool> stopped{false};
|
||||
std::thread worker;
|
||||
static void receive(int64_t,uint32_t id,uint8_t const* data,uint32_t size) {
|
||||
std::lock_guard<std::mutex> lock(owner_mutex);
|
||||
if(owner){std::lock_guard<std::mutex> stateLock(owner->mutex);owner->gate.receive(id,data,size,monotonicNs());}
|
||||
}
|
||||
explicit Impl(LazyGateConfig config):gate(config) {
|
||||
{std::lock_guard<std::mutex> lock(owner_mutex);if(owner)throw std::logic_error("Only one CAN receiver per process");owner=this;}
|
||||
CanComm::set_msg_callback(receive);
|
||||
try {worker=std::thread([this] {
|
||||
bool opened=false;
|
||||
auto attempted=monotonicNs();
|
||||
while(!stopped) {
|
||||
auto now=monotonicNs();
|
||||
auto status=snapshot();
|
||||
// Retry unplugged/stalled adapters while CAN fallback inference continues.
|
||||
if(!opened||(now-attempted>5000000000ULL&&!status.can_valid)) {
|
||||
if(opened)CanComm::can_stop();
|
||||
opened=CanComm::can_start();attempted=monotonicNs();
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
wake.wait_for(lock,std::chrono::seconds(1),[this]{return stopped.load();});
|
||||
}
|
||||
if(opened)CanComm::can_stop();
|
||||
});}catch(...){CanComm::set_msg_callback(nullptr);std::lock_guard<std::mutex> lock(owner_mutex);owner=nullptr;throw;}
|
||||
}
|
||||
~Impl() {
|
||||
stopped=true;wake.notify_all();if(worker.joinable())worker.join();
|
||||
CanComm::set_msg_callback(nullptr);
|
||||
std::lock_guard<std::mutex> lock(owner_mutex);owner=nullptr;
|
||||
}
|
||||
InferenceStatus snapshot() {std::lock_guard<std::mutex> lock(mutex);return gate.snapshot(monotonicNs());}
|
||||
};
|
||||
std::mutex CanInput::Impl::owner_mutex;
|
||||
CanInput::Impl* CanInput::Impl::owner=nullptr;
|
||||
CanInput::CanInput(LazyGateConfig config):impl_(std::make_unique<Impl>(config)){}
|
||||
CanInput::~CanInput()=default;
|
||||
InferenceStatus CanInput::snapshot(){return impl_->snapshot();}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "can_state.hpp"
|
||||
#include "can_comm.h"
|
||||
#include "camera.hpp"
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
namespace cms {
|
||||
namespace {
|
||||
std::mutex mutex;
|
||||
std::array<int,4> positions{{-1,-1,-1,-1}};
|
||||
std::uint64_t timestamp{};
|
||||
bool owned{};
|
||||
void received(int64_t,uint32_t id,uint8_t const* data,uint32_t size) {
|
||||
if(id!=0x391||size<4)return;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for(int i=0;i<4;++i)positions[i]=data[i];
|
||||
timestamp=monotonicNs();
|
||||
}
|
||||
}
|
||||
CanInput::CanInput(bool enabled) {
|
||||
{std::lock_guard<std::mutex> lock(mutex);if(owned)throw std::logic_error("Only one CAN input may run per process");owned=true;positions.fill(-1);timestamp=0;}
|
||||
if(enabled){CanComm::set_msg_callback(received);opened_=CanComm::can_start();}
|
||||
}
|
||||
CanInput::~CanInput() {
|
||||
if(opened_)CanComm::can_stop();
|
||||
CanComm::set_msg_callback(nullptr);
|
||||
std::lock_guard<std::mutex> lock(mutex);owned=false;
|
||||
}
|
||||
CanSnapshot CanInput::snapshot(std::string const& side,double timeoutMs) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);CanSnapshot s;s.positions=positions;s.side=side;
|
||||
s.age_ms=timestamp?(monotonicNs()-timestamp)*1e-6:-1;
|
||||
int offset=side=="left"?0:1;
|
||||
s.valid=s.age_ms>=0&&s.age_ms<=timeoutMs&&s.positions[offset]>=0&&s.positions[offset+2]>=0;return s;
|
||||
}
|
||||
std::string activeDoor(CanSnapshot const& s) {
|
||||
if(!s.valid)return "none";
|
||||
int i=s.side=="left"?0:1;bool front=s.positions[i]==0,rear=s.positions[i+2]==0;
|
||||
return front&&rear?"both":front?"front":rear?"rear":"none";
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -19,11 +19,10 @@ PipelineConfig loadConfig(std::filesystem::path const& path) {
|
||||
auto const& door=j.at("door");config.door_mask=resolve(door.at("mask"));
|
||||
config.warning_relative=door.at("warning_relative");config.release_relative=door.at("release_relative");
|
||||
config.door_mask_calibration_verified=door.at("calibration_verified");
|
||||
if(j.contains("lazy_inference")) {
|
||||
auto const& gate=j.at("lazy_inference");
|
||||
config.lazy_gate.threshold_percent=gate.value("threshold_percent",15);
|
||||
config.lazy_gate.can_timeout_ms=gate.value("can_timeout_ms",1000);
|
||||
config.lazy_gate.motion_timeout_ms=gate.value("motion_timeout_ms",300);
|
||||
config.door_zone_config=resolve(door.at("zone_config"));
|
||||
if(j.contains("can")) {
|
||||
auto const& can=j.at("can");config.can_enabled=can.value("enabled",true);
|
||||
config.can_side=can.value("side",std::string("left"));config.can_timeout_ms=can.value("timeout_ms",1000.0);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "door_zone.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
namespace cms {
|
||||
namespace {
|
||||
double median(std::deque<double> const& values) {
|
||||
std::vector<double> sorted(values.begin(),values.end());
|
||||
std::sort(sorted.begin(),sorted.end());auto n=sorted.size();
|
||||
return n%2?sorted[n/2]:(sorted[n/2-1]+sorted[n/2])*0.5;
|
||||
}
|
||||
std::string upper(std::string value) { for(auto& c:value)c=char(std::toupper(static_cast<unsigned char>(c)));return value; }
|
||||
Json features(Json const& record,std::size_t position,cv::Size size,int& poseCount) {
|
||||
auto const& b=record.at("boxes").at(position);
|
||||
double w=std::max(size.width,1),h=std::max(size.height,1);
|
||||
Json result={{"box_center_x",(b[0].get<double>()+b[2].get<double>())*.5/w},{"box_height",(b[3].get<double>()-b[1].get<double>())/h}};
|
||||
poseCount=0;
|
||||
if(!record.contains("temporal_selected_keypoints")||position>=record["temporal_selected_keypoints"].size())return result;
|
||||
auto const& points=record["temporal_selected_keypoints"][position];
|
||||
for(int k:{5,6,11,12})if(k>=int(points.size())||points[k].size()<2||!std::isfinite(points[k][0].get<double>())||!std::isfinite(points[k][1].get<double>()))return result;
|
||||
double sx=(points[5][0].get<double>()+points[6][0].get<double>())*.5;
|
||||
double sy=(points[5][1].get<double>()+points[6][1].get<double>())*.5;
|
||||
double hy=(points[11][1].get<double>()+points[12][1].get<double>())*.5;
|
||||
result["shoulder_center_x"]=sx/w;result["shoulder_center_y"]=sy/h;
|
||||
result["shoulder_width"]=std::hypot(points[5][0].get<double>()-points[6][0].get<double>(),points[5][1].get<double>()-points[6][1].get<double>())/w;
|
||||
result["torso_height"]=std::abs(hy-sy)/h;result["hip_center_y"]=hy/h;poseCount=5;return result;
|
||||
}
|
||||
Json baseRisk(std::string const& door,std::string const& level,Json score,std::string const& status,std::string const& decision,Json regions=Json::array()) {
|
||||
return {{"door_id",door},{"status",status},{"risk_score",score},{"risk_level",level},{"decision",decision},{"risk_type",upper(door)+"_"+upper(level)},{"source",nullptr},{"track_id",nullptr},{"side",nullptr},{"regions",regions}};
|
||||
}
|
||||
}
|
||||
DoorZoneAssigner::DoorZoneAssigner(std::filesystem::path const& configuration) {
|
||||
std::ifstream file(configuration);if(!file)throw std::runtime_error("Cannot read door-zone configuration: "+configuration.string());
|
||||
auto j=Json::parse(file);features_=j.at("features");
|
||||
frontMin_=j.value("front_probability_min",.72);rearMax_=j.value("rear_probability_max",.28);
|
||||
minimumHeight_=j.value("minimum_person_height",.4);minimumPoseFeatures_=j.value("minimum_pose_features",4);
|
||||
historyFrames_=j.value("history_frames",5);staleFrames_=j.value("stale_track_frames",120);
|
||||
if(historyFrames_<1||staleFrames_<0||rearMax_<0||frontMin_>1||rearMax_>=frontMin_)throw std::invalid_argument("Invalid door-zone configuration");
|
||||
}
|
||||
std::string DoorZoneAssigner::classify(double p) const {return p>=frontMin_?"front":p<=rearMax_?"rear":"transition";}
|
||||
Json DoorZoneAssigner::update(int frame,Json const& record,cv::Size size) {
|
||||
Json assignments=Json::array();auto const& boxes=record.at("boxes");auto ids=record.value("track_ids",Json::array());
|
||||
for(std::size_t i=0;i<boxes.size();++i) {
|
||||
int id=i<ids.size()?ids[i].get<int>():int(i)+1,count=0;
|
||||
auto f=features(record,i,size,count);double weighted=0,total=0;Json contributions=Json::object();
|
||||
for(auto const& entry:features_.items())if(f.contains(entry.key())) {
|
||||
auto const& c=entry.value();double rear=c.at("rear"),front=c.at("front"),weight=c.at("weight");
|
||||
double score=front==rear?.5:std::clamp((f[entry.key()].get<double>()-rear)/(front-rear),0.0,1.0);
|
||||
contributions[entry.key()]=rounded(score,6);weighted+=score*weight;total+=weight;
|
||||
}
|
||||
double probability=total?weighted/total:.5;std::string rawZone,zone;
|
||||
if(f["box_height"].get<double>()<minimumHeight_)rawZone=zone="unassigned";
|
||||
else if(count<minimumPoseFeatures_)rawZone=zone="transition";
|
||||
else {rawZone=classify(probability);auto& values=history_[id];values.push_back(probability);while(int(values.size())>historyFrames_)values.pop_front();zone=classify(median(values));}
|
||||
auto it=history_.find(id);double smoothed=it==history_.end()||it->second.empty()?probability:median(it->second);
|
||||
lastSeen_[id]=frame;for(auto& value:f.items())value.value()=rounded(value.value().get<double>(),6);
|
||||
assignments.push_back({{"track_id",id},{"person_index",i},{"door_zone",zone},{"raw_door_zone",rawZone},{"front_probability",rounded(probability,6)},{"smoothed_front_probability",rounded(smoothed,6)},{"confidence",rounded(std::abs(smoothed-.5)*2,6)},{"evidence_source","pose_primary_bbox_secondary"},{"pose_feature_count",count},{"features",f},{"feature_frontness",contributions},{"history_frames",it==history_.end()?0:it->second.size()}});
|
||||
}
|
||||
for(auto it=lastSeen_.begin();it!=lastSeen_.end();)if(frame-it->second>staleFrames_){history_.erase(it->first);it=lastSeen_.erase(it);}else ++it;
|
||||
return assignments;
|
||||
}
|
||||
DoorDecisions buildDoorRisks(Json const& raw,Json const& assignments,std::string const& active,bool canValid) {
|
||||
std::map<int,std::string> zones;for(auto const& a:assignments)zones[a.at("track_id").get<int>()]=a.at("door_zone");
|
||||
Json regions=Json::array();for(auto region:raw.value("regions",Json::array())){int id=region.value("track_id",-1);region["door_zone"]=zones.count(id)?zones[id]:"unknown";regions.push_back(std::move(region));}
|
||||
DoorDecisions result{Json::object(),Json::object()};
|
||||
for(std::string door:{"front","rear"}) {
|
||||
if(active!="both"&&active!=door){result.doors[door]=baseRisk(door,"inactive",nullptr,"inactive","DOOR_CAN_NOT_ACTIVE");continue;}
|
||||
Json exact=Json::array(),transition=Json::array(),nearest=nullptr;
|
||||
for(auto const& r:regions){if(r["door_zone"]==door){exact.push_back(r);if(!r.at("risk_score").is_null()&&(nearest.is_null()||r["risk_score"].get<double>()>nearest["risk_score"].get<double>()))nearest=r;}if(r["door_zone"]=="transition")transition.push_back(r);}
|
||||
if(!nearest.is_null()&&(transition.empty()||(nearest["risk_level"]!="clear"&&nearest["risk_level"]!="watch"))) {
|
||||
auto level=nearest["risk_level"].get<std::string>();auto r=baseRisk(door,level,nearest["risk_score"],"calculated",nearest["decision"],exact);
|
||||
for(auto key:{"source","track_id","side"}){r[key]=nearest[key];}
|
||||
result.doors[door]=std::move(r);
|
||||
} else if(!transition.empty())result.doors[door]=baseRisk(door,"unknown",nullptr,"door_zone_ambiguous","EVIDENCE_UNAVAILABLE_TRANSITION_ZONE",transition);
|
||||
else result.doors[door]=baseRisk(door,"clear",0.0,"calculated","CLEAR_NO_PERSON_ASSIGNED_TO_ACTIVE_DOOR");
|
||||
}
|
||||
if(active=="front"||active=="rear")result.selected=result.doors[active];
|
||||
else if(active=="none") {
|
||||
result.selected=baseRisk("none","inactive",nullptr,"inactive","NO_DOOR_CAN_ACTIVE");result.selected["door_id"]=nullptr;result.selected["risk_type"]="NO_ACTIVE_DOOR";
|
||||
} else {
|
||||
std::map<std::string,int> severity{{"danger",4},{"warning",3},{"watch",2},{"unknown",1},{"clear",0}};
|
||||
result.selected=result.doors["front"];
|
||||
if(severity[result.doors["rear"]["risk_level"]]>severity[result.selected["risk_level"]])result.selected=result.doors["rear"];
|
||||
}
|
||||
for(auto key:{"door_mask","warning_distance_relative","release_distance_relative"})result.selected[key]=raw.at(key);
|
||||
if(!canValid) {
|
||||
for(auto& entry:result.doors.items()){auto& r=entry.value();r["risk_type"]=upper(entry.key())+"_UNKNOWN";r["risk_level"]="unknown";r["risk_score"]=nullptr;r["decision"]="CAN_UNAVAILABLE_OR_STALE";}
|
||||
result.selected["risk_type"]="CAN_UNKNOWN";result.selected["risk_level"]="unknown";result.selected["risk_score"]=nullptr;result.selected["decision"]="CAN_UNAVAILABLE_OR_STALE";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
#include "lazy_gate.hpp"
|
||||
#include <stdexcept>
|
||||
namespace cms {
|
||||
LazyGate::LazyGate(LazyGateConfig config):config_(config) {
|
||||
if(config.threshold_percent<1||config.threshold_percent>100||config.can_timeout_ms<=0||config.motion_timeout_ms<=0||config.motion_timeout_ms>config.can_timeout_ms)
|
||||
throw std::invalid_argument("Invalid lazy inference gate configuration");
|
||||
}
|
||||
void LazyGate::receive(std::uint32_t id,std::uint8_t const* data,std::size_t size,std::uint64_t now) {
|
||||
if(id!=0x391||size<4||!data)return;
|
||||
snapshot(now); // Account for a timeout before receiving a new sample.
|
||||
bool continuity=have_sample_&&now>=last_sample_&&now-last_sample_<=std::uint64_t(config_.can_timeout_ms)*1000000;
|
||||
for(int side=0;side<2;++side) {
|
||||
int index=side*2;
|
||||
if(!continuity){state_.direction[side]=0;last_change_[side]=0;}
|
||||
else if(data[index]!=state_.positions[index]) {
|
||||
state_.direction[side]=data[index]>state_.positions[index]?1:-1;
|
||||
last_change_[side]=now;
|
||||
}
|
||||
}
|
||||
for(int i=0;i<4;++i)state_.positions[i]=data[i];
|
||||
have_sample_=data[0]<=100&&data[2]<=100;
|
||||
if(!have_sample_){state_.direction={};last_change_={};}
|
||||
last_sample_=now;
|
||||
snapshot(now);
|
||||
}
|
||||
InferenceStatus LazyGate::snapshot(std::uint64_t now) {
|
||||
state_.sample_monotonic_ns=now;
|
||||
state_.can_age_ms=have_sample_&&now>=last_sample_?(now-last_sample_)*1e-6:-1;
|
||||
state_.can_valid=state_.can_age_ms>=0&&state_.can_age_ms<=config_.can_timeout_ms;
|
||||
bool triggers[2]{};
|
||||
for(int i=0;i<2;++i) {
|
||||
if(!state_.can_valid||now<last_change_[i]||now-last_change_[i]>=std::uint64_t(config_.motion_timeout_ms)*1000000)state_.direction[i]=0;
|
||||
triggers[i]=state_.can_valid&&state_.positions[i*2]<config_.threshold_percent&&state_.direction[i]!=0;
|
||||
}
|
||||
state_.left_front_trigger=triggers[0];state_.left_rear_trigger=triggers[1];
|
||||
bool enabled=!state_.can_valid||triggers[0]||triggers[1];
|
||||
if(enabled!=state_.enabled){++state_.generation;state_.enabled=enabled;}
|
||||
state_.reason=!state_.can_valid?"CAN_UNAVAILABLE":enabled?"LEFT_DOOR_MOVING_BELOW_THRESHOLD":"IDLE";
|
||||
return state_;
|
||||
}
|
||||
}
|
||||
+41
-37
@@ -2,9 +2,8 @@
|
||||
#include "config.hpp"
|
||||
#include "stop_signals.hpp"
|
||||
#include "visualizer.hpp"
|
||||
#include <opencv2/core.hpp>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
@@ -13,58 +12,63 @@
|
||||
int main(int argc,char** argv)try {
|
||||
auto root=std::filesystem::canonical("/proc/self/exe").parent_path().parent_path();
|
||||
auto configPath=root/"config/camera.json";
|
||||
cms::OutputOptions output;output.directory=root/"output";
|
||||
bool visualize=false;double duration=0;
|
||||
for(int i=1;i<argc;++i)if(std::string(argv[i])=="--config") {
|
||||
if(++i>=argc)throw std::invalid_argument("--config needs a path");configPath=argv[i];
|
||||
}
|
||||
for(int i=1;i<argc;++i)if(std::string(argv[i])=="--help"||std::string(argv[i])=="-h") {
|
||||
std::cout<<"camera_risk [--config FILE] [--visualize] [--active-door can|front|rear|both|none]\n"
|
||||
<<" [--can-side left|right] [--no-can] [--duration SECONDS] [--max-frames N]\n"
|
||||
<<" [--video FILE] [--save-original] [--save-annotated] [--save-results [FILE.jsonl]] [--save-pose-records FILE.jsonl]\n"
|
||||
<<"Pure C++ inference; the latest immutable FrameResult is kept in memory.\n";return 0;
|
||||
}
|
||||
auto config=cms::loadConfig(configPath);config.output.directory=root/"output";bool visualize=false;double duration=0;
|
||||
auto value=[&](int& i){if(++i>=argc)throw std::invalid_argument("Missing option value");return std::string(argv[i]);};
|
||||
for(int i=1;i<argc;++i) {
|
||||
std::string arg=argv[i];
|
||||
if(arg=="--config")configPath=value(i);
|
||||
else if(arg=="--save-original")output.original=true;
|
||||
else if(arg=="--save-annotated")output.annotated=true;
|
||||
else if(arg=="--save-results")output.results=true;
|
||||
if(arg=="--config")value(i);
|
||||
else if(arg=="--visualize")visualize=true;
|
||||
else if(arg=="--duration") {duration=std::stod(value(i));if(!std::isfinite(duration)||duration<0)throw std::invalid_argument("Invalid duration");}
|
||||
else if(arg=="--help") {
|
||||
std::cout<<"camera_risk [--save-original] [--save-annotated] [--save-results] [--config FILE] [--visualize] [--duration SECONDS]\n"
|
||||
<<"C++ lazy inference; resident warm engines, continuous CAN reception.\n";return 0;
|
||||
}else throw std::invalid_argument("Unknown option: "+arg);
|
||||
else if(arg=="--no-can")config.can_enabled=false;
|
||||
else if(arg=="--active-door")config.active_door=value(i);
|
||||
else if(arg=="--can-side")config.can_side=value(i);
|
||||
else if(arg=="--video")config.camera.video=value(i);
|
||||
else if(arg=="--save-original")config.output.original=true;
|
||||
else if(arg=="--save-annotated")config.output.annotated=true;
|
||||
else if(arg=="--save-results") {
|
||||
if(i+1<argc&&std::string(argv[i+1]).rfind("--",0)!=0)config.results_path=value(i); // Existing explicit JSONL path.
|
||||
else config.output.results=true;
|
||||
}
|
||||
else if(arg=="--save-pose-records")config.pose_records_path=value(i);
|
||||
else if(arg=="--max-frames"){auto n=std::stoll(value(i));if(n<0)throw std::invalid_argument("Negative frame count");config.max_frames=n;}
|
||||
else if(arg=="--duration"){duration=std::stod(value(i));if(!std::isfinite(duration)||duration<0)throw std::invalid_argument("Invalid duration");}
|
||||
else throw std::invalid_argument("Unknown option: "+arg);
|
||||
}
|
||||
cv::setNumThreads(2);
|
||||
auto config=cms::loadConfig(configPath);config.output=output;
|
||||
cms::CameraPipeline pipeline(config);
|
||||
cms::StopSignals signals(pipeline);pipeline.start();
|
||||
cms::CameraPipeline pipeline(config);cms::StopSignals signals(pipeline);pipeline.start();
|
||||
auto started=std::chrono::steady_clock::now();std::atomic<bool> monitorStop{false};
|
||||
std::thread monitor([&] {
|
||||
auto reported=started-std::chrono::seconds(5);std::uint64_t generation=0;
|
||||
auto report=started;
|
||||
while(!monitorStop&&pipeline.running()) {
|
||||
auto now=std::chrono::steady_clock::now();
|
||||
if(duration>0&&std::chrono::duration<double>(now-started).count()>=duration){pipeline.requestStop();break;}
|
||||
auto state=pipeline.inferenceStatus();
|
||||
if(state.generation!=generation||now-reported>=std::chrono::seconds(5)) {
|
||||
auto stats=pipeline.stats();
|
||||
std::cout<<"inference="<<(state.enabled?"RUNNING":"IDLE")<<" reason="<<state.reason
|
||||
<<" left_front="<<state.positions[0]<<" left_rear="<<state.positions[2]
|
||||
<<" generation="<<state.generation<<" detector_runs="<<stats.detector_runs
|
||||
<<" pose_runs="<<stats.pose_runs<<" results="<<stats.published<<std::endl;
|
||||
generation=state.generation;reported=now;
|
||||
auto now=std::chrono::steady_clock::now();double elapsed=std::chrono::duration<double>(now-started).count();
|
||||
if(duration>0&&elapsed>=duration){pipeline.requestStop();break;}
|
||||
if(std::chrono::duration<double>(now-report).count()>=5) {
|
||||
auto s=pipeline.stats();auto r=pipeline.latest();
|
||||
std::cout<<"frames="<<s.published<<" fps="<<s.published/elapsed<<" risk="<<(r?r->risk.risk_type:"STARTING")<<std::endl;report=now;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
});
|
||||
try {
|
||||
if(visualize) {
|
||||
if(!std::getenv("DISPLAY")&&!std::getenv("WAYLAND_DISPLAY"))std::cerr<<"No display session; running without visualization.\n";
|
||||
else try {cms::runLocalVisualization(pipeline);}
|
||||
catch(std::exception const& e){std::cerr<<"Visualization unavailable: "<<e.what()<<std::endl;}
|
||||
if(!std::getenv("DISPLAY")&&!std::getenv("WAYLAND_DISPLAY"))std::cerr<<"No display session; inference continues without visualization.\n";
|
||||
else try{cms::runLocalVisualization(pipeline);}catch(std::exception const& e){std::cerr<<"Visualization unavailable; inference continues: "<<e.what()<<std::endl;}
|
||||
}
|
||||
// Downstream reads: pipeline.inferenceStatus(); auto result=pipeline.latest();
|
||||
// result is null while idle, starting, or awaiting the new generation's first frame.
|
||||
// Downstream C++ code reads this same variable from its own thread:
|
||||
// auto result = pipeline.latest(); // shared_ptr<const cms::FrameResult>
|
||||
// if(result) { use(result->risk, result->door_risks, result->persons); }
|
||||
pipeline.join();
|
||||
}catch(...){monitorStop=true;pipeline.requestStop();monitor.join();throw;}
|
||||
monitorStop=true;monitor.join();auto stats=pipeline.stats();
|
||||
auto elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-started).count();
|
||||
std::cout<<"captured="<<stats.captured<<" results="<<stats.published<<" detector_runs="<<stats.detector_runs
|
||||
<<" pose_runs="<<stats.pose_runs<<" skipped_idle="<<stats.skipped_idle<<" elapsed_seconds="<<elapsed
|
||||
<<" capture_to_result_ms="<<stats.mean_capture_to_result_ms<<std::endl;
|
||||
auto seconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-started).count();
|
||||
std::cout<<"frames="<<stats.published<<" elapsed_seconds="<<seconds<<" fps="<<stats.published/std::max(seconds,.001)<<" capture_to_result_ms="<<stats.mean_capture_to_result_ms<<std::endl;
|
||||
return 0;
|
||||
}catch(std::exception const& e){std::cerr<<"Camera pipeline failed: "<<e.what()<<std::endl;return 1;}
|
||||
|
||||
@@ -44,6 +44,8 @@ cv::Mat renderResult(FrameResult const& r) {
|
||||
cv::rectangle(canvas,{int(b[0]),int(b[1])},{int(b[2]),int(b[3])},{0,220,255},2);
|
||||
std::ostringstream text;text<<"ID "<<person.track_id<<" "<<std::fixed<<std::setprecision(2)<<person.confidence;
|
||||
cv::putText(canvas,text.str(),{int(b[0])+4,std::max(180,int(b[1])+25)},cv::FONT_HERSHEY_SIMPLEX,.8,{0,220,255},2,cv::LINE_AA);
|
||||
for(auto const& a:r.door_assignments)if(a.track_id==person.track_id)
|
||||
cv::putText(canvas,a.door_zone,{int(b[0])+4,std::max(155,int(b[1])-8)},cv::FONT_HERSHEY_SIMPLEX,.7,{0,220,255},2,cv::LINE_AA);
|
||||
for(int s=0;s<2;++s) {
|
||||
auto const& state=person.hands[s];auto const& k=person.selected;
|
||||
cv::Scalar arm=s==0?cv::Scalar(40,40,255):cv::Scalar(30,230,30);
|
||||
@@ -69,12 +71,12 @@ cv::Mat renderResult(FrameResult const& r) {
|
||||
if(it!=r.track_ids.end()){auto const& b=r.boxes.at(it-r.track_ids.begin());cv::putText(canvas,"BODY FALLBACK",{int(b[0])+4,int(b[1])+50},cv::FONT_HERSHEY_SIMPLEX,.65,{0,160,255},2,cv::LINE_AA);}
|
||||
}
|
||||
cv::rectangle(canvas,{0,0},{canvas.cols,148},{20,24,30},-1);
|
||||
std::ostringstream headline;headline<<"RISK ";if(r.risk.risk_score)headline<<std::fixed<<std::setprecision(3)<<*r.risk.risk_score;else headline<<"N/A";headline<<" "<<r.risk.risk_level;
|
||||
std::ostringstream headline;headline<<r.risk.risk_type<<" ";if(r.risk.risk_score)headline<<std::fixed<<std::setprecision(3)<<*r.risk.risk_score;else headline<<"N/A";headline<<" "<<r.risk.risk_level;
|
||||
cv::putText(canvas,headline.str(),{24,40},cv::FONT_HERSHEY_SIMPLEX,1.0,color(r.risk.risk_level),2,cv::LINE_AA);
|
||||
std::ostringstream status;status<<"camera frame "<<r.frame_index<<" | seq "<<r.capture.driver_sequence<<" | latency "<<std::fixed<<std::setprecision(1)<<r.timing.capture_to_result_ms<<" ms";
|
||||
cv::putText(canvas,status.str(),{24,82},cv::FONT_HERSHEY_SIMPLEX,.72,{235,235,235},2,cv::LINE_AA);
|
||||
std::string calibration=r.door_mask_calibration_verified?"door calibration verified":"door calibration NOT verified (bring-up)";
|
||||
cv::putText(canvas,calibration+" | q/Esc: close preview",{24,123},cv::FONT_HERSHEY_SIMPLEX,.70,{0,190,255},2,cv::LINE_AA);
|
||||
cv::putText(canvas,calibration+" | CAN "+r.can.source+" | q/Esc: close preview",{24,123},cv::FONT_HERSHEY_SIMPLEX,.70,{0,190,255},2,cv::LINE_AA);
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace {
|
||||
template<class T> Json optional(std::optional<T> const& x){return x?Json(*x):Json(nullptr);}
|
||||
Json riskJson(RiskResult const& r) {
|
||||
Json out={{"status",r.status},{"risk_score",optional(r.risk_score)},{"risk_level",r.risk_level},{"decision",r.decision},{"source",optional(r.source)},{"track_id",optional(r.track_id)},{"side",optional(r.side)},{"regions",Json::array()}};
|
||||
if(!r.risk_type.empty()){out["risk_type"]=r.risk_type;out["door_id"]=optional(r.door_id);}
|
||||
if(!r.door_mask.status.empty()) {
|
||||
auto const& d=r.door_mask;out["door_mask"]={{"status",d.status},{"annotation_index",d.annotation_index},{"path",optional(d.path)},{"format",d.format},{"area_px",d.area_px}};
|
||||
out["warning_distance_relative"]=r.warning_distance_relative;out["release_distance_relative"]=r.release_distance_relative;
|
||||
@@ -11,6 +12,7 @@ Json riskJson(RiskResult const& r) {
|
||||
for(auto const& v:r.regions) {
|
||||
Json j={{"status",v.status},{"risk_score",optional(v.risk_score)},{"risk_level",v.risk_level},{"decision",v.decision},{"source",v.source},{"track_id",v.track_id},{"side",v.side}};
|
||||
if(v.state)j["state"]=*v.state;
|
||||
if(!v.door_zone.empty())j["door_zone"]=v.door_zone;
|
||||
if(v.region_area_px) {
|
||||
j["region_area_px"]=optional(v.region_area_px);j["door_overlap_px"]=optional(v.door_overlap_px);j["region_overlap_ratio"]=optional(v.region_overlap_ratio);
|
||||
j["min_distance_px"]=optional(v.min_distance_px);j["region_scale_px"]=optional(v.region_scale_px);j["distance_relative"]=optional(v.distance_relative);
|
||||
@@ -21,14 +23,15 @@ Json riskJson(RiskResult const& r) {
|
||||
}
|
||||
}
|
||||
Json serializeResult(FrameResult const& r) {
|
||||
Json out={{"frame_index",r.frame_index},{"track_ids",r.track_ids},{"boxes",r.boxes},{"skeletons",Json::array()},{"risk",riskJson(r.risk)}};
|
||||
Json out={{"frame_index",r.frame_index},{"track_ids",r.track_ids},{"boxes",r.boxes},{"skeletons",Json::array()},{"active_door",r.active_door},{"door_assignments",Json::array()},{"door_risks",Json::object()},{"risk",riskJson(r.risk)},{"ungated_risk",riskJson(r.ungated_risk)}};
|
||||
for(std::size_t i=0;i<r.persons.size();++i) {
|
||||
auto const& p=r.persons[i];Json points=Json::array(),metadata=Json::object();for(auto const& k:p.selected)points.push_back({rounded(k.x,3),rounded(k.y,3)});
|
||||
for(int s=0;s<2;++s){auto const& h=p.hands[s];metadata[s==0?"left":"right"]={{"limb_state",h.limb_consensus?"consensus":"inferred"},{"hand_source",h.source},{"hand_state",h.state},{"distance_eligible",h.distance_eligible},{"alarm_eligible",h.alarm_eligible}};}
|
||||
out["skeletons"].push_back({{"track_id",p.track_id},{"person_index",i},{"layout","coco_wholebody_133"},{"keypoints",points},{"metadata",metadata}});
|
||||
}
|
||||
auto const& state=r.inference;
|
||||
out["inference"]={{"models_ready",state.models_ready},{"enabled",state.enabled},{"can_valid",state.can_valid},{"positions",state.positions},{"direction",state.direction},{"left_front_trigger",state.left_front_trigger},{"left_rear_trigger",state.left_rear_trigger},{"generation",state.generation},{"can_age_ms",state.can_age_ms},{"reason",state.reason}};
|
||||
for(auto const& a:r.door_assignments)out["door_assignments"].push_back({{"track_id",a.track_id},{"person_index",a.person_index},{"door_zone",a.door_zone},{"raw_door_zone",a.raw_door_zone},{"front_probability",a.front_probability},{"smoothed_front_probability",a.smoothed_front_probability},{"confidence",a.confidence},{"evidence_source",a.evidence_source},{"pose_feature_count",a.pose_feature_count},{"history_frames",a.history_frames},{"features",a.features},{"feature_frontness",a.feature_frontness}});
|
||||
for(auto const& entry:r.door_risks)out["door_risks"][entry.first]=riskJson(entry.second);
|
||||
out["can"]={{"positions",r.can.positions},{"age_ms",r.can.age_ms},{"valid",r.can.valid},{"source",r.can.source},{"side",r.can.side}};
|
||||
for(std::size_t i=0;i<r.persons.size();++i) {
|
||||
auto const& p=r.persons[i];auto& skeleton=out["skeletons"][i];
|
||||
skeleton["confidence"]=p.confidence;skeleton["keypoint_scores"]=Json::array();
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
#include "v4l2_capture.h"
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
|
||||
// 四字符码构造宏 (使用 v4l2_fourcc 避免重复定义)
|
||||
// 注意: 这里使用videodev2.h已有的v4l2_fourcc宏
|
||||
|
||||
namespace {
|
||||
|
||||
// 将 v4l2_capability 的 driver[16] / card[32] 转成 C 字符串 (v4l2_capability 使用 __u8)
|
||||
inline std::string to_cstr(const __u8 *arr, size_t len) {
|
||||
std::string s;
|
||||
s.reserve(len);
|
||||
for (size_t i = 0; i < len && arr[i] != '\0'; ++i) {
|
||||
char c = static_cast<char>(arr[i]);
|
||||
if (c >= 32 && c < 127) s += c; // 只保留可打印 ASCII
|
||||
}
|
||||
// 去除末尾空格
|
||||
while (!s.empty() && s.back() == ' ') s.pop_back();
|
||||
return s;
|
||||
}
|
||||
|
||||
// 打印 v4l2 格式调试信息
|
||||
void dumpFormat(struct v4l2_format &fmt, const char *msg) {
|
||||
printf("%s: width=%u height=%u pixfmt=0x%08x '%c%c%c%c' bpl=%u size=%u\n",
|
||||
msg,
|
||||
fmt.fmt.pix.width,
|
||||
fmt.fmt.pix.height,
|
||||
fmt.fmt.pix.pixelformat,
|
||||
(fmt.fmt.pix.pixelformat) & 0xFF,
|
||||
(fmt.fmt.pix.pixelformat >> 8) & 0xFF,
|
||||
(fmt.fmt.pix.pixelformat >> 16) & 0xFF,
|
||||
(fmt.fmt.pix.pixelformat >> 24) & 0xFF,
|
||||
fmt.fmt.pix.bytesperline,
|
||||
fmt.fmt.pix.sizeimage);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
V4l2Capture::~V4l2Capture() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool V4l2Capture::open(const std::string &device) {
|
||||
reconnect_needed_ = false;
|
||||
if (fd_ >= 0) {
|
||||
return false;
|
||||
}
|
||||
fd_ = ::open(device.c_str(), O_RDWR | O_NONBLOCK, 0);
|
||||
if (fd_ < 0) {
|
||||
printf("V4l2Capture: open %s failed: %s\n", device.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查设备能力
|
||||
struct v4l2_capability cap;
|
||||
if (ioctl(fd_, VIDIOC_QUERYCAP, &cap) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_QUERYCAP failed: %s\n", strerror(errno));
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
printf("V4l2Capture: opened %s, driver='%s' card='%s', caps=0x%x\n",
|
||||
device.c_str(),
|
||||
to_cstr(cap.driver, sizeof(cap.driver)).c_str(),
|
||||
to_cstr(cap.card, sizeof(cap.card)).c_str(),
|
||||
cap.capabilities);
|
||||
|
||||
// 必须支持 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
|
||||
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
printf("V4l2Capture: not a video capture device\n");
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
|
||||
printf("V4l2Capture: does not support streaming I/O\n");
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void V4l2Capture::close() {
|
||||
if (streaming_) {
|
||||
stopStreaming();
|
||||
}
|
||||
// MMAP mappings keep the old V4L2 queue alive even after closing its fd.
|
||||
// Release them before reopening, otherwise VIDIOC_S_FMT returns EBUSY.
|
||||
for (auto const& buffer : buffers_) {
|
||||
if (buffer.start && buffer.start != MAP_FAILED) {
|
||||
munmap(buffer.start, buffer.length);
|
||||
}
|
||||
}
|
||||
buffers_.clear();
|
||||
if (fd_ >= 0) {
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
streaming_ = false;
|
||||
last_dequeued_ = -1;
|
||||
}
|
||||
|
||||
bool V4l2Capture::setFormat(uint32_t width, uint32_t height, uint32_t pixelformat) {
|
||||
if (fd_ < 0) return false;
|
||||
|
||||
struct v4l2_format fmt;
|
||||
std::memset(&fmt, 0, sizeof(fmt));
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = width;
|
||||
fmt.fmt.pix.height = height;
|
||||
fmt.fmt.pix.pixelformat = pixelformat;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE; // 逐行扫描
|
||||
|
||||
if (ioctl(fd_, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_S_FMT failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
dumpFormat(fmt, "V4l2Capture: setFormat");
|
||||
|
||||
width_ = fmt.fmt.pix.width;
|
||||
height_ = fmt.fmt.pix.height;
|
||||
pixelformat_ = fmt.fmt.pix.pixelformat;
|
||||
bytes_per_line_ = fmt.fmt.pix.bytesperline;
|
||||
|
||||
// 如果驱动不按请求设置, 给出警告
|
||||
if (width_ != width || height_ != height) {
|
||||
printf("V4l2Capture: warning: requested %ux%u but got %ux%u\n",
|
||||
width, height, width_, height_);
|
||||
}
|
||||
if (pixelformat_ != pixelformat) {
|
||||
char req[5] = {0}, got[5] = {0};
|
||||
req[0] = pixelformat & 0xFF; req[1] = (pixelformat>>8)&0xFF;
|
||||
req[2] = (pixelformat>>16)&0xFF; req[3] = (pixelformat>>24)&0xFF;
|
||||
got[0] = pixelformat_ & 0xFF; got[1] = (pixelformat_>>8)&0xFF;
|
||||
got[2] = (pixelformat_>>16)&0xFF; got[3] = (pixelformat_>>24)&0xFF;
|
||||
printf("V4l2Capture: warning: requested '%s' but got '%s'\n", req, got);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::setFps(uint32_t fps) {
|
||||
if (fd_ < 0) return false;
|
||||
|
||||
struct v4l2_streamparm parm;
|
||||
std::memset(&parm, 0, sizeof(parm));
|
||||
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_G_PARM, &parm) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_G_PARM failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试设置 FPS. 需要 V4L2_CAP_TIMEPERFRAME 支持
|
||||
if (parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
|
||||
parm.parm.capture.timeperframe.numerator = 1;
|
||||
parm.parm.capture.timeperframe.denominator = fps;
|
||||
if (ioctl(fd_, VIDIOC_S_PARM, &parm) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_S_PARM fps=%u failed: %s\n", fps, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
printf("V4l2Capture: set fps=%u (actual %u/%u)\n",
|
||||
fps,
|
||||
parm.parm.capture.timeperframe.denominator,
|
||||
parm.parm.capture.timeperframe.numerator);
|
||||
} else {
|
||||
printf("V4l2Capture: device does not support V4L2_CAP_TIMEPERFRAME\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::setControl(uint32_t id, int32_t value) {
|
||||
if (fd_ < 0) return false;
|
||||
|
||||
struct v4l2_control ctrl;
|
||||
std::memset(&ctrl, 0, sizeof(ctrl));
|
||||
ctrl.id = id;
|
||||
ctrl.value = value;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_S_CTRL, &ctrl) < 0) {
|
||||
// 有些控制可能是只读的或不存在, 打印警告但不失败
|
||||
// 继续尝试 VIDIOC_TRY_CTRL 看是否可用
|
||||
struct v4l2_ext_controls ext_ctrls = {};
|
||||
struct v4l2_ext_control ext_ctrl = {};
|
||||
ext_ctrl.id = id;
|
||||
ext_ctrl.value = value;
|
||||
ext_ctrls.count = 1;
|
||||
ext_ctrls.controls = &ext_ctrl;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_TRY_EXT_CTRLS, &ext_ctrls) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_S_CTRL id=0x%08x value=%d failed: %s (ignored)\n",
|
||||
id, value, strerror(errno));
|
||||
return false;
|
||||
} else {
|
||||
printf("V4l2Capture: VIDIOC_S_CTRL id=0x%08x value=%d not supported (ignored)\n", id, value);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
printf("V4l2Capture: set control id=0x%08x value=%d\n", id, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::initMmap(uint32_t buffer_count) {
|
||||
if (fd_ < 0) return false;
|
||||
|
||||
// 请求 MMAP 缓冲区
|
||||
struct v4l2_requestbuffers req;
|
||||
std::memset(&req, 0, sizeof(req));
|
||||
req.count = buffer_count;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_REQBUFS, &req) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_REQBUFS failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (req.count < buffer_count) {
|
||||
printf("V4l2Capture: only %u buffers available (requested %u)\n", req.count, buffer_count);
|
||||
buffer_count = req.count;
|
||||
}
|
||||
printf("V4l2Capture: allocated %u MMAP buffers\n", (unsigned)req.count);
|
||||
|
||||
buffers_.resize(buffer_count);
|
||||
for (uint32_t i = 0; i < buffer_count; ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_QUERYBUF %u failed: %s\n", i, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
buffers_[i].length = buf.length;
|
||||
buffers_[i].start = mmap(nullptr, buf.length,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
fd_, buf.m.offset);
|
||||
if (buffers_[i].start == MAP_FAILED) {
|
||||
printf("V4l2Capture: mmap buffer %u failed: %s\n", i, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
buffers_[i].queued = false;
|
||||
printf("V4l2Capture: buffer %u: addr=%p len=%zu\n",
|
||||
i, buffers_[i].start, buffers_[i].length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::startStreaming() {
|
||||
if (fd_ < 0 || streaming_) return false;
|
||||
|
||||
// 把所有缓冲区入队
|
||||
for (uint32_t i = 0; i < buffers_.size(); ++i) {
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
buf.bytesused = 0; // 驱动会填充
|
||||
|
||||
if (ioctl(fd_, VIDIOC_QBUF, &buf) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_QBUF %u failed: %s\n", i, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
buffers_[i].queued = true;
|
||||
}
|
||||
|
||||
// 启动流
|
||||
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(fd_, VIDIOC_STREAMON, &type) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_STREAMON failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
streaming_ = true;
|
||||
printf("V4l2Capture: streaming started\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::stopStreaming() {
|
||||
if (fd_ < 0 || !streaming_) return false;
|
||||
|
||||
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(fd_, VIDIOC_STREAMOFF, &type) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_STREAMOFF failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
streaming_ = false;
|
||||
last_dequeued_ = -1;
|
||||
printf("V4l2Capture: streaming stopped\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool V4l2Capture::getFrame(Frame &frame, int timeout_ms) {
|
||||
if (fd_ < 0 || !streaming_) { reconnect_needed_ = true; return false; }
|
||||
reconnect_needed_ = false;
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd_, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
int r = select(fd_ + 1, &fds, nullptr, nullptr, &tv);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) return getFrame(frame, timeout_ms); // 重试
|
||||
reconnect_needed_ = true;
|
||||
printf("V4l2Capture: select failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (r == 0) {
|
||||
// 超时
|
||||
return false;
|
||||
}
|
||||
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(fd_, VIDIOC_DQBUF, &buf) < 0) {
|
||||
if (errno == EAGAIN || errno == EIO) {
|
||||
return false; // 再试
|
||||
}
|
||||
reconnect_needed_ = true;
|
||||
printf("V4l2Capture: VIDIOC_DQBUF failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果之前有未归还的缓冲区, 先归还
|
||||
if (last_dequeued_ >= 0) {
|
||||
releaseFrameByIndex(last_dequeued_);
|
||||
}
|
||||
|
||||
uint32_t idx = buf.index;
|
||||
if (idx >= buffers_.size()) {
|
||||
reconnect_needed_ = true;
|
||||
printf("V4l2Capture: invalid buffer index %u\n", idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
last_dequeued_ = idx;
|
||||
if ((buf.flags & V4L2_BUF_FLAG_ERROR) || buf.bytesused > buffers_[idx].length) {
|
||||
releaseFrameByIndex(idx);
|
||||
last_dequeued_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.data = buffers_[idx].start;
|
||||
frame.size = buf.bytesused;
|
||||
frame.width = width_;
|
||||
frame.height = height_;
|
||||
frame.pixelformat = pixelformat_;
|
||||
frame.bytes_per_line = bytes_per_line_;
|
||||
frame.sequence = buf.sequence;
|
||||
frame.timestamp_us = (uint64_t)buf.timestamp.tv_sec * 1000000ULL + buf.timestamp.tv_usec;
|
||||
frame.index = idx;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void V4l2Capture::releaseFrame(const Frame &frame) {
|
||||
if (frame.index >= 0) {
|
||||
releaseFrameByIndex(frame.index);
|
||||
last_dequeued_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void V4l2Capture::releaseFrameByIndex(int idx) {
|
||||
if (idx < 0 || (uint32_t)idx >= buffers_.size()) return;
|
||||
struct v4l2_buffer buf;
|
||||
std::memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = idx;
|
||||
if (ioctl(fd_, VIDIOC_QBUF, &buf) < 0) {
|
||||
printf("V4l2Capture: VIDIOC_QBUF %d failed: %s\n", idx, strerror(errno));
|
||||
}
|
||||
buffers_[idx].queued = true;
|
||||
}
|
||||
@@ -14,13 +14,6 @@ void runLocalVisualization(CameraPipeline const& pipeline) {
|
||||
while(pipeline.running()) {
|
||||
auto result=pipeline.latest();
|
||||
if(result&&result!=shown){cv::imshow(windowName,renderResult(*result));shown=std::move(result);}
|
||||
if(!result) {
|
||||
auto state=pipeline.inferenceStatus();
|
||||
cv::Mat idle(800,1000,CV_8UC3,cv::Scalar(20,24,30));
|
||||
cv::putText(idle,state.enabled?"WAITING FOR FRESH RESULT":"INFERENCE IDLE - MODELS READY",{35,100},cv::FONT_HERSHEY_SIMPLEX,.85,{235,235,235},2);
|
||||
cv::putText(idle,state.reason,{35,150},cv::FONT_HERSHEY_SIMPLEX,.7,{0,190,255},2);
|
||||
cv::imshow(windowName,idle);shown.reset();
|
||||
}
|
||||
int key=cv::waitKey(16)&0xff;
|
||||
if(key==27||key=='q'||key=='Q'||cvGetWindowHandle(windowName)==nullptr)return;
|
||||
}
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
test_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
"${CXX:-g++}" -std=c++17 -O1 -g -fsanitize=undefined -fno-sanitize-recover=all -I"$root/include" "$root/src/v4l2_capture.cpp" "$root/tests/v4l2_reopen_test.cpp" -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=ioctl -Wl,--wrap=mmap -Wl,--wrap=munmap -o "$test_dir/v4l2_reopen_test"
|
||||
"$test_dir/v4l2_reopen_test"
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "v4l2_capture.h"
|
||||
#include <linux/videodev2.h>
|
||||
#include <sys/mman.h>
|
||||
#include <cstdarg>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
static int mappings=0;static bool failSecondMap=false;
|
||||
extern "C" {
|
||||
int __wrap_open(char const*,int,...){return 50;}
|
||||
int __wrap_close(int){return 0;}
|
||||
void* __wrap_mmap(void*,size_t n,int,int,int,off_t){if(failSecondMap&&mappings==1){errno=ENOMEM;return MAP_FAILED;}++mappings;return std::malloc(n);}
|
||||
int __wrap_munmap(void* p,size_t){if(!p||p==MAP_FAILED||mappings<1)std::abort();std::free(p);--mappings;return 0;}
|
||||
int __wrap_ioctl(int,unsigned long req,...){
|
||||
va_list args;va_start(args,req);void* p=va_arg(args,void*);va_end(args);
|
||||
if(req==VIDIOC_QUERYCAP){*static_cast<v4l2_capability*>(p)={};static_cast<v4l2_capability*>(p)->capabilities=V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING;return 0;}
|
||||
if(req==VIDIOC_S_FMT){if(mappings){errno=EBUSY;return -1;}static_cast<v4l2_format*>(p)->fmt.pix.bytesperline=4;return 0;}
|
||||
if(req==VIDIOC_QUERYBUF){static_cast<v4l2_buffer*>(p)->length=8;return 0;}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static void check(bool b){if(!b)throw std::runtime_error("V4L2 reopen regression");}
|
||||
int main()try{
|
||||
V4l2Capture camera;
|
||||
for(int i=0;i<3;++i){check(camera.open("/mock"));check(camera.setFormat(2,2,V4L2_PIX_FMT_UYVY));check(camera.initMmap(2));check(camera.startStreaming());camera.close();check(mappings==0);}
|
||||
check(camera.open("/mock"));check(camera.setFormat(2,2,V4L2_PIX_FMT_UYVY));failSecondMap=true;check(!camera.initMmap(2));camera.close();check(mappings==0);
|
||||
std::cout<<"PASS repeated reopen and partial mmap failure release every mapping\n";return 0;
|
||||
}catch(std::exception const& e){std::cerr<<e.what()<<'\n';return 1;}
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
MIT License
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-2022 Niels Lohmann
|
||||
|
||||
|
||||
Reference in New Issue
Block a user