重构视频推理服务

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/rkYolov5s.cc
src/rknn/preprocess.cc src/rknn/preprocess.cc
src/rknn/postprocess.cc src/rknn/postprocess.cc
src/videoServiceManager/video_service_manager.cc src/videoService/video_service_manager.cc
src/algorithm/IntrusionModule.cc src/algorithm/IntrusionModule.cc
) )

View File

@ -7,41 +7,71 @@
"enabled": true, "enabled": true,
"id": "cam_01_intrusion", "id": "cam_01_intrusion",
"input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301", "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", "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", "id": "cam_03_intrusion",
"input_url": "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1501", "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": { "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", "model_path": "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn",
"rknn_thread_num": 3, "rknn_thread_num": 3,
"time_threshold_sec": 3 "pre_processor": {
}, "type": "letterbox",
"module_type": "intrusion_detection", "target_width": 640,
"output_rtsp": "rtsp://127.0.0.1:8554/ch1501" "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 "spdlog/spdlog.h"
#include <stdio.h> #include <stdio.h>
IntrusionModule::IntrusionModule(std::string model_path, int thread_num, /**
cv::Rect intrusion_zone, * @brief []
double intrusion_time_threshold) */
: model_path_(model_path), thread_num_(thread_num), IntrusionModule::IntrusionModule()
intrusion_zone_(intrusion_zone), : next_track_id_(1), thread_num_(1), intrusion_time_threshold_(3.0) //
intrusion_time_threshold_(intrusion_time_threshold), next_track_id_(1) //
{ {
spdlog::info("[IntrusionModule] Created. Model: {}, Threads: {}", model_path_, spdlog::info("[IntrusionModule] Instance created. Awaiting initialization "
thread_num_); "from config.");
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.");
}
} }
/**
* @brief [] init
*/
bool IntrusionModule::init(const nlohmann::json &module_config) { bool IntrusionModule::init(const nlohmann::json &module_config) {
std::string label_path = module_config.value( try {
"label_path", "/app/edge-proxy/models/coco_80_labels_list.txt"); // 1. [新增] 从 JSON 加载所有配置到成员变量
int class_num = module_config.value("class_num", 80); // 使用 .at() 来确保必需的字段存在
model_path_ = module_config.at("model_path").get<std::string>();
rknn_pool_ = // 使用 .value() 来为可选字段提供默认值
std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>( thread_num_ = module_config.value("rknn_thread_num", 3);
model_path_.c_str(), thread_num_, label_path, class_num); 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) { // 2. [新增] 解析入侵区域 (之前在 manager 中)
spdlog::error("[IntrusionModule] rknnPool init fail!"); 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; return false;
} }
spdlog::info("[IntrusionModule] rknnPool init success.");
return true;
} }
// ... (process, update_tracker, draw_results, stop 等函数保持不变) ...
bool IntrusionModule::process(cv::Mat &frame) { bool IntrusionModule::process(cv::Mat &frame) {
if (frame.empty()) { if (frame.empty()) {
return false; return false;

View File

@ -24,14 +24,9 @@ struct TrackedPerson {
class IntrusionModule : public IAnalysisModule { class IntrusionModule : public IAnalysisModule {
public: public:
/** /**
* @brief * @brief []
* @param model_path rknn模型文件路径
* @param thread_num rknn线程池数量
* @param intrusion_zone
* @param intrusion_time_threshold
*/ */
IntrusionModule(std::string model_path, int thread_num, IntrusionModule();
cv::Rect intrusion_zone, double intrusion_time_threshold);
virtual ~IntrusionModule() = default; 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 "video_service_manager.h"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include <fstream> // <-- 新增: 用于文件读取 #include <fstream>
VideoServiceManager::~VideoServiceManager() { stop_all(); } VideoServiceManager::~VideoServiceManager() { stop_all(); }
/**
* @brief [] load_config
*/
bool VideoServiceManager::load_config(const std::string &config_path) { bool VideoServiceManager::load_config(const std::string &config_path) {
std::ifstream ifs(config_path); std::ifstream ifs(config_path);
if (!ifs.is_open()) { if (!ifs.is_open()) {
@ -19,16 +16,14 @@ bool VideoServiceManager::load_config(const std::string &config_path) {
nlohmann::json video_json; nlohmann::json video_json;
ifs >> video_json; ifs >> video_json;
// 1. 加载 video_service.enabled
m_enabled = video_json.value("/video_service/enabled"_json_pointer, false); m_enabled = video_json.value("/video_service/enabled"_json_pointer, false);
// 2. 加载 video_streams 数组
if (video_json.contains("video_streams") && if (video_json.contains("video_streams") &&
video_json["video_streams"].is_array()) { video_json["video_streams"].is_array()) {
m_stream_configs_json = video_json["video_streams"]; m_stream_configs_json = video_json["video_streams"];
} else { } else {
spdlog::warn("Video config contains no 'video_streams' array."); 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: {}. " 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() { void VideoServiceManager::load_and_start() {
if (!m_enabled) { if (!m_enabled) {
spdlog::warn("VideoService is disabled in video configuration. No streams " 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.", spdlog::info("Found {} video stream configurations.",
m_stream_configs_json.size()); m_stream_configs_json.size());
// 遍历存储的 json 数组
for (const auto &sc_json : m_stream_configs_json) { for (const auto &sc_json : m_stream_configs_json) {
// 从 JSON 中提取配置
std::string id = sc_json.value("id", "unknown"); std::string id = sc_json.value("id", "unknown");
bool enabled = sc_json.value("enabled", false); bool enabled = sc_json.value("enabled", false);
std::string input_url = sc_json.value("input_url", ""); std::string input_url = sc_json.value("input_url", "");
@ -78,35 +68,9 @@ void VideoServiceManager::load_and_start() {
std::unique_ptr<IAnalysisModule> module = nullptr; std::unique_ptr<IAnalysisModule> module = nullptr;
try { try {
// 1. 根据配置创建 "AI模块" (策略)
if (module_type == "intrusion_detection") { if (module_type == "intrusion_detection") {
// 从 module_config 中提取用于构造函数的参数 module = std::make_unique<IntrusionModule>();
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);
} else if (module_type == "face_recognition") { } else if (module_type == "face_recognition") {
spdlog::warn("Module type 'face_recognition' for stream '{}' is not " spdlog::warn("Module type 'face_recognition' for stream '{}' is not "
@ -119,17 +83,9 @@ void VideoServiceManager::load_and_start() {
module_type, id); module_type, id);
continue; continue;
} }
// 2. 创建 "管线" (VideoService) 并注入模块
// --- 关键修改 ---
// 我们需要将 module_config 传递给 VideoService以便它在 start() 时
// 可以调用 module->init(module_config)
auto service = std::make_unique<VideoService>( auto service = std::make_unique<VideoService>(
std::move(module), input_url, output_rtsp, std::move(module), input_url, output_rtsp, module_config);
module_config // <-- [修改] 传递完整的模块配置
);
// 3. 启动服务 (逻辑不变)
if (service->start()) { if (service->start()) {
spdlog::info("Successfully started video service for stream '{}' " spdlog::info("Successfully started video service for stream '{}' "
"[Module: {}]. Output is [{}].", "[Module: {}]. Output is [{}].",