Go to file
2026-04-14 17:47:45 +08:00
agent 编译agent为1.0.0版本 2026-03-03 09:55:54 +08:00
cmake/toolchain Initial commit: OrangePi3588 Media Server project 2025-12-08 17:05:22 +08:00
configs Tune person shoe ROI vertical range 2026-04-14 16:02:14 +08:00
docs Refine shoe box gating responsibilities 2026-04-14 12:06:40 +08:00
include Center person shoe ROI horizontally 2026-04-14 15:46:50 +08:00
models Add generated face gallery artifacts for device testing 2026-04-14 11:13:01 +08:00
plugins Adjust shoe color sampling window 2026-04-14 17:47:45 +08:00
samples Update CMakeLists.txt 2025-12-09 11:18:22 +08:00
scripts Highlight missed ROI files below 0.22 2026-03-16 10:19:51 +08:00
src Fix FFmpeg decode frame copy and publish encoder config 2026-03-15 02:53:50 +08:00
test_assets Add generated face gallery artifacts for device testing 2026-04-14 11:13:01 +08:00
tests Adjust shoe color sampling window 2026-04-14 17:47:45 +08:00
third_party Add ZLMediaKit libmk_api.so for embedded HTTP server 2026-02-27 00:09:09 +08:00
train rknn 2026-04-01 16:08:38 +08:00
transform 增加了支持滑动窗口的检测鞋子的节点 2026-03-13 21:15:43 +08:00
web 新增滑动窗口节点,提高了人脸检测和识别能力 2026-03-12 20:12:00 +08:00
.clang-format 性能优化4 2026-01-12 20:51:27 +08:00
.gitignore Add agent guidance and plan scaffolding 2026-04-01 09:59:43 +08:00
AGENTS.md Tighten person-shoe shape filtering 2026-04-14 13:09:01 +08:00
CHANGELOG.md 升级RKNN库文件;优化hls网页本地运行 2026-03-10 22:27:36 +08:00
CMakeLists.txt fix: use system MPP/RGA libraries by default 2026-02-28 17:26:24 +08:00
Readme.md docs: add behavior event sample pipelines 2026-04-01 10:37:59 +08:00

RK3588 智能视频分析系统 PRDv1.2


一、文档综述

1.1 项目背景

为了充分利用瑞芯微 RK3588 芯片提供的:

  • NPU约 6 TOPSRKNNINT8 推理能力)
  • VPU多路 1080p 硬件解码/编码
  • RGA硬件图像处理缩放/颜色转换/OSD 等)

构建一套纯 C++ 实现、高性能、低延迟、模块化的边缘计算视频分析平台。平台主要部署在园区、仓库、工厂等场景,实现就地智能分析与处理,减少带宽与中心算力压力。

1.2 核心目标

  • 高性能
    • 单 RK3588 支持 816 路 1080p 实时分析
    • 单路端到端(拉流→推理→输出)延迟 < 500ms
  • 低耦合
    • 基于插件 + DAG 流水线架构
    • 算法、业务逻辑与底层框架完全解耦
  • 易运维
    • JSON 配置驱动
    • 支持热更新,无需重启进程即可变更流程/参数
  • 多场景覆盖
    • 同一套框架通过不同配置图覆盖:
      • 安防报警
      • 录像存储
      • 转码网关RTSP/HLS
      • 混合场景(报警 + 存储 + 推流)

二、总体架构设计

2.1 系统逻辑架构

[ media-server 主进程 ]
        |
        +-- Graph Manager        (图管理加载配置、构建DAG、热更新/回滚)
        |
        +-- Plugin Loader        (插件加载dlopen .so检查 ABI 版本)
        |
        +-- AiScheduler          (统一调度 NPU 推理请求)
        |
        +-- Metrics & Logger     (监控指标、日志)
        |
        +-- HTTP Server          (REST API + Web 控制台 + 状态查询)
  • media-server:主入口,负责初始化配置、图构建、主事件循环。
  • Graph ManagerGraphMgr
    • 对应多个 graph每个 graph 一路/一组业务流程)。
    • 实例化节点Node建立边Edge负责热更新时的 diff & 切换。
  • Plugin Loader
    • 动态加载插件 .so,校验节点类型和 ABI 版本。
  • AiScheduler
    • 所有 AI 节点统一将推理任务提交到 AiScheduler由其管理 NPU 资源。
  • HTTP Server
    • 对外提供图状态查询、节点指标、配置热更新触发等。

2.2 数据流架构DAG

  • Graph一个完整业务通道。例如Camera 1 的“拉流→AI→报警+录像+推流”。
  • Node节点:功能基本单元,由动态加载的 .so 实例实现。
  • Edge:节点间的数据通道,每条边对应一个 SPSC单生产者单消费者环形队列
  • 多分支支持:一个节点可以有多个下游(多条边),实现一对多分支。

示意:

[in] → [pre] → [ai_yolo] → [det_post] ───→ [alarm]
                              └──────→ [osd] → [storage]
                                         └──→ [publish]

说明:det_post 是检测结果后处理节点(可选)。它会对 frame->det 做过滤/修正/二次验证,使 OSD 与告警读取到一致的“后处理结果”。

2.3 多硬件解耦架构(已完成)

为支持 RK3588 之外的平台Atlas/Jetson 等),已将硬件强绑定模块抽象为统一接口层,并保留 RK3588 默认实现与 DMA-BUF 快路径:

  • 推理IInferBackend(默认 Rk3588InferBackend
  • 图像处理IImageProcessor(默认 Rk3588ImageProcessorRGA/Swscale 兜底)
  • 编解码IDecoder / IEncoder(默认 RK3588 MPP 路径)
  • 缓冲区FrameBuffer 统一 DMA/内存同步语义

运行时通过 HwFactory 根据配置选择后端实现:

  • 配置字段:platform / hw_platform(不填默认 rk3588
  • 可扩展实现:Atlas* / Jetson*(保持现有业务配置不变,仅切换硬件实现)

三、数据模型Frame & 元数据)

3.1 检测结果结构Detection & DetectionResult

约定:所有 AI 节点写入此结构OSD / 报警 等节点只读。

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<Detection> items;
    int img_w;              // 推理输入宽
    int img_h;              // 推理输入高
    std::string model_name; // 模型名/版本
};

约定:

  • bbox 坐标标准在 PRD 中约定好(建议:像素坐标),避免后续插件互相搞混。
  • class 映射表由 AI 节点自行维护OSD/报警通过 cls_id 做规则匹配。

3.2 Frame 结构

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<DetectionResult> det;       // AI 检测结果(通用目标检测)
    std::shared_ptr<FaceDetResult> face_det;    // 人脸检测结果
    std::shared_ptr<FaceRecogResult> face_recog;// 人脸识别结果
    std::shared_ptr<void> user_meta;            // 扩展元数据(具体类型由插件间约定)
};

约定:

  • 零拷贝优先:解码/前处理尽量通过 dma_fd 传递,必要时才用 data。
  • 节点默认只读图像数据,可以更新 det / face_det / face_recog / user_meta
  • 所有队列中传递的都是 std::shared_ptr<Frame>,实现多下游共享。

3.3 人脸检测结果结构FaceDetResult

struct Point2f {
    float x;
    float y;
};

struct FaceDetItem {
    Rect bbox;                          // 人脸框
    float score;                        // 检测置信度
    int track_id;                       // 跟踪ID可选-1表示无
    bool has_landmarks;                 // 是否有关键点
    std::array<Point2f, 5> landmarks;   // 5点关键点左眼/右眼/鼻尖/左嘴角/右嘴角
};

struct FaceDetResult {
    std::vector<FaceDetItem> faces;
    int img_w;                          // 原始图像宽
    int img_h;                          // 原始图像高
    std::string model_name;             // 模型名(如 "retinaface"
};

3.4 人脸识别结果结构FaceRecogResult

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<float> embedding;       // 特征向量(可选,调试/注册用)
};

struct FaceRecogResult {
    std::vector<FaceRecogItem> items;
    int img_w;
    int img_h;
    std::string model_name;             // 模型名(如 "arcface"
};

四、插件模型与生命周期

4.1 节点通用字段Node 通用配置)

每个节点配置中推荐包含以下通用字段:

{
  "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 = falseGraphMgr 构建图时可直接忽略该节点及相关 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

// 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> 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 版本

#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:全局配置
  • templatespipeline 模板(复用节点与拓扑)
  • instances / graphs:具体业务实例(通道)

5.1 顶层结构示例

{
  "global": {
    "plugin_path": "/usr/lib/rknodes",
    "log_level": "info",
    "metrics_port": 9000
  },
  "templates": { ... },
  "instances": [ ... ],
  "graphs": [ ... ]  // 可选直接手写graph
}

说明:global.log_level 只控制框架内部 Loggerdebug/info/warn/error 若启用了 FFmpeg例如 publish 的 HLS mux建议通过节点 debug.ffmpeg_log_level 将 FFmpeg 内部日志降到 error 避免出现大量类似 hls ... Opening ... 的 info 日志刷屏。

  • 优先方式:使用 templates + instances 快速生成多路通道。
  • 高级用法:直接使用 graphs 写复杂拓扑(例如:一条流拆成多个完全不同的分支)。

5.2 DAG 多分支完整示例(带报警 + 存储 + 推流)

{
  "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/best-768.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=falseper_class 为空),此时 det_post 会作为轻量透传。
  • 若确实要移除该节点:需要同时删除并重连拓扑(例如改回 ai_cam1 -> osd_cam1ai_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 模板示例:标准安防流程

"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/best-768.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禁用某些节点

"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_yoloosd)会自动处理坐标映射,无需手动转换。

6.2 AI 分析

节点类型 功能描述 关键参数 硬件依赖
ai_yolo 通用目标检测YOLOv5/v8 model_path, model_version, num_classes, conf, nms, class_filter NPU(RKNN)

ai_yolo 配置示例best-768.rknn

{
  "id": "yolo_cam1",
  "type": "ai_yolo",
  "model_path": "./models/best-768.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 模型版本: v5v8
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
ai_face_recog 人脸识别(对齐+特征提取+检索) model_path, gallery, threshold, align
ai_pose 关键点检测(可选扩展) model_path
ai_custom 自定义 AI 算法 model_path, custom_param

6.2.1 det_post检测结果后处理按类别策略链

det_post 是一个 Filter 节点,位于 ai_yolo 之后,负责对 frame->det->items按类别可配置的后处理。

设计目标:

  • 不同类别可以挂不同的处理器链Processor/Strategy Registry避免“一个类别一个插件”。
  • 默认改写 frame->det,让 osd / alarm 读到一致结果。

配置示例:

{
  "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:框内采样像素转 LabKMeans 聚类后取最暗簇做判定(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 点关键点。

配置示例:

{
  "id": "face_det_cam1",
  "type": "ai_face_det",
  "role": "filter",
  "enable": true,
  "model_path": "./models/RetinaFace_mobile320.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: 输入格式 rgbbgr

输出: 写入 frame->face_det,供 ai_face_recogosd 读取。

6.2.3 ai_face_recog人脸识别

人脸识别节点,读取检测结果,对每个人脸进行对齐、特征提取、人脸库检索。

配置示例:

{
  "id": "face_recog_cam1",
  "type": "ai_face_recog",
  "role": "filter",
  "enable": true,
  "model_path": "./models/mobilefacenet_arcface.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,供 osdalarm 读取。

6.2.4 人脸识别 Pipeline 示例

[input_rtsp] → [preprocess] → [ai_face_det] → [ai_face_recog] → [osd] → [publish]

完整配置示例:

{
  "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/RetinaFace_mobile320.rknn", "conf": 0.6 },
    { "id": "face_recog_cam1", "type": "ai_face_recog", "model_path": "./models/mobilefacenet_arcface.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 存储,表结构:

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:

[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:

{
  "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) |

说明:当前实现中 publishproto:"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     → 视频片段+上传 │ │
│                    └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

完整配置示例:

{
  "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再上报告警

  • 取 tokengetTokenUrl 发起 HTTP POST默认从响应 JSON 的 responseBody.token 读取(可用 token_json_path 修改点号路径)。
  • 上报告警:对 putMessageUrl 发起 HTTP POST并携带 HeaderContent-Type: application/jsonX-Access-Token: <token>(可用 token_header 修改)。
  • 上报请求体(固定结构):
{
  "tenantCode": "32",
  "channelNo": "<vod_channelNo>",
  "alarmContent": "<msg>",
  "alarmTime": "YYYY-MM-DD HH:MM:SS",
  "picInfo": [{"url": "<picUrl>"}],
  "videoInfo": [{"url": "<videoUrl>"}]
}
  • alarmTime 使用本地时间格式化。
  • picUrl/videoUrl 来自 snapshot/clip 动作上传后的 URL通常为 MinIO 返回链接);若为空则对应数组为空。
  • alarmContent:若配置中未提供 alarmContent,默认使用 rule_name

external_api 参数说明:

  • 必填:getTokenUrl, putMessageUrl, tenantCode, channelNo
  • 常用可选:timeout_ms(默认 3000include_media_url(默认 truetoken_cache_sec(默认 600为 0 时仅在 401/403 时刷新 token
  • 其它可选:token_headertoken_json_pathalarmContentmax_queue_size / queue_policymax_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 的事件录像功能互补。

配置示例:

{
  "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: 输出格式,mp4ts
  • 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 配置控制

  • 节点配置中可增加字段:
"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_msProcess 平均处理耗时)

9.2 图 / 通道级指标

  • 通道状态running / error / disabled
  • 报警次数 / 最近报警时间
  • 当前推流客户端数RTSP/HLS
  • 当前录像文件信息(路径 / 大小 / 最近片段时间)

9.3 管理接口REST 简要)

  • GET /api/graphs:列出所有 graph 状态
  • GET /api/graphs/{name}:单通道详情
  • POST /api/config/reload触发配<EFBFBD><EFBFBD><EFBFBD>重加载
  • GET /api/nodes/{id}/metrics:查询节点指标
  • POST /api/nodes/{id}/config:动态更新节点配置(调用 UpdateConfig

十、开发与部署环境

  • 硬件平台RK35884×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 板子上直接编译,无需配置交叉编译环境。

依赖安装:

# Ubuntu/Debian
sudo apt update
sudo apt install -y build-essential cmake git pkg-config \
    libavformat-dev libavcodec-dev libavutil-dev libswscale-dev

编译命令:

cd Rk3588Sys

# 配置(启用所有硬件加速)
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

运行:

# 纯转码网关
./build/media-server --config configs/sample_cam1.json

# 带预处理的 pipeline
./build/media-server --config configs/sample_preprocess.json

# 内置 RTSP Server 模式
./build/media-server --config configs/sample_cam1_rtsp_server.json

10.2 交叉编译(可选)

在 x86 主机上交叉编译,适合 CI/CD 或批量部署。

# 需要安装交叉编译工具链
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/media-server/

十一、扩展与演进

  1. 新增算法插件

    • 实现新的 INode 子类(例如 ai_cls, ai_reid)。
    • 使用 REGISTER_NODE 编译为 .so 放入 plugin_path
    • config.json 中新增节点即可接入全流程。
  2. 新增输出类型

    • 新增 publish_webrtcstorage_s3 等节点类型。
    • 不改动上游图结构,只调整 sink 部分。
  3. 分布式扩展(后续版本)

    • 引入 ZeroMQ / NanoMSG将远端设备作为特殊节点处理remote_sink)。
    • 中控只维护抽象图,实际在多台 RK3588 设备上分布执行。


附录:问题修复与调试文档

已知问题修复

问题 文档 日期
YOLOv8 FP16 数据解析错误 docs/bugfix_yolov8_fp16.md 2025-02
RKNN 输出格式配置错误 docs/fix_rknn_output_format.md 2026-02

调试与开发文档