diff --git a/hz/src/hz_pkg/CMakeLists.txt b/hz/src/hz_pkg/CMakeLists.txt new file mode 100644 index 0000000..9bd3fb5 --- /dev/null +++ b/hz/src/hz_pkg/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.5) +project(hz_pkg) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# 查找依赖 +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +# 信号发生器节点 +add_executable(signal_generator src/signal_generator.cpp) +ament_target_dependencies(signal_generator rclcpp std_msgs) + +# 信号处理器节点 +add_executable(signal_processor src/signal_processor.cpp) +ament_target_dependencies(signal_processor rclcpp std_msgs) + +# 安装可执行文件 +install(TARGETS + signal_generator + signal_processor + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + \ No newline at end of file diff --git a/hz/src/hz_pkg/package.xml b/hz/src/hz_pkg/package.xml new file mode 100644 index 0000000..f571fcf --- /dev/null +++ b/hz/src/hz_pkg/package.xml @@ -0,0 +1,21 @@ + + + + hz_pkg + 0.0.0 + ROS 2 信号发生器和处理器示例 + Your Name + Apache-2.0 + + ament_cmake + rclcpp + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + + \ No newline at end of file diff --git a/hz/src/hz_pkg/src/signal_generator.cpp b/hz/src/hz_pkg/src/signal_generator.cpp new file mode 100644 index 0000000..78de676 --- /dev/null +++ b/hz/src/hz_pkg/src/signal_generator.cpp @@ -0,0 +1,64 @@ +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/float64.hpp" +#include + +using namespace std::chrono_literals; + +class SignalGenerator : public rclcpp::Node +{ +public: + SignalGenerator() : Node("signal_generator") + { + // 创建发布者,分别发布正弦波和方波 + sine_publisher_ = this->create_publisher("sine_wave", 10); + square_publisher_ = this->create_publisher("square_wave", 10); + + // 以1000Hz的频率发布信号 + timer_ = this->create_wall_timer( + 5ms, std::bind(&SignalGenerator::timer_callback, this)); + + // 初始化时间 + start_time_ = this->now(); + } + +private: + void timer_callback() + { + double t = (this->now() - start_time_).seconds(); + + double sine_value = std::sin(2 * M_PI * 10 * t); + + // 生成1Hz方波: + double square_value = (std::sin(2 * M_PI * 1 * t) >= 0) ? 1.0 : -1.0; + + // 发布正弦波 + auto sine_msg = std_msgs::msg::Float64(); + sine_msg.data = sine_value; + sine_publisher_->publish(sine_msg); + + // 发布方波 + auto square_msg = std_msgs::msg::Float64(); + square_msg.data = square_value; + square_publisher_->publish(square_msg); + + if (count_ % 100 == 0) { + RCLCPP_INFO(this->get_logger(), "Sine: %.2f, Square: %.2f", sine_value, square_value); + } + count_++; + } + + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr sine_publisher_; + rclcpp::Publisher::SharedPtr square_publisher_; + rclcpp::Time start_time_; + size_t count_ = 0; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} + \ No newline at end of file diff --git a/hz/src/hz_pkg/src/signal_processor.cpp b/hz/src/hz_pkg/src/signal_processor.cpp new file mode 100644 index 0000000..53cdb5d --- /dev/null +++ b/hz/src/hz_pkg/src/signal_processor.cpp @@ -0,0 +1,96 @@ +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/float64.hpp" +#include + +class SignalProcessor : public rclcpp::Node +{ +public: + SignalProcessor() : Node("signal_processor") + { + // 订阅正弦波信号 + sine_subscription_ = this->create_subscription( + "sine_wave", 10, + std::bind(&SignalProcessor::sine_callback, this, std::placeholders::_1)); + + // 订阅方波信号 + square_subscription_ = this->create_subscription( + "square_wave", 10, + std::bind(&SignalProcessor::square_callback, this, std::placeholders::_1)); + + // 创建处理后信号的发布者 + processed_publisher_ = this->create_publisher("processed_signal", 10); + + latest_sine_ = 0.0; + latest_square_ = 0.0; + has_sine_ = false; + has_square_ = false; + } + +private: + // 处理正弦波回调 + void sine_callback(const std_msgs::msg::Float64::SharedPtr msg) + { + std::lock_guard lock(data_mutex_); + latest_sine_ = msg->data; + has_sine_ = true; + process_signals(); + } + + // 处理方波回调 + void square_callback(const std_msgs::msg::Float64::SharedPtr msg) + { + std::lock_guard lock(data_mutex_); + latest_square_ = msg->data; + has_square_ = true; + process_signals(); + } + + void process_signals() + { + // 确保信号收到 + if (!has_sine_ || !has_square_) { + return; + } + + double processed_value; + if ((latest_sine_ >= 0 && latest_square_ >= 0) || + (latest_sine_ < 0 && latest_square_ < 0)) { + processed_value = latest_sine_; + } else { + processed_value = 0.0; + } + + // 发布处理后的信号 + auto msg = std_msgs::msg::Float64(); + msg.data = processed_value; + processed_publisher_->publish(msg); + + // 每100次处理打印一次状态 + if (count_ % 100 == 0) { + RCLCPP_INFO(this->get_logger(), "Sine: %.2f, Square: %.2f, Processed: %.2f", + latest_sine_, latest_square_, processed_value); + } + count_++; + } + + rclcpp::Subscription::SharedPtr sine_subscription_; + rclcpp::Subscription::SharedPtr square_subscription_; + rclcpp::Publisher::SharedPtr processed_publisher_; + + // 存储最新的信号值 + double latest_sine_; + double latest_square_; + bool has_sine_; + bool has_square_; + std::mutex data_mutex_; + size_t count_ = 0; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} + \ No newline at end of file diff --git a/ros2_ws/src/my_ros2_pkg/CMakeLists.txt b/ros2_ws/src/my_ros2_pkg/CMakeLists.txt new file mode 100644 index 0000000..f39baa6 --- /dev/null +++ b/ros2_ws/src/my_ros2_pkg/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.5) +project(my_ros2_pkg) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# 查找依赖 +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +# 声明发布者节点 +add_executable(publisher_node src/publisher_node.cpp) +ament_target_dependencies(publisher_node rclcpp std_msgs) + +# 声明订阅者节点 +add_executable(subscriber_node src/subscriber_node.cpp) +ament_target_dependencies(subscriber_node rclcpp std_msgs) + +# 安装可执行文件 +install(TARGETS + publisher_node + subscriber_node + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() + \ No newline at end of file diff --git a/ros2_ws/src/my_ros2_pkg/package.xml b/ros2_ws/src/my_ros2_pkg/package.xml new file mode 100644 index 0000000..876c02a --- /dev/null +++ b/ros2_ws/src/my_ros2_pkg/package.xml @@ -0,0 +1,21 @@ + + + + my_ros2_pkg + 0.0.0 + ROS 2 C++ 发布者和订阅者示例 + Your Name + Apache-2.0 + + ament_cmake + rclcpp + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + + \ No newline at end of file diff --git a/ros2_ws/src/my_ros2_pkg/src/publisher_node.cpp b/ros2_ws/src/my_ros2_pkg/src/publisher_node.cpp new file mode 100644 index 0000000..4e521df --- /dev/null +++ b/ros2_ws/src/my_ros2_pkg/src/publisher_node.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + + +class MinimalPublisher : public rclcpp::Node +{ + public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + timer_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } + + private: + void timer_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_->publish(message); + } + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/ros2_ws/src/my_ros2_pkg/src/subscriber_node.cpp b/ros2_ws/src/my_ros2_pkg/src/subscriber_node.cpp new file mode 100644 index 0000000..83345b4 --- /dev/null +++ b/ros2_ws/src/my_ros2_pkg/src/subscriber_node.cpp @@ -0,0 +1,31 @@ +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +using std::placeholders::_1; + +class MinimalSubscriber : public rclcpp::Node +{ + public: + MinimalSubscriber() + : Node("minimal_subscriber") + { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + } + + private: + void topic_callback(const std_msgs::msg::String::SharedPtr msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/work1/src/filter_pkg/CMakeLists.txt b/work1/src/filter_pkg/CMakeLists.txt new file mode 100644 index 0000000..3c6d5ea --- /dev/null +++ b/work1/src/filter_pkg/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) +project(filter_visualization) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) +find_package(sensor_msgs REQUIRED) + +add_executable(data_publisher src/data_publisher.cpp) +ament_target_dependencies(data_publisher rclcpp std_msgs sensor_msgs) + +add_executable(filter_node src/filter_node.cpp) +ament_target_dependencies(filter_node rclcpp std_msgs sensor_msgs) + +install(TARGETS + data_publisher + filter_node + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/work1/src/filter_pkg/package.xml b/work1/src/filter_pkg/package.xml new file mode 100644 index 0000000..fec4dc4 --- /dev/null +++ b/work1/src/filter_pkg/package.xml @@ -0,0 +1,24 @@ + + + + filter_visualization + 0.0.0 + ROS2 package for data filtering with Foxglove visualization + Your Name + Apache-2.0 + + ament_cmake + rclcpp + std_msgs + sensor_msgs + + ament_lint_auto + ament_lint_common + + ament_cmake_pytest + + + ament_cmake + + + \ No newline at end of file diff --git a/work1/src/filter_pkg/src/data_publisher.cpp b/work1/src/filter_pkg/src/data_publisher.cpp new file mode 100644 index 0000000..21a4f10 --- /dev/null +++ b/work1/src/filter_pkg/src/data_publisher.cpp @@ -0,0 +1,63 @@ +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/joy.hpp" +#include "std_msgs/msg/float64.hpp" +#include +#include + +class DataPublisherNode : public rclcpp::Node +{ +public: + DataPublisherNode() : Node("data_publisher") + { + // 创建发布者 + raw_data_pub_ = this->create_publisher("raw_data", 10); + + // 设置发布频率 + double publish_frequency = 100.0; + timer_ = this->create_wall_timer( + std::chrono::microseconds(static_cast(1e6 / publish_frequency)), + std::bind(&DataPublisherNode::timer_callback, this) + ); + + std::random_device rd; + gen_ = std::mt19937(rd()); + dist_ = std::normal_distribution(0.0, 0.15); + + RCLCPP_INFO(this->get_logger(), "Data publisher node initialized. Publishing to 'raw_data'"); + } + +private: + void timer_callback() + { + // 获取当前时间 + auto time = this->now().seconds(); + + // 生成基础信号:2Hz的正弦波 1Hz的余弦波 + double base_frequency1 = 2.0; + double base_frequency2 = 1.0; + double clean_data = 0.7 * sin(2 * M_PI * base_frequency1 * time) + + 0.3 * cos(2 * M_PI * base_frequency2 * time); + + // 添加随机噪声 + double noise = dist_(gen_); + double noisy_data = clean_data + noise; + + // 发布原始数据 + auto msg = std_msgs::msg::Float64(); + msg.data = noisy_data; + raw_data_pub_->publish(msg); + } + + rclcpp::Publisher::SharedPtr raw_data_pub_; + rclcpp::TimerBase::SharedPtr timer_; + std::mt19937 gen_; + std::normal_distribution dist_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/work1/src/filter_pkg/src/filter_node.cpp b/work1/src/filter_pkg/src/filter_node.cpp new file mode 100644 index 0000000..72541e7 --- /dev/null +++ b/work1/src/filter_pkg/src/filter_node.cpp @@ -0,0 +1,123 @@ +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/float64.hpp" +#include +#include + +class FilterNode : public rclcpp::Node +{ +public: + FilterNode() : Node("filter_node") + { + // 中值滤波器参数 + median_window_size_ = 7u; + + // 低通滤波器参数 + lowpass_alpha_ = 0.15; + lowpass_initialized_ = false; + lowpass_value_ = 0.0; + + // 创建订阅者 + raw_data_sub_ = this->create_subscription( + "raw_data", + 10, + std::bind(&FilterNode::data_callback, this, std::placeholders::_1) + ); + + // 创建发布者 + median_pub_ = this->create_publisher("median_filtered", 10); + lowpass_pub_ = this->create_publisher("lowpass_filtered", 10); + + RCLCPP_INFO(this->get_logger(), "Filter node initialized"); + RCLCPP_INFO(this->get_logger(), "Median filter window size: %zu", median_window_size_); + RCLCPP_INFO(this->get_logger(), "Lowpass filter alpha: %f", lowpass_alpha_); + } + +private: + void data_callback(const std_msgs::msg::Float64::SharedPtr msg) + { + double raw_data = msg->data; + + // 中值滤波处理 + process_median_filter(raw_data); + + // 低通滤波处理 + process_lowpass_filter(raw_data); + } + + void process_median_filter(double data) + { + // 将新数据添加到窗口 + median_window_.push_back(data); + + if (median_window_.size() > median_window_size_) + { + median_window_.pop_front(); + } + + // 窗口填满后才进行滤波 + if (median_window_.size() == median_window_size_) + { + std::deque sorted_data = median_window_; + std::sort(sorted_data.begin(), sorted_data.end()); + + // 计算中值 + double median; + if (median_window_size_ % 2 == 1) + { + median = sorted_data[median_window_size_ / 2]; + } + else + { + median = (sorted_data[median_window_size_ / 2 - 1] + + sorted_data[median_window_size_ / 2]) / 2.0; + } + + // 发布中值滤波结果 + auto msg = std_msgs::msg::Float64(); + msg.data = median; + median_pub_->publish(msg); + } + } + + void process_lowpass_filter(double data) + { + // 初始化 + if (!lowpass_initialized_) + { + lowpass_value_ = data; + lowpass_initialized_ = true; + return; + } + + lowpass_value_ = lowpass_alpha_ * data + (1 - lowpass_alpha_) * lowpass_value_; + + // 发布低通滤波结果 + auto msg = std_msgs::msg::Float64(); + msg.data = lowpass_value_; + lowpass_pub_->publish(msg); + } + + // 订阅者 + rclcpp::Subscription::SharedPtr raw_data_sub_; + + // 发布者 + rclcpp::Publisher::SharedPtr median_pub_; + rclcpp::Publisher::SharedPtr lowpass_pub_; + + // 中值滤波器变量 + std::deque median_window_; + size_t median_window_size_; + + // 低通滤波器变量 + double lowpass_alpha_; + double lowpass_value_; + bool lowpass_initialized_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/work2/src/armor_detection/CMakeLists.txt b/work2/src/armor_detection/CMakeLists.txt new file mode 100644 index 0000000..561bb1f --- /dev/null +++ b/work2/src/armor_detection/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.8) +project(armor_detection) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# 查找依赖 +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(OpenCV REQUIRED) +find_package(ament_index_cpp REQUIRED) + +# 添加可执行文件 +add_executable(armor_detector src/armor_detector.cpp) + +# 链接依赖库 +ament_target_dependencies(armor_detector + rclcpp + OpenCV + ament_index_cpp +) +target_link_libraries(armor_detector ${OpenCV_LIBS}) + +# 安装可执行文件 +install(TARGETS + armor_detector + DESTINATION lib/${PROJECT_NAME} +) + +# 安装图片资源 +install(DIRECTORY + images + DESTINATION share/${PROJECT_NAME} +) + +ament_package() \ No newline at end of file diff --git a/work2/src/armor_detection/images/work2.png b/work2/src/armor_detection/images/work2.png new file mode 100644 index 0000000..2d1da41 Binary files /dev/null and b/work2/src/armor_detection/images/work2.png differ diff --git a/work2/src/armor_detection/package.xml b/work2/src/armor_detection/package.xml new file mode 100644 index 0000000..62bbbcc --- /dev/null +++ b/work2/src/armor_detection/package.xml @@ -0,0 +1,21 @@ + + + + armor_detection + 0.0.0 + Blue armor detection with ROS 2 and OpenCV + your_name + Apache-2.0 + + ament_cmake + rclcpp + opencv2 + ament_index_cpp + + ament_cmake + ament_cmake + + + ament_cmake + + \ No newline at end of file diff --git a/work2/src/armor_detection/src/armor_detector.cpp b/work2/src/armor_detection/src/armor_detector.cpp new file mode 100644 index 0000000..8b472a3 --- /dev/null +++ b/work2/src/armor_detection/src/armor_detector.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include + +using namespace cv; +using namespace std; + +class ArmorDetector : public rclcpp::Node { +public: + ArmorDetector() : Node("armor_detector") { + string package_path; + try { + package_path = ament_index_cpp::get_package_share_directory("armor_detection"); + } catch (...) { + RCLCPP_ERROR(this->get_logger(), "找不到包 'armor_detection'!"); + return; + } + + string image_path = package_path + "/images/work2.png"; + RCLCPP_INFO(this->get_logger(), "图片路径: %s", image_path.c_str()); + detectArmor(image_path); + } + +private: + struct LightBar { + Rect rect; + Point center; + double area; + float ratio; + int y_center; + }; + + void stepShow(const string& title, const Mat& img, int delay = 1000) { + namedWindow(title, WINDOW_AUTOSIZE); + imshow(title, img); + RCLCPP_INFO(this->get_logger(), "显示窗口: %s(%dms后关闭)", title.c_str(), delay); + waitKey(delay); + destroyWindow(title); + } + + // 1. 读取图片 + void detectArmor(const string& image_path) { + Mat img = imread(image_path, IMREAD_COLOR); + if (img.empty()) { + RCLCPP_ERROR(this->get_logger(), "图片读取失败!请检查路径"); + Mat err = Mat::zeros(480, 640, CV_8UC3); + err.setTo(Scalar(0,0,255)); + putText(err, "Image Not Found", Point(50,240), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255,255,255), 2); + stepShow("错误", err, 3000); + return; + } + stepShow("1. 原始图像", img); + + // 2. 提取蓝色 + Mat hsv, mask; + cvtColor(img, hsv, COLOR_BGR2HSV); + Scalar lower_blue = Scalar(80, 30, 30); + Scalar upper_blue = Scalar(160, 255, 255); + inRange(hsv, lower_blue, upper_blue, mask); + stepShow("2. 蓝色区域(放宽阈值)", mask); + + // 3. 轻度去噪 + Mat kernel = getStructuringElement(MORPH_RECT, Size(2,2)); + morphologyEx(mask, mask, MORPH_CLOSE, kernel, Point(-1,-1), 1); + stepShow("3. 轻度去噪后", mask); + + // 4. 识别灯条 + vector> contours; + findContours(mask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); + + vector light_bars; + // 5. 框选所有非倒影的装甲板 + Mat light_img = img.clone(); + for (const auto& cnt : contours) { + double area = contourArea(cnt); + if (area < 20) { + RCLCPP_DEBUG(this->get_logger(), "过滤小轮廓(面积=%.1f)", area); + continue; + } + + Rect rect = boundingRect(cnt); + float ratio = (float)rect.width / rect.height; + if (ratio > 0.8) { + RCLCPP_DEBUG(this->get_logger(), "过滤宽高比过大的轮廓(ratio=%.2f)", ratio); + continue; + } + + int y_center = rect.y + rect.height / 2; + LightBar lb; + lb.rect = rect; + lb.center = Point(rect.x + rect.width/2, y_center); + lb.area = area; + lb.ratio = ratio; + lb.y_center = y_center; + light_bars.push_back(lb); + + rectangle(light_img, rect, Scalar(0,0,255), 2); + putText(light_img, to_string(light_bars.size()), lb.center, + FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255,255,255), 1); + } + + RCLCPP_INFO(this->get_logger(), "识别到灯条数量: %zu", light_bars.size()); + stepShow("4. 识别到的灯条", light_img, 2000); + + Mat final_img = light_img.clone(); + vector valid_armors; // 存储有效装甲板,避免重复框选 + + int left_area_end = img.cols * 2 / 3; // 左区域 + int right_area_start = img.cols * 1 / 3; // 右区域 + + // 配对左装甲板 + for (size_t i = 0; i < light_bars.size(); ++i) { + for (size_t j = i + 1; j < light_bars.size(); ++j) { + LightBar& l = light_bars[i]; + LightBar& r = light_bars[j]; + if (l.center.x > r.center.x) swap(l, r); + + // 左装甲板的灯条都在左2/3区域 + if (r.center.x > left_area_end) continue; + // 过滤倒影 + if (l.y_center > img.rows * 0.65 || r.y_center > img.rows * 0.65) continue; + // 间距合理 + double dist = r.center.x - l.center.x; + double avg_h = (l.rect.height + r.rect.height) / 2; + if (dist < avg_h * 1.5 || dist > avg_h * 4) continue; + // 高度/垂直偏差正常 + if (abs(l.rect.height - r.rect.height) > avg_h * 0.4) continue; + if (abs(l.center.y - r.center.y) > avg_h * 0.3) continue; + + // 左装甲板有效,加入列表 + Rect armor( + min(l.rect.x, r.rect.x), min(l.rect.y, r.rect.y), + max(l.rect.x + l.rect.width, r.rect.x + r.rect.width) - min(l.rect.x, r.rect.x), + max(l.rect.y + l.rect.height, r.rect.y + r.rect.height) - min(l.rect.y, r.rect.y) + ); + valid_armors.push_back(armor); + } + } + + // 配对右装甲板 + for (size_t i = 0; i < light_bars.size(); ++i) { + for (size_t j = i + 1; j < light_bars.size(); ++j) { + LightBar& l = light_bars[i]; + LightBar& r = light_bars[j]; + if (l.center.x > r.center.x) swap(l, r); + + if (l.center.x < right_area_start) continue; + if (l.y_center > img.rows * 0.65 || r.y_center > img.rows * 0.65) continue; + double dist = r.center.x - l.center.x; + double avg_h = (l.rect.height + r.rect.height) / 2; + if (dist < avg_h * 1.5 || dist > avg_h * 4) continue; + + if (abs(l.rect.height - r.rect.height) > avg_h * 0.4) continue; + if (abs(l.center.y - r.center.y) > avg_h * 0.3) continue; + + // 右装甲板有效,加入列表 + Rect armor( + min(l.rect.x, r.rect.x), min(l.rect.y, r.rect.y), + max(l.rect.x + l.rect.width, r.rect.x + r.rect.width) - min(l.rect.x, r.rect.x), + max(l.rect.y + l.rect.height, r.rect.y + r.rect.height) - min(l.rect.y, r.rect.y) + ); + valid_armors.push_back(armor); + } + } + + // 绘制有效装甲板 + for (size_t k = 0; k < valid_armors.size(); ++k) { + bool is_dup = false; + for (size_t m = 0; m < k; ++m) { + Rect overlap = valid_armors[k] & valid_armors[m]; + double overlap_ratio = (double)overlap.area() / valid_armors[k].area(); + if (overlap_ratio > 0.5) { + is_dup = true; + break; + } + } + if (!is_dup) { + rectangle(final_img, valid_armors[k], Scalar(0,255,0), 2); + putText(final_img, "Armor " + to_string(k+1), Point(valid_armors[k].x, valid_armors[k].y-10), + FONT_HERSHEY_SIMPLEX, 0.6, Scalar(0,255,0), 2); + } + } + + // 提示信息 + if (valid_armors.empty() && !light_bars.empty()) { + putText(final_img, "No Valid Armor (Filtered Reflection)", Point(50,50), + FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0,0,255), 2); + } else if (light_bars.empty()) { + putText(final_img, "No Enough Light Bars", Point(50,50), + FONT_HERSHEY_SIMPLEX, 0.8, Scalar(0,0,255), 2); + } + + // 显示最终结果 + namedWindow("5. 最终结果", WINDOW_NORMAL); + resizeWindow("5. 最终结果", 1024, 768); + imshow("5. 最终结果", final_img); + waitKey(0); + destroyAllWindows(); + } +}; + +int main(int argc, char**argv) { + rclcpp::init(argc, argv); + auto node = make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file