Compare commits
3 Commits
28c8149491
...
07e692970f
| Author | SHA1 | Date |
|---|---|---|
|
|
07e692970f | |
|
|
3d07422520 | |
|
|
f0e48fcb59 |
|
|
@ -76,6 +76,7 @@
|
|||
"valarray": "cpp",
|
||||
"charconv": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"shared_mutex": "cpp"
|
||||
"shared_mutex": "cpp",
|
||||
"*.bak": "cpp"
|
||||
}
|
||||
}
|
||||
|
|
@ -62,10 +62,18 @@ add_library(edge_proxy_lib STATIC
|
|||
src/config/config_manager.cc
|
||||
#告警模块
|
||||
src/alarm/alarm_service.cc
|
||||
# 推理模块
|
||||
src/rknn/video_service.cc
|
||||
src/rknn/rkYolov5s.cc
|
||||
src/rknn/preprocess.cc
|
||||
src/rknn/postprocess.cc
|
||||
)
|
||||
|
||||
target_include_directories(edge_proxy_lib PUBLIC
|
||||
/usr/include/opencv4
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${GST_INCLUDE_DIRS}
|
||||
/usr/include/rga # 编译器会在这里面找 rga.h 和 im2d.h
|
||||
)
|
||||
|
||||
target_link_libraries(edge_proxy_lib PRIVATE
|
||||
|
|
@ -77,39 +85,28 @@ target_link_libraries(edge_proxy_lib PRIVATE
|
|||
pthread
|
||||
# rknn_api
|
||||
rknnrt
|
||||
rockchip_mpp
|
||||
rga
|
||||
${OpenCV_LIBS}
|
||||
${GST_LIBRARIES}
|
||||
|
||||
)
|
||||
|
||||
target_link_libraries(edge_proxy_lib PUBLIC
|
||||
Crow
|
||||
nlohmann_json
|
||||
)
|
||||
|
||||
# ==================================
|
||||
# Main Application Target
|
||||
# ==================================
|
||||
add_executable(edge_proxy
|
||||
src/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(edge_proxy PRIVATE
|
||||
target_link_libraries(edge_proxy PRIVATE
|
||||
edge_proxy_lib
|
||||
)
|
||||
|
||||
|
||||
add_executable(edge_streamer
|
||||
src/streamer/main_streamer.cpp
|
||||
)
|
||||
|
||||
# 链接新服务所需的所有库
|
||||
target_link_libraries(edge_streamer PRIVATE
|
||||
${GST_LIBRARIES}
|
||||
pthread
|
||||
)
|
||||
|
||||
target_include_directories(edge_streamer PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include
|
||||
${GST_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# =================================================================
|
||||
# 测试目标
|
||||
# =================================================================
|
||||
|
|
@ -123,31 +120,27 @@ target_include_directories(edge_streamer PRIVATE
|
|||
# edge_proxy_lib
|
||||
# )
|
||||
|
||||
add_executable(rknn
|
||||
src/rknn/main.cc
|
||||
src/rknn/postprocess.cc
|
||||
src/rknn/preprocess.cc
|
||||
src/rknn/rkYolov5s.cc
|
||||
# add_executable(rknn
|
||||
# src/rknn/main.cc
|
||||
# src/rknn/postprocess.cc
|
||||
# src/rknn/preprocess.cc
|
||||
# src/rknn/rkYolov5s.cc
|
||||
|
||||
)
|
||||
# # )
|
||||
|
||||
target_link_libraries(rknn PRIVATE
|
||||
edge_proxy_lib
|
||||
${OpenCV_LIBS}
|
||||
rockchip_mpp
|
||||
rga
|
||||
)
|
||||
target_include_directories(rknn PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/services/intrusion-detection/include"
|
||||
# RKNN SDK 的头文件
|
||||
/usr/local/include
|
||||
# OpenCV 头文件路径 (根据你的 find 命令输出)
|
||||
/usr/include/opencv4 # 编译器会在这里面找 opencv2/core/core.hpp
|
||||
# RGA 和 im2d 的头文件路径 (根据你的 find 命令输出)
|
||||
/usr/include/rga # 编译器会在这里面找 rga.h 和 im2d.h
|
||||
# Paho MQTT 头文件(通常在 /usr/local/include)
|
||||
/usr/local/include
|
||||
# target_link_libraries(rknn PRIVATE
|
||||
# edge_proxy_lib
|
||||
# ${OpenCV_LIBS}
|
||||
# rockchip_mpp
|
||||
# rga
|
||||
# )
|
||||
# target_include_directories(rknn PRIVATE
|
||||
# "${CMAKE_CURRENT_SOURCE_DIR}/services/intrusion-detection/include"
|
||||
# /usr/local/include
|
||||
# /usr/include/opencv4 # 编译器会在这里面找 opencv2/core/core.hpp
|
||||
# /usr/include/rga # 编译器会在这里面找 rga.h 和 im2d.h
|
||||
# /usr/local/include
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include
|
||||
${GST_INCLUDE_DIRS}
|
||||
)
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/src/streamer/include
|
||||
# ${GST_INCLUDE_DIRS}
|
||||
# )
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
{
|
||||
"config_base_path": "/app/config/",
|
||||
"alarm_rules_path": "alarms.json",
|
||||
"config_base_path": "/app/config/",
|
||||
"data_cache_db_path": "edge_data_cache.db",
|
||||
"data_storage_db_path": "edge_proxy_data.db",
|
||||
"device_id": "rk3588-proxy-001",
|
||||
"log_level": "info",
|
||||
"mqtt_broker": "tcp://localhost:1883",
|
||||
"mqtt_client_id_prefix": "edge-proxy-",
|
||||
"piper_executable_path": "/usr/bin/piper",
|
||||
"piper_model_path": "/app/models/model.onnx",
|
||||
"tcp_server_ports": [
|
||||
12345,
|
||||
502
|
||||
12345
|
||||
],
|
||||
"web_server_port": 8080
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"modbus_rtu_devices": [
|
||||
{
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"device_id": "rtu_temp_sensor_lab",
|
||||
"port_path": "/dev/ttyS7",
|
||||
"baud_rate": 9600,
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -138,12 +138,25 @@ void AlarmService::check_rule_against_value(AlarmRule& rule, double value, const
|
|||
default: break;
|
||||
}
|
||||
|
||||
AlarmState& state = m_alarm_states.at(rule.rule_id);
|
||||
|
||||
auto it = m_alarm_states.find(rule.rule_id);
|
||||
|
||||
if (it == m_alarm_states.end()) {
|
||||
spdlog::warn("AlarmState for rule '{}' was missing. Creating it on-the-fly.", rule.rule_id);
|
||||
|
||||
auto result = m_alarm_states.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(rule.rule_id),
|
||||
std::forward_as_tuple(m_io_context)
|
||||
);
|
||||
it = result.first; // 'it' 现在指向新创建的元素
|
||||
}
|
||||
|
||||
AlarmState& state = it->second;
|
||||
|
||||
if (condition_met) {
|
||||
// 条件满足
|
||||
if (state.current_state == AlarmStateType::NORMAL) {
|
||||
// 状态:NORMAL -> PENDING
|
||||
state.current_state = AlarmStateType::PENDING;
|
||||
|
||||
if (rule.debounce_seconds <= 0) {
|
||||
|
|
|
|||
54
src/main.cpp
54
src/main.cpp
|
|
@ -11,7 +11,7 @@
|
|||
#include "dataCache/live_data_cache.h"
|
||||
#include "dataStorage/data_storage.h"
|
||||
#include "config/config_manager.h"
|
||||
|
||||
#include "rknn/video_service.h"
|
||||
#include "alarm/alarm_service.h"
|
||||
#include "tts/piper_tts_interface.h"
|
||||
|
||||
|
|
@ -86,6 +86,33 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
try {
|
||||
spdlog::info("Initializing Video Service...");
|
||||
std::unique_ptr<VideoService> video_service;
|
||||
|
||||
const char* model_path = "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn";
|
||||
int thread_num = 3;
|
||||
|
||||
std::string input_rtsp = "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301";
|
||||
|
||||
std::string output_rtsp = "rtsp://127.0.0.1:8554/processed";
|
||||
if (true) { //config.isVideoServiceEnabled()
|
||||
try {
|
||||
video_service = std::make_unique<VideoService>(
|
||||
model_path,
|
||||
thread_num,
|
||||
input_rtsp,
|
||||
output_rtsp
|
||||
);
|
||||
spdlog::info("VideoService configured. Input: {}, Output: {}",
|
||||
model_path, output_rtsp);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Failed to initialize VideoService: {}. Video processing will be disabled.", e.what());
|
||||
video_service.reset(); // 确保为 null
|
||||
}
|
||||
} else {
|
||||
spdlog::warn("VideoService is disabled in configuration.");
|
||||
}
|
||||
|
||||
DataCache data_cache;
|
||||
LiveDataCache live_data_cache;
|
||||
MqttClient mqtt_client(config.getMqttBroker(),
|
||||
|
|
@ -156,30 +183,34 @@ int main(int argc, char* argv[]) {
|
|||
monitor.getCpuUtilization();
|
||||
boost::asio::steady_timer system_monitor_timer(g_io_context, std::chrono::seconds(15));
|
||||
|
||||
// [修改] system_monitor_timer 的绑定
|
||||
system_monitor_timer.async_wait(std::bind(poll_system_metrics,
|
||||
std::ref(system_monitor_timer),
|
||||
std::ref(monitor),
|
||||
std::ref(mqtt_client),
|
||||
std::ref(alarm_service) // <--- [新]
|
||||
std::ref(alarm_service)
|
||||
));
|
||||
|
||||
|
||||
device_manager.load_and_start(config.getDevicesConfigPath());
|
||||
|
||||
// [修改] WebServer 实例化
|
||||
WebServer web_server(monitor,
|
||||
device_manager,
|
||||
live_data_cache,
|
||||
alarm_service, // <--- [新]
|
||||
alarm_service,
|
||||
config.getWebServerPort());
|
||||
web_server.start();
|
||||
if (video_service) {
|
||||
if (video_service->start()) {
|
||||
spdlog::info("VideoService started successfully.");
|
||||
} else {
|
||||
spdlog::error("Failed to start VideoService. Video processing will not run.");
|
||||
video_service.reset(); // 设置为 null,以便在关闭时安全跳过
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](const boost::system::error_code& error, int signal_number) {
|
||||
spdlog::warn("Interrupt signal ({}) received. Shutting down.", signal_number);
|
||||
|
||||
// [修改] 关停顺序
|
||||
|
||||
// a. 停止所有数据采集
|
||||
spdlog::info("[Shutdown] A. Stopping device manager services...");
|
||||
|
|
@ -197,8 +228,15 @@ int main(int argc, char* argv[]) {
|
|||
spdlog::info("[Shutdown] D. Disconnecting from MQTT broker...");
|
||||
mqtt_client.disconnect();
|
||||
|
||||
spdlog::info("[Shutdown] E. Stopping video Service loop...");
|
||||
if (video_service) {
|
||||
spdlog::info("[Shutdown] F. Stopping video service...");
|
||||
video_service->stop();
|
||||
}
|
||||
|
||||
// e. 最后,安全地停止io_context
|
||||
spdlog::info("[Shutdown] E. Stopping main event loop...");
|
||||
spdlog::info("[Shutdown] F. Stopping main event loop...");
|
||||
|
||||
g_io_context.stop();
|
||||
});
|
||||
|
||||
|
|
|
|||
138
src/rknn/main.cc
138
src/rknn/main.cc
|
|
@ -1,138 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <memory>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <thread> // for std::this_thread::sleep_for
|
||||
#include <chrono> // for std::chrono::milliseconds
|
||||
|
||||
#include "opencv2/core/core.hpp"
|
||||
#include "opencv2/highgui/highgui.hpp"
|
||||
#include "opencv2/imgproc/imgproc.hpp"
|
||||
#include "rknn/rkYolov5s.hpp"
|
||||
#include "rknnPool.hpp"
|
||||
|
||||
const std::string GSTREAMER_PIPELINE =
|
||||
"rtspsrc location=rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301 is-live=true latency=0 ! "
|
||||
"rtph265depay ! h265parse ! mppvideodec ! " // mppvideodec 是 RK 平台的硬解码器
|
||||
"videoconvert ! video/x-raw,format=BGR ! appsink drop=1";
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *model_name = NULL;
|
||||
// const char* model_path = "/home/forlinx/rknn-cpp-Multithreading-main/model/RK3588/yolov5s-640-640.rknn";
|
||||
const char* model_path = "/app/edge-proxy/models/RK3588/yolov5s-640-640.rknn";
|
||||
|
||||
// 参数二,模型所在路径/The path where the model is located
|
||||
|
||||
|
||||
|
||||
// 初始化rknn线程池/Initialize the rknn thread pool
|
||||
int threadNum = 3;
|
||||
rknnPool<rkYolov5s, cv::Mat, cv::Mat> testPool(model_path, threadNum);
|
||||
if (testPool.init() != 0)
|
||||
{
|
||||
printf("rknnPool init fail!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// const char *video_name = "/home/forlinx/Videos/test_video.mp4";
|
||||
// cv::VideoCapture capture;
|
||||
// if (strlen(video_name) == 1)
|
||||
// capture.open((int)(video_name[0] - '0'));
|
||||
// else
|
||||
// capture.open(video_name);
|
||||
setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1);
|
||||
printf("Set RTSP transport protocol to TCP\n");
|
||||
std::string rtsp_url = "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1301";
|
||||
// cv::VideoCapture capture(rtsp_url, cv::CAP_FFMPEG);
|
||||
cv::VideoCapture capture(GSTREAMER_PIPELINE, cv::CAP_GSTREAMER);
|
||||
// if (!capture.isOpened())
|
||||
// {
|
||||
// printf("Error: Could not open RTSP stream.\n");
|
||||
// return -1;
|
||||
// }
|
||||
// printf("RTSP stream opened successfully!\n");
|
||||
if (!capture.isOpened())
|
||||
{
|
||||
printf("Error: Could not open GStreamer RTSP stream. Check pipeline or RTSP URL.\n");
|
||||
// 可以打印 GStreamer 错误到 stderr
|
||||
fprintf(stderr, "GStreamer pipeline used: %s\n", GSTREAMER_PIPELINE.c_str());
|
||||
return -1;
|
||||
}
|
||||
printf("GStreamer RTSP stream opened successfully!\n");
|
||||
|
||||
const char* WINDOW_NAME = "RTSP Fullscreen Display";
|
||||
cv::namedWindow(WINDOW_NAME, cv::WINDOW_NORMAL);
|
||||
cv::setWindowProperty(WINDOW_NAME, cv::WND_PROP_FULLSCREEN, cv::WINDOW_FULLSCREEN);
|
||||
|
||||
|
||||
|
||||
|
||||
struct timeval time;
|
||||
gettimeofday(&time, nullptr);
|
||||
auto startTime = time.tv_sec * 1000 + time.tv_usec / 1000;
|
||||
|
||||
int frames = 0;
|
||||
auto beforeTime = startTime;
|
||||
while (capture.isOpened())
|
||||
{
|
||||
cv::Mat img;
|
||||
// if (capture.read(img) == false)
|
||||
// break;
|
||||
if (capture.read(img) == false) {
|
||||
printf("Failed to read frame from GStreamer stream. Re-attempting...\n");
|
||||
// 尝试重新打开流,或者等待一段时间
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (!capture.isOpened()) { // 如果流关闭了,尝试重新打开
|
||||
capture.open(GSTREAMER_PIPELINE, cv::CAP_GSTREAMER);
|
||||
if (!capture.isOpened()) {
|
||||
printf("Failed to re-open GStreamer stream. Exiting.\n");
|
||||
break;
|
||||
}
|
||||
printf("GStreamer stream re-opened successfully!\n");
|
||||
}
|
||||
continue; // 继续下一帧
|
||||
}
|
||||
if (img.empty()) {
|
||||
printf("Empty frame received. Skipping.\n");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 防止空循环过快
|
||||
continue;
|
||||
}
|
||||
if (testPool.put(img) != 0)
|
||||
break;
|
||||
|
||||
if (frames >= threadNum && testPool.get(img) != 0)
|
||||
break;
|
||||
cv::imshow(WINDOW_NAME, img);
|
||||
if (cv::waitKey(1) == 'q') // 延时1毫秒,按q键退出/Press q to exit
|
||||
break;
|
||||
frames++;
|
||||
|
||||
if (frames % 120 == 0)
|
||||
{
|
||||
gettimeofday(&time, nullptr);
|
||||
auto currentTime = time.tv_sec * 1000 + time.tv_usec / 1000;
|
||||
printf("120帧内平均帧率:\t %f fps/s\n", 120.0 / float(currentTime - beforeTime) * 1000.0);
|
||||
beforeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 清空rknn线程池/Clear the thread pool
|
||||
while (true)
|
||||
{
|
||||
cv::Mat img;
|
||||
if (testPool.get(img) != 0)
|
||||
break;
|
||||
cv::imshow(WINDOW_NAME, img);
|
||||
if (cv::waitKey(1) == 'q') // 延时1毫秒,按q键退出/Press q to exit
|
||||
break;
|
||||
frames++;
|
||||
}
|
||||
|
||||
gettimeofday(&time, nullptr);
|
||||
auto endTime = time.tv_sec * 1000 + time.tv_usec / 1000;
|
||||
|
||||
printf("Average:\t %f fps/s\n", float(frames) / float(endTime - startTime) * 1000.0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#include <stdio.h>
|
||||
#include <mutex>
|
||||
#include <chrono> // 用于计时
|
||||
#include <string> // 使用 std::string
|
||||
#include <vector> // 使用 std::vector
|
||||
#include <algorithm> // 使用 std::min/max
|
||||
// #include <mutex> // 已移除
|
||||
// #include <chrono> // 已移除
|
||||
#include <string>
|
||||
#include <vector>
|
||||
// #include <algorithm> // 已移除
|
||||
|
||||
#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::duration<double>>(
|
||||
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<std::mutex> 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<cv::Rect> 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<std::mutex> lock(mtx);
|
||||
// std::lock_guard<std::mutex> 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()
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@
|
|||
|
||||
#include "rknn_api.h"
|
||||
#include "opencv2/core/core.hpp"
|
||||
#include <map> // 用于存储跟踪目标
|
||||
#include <mutex> // 确保线程安全
|
||||
#include <string> // 使用 std::string
|
||||
#include <vector> // 使用 std::vector
|
||||
#include "postprocess.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#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<int, TrackedPerson> 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
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
#ifndef RKNNPOOL_H
|
||||
#define RKNNPOOL_H
|
||||
|
||||
#include "rknn/ThreadPool.hpp"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
|
||||
// rknnModel模型类, inputType模型输入类型, outputType模型输出类型
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
class rknnPool
|
||||
{
|
||||
private:
|
||||
int threadNum;
|
||||
std::string modelPath;
|
||||
|
||||
long long id;
|
||||
std::mutex idMtx, queueMtx;
|
||||
std::unique_ptr<dpool::ThreadPool> pool;
|
||||
std::queue<std::future<outputType>> futs;
|
||||
std::vector<std::shared_ptr<rknnModel>> models;
|
||||
|
||||
protected:
|
||||
int getModelId();
|
||||
|
||||
public:
|
||||
rknnPool(const std::string modelPath, int threadNum);
|
||||
int init();
|
||||
// 模型推理/Model inference
|
||||
int put(inputType inputData);
|
||||
// 获取推理结果/Get the results of your inference
|
||||
int get(outputType &outputData);
|
||||
~rknnPool();
|
||||
};
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
rknnPool<rknnModel, inputType, outputType>::rknnPool(const std::string modelPath, int threadNum)
|
||||
{
|
||||
this->modelPath = modelPath;
|
||||
this->threadNum = threadNum;
|
||||
this->id = 0;
|
||||
}
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
int rknnPool<rknnModel, inputType, outputType>::init()
|
||||
{
|
||||
try
|
||||
{
|
||||
this->pool = std::make_unique<dpool::ThreadPool>(this->threadNum);
|
||||
for (int i = 0; i < this->threadNum; i++)
|
||||
models.push_back(std::make_shared<rknnModel>(this->modelPath.c_str()));
|
||||
}
|
||||
catch (const std::bad_alloc &e)
|
||||
{
|
||||
std::cout << "Out of memory: " << e.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
// 初始化模型/Initialize the model
|
||||
for (int i = 0, ret = 0; i < threadNum; i++)
|
||||
{
|
||||
ret = models[i]->init(models[0]->get_pctx(), i != 0);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
int rknnPool<rknnModel, inputType, outputType>::getModelId()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(idMtx);
|
||||
int modelId = id % threadNum;
|
||||
id++;
|
||||
return modelId;
|
||||
}
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
int rknnPool<rknnModel, inputType, outputType>::put(inputType inputData)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMtx);
|
||||
futs.push(pool->submit(&rknnModel::infer, models[this->getModelId()], inputData));
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
int rknnPool<rknnModel, inputType, outputType>::get(outputType &outputData)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMtx);
|
||||
if(futs.empty() == true)
|
||||
return 1;
|
||||
outputData = futs.front().get();
|
||||
futs.pop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename rknnModel, typename inputType, typename outputType>
|
||||
rknnPool<rknnModel, inputType, outputType>::~rknnPool()
|
||||
{
|
||||
while (!futs.empty())
|
||||
{
|
||||
outputType temp = futs.front().get();
|
||||
futs.pop();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
// video_service.cpp
|
||||
#include "video_service.h"
|
||||
#include <stdio.h>
|
||||
#include "opencv2/imgproc/imgproc.hpp"
|
||||
#include "rknn/rkYolov5s.hpp"
|
||||
#include "rknn/rknnPool.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <chrono> // (新增) 用于计时
|
||||
#include <algorithm> // (新增)
|
||||
|
||||
// (新增) 报警接口函数 (从 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::duration<double>>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch()
|
||||
).count();
|
||||
}
|
||||
|
||||
VideoService::VideoService(std::string model_path,
|
||||
int thread_num,
|
||||
std::string input_url,
|
||||
std::string output_rtsp_url)
|
||||
: model_path_(model_path),
|
||||
thread_num_(thread_num),
|
||||
input_url_(input_url),
|
||||
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());
|
||||
}
|
||||
|
||||
VideoService::~VideoService() {
|
||||
if (running_) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
if (frame_fps_ <= 0) frame_fps_ = 25.0;
|
||||
|
||||
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";
|
||||
|
||||
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
|
||||
|
||||
writer_.open(gst_pipeline,
|
||||
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");
|
||||
capture_.release();
|
||||
return false;
|
||||
}
|
||||
printf("VideoWriter opened successfully.\n");
|
||||
|
||||
// 6. 启动处理线程
|
||||
running_ = true;
|
||||
processing_thread_ = std::thread(&VideoService::processing_loop, this);
|
||||
printf("Processing thread started.\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void VideoService::stop() {
|
||||
printf("Stopping VideoService...\n");
|
||||
running_ = false;
|
||||
|
||||
if (processing_thread_.joinable()) {
|
||||
processing_thread_.join();
|
||||
}
|
||||
printf("Processing thread joined.\n");
|
||||
|
||||
if (capture_.isOpened()) {
|
||||
capture_.release();
|
||||
}
|
||||
if (writer_.isOpened()) {
|
||||
writer_.release();
|
||||
}
|
||||
|
||||
printf("VideoService stopped.\n");
|
||||
}
|
||||
|
||||
|
||||
// (新增) 从 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<cv::Rect> 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;
|
||||
detect_result_group_t detection_results; // (修改) 存储推理结果
|
||||
|
||||
while (running_) {
|
||||
if (!capture_.read(frame)) {
|
||||
spdlog::warn("VideoService: Failed to read frame from capture. Stopping capture.");
|
||||
running_ = false; // 停止循环
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. (并行) 将原始帧放入池中进行推理
|
||||
if (rknn_pool_->put(frame) != 0) {
|
||||
spdlog::error("VideoService: Failed to put frame into 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;
|
||||
}
|
||||
|
||||
// 3. (串行) 在主循环中更新唯一的跟踪器
|
||||
this->update_tracker(detection_results, frame.size());
|
||||
|
||||
// 4. (串行) 在主循环中将跟踪结果绘制到帧上
|
||||
this->draw_results(frame);
|
||||
|
||||
// 5. (串行) 将处理和绘制完毕的帧推流
|
||||
if (writer_.isOpened()) {
|
||||
writer_.write(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// (修改) 移除排空循环 (Draining loop)
|
||||
// 新的 "一进一出" 逻辑不需要排空,退出即停止
|
||||
spdlog::info("VideoService: Processing loop finished.");
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// video_service.h
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <map> // (新增) 用于跟踪
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include "postprocess.h" // (新增) 需要 detect_result_group_t
|
||||
|
||||
// 向前声明
|
||||
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; // 消失的帧数
|
||||
};
|
||||
|
||||
class VideoService {
|
||||
public:
|
||||
VideoService(std::string model_path,
|
||||
int thread_num,
|
||||
std::string input_url,
|
||||
std::string output_rtsp_url);
|
||||
|
||||
~VideoService();
|
||||
|
||||
bool start();
|
||||
void stop();
|
||||
|
||||
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_;
|
||||
|
||||
// 视频属性
|
||||
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::atomic<bool> running_{false};
|
||||
|
||||
// (新增) 跟踪器状态变量 (从 rkYolov5s 移入)
|
||||
cv::Rect intrusion_zone_;
|
||||
std::map<int, TrackedPerson> tracked_persons_;
|
||||
int next_track_id_;
|
||||
double intrusion_time_threshold_;
|
||||
};
|
||||
|
|
@ -34,7 +34,7 @@ void WebServer::start() {
|
|||
}
|
||||
m_thread = std::thread([this]() {
|
||||
spdlog::info("Starting Web server on port {}", m_port);
|
||||
this->port(m_port).run();
|
||||
this->bindaddr("0.0.0.0").port(m_port).run();
|
||||
spdlog::info("Web server has stopped.");
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue