webRTC算法无延迟
This commit is contained in:
parent
998991d7e5
commit
29ecc25221
|
|
@ -45,7 +45,6 @@ VideoService::~VideoService() {
|
||||||
|
|
||||||
|
|
||||||
bool VideoService::start() {
|
bool VideoService::start() {
|
||||||
// (关键修改) rknnPool 的模板参数已更新
|
|
||||||
rknn_pool_ = std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(model_path_.c_str(), thread_num_);
|
rknn_pool_ = std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(model_path_.c_str(), thread_num_);
|
||||||
if (rknn_pool_->init() != 0) {
|
if (rknn_pool_->init() != 0) {
|
||||||
printf("rknnPool init fail!\n");
|
printf("rknnPool init fail!\n");
|
||||||
|
|
@ -53,18 +52,15 @@ bool VideoService::start() {
|
||||||
}
|
}
|
||||||
printf("rknnPool init success.\n");
|
printf("rknnPool init success.\n");
|
||||||
|
|
||||||
// 2. 设置RTSP传输协议
|
|
||||||
setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1);
|
setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1);
|
||||||
printf("Set RTSP transport protocol to TCP\n");
|
printf("Set RTSP transport protocol to TCP\n");
|
||||||
|
|
||||||
// 3. 初始化 VideoCapture
|
|
||||||
capture_.open(input_url_, cv::CAP_FFMPEG);
|
capture_.open(input_url_, cv::CAP_FFMPEG);
|
||||||
if (!capture_.isOpened()) {
|
if (!capture_.isOpened()) {
|
||||||
printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str());
|
printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 获取输入视频的属性
|
|
||||||
frame_width_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_WIDTH));
|
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_height_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_HEIGHT));
|
||||||
frame_fps_ = capture_.get(cv::CAP_PROP_FPS);
|
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_);
|
printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, frame_height_, frame_fps_);
|
||||||
|
|
||||||
|
|
||||||
std::string gst_pipeline =
|
std::string gst_pipeline =
|
||||||
"appsrc ! "
|
"appsrc ! "
|
||||||
"video/x-raw,format=BGR ! "
|
"queue max-size-buffers=2 leaky=downstream ! "
|
||||||
"videoconvert ! "
|
"video/x-raw,format=BGR ! "
|
||||||
"video/x-raw,format=NV12 ! "
|
"videoconvert ! "
|
||||||
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " // (备注) 你可以根据需要调整 mpph265enc 参数
|
"video/x-raw,format=NV12 ! "
|
||||||
"h265parse ! "
|
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! "
|
||||||
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp";
|
"h265parse ! "
|
||||||
|
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp";
|
||||||
|
|
||||||
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
|
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
|
||||||
|
|
||||||
|
|
@ -98,8 +95,8 @@ bool VideoService::start() {
|
||||||
}
|
}
|
||||||
printf("VideoWriter opened successfully.\n");
|
printf("VideoWriter opened successfully.\n");
|
||||||
|
|
||||||
// 6. 启动处理线程
|
|
||||||
running_ = true;
|
running_ = true;
|
||||||
|
reading_thread_ = std::thread(&VideoService::reading_loop, this);
|
||||||
processing_thread_ = std::thread(&VideoService::processing_loop, this);
|
processing_thread_ = std::thread(&VideoService::processing_loop, this);
|
||||||
printf("Processing thread started.\n");
|
printf("Processing thread started.\n");
|
||||||
|
|
||||||
|
|
@ -111,6 +108,10 @@ void VideoService::stop() {
|
||||||
printf("Stopping VideoService...\n");
|
printf("Stopping VideoService...\n");
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
|
if (reading_thread_.joinable()) {
|
||||||
|
reading_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
if (processing_thread_.joinable()) {
|
if (processing_thread_.joinable()) {
|
||||||
processing_thread_.join();
|
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)
|
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) {
|
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);
|
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) {
|
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
|
||||||
it->second.frames_unseen++;
|
it->second.frames_unseen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 将当前帧的检测结果与已有的跟踪目标进行匹配
|
|
||||||
for (const auto& det_box : current_detections) {
|
for (const auto& det_box : current_detections) {
|
||||||
bool is_matched = false;
|
bool is_matched = false;
|
||||||
int best_match_id = -1;
|
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) {
|
if (best_match_id != -1) {
|
||||||
// 匹配成功
|
|
||||||
tracked_persons_[best_match_id].box = det_box;
|
tracked_persons_[best_match_id].box = det_box;
|
||||||
tracked_persons_[best_match_id].frames_unseen = 0;
|
tracked_persons_[best_match_id].frames_unseen = 0;
|
||||||
is_matched = true;
|
is_matched = true;
|
||||||
} else {
|
} else {
|
||||||
// 匹配失败,创建新的跟踪目标
|
|
||||||
TrackedPerson new_person;
|
TrackedPerson new_person;
|
||||||
new_person.id = this->next_track_id_++; // 使用成员变量
|
new_person.id = this->next_track_id_++; // 使用成员变量
|
||||||
new_person.box = det_box;
|
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();
|
double current_time = get_current_time_seconds();
|
||||||
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
|
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
|
||||||
TrackedPerson& person = it->second;
|
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(); ) {
|
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ) {
|
||||||
// (建议) 增加到50帧 (约2秒) 提高鲁棒性,减少ID切换
|
// (建议) 增加到50帧 (约2秒) 提高鲁棒性,减少ID切换
|
||||||
if (it->second.frames_unseen > 50) {
|
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)
|
void VideoService::draw_results(cv::Mat& frame)
|
||||||
{
|
{
|
||||||
// 绘制入侵区域
|
|
||||||
cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色
|
cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色
|
||||||
|
|
||||||
// 绘制框体和报警状态
|
|
||||||
for (auto const& [id, person] : this->tracked_persons_) {
|
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);
|
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;
|
int line_thickness = person.alarm_triggered ? 3 : 2;
|
||||||
|
|
||||||
|
|
@ -243,24 +259,36 @@ void VideoService::draw_results(cv::Mat& frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// (关键修改) 彻底重写处理循环为 "一进一出" 模式
|
|
||||||
void VideoService::processing_loop() {
|
void VideoService::processing_loop() {
|
||||||
cv::Mat frame;
|
cv::Mat frame;
|
||||||
detect_result_group_t detection_results;
|
detect_result_group_t detection_results;
|
||||||
|
|
||||||
while (running_) {
|
while (running_) {
|
||||||
auto t_start = std::chrono::high_resolution_clock::now();
|
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;
|
std::unique_lock<std::mutex> lock(frame_mutex_);
|
||||||
break;
|
|
||||||
|
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();
|
|
||||||
|
auto t_read = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
if (frame.empty()) {
|
if (frame.empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [保留] 后续的 AI 推理、跟踪、绘图、推流逻辑完全不变
|
||||||
if (rknn_pool_->put(frame) != 0) {
|
if (rknn_pool_->put(frame) != 0) {
|
||||||
spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping.");
|
spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping.");
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
@ -283,18 +311,17 @@ void VideoService::processing_loop() {
|
||||||
writer_.write(frame);
|
writer_.write(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打印详细耗时
|
|
||||||
auto t_write = std::chrono::high_resolution_clock::now();
|
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 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 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 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 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 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",
|
printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n",
|
||||||
total_ms, read_ms, infer_ms, track_ms, write_ms);
|
total_ms, read_ms, infer_ms, track_ms, write_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
spdlog::info("VideoService: Processing loop finished.");
|
spdlog::info("VideoService: Processing loop finished.");
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,26 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <map> // (新增) 用于跟踪
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
#include <opencv2/core/core.hpp>
|
#include <opencv2/core/core.hpp>
|
||||||
#include <opencv2/videoio.hpp>
|
#include <opencv2/videoio.hpp>
|
||||||
#include "postprocess.h" // (新增) 需要 detect_result_group_t
|
#include "postprocess.h"
|
||||||
|
|
||||||
// 向前声明
|
// 向前声明
|
||||||
template<typename T, typename IN, typename OUT>
|
template<typename T, typename IN, typename OUT>
|
||||||
class rknnPool;
|
class rknnPool;
|
||||||
class rkYolov5s;
|
class rkYolov5s;
|
||||||
|
|
||||||
// (新增) 从 rkYolov5s.hpp 移动过来的结构体
|
|
||||||
struct TrackedPerson
|
struct TrackedPerson
|
||||||
{
|
{
|
||||||
int id; // 唯一ID
|
int id;
|
||||||
cv::Rect box; // 当前位置
|
cv::Rect box;
|
||||||
double entry_time; // 进入入侵区域的时间戳 (秒)
|
double entry_time;
|
||||||
bool is_in_zone; // 是否在区域内
|
bool is_in_zone;
|
||||||
bool alarm_triggered; // 是否已触发报警
|
bool alarm_triggered;
|
||||||
int frames_unseen; // 消失的帧数
|
int frames_unseen;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VideoService {
|
class VideoService {
|
||||||
|
|
@ -40,35 +41,36 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void processing_loop();
|
void processing_loop();
|
||||||
|
void reading_loop(); // [新增] 读取线程的循环函数
|
||||||
|
|
||||||
// (新增) 跟踪和绘图相关的私有方法
|
|
||||||
void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size);
|
void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size);
|
||||||
void draw_results(cv::Mat& frame); // 绘图辅助函数
|
void draw_results(cv::Mat& frame); // 绘图辅助函数
|
||||||
void trigger_alarm(int person_id, const cv::Rect& box);
|
void trigger_alarm(int person_id, const cv::Rect& box);
|
||||||
double get_current_time_seconds();
|
double get_current_time_seconds();
|
||||||
|
|
||||||
// 配置
|
|
||||||
std::string model_path_;
|
std::string model_path_;
|
||||||
int thread_num_;
|
int thread_num_;
|
||||||
std::string input_url_;
|
std::string input_url_;
|
||||||
std::string output_rtsp_url_;
|
std::string output_rtsp_url_;
|
||||||
|
|
||||||
// 视频属性
|
|
||||||
int frame_width_ = 0;
|
int frame_width_ = 0;
|
||||||
int frame_height_ = 0;
|
int frame_height_ = 0;
|
||||||
double frame_fps_ = 0.0;
|
double frame_fps_ = 0.0;
|
||||||
|
|
||||||
// 资源
|
|
||||||
// (关键修改) rknnPool 的输出类型变为 detect_result_group_t
|
|
||||||
std::unique_ptr<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>> rknn_pool_;
|
std::unique_ptr<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>> rknn_pool_;
|
||||||
cv::VideoCapture capture_;
|
cv::VideoCapture capture_;
|
||||||
cv::VideoWriter writer_;
|
cv::VideoWriter writer_;
|
||||||
|
|
||||||
// 线程管理
|
|
||||||
std::thread processing_thread_;
|
std::thread processing_thread_;
|
||||||
|
std::thread reading_thread_;
|
||||||
std::atomic<bool> running_{false};
|
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_;
|
cv::Rect intrusion_zone_;
|
||||||
std::map<int, TrackedPerson> tracked_persons_;
|
std::map<int, TrackedPerson> tracked_persons_;
|
||||||
int next_track_id_;
|
int next_track_id_;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue