- 创建基本项目结构和目录 - 添加CMake构建系统 - 实现基础的配置解析功能 - 添加YOLO推理框架支持 - 集成RTSP和视频流处理功能 - 添加性能监控和日志系统
367 lines
9.8 KiB
C++
367 lines
9.8 KiB
C++
#include <gtest/gtest.h>
|
||
#include "pipeline/common/yaml_config_parser.hpp"
|
||
#include <fstream>
|
||
#include <filesystem>
|
||
|
||
using namespace pipeline;
|
||
|
||
class YamlConfigTest : public ::testing::Test {
|
||
protected:
|
||
void SetUp() override {
|
||
// 创建测试配置文件
|
||
std::ofstream test_config("test_config.yaml");
|
||
test_config << R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
buffer_size: 30
|
||
max_batch_size: 4
|
||
|
||
inference:
|
||
model:
|
||
onnx_path: /path/to/model.onnx
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
precision: FP16
|
||
threshold:
|
||
conf: 0.5
|
||
nms: 0.45
|
||
gpu_id: 0
|
||
|
||
render:
|
||
window:
|
||
name: "Detection Results"
|
||
width: 1280
|
||
height: 720
|
||
fullscreen: false
|
||
|
||
default_style:
|
||
box_color: [0, 255, 0]
|
||
text_color: [255, 255, 255]
|
||
transparency: 0.0
|
||
box_thickness: 2
|
||
font_scale: 0.5
|
||
font_thickness: 1
|
||
|
||
class_styles:
|
||
person:
|
||
box_color: [255, 0, 0]
|
||
text_color: [255, 255, 255]
|
||
transparency: 0.2
|
||
box_thickness: 2
|
||
font_scale: 0.5
|
||
font_thickness: 1
|
||
car:
|
||
box_color: [0, 255, 0]
|
||
text_color: [255, 255, 255]
|
||
transparency: 0.2
|
||
box_thickness: 2
|
||
font_scale: 0.5
|
||
font_thickness: 1
|
||
|
||
metrics:
|
||
show_fps: true
|
||
show_inference_time: true
|
||
show_gpu_usage: true
|
||
update_interval_ms: 1000
|
||
|
||
output:
|
||
targets:
|
||
- type: video
|
||
name: output1
|
||
path: /path/to/output.mp4
|
||
fps: 30
|
||
codec: h264
|
||
bitrate: 4000000
|
||
|
||
log:
|
||
level: info
|
||
save_path: logs/
|
||
)";
|
||
test_config.close();
|
||
}
|
||
|
||
void TearDown() override {
|
||
// 清理测试文件
|
||
std::filesystem::remove("test_config.yaml");
|
||
std::filesystem::remove("invalid_config.yaml");
|
||
}
|
||
};
|
||
|
||
TEST_F(YamlConfigTest, LoadConfig) {
|
||
auto parser = createConfigParser();
|
||
ASSERT_TRUE(parser->parse("test_config.yaml"));
|
||
|
||
const auto& config = parser->getConfig();
|
||
|
||
// 验证输入配置
|
||
ASSERT_EQ(config.input.sources.size(), 1);
|
||
EXPECT_EQ(config.input.sources[0].type, "rtsp");
|
||
EXPECT_EQ(config.input.sources[0].name, "camera1");
|
||
EXPECT_EQ(config.input.sources[0].url, "rtsp://example.com/stream1");
|
||
EXPECT_EQ(config.input.sources[0].buffer_size, 30);
|
||
EXPECT_EQ(config.input.max_batch_size, 4);
|
||
|
||
// 验证推理配置
|
||
EXPECT_EQ(config.inference.engine_path, "/path/to/model.engine");
|
||
ASSERT_EQ(config.inference.input_shape.size(), 3);
|
||
EXPECT_EQ(config.inference.input_shape[0], 3);
|
||
EXPECT_EQ(config.inference.input_shape[1], 640);
|
||
EXPECT_EQ(config.inference.input_shape[2], 640);
|
||
EXPECT_EQ(config.inference.precision, "FP16");
|
||
EXPECT_FLOAT_EQ(config.inference.threshold.conf, 0.5f);
|
||
EXPECT_FLOAT_EQ(config.inference.threshold.nms, 0.45f);
|
||
EXPECT_EQ(config.inference.gpu_id, 0);
|
||
|
||
// 验证渲染配置
|
||
// 窗口配置
|
||
EXPECT_EQ(config.render.window.name, "Detection Results");
|
||
EXPECT_EQ(config.render.window.width, 1280);
|
||
EXPECT_EQ(config.render.window.height, 720);
|
||
EXPECT_FALSE(config.render.window.fullscreen);
|
||
|
||
// 默认样式
|
||
EXPECT_EQ(config.render.default_style.box_color, cv::Scalar(0, 255, 0));
|
||
EXPECT_EQ(config.render.default_style.text_color, cv::Scalar(255, 255, 255));
|
||
EXPECT_FLOAT_EQ(config.render.default_style.transparency, 0.0f);
|
||
EXPECT_EQ(config.render.default_style.box_thickness, 2);
|
||
EXPECT_DOUBLE_EQ(config.render.default_style.font_scale, 0.5);
|
||
EXPECT_EQ(config.render.default_style.font_thickness, 1);
|
||
|
||
// 类别样式
|
||
ASSERT_EQ(config.render.class_styles.size(), 2);
|
||
const auto& person_style = config.render.class_styles.at("person");
|
||
EXPECT_EQ(person_style.box_color, cv::Scalar(255, 0, 0));
|
||
EXPECT_EQ(person_style.text_color, cv::Scalar(255, 255, 255));
|
||
EXPECT_FLOAT_EQ(person_style.transparency, 0.2f);
|
||
|
||
const auto& car_style = config.render.class_styles.at("car");
|
||
EXPECT_EQ(car_style.box_color, cv::Scalar(0, 255, 0));
|
||
EXPECT_EQ(car_style.text_color, cv::Scalar(255, 255, 255));
|
||
EXPECT_FLOAT_EQ(car_style.transparency, 0.2f);
|
||
|
||
// 性能指标配置
|
||
EXPECT_TRUE(config.render.metrics.show_fps);
|
||
EXPECT_TRUE(config.render.metrics.show_inference_time);
|
||
EXPECT_TRUE(config.render.metrics.show_gpu_usage);
|
||
EXPECT_EQ(config.render.metrics.update_interval_ms, 1000);
|
||
|
||
// 验证输出配置
|
||
ASSERT_EQ(config.output.targets.size(), 1);
|
||
EXPECT_EQ(config.output.targets[0].type, "video");
|
||
EXPECT_EQ(config.output.targets[0].name, "output1");
|
||
EXPECT_EQ(config.output.targets[0].path, "/path/to/output.mp4");
|
||
EXPECT_EQ(config.output.targets[0].fps, 30);
|
||
EXPECT_EQ(config.output.targets[0].codec, "h264");
|
||
EXPECT_EQ(config.output.targets[0].bitrate, 4000000);
|
||
|
||
// 验证日志配置
|
||
EXPECT_EQ(config.log.level, "info");
|
||
EXPECT_EQ(config.log.save_path, "logs/");
|
||
}
|
||
|
||
TEST_F(YamlConfigTest, InvalidConfig) {
|
||
auto parser = createConfigParser();
|
||
|
||
// 测试不存在的文件
|
||
EXPECT_FALSE(parser->parse("non_existent.yaml"));
|
||
|
||
// 创建无效的配置文件
|
||
std::ofstream invalid_config("invalid_config.yaml");
|
||
invalid_config << R"(
|
||
input:
|
||
sources: [] # 空的输入源列表,这应该是无效的
|
||
)";
|
||
invalid_config.close();
|
||
|
||
// 测试无效的配置
|
||
EXPECT_FALSE(parser->parse("invalid_config.yaml"));
|
||
}
|
||
|
||
TEST_F(YamlConfigTest, InvalidConfigs) {
|
||
auto parser = createConfigParser();
|
||
|
||
// 测试各种无效配置场景
|
||
std::vector<std::pair<std::string, std::string>> invalid_configs = {
|
||
// 空输入源
|
||
{R"(
|
||
input:
|
||
sources: []
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
)", "空输入源"},
|
||
|
||
// 无效的输入源配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: "" # 空名称
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
)", "无效的输入源名称"},
|
||
|
||
// 无效的推理配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: "" # 空引擎路径
|
||
input_shape: [3, 640, 640]
|
||
)", "无效的引擎路径"},
|
||
|
||
// 无效的渲染配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
render:
|
||
window:
|
||
name: "Detection Results"
|
||
width: -1 # 无效的窗口尺寸
|
||
height: 720
|
||
)", "无效的窗口尺寸"},
|
||
|
||
// 无效的样式配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
render:
|
||
window:
|
||
name: "Detection Results"
|
||
width: 1280
|
||
height: 720
|
||
default_style:
|
||
transparency: 2.0 # 无效的透明度
|
||
)", "无效的样式透明度"},
|
||
|
||
// 无效的输出配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
output:
|
||
targets:
|
||
- type: video
|
||
name: output1
|
||
path: "" # 空输出路径
|
||
fps: 30
|
||
)", "无效的输出路径"},
|
||
|
||
// 无效的日志配置
|
||
{R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
log:
|
||
level: "" # 空日志级别
|
||
save_path: logs/
|
||
)", "无效的日志级别"}
|
||
};
|
||
|
||
for (const auto& [config_str, desc] : invalid_configs) {
|
||
std::ofstream config_file("invalid_config.yaml");
|
||
config_file << config_str;
|
||
config_file.close();
|
||
|
||
EXPECT_FALSE(parser->parse("invalid_config.yaml")) << "应该检测到无效配置: " << desc;
|
||
std::filesystem::remove("invalid_config.yaml");
|
||
}
|
||
}
|
||
|
||
TEST_F(YamlConfigTest, ValidateThresholds) {
|
||
auto parser = createConfigParser();
|
||
|
||
// 测试阈值验证
|
||
std::string config_str = R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
threshold:
|
||
conf: 1.5 # 无效的置信度阈值
|
||
nms: 0.45
|
||
)";
|
||
|
||
std::ofstream config_file("invalid_threshold.yaml");
|
||
config_file << config_str;
|
||
config_file.close();
|
||
|
||
EXPECT_FALSE(parser->parse("invalid_threshold.yaml"));
|
||
std::filesystem::remove("invalid_threshold.yaml");
|
||
}
|
||
|
||
TEST_F(YamlConfigTest, ValidateClassStyles) {
|
||
auto parser = createConfigParser();
|
||
|
||
// 测试类别样式验证
|
||
std::string config_str = R"(
|
||
input:
|
||
sources:
|
||
- type: rtsp
|
||
name: camera1
|
||
url: rtsp://example.com/stream1
|
||
inference:
|
||
model:
|
||
engine_path: /path/to/model.engine
|
||
input_shape: [3, 640, 640]
|
||
render:
|
||
window:
|
||
name: "Detection Results"
|
||
width: 1280
|
||
height: 720
|
||
class_styles:
|
||
"": # 空类别名称
|
||
box_color: [255, 0, 0]
|
||
text_color: [255, 255, 255]
|
||
transparency: 0.2
|
||
)";
|
||
|
||
std::ofstream config_file("invalid_class_style.yaml");
|
||
config_file << config_str;
|
||
config_file.close();
|
||
|
||
EXPECT_FALSE(parser->parse("invalid_class_style.yaml"));
|
||
std::filesystem::remove("invalid_class_style.yaml");
|
||
}
|