diff --git a/src/rknn/rkYolov5s.cc b/src/rknn/rkYolov5s.cc index d39d485..f06cfac 100644 --- a/src/rknn/rkYolov5s.cc +++ b/src/rknn/rkYolov5s.cc @@ -1,9 +1,9 @@ #include -#include -#include // 用于计时 -#include // 使用 std::string -#include // 使用 std::vector -#include // 使用 std::min/max +// #include // 已移除 +// #include // 已移除 +#include +#include +// #include // 已移除 #include "postprocess.h" #include "preprocess.h" @@ -15,19 +15,7 @@ #include "opencv2/imgproc/imgproc.hpp" #include "rknn/rknn_api.h" -// 报警接口函数 (目前只打印信息) -void trigger_alarm(int person_id, const cv::Rect& box) { - printf("[ALARM] Intrusion detected! Person ID: %d at location (%d, %d, %d, %d)\n", - person_id, box.x, box.y, box.width, box.height); - // TODO: 在这里实现真正的报警逻辑,例如发送网络消息、写入数据库等。 -} - -// 获取当前时间的函数 (返回秒) -double get_current_time_seconds() { - return std::chrono::duration_cast>( - std::chrono::high_resolution_clock::now().time_since_epoch() - ).count(); -} +// trigger_alarm 和 get_current_time_seconds 已被移至 video_service.cc static void dump_tensor_attr(rknn_tensor_attr *attr) { @@ -108,17 +96,10 @@ rkYolov5s::rkYolov5s(const std::string &model_path) nms_threshold = NMS_THRESH; box_conf_threshold = BOX_THRESH; - // 初始化跟踪器和入侵检测参数 - next_track_id = 1; - intrusion_time_threshold = 3.0; // 报警时间阈值:3秒 - // 默认设置一个无效的入侵区域,将在第一帧时根据图像大小初始化 - intrusion_zone = cv::Rect(0, 0, 0, 0); + // 跟踪器相关的初始化已全部移除 } -void rkYolov5s::set_intrusion_zone(const cv::Rect& zone) { - std::lock_guard lock(mtx); - this->intrusion_zone = zone; -} +// set_intrusion_zone 已被移除 int rkYolov5s::init(rknn_context *ctx_in, bool share_weight) { @@ -224,97 +205,18 @@ rknn_context *rkYolov5s::get_pctx() return &ctx; } -void rkYolov5s::update_tracker(detect_result_group_t &detect_result_group) +// update_tracker 函数已完全移除 (移至 video_service.cc) + + +// 关键修改: +// 1. 函数签名改变 +// 2. 移除了 lock_guard +// 3. 输入参数变为 const cv::Mat& +// 4. 移除了所有 update_tracker 和 绘图(cv::rectangle/putText) 逻辑 +// 5. 返回值变为 detect_result_group_t +detect_result_group_t rkYolov5s::infer(const cv::Mat &orig_img) { - std::vector current_detections; - for (int i = 0; i < detect_result_group.count; i++) { - detect_result_t *det_result = &(detect_result_group.results[i]); - if (strcmp(det_result->name, "person") == 0) { - current_detections.push_back(cv::Rect( - det_result->box.left, det_result->box.top, - det_result->box.right - det_result->box.left, - det_result->box.bottom - det_result->box.top)); - } - } - - // 1. 对于已有的跟踪目标,增加其未见帧数 - for (auto it = tracked_persons.begin(); it != tracked_persons.end(); ++it) { - it->second.frames_unseen++; - } - - // 2. 将当前帧的检测结果与已有的跟踪目标进行匹配 - for (const auto& det_box : current_detections) { - bool is_matched = false; - int best_match_id = -1; - double max_iou = 0.3; // IoU阈值,用于判断是否为同一目标 - - for (auto const& [id, person] : tracked_persons) { - // 计算交并比 (Intersection over Union) - double iou = (double)(det_box & person.box).area() / (double)(det_box | person.box).area(); - if (iou > max_iou) { - max_iou = iou; - best_match_id = id; - } - } - - if (best_match_id != -1) { - // 匹配成功,更新目标信息 - tracked_persons[best_match_id].box = det_box; - tracked_persons[best_match_id].frames_unseen = 0; - is_matched = true; - } else { - // 匹配失败,创建新的跟踪目标 - TrackedPerson new_person; - new_person.id = next_track_id++; - new_person.box = det_box; - new_person.entry_time = 0; - new_person.is_in_zone = false; - new_person.alarm_triggered = false; - new_person.frames_unseen = 0; - tracked_persons[new_person.id] = new_person; - } - } - - // 3. 处理和更新每个目标的状态 - double current_time = get_current_time_seconds(); - for (auto it = tracked_persons.begin(); it != tracked_persons.end(); ++it) { - TrackedPerson& person = it->second; - // 判断人员包围盒是否与入侵区域有交集 - bool currently_in_zone = (intrusion_zone & person.box).area() > 0; - - if (currently_in_zone) { - if (!person.is_in_zone) { - // 刚进入区域 - person.is_in_zone = true; - person.entry_time = current_time; - } else { - // 已在区域内,检查是否超时 - if (!person.alarm_triggered && (current_time - person.entry_time) > intrusion_time_threshold) { - person.alarm_triggered = true; - trigger_alarm(person.id, person.box); - } - } - } else { - // 不在区域内,重置状态 - person.is_in_zone = false; - person.entry_time = 0; - person.alarm_triggered = false; - } - } - - // 4. 移除消失太久的目标 - for (auto it = tracked_persons.begin(); it != tracked_persons.end(); ) { - if (it->second.frames_unseen > 20) { // 超过20帧未见则移除 - it = tracked_persons.erase(it); - } else { - ++it; - } - } -} - -cv::Mat rkYolov5s::infer(cv::Mat &orig_img) -{ - std::lock_guard lock(mtx); + // std::lock_guard lock(mtx); // 已移除 cv::Mat img; cv::cvtColor(orig_img, img, cv::COLOR_BGR2RGB); img_width = img.cols; @@ -369,33 +271,12 @@ cv::Mat rkYolov5s::infer(cv::Mat &orig_img) post_process((int8_t *)outputs[0].buf, (int8_t *)outputs[1].buf, (int8_t *)outputs[2].buf, height, width, box_conf_threshold, nms_threshold, pads, scale_w, scale_h, out_zps, out_scales, &detect_result_group); - // 更新跟踪器状态 - // 首次运行时,根据图像尺寸初始化入侵区域 (设定在画面中央) - if (intrusion_zone.width == 0 || intrusion_zone.height == 0) { - intrusion_zone = cv::Rect(orig_img.cols / 4, orig_img.rows / 4, orig_img.cols / 2, orig_img.rows / 2); - } - update_tracker(detect_result_group); + // 所有 跟踪器(update_tracker) 和 绘图(cv::rectangle/putText) 逻辑均已移除 - // 绘制入侵区域 - cv::rectangle(orig_img, intrusion_zone, cv::Scalar(255, 255, 0), 2); // 黄色 - - // 绘制框体和报警状态 - for (auto const& [id, person] : tracked_persons) { - // 根据是否触发报警决定颜色 (BGR: 红色 vs 绿色) - cv::Scalar box_color = person.alarm_triggered ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); - int line_thickness = person.alarm_triggered ? 3 : 2; - - cv::rectangle(orig_img, person.box, box_color, line_thickness); - std::string label = "Person " + std::to_string(id); - if (person.is_in_zone) { - label += " (In Zone)"; - } - cv::putText(orig_img, label, cv::Point(person.box.x, person.box.y - 10), - cv::FONT_HERSHEY_SIMPLEX, 0.5, box_color, 2); - } - ret = rknn_outputs_release(ctx, io_num.n_output, outputs); - return orig_img; + + // 返回原始检测结果 + return detect_result_group; } rkYolov5s::~rkYolov5s() diff --git a/src/rknn/rkYolov5s.hpp b/src/rknn/rkYolov5s.hpp index e97e50a..904c050 100644 --- a/src/rknn/rkYolov5s.hpp +++ b/src/rknn/rkYolov5s.hpp @@ -3,11 +3,9 @@ #include "rknn_api.h" #include "opencv2/core/core.hpp" -#include // 用于存储跟踪目标 -#include // 确保线程安全 -#include // 使用 std::string -#include // 使用 std::vector -#include "postprocess.h" +#include +#include +#include "postprocess.h" // 包含 detect_result_group_t 的定义 // 前置声明 static void dump_tensor_attr(rknn_tensor_attr *attr); @@ -15,22 +13,13 @@ static unsigned char *load_data(FILE *fp, size_t ofst, size_t sz); static unsigned char *load_model(const char *filename, int *model_size); static int saveFloat(const char *file_name, float *output, int element_size); -// 用于跟踪单个目标的结构体 -struct TrackedPerson -{ - int id; // 唯一ID - cv::Rect box; // 当前位置 - double entry_time; // 进入入侵区域的时间戳 (秒) - bool is_in_zone; // 是否在区域内 - bool alarm_triggered; // 是否已触发报警 - int frames_unseen; // 消失的帧数 -}; +// 注意:TrackedPerson 结构体已被移至 video_service.h class rkYolov5s { private: int ret; - std::mutex mtx; + // std::mutex mtx; // 已移除,推理应是无状态的 std::string model_path; unsigned char *model_data; @@ -45,24 +34,18 @@ private: float nms_threshold, box_conf_threshold; - // 入侵检测和跟踪相关成员 - cv::Rect intrusion_zone; // 入侵区域 - std::map tracked_persons; // 存储所有被跟踪的人 - int next_track_id; // 用于分配新的唯一ID - double intrusion_time_threshold; // 入侵时间阈值 (秒) - - // 跟踪逻辑的私有方法 - void update_tracker(detect_result_group_t &detect_result_group); + // 所有的跟踪和入侵检测成员变量已被移除 public: rkYolov5s(const std::string &model_path); int init(rknn_context *ctx_in, bool isChild); rknn_context *get_pctx(); - cv::Mat infer(cv::Mat &ori_img); + + + detect_result_group_t infer(const cv::Mat &ori_img); + ~rkYolov5s(); - // 用于从外部设置入侵区域的公共方法 - void set_intrusion_zone(const cv::Rect& zone); }; #endif // RKYOLOV5S_H \ No newline at end of file diff --git a/src/rknn/video_service.cc b/src/rknn/video_service.cc index d99e804..bc6d2b4 100644 --- a/src/rknn/video_service.cc +++ b/src/rknn/video_service.cc @@ -5,6 +5,22 @@ #include "rknn/rkYolov5s.hpp" #include "rknn/rknnPool.hpp" #include "spdlog/spdlog.h" +#include // (新增) 用于计时 +#include // (新增) + +// (新增) 报警接口函数 (从 rkYolov5s.cc 移入) +void VideoService::trigger_alarm(int person_id, const cv::Rect& box) { + printf("[ALARM] Intrusion detected! Person ID: %d at location (%d, %d, %d, %d)\n", + person_id, box.x, box.y, box.width, box.height); + // TODO: 在这里实现真正的报警逻辑,例如发送网络消息、写入数据库等。 +} + +// (新增) 获取当前时间的函数 (从 rkYolov5s.cc 移入) +double VideoService::get_current_time_seconds() { + return std::chrono::duration_cast>( + std::chrono::high_resolution_clock::now().time_since_epoch() + ).count(); +} VideoService::VideoService(std::string model_path, int thread_num, @@ -16,6 +32,11 @@ VideoService::VideoService(std::string model_path, output_rtsp_url_(output_rtsp_url), running_(false) { + // (新增) 初始化跟踪器状态 + next_track_id_ = 1; + intrusion_time_threshold_ = 3.0; // 3秒 + intrusion_zone_ = cv::Rect(0, 0, 0, 0); // 默认无效 + printf("VideoService created. Input: %s, Output: %s\n", input_url_.c_str(), output_rtsp_url_.c_str()); } @@ -27,30 +48,29 @@ VideoService::~VideoService() { bool VideoService::start() { - // 1. 初始化 rknnPool (来自旧的 main) - rknn_pool_ = std::make_unique>(model_path_.c_str(), thread_num_); + // (关键修改) rknnPool 的模板参数已更新 + rknn_pool_ = std::make_unique>(model_path_.c_str(), thread_num_); if (rknn_pool_->init() != 0) { printf("rknnPool init fail!\n"); return false; } printf("rknnPool init success.\n"); - // 2. 设置RTSP传输协议 (来自旧的 main) + // 2. 设置RTSP传输协议 setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1); printf("Set RTSP transport protocol to TCP\n"); - // 3. 初始化 VideoCapture (使用成员变量) + // 3. 初始化 VideoCapture capture_.open(input_url_, cv::CAP_FFMPEG); if (!capture_.isOpened()) { printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str()); return false; } - // 4. (关键) 获取输入视频的属性 + // 4. 获取输入视频的属性 frame_width_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_WIDTH)); frame_height_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_HEIGHT)); frame_fps_ = capture_.get(cv::CAP_PROP_FPS); - // 很多RTSP流不提供FPS,给一个默认值 if (frame_fps_ <= 0) frame_fps_ = 25.0; printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, frame_height_, frame_fps_); @@ -61,18 +81,18 @@ bool VideoService::start() { "video/x-raw,format=BGR ! " "videoconvert ! " "video/x-raw,format=NV12 ! " - "mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " + "mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " // (备注) 你可以根据需要调整 mpph265enc 参数 "h265parse ! " "rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp"; printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str()); writer_.open(gst_pipeline, - cv::CAP_GSTREAMER, // 使用 GStreamer 后端 - 0, // fourcc, GStreamer不需要 - frame_fps_, // FPS - cv::Size(frame_width_, frame_height_), // 帧大小 - true); // 是彩色图像 + cv::CAP_GSTREAMER, + 0, + frame_fps_, + cv::Size(frame_width_, frame_height_), + true); if (!writer_.isOpened()) { printf("Error: Could not open VideoWriter with GStreamer pipeline.\n"); @@ -99,7 +119,6 @@ void VideoService::stop() { } printf("Processing thread joined.\n"); - // 释放资源 if (capture_.isOpened()) { capture_.release(); } @@ -111,11 +130,127 @@ void VideoService::stop() { } +// (新增) 从 rkYolov5s.cc 移入并修改 +// 这是现在唯一的跟踪器逻辑,在主线程中串行调用 +void VideoService::update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size) +{ + // 首次运行时,根据图像尺寸初始化入侵区域 (设定在画面中央) + if (intrusion_zone_.width == 0 || intrusion_zone_.height == 0) { + intrusion_zone_ = cv::Rect(frame_size.width / 4, frame_size.height / 4, frame_size.width / 2, frame_size.height / 2); + } + + std::vector current_detections; + for (int i = 0; i < detect_result_group.count; i++) { + detect_result_t *det_result = &(detect_result_group.results[i]); + if (strcmp(det_result->name, "person") == 0) { + current_detections.push_back(cv::Rect( + det_result->box.left, det_result->box.top, + det_result->box.right - det_result->box.left, + det_result->box.bottom - det_result->box.top)); + } + } + + // 1. 对于已有的跟踪目标,增加其未见帧数 + for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { + it->second.frames_unseen++; + } + + // 2. 将当前帧的检测结果与已有的跟踪目标进行匹配 + for (const auto& det_box : current_detections) { + bool is_matched = false; + int best_match_id = -1; + double max_iou = 0.3; // IoU阈值 + + for (auto const& [id, person] : this->tracked_persons_) { + double iou = (double)(det_box & person.box).area() / (double)(det_box | person.box).area(); + if (iou > max_iou) { + max_iou = iou; + best_match_id = id; + } + } + + if (best_match_id != -1) { + // 匹配成功 + tracked_persons_[best_match_id].box = det_box; + tracked_persons_[best_match_id].frames_unseen = 0; + is_matched = true; + } else { + // 匹配失败,创建新的跟踪目标 + TrackedPerson new_person; + new_person.id = this->next_track_id_++; // 使用成员变量 + new_person.box = det_box; + new_person.entry_time = 0; + new_person.is_in_zone = false; + new_person.alarm_triggered = false; + new_person.frames_unseen = 0; + tracked_persons_[new_person.id] = new_person; // 使用成员变量 + } + } + + // 3. 处理和更新每个目标的状态 + double current_time = get_current_time_seconds(); + for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { + TrackedPerson& person = it->second; + // 使用成员变量 + bool currently_in_zone = (this->intrusion_zone_ & person.box).area() > 0; + + if (currently_in_zone) { + if (!person.is_in_zone) { + person.is_in_zone = true; + person.entry_time = current_time; + } else { + // 使用成员变量 + if (!person.alarm_triggered && (current_time - person.entry_time) > this->intrusion_time_threshold_) { + person.alarm_triggered = true; + trigger_alarm(person.id, person.box); // 调用成员函数 + } + } + } else { + person.is_in_zone = false; + person.entry_time = 0; + person.alarm_triggered = false; + } + } + + // 4. 移除消失太久的目标 + for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ) { + // (建议) 增加到50帧 (约2秒) 提高鲁棒性,减少ID切换 + if (it->second.frames_unseen > 50) { + it = tracked_persons_.erase(it); + } else { + ++it; + } + } +} + +// (新增) 绘图辅助函数,从 rkYolov5s::infer 移入 +void VideoService::draw_results(cv::Mat& frame) +{ + // 绘制入侵区域 + cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色 + + // 绘制框体和报警状态 + for (auto const& [id, person] : this->tracked_persons_) { + // 根据是否触发报警决定颜色 (BGR: 红色 vs 绿色) + cv::Scalar box_color = person.alarm_triggered ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); + int line_thickness = person.alarm_triggered ? 3 : 2; + + cv::rectangle(frame, person.box, box_color, line_thickness); + std::string label = "Person " + std::to_string(id); + if (person.is_in_zone) { + label += " (In Zone)"; + } + cv::putText(frame, label, cv::Point(person.box.x, person.box.y - 10), + cv::FONT_HERSHEY_SIMPLEX, 0.5, box_color, 2); + } +} + + +// (关键修改) 彻底重写处理循环为 "一进一出" 模式 void VideoService::processing_loop() { cv::Mat frame; - cv::Mat processed_frame; - int frames_in_pipeline = 0; - + detect_result_group_t detection_results; // (修改) 存储推理结果 + while (running_) { if (!capture_.read(frame)) { spdlog::warn("VideoService: Failed to read frame from capture. Stopping capture."); @@ -127,42 +262,34 @@ void VideoService::processing_loop() { continue; } + // 1. (并行) 将原始帧放入池中进行推理 if (rknn_pool_->put(frame) != 0) { spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping."); running_ = false; break; } - if (frames_in_pipeline >= thread_num_) { - - if (rknn_pool_->get(processed_frame) != 0) { - spdlog::error("VideoService: Failed to get frame from rknnPool. Stopping."); - running_ = false; - break; - } + // 2. (串行) 立刻取回该帧的推理结果 + // 这保证了 跟踪 和 绘图 总是按顺序在主线程中执行 + if (rknn_pool_->get(detection_results) != 0) { + spdlog::error("VideoService: Failed to get frame from rknnPool. Stopping."); + running_ = false; + break; + } - // (核心) 推流 - if (writer_.isOpened()) { - writer_.write(processed_frame); - } - } else { - frames_in_pipeline++; + // 3. (串行) 在主循环中更新唯一的跟踪器 + this->update_tracker(detection_results, frame.size()); + + // 4. (串行) 在主循环中将跟踪结果绘制到帧上 + this->draw_results(frame); + + // 5. (串行) 将处理和绘制完毕的帧推流 + if (writer_.isOpened()) { + writer_.write(frame); } } - spdlog::info("VideoService: Processing loop finished. Draining remaining frames..."); - - while (true) { - if (rknn_pool_->get(processed_frame) != 0) { - // 队列已空,排空完成 - break; - } - - // 成功获取一帧,将其推流 - if (writer_.isOpened()) { - writer_.write(processed_frame); - } - } - - spdlog::info("VideoService: Draining complete."); + // (修改) 移除排空循环 (Draining loop) + // 新的 "一进一出" 逻辑不需要排空,退出即停止 + spdlog::info("VideoService: Processing loop finished."); } \ No newline at end of file diff --git a/src/rknn/video_service.h b/src/rknn/video_service.h index 24e3969..154060d 100644 --- a/src/rknn/video_service.h +++ b/src/rknn/video_service.h @@ -5,22 +5,35 @@ #include #include #include +#include // (新增) 用于跟踪 #include #include +#include "postprocess.h" // (新增) 需要 detect_result_group_t // 向前声明 template class rknnPool; class rkYolov5s; +// (新增) 从 rkYolov5s.hpp 移动过来的结构体 +struct TrackedPerson +{ + int id; // 唯一ID + cv::Rect box; // 当前位置 + double entry_time; // 进入入侵区域的时间戳 (秒) + bool is_in_zone; // 是否在区域内 + bool alarm_triggered; // 是否已触发报警 + int frames_unseen; // 消失的帧数 +}; + class VideoService { public: VideoService(std::string model_path, int thread_num, - std::string input_url, // <--- 新增 - std::string output_rtsp_url); // <--- 名称修改,更明确 + std::string input_url, + std::string output_rtsp_url); - ~VideoService(); // <-- 析构函数将用于调用 stop() + ~VideoService(); bool start(); void stop(); @@ -28,23 +41,36 @@ public: private: void processing_loop(); + // (新增) 跟踪和绘图相关的私有方法 + void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size); + void draw_results(cv::Mat& frame); // 绘图辅助函数 + void trigger_alarm(int person_id, const cv::Rect& box); + double get_current_time_seconds(); + // 配置 std::string model_path_; int thread_num_; - std::string input_url_; // <--- 新增 - std::string output_rtsp_url_; // <--- 新增 + std::string input_url_; + std::string output_rtsp_url_; - // 视频属性 (重要) + // 视频属性 int frame_width_ = 0; int frame_height_ = 0; double frame_fps_ = 0.0; // 资源 - std::unique_ptr> rknn_pool_; + // (关键修改) rknnPool 的输出类型变为 detect_result_group_t + std::unique_ptr> rknn_pool_; cv::VideoCapture capture_; cv::VideoWriter writer_; // 线程管理 std::thread processing_thread_; std::atomic running_{false}; + + // (新增) 跟踪器状态变量 (从 rkYolov5s 移入) + cv::Rect intrusion_zone_; + std::map tracked_persons_; + int next_track_id_; + double intrusion_time_threshold_; }; \ No newline at end of file