重构视频推理服务
This commit is contained in:
parent
ab2c77f7a4
commit
e33d0c7854
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
@ -0,0 +1,3 @@
|
||||||
|
full_body
|
||||||
|
visible_body
|
||||||
|
head
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
@ -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 [{}].",
|
||||||
Loading…
Reference in New Issue