- 创建基本项目结构和目录 - 添加CMake构建系统 - 实现基础的配置解析功能 - 添加YOLO推理框架支持 - 集成RTSP和视频流处理功能 - 添加性能监控和日志系统
467 lines
18 KiB
C++
467 lines
18 KiB
C++
#include "yaml_config_parser.hpp"
|
|
#include "../common/logger.hpp"
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <opencv2/core.hpp>
|
|
|
|
namespace pipeline {
|
|
|
|
bool YamlConfigParser::parse(const std::string& config_file) {
|
|
try {
|
|
Logger::info("Parsing config file: " + config_file);
|
|
yaml_config_ = YAML::LoadFile(config_file);
|
|
|
|
// 解析各个配置部分
|
|
if (!parseInputConfig(yaml_config_["input"])) {
|
|
Logger::error("Failed to parse input config");
|
|
return false;
|
|
}
|
|
|
|
if (!parseModelConfig(yaml_config_["inference"])) {
|
|
Logger::error("Failed to parse model config");
|
|
return false;
|
|
}
|
|
|
|
if (!parseRenderConfig(yaml_config_["render"])) {
|
|
Logger::error("Failed to parse render config");
|
|
return false;
|
|
}
|
|
|
|
if (!parseOutputConfig(yaml_config_["output"])) {
|
|
Logger::error("Failed to parse output config");
|
|
return false;
|
|
}
|
|
|
|
if (!parseLogConfig(yaml_config_["log"])) {
|
|
Logger::error("Failed to parse log config");
|
|
return false;
|
|
}
|
|
|
|
Logger::info("Successfully parsed all configurations");
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
Logger::error("YAML parsing error: " + std::string(e.what()));
|
|
return false;
|
|
} catch (const std::exception& e) {
|
|
Logger::error("Error in parse: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::validate() {
|
|
// 基本验证
|
|
if (config_.input.sources.empty()) {
|
|
std::cerr << "Error: No input sources configured" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 验证输入源配置
|
|
for (const auto& source : config_.input.sources) {
|
|
if (source.type.empty() || source.name.empty()) {
|
|
std::cerr << "Error: Input source missing type or name" << std::endl;
|
|
return false;
|
|
}
|
|
if (source.type == "rtsp" && source.url.empty()) {
|
|
std::cerr << "Error: RTSP source missing URL" << std::endl;
|
|
return false;
|
|
}
|
|
if (source.buffer_size <= 0) {
|
|
std::cerr << "Error: Invalid buffer size for source: " << source.name << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 验证推理配置
|
|
if (config_.inference.engine_path.empty()) {
|
|
std::cerr << "Error: Model engine path not specified" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.inference.input_shape.empty()) {
|
|
std::cerr << "Error: Model input shape not specified" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.inference.threshold.conf < 0.0f || config_.inference.threshold.conf > 1.0f) {
|
|
std::cerr << "Error: Invalid confidence threshold (must be between 0.0 and 1.0)" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.inference.threshold.nms < 0.0f || config_.inference.threshold.nms > 1.0f) {
|
|
std::cerr << "Error: Invalid NMS threshold (must be between 0.0 and 1.0)" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 验证渲染配置
|
|
// 验证窗口配置
|
|
if (config_.render.window.width <= 0 || config_.render.window.height <= 0) {
|
|
std::cerr << "Error: Invalid window dimensions" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.render.window.name.empty()) {
|
|
std::cerr << "Error: Window name not specified" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 验证默认样式
|
|
if (config_.render.default_style.transparency < 0.0f ||
|
|
config_.render.default_style.transparency > 1.0f) {
|
|
std::cerr << "Error: Invalid default style transparency (must be between 0.0 and 1.0)" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.render.default_style.box_thickness <= 0 ||
|
|
config_.render.default_style.font_thickness <= 0) {
|
|
std::cerr << "Error: Invalid default style thickness values" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.render.default_style.font_scale <= 0.0) {
|
|
std::cerr << "Error: Invalid default style font scale" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 验证类别样式
|
|
for (const auto& [class_name, style] : config_.render.class_styles) {
|
|
if (class_name.empty()) {
|
|
std::cerr << "Error: Empty class name in style configuration" << std::endl;
|
|
return false;
|
|
}
|
|
if (style.transparency < 0.0f || style.transparency > 1.0f) {
|
|
std::cerr << "Error: Invalid transparency for class " << class_name << std::endl;
|
|
return false;
|
|
}
|
|
if (style.box_thickness <= 0 || style.font_thickness <= 0) {
|
|
std::cerr << "Error: Invalid thickness values for class " << class_name << std::endl;
|
|
return false;
|
|
}
|
|
if (style.font_scale <= 0.0) {
|
|
std::cerr << "Error: Invalid font scale for class " << class_name << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 验证性能指标配置
|
|
if (config_.render.metrics.update_interval_ms <= 0) {
|
|
std::cerr << "Error: Invalid metrics update interval" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
// 验证输出配置
|
|
for (const auto& target : config_.output.targets) {
|
|
if (target.type.empty() || target.name.empty()) {
|
|
std::cerr << "Error: Output target missing type or name" << std::endl;
|
|
return false;
|
|
}
|
|
if (target.type == "video") {
|
|
if (target.path.empty()) {
|
|
std::cerr << "Error: Video output target missing path" << std::endl;
|
|
return false;
|
|
}
|
|
if (target.fps <= 0) {
|
|
std::cerr << "Error: Invalid video output fps" << std::endl;
|
|
return false;
|
|
}
|
|
if (target.bitrate <= 0) {
|
|
std::cerr << "Error: Invalid video output bitrate" << std::endl;
|
|
return false;
|
|
}
|
|
if (target.codec.empty()) {
|
|
std::cerr << "Error: Video output codec not specified" << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 验证日志配置
|
|
if (config_.log.level.empty()) {
|
|
std::cerr << "Error: Log level not specified" << std::endl;
|
|
return false;
|
|
}
|
|
if (config_.log.save_path.empty()) {
|
|
std::cerr << "Error: Log save path not specified" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool YamlConfigParser::parseInputConfig(const YAML::Node& node) {
|
|
try {
|
|
// 解析输入源
|
|
if (node["sources"]) {
|
|
for (const auto& source : node["sources"]) {
|
|
InputSourceConfig src_config;
|
|
src_config.type = source["type"].as<std::string>();
|
|
src_config.name = source["name"].as<std::string>();
|
|
if (source["url"]) {
|
|
src_config.url = source["url"].as<std::string>();
|
|
}
|
|
if (source["buffer_size"]) {
|
|
src_config.buffer_size = source["buffer_size"].as<int>();
|
|
}
|
|
// 解析输出目标列表
|
|
if (source["outputs"]) {
|
|
src_config.outputs = source["outputs"].as<std::vector<std::string>>();
|
|
}
|
|
config_.input.sources.push_back(src_config);
|
|
}
|
|
}
|
|
|
|
// 解析批处理大小
|
|
if (node["max_batch_size"]) {
|
|
config_.input.max_batch_size = node["max_batch_size"].as<int>();
|
|
}
|
|
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
Logger::error("Error parsing input config: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::parseModelConfig(const YAML::Node& node) {
|
|
try {
|
|
if (!node["model"]) {
|
|
Logger::error("Model configuration section not found");
|
|
return false;
|
|
}
|
|
|
|
const auto& model = node["model"];
|
|
|
|
// 检查必需的字段
|
|
if (!model["onnx_path"]) {
|
|
Logger::error("ONNX path not specified in model config");
|
|
return false;
|
|
}
|
|
if (!model["engine_path"]) {
|
|
Logger::error("Engine path not specified in model config");
|
|
return false;
|
|
}
|
|
if (!model["input_shape"]) {
|
|
Logger::error("Input shape not specified in model config");
|
|
return false;
|
|
}
|
|
|
|
// 读取配置
|
|
config_.inference.engine_path = model["engine_path"].as<std::string>();
|
|
config_.inference.input_shape = model["input_shape"].as<std::vector<int>>();
|
|
config_.inference.precision = model["precision"] ? model["precision"].as<std::string>() : "FP16";
|
|
|
|
// 读取阈值配置
|
|
if (node["threshold"]) {
|
|
config_.inference.threshold.conf = node["threshold"]["conf"] ?
|
|
node["threshold"]["conf"].as<float>() : 0.5f;
|
|
config_.inference.threshold.nms = node["threshold"]["nms"] ?
|
|
node["threshold"]["nms"].as<float>() : 0.45f;
|
|
}
|
|
|
|
// 读取GPU ID
|
|
config_.inference.gpu_id = node["gpu_id"] ? node["gpu_id"].as<int>() : 0;
|
|
|
|
// 打印读取的配置
|
|
Logger::info("Loaded model configuration:");
|
|
Logger::info(" Engine path: " + config_.inference.engine_path);
|
|
Logger::info(" Input shape: " + std::to_string(config_.inference.input_shape[0]) + "," +
|
|
std::to_string(config_.inference.input_shape[1]) + "," +
|
|
std::to_string(config_.inference.input_shape[2]));
|
|
Logger::info(" Precision: " + config_.inference.precision);
|
|
Logger::info(" GPU ID: " + std::to_string(config_.inference.gpu_id));
|
|
Logger::info(" Confidence threshold: " + std::to_string(config_.inference.threshold.conf));
|
|
Logger::info(" NMS threshold: " + std::to_string(config_.inference.threshold.nms));
|
|
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
Logger::error("Failed to parse model config: " + std::string(e.what()));
|
|
return false;
|
|
} catch (const std::exception& e) {
|
|
Logger::error("Error in parseModelConfig: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::parseRenderConfig(const YAML::Node& node) {
|
|
try {
|
|
// 解析窗口配置
|
|
if (node["window"]) {
|
|
const auto& window = node["window"];
|
|
if (window["name"]) {
|
|
config_.render.window.name = window["name"].as<std::string>();
|
|
}
|
|
if (window["width"]) {
|
|
config_.render.window.width = window["width"].as<int>();
|
|
}
|
|
if (window["height"]) {
|
|
config_.render.window.height = window["height"].as<int>();
|
|
}
|
|
if (window["fullscreen"]) {
|
|
config_.render.window.fullscreen = window["fullscreen"].as<bool>();
|
|
}
|
|
}
|
|
|
|
// 解析默认样式
|
|
if (node["default_style"]) {
|
|
const auto& style = node["default_style"];
|
|
if (style["box_color"]) {
|
|
auto color = style["box_color"].as<std::vector<int>>();
|
|
if (color.size() >= 3) {
|
|
config_.render.default_style.box_color = cv::Scalar(color[0], color[1], color[2]);
|
|
}
|
|
}
|
|
if (style["text_color"]) {
|
|
auto color = style["text_color"].as<std::vector<int>>();
|
|
if (color.size() >= 3) {
|
|
config_.render.default_style.text_color = cv::Scalar(color[0], color[1], color[2]);
|
|
}
|
|
}
|
|
if (style["transparency"]) {
|
|
config_.render.default_style.transparency = style["transparency"].as<float>();
|
|
}
|
|
if (style["box_thickness"]) {
|
|
config_.render.default_style.box_thickness = style["box_thickness"].as<int>();
|
|
}
|
|
if (style["font_scale"]) {
|
|
config_.render.default_style.font_scale = style["font_scale"].as<double>();
|
|
}
|
|
if (style["font_thickness"]) {
|
|
config_.render.default_style.font_thickness = style["font_thickness"].as<int>();
|
|
}
|
|
}
|
|
|
|
// 解析类别样式
|
|
if (node["class_styles"]) {
|
|
for (const auto& class_style : node["class_styles"]) {
|
|
std::string class_name = class_style.first.as<std::string>();
|
|
const auto& style = class_style.second;
|
|
RenderConfig::ClassStyle class_config;
|
|
|
|
if (style["box_color"]) {
|
|
auto color = style["box_color"].as<std::vector<int>>();
|
|
if (color.size() >= 3) {
|
|
class_config.box_color = cv::Scalar(color[0], color[1], color[2]);
|
|
}
|
|
}
|
|
if (style["text_color"]) {
|
|
auto color = style["text_color"].as<std::vector<int>>();
|
|
if (color.size() >= 3) {
|
|
class_config.text_color = cv::Scalar(color[0], color[1], color[2]);
|
|
}
|
|
}
|
|
if (style["transparency"]) {
|
|
class_config.transparency = style["transparency"].as<float>();
|
|
}
|
|
if (style["box_thickness"]) {
|
|
class_config.box_thickness = style["box_thickness"].as<int>();
|
|
}
|
|
if (style["font_scale"]) {
|
|
class_config.font_scale = style["font_scale"].as<double>();
|
|
}
|
|
if (style["font_thickness"]) {
|
|
class_config.font_thickness = style["font_thickness"].as<int>();
|
|
}
|
|
|
|
config_.render.class_styles[class_name] = class_config;
|
|
}
|
|
}
|
|
|
|
// 解析性能指标配置
|
|
if (node["metrics"]) {
|
|
const auto& metrics = node["metrics"];
|
|
if (metrics["show_fps"]) {
|
|
config_.render.metrics.show_fps = metrics["show_fps"].as<bool>();
|
|
}
|
|
if (metrics["show_inference_time"]) {
|
|
config_.render.metrics.show_inference_time = metrics["show_inference_time"].as<bool>();
|
|
}
|
|
if (metrics["show_gpu_usage"]) {
|
|
config_.render.metrics.show_gpu_usage = metrics["show_gpu_usage"].as<bool>();
|
|
}
|
|
if (metrics["update_interval_ms"]) {
|
|
config_.render.metrics.update_interval_ms = metrics["update_interval_ms"].as<int>();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
Logger::error("Error parsing render config: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::parseOutputConfig(const YAML::Node& node) {
|
|
try {
|
|
if (node["targets"]) {
|
|
for (const auto& target : node["targets"]) {
|
|
OutputTargetConfig target_config;
|
|
target_config.type = target["type"].as<std::string>();
|
|
target_config.name = target["name"].as<std::string>();
|
|
if (target["path"]) {
|
|
target_config.path = target["path"].as<std::string>();
|
|
}
|
|
if (target["fps"]) {
|
|
target_config.fps = target["fps"].as<int>();
|
|
}
|
|
if (target["codec"]) {
|
|
target_config.codec = target["codec"].as<std::string>();
|
|
}
|
|
if (target["bitrate"]) {
|
|
target_config.bitrate = target["bitrate"].as<int>();
|
|
}
|
|
config_.output.targets.push_back(target_config);
|
|
}
|
|
}
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::parseLogConfig(const YAML::Node& node) {
|
|
try {
|
|
if (node["level"]) {
|
|
config_.log.level = node["level"].as<std::string>();
|
|
}
|
|
if (node["save_path"]) {
|
|
config_.log.save_path = node["save_path"].as<std::string>();
|
|
}
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool YamlConfigParser::parseRtspConfig(const YAML::Node& config, RtspReader::Config& rtsp_config) {
|
|
try {
|
|
auto rtsp_node = config["rtsp"];
|
|
if (!rtsp_node) {
|
|
return false;
|
|
}
|
|
|
|
// 设置默认值
|
|
rtsp_config = RtspReader::Config();
|
|
|
|
// 读取配置
|
|
if (rtsp_node["buffer_size"]) {
|
|
rtsp_config.buffer_size = rtsp_node["buffer_size"].as<int>();
|
|
}
|
|
if (rtsp_node["max_retry_count"]) {
|
|
rtsp_config.max_retry_count = rtsp_node["max_retry_count"].as<int>();
|
|
}
|
|
if (rtsp_node["retry_interval_ms"]) {
|
|
rtsp_config.retry_interval_ms = rtsp_node["retry_interval_ms"].as<int>();
|
|
}
|
|
if (rtsp_node["frame_timeout_ms"]) {
|
|
rtsp_config.frame_timeout_ms = rtsp_node["frame_timeout_ms"].as<int>();
|
|
}
|
|
if (rtsp_node["target_fps"]) {
|
|
rtsp_config.target_fps = rtsp_node["target_fps"].as<float>();
|
|
}
|
|
|
|
return true;
|
|
} catch (const YAML::Exception& e) {
|
|
Logger::error("Error parsing RTSP config: " + std::string(e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 工厂函数实现
|
|
std::unique_ptr<ConfigParser> createConfigParser() {
|
|
return std::make_unique<YamlConfigParser>();
|
|
}
|
|
|
|
} // namespace pipeline
|