diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dd4d0f..2fc1650 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ) diff --git a/config/video_config.json b/config/video_config.json index 924bd13..01c614c 100644 --- a/config/video_config.json +++ b/config/video_config.json @@ -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" + ] + } + } } ] } \ No newline at end of file diff --git a/edge-proxy/models/RK3588/human.rknn b/edge-proxy/models/RK3588/human.rknn new file mode 100644 index 0000000..a764cef Binary files /dev/null and b/edge-proxy/models/RK3588/human.rknn differ diff --git a/edge-proxy/models/human.txt b/edge-proxy/models/human.txt new file mode 100644 index 0000000..ab2c3a7 --- /dev/null +++ b/edge-proxy/models/human.txt @@ -0,0 +1,3 @@ +full_body +visible_body +head \ No newline at end of file diff --git a/src/algorithm/IntrusionModule.cc b/src/algorithm/intrusion_detection/IntrusionModule.cc similarity index 69% rename from src/algorithm/IntrusionModule.cc rename to src/algorithm/intrusion_detection/IntrusionModule.cc index 3b53295..5ead917 100644 --- a/src/algorithm/IntrusionModule.cc +++ b/src/algorithm/intrusion_detection/IntrusionModule.cc @@ -4,39 +4,84 @@ #include "spdlog/spdlog.h" #include -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(); - rknn_pool_ = - std::make_unique>( - 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 zone_vec = + module_config["intrusion_zone"].get>(); + 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>( + 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; diff --git a/src/algorithm/IntrusionModule.h b/src/algorithm/intrusion_detection/IntrusionModule.h similarity index 76% rename from src/algorithm/IntrusionModule.h rename to src/algorithm/intrusion_detection/IntrusionModule.h index c4531fb..2143db0 100644 --- a/src/algorithm/IntrusionModule.h +++ b/src/algorithm/intrusion_detection/IntrusionModule.h @@ -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; diff --git a/src/processors/IPostProcessor.h b/src/processors/IPostProcessor.h new file mode 100644 index 0000000..66e68b0 --- /dev/null +++ b/src/processors/IPostProcessor.h @@ -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; +}; \ No newline at end of file diff --git a/src/processors/IPreProcessor.h b/src/processors/IPreProcessor.h new file mode 100644 index 0000000..525fe4b --- /dev/null +++ b/src/processors/IPreProcessor.h @@ -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; +}; \ No newline at end of file diff --git a/src/processors/impl_yolov5/Yolov5PostProcessor.cc b/src/processors/impl_yolov5/Yolov5PostProcessor.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/processors/impl_yolov5/Yolov5PostProcessor.h b/src/processors/impl_yolov5/Yolov5PostProcessor.h new file mode 100644 index 0000000..e69de29 diff --git a/src/rknn/postprocess.cc b/src/processors/impl_yolov5/postprocess_v5.cc similarity index 100% rename from src/rknn/postprocess.cc rename to src/processors/impl_yolov5/postprocess_v5.cc diff --git a/src/rknn/postprocess.h b/src/processors/impl_yolov5/postprocess_v5.h similarity index 100% rename from src/rknn/postprocess.h rename to src/processors/impl_yolov5/postprocess_v5.h diff --git a/src/rknn/RawModelOutput.h b/src/rknn/RawModelOutput.h new file mode 100644 index 0000000..580dfa0 --- /dev/null +++ b/src/rknn/RawModelOutput.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +// 用于在 rknnPool 线程和主线程间传递“原始”张量数据 +// 它必须深拷贝 rknn_output.buf 的数据 +struct RawModelOutput { + // 每个输出张量的深拷贝数据 + std::vector> output_buffers; + + // 后处理所需的量化参数 + std::vector zps; + std::vector scales; +}; \ No newline at end of file diff --git a/src/rknn/RknnModelRunner.cc b/src/rknn/RknnModelRunner.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/rknn/RknnModelRunner.h b/src/rknn/RknnModelRunner.h new file mode 100644 index 0000000..e69de29 diff --git a/src/rknn/preprocess.cc b/src/rknn/preprocess_utils.cc similarity index 100% rename from src/rknn/preprocess.cc rename to src/rknn/preprocess_utils.cc diff --git a/src/rknn/preprocess.h b/src/rknn/preprocess_utils.h similarity index 100% rename from src/rknn/preprocess.h rename to src/rknn/preprocess_utils.h diff --git a/src/rknn/video_service.cc b/src/videoService/video_service.cc similarity index 100% rename from src/rknn/video_service.cc rename to src/videoService/video_service.cc diff --git a/src/rknn/video_service.h b/src/videoService/video_service.h similarity index 100% rename from src/rknn/video_service.h rename to src/videoService/video_service.h diff --git a/src/videoServiceManager/video_service_manager.cc b/src/videoService/video_service_manager.cc similarity index 64% rename from src/videoServiceManager/video_service_manager.cc rename to src/videoService/video_service_manager.cc index e4729b3..16ef6f3 100644 --- a/src/videoServiceManager/video_service_manager.cc +++ b/src/videoService/video_service_manager.cc @@ -1,13 +1,10 @@ -// video_service_manager.cc (重构后) +// video_service_manager.cc #include "video_service_manager.h" #include "spdlog/spdlog.h" -#include // <-- 新增: 用于文件读取 +#include 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 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 zone_array; - - if (module_config.contains("intrusion_zone") && - module_config["intrusion_zone"].is_array()) { - zone_array = module_config["intrusion_zone"].get>(); - } - - 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( - module_model_path, module_threads, zone, threshold); + module = std::make_unique(); } 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( - 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 [{}].", diff --git a/src/videoServiceManager/video_service_manager.h b/src/videoService/video_service_manager.h similarity index 100% rename from src/videoServiceManager/video_service_manager.h rename to src/videoService/video_service_manager.h