From dc62cf066cc936e438e5bd592e738a19ef57ce8a Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sun, 2 Aug 2026 11:15:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E8=A2=AB=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E7=9A=84=20Readme.md(PRD)=EF=BC=8C=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=A7=BB=E8=87=B3=20docs/README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows 大小写不敏感导致新 README.md 覆盖了原有 Readme.md(PRD 1305行) - 从 git 历史恢复 PRD,导航内容另存为 docs/README.md --- Readme.md | 1336 ++++++++++++++++++++++++++++++++++++++++++-- docs/README.md | 73 +++ docs/deployment.md | 1 + 3 files changed, 1358 insertions(+), 52 deletions(-) create mode 100644 docs/README.md diff --git a/Readme.md b/Readme.md index 0e057ae..0cb333a 100644 --- a/Readme.md +++ b/Readme.md @@ -1,73 +1,1305 @@ -# SafeSight — AI 视觉安全监测平台 -> **Safe Sight, Safe Production.** -> 以 AI 视觉算法驱动的工业安全生产监测平台。 +# SafeSight 智能视觉安全监测平台 PRD(v1.2) -## 仓库结构(单仓库) +--- -``` -safesight-edge/ ← 唯一代码仓库(历史来自 safesight-edge + safesight-control) -├── VERSION ← 设备端主版本号(发版修改) -├── agent/ → 边缘代理(Go):UDP 发现、模型管理、agent API -├── include/ src/ → 媒体引擎(C++):视频 DAG 流水线、NPU 推理、推流报警 -├── scripts/ → 设备端部署/运维脚本(deploy.sh、setup-overlayroot.sh 等) -├── control/ → 管理端(原 safesight-control 仓库,完整保留) -│ ├── VERSION ← 管理端主版本号 -│ ├── cmd/ internal/ → safesightd:Web UI / HTTP API / 设备管理 -│ └── scripts/ → 管理端构建/打包脚本 -├── docs/ → 产品级文档(部署手册、检查表、离线部署) -│ ├── deployment.md ← Edge 设备部署手册(唯一操作手册) -│ ├── control-deployment.md ← 管理端部署指南 -│ ├── 客户现场部署检查表.md -│ └── 离线部署文档.md -└── third_party/ models/ web/ → 设备端依赖与资源 +## 一、文档综述 + +### 1.1 项目背景 + +为了充分利用瑞芯微 RK3588 芯片提供的: + +- **NPU:约 6 TOPS**(RKNN,INT8 推理能力) +- **VPU:多路 1080p 硬件解码/编码** +- **RGA:硬件图像处理(缩放/颜色转换/OSD 等)** + +构建一套**纯 C++ 实现、高性能、低延迟、模块化**的边缘计算视频分析平台。平台主要部署在园区、仓库、工厂等场景,实现就地智能分析与处理,减少带宽与中心算力压力。 + +### 1.2 核心目标 + +- **高性能** + - 单 RK3588 支持 8–16 路 1080p 实时分析 + - 单路端到端(拉流→推理→输出)延迟 < 500ms +- **低耦合** + - 基于插件 + DAG 流水线架构 + - 算法、业务逻辑与底层框架完全解耦 +- **易运维** + - JSON 配置驱动 + - 支持热更新,无需重启进程即可变更流程/参数 +- **多场景覆盖** + - 同一套框架通过不同配置图覆盖: + - 安防报警 + - 录像存储 + - 转码网关(RTSP/HLS) + - 混合场景(报警 + 存储 + 推流) + +--- + +## 二、总体架构设计 + +### 2.1 系统逻辑架构 + +```text +[ safesight-media 主进程 ] + | + +-- Graph Manager (图管理:加载配置、构建DAG、热更新/回滚) + | + +-- Plugin Loader (插件加载:dlopen .so,检查 ABI 版本) + | + +-- AiScheduler (统一调度 NPU 推理请求) + | + +-- Metrics & Logger (监控指标、日志) + | + +-- HTTP Server (REST API + Web 控制台 + 状态查询) ``` -## 快速导航 +- **safesight-media**:主入口,负责初始化配置、图构建、主事件循环。 +- **Graph Manager(GraphMgr)**: + - 对应多个 graph(每个 graph 一路/一组业务流程)。 + - 实例化节点(Node),建立边(Edge),负责热更新时的 diff & 切换。 +- **Plugin Loader**: + - 动态加载插件 `.so`,校验节点类型和 ABI 版本。 +- **AiScheduler**: + - 所有 AI 节点统一将推理任务提交到 AiScheduler,由其管理 NPU 资源。 +- **HTTP Server**: + - 对外提供图状态查询、节点指标、配置热更新触发等。 -| 想做什么 | 看这里 | -|---|---| -| 产品镜像制作 / 批量部署 / 断电保护 | `docs/deployment.md`(主手册) | -| 现场施工清单 | `docs/客户现场部署检查表.md` | -| 管理端安装打包 | `docs/control-deployment.md` | -| 设备端部署脚本 | `scripts/deploy.sh`(部署到设备后位于 /opt/safesight-edge-server/scripts/) | -| 管理端构建 | `control/scripts/package.sh` | +### 2.2 数据流架构(DAG) -## 版本机制(双层) +- **Graph(图)**:一个完整业务通道。例如:Camera 1 的“拉流→AI→报警+录像+推流”。 +- **Node(节点)**:功能基本单元,由动态加载的 `.so` 实例实现。 +- **Edge(边)**:节点间的数据通道,每条边对应一个 **SPSC(单生产者单消费者)环形队列**。 +- **多分支支持**:一个节点可以有多个下游(多条边),实现一对多分支。 -- **主版本号**:各端根目录 `VERSION` 文件(如 `1.0.0`),发版改文件即可,不依赖 git tag -- **构建号**:构建时自动注入 `YYYYMMDD.HHMMSS`,区分大量小版本迭代 -- **git sha**:自动注入,精确定位代码 +示意: -设备端列表页显示 `V1.0.0`,详情页显示 `V1.0.0 (build 20260801.171659 · 923ab10)`。 +```text +[in] → [pre] → [ai_yolo] → [det_post] ───→ [alarm] + └──────→ [osd] → [storage] + └──→ [publish] +``` -## 合并历史说明 +> 说明:`det_post` 是检测结果后处理节点(可选)。它会对 `frame->det` 做过滤/修正/二次验证,使 OSD 与告警读取到一致的“后处理结果”。 -本仓库由 `safesight-edge`(设备端)与 `safesight-control`(管理端)通过 `git subtree` 合并: -- 仓库根 = 原 safesight-edge(其 git 历史、tag v1.0.0 保留) -- `control/` = 原 safesight-control(其 git 历史、tag v1.0.0 保留) -- 合并提交:`98e375d` +### 2.3 多硬件解耦架构(已完成) -## 快速开始 +为支持 RK3588 之外的平台(Atlas/Jetson 等),已将硬件强绑定模块抽象为统一接口层,并保留 RK3588 默认实现与 DMA-BUF 快路径: -### 管理端(control/) +- **推理**:`IInferBackend`(默认 `Rk3588InferBackend`) +- **图像处理**:`IImageProcessor`(默认 `Rk3588ImageProcessor`,RGA/Swscale 兜底) +- **编解码**:`IDecoder` / `IEncoder`(默认 RK3588 MPP 路径) +- **缓冲区**:`FrameBuffer` 统一 DMA/内存同步语义 + +运行时通过 `HwFactory` 根据配置选择后端实现: + +- 配置字段:`platform` / `hw_platform`(不填默认 `rk3588`) +- 可扩展实现:`Atlas*` / `Jetson*`(保持现有业务配置不变,仅切换硬件实现) + +--- + +## 三、数据模型(Frame & 元数据) + +### 3.1 检测结果结构(Detection & DetectionResult) + +> 约定:**所有 AI 节点写入此结构**,OSD / 报警 等节点只读。 + +```cpp +struct Rect { + float x; // 左上角X,像素或归一化坐标 + float y; // 左上角Y + float w; // 宽 + float h; // 高 +}; + +struct Detection { + int cls_id; // 类别ID + float score; // 置信度 [0,1] + Rect bbox; // 检测框 + int track_id; // 跟踪ID,可选(无则 -1 或 0) +}; + +struct DetectionResult { + std::vector items; + int img_w; // 推理输入宽 + int img_h; // 推理输入高 + std::string model_name; // 模型名/版本 +}; +``` + +**约定:** + +- `bbox` 坐标标准在 PRD 中约定好(建议:像素坐标),避免后续插件互相搞混。 +- class 映射表由 AI 节点自行维护,OSD/报警通过 `cls_id` 做规则匹配。 + +### 3.2 Frame 结构 + +```cpp +enum class PixelFormat { + NV12, + YUV420, + RGB, + BGR, + UNKNOWN +}; + +struct Frame { + int width; + int height; + PixelFormat format; + + int dma_fd; // DMA-BUF FD;无则为 -1 + uint8_t* data; // CPU 内存指针(可选,有则指向内存buffer) + + uint64_t pts; // 时间戳(微秒) + uint64_t frame_id; // 单调递增ID + + std::shared_ptr det; // AI 检测结果(通用目标检测) + std::shared_ptr face_det; // 人脸检测结果 + std::shared_ptr face_recog;// 人脸识别结果 + std::shared_ptr user_meta; // 扩展元数据(具体类型由插件间约定) +}; +``` + +**约定:** + +- **零拷贝优先**:解码/前处理尽量通过 `dma_fd` 传递,必要时才用 data。 +- 节点默认只读图像数据,**可以更新 `det` / `face_det` / `face_recog` / `user_meta`**。 +- 所有队列中传递的都是 `std::shared_ptr`,实现多下游共享。 + +### 3.3 人脸检测结果结构(FaceDetResult) + +```cpp +struct Point2f { + float x; + float y; +}; + +struct FaceDetItem { + Rect bbox; // 人脸框 + float score; // 检测置信度 + int track_id; // 跟踪ID(可选,-1表示无) + bool has_landmarks; // 是否有关键点 + std::array landmarks; // 5点关键点:左眼/右眼/鼻尖/左嘴角/右嘴角 +}; + +struct FaceDetResult { + std::vector faces; + int img_w; // 原始图像宽 + int img_h; // 原始图像高 + std::string model_name; // 模型名(如 "retinaface") +}; +``` + +### 3.4 人脸识别结果结构(FaceRecogResult) + +```cpp +struct FaceRecogItem { + Rect bbox; // 人脸框(与检测结果对应) + int best_person_id; // 匹配的人员ID(-1表示陌生人) + std::string best_name; // 匹配的人员姓名("unknown"表示陌生人) + float best_sim; // 最高相似度 + float second_sim; // 次高相似度(用于margin判定) + bool unknown; // 是否为陌生人 + std::vector embedding; // 特征向量(可选,调试/注册用) +}; + +struct FaceRecogResult { + std::vector items; + int img_w; + int img_h; + std::string model_name; // 模型名(如 "arcface") +}; +``` + +--- + +## 四、插件模型与生命周期 + +### 4.1 节点通用字段(Node 通用配置) + +每个节点配置中推荐包含以下通用字段: + +```json +{ + "id": "唯一节点ID", + "type": "节点类型,如 input_rtsp / ai_yolo", + "enable": true, + "role": "source|filter|sink", // 可选,但建议写上,便于校验 + "queue": { // 可选:覆盖边上的默认队列策略 + "size": 128, + "policy": "drop_oldest" // drop_oldest / drop_newest / block + }, + "debug": { // 可选:调试/日志开关(生产默认建议关闭) + "stats": false, // 各节点的周期性统计日志(processed/queue/drops 等) + "stats_interval": 100, // 统计日志间隔(帧数) + "detections": false, // 部分 AI 节点的前几帧检测框调试输出 + "ffmpeg_log_level": "error" // FFmpeg 日志级别(影响 HLS/RTSP mux 的内部日志;默认 error) + }, + "...特定节点参数..." +} +``` + +- `enable = false`:GraphMgr 构建图时可直接忽略该节点及相关 edges。 +- `role` 主要用于配置校验(source 不应有上游,sink 不应有下游等)。 + +**日志/性能建议:** + +- 生产环境建议:`global.log_level=warn`,并保持各节点 `debug.stats=false`,避免高频 stdout/stderr 造成 CPU 抖动。 +- 需要定位丢帧/队列积压时,再对目标节点打开 `debug.stats=true`(并把 `stats_interval` 调大,如 500/1000)。 + +### 4.2 INode 接口(含 Drain) + +```cpp +// NodeStatus 用于描述处理结果 +enum class NodeStatus { + OK, // 正常处理 + DROP, // 丢弃该帧(例如不满足条件) + ERROR // 发生错误,需要记录日志 +}; + +class INode { +public: + virtual ~INode() = default; + + // 1. 初始化:解析 JSON 配置,申请静态资源 + virtual bool Init(const Json::Value& config) = 0; + + // 2. 启动:启动内部线程(如有),准备接收/发送数据 + virtual bool Start() = 0; + + // 3. 处理数据:GraphMgr 从队列中取到 Frame 后调用 + // - 对 filter/sink 节点:正常消费上游 Frame + // - 对 source 节点:通常可忽略(见下文约定) + virtual NodeStatus Process(std::shared_ptr frame) = 0; + + // 4. 动态配置更新:支持热更参数(如改变报警阈值、bitrate) + // 返回 true 表示就地更新成功,无需重建节点 + virtual bool UpdateConfig(const Json::Value& new_config) { return false; } + + // 5. Drain:停止前,**不再有新输入**,但需处理完内部队列/缓冲数据 + // 如:storage 完成当前切片写入,publish 发送剩余帧等 + virtual void Drain() {} + + // 6. 停止:释放资源(线程、句柄、内存等),Drain 之后调用 + virtual void Stop() = 0; +}; +``` + +### 4.3 插件导出与 ABI 版本 + +```cpp +#define ABI_VERSION 20250101 + +#define REGISTER_NODE(TYPE_NAME, CLASS_NAME) \ +extern "C" { \ + INode* CreateNode() { \ + return new CLASS_NAME(); \ + } \ + const char* GetNodeType() { \ + return TYPE_NAME; \ + } \ + int GetAbiVersion() { \ + return ABI_VERSION; \ + } \ + const char* GetNodeDescription() { \ + return "node: " TYPE_NAME; \ + } \ +} +``` + +GraphMgr 在加载插件 `.so` 时: + +- 调用 `GetAbiVersion()` 校验兼容性。 +- 通过 `GetNodeType()` 找到对应 `type` 的构造函数。 + +### 4.4 Source 节点约定 + +为避免把所有采集逻辑都硬塞进 `Process()`,PRD 中统一约定: + +- `role == "source"` 的节点(如 `input_rtsp` / `input_file`): + - 在 `Start()` 中启动**内部采集线程**。 + - 线程中从外部拉流/读文件,构造 `Frame`,通过框架提供的 `FrameEmitter` 写入下游队列。 + - `Process()` 可以是空实现或仅做心跳/监控。 +- `role != "source"` 的节点: + - 由 GraphMgr 驱动 `Process(frame)`,消费上游队列元素。 + +GraphMgr 负责: + +- 对非 source 节点:循环从对应队列取 `Frame` → 调用 `Process()`。 +- 对所有节点:在热更新 / 停止时,控制 `Drain()` 和 `Stop()` 的调用顺序。 + +--- + +## 五、配置系统(Config System) + +整个系统由 `config.json` 驱动,主要包含三层: + +- `global`:全局配置 +- `templates`:pipeline 模板(复用节点与拓扑) +- `instances` / `graphs`:具体业务实例(通道) + +### 5.1 顶层结构示例 + +```json +{ + "global": { + "plugin_path": "/usr/lib/rknodes", + "log_level": "info", + "metrics_port": 9000 + }, + "templates": { ... }, + "instances": [ ... ], + "graphs": [ ... ] // 可选:直接手写graph +} +``` + +> 说明:`global.log_level` 只控制框架内部 Logger(debug/info/warn/error); +> 若启用了 FFmpeg(例如 publish 的 HLS mux),建议通过节点 `debug.ffmpeg_log_level` 将 FFmpeg 内部日志降到 `error`, +> 避免出现大量类似 `hls ... Opening ...` 的 info 日志刷屏。 + +- **优先方式**:使用 `templates + instances` 快速生成多路通道。 +- **高级用法**:直接使用 `graphs` 写复杂拓扑(例如:一条流拆成多个完全不同的分支)。 + +### 5.2 DAG 多分支完整示例(带报警 + 存储 + 推流) + +```json +{ + "graphs": [ + { + "name": "cam1_pipeline", + "nodes": [ + { + "id": "in_cam1", + "type": "input_rtsp", + "role": "source", + "enable": true, + "url": "rtsp://user:pwd@ip/stream1" + }, + { + "id": "pre_cam1", + "type": "preprocess", + "role": "filter", + "enable": true, + "dst_w": 768, + "dst_h": 768, + "dst_format": "rgb", + "resize_mode": "letterbox" + }, + { + "id": "ai_cam1", + "type": "ai_yolo", + "role": "filter", + "enable": true, + "model_path": "./models/ppe_det_yolov8_ppe11_768_rk3588.rknn", + "model_version": "v8", + "model_w": 768, + "model_h": 768, + "num_classes": 11, + "conf": 0.35, + "nms": 0.45, + "class_filter": [3, 6] + }, + { + "id": "pp_cam1", + "type": "det_post", + "role": "filter", + "enable": true, + "default": { "enable": false }, + "per_class": [ + { + "class_id": 0, + "enable": true, + "processors": [ + { "type": "conf_gate", "conf_min": 0.65 }, + { "type": "bbox_gate", "min_w": 10, "min_h": 10 } + ] + } + ] + }, + { + "id": "osd_cam1", + "type": "osd", + "role": "filter", + "enable": true, + "draw_bbox": true, + "draw_text": true + }, + { + "id": "alarm_cam1", + "type": "alarm", + "role": "sink", + "enable": true, + "rules": [ + { + "name": "intrusion_day", + "object": ["person"], + "roi": "roi1", + "min_duration_ms": 500, + "schedule": "08:00-20:00" + } + ] + }, + { + "id": "storage_cam1", + "type": "storage", + "role": "sink", + "enable": true, + "mode": "continuous", + "segment_sec": 300, + "path": "/rec/cam1" + }, + { + "id": "publish_cam1", + "type": "publish", + "role": "sink", + "enable": true, + "codec": { + "video": "h264", + "bitrate_kbps": 2048, + "gop": 50 + }, + "outputs": [ + { "proto": "rtsp", "port": 8554, "path": "/live/cam1" }, + { "proto": "hls", "path": "/data/hls/cam1/index.m3u8", "segment_sec": 2 } + ] + } + ], + "edges": [ + ["in_cam1", "pre_cam1"], + ["pre_cam1", "ai_cam1"], + ["ai_cam1", "pp_cam1"], + ["pp_cam1", "alarm_cam1"], + ["pp_cam1", "osd_cam1"], + ["osd_cam1", "storage_cam1"], + ["osd_cam1", "publish_cam1"] + ] + } + ] +} +``` + +> 说明:如果不需要后处理: +> +> - **推荐**:保留 `pp_cam1` 节点(`enable:true`),并确保其不启用任何处理链(例如:`default.enable=false` 且 `per_class` 为空),此时 `det_post` 会作为轻量透传。 +> - 若确实要移除该节点:需要**同时删除并重连拓扑**(例如改回 `ai_cam1 -> osd_cam1` 与 `ai_cam1 -> alarm_cam1`),仅设置 `enable=false` 会导致下游节点无输入而构图失败。 + +通过简单改动即可支持不同组合: + +- 不报警:`alarm_cam1.enable = false` 或移除该节点/边。 +- 只转码网关:直接连 `["in_cam1", "publish_cam1"]`,并禁用 AI/OSD/报警/storage。 +- 只录像:`["in_cam1", "storage_cam1"]`。 + +### 5.3 模板(templates)与实例(instances) + +#### 5.3.1 模板示例:标准安防流程 + +```json +"templates": { + "standard_security": { + "nodes": [ + { "id": "in", "type": "input_rtsp", "role": "source", "url": "${url}" }, + { "id": "pre", "type": "preprocess", "role": "filter", "dst_w": 768, "dst_h": 768, "resize_mode": "letterbox" }, + { "id": "ai", "type": "ai_yolo", "role": "filter", "model_path": "./models/ppe_det_yolov8_ppe11_768_rk3588.rknn", "model_version": "v8", "num_classes": 11, "conf": 0.35, "class_filter": [3, 6] }, + { "id": "pp", "type": "det_post", "role": "filter", "enable": true, "default": { "enable": false } }, + { "id": "osd", "type": "osd", "role": "filter" }, + { "id": "pub", "type": "publish", "role": "sink", "path": "/live/${name}" } + ], + "edges": [ + ["in", "pre"], + ["pre", "ai"], + ["ai", "pp"], + ["pp", "osd"], + ["osd", "pub"] + ] + } +} +``` + +#### 5.3.2 实例与 override(禁用某些节点) + +```json +"instances": [ + { + "name": "cam_01", + "template": "standard_security", + "params": { + "url": "rtsp://192.168.1.101/stream1", + "name": "office" + }, + "override": { + "nodes": { + "ai": { "enable": true }, + "osd": { "enable": true } + } + } + }, + { + "name": "cam_02_only_transcode", + "template": "standard_security", + "params": { + "url": "rtsp://192.168.1.102/stream1", + "name": "transcoder" + }, + "override": { + "nodes": { + "ai": { "enable": false }, + "osd": { "enable": false } + } + } + } +] +``` + +> GraphMgr 在加载时将模板 + 实例 merge 成具体的 graph(节点 ID 会按实例名做前缀防冲突)。 + +### 5.4 热更新与回滚(简要) + +- 通过 inotify 监控 `config.json`。 +- 检测到变更时: + 1. 读取新配置 → JSON Schema 校验; + 2. 构建新的图对象(dry run),包括插件加载与节点实例化; + 3. 与旧图做 diff: + - 能 `UpdateConfig` 的节点优先调用 `UpdateConfig`; + - 需要重建的节点:先创建新节点并预热(如加载模型),然后做 **边的原子切换**; + 4. 对旧节点:依次调用 `Drain()` → `Stop()`; + 5. 若任意步骤失败:回滚到旧配置,并输出错误日志。 +- 保留上一份有效配置,支持手动回滚。 + +--- + +## 六、功能模块定义(插件类型) + +> 下表是“典型插件类型”,后续可以扩展更多。 + +### 6.1 输入与前处理 + +| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 | +| :------------- | :------------------------------- | :------------------------- | :--------------- | +| `input_rtsp` | 拉取 RTSP 流,断线重连 | `url`, `reconnect_sec` | MPP VDEC / 网络 | +| `input_file` | 读取本地 MP4/MKV 文件循环播放 | `path`, `loop` | MPP VDEC | +| `preprocess` | 缩放、裁剪、NV12→RGB/BGR 等 | `dst_w`, `dst_h`, `dst_format`, `resize_mode` | RGA / GPU | + +#### Preprocess 节点参数说明 + +**关键参数:** + +| 参数 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `dst_w` | int | 输出宽度 | 640 | +| `dst_h` | int | 输出高度 | 640 | +| `dst_format` | string | 输出格式: `rgb`, `bgr`, `nv12`, `yuv420` | 保持原格式 | +| `resize_mode` | string | 缩放模式: `stretch`, `keep_ratio`, `letterbox` | `stretch` | +| `use_rga` | bool | 是否使用 RGA 硬件加速 | true | + +**resize_mode 说明:** + +- `stretch`: 拉伸填充,可能改变目标宽高比 +- `keep_ratio`: 保持比例缩放,可能无法填满目标尺寸 +- `letterbox`: 保持比例缩放,边缘填充黑色(推荐,避免目标变形) + +当使用 `letterbox` 模式时,系统会自动记录变换元数据(`FrameTransformMeta`),后续节点(如 `ai_yolo`、`osd`)会自动处理坐标映射,无需手动转换。 + +### 6.2 AI 分析 + +| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 | +| :-------------- | :------------------------- | :----------------------------------- | :------- | +| `ai_yolo` | 通用目标检测(YOLOv5/v8) | `model_path`, `model_version`, `num_classes`, `conf`, `nms`, `class_filter` | NPU(RKNN) | + +**ai_yolo 配置示例(ppe_det_yolov8_ppe11_768_rk3588.rknn):** + +```json +{ + "id": "yolo_cam1", + "type": "ai_yolo", + "model_path": "./models/ppe_det_yolov8_ppe11_768_rk3588.rknn", + "model_version": "v8", + "model_w": 768, + "model_h": 768, + "num_classes": 11, + "conf": 0.35, + "nms": 0.45, + "class_filter": [3, 6] +} +``` + +**关键参数:** + +| 参数 | 类型 | 说明 | +|------|------|------| +| `model_path` | string | RKNN 模型路径 | +| `model_version` | string | 模型版本: `v5` 或 `v8` | +| `num_classes` | int | 模型类别数(必须与实际一致) | +| `conf` | float | 置信度阈值(0-1) | +| `nms` | float | NMS IoU 阈值(0-1) | +| `class_filter` | array | 只输出指定类别的检测结果,如 `[3, 6]` 表示只检测 boots 和 Person | +| `ai_face_det` | 人脸检测(含5点关键点) | `model_path`, `conf`, `nms`, `max_faces`, `output_landmarks` | NPU(RKNN) | +| `ai_face_recog` | 人脸识别(对齐+特征提取+检索) | `model_path`, `gallery`, `threshold`, `align` | NPU(RKNN) | +| `ai_pose` | 关键点检测(可选扩展) | `model_path` | NPU | +| `ai_custom` | 自定义 AI 算法 | `model_path`, `custom_param` 等 | NPU | + +#### 6.2.1 det_post(检测结果后处理,按类别策略链) + +`det_post` 是一个 **Filter** 节点,位于 `ai_yolo` 之后,负责对 `frame->det->items` 做**按类别可配置**的后处理。 + +**设计目标:** + +- 不同类别可以挂不同的处理器链(Processor/Strategy Registry),避免“一个类别一个插件”。 +- 默认改写 `frame->det`,让 `osd` / `alarm` 读到一致结果。 + +**配置示例:** + +```json +{ + "id": "pp_cam1", + "type": "det_post", + "role": "filter", + "enable": true, + + "default": { "enable": false }, + "per_class": [ + { + "class_id": 3, + "enable": true, + "processors": [ + { "type": "conf_gate", "conf_min": 0.65 }, + { "type": "bbox_gate", "min_w": 10, "min_h": 10 }, + { "type": "roi_gate", "roi": { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 }, "mode": "center" } + ] + }, + { + "class_id": 5, + "enable": true, + "processors": [ + { "type": "hsv_ratio", "h_min": 0, "h_max": 20, "s_min": 20, "v_min": 70, "ratio_min": 0.7, + "max_samples": 2048, "step": 0, "min_samples": 32, + "crop": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 } } + ] + }, + { + "class_id": 7, + "enable": true, + "processors": [ + { "type": "lab_kmeans", "k": 3, "iters": 8, "l_max": 50, "ratio_min": 0.4, + "max_samples": 2048, "step": 0, "min_samples": 64, + "crop": { "x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0 } } + ] + } + ] +} +``` + +**内置 processors(当前实现):** + +- `conf_gate`:按置信度过滤(`conf_min`) +- `bbox_gate`:按框尺寸过滤(`min_w`, `min_h`) +- `roi_gate`:按 ROI 过滤(`roi:{x,y,w,h}` 归一化;`mode:center` 使用 bbox 中心点判断) +- `hsv_ratio`:框内采样像素转 HSV,统计阈值命中占比(`h_min/h_max/s_min/s_max/v_min/v_max/ratio_min`,支持 `crop/max_samples/step`) +- `lab_kmeans`:框内采样像素转 Lab,KMeans 聚类后取最暗簇做判定(`k/iters/l_max/ratio_min`,支持 `crop/max_samples/step`) + +> 注意:`hsv_ratio/lab_kmeans` 需要 `Frame` 提供可读的像素数据(`frame->data` 可访问)。若上游是纯 DMA-BUF 且未映射到 CPU,这两类处理器会自然退化为“不过滤”。 + +**ai_yolo 参数说明:** + +- `model_version`: `"v5"`, `"v8"` 或 `"auto"`(自动检测) +- `num_classes`: 模型类别数(默认 80) +- `class_filter`: 只检测指定类别 ID 的数组,如 `[0, 1, 2]` + +> 所有 AI 节点通过 **AiScheduler** 使用 NPU,不直接持有 NPU 句柄。 + +#### 6.2.2 ai_face_det(人脸检测) + +人脸检测节点,基于 RetinaFace/SCRFD 等轻量模型,输出人脸框和 5 点关键点。 + +**配置示例:** + +```json +{ + "id": "face_det_cam1", + "type": "ai_face_det", + "role": "filter", + "enable": true, + "model_path": "./models/face_det_scrfd_500m_640_rk3588.rknn", + "conf": 0.6, + "nms": 0.4, + "max_faces": 10, + "output_landmarks": true, + "input_format": "rgb" +} +``` + +**参数说明:** + +- `model_path`: RetinaFace RKNN 模型路径 +- `conf`: 置信度阈值(默认 0.6) +- `nms`: NMS 阈值(默认 0.4) +- `max_faces`: 最大检测人脸数 +- `output_landmarks`: 是否输出 5 点关键点(用于后续对齐) +- `input_format`: 输入格式 `rgb` 或 `bgr` + +**输出:** 写入 `frame->face_det`,供 `ai_face_recog` 和 `osd` 读取。 + +#### 6.2.3 ai_face_recog(人脸识别) + +人脸识别节点,读取检测结果,对每个人脸进行对齐、特征提取、人脸库检索。 + +**配置示例:** + +```json +{ + "id": "face_recog_cam1", + "type": "ai_face_recog", + "role": "filter", + "enable": true, + "model_path": "./models/face_recog_mobilefacenet_arcface_112_rk3588.rknn", + "align": true, + "emit_embedding": false, + "max_faces": 10, + "input_format": "rgb", + "threshold": { + "accept": 0.45, + "margin": 0.05 + }, + "gallery": { + "backend": "sqlite", + "path": "./models/face_gallery.db", + "load_on_start": true, + "expected_dim": 512 + } +} +``` + +**参数说明:** + +- `model_path`: ArcFace/MobileFaceNet RKNN 模型路径(输入 112x112,输出 512D embedding) +- `align`: 是否使用 5 点关键点做仿射对齐(推荐开启) +- `emit_embedding`: 是否在结果中输出 embedding(调试用,生产关闭以省内存) +- `threshold.accept`: 相似度接受阈值(高于此值认为匹配成功) +- `threshold.margin`: top1 与 top2 相似度差值阈值(可选,用于降低误识率) +- `gallery.backend`: 人脸库后端(当前支持 `sqlite`) +- `gallery.path`: SQLite 数据库路径 +- `gallery.expected_dim`: embedding 维度(需与模型匹配) + +**判定规则:** + +- `top1_sim >= accept` 且(可选)`top1_sim - top2_sim >= margin` → 识别为该人 +- 否则标记为 `unknown` + +**输出:** 写入 `frame->face_recog`,供 `osd` 和 `alarm` 读取。 + +#### 6.2.4 人脸识别 Pipeline 示例 + +```text +[input_rtsp] → [preprocess] → [ai_face_det] → [ai_face_recog] → [osd] → [publish] +``` + +**完整配置示例:** + +```json +{ + "name": "cam1_face_recog", + "nodes": [ + { "id": "in_cam1", "type": "input_rtsp", "url": "rtsp://..." }, + { "id": "pre_cam1", "type": "preprocess", "dst_w": 1280, "dst_h": 720, "dst_format": "rgb" }, + { "id": "face_det_cam1", "type": "ai_face_det", "model_path": "./models/face_det_scrfd_500m_640_rk3588.rknn", "conf": 0.6 }, + { "id": "face_recog_cam1", "type": "ai_face_recog", "model_path": "./models/face_recog_mobilefacenet_arcface_112_rk3588.rknn", "gallery": { "path": "./models/face_gallery.db" } }, + { "id": "osd_cam1", "type": "osd", "draw_face_det": true }, + { "id": "pub_cam1", "type": "publish", "outputs": [{ "proto": "rtsp_server", "port": 8555, "path": "/live/face" }] } + ], + "edges": [ + ["in_cam1", "pre_cam1"], + ["pre_cam1", "face_det_cam1"], + ["face_det_cam1", "face_recog_cam1"], + ["face_recog_cam1", "osd_cam1"], + ["osd_cam1", "pub_cam1"] + ] +} +``` + +> **注意**:人脸识别应在**原始分辨率**(如 1280x720)上进行,检测模型内部会自动缩放到 320x320 推理,输出坐标缩放回原始尺寸。这样从高清图裁剪对齐人脸,识别质量更高。 + +#### 6.2.5 人脸库(Gallery)说明 + +人脸库使用 SQLite 存储,表结构: + +```sql +CREATE TABLE person ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + created_at TEXT, + extra TEXT -- JSON 扩展字段 +); + +CREATE TABLE embedding ( + id INTEGER PRIMARY KEY, + person_id INTEGER NOT NULL, + embedding BLOB NOT NULL, -- 512D float32 向量 + created_at TEXT, + FOREIGN KEY (person_id) REFERENCES person(id) +); +``` + +**检索实现:** + +- 小库(≤几千人):CPU 暴力点积,无需额外索引 +- 大库(≥几万人):可扩展 HNSW/Faiss 等 ANN 索引 + +### 6.3 业务逻辑与输出 + +| 节点类型 | 功能描述 | 关键参数 | 硬件依赖 | +| :---------- | :--------------------------------- | :----------------------------------- | :----------------- | +| `osd` | 绘制检测框、文字等 | `draw_bbox`, `draw_text`, `labels`, `line_width`, `font_scale` | RGA / CPU | +| `alarm` | 报警规则 + 多种报警动作 | `rules`, `actions`(见下文详细说明) | CPU | + +#### 6.3 Behavior Events + +Behavior events are emitted through `frame->behavior_events` and are not encoded as synthetic detection classes. + +First-release behavior nodes: + +- `region_event`: rule-driven `intrusion` and rule-based `climb` +- `action_recog`: temporal-rule `fall` and `fight` +- `event_fusion`: stable `event_id` assignment and lifecycle normalization + +Recommended first-release graph order: + +```text +[input_rtsp] -> [preprocess] -> [ai_yolo] -> [tracker] -> [region_event] -> [action_recog] -> [event_fusion] -> [osd] -> [publish] -> [alarm] +``` + +Behavior event payload includes: + +- event type +- lifecycle status +- related `track_id` values +- duration in milliseconds +- event bbox +- optional `region_id` + +Behavior event alarm rules should use: + +- `use_behavior_events: true` +- `event_types` +- `region_ids` when region filtering is needed +- `min_duration_ms` +- `cooldown_ms` + +Example alarm rule: + +```json +{ + "name": "intrusion_zone_a", + "use_behavior_events": true, + "event_types": ["intrusion"], + "region_ids": ["zone_a"], + "min_duration_ms": 1000, + "cooldown_ms": 10000 +} +``` +| `storage` | 持续录像(MP4/TS 切片) | `mode`, `segment_sec`, `path` | MPP VENC + IO | +| `publish` | 编码并推流(RTSP/HLS) | `codec`, `bitrate`, `outputs` | MPP VENC + 网络 | +| `zlm_http` | 内嵌 HTTP 文件服务(为 HLS 提供访问) | `root`, `prefix`, `port`, `ssl` | ZLMediaKit(mk_api) | + +> 说明:当前实现中 `publish` 的 `proto:"hls"` 为**落盘生成 m3u8/ts 文件**,`path` 应指向 `index.m3u8` 文件路径;若需要通过网络播放,建议新增 `zlm_http` 节点启动内嵌 HTTP,并将 `root` 指向 HLS 父目录(如 `/data/hls`)。 + +**osd 参数说明:** + +- `labels`: 自定义类别标签数组,如 `["cat", "dog", "bird"]`,为空则使用 COCO 默认标签 + +### 6.4 alarm 插件详细说明 + +alarm 插件采用**模块化 Actions 架构**,支持多种报警动作的灵活组合: + +**架构设计:** + +``` +┌─────────────────────────────────────────────────────────┐ +│ AlarmNode │ +│ ┌──────────────┐ ┌──────────────────────────────────┐ │ +│ │ RuleEngine │ │ FrameRingBuffer (pre-event) │ │ +│ │ - 类别过滤 │ │ - 保存最近 N 秒的帧 │ │ +│ │ - ROI 检测 │ └──────────────────────────────────┘ │ +│ │ - 持续时间 │ │ +│ │ - 冷却控制 │ ┌──────────────────────────────────┐ │ +│ └──────────────┘ │ AlarmActions (可配置) │ │ +│ │ - LogAction → 日志输出 │ │ +│ │ - HttpAction → HTTP 回调 │ │ +│ │ - ExternalApiAction → 外部API报警 │ │ +│ │ - SnapshotAction → 截图+上传 │ │ +│ │ - ClipAction → 视频片段+上传 │ │ +│ └──────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +**完整配置示例:** + +```json +{ + "id": "alarm_cam1", + "type": "alarm", + "role": "sink", + "labels": ["矿体A", "矿体B", "异物"], + "rules": [ + { + "name": "ore_detection", + "class_ids": [0, 1], + "objects": ["矿体A"], + "roi": { "x": 0.1, "y": 0.1, "w": 0.8, "h": 0.8 }, + "min_duration_ms": 500, + "cooldown_ms": 5000, + "schedule": "08:00-20:00" + } + ], + "actions": { + "log": { "enable": true, "level": "info" }, + "http": { + "enable": true, + "url": "http://业务系统/api/alarm", + "timeout_ms": 3000, + "include_media_url": true + }, + "external_api": { + "enable": false, + "getTokenUrl": "http://业务系统/api/getToken", + "putMessageUrl": "http://业务系统/api/putMessage", + "tenantCode": "32", + "channelNo": "${vod_channelNo}", + "timeout_ms": 3000, + "include_media_url": true, + "token_header": "X-Access-Token", + "token_json_path": "responseBody.token", + "token_cache_sec": 600 + }, + "snapshot": { + "enable": true, + "format": "jpg", + "quality": 85, + "upload": { + "type": "minio", + "endpoint": "http://minio:9000", + "bucket": "alarms", + "access_key": "${MINIO_ACCESS_KEY}", + "secret_key": "${MINIO_SECRET_KEY}" + } + }, + "clip": { + "enable": true, + "pre_sec": 5, + "post_sec": 10, + "format": "mp4", + "upload": { "type": "local", "path": "/tmp/alarms" } + } + } +} +``` + +**external_api 动作说明(对接外部系统报警:先取 token,再上报告警):** + +- 取 token:对 `getTokenUrl` 发起 HTTP POST,默认从响应 JSON 的 `responseBody.token` 读取(可用 `token_json_path` 修改点号路径)。 +- 上报告警:对 `putMessageUrl` 发起 HTTP POST,并携带 Header:`Content-Type: application/json`、`X-Access-Token: `(可用 `token_header` 修改)。 +- 上报请求体(固定结构): + +```json +{ + "tenantCode": "32", + "channelNo": "", + "alarmContent": "", + "alarmTime": "YYYY-MM-DD HH:MM:SS", + "picInfo": [{"url": ""}], + "videoInfo": [{"url": ""}] +} +``` + +- `alarmTime` 使用本地时间格式化。 +- `picUrl/videoUrl` 来自 `snapshot/clip` 动作上传后的 URL(通常为 MinIO 返回链接);若为空则对应数组为空。 +- `alarmContent`:若配置中未提供 `alarmContent`,默认使用 `rule_name`。 + +**external_api 参数说明:** + +- 必填:`getTokenUrl`, `putMessageUrl`, `tenantCode`, `channelNo` +- 常用可选:`timeout_ms`(默认 3000)、`include_media_url`(默认 true)、`token_cache_sec`(默认 600;为 0 时仅在 401/403 时刷新 token) +- 其它可选:`token_header`、`token_json_path`、`alarmContent`、`max_queue_size` / `queue_policy`、`max_retries` / `retry_backoff_ms` + +**规则参数说明:** + +- `class_ids`: 要监控的类别 ID 数组(与模型输出对应) +- `objects`: 要监控的类别名称数组(需配合 `labels` 使用) +- `roi`: 感兴趣区域,归一化坐标 (0-1) +- `min_duration_ms`: 目标在 ROI 内持续多久才触发报警 +- `cooldown_ms`: 同一规则连续触发的冷却时间 +- `schedule`: 有效时间段,格式 "HH:MM-HH:MM" + +**上传器类型:** + +- `local`: 保存到本地文件系统 +- `minio`: 上传到 MinIO 对象存储 +- `s3`: 上传到 AWS S3(兼容 MinIO) + +### 6.5 storage 插件详细说明 + +storage 插件用于**持续录像**(7x24 小时),与 alarm 的事件录像功能互补。 + +**配置示例:** + +```json +{ + "id": "storage_cam1", + "type": "storage", + "role": "sink", + "mode": "continuous", + "format": "mp4", + "codec": "h264", + "segment_sec": 300, + "path": "/rec/cam1", + "filename_pattern": "%Y%m%d/%H%M%S", + "fps": 25, + "bitrate_kbps": 2000 +} +``` + +**参数说明:** + +- `mode`: 录像模式,当前支持 `continuous`(持续录像) +- `format`: 输出格式,`mp4` 或 `ts` +- `segment_sec`: 单个文件时长(秒) +- `filename_pattern`: 文件名模式,支持 strftime 格式 + +--- + +## 七、线程模型(Thread Model) + +### 7.1 线程类型与职责 + +| 线程类型 | 职责 | 绑核策略(可配置) | +| :-------------- | :-------------------------------------- | :-------------------------- | +| `main` | 程序入口、配置加载、GraphMgr 调度 | 默认 CPU0 | +| `input-io` | RTSP 网络 IO | 大核/通用 | +| `decode` | 调用 MPP 解码 | 大核 | +| `preprocess` | RGA/GPU 前处理 | 视情况绑核 | +| `ai-worker` | 调度 AiScheduler,异步调用 NPU | 大核 | +| `post/osd` | OSD 绘制、报警逻辑等 | 小核优先 | +| `storage` | 文件写入、切片管理 | 小核 | +| `publish` | RTSP/HLS 推流 | 小核 | +| `metrics/log` | 指标采集、日志异步写入 | 任意核 | + +### 7.2 配置控制 + +- 节点配置中可增加字段: + +```json +"cpu_affinity": [2, 3] // 可选 +``` + +- 若未配置,由框架根据 `role` 和负载自动分配。 + +--- + +## 八、性能指标(Performance Targets) + +| 场景 | 指标 | 目标值 | +| :------------- | :------------- | :--------------------- | +| 8×1080p@25fps | 整机总处理帧率 | ≥ 200 fps | +| 单路端到端 | 延迟 | ≤ 500 ms(典型 ≤ 380) | +| NPU | 利用率 | ≥ 90% | +| 8 路全开 | CPU 占用 | ≤ 30% | +| 7×24 小时运行 | 内存泄漏 | 无明显增长(< 10MB) | + +测试要求(简要): + +- 在实际 RK3588 设备上,使用典型 YOLO 模型和 8 路 RTSP 输入进行长稳测试。 +- 统计长时间运行中的 CPU、内存、NPU 使用情况。 + +--- + +## 九、监控与运维(Metrics & Ops) + +### 9.1 节点级指标 + +每个节点提供以下基本指标(通过 HTTP / Prometheus 等暴露): + +- `node_input_fps` / `node_output_fps` +- `queue_length`(入队/出队队列长度) +- `frame_drop_count` +- `error_count` +- `avg_process_time_ms`(Process 平均处理耗时) + +### 9.2 图 / 通道级指标 + +- 通道状态:running / error / disabled +- 报警次数 / 最近报警时间 +- 当前推流客户端数(RTSP/HLS) +- 当前录像文件信息(路径 / 大小 / 最近片段时间) + +### 9.3 管理接口(REST 简要) + +- `GET /api/graphs`:列出所有 graph 状态 +- `GET /api/graphs/{name}`:单通道详情 +- `POST /api/config/reload`:触发配���重加载 +- `GET /api/nodes/{id}/metrics`:查询节点指标 +- `POST /api/nodes/{id}/config`:动态更新节点配置(调用 `UpdateConfig`) + +--- + +## 十、开发与部署环境 + +- **硬件平台**:RK3588(4×A76 + 4×A55 + NPU + VPU + RGA) +- **工具链**: + - 板上编译:GCC 10+(推荐) + - 交叉编译:`aarch64-linux-gnu-gcc 12+`(可选) + - 构建:CMake ≥ 3.20 +- **调试**: + - `gdbserver` + VSCode 远程调试 + - RKNN Profiler 分析 NPU 利用率 +- **日志**: + - `spdlog` 异步日志 + - 日志级别可运行时调整(通过配置或 REST 接口) + +### 10.1 板上编译(推荐) + +在 RK3588 板子上直接编译,无需配置交叉编译环境。 + +**依赖安装:** ```bash -cd control -go build -o safesightd.exe ./cmd/safesightd -./safesightd -# 访问 http://localhost:18080/ui +# Ubuntu/Debian +sudo apt update +sudo apt install -y build-essential cmake git pkg-config \ + libavformat-dev libavcodec-dev libavutil-dev libswscale-dev ``` -### 设备端 +**编译命令:** ```bash -# 编译 Agent -cd agent -go build -o safesight-edge-agent ./cmd/safesight-agent +cd Rk3588Sys -# 编译 Media Engine(RK3588 板载编译) -cd .. -bash scripts/build.sh -m -j4 -./build/safesight-edge-server --config configs/sample_cam1.json +# 配置(启用所有硬件加速) +cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DRK3588_ENABLE_FFMPEG=ON \ + -DRK3588_ENABLE_MPP=ON \ + -DRK3588_ENABLE_RGA=ON \ + -DRK3588_ENABLE_ZLMEDIAKIT=ON \ + -DRK_ZLMK_API_LIB_PATH=$PWD/third_party/rknpu2/examples/3rdparty/zlmediakit/aarch64/libmk_api.so \ + -DRK_ZLMEDIAKIT_INCLUDE_DIR=$PWD/third_party/rknpu2/examples/3rdparty/zlmediakit/include + +# 编译 +cmake --build build -j$(nproc) ``` + +**CMake 选项说明:** + +| 选项 | 说明 | 默认值 | +|------|------|--------| +| `RK3588_ENABLE_FFMPEG` | 启用 FFmpeg 拉流/软解码 | OFF | +| `RK3588_ENABLE_MPP` | 启用 MPP 硬件编解码 | OFF | +| `RK3588_ENABLE_RGA` | 启用 RGA 硬件图像处理 | OFF | +| `RK3588_ENABLE_RKNN` | 启用 RKNN NPU 推理 | OFF | +| `RK3588_ENABLE_ZLMEDIAKIT` | 启用内置 RTSP Server | OFF | + +**运行:** + +```bash +# 纯转码网关 +./build/safesight-media --config configs/sample_cam1.json + +# 带预处理的 pipeline +./build/safesight-media --config configs/sample_preprocess.json + +# 内置 RTSP Server 模式 +./build/safesight-media --config configs/sample_cam1_rtsp_server.json +``` + +### 10.2 交叉编译(可选) + +在 x86 主机上交叉编译,适合 CI/CD 或批量部署。 + +```bash +# 需要安装交叉编译工具链 +sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + +# 配置 +cmake -S . -B build-cross \ + -DCMAKE_TOOLCHAIN_FILE=cmake/aarch64-linux-gnu.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DRK3588_ENABLE_FFMPEG=ON \ + -DRK3588_ENABLE_MPP=ON \ + -DRK3588_ENABLE_RGA=ON + +# 编译 +cmake --build build-cross -j$(nproc) + +# 同步到板子 +rsync -avz build-cross/ user@board_ip:/opt/safesight-media/ +``` + +--- + +## 十一、扩展与演进 + +1. **新增算法插件** + - 实现新的 `INode` 子类(例如 `ai_cls`, `ai_reid`)。 + - 使用 `REGISTER_NODE` 编译为 `.so` 放入 `plugin_path`。 + - 在 `config.json` 中新增节点即可接入全流程。 + +2. **新增输出类型** + - 新增 `publish_webrtc`、`storage_s3` 等节点类型。 + - 不改动上游图结构,只调整 sink 部分。 + +3. **分布式扩展(后续版本)** + - 引入 ZeroMQ / NanoMSG,将远端设备作为特殊节点处理(如 `remote_sink`)。 + - 中控只维护抽象图,实际在多台 RK3588 设备上分布执行。 + +--- + +--- + +## 附录:问题修复与调试文档 + +### 已知问题修复 + +| 问题 | 文档 | 日期 | +|------|------|------| +| YOLOv8 FP16 数据解析错误 | [docs/bugfix_yolov8_fp16.md](docs/bugfix_yolov8_fp16.md) | 2025-02 | +| RKNN 输出格式配置错误 | [docs/fix_rknn_output_format.md](docs/fix_rknn_output_format.md) | 2026-02 | + +### 调试与开发文档 + +- [docs/models.md](docs/models.md) - 模型转换与部署指南 +- [docs/Agent_API_Extensions.md](docs/Agent_API_Extensions.md) - Agent API 扩展说明 +- [docs/API_Device_RemoteMgmt_InterfaceTable.md](docs/API_Device_RemoteMgmt_InterfaceTable.md) - 远程管理接口 +--- + +## Pose Behavior Pipeline + +The pose behavior pipeline is optional and does not replace the existing detection pipeline. + +Baseline behavior graph: + +```text +input_rtsp -> preprocess -> ai_yolo -> tracker -> region_event -> action_recog -> event_fusion +``` + +Pose-enabled behavior graph: + +```text +input_rtsp -> preprocess -> ai_yolo -> tracker -> ai_pose -> pose_assoc -> region_event -> action_recog -> event_fusion +``` + +Engineering rules: + +- `tracker` remains the source of stable `track_id` for the main pipeline. +- `ai_pose` only produces `Frame.pose` and does not replace `Frame.det`. +- `pose_assoc` is the canonical node for assigning `PoseItem.track_id`. +- `action_recog` may use pose-enhanced rules, but it must still run when pose nodes are absent. + +Degradation semantics: + +- If `ai_pose` is not present, the graph keeps the original bbox-based behavior logic. +- If `ai_pose` is present but inference fails on some frames, `Frame.pose` may be empty and downstream nodes must continue with bbox data. +- If `pose_assoc` is absent, downstream nodes should treat `pose.track_id` as optional and may fall back to bbox matching only as a compatibility path. + +Sample configs: + +- `configs/sample_region_behavior_intrusion.json` + - Baseline intrusion-only graph without pose nodes. This is the reference for the non-pose deployment path. +- `configs/sample_region_behavior_full.json` + - Full behavior graph with `ai_pose` and `pose_assoc`, intended for pose-aware `fall` and `fight`. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..0e057ae --- /dev/null +++ b/docs/README.md @@ -0,0 +1,73 @@ +# SafeSight — AI 视觉安全监测平台 + +> **Safe Sight, Safe Production.** +> 以 AI 视觉算法驱动的工业安全生产监测平台。 + +## 仓库结构(单仓库) + +``` +safesight-edge/ ← 唯一代码仓库(历史来自 safesight-edge + safesight-control) +├── VERSION ← 设备端主版本号(发版修改) +├── agent/ → 边缘代理(Go):UDP 发现、模型管理、agent API +├── include/ src/ → 媒体引擎(C++):视频 DAG 流水线、NPU 推理、推流报警 +├── scripts/ → 设备端部署/运维脚本(deploy.sh、setup-overlayroot.sh 等) +├── control/ → 管理端(原 safesight-control 仓库,完整保留) +│ ├── VERSION ← 管理端主版本号 +│ ├── cmd/ internal/ → safesightd:Web UI / HTTP API / 设备管理 +│ └── scripts/ → 管理端构建/打包脚本 +├── docs/ → 产品级文档(部署手册、检查表、离线部署) +│ ├── deployment.md ← Edge 设备部署手册(唯一操作手册) +│ ├── control-deployment.md ← 管理端部署指南 +│ ├── 客户现场部署检查表.md +│ └── 离线部署文档.md +└── third_party/ models/ web/ → 设备端依赖与资源 +``` + +## 快速导航 + +| 想做什么 | 看这里 | +|---|---| +| 产品镜像制作 / 批量部署 / 断电保护 | `docs/deployment.md`(主手册) | +| 现场施工清单 | `docs/客户现场部署检查表.md` | +| 管理端安装打包 | `docs/control-deployment.md` | +| 设备端部署脚本 | `scripts/deploy.sh`(部署到设备后位于 /opt/safesight-edge-server/scripts/) | +| 管理端构建 | `control/scripts/package.sh` | + +## 版本机制(双层) + +- **主版本号**:各端根目录 `VERSION` 文件(如 `1.0.0`),发版改文件即可,不依赖 git tag +- **构建号**:构建时自动注入 `YYYYMMDD.HHMMSS`,区分大量小版本迭代 +- **git sha**:自动注入,精确定位代码 + +设备端列表页显示 `V1.0.0`,详情页显示 `V1.0.0 (build 20260801.171659 · 923ab10)`。 + +## 合并历史说明 + +本仓库由 `safesight-edge`(设备端)与 `safesight-control`(管理端)通过 `git subtree` 合并: +- 仓库根 = 原 safesight-edge(其 git 历史、tag v1.0.0 保留) +- `control/` = 原 safesight-control(其 git 历史、tag v1.0.0 保留) +- 合并提交:`98e375d` + +## 快速开始 + +### 管理端(control/) + +```bash +cd control +go build -o safesightd.exe ./cmd/safesightd +./safesightd +# 访问 http://localhost:18080/ui +``` + +### 设备端 + +```bash +# 编译 Agent +cd agent +go build -o safesight-edge-agent ./cmd/safesight-agent + +# 编译 Media Engine(RK3588 板载编译) +cd .. +bash scripts/build.sh -m -j4 +./build/safesight-edge-server --config configs/sample_cam1.json +``` diff --git a/docs/deployment.md b/docs/deployment.md index cecaa24..e8fff5c 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,6 +1,7 @@ # Edge 设备部署指南 > **本文件是 Edge 设备的唯一操作手册。** 现场部署时配合 `docs/客户现场部署检查表.md` 逐项打勾。 +> 仓库导航/版本机制说明见 `docs/README.md`,管理端部署见 `docs/control-deployment.md`。 > > **推荐流程(新项目)**:`产品镜像批量生产`(第 2 章)——16G 母卡制作产品镜像 → Etcher 批量烧录 → 每台脚本初始化。 > 旧版"克隆卡"方式不再使用。