From 193eb64a10f65b3b8b7328b6a0325f12d734d1c7 Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Mon, 18 May 2026 23:44:46 +0800 Subject: [PATCH 1/6] feat: green light detection and armor filter --- config/config.yaml | 18 +- src/kernel/identifier.cpp | 56 ++++++- src/kernel/identifier.hpp | 4 + .../identifier/green_light_armor_filter.cpp | 100 +++++++++++ .../identifier/green_light_armor_filter.hpp | 27 +++ .../identifier/green_light_detection.cpp | 157 ++++++++++++++++++ .../identifier/green_light_detection.hpp | 23 +++ src/module/tracker/armor_filter.cpp | 2 +- src/runtime.cpp | 5 + src/utility/image/green_light.cpp | 20 +++ src/utility/image/green_light.hpp | 11 ++ 11 files changed, 412 insertions(+), 11 deletions(-) create mode 100644 src/module/identifier/green_light_armor_filter.cpp create mode 100644 src/module/identifier/green_light_armor_filter.hpp create mode 100644 src/module/identifier/green_light_detection.cpp create mode 100644 src/module/identifier/green_light_detection.hpp create mode 100644 src/utility/image/green_light.cpp create mode 100644 src/utility/image/green_light.hpp diff --git a/config/config.yaml b/config/config.yaml index b64f63a..affb448 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -51,9 +51,15 @@ identifier: score_threshold: 0.7 nms_threshold: 0.3 + outpost_green_light_filter: + green_threshold: 120 + min_area: 20 + min_circularity: 0.6 + max_aspect_ratio: 1.5 + tracker: # enemy_color: red - enemy_color: blue + enemy_color: red pose_estimator: yaw_optimizer: true @@ -73,11 +79,11 @@ pose_estimator: [-0.064232403853946, -0.087667493884102, 0, 0, 0.792381808294582] fire_control: - initial_bullet_speed: 21.4 # m/s - shoot_delay: 0.07 # s + initial_bullet_speed: 22.7 # m/s + shoot_delay: 0.09 # s mpc_enable: true - yaw_offset: 0.0 # degree - pitch_offset: 0.0 # degree + yaw_offset: 0.5 # degree + pitch_offset: 2.0 # degree aim_point_chooser: coming_angle: 70.0 # degree @@ -104,7 +110,7 @@ fire_control: visualization: framerate: 60 # e.g. monitor_host: "192.168.3.125" - monitor_host: "127.0.0.1" + monitor_host: "169.254.233.1" monitor_port: "5000" stream_type: "RTP_JEPG" diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index 0770c18..8b7eb03 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -1,26 +1,70 @@ #include "identifier.hpp" #include "module/identifier/armor_detection.hpp" +#include "module/identifier/green_light_armor_filter.hpp" #include "utility/robot/armor.hpp" +#include +#include +#include +#include + using namespace rmcs; using namespace rmcs::kernel; using namespace rmcs::identifier; struct Identifier::Impl { ArmorDetection armor_detection; + GreenLightArmorFilter green_light_armor_filter; auto initialize(const YAML::Node& yaml) noexcept -> std::expected { { auto result = armor_detection.initialize(yaml); - if (!result.has_value()) { - return std::unexpected { result.error() }; - } + if (!result.has_value()) return std::unexpected { result.error() }; + } + { + auto result = green_light_armor_filter.initialize(yaml); + if (!result.has_value()) return std::unexpected { result.error() }; } return {}; } - auto identify(const Image& src) noexcept { return armor_detection.sync_detect(src); } + auto identify(const Image& src) noexcept -> std::optional> { + auto detected_armors = armor_detection.sync_detect(src); + if (!detected_armors.has_value()) return std::nullopt; + + const auto has_outpost = std::ranges::any_of(*detected_armors, + [](const Armor2D& armor) { return armor.genre == DeviceId::OUTPOST; }); + if (!has_outpost) return detected_armors; + + auto outpost_armors = std::vector {}; + outpost_armors.reserve(detected_armors->size()); + for (const auto& armor : *detected_armors) { + if (armor.genre == DeviceId::OUTPOST) outpost_armors.push_back(armor); + } + + const auto keep_mask = green_light_armor_filter.filter(src, outpost_armors); + + auto filtered = std::vector {}; + filtered.reserve(detected_armors->size()); + + auto keep_it = keep_mask.begin(); + for (const auto& armor : *detected_armors) { + if (armor.genre != DeviceId::OUTPOST) { + filtered.push_back(armor); + continue; + } + + if (*keep_it) filtered.push_back(armor); + ++keep_it; + } + + return filtered; + } + + auto green_light() const noexcept -> std::optional { + return green_light_armor_filter.green_light(); + } }; auto Identifier::initialize(const YAML::Node& yaml) noexcept -> std::expected { @@ -31,6 +75,10 @@ auto Identifier::sync_identify(const Image& src) noexcept -> std::optionalidentify(src); } +auto Identifier::green_light() const noexcept -> std::optional { + return pimpl->green_light(); +} + Identifier::Identifier() noexcept : pimpl { std::make_unique() } { } diff --git a/src/kernel/identifier.hpp b/src/kernel/identifier.hpp index 3f0e583..8608d91 100644 --- a/src/kernel/identifier.hpp +++ b/src/kernel/identifier.hpp @@ -5,6 +5,9 @@ #include "utility/robot/armor.hpp" #include +#include + +#include #include namespace rmcs::kernel { @@ -16,6 +19,7 @@ class Identifier { auto initialize(const YAML::Node&) noexcept -> std::expected; auto sync_identify(const Image&) noexcept -> std::optional>; + auto green_light() const noexcept -> std::optional; }; } diff --git a/src/module/identifier/green_light_armor_filter.cpp b/src/module/identifier/green_light_armor_filter.cpp new file mode 100644 index 0000000..32e1ab5 --- /dev/null +++ b/src/module/identifier/green_light_armor_filter.cpp @@ -0,0 +1,100 @@ +#include "green_light_armor_filter.hpp" + +#include "module/identifier/green_light_detection.hpp" +#include "utility/image/image.details.hpp" + +#include +#include +#include +#include + +#include + +using namespace rmcs::identifier; + +struct GreenLightArmorFilter::Impl { + GreenLightDetection green_light_detection; + + auto initialize(const YAML::Node& yaml) noexcept -> std::expected { + return green_light_detection.initialize(yaml["outpost_green_light_filter"]); + } + + auto filter(const Image& image, std::span armors) noexcept -> std::vector { + auto keep_mask = std::vector(armors.size(), true); + if (armors.empty()) return keep_mask; + + auto detect_roi = compute_detect_roi(image, armors); + if (!detect_roi.has_value()) return keep_mask; + + auto green_light_rect = green_light_detection.sync_detect(image, *detect_roi); + if (!green_light_rect.has_value()) return keep_mask; + + const auto threshold_y = green_light_rect->y + green_light_rect->height; + for (std::size_t index = 0; index < armors.size(); ++index) { + keep_mask[index] = armors[index].center.y >= 1.0 * threshold_y; + } + + return keep_mask; + } + + auto green_light() const noexcept -> std::optional { + return green_light_detection.green_light(); + } + +private: + static auto compute_detect_roi(const Image& image, std::span armors) + -> std::optional { + constexpr auto kExpandPixels = 300; + + if (armors.empty()) return std::nullopt; + + const auto& mat = image.details().mat; + if (mat.empty()) return std::nullopt; + + auto union_rect = cv::boundingRect(std::vector { + armors.front().tl, + armors.front().tr, + armors.front().br, + armors.front().bl, + }); + + for (const auto& armor : armors | std::views::drop(1)) { + const auto armor_rect = cv::boundingRect( + std::vector { armor.tl, armor.tr, armor.br, armor.bl }); + union_rect |= armor_rect; + } + + auto roi = cv::Rect2i { + union_rect.x - kExpandPixels, + union_rect.y - kExpandPixels, + union_rect.width + kExpandPixels * 2, + union_rect.height + kExpandPixels * 2, + }; + + roi &= cv::Rect2i { 0, 0, mat.cols, mat.rows }; + if ((roi.width <= 0) || (roi.height <= 0)) { + return std::nullopt; + } + + return roi; + } +}; + +auto GreenLightArmorFilter::initialize(const YAML::Node& yaml) noexcept + -> std::expected { + return pimpl->initialize(yaml); +} + +auto GreenLightArmorFilter::filter(const Image& image, std::span armors) noexcept + -> std::vector { + return pimpl->filter(image, armors); +} + +auto GreenLightArmorFilter::green_light() const noexcept -> std::optional { + return pimpl->green_light(); +} + +GreenLightArmorFilter::GreenLightArmorFilter() noexcept + : pimpl { std::make_unique() } { } + +GreenLightArmorFilter::~GreenLightArmorFilter() noexcept = default; diff --git a/src/module/identifier/green_light_armor_filter.hpp b/src/module/identifier/green_light_armor_filter.hpp new file mode 100644 index 0000000..7f665d6 --- /dev/null +++ b/src/module/identifier/green_light_armor_filter.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "utility/image/image.hpp" +#include "utility/pimpl.hpp" +#include "utility/robot/armor.hpp" + +#include +#include +#include +#include + +#include + +#include + +namespace rmcs::identifier { + +class GreenLightArmorFilter { + RMCS_PIMPL_DEFINITION(GreenLightArmorFilter) + +public: + auto initialize(const YAML::Node&) noexcept -> std::expected; + auto filter(const Image&, std::span) noexcept -> std::vector; + auto green_light() const noexcept -> std::optional; +}; + +} diff --git a/src/module/identifier/green_light_detection.cpp b/src/module/identifier/green_light_detection.cpp new file mode 100644 index 0000000..b3df8fa --- /dev/null +++ b/src/module/identifier/green_light_detection.cpp @@ -0,0 +1,157 @@ +#include "green_light_detection.hpp" + +#include "utility/image/image.details.hpp" +#include "utility/serializable.hpp" + +#include +#include +#include +#include + +#include + +using namespace rmcs::identifier; + +struct GreenLightDetection::Impl { + std::optional green_light; + + struct Config : util::Serializable { + int green_threshold; + + double min_area; + double min_circularity; + double max_aspect_ratio; + + constexpr static std::tuple metas { + &Config::green_threshold, + "green_threshold", + &Config::min_area, + "min_area", + &Config::min_circularity, + "min_circularity", + &Config::max_aspect_ratio, + "max_aspect_ratio", + }; + } config; + + auto initialize(const YAML::Node& yaml) noexcept -> std::expected { + auto result = config.serialize(yaml); + if (!result.has_value()) { + return std::unexpected { result.error() }; + } + + if ((config.green_threshold < 0) || (config.green_threshold > 255)) { + return std::unexpected { "green_threshold must satisfy 0 <= green_threshold <= 255" }; + } + + if (!(config.min_area > 0.0)) { + return std::unexpected { "min_area must be > 0" }; + } + + if ((config.min_circularity <= 0.0) || (config.min_circularity > 1.0)) { + return std::unexpected { "min_circularity must satisfy 0 < min_circularity <= 1" }; + } + + if (!(config.max_aspect_ratio >= 1.0)) { + return std::unexpected { "max_aspect_ratio must be >= 1" }; + } + + return {}; + } + + auto sync_detect(const Image& image, const cv::Rect2i& roi) noexcept + -> std::optional { + green_light.reset(); + + const auto& mat = image.details().mat; + if (mat.empty()) { + return std::nullopt; + } + + if ((roi.width <= 0) || (roi.height <= 0)) { + return std::nullopt; + } + + const auto image_rect = cv::Rect2i { 0, 0, mat.cols, mat.rows }; + const auto clipped_roi = roi & image_rect; + if ((clipped_roi.width != roi.width) || (clipped_roi.height != roi.height)) { + return std::nullopt; + } + + const auto roi_mat = mat(roi); + + auto green_channel = cv::Mat {}; + cv::extractChannel(roi_mat, green_channel, 1); + + auto mask = cv::Mat {}; + cv::threshold(green_channel, mask, static_cast(config.green_threshold), 255.0, + cv::THRESH_BINARY); + + auto kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size { 5, 5 }); + cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel); + + auto contours = std::vector> {}; + cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); + + auto best_area = 0.0; + + for (const auto& contour : contours) { + const auto area = cv::contourArea(contour); + if (area < config.min_area) { + continue; + } + + const auto perimeter = cv::arcLength(contour, true); + if (!(perimeter > 0.0)) { + continue; + } + + const auto circularity = + 4.0 * std::numbers::pi_v * area / (perimeter * perimeter); + if (circularity < config.min_circularity) { + continue; + } + + const auto rect = cv::boundingRect(contour); + if ((rect.width <= 0) || (rect.height <= 0)) { + continue; + } + + const auto aspect_ratio = static_cast(std::max(rect.width, rect.height)) + / std::min(rect.width, rect.height); + if (aspect_ratio > config.max_aspect_ratio) { + continue; + } + + if (!green_light.has_value() || (area > best_area)) { + best_area = area; + green_light = rect; + green_light->x += roi.x; + green_light->y += roi.y; + } + } + + return green_light; + } + + auto green_light_value() const noexcept -> std::optional { return green_light; } +}; + +auto GreenLightDetection::initialize(const YAML::Node& yaml) noexcept + -> std::expected { + return pimpl->initialize(yaml); +} + +auto GreenLightDetection::sync_detect(const Image& image, const cv::Rect2i& roi) noexcept + -> std::optional { + return pimpl->sync_detect(image, roi); +} + +auto GreenLightDetection::green_light() const noexcept -> std::optional { + return pimpl->green_light_value(); +} + +GreenLightDetection::GreenLightDetection() noexcept + : pimpl { std::make_unique() } { } + +GreenLightDetection::~GreenLightDetection() noexcept = default; diff --git a/src/module/identifier/green_light_detection.hpp b/src/module/identifier/green_light_detection.hpp new file mode 100644 index 0000000..cae9896 --- /dev/null +++ b/src/module/identifier/green_light_detection.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "utility/image/image.hpp" +#include "utility/pimpl.hpp" + +#include +#include + +#include +#include + +namespace rmcs::identifier { + +class GreenLightDetection { + RMCS_PIMPL_DEFINITION(GreenLightDetection) + +public: + auto initialize(const YAML::Node&) noexcept -> std::expected; + auto sync_detect(const Image&, const cv::Rect2i&) noexcept -> std::optional; + auto green_light() const noexcept -> std::optional; +}; + +} diff --git a/src/module/tracker/armor_filter.cpp b/src/module/tracker/armor_filter.cpp index a0f83fe..bf26bc5 100644 --- a/src/module/tracker/armor_filter.cpp +++ b/src/module/tracker/armor_filter.cpp @@ -10,7 +10,7 @@ struct ArmorFilter::Impl { auto filter(std::span const& armors) const -> std::vector { auto filtered = armors | std::views::filter([&](Armor2D const& armor) { - return (armor.genre != DeviceId::INFANTRY_5) + return (armor.genre != DeviceId::INFANTRY_5) && (armor.genre != DeviceId::UNKNOWN) && (armor_color2camp_color(armor.color) == enemy_color) && (!invincible_armors.contains(armor.genre)); }); diff --git a/src/runtime.cpp b/src/runtime.cpp index 9ede29f..3dd1977 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -8,6 +8,7 @@ #include "module/debug/framerate.hpp" #include "utility/image/armor.hpp" +#include "utility/image/green_light.hpp" #include "utility/logging_util.hpp" #include "utility/math/linear.hpp" #include "utility/panic.hpp" @@ -145,6 +146,10 @@ auto main() -> int { if (use_painted_image) { for (const auto& armor : *result) util::draw(*image, armor); + + if (const auto rect = identifier.green_light(); rect.has_value()) { + util::draw_green_light(*image, *rect); + } } logging.reset("detection", 5); diff --git a/src/utility/image/green_light.cpp b/src/utility/image/green_light.cpp new file mode 100644 index 0000000..ee4654a --- /dev/null +++ b/src/utility/image/green_light.cpp @@ -0,0 +1,20 @@ +#include "green_light.hpp" + +#include "utility/image/image.details.hpp" + +#include + +#include + +namespace rmcs::util { + +auto draw_green_light(Image& canvas, const cv::Rect2i& rect) noexcept -> void { + auto& opencv_mat = canvas.details().mat; + + cv::rectangle(opencv_mat, rect, cv::Scalar { 0, 255, 0 }, 2, cv::LINE_AA); + cv::putText(opencv_mat, "green_light", + cv::Point2i { rect.x, std::max(rect.y - 6, 0) }, cv::FONT_HERSHEY_SIMPLEX, 0.6, + cv::Scalar { 0, 255, 0 }, 1, cv::LINE_AA); +} + +} diff --git a/src/utility/image/green_light.hpp b/src/utility/image/green_light.hpp new file mode 100644 index 0000000..4f32daf --- /dev/null +++ b/src/utility/image/green_light.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "utility/image/image.hpp" + +#include + +namespace rmcs::util { + +auto draw_green_light(Image&, const cv::Rect2i&) noexcept -> void; + +} From eb9491020e3493928481e767376fe5a26b79dc54 Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Tue, 19 May 2026 02:28:28 +0800 Subject: [PATCH 2/6] feat: add auto aim command visualization --- config/config.yaml | 14 ++++---- src/kernel/identifier.cpp | 2 +- .../identifier/green_light_armor_filter.cpp | 2 +- src/runtime.cpp | 3 ++ src/utility/image/text.cpp | 32 +++++++++++++++++++ src/utility/image/text.hpp | 11 +++++++ 6 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 src/utility/image/text.cpp create mode 100644 src/utility/image/text.hpp diff --git a/config/config.yaml b/config/config.yaml index affb448..d20cb86 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -22,7 +22,7 @@ capturer: invert_image: false software_sync: false trigger_mode: false - fixed_framerate: true + fixed_framerate: false local_video: # 替换为你具体的路径 location: "/path/to/your/video" @@ -80,16 +80,16 @@ pose_estimator: fire_control: initial_bullet_speed: 22.7 # m/s - shoot_delay: 0.09 # s + shoot_delay: 0.07 # s mpc_enable: true - yaw_offset: 0.5 # degree - pitch_offset: 2.0 # degree + yaw_offset: 0.0 # degree + pitch_offset: 0.0 # degree aim_point_chooser: coming_angle: 70.0 # degree leaving_angle: 20.0 # degree - outpost_coming_angle: 70.0 # degree - outpost_leaving_angle: 30.0 # degree + outpost_coming_angle: 60.0 # degree + outpost_leaving_angle: 25.0 # degree mpc: mpc_max_yaw_acc: 50.0 @@ -110,7 +110,7 @@ fire_control: visualization: framerate: 60 # e.g. monitor_host: "192.168.3.125" - monitor_host: "169.254.233.1" + monitor_host: "127.0.0.1" monitor_port: "5000" stream_type: "RTP_JEPG" diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index 8b7eb03..7507f5a 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -22,7 +22,7 @@ struct Identifier::Impl { if (!result.has_value()) return std::unexpected { result.error() }; } { - auto result = green_light_armor_filter.initialize(yaml); + auto result = green_light_armor_filter.initialize(yaml["outpost_green_light_filter"]); if (!result.has_value()) return std::unexpected { result.error() }; } diff --git a/src/module/identifier/green_light_armor_filter.cpp b/src/module/identifier/green_light_armor_filter.cpp index 32e1ab5..7dcb8c0 100644 --- a/src/module/identifier/green_light_armor_filter.cpp +++ b/src/module/identifier/green_light_armor_filter.cpp @@ -16,7 +16,7 @@ struct GreenLightArmorFilter::Impl { GreenLightDetection green_light_detection; auto initialize(const YAML::Node& yaml) noexcept -> std::expected { - return green_light_detection.initialize(yaml["outpost_green_light_filter"]); + return green_light_detection.initialize(yaml); } auto filter(const Image& image, std::span armors) noexcept -> std::vector { diff --git a/src/runtime.cpp b/src/runtime.cpp index 3dd1977..db37c78 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -9,6 +9,7 @@ #include "module/debug/framerate.hpp" #include "utility/image/armor.hpp" #include "utility/image/green_light.hpp" +#include "utility/image/text.hpp" #include "utility/logging_util.hpp" #include "utility/math/linear.hpp" #include "utility/panic.hpp" @@ -211,6 +212,8 @@ auto main() -> int { } } + util::draw_text(*image, command.should_shoot ? "ATTACK" : "IDLE"); + /// 4. Transmit State /// feishu.send(command); diff --git a/src/utility/image/text.cpp b/src/utility/image/text.cpp new file mode 100644 index 0000000..a5aab2c --- /dev/null +++ b/src/utility/image/text.cpp @@ -0,0 +1,32 @@ +#include "text.hpp" + +#include "utility/image/image.details.hpp" + +#include + +#include + +namespace rmcs::util { + +auto draw_text(Image& canvas, const std::string& text) noexcept -> void { + auto& opencv_mat = canvas.details().mat; + + const auto thickness = 1; + const auto font = cv::FONT_HERSHEY_SIMPLEX; + const auto scale = 0.6; + const auto margin = 10; + const auto white = cv::Scalar { 255, 255, 255 }; + const auto black = cv::Scalar { 0, 0, 0 }; + + auto baseline = 0; + const auto size = cv::getTextSize(text, font, scale, thickness, &baseline); + const auto x = std::max(canvas.details().get_cols() - size.width - margin, 0); + const auto y = std::max(canvas.details().get_rows() - baseline - margin, size.height); + const auto org = cv::Point2i { x, y }; + + cv::putText(opencv_mat, text, org + cv::Point2i { 1, 1 }, font, scale, black, thickness + 2, + cv::LINE_AA); + cv::putText(opencv_mat, text, org, font, scale, white, thickness, cv::LINE_AA); +} + +} diff --git a/src/utility/image/text.hpp b/src/utility/image/text.hpp new file mode 100644 index 0000000..0b1f697 --- /dev/null +++ b/src/utility/image/text.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "utility/image/image.hpp" + +#include + +namespace rmcs::util { + +auto draw_text(Image&, const std::string& text) noexcept -> void; + +} From eaf4a73625996ec3443fac333421e3f9c93e91c0 Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Tue, 19 May 2026 17:42:42 +0800 Subject: [PATCH 3/6] refactor: restructure green light detection to output results via function return value --- config/config.yaml | 2 +- src/kernel/identifier.cpp | 41 +++++++------------ src/kernel/identifier.hpp | 8 +++- .../identifier/green_light_armor_filter.cpp | 32 +++++++-------- .../identifier/green_light_armor_filter.hpp | 8 +++- .../identifier/green_light_detection.cpp | 12 +----- .../identifier/green_light_detection.hpp | 1 - src/runtime.cpp | 9 ++-- 8 files changed, 47 insertions(+), 66 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index d20cb86..4457e6e 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -59,7 +59,7 @@ identifier: tracker: # enemy_color: red - enemy_color: red + enemy_color: blue pose_estimator: yaw_optimizer: true diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index 7507f5a..24425db 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -3,7 +3,6 @@ #include "module/identifier/green_light_armor_filter.hpp" #include "utility/robot/armor.hpp" -#include #include #include #include @@ -17,38 +16,31 @@ struct Identifier::Impl { GreenLightArmorFilter green_light_armor_filter; auto initialize(const YAML::Node& yaml) noexcept -> std::expected { - { - auto result = armor_detection.initialize(yaml); - if (!result.has_value()) return std::unexpected { result.error() }; - } - { - auto result = green_light_armor_filter.initialize(yaml["outpost_green_light_filter"]); - if (!result.has_value()) return std::unexpected { result.error() }; - } + auto armor_result = armor_detection.initialize(yaml); + if (!armor_result.has_value()) return std::unexpected { armor_result.error() }; + auto filter_result = green_light_armor_filter.initialize(yaml["outpost_green_light_" + "filter"]); + if (!filter_result.has_value()) return std::unexpected { filter_result.error() }; return {}; } - auto identify(const Image& src) noexcept -> std::optional> { + auto identify(const Image& src) noexcept -> std::optional { auto detected_armors = armor_detection.sync_detect(src); if (!detected_armors.has_value()) return std::nullopt; - const auto has_outpost = std::ranges::any_of(*detected_armors, - [](const Armor2D& armor) { return armor.genre == DeviceId::OUTPOST; }); - if (!has_outpost) return detected_armors; - auto outpost_armors = std::vector {}; outpost_armors.reserve(detected_armors->size()); for (const auto& armor : *detected_armors) { if (armor.genre == DeviceId::OUTPOST) outpost_armors.push_back(armor); } - const auto keep_mask = green_light_armor_filter.filter(src, outpost_armors); + const auto filter_result = green_light_armor_filter.filter(src, outpost_armors); - auto filtered = std::vector {}; + auto filtered = Armor2Ds {}; filtered.reserve(detected_armors->size()); - auto keep_it = keep_mask.begin(); + auto keep_it = filter_result.keep_mask.begin(); for (const auto& armor : *detected_armors) { if (armor.genre != DeviceId::OUTPOST) { filtered.push_back(armor); @@ -59,11 +51,10 @@ struct Identifier::Impl { ++keep_it; } - return filtered; - } - - auto green_light() const noexcept -> std::optional { - return green_light_armor_filter.green_light(); + return Identifier::Result { + .armors = std::move(filtered), + .green_light = filter_result.green_light, + }; } }; @@ -71,14 +62,10 @@ auto Identifier::initialize(const YAML::Node& yaml) noexcept -> std::expectedinitialize(yaml); } -auto Identifier::sync_identify(const Image& src) noexcept -> std::optional> { +auto Identifier::sync_identify(const Image& src) noexcept -> std::optional { return pimpl->identify(src); } -auto Identifier::green_light() const noexcept -> std::optional { - return pimpl->green_light(); -} - Identifier::Identifier() noexcept : pimpl { std::make_unique() } { } diff --git a/src/kernel/identifier.hpp b/src/kernel/identifier.hpp index 8608d91..cea070c 100644 --- a/src/kernel/identifier.hpp +++ b/src/kernel/identifier.hpp @@ -16,10 +16,14 @@ class Identifier { RMCS_PIMPL_DEFINITION(Identifier) public: + struct Result { + Armor2Ds armors; + std::optional green_light; + }; + auto initialize(const YAML::Node&) noexcept -> std::expected; - auto sync_identify(const Image&) noexcept -> std::optional>; - auto green_light() const noexcept -> std::optional; + auto sync_identify(const Image&) noexcept -> std::optional; }; } diff --git a/src/module/identifier/green_light_armor_filter.cpp b/src/module/identifier/green_light_armor_filter.cpp index 7dcb8c0..54f3a73 100644 --- a/src/module/identifier/green_light_armor_filter.cpp +++ b/src/module/identifier/green_light_armor_filter.cpp @@ -19,26 +19,26 @@ struct GreenLightArmorFilter::Impl { return green_light_detection.initialize(yaml); } - auto filter(const Image& image, std::span armors) noexcept -> std::vector { - auto keep_mask = std::vector(armors.size(), true); - if (armors.empty()) return keep_mask; + auto filter(const Image& image, std::span armors) noexcept + -> GreenLightArmorFilter::Result { + auto result = GreenLightArmorFilter::Result { + .keep_mask = std::vector(armors.size(), true), + .green_light = std::nullopt, + }; + if (armors.empty()) return result; auto detect_roi = compute_detect_roi(image, armors); - if (!detect_roi.has_value()) return keep_mask; + if (!detect_roi.has_value()) return result; - auto green_light_rect = green_light_detection.sync_detect(image, *detect_roi); - if (!green_light_rect.has_value()) return keep_mask; + result.green_light = green_light_detection.sync_detect(image, *detect_roi); + if (!result.green_light.has_value()) return result; - const auto threshold_y = green_light_rect->y + green_light_rect->height; + const auto threshold_y = result.green_light->y + result.green_light->height; for (std::size_t index = 0; index < armors.size(); ++index) { - keep_mask[index] = armors[index].center.y >= 1.0 * threshold_y; + result.keep_mask[index] = armors[index].center.y >= 1.0 * threshold_y; } - return keep_mask; - } - - auto green_light() const noexcept -> std::optional { - return green_light_detection.green_light(); + return result; } private: @@ -86,14 +86,10 @@ auto GreenLightArmorFilter::initialize(const YAML::Node& yaml) noexcept } auto GreenLightArmorFilter::filter(const Image& image, std::span armors) noexcept - -> std::vector { + -> Result { return pimpl->filter(image, armors); } -auto GreenLightArmorFilter::green_light() const noexcept -> std::optional { - return pimpl->green_light(); -} - GreenLightArmorFilter::GreenLightArmorFilter() noexcept : pimpl { std::make_unique() } { } diff --git a/src/module/identifier/green_light_armor_filter.hpp b/src/module/identifier/green_light_armor_filter.hpp index 7f665d6..0486dec 100644 --- a/src/module/identifier/green_light_armor_filter.hpp +++ b/src/module/identifier/green_light_armor_filter.hpp @@ -19,9 +19,13 @@ class GreenLightArmorFilter { RMCS_PIMPL_DEFINITION(GreenLightArmorFilter) public: + struct Result { + std::vector keep_mask; + std::optional green_light; + }; + auto initialize(const YAML::Node&) noexcept -> std::expected; - auto filter(const Image&, std::span) noexcept -> std::vector; - auto green_light() const noexcept -> std::optional; + auto filter(const Image&, std::span) noexcept -> Result; }; } diff --git a/src/module/identifier/green_light_detection.cpp b/src/module/identifier/green_light_detection.cpp index b3df8fa..d17b0e1 100644 --- a/src/module/identifier/green_light_detection.cpp +++ b/src/module/identifier/green_light_detection.cpp @@ -13,8 +13,6 @@ using namespace rmcs::identifier; struct GreenLightDetection::Impl { - std::optional green_light; - struct Config : util::Serializable { int green_threshold; @@ -59,9 +57,9 @@ struct GreenLightDetection::Impl { return {}; } - auto sync_detect(const Image& image, const cv::Rect2i& roi) noexcept + auto sync_detect(const Image& image, const cv::Rect2i& roi) const noexcept -> std::optional { - green_light.reset(); + auto green_light = std::optional {}; const auto& mat = image.details().mat; if (mat.empty()) { @@ -133,8 +131,6 @@ struct GreenLightDetection::Impl { return green_light; } - - auto green_light_value() const noexcept -> std::optional { return green_light; } }; auto GreenLightDetection::initialize(const YAML::Node& yaml) noexcept @@ -147,10 +143,6 @@ auto GreenLightDetection::sync_detect(const Image& image, const cv::Rect2i& roi) return pimpl->sync_detect(image, roi); } -auto GreenLightDetection::green_light() const noexcept -> std::optional { - return pimpl->green_light_value(); -} - GreenLightDetection::GreenLightDetection() noexcept : pimpl { std::make_unique() } { } diff --git a/src/module/identifier/green_light_detection.hpp b/src/module/identifier/green_light_detection.hpp index cae9896..a08d744 100644 --- a/src/module/identifier/green_light_detection.hpp +++ b/src/module/identifier/green_light_detection.hpp @@ -17,7 +17,6 @@ class GreenLightDetection { public: auto initialize(const YAML::Node&) noexcept -> std::expected; auto sync_detect(const Image&, const cv::Rect2i&) noexcept -> std::optional; - auto green_light() const noexcept -> std::optional; }; } diff --git a/src/runtime.cpp b/src/runtime.cpp index db37c78..0484c35 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -145,12 +145,11 @@ auto main() -> int { continue; // 一般不会推理出错喵~ } if (use_painted_image) { - for (const auto& armor : *result) + for (const auto& armor : result->armors) util::draw(*image, armor); - if (const auto rect = identifier.green_light(); rect.has_value()) { - util::draw_green_light(*image, *rect); - } + if (result->green_light.has_value()) + util::draw_green_light(*image, *result->green_light); } logging.reset("detection", 5); @@ -163,7 +162,7 @@ auto main() -> int { tracker.set_enemy_color(CampColor::RED); tracker.set_invincible_armors(context.invincible_devices); - armors_2d = tracker.filter_armors(*result); + armors_2d = tracker.filter_armors(result->armors); if (armors_2d.empty()) continue; } From e1f197be202c364acde5679d1e0b5cb0dbfdeda5 Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Thu, 21 May 2026 00:06:29 +0800 Subject: [PATCH 4/6] feat: filter out all armor plates overlapping with green light detections --- config/config.yaml | 2 +- src/kernel/identifier.cpp | 29 +++++++++---------- ...mor_filter.cpp => green_light_locator.cpp} | 29 +++++++------------ ...mor_filter.hpp => green_light_locator.hpp} | 8 ++--- 4 files changed, 28 insertions(+), 40 deletions(-) rename src/module/identifier/{green_light_armor_filter.cpp => green_light_locator.cpp} (68%) rename src/module/identifier/{green_light_armor_filter.hpp => green_light_locator.hpp} (69%) diff --git a/config/config.yaml b/config/config.yaml index 4457e6e..2dd10ae 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -79,7 +79,7 @@ pose_estimator: [-0.064232403853946, -0.087667493884102, 0, 0, 0.792381808294582] fire_control: - initial_bullet_speed: 22.7 # m/s + initial_bullet_speed: 23.2 # m/s shoot_delay: 0.07 # s mpc_enable: true yaw_offset: 0.0 # degree diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index 24425db..91406c3 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -1,6 +1,6 @@ #include "identifier.hpp" #include "module/identifier/armor_detection.hpp" -#include "module/identifier/green_light_armor_filter.hpp" +#include "module/identifier/green_light_locator.hpp" #include "utility/robot/armor.hpp" #include @@ -13,14 +13,14 @@ using namespace rmcs::identifier; struct Identifier::Impl { ArmorDetection armor_detection; - GreenLightArmorFilter green_light_armor_filter; + GreenLightLocator green_light_locator; auto initialize(const YAML::Node& yaml) noexcept -> std::expected { auto armor_result = armor_detection.initialize(yaml); if (!armor_result.has_value()) return std::unexpected { armor_result.error() }; - auto filter_result = green_light_armor_filter.initialize(yaml["outpost_green_light_" - "filter"]); - if (!filter_result.has_value()) return std::unexpected { filter_result.error() }; + auto locator_result = green_light_locator.initialize(yaml["outpost_green_light_" + "filter"]); + if (!locator_result.has_value()) return std::unexpected { locator_result.error() }; return {}; } @@ -35,25 +35,24 @@ struct Identifier::Impl { if (armor.genre == DeviceId::OUTPOST) outpost_armors.push_back(armor); } - const auto filter_result = green_light_armor_filter.filter(src, outpost_armors); + const auto locator_result = green_light_locator.locate(src, outpost_armors); auto filtered = Armor2Ds {}; filtered.reserve(detected_armors->size()); - auto keep_it = filter_result.keep_mask.begin(); - for (const auto& armor : *detected_armors) { - if (armor.genre != DeviceId::OUTPOST) { - filtered.push_back(armor); - continue; + if (!locator_result.green_light.has_value()) { + filtered = *detected_armors; + } else { + const auto threshold_y = + locator_result.green_light->y + locator_result.green_light->height; + for (const auto& armor : *detected_armors) { + if (armor.center.y >= 1.0 * threshold_y) filtered.push_back(armor); } - - if (*keep_it) filtered.push_back(armor); - ++keep_it; } return Identifier::Result { .armors = std::move(filtered), - .green_light = filter_result.green_light, + .green_light = locator_result.green_light, }; } }; diff --git a/src/module/identifier/green_light_armor_filter.cpp b/src/module/identifier/green_light_locator.cpp similarity index 68% rename from src/module/identifier/green_light_armor_filter.cpp rename to src/module/identifier/green_light_locator.cpp index 54f3a73..4fda458 100644 --- a/src/module/identifier/green_light_armor_filter.cpp +++ b/src/module/identifier/green_light_locator.cpp @@ -1,9 +1,8 @@ -#include "green_light_armor_filter.hpp" +#include "green_light_locator.hpp" #include "module/identifier/green_light_detection.hpp" #include "utility/image/image.details.hpp" -#include #include #include #include @@ -12,17 +11,16 @@ using namespace rmcs::identifier; -struct GreenLightArmorFilter::Impl { +struct GreenLightLocator::Impl { GreenLightDetection green_light_detection; auto initialize(const YAML::Node& yaml) noexcept -> std::expected { return green_light_detection.initialize(yaml); } - auto filter(const Image& image, std::span armors) noexcept - -> GreenLightArmorFilter::Result { - auto result = GreenLightArmorFilter::Result { - .keep_mask = std::vector(armors.size(), true), + auto locate(const Image& image, std::span armors) noexcept + -> GreenLightLocator::Result { + auto result = GreenLightLocator::Result { .green_light = std::nullopt, }; if (armors.empty()) return result; @@ -31,13 +29,6 @@ struct GreenLightArmorFilter::Impl { if (!detect_roi.has_value()) return result; result.green_light = green_light_detection.sync_detect(image, *detect_roi); - if (!result.green_light.has_value()) return result; - - const auto threshold_y = result.green_light->y + result.green_light->height; - for (std::size_t index = 0; index < armors.size(); ++index) { - result.keep_mask[index] = armors[index].center.y >= 1.0 * threshold_y; - } - return result; } @@ -80,17 +71,17 @@ struct GreenLightArmorFilter::Impl { } }; -auto GreenLightArmorFilter::initialize(const YAML::Node& yaml) noexcept +auto GreenLightLocator::initialize(const YAML::Node& yaml) noexcept -> std::expected { return pimpl->initialize(yaml); } -auto GreenLightArmorFilter::filter(const Image& image, std::span armors) noexcept +auto GreenLightLocator::locate(const Image& image, std::span armors) noexcept -> Result { - return pimpl->filter(image, armors); + return pimpl->locate(image, armors); } -GreenLightArmorFilter::GreenLightArmorFilter() noexcept +GreenLightLocator::GreenLightLocator() noexcept : pimpl { std::make_unique() } { } -GreenLightArmorFilter::~GreenLightArmorFilter() noexcept = default; +GreenLightLocator::~GreenLightLocator() noexcept = default; diff --git a/src/module/identifier/green_light_armor_filter.hpp b/src/module/identifier/green_light_locator.hpp similarity index 69% rename from src/module/identifier/green_light_armor_filter.hpp rename to src/module/identifier/green_light_locator.hpp index 0486dec..d7c66c4 100644 --- a/src/module/identifier/green_light_armor_filter.hpp +++ b/src/module/identifier/green_light_locator.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include @@ -15,17 +14,16 @@ namespace rmcs::identifier { -class GreenLightArmorFilter { - RMCS_PIMPL_DEFINITION(GreenLightArmorFilter) +class GreenLightLocator { + RMCS_PIMPL_DEFINITION(GreenLightLocator) public: struct Result { - std::vector keep_mask; std::optional green_light; }; auto initialize(const YAML::Node&) noexcept -> std::expected; - auto filter(const Image&, std::span) noexcept -> Result; + auto locate(const Image&, std::span) noexcept -> Result; }; } From 70f6cbfb06a1ffdb5e61b16baf5df1d48357cc2b Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Thu, 21 May 2026 01:03:34 +0800 Subject: [PATCH 5/6] feat: refine detector logic to filter out outpost, base, and unknown armors within green light regions --- src/kernel/identifier.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index 91406c3..e4190ed 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -46,7 +46,11 @@ struct Identifier::Impl { const auto threshold_y = locator_result.green_light->y + locator_result.green_light->height; for (const auto& armor : *detected_armors) { - if (armor.center.y >= 1.0 * threshold_y) filtered.push_back(armor); + const auto outpost_interference = DeviceIds::kBuilding().contains(armor.genre) + || armor.genre == DeviceId::UNKNOWN; + // 过滤掉绿灯之上的前哨站、基地和未知装甲板(图像坐标系y向下为正) + if (outpost_interference && armor.center.y < threshold_y) continue; + filtered.push_back(armor); } } From e1502d649c32e64b717a74056b53bd68cc504e9f Mon Sep 17 00:00:00 2001 From: heyeuu <2829004293@qq.com> Date: Thu, 21 May 2026 01:37:34 +0800 Subject: [PATCH 6/6] feat: refine detector logic to filter out outpost and base armors within green light regions --- config/config.yaml | 2 +- src/kernel/identifier.cpp | 41 +++++++++++++++++++++++++++------------ src/kernel/identifier.hpp | 3 ++- src/runtime.cpp | 11 +++++++---- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/config/config.yaml b/config/config.yaml index 2dd10ae..104fd62 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -51,7 +51,7 @@ identifier: score_threshold: 0.7 nms_threshold: 0.3 - outpost_green_light_filter: + green_light_filter: green_threshold: 120 min_area: 20 min_circularity: 0.6 diff --git a/src/kernel/identifier.cpp b/src/kernel/identifier.cpp index e4190ed..aadf899 100644 --- a/src/kernel/identifier.cpp +++ b/src/kernel/identifier.cpp @@ -18,8 +18,7 @@ struct Identifier::Impl { auto initialize(const YAML::Node& yaml) noexcept -> std::expected { auto armor_result = armor_detection.initialize(yaml); if (!armor_result.has_value()) return std::unexpected { armor_result.error() }; - auto locator_result = green_light_locator.initialize(yaml["outpost_green_light_" - "filter"]); + auto locator_result = green_light_locator.initialize(yaml["green_light_filter"]); if (!locator_result.has_value()) return std::unexpected { locator_result.error() }; return {}; @@ -30,33 +29,51 @@ struct Identifier::Impl { if (!detected_armors.has_value()) return std::nullopt; auto outpost_armors = std::vector {}; + auto base_armors = std::vector {}; outpost_armors.reserve(detected_armors->size()); + base_armors.reserve(detected_armors->size()); for (const auto& armor : *detected_armors) { if (armor.genre == DeviceId::OUTPOST) outpost_armors.push_back(armor); + if (armor.genre == DeviceId::BASE) base_armors.push_back(armor); } - const auto locator_result = green_light_locator.locate(src, outpost_armors); + const auto outpost_locator_result = green_light_locator.locate(src, outpost_armors); + const auto base_locator_result = green_light_locator.locate(src, base_armors); auto filtered = Armor2Ds {}; filtered.reserve(detected_armors->size()); - if (!locator_result.green_light.has_value()) { + if (!outpost_locator_result.green_light.has_value() + && !base_locator_result.green_light.has_value()) { filtered = *detected_armors; } else { - const auto threshold_y = - locator_result.green_light->y + locator_result.green_light->height; + const auto outpost_threshold_y = outpost_locator_result.green_light.has_value() + ? std::optional { + outpost_locator_result.green_light->y + + outpost_locator_result.green_light->height, + } + : std::nullopt; + const auto base_threshold_y = base_locator_result.green_light.has_value() + ? std::optional { + base_locator_result.green_light->y + base_locator_result.green_light->height, + } + : std::nullopt; + for (const auto& armor : *detected_armors) { - const auto outpost_interference = DeviceIds::kBuilding().contains(armor.genre) - || armor.genre == DeviceId::UNKNOWN; - // 过滤掉绿灯之上的前哨站、基地和未知装甲板(图像坐标系y向下为正) - if (outpost_interference && armor.center.y < threshold_y) continue; + const auto threshold_y = armor.genre == DeviceId::OUTPOST ? outpost_threshold_y + : armor.genre == DeviceId::BASE ? base_threshold_y + : std::nullopt; + // 过滤掉绿灯之上的对应装甲板(图像坐标系 y 向下为正) + if (threshold_y.has_value() && (armor.center.y < *threshold_y)) continue; + filtered.push_back(armor); } } return Identifier::Result { - .armors = std::move(filtered), - .green_light = locator_result.green_light, + .armors = std::move(filtered), + .outpost_green_light = outpost_locator_result.green_light, + .base_green_light = base_locator_result.green_light, }; } }; diff --git a/src/kernel/identifier.hpp b/src/kernel/identifier.hpp index cea070c..e395df7 100644 --- a/src/kernel/identifier.hpp +++ b/src/kernel/identifier.hpp @@ -18,7 +18,8 @@ class Identifier { public: struct Result { Armor2Ds armors; - std::optional green_light; + std::optional outpost_green_light; + std::optional base_green_light; }; auto initialize(const YAML::Node&) noexcept -> std::expected; diff --git a/src/runtime.cpp b/src/runtime.cpp index 0484c35..5ec40d8 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -148,8 +148,10 @@ auto main() -> int { for (const auto& armor : result->armors) util::draw(*image, armor); - if (result->green_light.has_value()) - util::draw_green_light(*image, *result->green_light); + if (result->outpost_green_light.has_value()) + util::draw_green_light(*image, *result->outpost_green_light); + if (result->base_green_light.has_value()) + util::draw_green_light(*image, *result->base_green_light); } logging.reset("detection", 5); @@ -210,8 +212,9 @@ auto main() -> int { command.pitch_rate, command.yaw_acc, command.pitch_acc); } } - - util::draw_text(*image, command.should_shoot ? "ATTACK" : "IDLE"); + if (use_visualization) { + util::draw_text(*image, command.should_shoot ? "ATTACK" : "IDLE"); + } /// 4. Transmit State ///