重构视频推理服务

This commit is contained in:
GuanYuankai 2025-11-06 10:11:11 +00:00
parent ab2c77f7a4
commit e33d0c7854
21 changed files with 196 additions and 106 deletions

View File

@ -67,7 +67,7 @@ add_library(edge_proxy_lib STATIC
src/rknn/rkYolov5s.cc
src/rknn/preprocess.cc
src/rknn/postprocess.cc
src/videoServiceManager/video_service_manager.cc
src/videoService/video_service_manager.cc
src/algorithm/IntrusionModule.cc
)

View File

@ -7,41 +7,71 @@
"enabled": true,
"id": "cam_01_intrusion",
"input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301",
"module_config": {
"class_num": 80,
"intrusion_zone": [
100,
100,
1820,
1820
],
"label_path": "/app/edge-proxy/models/coco_80_labels_list.txt",
"model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn",
"rknn_thread_num": 3,
"time_threshold_sec": 3
},
"module_type": "intrusion_detection",
"output_rtsp": "rtsp://127.0.0.1:8554/ch1301"
"output_rtsp": "rtsp://127.0.0.1:8554/ch1301",
"module_config": {
"model_path": "/app/edge-proxy/models/RK3588/human.rknn",
"rknn_thread_num": 3,
"pre_processor": {
"type": "letterbox",
"target_width": 640,
"target_height": 640
},
"post_processor": {
"type": "yolov5",
"label_path": "/app/edge-proxy/models/human.txt",
"class_num": 3,
"conf_threshold": 0.25,
"nms_threshold": 0.45
},
"tracker_config": {
"intrusion_zone": [
100,
100,
300,
300
],
"time_threshold_sec": 3,
"target_classes": [
"person"
]
}
}
},
{
"enabled": true,
"enabled": false,
"id": "cam_03_intrusion",
"input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1501",
"module_type": "intrusion_detection",
"output_rtsp": "rtsp://127.0.0.1:8554/ch1501",
"module_config": {
"class_num": 80,
"intrusion_zone": [
100,
100,
300,
300
],
"label_path": "/app/edge-proxy/models/coco_80_labels_list.txt",
"model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn",
"rknn_thread_num": 3,
"time_threshold_sec": 3
},
"module_type": "intrusion_detection",
"output_rtsp": "rtsp://127.0.0.1:8554/ch1501"
"pre_processor": {
"type": "letterbox",
"target_width": 640,
"target_height": 640
},
"post_processor": {
"type": "yolov5",
"label_path": "/app/edge-proxy/models/coco_80_labels_list.txt",
"class_num": 80,
"conf_threshold": 0.25,
"nms_threshold": 0.45
},
"tracker_config": {
"intrusion_zone": [
100,
100,
300,
300
],
"time_threshold_sec": 3,
"target_classes": [
"person"
]
}
}
}
]
}

Binary file not shown.

View File

@ -0,0 +1,3 @@
full_body
visible_body
head

View File

@ -4,39 +4,84 @@
#include "spdlog/spdlog.h"
#include <stdio.h>
IntrusionModule::IntrusionModule(std::string model_path, int thread_num,
cv::Rect intrusion_zone,
double intrusion_time_threshold)
: model_path_(model_path), thread_num_(thread_num),
intrusion_zone_(intrusion_zone),
intrusion_time_threshold_(intrusion_time_threshold), next_track_id_(1) //
/**
* @brief []
*/
IntrusionModule::IntrusionModule()
: next_track_id_(1), thread_num_(1), intrusion_time_threshold_(3.0) //
{
spdlog::info("[IntrusionModule] Created. Model: {}, Threads: {}", model_path_,
thread_num_);
if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) {
spdlog::warn("[IntrusionModule] Warning: Intrusion zone is invalid "
"(0,0,0,0). It will be set at runtime.");
}
spdlog::info("[IntrusionModule] Instance created. Awaiting initialization "
"from config.");
}
/**
* @brief [] init
*/
bool IntrusionModule::init(const nlohmann::json &module_config) {
std::string label_path = module_config.value(
"label_path", "/app/edge-proxy/models/coco_80_labels_list.txt");
int class_num = module_config.value("class_num", 80);
try {
// 1. [新增] 从 JSON 加载所有配置到成员变量
// 使用 .at() 来确保必需的字段存在
model_path_ = module_config.at("model_path").get<std::string>();
rknn_pool_ =
std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(
model_path_.c_str(), thread_num_, label_path, class_num);
// 使用 .value() 来为可选字段提供默认值
thread_num_ = module_config.value("rknn_thread_num", 3);
intrusion_time_threshold_ = module_config.value("time_threshold_sec", 3.0);
std::string label_path =
module_config.value("label_path", "/app/edge-proxy/models/human.txt");
int class_num = module_config.value("class_num", 3);
if (rknn_pool_->init() != 0) {
spdlog::error("[IntrusionModule] rknnPool init fail!");
// 2. [新增] 解析入侵区域 (之前在 manager 中)
if (module_config.contains("intrusion_zone") &&
module_config["intrusion_zone"].is_array() &&
module_config["intrusion_zone"].size() == 4) {
std::vector<int> zone_vec =
module_config["intrusion_zone"].get<std::vector<int>>();
intrusion_zone_ =
cv::Rect(zone_vec[0], zone_vec[1], zone_vec[2], zone_vec[3]);
} else {
intrusion_zone_ = cv::Rect(0, 0, 0, 0); // 设置为无效
}
// 3. [新增] 验证和日志记录 (从旧构造函数移来)
spdlog::info("[IntrusionModule] Initializing... Model: {}, Threads: {}",
model_path_, thread_num_);
if (intrusion_zone_.width <= 0 || intrusion_zone_.height <= 0) {
spdlog::warn(
"[IntrusionModule] Warning: Intrusion zone is invalid or not "
"provided (0,0,0,0). It will be set at runtime.");
intrusion_zone_ = cv::Rect(0, 0, 0, 0); // 确保是无效的
} else {
spdlog::info("[IntrusionModule] Loaded intrusion_zone from config: [{}, "
"{}, {}, {}]",
intrusion_zone_.x, intrusion_zone_.y, intrusion_zone_.width,
intrusion_zone_.height);
}
// 4. [不变] 使用成员变量初始化 rknn_pool_
rknn_pool_ =
std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(
model_path_.c_str(), thread_num_, label_path, class_num);
if (rknn_pool_->init() != 0) {
spdlog::error("[IntrusionModule] rknnPool init fail!");
return false;
}
spdlog::info("[IntrusionModule] rknnPool init success.");
return true;
} catch (const nlohmann::json::exception &e) {
// 捕获 JSON 解析错误 (例如 "model_path" 缺失)
spdlog::error("[IntrusionModule] Failed to parse module_config: {}. Check "
"your video_config.json.",
e.what());
return false;
}
spdlog::info("[IntrusionModule] rknnPool init success.");
return true;
}
// ... (process, update_tracker, draw_results, stop 等函数保持不变) ...
bool IntrusionModule::process(cv::Mat &frame) {
if (frame.empty()) {
return false;

View File

@ -24,14 +24,9 @@ struct TrackedPerson {
class IntrusionModule : public IAnalysisModule {
public:
/**
* @brief
* @param model_path rknn模型文件路径
* @param thread_num rknn线程池数量
* @param intrusion_zone
* @param intrusion_time_threshold
* @brief []
*/
IntrusionModule(std::string model_path, int thread_num,
cv::Rect intrusion_zone, double intrusion_time_threshold);
IntrusionModule();
virtual ~IntrusionModule() = default;

View File

@ -0,0 +1,23 @@
#pragma once
#include "nlohmann/json.hpp"
#include "processors/impl_yolov5/postprocess_v5.h" // 引用重命名后的文件
#include "rknn/RawModelOutput.h"
// 后处理模块的接口
class IPostProcessor {
public:
virtual ~IPostProcessor() = default;
// 从 "post_processor" JSON 块中初始化
virtual bool init(const nlohmann::json &config) = 0;
/**
* @brief
* @param model_output [in] rknnPool
* @param pads [in] letterbox
* @param scale [in]
* @param results [out]
*/
virtual bool process(const RawModelOutput &model_output, const BOX_RECT &pads,
float scale, detect_result_group_t &results) = 0;
};

View File

@ -0,0 +1,23 @@
#pragma once
#include "nlohmann/json.hpp"
#include "opencv2/core/core.hpp"
#include "rknn/preprocess_utils.h" // 引用重命名后的文件
// 预处理模块的接口
class IPreProcessor {
public:
virtual ~IPreProcessor() = default;
// 从 "pre_processor" JSON 块中初始化
virtual bool init(const nlohmann::json &config) = 0;
/**
* @brief
* @param raw_frame [in]
* @param model_input [out] ( 640x640)
* @param pads [out] (letterbox)
* @param scale [out]
*/
virtual bool process(const cv::Mat &raw_frame, cv::Mat &model_input,
BOX_RECT &pads, float &scale) = 0;
};

15
src/rknn/RawModelOutput.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <rknn_api.h>
#include <stdint.h>
#include <vector>
// 用于在 rknnPool 线程和主线程间传递“原始”张量数据
// 它必须深拷贝 rknn_output.buf 的数据
struct RawModelOutput {
// 每个输出张量的深拷贝数据
std::vector<std::vector<int8_t>> output_buffers;
// 后处理所需的量化参数
std::vector<int32_t> zps;
std::vector<float> scales;
};

View File

View File

View File

@ -1,13 +1,10 @@
// video_service_manager.cc (重构后)
// video_service_manager.cc
#include "video_service_manager.h"
#include "spdlog/spdlog.h"
#include <fstream> // <-- 新增: 用于文件读取
#include <fstream>
VideoServiceManager::~VideoServiceManager() { stop_all(); }
/**
* @brief [] load_config
*/
bool VideoServiceManager::load_config(const std::string &config_path) {
std::ifstream ifs(config_path);
if (!ifs.is_open()) {
@ -19,16 +16,14 @@ bool VideoServiceManager::load_config(const std::string &config_path) {
nlohmann::json video_json;
ifs >> video_json;
// 1. 加载 video_service.enabled
m_enabled = video_json.value("/video_service/enabled"_json_pointer, false);
// 2. 加载 video_streams 数组
if (video_json.contains("video_streams") &&
video_json["video_streams"].is_array()) {
m_stream_configs_json = video_json["video_streams"];
} else {
spdlog::warn("Video config contains no 'video_streams' array.");
m_stream_configs_json = nlohmann::json::array(); // 确保它是一个空数组
m_stream_configs_json = nlohmann::json::array();
}
spdlog::info("Successfully loaded video config. Service enabled: {}. "
@ -43,9 +38,6 @@ bool VideoServiceManager::load_config(const std::string &config_path) {
}
}
/**
* @brief [] load_and_start
*/
void VideoServiceManager::load_and_start() {
if (!m_enabled) {
spdlog::warn("VideoService is disabled in video configuration. No streams "
@ -56,10 +48,8 @@ void VideoServiceManager::load_and_start() {
spdlog::info("Found {} video stream configurations.",
m_stream_configs_json.size());
// 遍历存储的 json 数组
for (const auto &sc_json : m_stream_configs_json) {
// 从 JSON 中提取配置
std::string id = sc_json.value("id", "unknown");
bool enabled = sc_json.value("enabled", false);
std::string input_url = sc_json.value("input_url", "");
@ -78,35 +68,9 @@ void VideoServiceManager::load_and_start() {
std::unique_ptr<IAnalysisModule> module = nullptr;
try {
// 1. 根据配置创建 "AI模块" (策略)
if (module_type == "intrusion_detection") {
// 从 module_config 中提取用于构造函数的参数
std::string module_model_path = module_config.value("model_path", "");
int module_threads = module_config.value("rknn_thread_num", 1);
double threshold = module_config.value("time_threshold_sec", 3.0);
std::vector<int> zone_array;
if (module_config.contains("intrusion_zone") &&
module_config["intrusion_zone"].is_array()) {
zone_array = module_config["intrusion_zone"].get<std::vector<int>>();
}
if (module_threads <= 0) {
spdlog::warn(
"Video stream '{}' has invalid rknn_thread_num. Defaulting to 1.",
id);
module_threads = 1;
}
cv::Rect zone = (zone_array.size() == 4)
? cv::Rect(zone_array[0], zone_array[1],
zone_array[2], zone_array[3])
: cv::Rect(0, 0, 0, 0);
// 创建模块 (构造函数不变)
module = std::make_unique<IntrusionModule>(
module_model_path, module_threads, zone, threshold);
module = std::make_unique<IntrusionModule>();
} else if (module_type == "face_recognition") {
spdlog::warn("Module type 'face_recognition' for stream '{}' is not "
@ -119,17 +83,9 @@ void VideoServiceManager::load_and_start() {
module_type, id);
continue;
}
// 2. 创建 "管线" (VideoService) 并注入模块
// --- 关键修改 ---
// 我们需要将 module_config 传递给 VideoService以便它在 start() 时
// 可以调用 module->init(module_config)
auto service = std::make_unique<VideoService>(
std::move(module), input_url, output_rtsp,
module_config // <-- [修改] 传递完整的模块配置
);
std::move(module), input_url, output_rtsp, module_config);
// 3. 启动服务 (逻辑不变)
if (service->start()) {
spdlog::info("Successfully started video service for stream '{}' "
"[Module: {}]. Output is [{}].",