feat(armor_yolo_detect): hybrid detection with YOLO ROI + traditional vision refinement

- Use YOLO to detect armor ROI regions instead of full image
- Expand ROI by configurable pixels (default 75) for light detection
- Apply traditional binary + light detection within ROI
- Keep YOLO's number classification result
- Use LightCornerCorrector for corner refinement
- Add debug visualization for ROI regions (blue rectangles)
- Compatible input/output with armor_detector node

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MobKBK
2026-03-23 07:27:01 +08:00
parent 52257cf15c
commit 46bf3c9b52
4 changed files with 426 additions and 74 deletions

View File

@@ -19,6 +19,7 @@ find_package(ament_cmake_auto REQUIRED)
find_package(OpenCV REQUIRED)
find_package(fmt REQUIRED)
find_package(Sophus REQUIRED)
find_package(armor_detector REQUIRED)
ament_auto_find_build_dependencies()
# TensorRT
@@ -76,6 +77,7 @@ target_link_libraries(${PROJECT_NAME}
nvinfer_plugin
nvonnxparser
fmt::fmt
armor_detector
)
rclcpp_components_register_node(${PROJECT_NAME}

View File

@@ -1,5 +1,8 @@
// Copyright chenyy 2026. Licensed under the MIT License.
// YOLOv5-based armor detector
// YOLOv5-based armor detector with traditional vision refinement
//
// Uses YOLO to provide ROI regions, then applies traditional binary + light detection
// for precise corner localization. Input/output compatible with armor_detector.
//
// Copyright (C) FYT Vision Group. All rights reserved.
//
@@ -26,6 +29,8 @@
// project
#include "armor_yolo_detect/types.hpp"
#include "armor_yolo_detect/yolo_tensorrt.hpp"
#include "armor_detector/types.hpp"
#include "armor_detector/light_corner_corrector.hpp"
#include "rm_utils/common.hpp"
namespace armor_yolo_detect {
@@ -36,24 +41,47 @@ public:
float conf_threshold = 0.65f;
float nms_threshold = 0.45f;
int detect_color = 0; // 0=red, 1=blue
// ROI expansion pixels
int roi_expand_pixel = 75;
};
// Light detection params (from armor_detector)
struct LightParams {
double min_ratio = 0.08;
double max_ratio = 0.4;
double max_angle = 40.0;
bool use_fit_line = true;
int color_diff_thresh = 25;
};
// Armor matching params (from armor_detector)
struct ArmorParams {
double min_light_ratio = 0.6;
double min_small_center_distance = 0.8;
double max_small_center_distance = 3.2;
double min_large_center_distance = 3.2;
double max_large_center_distance = 5.0;
double max_angle = 35.0;
};
Detector() = default;
~Detector() = default;
/**
* @brief Initialize detector with TensorRT engine
* @brief Initialize detector with TensorRT engine and traditional vision params
* @param engine_path Path to TensorRT engine file
* @param params Detection parameters
* @param light_params Light bar detection parameters
* @param armor_params Armor matching parameters
*/
bool init(const std::string& engine_path, const Params& params);
/**
* @brief Detect armors in image
* @param input Input image (RGB)
* @return Vector of detected Armors
* @brief Detect armors using YOLO for ROI + traditional vision for refinement
* @param input Input image (BGR)
* @return Vector of detected Armors (fyt::auto_aim::Armor type for compatibility)
*/
std::vector<Armor> detect(const cv::Mat& input);
std::vector<fyt::auto_aim::Armor> detect(const cv::Mat& input);
/**
* @brief Detect armors and return raw YOLO objects
@@ -64,14 +92,38 @@ public:
// Parameters
Params params;
LightParams light_params;
ArmorParams armor_params;
int binary_thres = 160;
// Debug
bool enable_debug = false;
cv::Mat debug_img;
// Store ROI rectangles for debug drawing
std::vector<cv::Rect> debug_rois_;
// Draw detection results for debug visualization
void drawResults(cv::Mat& img) noexcept;
private:
// Traditional vision methods
std::vector<fyt::auto_aim::Light> findLightsInROI(
const cv::Mat& rgb_img, const cv::Mat& gray_img, const cv::Mat& binary_img,
const cv::Rect& roi);
bool isLight(const fyt::auto_aim::Light& light);
bool containLight(const int i, const int j, const std::vector<fyt::auto_aim::Light>& lights);
fyt::auto_aim::ArmorType isArmor(const fyt::auto_aim::Light& light_1, const fyt::auto_aim::Light& light_2);
std::vector<fyt::auto_aim::Armor> matchLightsInROI(const std::vector<fyt::auto_aim::Light>& lights);
void correctCorners(fyt::auto_aim::Armor& armor, const cv::Mat& gray_img);
// Expand YOLO bbox to ROI
cv::Rect expandBBox(const cv::Rect& bbox, const cv::Size& img_size, int pixel_expand);
std::unique_ptr<YoloTensorRT> tensorrt_;
std::vector<Armor> armors_;
std::vector<YoloObject> yolo_objects_;
std::vector<fyt::auto_aim::Armor> armors_;
std::unique_ptr<fyt::auto_aim::LightCornerCorrector> corner_corrector_;
};
} // namespace armor_yolo_detect

View File

@@ -1,5 +1,8 @@
// Copyright chenyy 2026. Licensed under the MIT License.
// YOLOv5-based armor detector implementation
// YOLOv5-based armor detector with traditional vision refinement
//
// Uses YOLO to provide ROI regions, then applies traditional binary + light detection
// for precise corner localization. Input/output compatible with armor_detector.
//
// Copyright (C) FYT Vision Group. All rights reserved.
//
@@ -33,34 +36,323 @@ bool Detector::init(const std::string& engine_path, const Params& params) {
tensorrt_->setPostprocessOptions(params.conf_threshold, params.nms_threshold, 300);
// Initialize corner corrector for precise corner refinement
corner_corrector_ = std::make_unique<fyt::auto_aim::LightCornerCorrector>();
return true;
}
std::vector<Armor> Detector::detect(const cv::Mat& input) {
std::vector<fyt::auto_aim::Armor> Detector::detect(const cv::Mat& input) {
armors_.clear();
debug_rois_.clear();
// Run inference
auto yolo_objects = tensorrt_->infer(input, params.detect_color);
// 1. Run YOLO inference to get ROI regions
yolo_objects_ = tensorrt_->infer(input, params.detect_color);
// Convert YOLO objects to Armors
for (const auto& obj : yolo_objects) {
// Calculate center from landmarks
cv::Point2f center(
(obj.landmarks[0] + obj.landmarks[2] + obj.landmarks[4] + obj.landmarks[6]) / 4,
(obj.landmarks[1] + obj.landmarks[3] + obj.landmarks[5] + obj.landmarks[7]) / 4);
Armor armor(obj, center);
armor.number_class = static_cast<NumberClass>(obj.label);
armor.confidence = obj.prob;
armors_.push_back(armor);
if (yolo_objects_.empty()) {
return armors_;
}
return armors_;
// 2. Preprocess image once
cv::Mat gray_img;
cv::cvtColor(input, gray_img, cv::COLOR_BGR2GRAY);
cv::Mat full_binary_img;
cv::threshold(gray_img, full_binary_img, binary_thres, 255, cv::THRESH_BINARY);
// 3. For each YOLO detection, create ROI and detect lights
for (const auto& yolo_obj : yolo_objects_) {
// Get bounding box from YOLO
cv::Rect_<float> bbox = yolo_obj.rect;
// Expand bbox to create ROI
cv::Rect roi = expandBBox(bbox, input.size(), params.roi_expand_pixel);
// Clip ROI to image bounds
roi = roi & cv::Rect(0, 0, input.size().width, input.size().height);
if (roi.area() <= 0) {
continue;
}
// Store ROI for debug visualization
debug_rois_.push_back(roi);
// Extract ROI images
cv::Mat roi_gray = gray_img(roi);
cv::Mat roi_binary = full_binary_img(roi);
// 4. Find lights within this ROI
auto lights = findLightsInROI(input, roi_gray, roi_binary, roi);
if (lights.empty()) {
continue;
}
// 5. Match lights to form armors within this ROI
auto roi_armors = matchLightsInROI(lights);
// 6. Process each armor: assign YOLO classification info
for (auto& armor : roi_armors) {
// Convert ROI coordinates to global coordinates
armor.left_light.top += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.left_light.bottom += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.left_light.center += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.right_light.top += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.right_light.bottom += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.right_light.center += cv::Point2f(static_cast<float>(roi.x), static_cast<float>(roi.y));
armor.center = (armor.left_light.center + armor.right_light.center) / 2;
// Use YOLO's classification result (color and number class)
armor.classfication_result = numberClassToString(static_cast<NumberClass>(yolo_obj.label));
armor.confidence = yolo_obj.prob;
// Use armor type from traditional detection (more accurate)
// YOLO info available if needed: yolo_obj.color
// 7. Correct corners using traditional method
correctCorners(armor, gray_img);
armors_.push_back(armor);
}
}
return armors;
}
std::vector<YoloObject> Detector::detectRaw(const cv::Mat& input) {
return tensorrt_->infer(input, params.detect_color);
}
cv::Rect Detector::expandBBox(const cv::Rect_<float>& bbox, const cv::Size& img_size, int pixel_expand) {
int x = static_cast<int>(bbox.x - pixel_expand);
int y = static_cast<int>(bbox.y - pixel_expand);
int w = static_cast<int>(bbox.width + 2 * pixel_expand);
int h = static_cast<int>(bbox.height + 2 * pixel_expand);
return cv::Rect(x, y, w, h);
}
std::vector<fyt::auto_aim::Light> Detector::findLightsInROI(
const cv::Mat& rgb_img,
const cv::Mat& gray_img,
const cv::Mat& binary_img,
const cv::Rect& roi) {
std::vector<fyt::auto_aim::Light> lights;
using std::vector;
vector<vector<cv::Point>> contours;
vector<cv::Vec4i> hierarchy;
cv::findContours(binary_img, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
lights.reserve(contours.size());
for (const auto& contour : contours) {
if (contour.size() < 6) continue;
fyt::auto_aim::Light light;
std::vector<cv::Point> points;
if (light_params.use_fit_line) {
auto b_rect = cv::boundingRect(contour);
auto r_rect = cv::minAreaRect(contour);
cv::Mat mask = cv::Mat::zeros(b_rect.size(), CV_8UC1);
vector<cv::Point> mask_contour;
mask_contour.reserve(contour.size());
for (const auto& p : contour) {
mask_contour.emplace_back(p - cv::Point(b_rect.x, b_rect.y));
}
cv::fillPoly(mask, {mask_contour}, 255);
cv::findNonZero(mask, points);
if (points.empty()) continue;
cv::Vec4f return_param;
cv::fitLine(points, return_param, cv::DIST_L2, 0, 0.01, 0.01);
cv::Point2f top, bottom;
if (int(return_param[0] * 100) == 100 || int(return_param[1] * 100) == 0) {
top = cv::Point2f(b_rect.x + b_rect.width / 2, b_rect.y);
bottom = cv::Point2f(b_rect.x + b_rect.width / 2, b_rect.y + b_rect.height);
} else {
auto k = return_param[1] / return_param[0];
auto b = (return_param[3] + b_rect.y) - k * (return_param[2] + b_rect.x);
top = cv::Point2f((b_rect.y - b) / k, b_rect.y);
bottom = cv::Point2f((b_rect.y + b_rect.height - b) / k, b_rect.y + b_rect.height);
}
double width = std::min(r_rect.size.width, r_rect.size.height);
double length = cv::norm(top - bottom);
light = fyt::auto_aim::Light(r_rect, top, bottom, length, width);
} else {
light = fyt::auto_aim::Light(contour);
}
if (isLight(light)) {
// Color detection within ROI
int sum_r = 0;
int sum_b = 0;
int sample_count = 0;
if (light_params.use_fit_line && !points.empty()) {
auto b_rect = cv::boundingRect(contour);
for (const auto& point : points) {
const int x = point.x + b_rect.x;
const int y = point.y + b_rect.y;
const auto& pixel = rgb_img.at<cv::Vec3b>(y, x);
sum_r += pixel[0];
sum_b += pixel[2];
++sample_count;
}
} else {
for (const auto& point : contour) {
const auto& pixel = rgb_img.at<cv::Vec3b>(point.y, point.x);
sum_r += pixel[0];
sum_b += pixel[2];
++sample_count;
}
}
if (sample_count > 0 &&
std::abs(sum_r - sum_b) / sample_count > light_params.color_diff_thresh) {
light.color = sum_r > sum_b ? EnemyColor::RED : EnemyColor::BLUE;
}
lights.emplace_back(light);
}
}
std::sort(lights.begin(), lights.end(), [](const fyt::auto_aim::Light& l1, const fyt::auto_aim::Light& l2) {
return l1.center.x < l2.center.x;
});
return lights;
}
bool Detector::isLight(const fyt::auto_aim::Light& light) {
float ratio = light.width / light.length;
bool ratio_ok = light_params.min_ratio < ratio && ratio < light_params.max_ratio;
bool angle_ok = light.tilt_angle < light_params.max_angle;
return ratio_ok && angle_ok;
}
bool Detector::containLight(const int i, const int j, const std::vector<fyt::auto_aim::Light>& lights) {
const fyt::auto_aim::Light& light_1 = lights.at(i);
const fyt::auto_aim::Light& light_2 = lights.at(j);
auto points = std::vector<cv::Point2f>{light_1.top, light_1.bottom, light_2.top, light_2.bottom};
auto bounding_rect = cv::boundingRect(points);
double avg_length = (light_1.length + light_2.length) / 2.0;
double avg_width = (light_1.width + light_2.width) / 2.0;
for (int k = i + 1; k < j; k++) {
const fyt::auto_aim::Light& test_light = lights.at(k);
if (test_light.width > 2 * avg_width) {
continue;
}
if (test_light.length < 0.5 * avg_length) {
continue;
}
if (bounding_rect.contains(test_light.top) || bounding_rect.contains(test_light.bottom) ||
bounding_rect.contains(test_light.center)) {
return true;
}
}
return false;
}
fyt::auto_aim::ArmorType Detector::isArmor(
const fyt::auto_aim::Light& light_1,
const fyt::auto_aim::Light& light_2) {
float light_length_ratio = light_1.length < light_2.length
? light_1.length / light_2.length
: light_2.length / light_1.length;
bool light_ratio_ok = light_length_ratio > armor_params.min_light_ratio;
float avg_light_length = (light_1.length + light_2.length) / 2;
float center_distance = cv::norm(light_1.center - light_2.center) / avg_light_length;
bool center_distance_ok =
(armor_params.min_small_center_distance <= center_distance &&
center_distance < armor_params.max_small_center_distance) ||
(armor_params.min_large_center_distance <= center_distance &&
center_distance < armor_params.max_large_center_distance);
cv::Point2f diff = light_1.center - light_2.center;
float angle = std::abs(std::atan(diff.y / diff.x)) / CV_PI * 180;
bool angle_ok = angle < armor_params.max_angle;
bool is_armor = light_ratio_ok && center_distance_ok && angle_ok;
if (is_armor) {
return center_distance > 3.5 ? fyt::auto_aim::ArmorType::LARGE : fyt::auto_aim::ArmorType::SMALL;
}
return fyt::auto_aim::ArmorType::INVALID;
}
std::vector<fyt::auto_aim::Armor> Detector::matchLightsInROI(const std::vector<fyt::auto_aim::Light>& lights) {
std::vector<fyt::auto_aim::Armor> armors;
EnemyColor detect_color = params.detect_color == 0 ? EnemyColor::RED : EnemyColor::BLUE;
for (auto light_1 = lights.begin(); light_1 != lights.end(); light_1++) {
// Skip lights not matching target color (or accept if color not determined)
if (light_1->color != EnemyColor::WHITE && light_1->color != detect_color) {
continue;
}
double max_iter_width = light_1->length * armor_params.max_large_center_distance;
for (auto light_2 = light_1 + 1; light_2 != lights.end(); light_2++) {
if (light_2->color != EnemyColor::WHITE && light_2->color != detect_color) {
continue;
}
if (containLight(light_1 - lights.begin(), light_2 - lights.begin(), lights)) {
continue;
}
if (light_2->center.x - light_1->center.x > max_iter_width) {
break;
}
auto type = isArmor(*light_1, *light_2);
if (type != fyt::auto_aim::ArmorType::INVALID) {
auto armor = fyt::auto_aim::Armor(*light_1, *light_2);
armor.type = type;
armors.emplace_back(armor);
}
}
}
return armors;
}
void Detector::correctCorners(fyt::auto_aim::Armor& armor, const cv::Mat& gray_img) {
if (corner_corrector_ != nullptr) {
corner_corrector_->correctCorners(armor, gray_img);
}
}
void Detector::drawResults(cv::Mat& img) noexcept {
// Draw ROI rectangles (expanded YOLO bboxes) in blue
for (const auto& roi : debug_rois_) {
cv::rectangle(img, roi, cv::Scalar(255, 0, 0), 2);
}
// Draw armor outlines
for (const auto& armor : armors_) {
// Draw armor outline
cv::line(
img, armor.left_light.top, armor.left_light.bottom, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
cv::line(
img, armor.right_light.bottom, armor.right_light.top, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
cv::line(
img, armor.left_light.top, armor.right_light.top, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
cv::line(
img, armor.right_light.bottom, armor.left_light.bottom, cv::Scalar(0, 255, 0), 1, cv::LINE_AA);
// Draw number and confidence
std::string text = fyt::auto_aim::armorTypeToString(armor.type) + " " + armor.classfication_result;
cv::putText(
img, text, armor.left_light.top, cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 255, 255), 2);
}
}
} // namespace armor_yolo_detect

View File

@@ -336,6 +336,7 @@ std::unique_ptr<Detector> ArmorYoloDetectorNode::initDetector() {
params.conf_threshold = this->declare_parameter("conf_threshold", 0.65);
params.nms_threshold = this->declare_parameter("nms_threshold", 0.50);
params.detect_color = this->declare_parameter("detect_color", -1);
params.roi_expand_pixel = this->declare_parameter("roi_expand_pixel", 75);
auto detector = std::make_unique<Detector>();
if (!detector->init(engine_path.string(), params)) {
@@ -343,6 +344,30 @@ std::unique_ptr<Detector> ArmorYoloDetectorNode::initDetector() {
return nullptr;
}
// Binary threshold for light detection
detector->binary_thres = this->declare_parameter("binary_thres", 160);
// Light detection params
detector->light_params.min_ratio = this->declare_parameter("light.min_ratio", 0.08);
detector->light_params.max_ratio = this->declare_parameter("light.max_ratio", 0.4);
detector->light_params.max_angle = this->declare_parameter("light.max_angle", 40.0);
detector->light_params.use_fit_line = this->declare_parameter("light.use_fit_line", true);
detector->light_params.color_diff_thresh =
static_cast<int>(this->declare_parameter("light.color_diff_thresh", 25));
// Armor matching params
detector->armor_params.min_light_ratio =
this->declare_parameter("armor.min_light_ratio", 0.6);
detector->armor_params.min_small_center_distance =
this->declare_parameter("armor.min_small_center_distance", 0.8);
detector->armor_params.max_small_center_distance =
this->declare_parameter("armor.max_small_center_distance", 3.2);
detector->armor_params.min_large_center_distance =
this->declare_parameter("armor.min_large_center_distance", 3.2);
detector->armor_params.max_large_center_distance =
this->declare_parameter("armor.max_large_center_distance", 5.0);
detector->armor_params.max_angle = this->declare_parameter("armor.max_angle", 35.0);
FYT_INFO("armor_yolo_detect", "Inference backend: TensorRT ({})", engine_path.string());
// Set dynamic parameter callback
@@ -362,70 +387,25 @@ std::vector<fyt::auto_aim::Armor> ArmorYoloDetectorNode::detectArmors(
// Convert ROS img to cv::Mat
auto img = cv_bridge::toCvShare(img_msg, "bgr8")->image;
// Run detection
auto yolo_armors = detector_->detect(img);
// Run detection: YOLO for ROI + traditional vision for refinement
auto armors = detector_->detect(img);
if (detect_ms != nullptr) {
*detect_ms = durationMs(std::chrono::steady_clock::now() - stage_start);
}
// Convert to auto_aim armor format
std::vector<fyt::auto_aim::Armor> armors;
for (const auto& yolo_armor : yolo_armors) {
armors.push_back(yolo_armor.toAutoAimArmor());
}
// Debug image publishing
stage_start = std::chrono::steady_clock::now();
if (debug_ && debug_result_img_) {
detector_->debug_img = img.clone();
cv::putText(detector_->debug_img,
"det=" + std::to_string(yolo_armors.size()),
"det=" + std::to_string(armors.size()),
cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0,
cv::Scalar(255, 255, 0), 2);
// Draw detection results
for (const auto& armor : yolo_armors) {
// Draw landmarks
std::vector<cv::Point> pts;
for (int i = 0; i < 6; i++) {
pts.emplace_back(static_cast<int>(armor.landmark_points[i].x),
static_cast<int>(armor.landmark_points[i].y));
cv::circle(detector_->debug_img,
cv::Point(static_cast<int>(armor.landmark_points[i].x),
static_cast<int>(armor.landmark_points[i].y)),
3, cv::Scalar(255, 0, 0), -1);
}
// Draw polygon
const cv::Point* pts_ptr = pts.data();
int npts = static_cast<int>(pts.size());
cv::polylines(detector_->debug_img, &pts_ptr, &npts, 1, true, cv::Scalar(0, 255, 0), 2);
float min_x = std::numeric_limits<float>::max();
float max_x = std::numeric_limits<float>::lowest();
float min_y = std::numeric_limits<float>::max();
float max_y = std::numeric_limits<float>::lowest();
for (const auto& p : armor.landmark_points) {
min_x = std::min(min_x, p.x);
max_x = std::max(max_x, p.x);
min_y = std::min(min_y, p.y);
max_y = std::max(max_y, p.y);
}
const cv::Rect bbox(
cv::Point(static_cast<int>(min_x), static_cast<int>(min_y)),
cv::Point(static_cast<int>(max_x), static_cast<int>(max_y)));
cv::rectangle(detector_->debug_img, bbox, cv::Scalar(0, 128, 255), 2);
// Draw label
std::ostringstream oss;
oss << numberClassToString(armor.number_class) << " " << std::fixed << std::setprecision(2)
<< armor.confidence;
cv::putText(detector_->debug_img, oss.str(),
cv::Point(static_cast<int>(armor.center.x), static_cast<int>(armor.center.y)),
cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 255), 2);
}
// Draw detection results using armor_detector's drawResults style
detector_->drawResults(detector_->debug_img);
// Publish result image
result_img_pub_.publish(
@@ -457,6 +437,32 @@ rcl_interfaces::msg::SetParametersResult ArmorYoloDetectorNode::onSetParameters(
detector_->params.nms_threshold = param.as_double();
} else if (param.get_name() == "detect_color") {
detector_->params.detect_color = param.as_int();
} else if (param.get_name() == "roi_expand_pixel") {
detector_->params.roi_expand_pixel = param.as_int();
} else if (param.get_name() == "binary_thres") {
detector_->binary_thres = param.as_int();
} else if (param.get_name() == "light.min_ratio") {
detector_->light_params.min_ratio = param.as_double();
} else if (param.get_name() == "light.max_ratio") {
detector_->light_params.max_ratio = param.as_double();
} else if (param.get_name() == "light.max_angle") {
detector_->light_params.max_angle = param.as_double();
} else if (param.get_name() == "light.use_fit_line") {
detector_->light_params.use_fit_line = param.as_bool();
} else if (param.get_name() == "light.color_diff_thresh") {
detector_->light_params.color_diff_thresh = param.as_int();
} else if (param.get_name() == "armor.min_light_ratio") {
detector_->armor_params.min_light_ratio = param.as_double();
} else if (param.get_name() == "armor.min_small_center_distance") {
detector_->armor_params.min_small_center_distance = param.as_double();
} else if (param.get_name() == "armor.max_small_center_distance") {
detector_->armor_params.max_small_center_distance = param.as_double();
} else if (param.get_name() == "armor.min_large_center_distance") {
detector_->armor_params.min_large_center_distance = param.as_double();
} else if (param.get_name() == "armor.max_large_center_distance") {
detector_->armor_params.max_large_center_distance = param.as_double();
} else if (param.get_name() == "armor.max_angle") {
detector_->armor_params.max_angle = param.as_double();
} else if (param.get_name() == "debug.enable_terminal_log") {
debug_terminal_log_ = param.as_bool();
} else if (param.get_name() == "debug.enable_markers") {