diff --git a/captures/20260107_083403_id20.jpg b/captures/20260107_083403_id20.jpg new file mode 100644 index 0000000..4a7cee4 Binary files /dev/null and b/captures/20260107_083403_id20.jpg differ diff --git a/captures/20260107_083403_id7.jpg b/captures/20260107_083403_id7.jpg new file mode 100644 index 0000000..925bf2e Binary files /dev/null and b/captures/20260107_083403_id7.jpg differ diff --git a/captures/20260107_083404_id19.jpg b/captures/20260107_083404_id19.jpg new file mode 100644 index 0000000..69510cc Binary files /dev/null and b/captures/20260107_083404_id19.jpg differ diff --git a/captures/20260107_083405_id27.jpg b/captures/20260107_083405_id27.jpg new file mode 100644 index 0000000..c0b5239 Binary files /dev/null and b/captures/20260107_083405_id27.jpg differ diff --git a/captures/20260107_083405_id31.jpg b/captures/20260107_083405_id31.jpg new file mode 100644 index 0000000..ce65e18 Binary files /dev/null and b/captures/20260107_083405_id31.jpg differ diff --git a/captures/20260107_083407_id38.jpg b/captures/20260107_083407_id38.jpg new file mode 100644 index 0000000..2f5e2b9 Binary files /dev/null and b/captures/20260107_083407_id38.jpg differ diff --git a/captures/20260107_083410_id45.jpg b/captures/20260107_083410_id45.jpg new file mode 100644 index 0000000..59eabc2 Binary files /dev/null and b/captures/20260107_083410_id45.jpg differ diff --git a/captures/20260107_083411_id50.jpg b/captures/20260107_083411_id50.jpg new file mode 100644 index 0000000..e065233 Binary files /dev/null and b/captures/20260107_083411_id50.jpg differ diff --git a/captures/20260107_083412_id54.jpg b/captures/20260107_083412_id54.jpg new file mode 100644 index 0000000..b68d49c Binary files /dev/null and b/captures/20260107_083412_id54.jpg differ diff --git a/captures/20260107_083413_id60.jpg b/captures/20260107_083413_id60.jpg new file mode 100644 index 0000000..10ec780 Binary files /dev/null and b/captures/20260107_083413_id60.jpg differ diff --git a/captures/20260107_083414_id52.jpg b/captures/20260107_083414_id52.jpg new file mode 100644 index 0000000..ef18880 Binary files /dev/null and b/captures/20260107_083414_id52.jpg differ diff --git a/captures/20260107_083416_id58.jpg b/captures/20260107_083416_id58.jpg new file mode 100644 index 0000000..dbd2a7f Binary files /dev/null and b/captures/20260107_083416_id58.jpg differ diff --git a/config/config.json b/config/config.json index 848f485..5eac0d5 100644 --- a/config/config.json +++ b/config/config.json @@ -1,35 +1,35 @@ { - "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", - "db_database": "smart-car-dev", - "db_host": "127.0.0.1", - "db_pwd": "forlinx", - "db_user": "forlinx", - "device_id": "rk3588-proxy-002", - "log_level": "info", - "mqtt_broker": "tcp://localhost:1883", - "mqtt_client_id_prefix": "vehicle-road-counter-", - "piper_executable_path": "/usr/bin/piper", - "piper_model_path": "/app/models/model.onnx", - "tcp_server_ports": [ - 12345 - ], - "video_config_path": "video_config.json", - "web_server_port": 8080, - "tripwire": { - "enable": true, - "line": { - "p1": { - "x": 0.0, - "y": 0.8 - }, - "p2": { - "x": 1.0, - "y": 0.8 - } - }, - "name": "Main_Gate_Line" - } + "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", + "db_database": "smart-car-dev", + "db_host": "127.0.0.1", + "db_pwd": "forlinx", + "db_user": "forlinx", + "device_id": "rk3588-proxy-002", + "log_level": "info", + "mqtt_broker": "tcp://localhost:1883", + "mqtt_client_id_prefix": "vehicle-road-counter-", + "piper_executable_path": "/usr/bin/piper", + "piper_model_path": "/app/models/model.onnx", + "tcp_server_ports": [ + 12345 + ], + "tripwire": { + "enable": true, + "line": { + "p1": { + "x": 0.0, + "y": 0.8 + }, + "p2": { + "x": 1.0, + "y": 0.8 + } + }, + "name": "Main_Gate_Line" + }, + "video_config_path": "video_config.json", + "web_server_port": 8080 } \ No newline at end of file diff --git a/src/DTOs/common_types.hpp b/src/DTOs/common_types.hpp index abe20ce..87aa230 100644 --- a/src/DTOs/common_types.hpp +++ b/src/DTOs/common_types.hpp @@ -8,18 +8,29 @@ // 或者,如果 DetectionResult 很简单,建议把它也直接移到这里来 // #include "yoloDetector/yolo_detector.hpp" +struct DetectionResult { + int x; + int y; + int width; + int height; + std::string label; + float confidence; + int class_id; +}; + // =========================== // 1. 核心检测与追踪数据结构 // =========================== // 追踪到的车辆对象 struct TrackedVehicle { - int id; // 轨迹ID - cv::Rect box; // 当前位置 - float ev_score; // 新能源平滑分数 - int missing_frames; // 丢失帧数计数 - int last_class_id; // 最终判定类别 (0: Fuel, 1: Green) - std::string label; // 显示标签 + int id; // 轨迹ID + cv::Rect box; // 当前位置 + float ev_score; // 新能源平滑分数 + int missing_frames; // 丢失帧数计数 + int last_class_id; // 最终判定类别 (0: Fuel, 1: Green) + std::string label; // 显示标签 + cv::Point prev_bottom_center = cv::Point(-1, -1); // 车辆上一帧的位置 // [预留] 如果需要记录车辆经过线条的时间,可以在这里加字段 // int64_t cross_line_timestamp = 0; @@ -45,13 +56,3 @@ struct TripwireConfig { cv::Point2f p1_norm; // 起点 cv::Point2f p2_norm; // 终点 }; - -struct DetectionResult { - int x; - int y; - int width; - int height; - std::string label; - float confidence; - int class_id; -}; \ No newline at end of file diff --git a/src/config/config_manager.cc b/src/config/config_manager.cc index 8d9f861..770496c 100644 --- a/src/config/config_manager.cc +++ b/src/config/config_manager.cc @@ -226,4 +226,44 @@ std::string ConfigManager::getDbHost() { } std::string ConfigManager::getDbName() { return get("db_database", "smart-car-dev"); +} + +TripwireConfig ConfigManager::getTripwireConfig() { + TripwireConfig config; + + std::shared_lock lock(m_mutex); + + if (!m_config_json.contains("tripwire") || !m_config_json["tripwire"].is_object()) { + spdlog::warn("Config key 'tripwire' not found or not an object. Using defaults."); + return config; + } + + try { + const auto& j = m_config_json.at("tripwire"); + + // 注意:JSON 中是 "enable",结构体中是 "enabled" + config.enabled = j.value("enable", false); + config.name = j.value("name", "Unnamed_Line"); + + // 5. 解析嵌套的 line 坐标 + if (j.contains("line") && j["line"].is_object()) { + const auto& line = j.at("line"); + + // 解析 p1 (起点) + if (line.contains("p1")) { + config.p1_norm.x = line["p1"].value("x", 0.0f); + config.p1_norm.y = line["p1"].value("y", 0.0f); + } + + // 解析 p2 (终点) + if (line.contains("p2")) { + config.p2_norm.x = line["p2"].value("x", 1.0f); + config.p2_norm.y = line["p2"].value("y", 1.0f); + } + } + } catch (const json::exception& e) { + spdlog::error("Failed to parse TripwireConfig: {}", e.what()); + } + + return config; } \ No newline at end of file diff --git a/src/config/config_manager.h b/src/config/config_manager.h index 783974b..c2093f7 100644 --- a/src/config/config_manager.h +++ b/src/config/config_manager.h @@ -11,6 +11,8 @@ #include #include +#include "DTOs/common_types.hpp" + using json = nlohmann::json; class ConfigManager { @@ -85,6 +87,8 @@ public: std::string getDbHost(); std::string getDbName(); + TripwireConfig getTripwireConfig(); + private: ConfigManager() = default; ~ConfigManager() = default; diff --git a/src/videoService/video_pipeline.cpp b/src/videoService/video_pipeline.cpp index e25b9da..11a2435 100644 --- a/src/videoService/video_pipeline.cpp +++ b/src/videoService/video_pipeline.cpp @@ -2,7 +2,27 @@ #include // for std::max, std::min #include +#include // C++17 +#include // for std::put_time +#include + +#include "config/config_manager.h" +// 引入业务 DAO + +#include "mysqlManager/DeviceIdentificationDao.h" +#include "mysqlManager/ResourceFileDao.h" + +namespace fs = std::filesystem; + const int YoloDetector::NPU_CORE_CNT; + +// === 静态辅助函数 === + +// 获取矩形底部的中心点 (作为车辆的"脚") +static cv::Point getBottomCenter(const cv::Rect& box) { + return cv::Point(box.x + box.width / 2, box.y + box.height); +} + VideoPipeline::VideoPipeline() : running_(false), next_track_id_(0) { detector_ = std::make_unique(); // 模型路径 @@ -11,6 +31,15 @@ VideoPipeline::VideoPipeline() : running_(false), next_track_id_(0) { } else { spdlog::info("YoloDetector initialized successfully."); } + try { + TripwireConfig config = ConfigManager::getInstance().getTripwireConfig(); + setTripwire(config); + spdlog::info("Tripwire loaded via ConfigManager: {}, Enabled: {}", config.name, + config.enabled); + + } catch (const std::exception& e) { + spdlog::warn("Failed to load tripwire config via manager: {}", e.what()); + } } VideoPipeline::~VideoPipeline() { @@ -39,6 +68,16 @@ void VideoPipeline::Stop() { processingThread_.join(); spdlog::info("VideoPipeline Stopped."); } + +void VideoPipeline::setTripwire(const TripwireConfig& config) { + // 可以在运行时更新配置 + tripwire_config_ = config; + // 重置宽高,强制下一帧重新计算像素坐标 + current_frame_width_ = 0; + current_frame_height_ = 0; + spdlog::info("Tripwire config set: {}", config.name); +} + void VideoPipeline::inferenceWorker() { while (running_) { FrameData data; @@ -71,7 +110,7 @@ void VideoPipeline::inferenceWorker() { } } -// [新增] 计算两个矩形的交并比 (IoU) +// 计算两个矩形的交并比 (IoU) float VideoPipeline::computeIOU(const cv::Rect& box1, const cv::Rect& box2) { int x1 = std::max(box1.x, box2.x); int y1 = std::max(box1.y, box2.y); @@ -87,94 +126,253 @@ float VideoPipeline::computeIOU(const cv::Rect& box1, const cv::Rect& box2) { return intersection / (area1 + area2 - intersection); } -// [新增] 核心逻辑:追踪与平滑 -void VideoPipeline::updateTracker(const std::vector& detections) { - // 1. 标记所有现有轨迹为丢失 (missing_frames + 1) +// [核心算法] 判断两条线段是否相交 (A-B) 和 (C-D) +bool VideoPipeline::isLineCrossed(const cv::Point& A, const cv::Point& B, const cv::Point& C, + const cv::Point& D) { + // 1. 快速排斥实验 (Bounding Box Check) + if (std::max(C.x, D.x) < std::min(A.x, B.x) || std::max(A.x, B.x) < std::min(C.x, D.x) || + std::max(C.y, D.y) < std::min(A.y, B.y) || std::max(A.y, B.y) < std::min(C.y, D.y)) { + return false; + } + + // 2. 跨立实验 (Cross Product) + auto crossProduct = [](const cv::Point& a, const cv::Point& b, + const cv::Point& c) -> long long { + return (long long)(b.x - a.x) * (c.y - a.y) - (long long)(b.y - a.y) * (c.x - a.x); + }; + + long long cp1 = crossProduct(A, B, C); + long long cp2 = crossProduct(A, B, D); + long long cp3 = crossProduct(C, D, A); + long long cp4 = crossProduct(C, D, B); + + // 如果 (C, D) 分布在 AB 两侧,且 (A, B) 分布在 CD 两侧,则相交 + if (((cp1 > 0 && cp2 < 0) || (cp1 < 0 && cp2 > 0)) && + ((cp3 > 0 && cp4 < 0) || (cp3 < 0 && cp4 > 0))) { + return true; + } + + return false; +} + +// [业务逻辑] 处理跨线车辆 (异步截图入库) +void VideoPipeline::processCrossing(const TrackedVehicle& vehicle, const cv::Mat& frame) { + // 启动分离线程,避免阻塞主视频流 + std::thread([this, vehicle, frame]() { + // === 1. 准备目录与文件名 === + std::string saveDir = "../captures"; + try { + if (!fs::exists(saveDir)) { + fs::create_directories(saveDir); + } + } catch (const std::exception& e) { + spdlog::error("Failed to create directory {}: {}", saveDir, e.what()); + return; + } + + // 生成基于时间戳的文件名: 20231027_103001_id15.jpg + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::stringstream ss; + ss << std::put_time(std::localtime(&now_c), "%Y%m%d_%H%M%S"); + std::string timeStr = ss.str(); + + std::string fileName = fmt::format("{}_id{}.jpg", timeStr, vehicle.id); + std::string fullPath = fmt::format("{}/{}", saveDir, fileName); + + // === 2. 安全截图 === + cv::Rect safeBox = vehicle.box; + // 边界钳制,防止 crash + if (safeBox.x < 0) + safeBox.x = 0; + if (safeBox.y < 0) + safeBox.y = 0; + if (safeBox.x + safeBox.width > frame.cols) + safeBox.width = frame.cols - safeBox.x; + if (safeBox.y + safeBox.height > frame.rows) + safeBox.height = frame.rows - safeBox.y; + + if (safeBox.width <= 0 || safeBox.height <= 0) { + spdlog::warn("Invalid box for screenshot, skip."); + return; + } + + try { + // Clone 数据,因为主线程可能会复用 frame 内存 (虽然当前架构中 frame + // 是独立的,但为了健壮性) + cv::Mat snapshot = frame(safeBox).clone(); + if (cv::imwrite(fullPath, snapshot)) { + spdlog::info("Snapshot saved: {}", fullPath); + } else { + spdlog::error("Failed to write image: {}", fullPath); + return; + } + } catch (const std::exception& e) { + spdlog::error("Save image exception: {}", e.what()); + return; + } + + // === 3. 数据库入库 === + DeviceIdentificationDao deviceDao; + ResourceFileDao fileDao; + + // 3.1 准备枚举数据 + // class_id: 1 -> EV(Green), 0 -> Fuel(Blue) + CarType cType = (vehicle.last_class_id == 1) ? CarType::ELECTRIC : CarType::GASOLINE; + CarColor cColor = (vehicle.last_class_id == 1) ? CarColor::GREEN : CarColor::BLUE; + + // 3.2 插入业务主表 + // 假设 SystemID 为 1,实际项目可能需配置 + int64_t systemId = 1; + std::string location = + tripwire_config_.name.empty() ? "Unkown_Line" : tripwire_config_.name; + + int64_t dataId = deviceDao.ReportIdentification(systemId, location, cColor, cType); + + if (dataId > 0) { + // 3.3 插入文件关联表 + bool fileSaved = fileDao.SaveFile("tb_device_identification_data", // source_table + dataId, // business_id + fullPath, // file_path + fileName, // source_file_name + FileType::ORIGINAL // file_type + ); + + if (fileSaved) { + spdlog::debug("DB Transaction Complete. DataID: {}", dataId); + } else { + spdlog::error("Failed to save file record for DataID: {}", dataId); + } + } else { + spdlog::error("Failed to insert device identification data."); + } + }).detach(); // 让线程后台运行 +} + +// 核心逻辑:追踪、平滑与跨线检测 +void VideoPipeline::updateTracker(const FrameData& frameData) { + const auto& detections = frameData.results; + const auto& frame = frameData.original_frame; + + // 1. 获取分辨率并更新绊线像素坐标 + int width = frame.cols; + int height = frame.rows; + + if (width != current_frame_width_ || height != current_frame_height_) { + current_frame_width_ = width; + current_frame_height_ = height; + + if (tripwire_config_.enabled) { + tripwire_p1_pixel_.x = (int)(tripwire_config_.p1_norm.x * width); + tripwire_p1_pixel_.y = (int)(tripwire_config_.p1_norm.y * height); + tripwire_p2_pixel_.x = (int)(tripwire_config_.p2_norm.x * width); + tripwire_p2_pixel_.y = (int)(tripwire_config_.p2_norm.y * height); + + spdlog::info("Tripwire pixel coords updated: ({},{}) -> ({},{})", tripwire_p1_pixel_.x, + tripwire_p1_pixel_.y, tripwire_p2_pixel_.x, tripwire_p2_pixel_.y); + } + } + + // 2. 标记现有轨迹丢失 for (auto& pair : tracks_) { pair.second.missing_frames++; } - // 2. 匹配当前帧检测结果与现有轨迹 + // 3. 匹配当前帧 for (const auto& det : detections) { cv::Rect detBox(det.x, det.y, det.width, det.height); + cv::Point current_bottom = getBottomCenter(detBox); int best_match_id = -1; float max_iou = 0.0f; - // 寻找 IoU 最大的匹配 for (auto& pair : tracks_) { float iou = computeIOU(detBox, pair.second.box); - if (iou > 0.3f && iou > max_iou) { // 阈值 0.3 可根据需要调整 + if (iou > 0.3f && iou > max_iou) { max_iou = iou; best_match_id = pair.first; } } - // 假设: class_id == 1 是新能源(Green), class_id == 0 是油车(Fuel) - // 请根据你模型的实际定义修改这里的 ID float current_is_ev = (det.class_id == 1) ? 1.0f : 0.0f; if (best_match_id != -1) { - // === 匹配成功:更新平滑分数 === + // === 匹配成功 === TrackedVehicle& track = tracks_[best_match_id]; - track.box = detBox; - track.missing_frames = 0; // 重置丢失计数 - // [核心算法] 指数移动平均 (EMA) - // alpha = 0.1 表示新结果占10%权重,历史占90%,数值越小越平滑,反应越慢 + // [新增] 跨线检测 + if (tripwire_config_.enabled && track.prev_bottom_center.x != -1) { + bool crossed = isLineCrossed(tripwire_p1_pixel_, tripwire_p2_pixel_, // 线 + track.prev_bottom_center, current_bottom // 轨迹 + ); + + if (crossed) { + spdlog::info(">>> Vehicle {} Crossed Line: {} <<<", track.id, + tripwire_config_.name); + // 触发业务逻辑 + processCrossing(track, frame); + } + } + + // 更新轨迹状态 + track.prev_bottom_center = current_bottom; // 更新"上一帧"位置为"当前" + track.box = detBox; + track.missing_frames = 0; + float alpha = 0.15f; track.ev_score = track.ev_score * (1.0f - alpha) + current_is_ev * alpha; } else { - // === 未匹配:创建新轨迹 === + // === 新轨迹 === TrackedVehicle newTrack; newTrack.id = next_track_id_++; newTrack.box = detBox; + newTrack.prev_bottom_center = current_bottom; // 初始位置 newTrack.missing_frames = 0; - newTrack.ev_score = current_is_ev; // 初始分数为当前检测结果 + newTrack.ev_score = current_is_ev; newTrack.last_class_id = det.class_id; tracks_[newTrack.id] = newTrack; } } - // 3. 移除长时间丢失的轨迹 & 更新显示状态 + // 4. 移除过期轨迹 & 更新显示类别 for (auto it = tracks_.begin(); it != tracks_.end();) { - if (it->second.missing_frames > 10) { // 超过10帧未检测到则移除 + if (it->second.missing_frames > 10) { it = tracks_.erase(it); } else { - // 根据平滑后的分数决定最终类别 - // 阈值 0.5: 分数 > 0.5 判定为新能源 + // 分数 > 0.5 判定为新能源 int final_class = (it->second.ev_score > 0.5f) ? 1 : 0; - it->second.last_class_id = final_class; - it->second.label = (final_class == 1) ? "Green" : "Fuel"; // 显示文本 - + it->second.label = (final_class == 1) ? "Green" : "Fuel"; ++it; } } } -// [修改] 绘制函数使用 TrackedVehicle void VideoPipeline::drawOverlay(cv::Mat& frame, const std::vector& trackedObjects) { + // 1. 画绊线 (如果启用) + if (tripwire_config_.enabled) { + cv::line(frame, tripwire_p1_pixel_, tripwire_p2_pixel_, cv::Scalar(0, 255, 255), 2); + cv::putText(frame, tripwire_config_.name, tripwire_p1_pixel_, cv::FONT_HERSHEY_SIMPLEX, 0.7, + cv::Scalar(0, 255, 255), 2); + } + + // 2. 画车辆 for (const auto& trk : trackedObjects) { - // 如果丢失了几帧但还在内存里,用虚线或者灰色表示(可选),这里保持原样 if (trk.missing_frames > 0) - continue; // 暂时只画当前帧存在的 + continue; - // 颜色:新能源用绿色,油车用红色 cv::Scalar color = (trk.last_class_id == 1) ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255); - cv::rectangle(frame, trk.box, color, 2); - // 显示 类别 + 平滑后的分数 - // 例如: Green 0.85 - std::string text = fmt::format("{} {:.2f}", trk.label, trk.ev_score); + // 画中心点轨迹(可选,调试用) + // cv::circle(frame, trk.prev_bottom_center, 3, cv::Scalar(255, 0, 0), -1); + std::string text = fmt::format("{} {:.2f}", trk.label, trk.ev_score); int y_pos = trk.box.y - 5; if (y_pos < 10) y_pos = trk.box.y + 15; - cv::putText(frame, text, cv::Point(trk.box.x, y_pos), cv::FONT_HERSHEY_SIMPLEX, 0.6, color, 2); } @@ -192,17 +390,15 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl, boo return; } - // 1. 启动 3 个工作线程 (对应 3 个 NPU 核心) - // 你的 NPU 利用率将在这里被填满 + // 1. 启动工作线程 for (int i = 0; i < 3; ++i) { worker_threads_.emplace_back(&VideoPipeline::inferenceWorker, this); } int width = cap.get(cv::CAP_PROP_FRAME_WIDTH); int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT); - const double TARGET_FPS = 30.0; // 提升目标 FPS + const double TARGET_FPS = 30.0; - // ... GStreamer pipeline string 设置保持不变 ... std::stringstream pipeline; pipeline << "appsrc ! " << "videoconvert ! " @@ -215,43 +411,38 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl, boo cv::VideoWriter writer; writer.open(pipeline.str(), cv::CAP_GSTREAMER, 0, TARGET_FPS, cv::Size(width, height), true); - long read_frame_idx = 0; // 读取计数 - long write_frame_idx = 0; // 写入计数 + long read_frame_idx = 0; + long write_frame_idx = 0; while (running_) { - // === 阶段 A: 读取并分发任务 (生产者) === - // 限制预读数量,防止内存爆满 (例如最多预读 5 帧) + // === 阶段 A: 生产者 === { std::unique_lock lock(input_mtx_); - // 如果输入队列满了,等待工作线程处理 if (input_queue_.size() < MAX_INPUT_QUEUE_SIZE) { cv::Mat frame; if (cap.read(frame) && !frame.empty()) { FrameData data; data.frame_id = read_frame_idx++; - data.original_frame = frame; // 拷贝一份 (必须,因为 cv::Mat 是引用计数) + data.original_frame = frame; input_queue_.push(data); - input_cv_.notify_one(); // 唤醒一个工作线程 + input_cv_.notify_one(); } else { if (isFileSource) { - // 文件读完了的处理... running_ = false; } else { - // 网络流断线的处理... + // Network disconnected logic... } } } } - // === 阶段 B: 按顺序收集结果并处理 (消费者) === + // === 阶段 B: 消费者 === FrameData current_data; bool has_data = false; { std::unique_lock lock(output_mtx_); - // 检查输出缓冲区里是否有我们期待的下一帧 (write_frame_idx) - // 因为多线程处理,第5帧可能比第4帧先处理完,必须等待第4帧 auto it = output_buffer_.find(write_frame_idx); if (it != output_buffer_.end()) { current_data = std::move(it->second); @@ -262,38 +453,29 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl, boo } if (has_data) { - // 注意:updateTracker 和 drawOverlay 必须在主线程串行执行 - // 因为它们依赖 tracks_ 状态,且必须按时间顺序更新 + // [修改] 传递整个 FrameData 结构体 + updateTracker(current_data); - // 1. 追踪 (CPU) - updateTracker(current_data.results); - - // 2. 准备绘图数据 (CPU) + // 准备绘图 std::vector tracks_to_draw; for (const auto& pair : tracks_) { tracks_to_draw.push_back(pair.second); } - // 3. 绘图 (CPU) drawOverlay(current_data.original_frame, tracks_to_draw); - // 4. 推流 (IO) if (writer.isOpened()) { writer.write(current_data.original_frame); } - // 简单的 FPS 打印 if (write_frame_idx % 60 == 0) { spdlog::info("Processed Frame ID: {}", write_frame_idx); } } else { - // 如果没有等到当前帧,稍微休眠一下避免死循环占满 CPU - // 但不要睡太久,否则延迟高 std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } - // 清理:通知线程退出并 Join input_cv_.notify_all(); for (auto& t : worker_threads_) { if (t.joinable()) @@ -303,4 +485,4 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl, boo cap.release(); writer.release(); -} +} \ No newline at end of file diff --git a/src/videoService/video_pipeline.hpp b/src/videoService/video_pipeline.hpp index 1b0e420..3b59190 100644 --- a/src/videoService/video_pipeline.hpp +++ b/src/videoService/video_pipeline.hpp @@ -2,16 +2,18 @@ #include #include +#include // C++17 用于创建文件夹 +#include // 用于时间格式化 #include #include #include #include #include +#include #include #include #include -#include "DTOs/common_types.hpp" #include "spdlog/spdlog.h" #include "yoloDetector/yolo_detector.hpp" @@ -32,7 +34,7 @@ private: void inferenceWorker(); void drawOverlay(cv::Mat& frame, const std::vector& trackedObjects); - void updateTracker(const std::vector& detections); + void updateTracker(const FrameData& frameData); float computeIOU(const cv::Rect& box1, const cv::Rect& box2); bool isLineCrossed(const cv::Point& p1, const cv::Point& p2, const cv::Point& line_start, @@ -63,4 +65,10 @@ private: std::map output_buffer_; std::mutex output_mtx_; std::condition_variable output_cv_; + + TripwireConfig tripwire_config_; // 配置数据 + cv::Point tripwire_p1_pixel_; // 缓存:转换后的起点像素坐标 + cv::Point tripwire_p2_pixel_; // 缓存:转换后的终点像素坐标 + int current_frame_width_ = 0; // 缓存:当前分辨率宽 + int current_frame_height_ = 0; // 缓存:当前分辨率高 };