webRTC算法无延迟
This commit is contained in:
parent
998991d7e5
commit
29ecc25221
|
|
@ -45,7 +45,6 @@ VideoService::~VideoService() {
|
|||
|
||||
|
||||
bool VideoService::start() {
|
||||
// (关键修改) rknnPool 的模板参数已更新
|
||||
rknn_pool_ = std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(model_path_.c_str(), thread_num_);
|
||||
if (rknn_pool_->init() != 0) {
|
||||
printf("rknnPool init fail!\n");
|
||||
|
|
@ -53,18 +52,15 @@ bool VideoService::start() {
|
|||
}
|
||||
printf("rknnPool init success.\n");
|
||||
|
||||
// 2. 设置RTSP传输协议
|
||||
setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1);
|
||||
printf("Set RTSP transport protocol to TCP\n");
|
||||
|
||||
// 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. 获取输入视频的属性
|
||||
frame_width_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_WIDTH));
|
||||
frame_height_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_HEIGHT));
|
||||
frame_fps_ = capture_.get(cv::CAP_PROP_FPS);
|
||||
|
|
@ -73,14 +69,15 @@ bool VideoService::start() {
|
|||
printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, frame_height_, frame_fps_);
|
||||
|
||||
|
||||
std::string gst_pipeline =
|
||||
"appsrc ! "
|
||||
"video/x-raw,format=BGR ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=NV12 ! "
|
||||
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " // (备注) 你可以根据需要调整 mpph265enc 参数
|
||||
"h265parse ! "
|
||||
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp";
|
||||
std::string gst_pipeline =
|
||||
"appsrc ! "
|
||||
"queue max-size-buffers=2 leaky=downstream ! "
|
||||
"video/x-raw,format=BGR ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=NV12 ! "
|
||||
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! "
|
||||
"h265parse ! "
|
||||
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp";
|
||||
|
||||
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
|
||||
|
||||
|
|
@ -98,8 +95,8 @@ bool VideoService::start() {
|
|||
}
|
||||
printf("VideoWriter opened successfully.\n");
|
||||
|
||||
// 6. 启动处理线程
|
||||
running_ = true;
|
||||
reading_thread_ = std::thread(&VideoService::reading_loop, this);
|
||||
processing_thread_ = std::thread(&VideoService::processing_loop, this);
|
||||
printf("Processing thread started.\n");
|
||||
|
||||
|
|
@ -111,6 +108,10 @@ void VideoService::stop() {
|
|||
printf("Stopping VideoService...\n");
|
||||
running_ = false;
|
||||
|
||||
if (reading_thread_.joinable()) {
|
||||
reading_thread_.join();
|
||||
}
|
||||
|
||||
if (processing_thread_.joinable()) {
|
||||
processing_thread_.join();
|
||||
}
|
||||
|
|
@ -127,11 +128,8 @@ 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);
|
||||
}
|
||||
|
|
@ -147,12 +145,10 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
|
@ -167,12 +163,10 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
@ -184,7 +178,6 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co
|
|||
}
|
||||
}
|
||||
|
||||
// 3. 处理和更新每个目标的状态
|
||||
double current_time = get_current_time_seconds();
|
||||
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
|
||||
TrackedPerson& person = it->second;
|
||||
|
|
@ -209,7 +202,6 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co
|
|||
}
|
||||
}
|
||||
|
||||
// 4. 移除消失太久的目标
|
||||
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ) {
|
||||
// (建议) 增加到50帧 (约2秒) 提高鲁棒性,减少ID切换
|
||||
if (it->second.frames_unseen > 50) {
|
||||
|
|
@ -220,15 +212,39 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co
|
|||
}
|
||||
}
|
||||
|
||||
// (新增) 绘图辅助函数,从 rkYolov5s::infer 移入
|
||||
void VideoService::reading_loop() {
|
||||
cv::Mat frame;
|
||||
spdlog::info("Reading thread started.");
|
||||
|
||||
while (running_) {
|
||||
if (!capture_.read(frame)) {
|
||||
spdlog::warn("Reading loop: Failed to read frame from capture. Stopping service.");
|
||||
running_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(frame_mutex_);
|
||||
latest_frame_ = frame;
|
||||
new_frame_available_ = true;
|
||||
}
|
||||
|
||||
frame_cv_.notify_one();
|
||||
}
|
||||
|
||||
frame_cv_.notify_all();
|
||||
spdlog::info("Reading loop finished.");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -243,24 +259,36 @@ void VideoService::draw_results(cv::Mat& frame)
|
|||
}
|
||||
|
||||
|
||||
// (关键修改) 彻底重写处理循环为 "一进一出" 模式
|
||||
void VideoService::processing_loop() {
|
||||
cv::Mat frame;
|
||||
detect_result_group_t detection_results;
|
||||
|
||||
while (running_) {
|
||||
auto t_start = std::chrono::high_resolution_clock::now();
|
||||
if (!capture_.read(frame)) {
|
||||
spdlog::warn("VideoService: Failed to read frame from capture. Stopping capture.");
|
||||
running_ = false;
|
||||
break;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(frame_mutex_);
|
||||
|
||||
frame_cv_.wait(lock, [&]{
|
||||
return new_frame_available_ || !running_;
|
||||
});
|
||||
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 确认是有新帧
|
||||
frame = latest_frame_.clone();
|
||||
new_frame_available_ = false;
|
||||
}
|
||||
|
||||
auto t_read = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (frame.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// [保留] 后续的 AI 推理、跟踪、绘图、推流逻辑完全不变
|
||||
if (rknn_pool_->put(frame) != 0) {
|
||||
spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping.");
|
||||
running_ = false;
|
||||
|
|
@ -283,18 +311,17 @@ void VideoService::processing_loop() {
|
|||
writer_.write(frame);
|
||||
}
|
||||
|
||||
// 打印详细耗时
|
||||
auto t_write = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// (新增) 打印耗时
|
||||
double read_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_read - t_start).count();
|
||||
double infer_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_infer - t_read).count();
|
||||
double track_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_track_draw - t_infer).count();
|
||||
double write_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_track_draw).count();
|
||||
double total_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_start).count();
|
||||
// [保留] 性能日志
|
||||
double read_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_read - t_start).count();
|
||||
double infer_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_infer - t_read).count();
|
||||
double track_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_track_draw - t_infer).count();
|
||||
double write_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_track_draw).count();
|
||||
double total_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_start).count();
|
||||
|
||||
printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n",
|
||||
total_ms, read_ms, infer_ms, track_ms, write_ms);
|
||||
printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n",
|
||||
total_ms, read_ms, infer_ms, track_ms, write_ms);
|
||||
}
|
||||
|
||||
spdlog::info("VideoService: Processing loop finished.");
|
||||
|
|
|
|||
|
|
@ -5,25 +5,26 @@
|
|||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <map> // (新增) 用于跟踪
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include "postprocess.h" // (新增) 需要 detect_result_group_t
|
||||
#include "postprocess.h"
|
||||
|
||||
// 向前声明
|
||||
template<typename T, typename IN, typename OUT>
|
||||
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; // 消失的帧数
|
||||
int id;
|
||||
cv::Rect box;
|
||||
double entry_time;
|
||||
bool is_in_zone;
|
||||
bool alarm_triggered;
|
||||
int frames_unseen;
|
||||
};
|
||||
|
||||
class VideoService {
|
||||
|
|
@ -40,35 +41,36 @@ public:
|
|||
|
||||
private:
|
||||
void processing_loop();
|
||||
void reading_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_;
|
||||
|
||||
// 视频属性
|
||||
int frame_width_ = 0;
|
||||
int frame_height_ = 0;
|
||||
double frame_fps_ = 0.0;
|
||||
|
||||
// 资源
|
||||
// (关键修改) rknnPool 的输出类型变为 detect_result_group_t
|
||||
std::unique_ptr<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>> rknn_pool_;
|
||||
cv::VideoCapture capture_;
|
||||
cv::VideoWriter writer_;
|
||||
|
||||
// 线程管理
|
||||
|
||||
std::thread processing_thread_;
|
||||
std::thread reading_thread_;
|
||||
std::atomic<bool> running_{false};
|
||||
|
||||
// (新增) 跟踪器状态变量 (从 rkYolov5s 移入)
|
||||
std::mutex frame_mutex_;
|
||||
std::condition_variable frame_cv_;
|
||||
cv::Mat latest_frame_;
|
||||
bool new_frame_available_{false};
|
||||
|
||||
cv::Rect intrusion_zone_;
|
||||
std::map<int, TrackedPerson> tracked_persons_;
|
||||
int next_track_id_;
|
||||
|
|
|
|||
Loading…
Reference in New Issue