From 3722c0e1300f49a66541fb4449fafc318d5eb2d7 Mon Sep 17 00:00:00 2001 From: Anttra Date: Sat, 4 Apr 2026 15:18:37 +0200 Subject: [PATCH 1/6] three different thresholding methods added --- .../config/image_filtering_params.yaml | 3 +-- .../image_filtering/lib/filters/remove_grid.hpp | 16 ++++++++++++---- image-filtering/src/ros/image_filtering_ros.cpp | 3 --- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/image-filtering/config/image_filtering_params.yaml b/image-filtering/config/image_filtering_params.yaml index 6e0ac28..b946bf5 100644 --- a/image-filtering/config/image_filtering_params.yaml +++ b/image-filtering/config/image_filtering_params.yaml @@ -31,8 +31,7 @@ erosion_size: 2 dilation_size: 2 remove_grid: - threshold_green: 0.5 - threshold_binary: 30 + threshold_binary: 30.0 inpaint_radius: 1.0 rotation: 0 height: 400 diff --git a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp index 3bf1ba4..76ba0cd 100644 --- a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp +++ b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp @@ -12,8 +12,7 @@ namespace vortex::image_filtering { struct RemoveGridParams { - double threshold_green; - int threshold_binary; + double threshold_binary; double inpaint_radius; int rotation; int height; @@ -89,8 +88,17 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original, for (auto& c : ch) c /= sum; // normalized color values - // mask the green channel - cv::Mat grid_mask = (ch[1] > params_.threshold_green); + // Otsu threshold on normalized green channel + cv::Mat green8; + cv::Mat greenTemp; + greenTemp = ch[1] * 255; + greenTemp.convertTo(green8, CV_8U); + cv::Mat grid_mask; + + //cv::threshold(green8, grid_mask, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); + //cv::adaptiveThreshold(green8, grid_mask, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 11, 2); + cv::adaptiveThreshold(green8, grid_mask, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 11, 2); + static const cv::Mat kernel = cv::Mat::ones(3, 3, CV_8U); cv::Mat dilated; cv::dilate(grid_mask, dilated, kernel); diff --git a/image-filtering/src/ros/image_filtering_ros.cpp b/image-filtering/src/ros/image_filtering_ros.cpp index 9c91d27..8ffa3a8 100644 --- a/image-filtering/src/ros/image_filtering_ros.cpp +++ b/image-filtering/src/ros/image_filtering_ros.cpp @@ -168,9 +168,6 @@ void ImageFilteringNode::set_filter_params() { case FilterType::RemoveGrid: { RemoveGridParams params; - params.threshold_green = declare_and_get( - "filter_params.remove_grid.threshold_green"); - params.threshold_binary = declare_and_get( "filter_params.remove_grid.threshold_binary"); From 3c127144f824344b91788c55b365a06ea7083e4e Mon Sep 17 00:00:00 2001 From: Anttra Date: Sat, 4 Apr 2026 20:03:41 +0200 Subject: [PATCH 2/6] implemented HSV tuning --- .../config/image_filtering_params.yaml | 13 ++- .../lib/filters/binary_threshold.hpp | 2 +- .../lib/filters/remove_grid.hpp | 91 ++++++++----------- .../src/ros/image_filtering_ros.cpp | 22 ++++- 4 files changed, 70 insertions(+), 58 deletions(-) diff --git a/image-filtering/config/image_filtering_params.yaml b/image-filtering/config/image_filtering_params.yaml index b946bf5..a954966 100644 --- a/image-filtering/config/image_filtering_params.yaml +++ b/image-filtering/config/image_filtering_params.yaml @@ -1,6 +1,6 @@ /**: ros__parameters: - sub_topic: "/cam/image_color" + sub_topic: "/realsense_d555/color/image_rect" pub_topic: "/filtered_image" input_encoding: "rgb8" output_encoding: "rgb8" @@ -31,11 +31,18 @@ erosion_size: 2 dilation_size: 2 remove_grid: - threshold_binary: 30.0 inpaint_radius: 1.0 + threshold_binary: 90 + use_binary_threshold: true rotation: 0 height: 400 - width: 400 + width: 800 + hsv_hue_low: 9 + hsv_hue_high: 40 + hsv_sat_low: 50 + hsv_sat_high: 255 + hsv_val_low: 50 + hsv_val_high: 255 overlap: percentage_threshold: 20.0 # Percentage (0-100) to cap the pixel intensity difference median_binary: # finds the median of each n x n square around each pixel diff --git a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp index c33b28a..26d174f 100644 --- a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp +++ b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp @@ -30,7 +30,7 @@ inline void BinaryThreshold::apply_filter(const cv::Mat& original, cv::Mat& filtered) const { CV_Assert(!original.empty()); - const double thresh = this->filter_params_.threshold; + const double thresh = this->filter_params_.threshold * 255.0 / 100.0; const double maxval = this->filter_params_.maxval; const bool invert = this->filter_params_.invert; diff --git a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp index 76ba0cd..8c18288 100644 --- a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp +++ b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp @@ -12,11 +12,18 @@ namespace vortex::image_filtering { struct RemoveGridParams { - double threshold_binary; double inpaint_radius; + int threshold_binary; + bool use_binary_threshold; int rotation; int height; int width; + int hsv_hue_low; + int hsv_hue_high; + int hsv_sat_low; + int hsv_sat_high; + int hsv_val_low; + int hsv_val_high; }; class RemoveGrid : public Filter { @@ -43,7 +50,7 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original, return; } - // Rotate directly into cropped output + // Rotate/crop to ROI int crop_w = std::min(params_.width, original.cols); int crop_h = std::min(params_.height, original.rows); @@ -57,19 +64,13 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original, crop_h); } - const cv::Point2f center_src( - original.cols * 0.5f, original.rows * 0.5f); // center of source image - const cv::Point2f center_dst(crop_w * 0.5f, - crop_h * 0.5f); // center of destination image + const cv::Point2f center_src(original.cols * 0.5f, original.rows * 0.5f); + const cv::Point2f center_dst(crop_w * 0.5f, crop_h * 0.5f); - cv::Mat M = cv::getRotationMatrix2D(center_src, params_.rotation, - 1.0); // affine matrix - // Ensure type for at - if (M.type() != CV_64F) { + cv::Mat M = cv::getRotationMatrix2D(center_src, params_.rotation, 1.0); + if (M.type() != CV_64F) M.convertTo(M, CV_64F); - } - // Shift translation so original center maps to cropped center M.at(0, 2) += (center_dst.x - center_src.x); M.at(1, 2) += (center_dst.y - center_src.y); @@ -77,74 +78,62 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original, cv::warpAffine(original, cropped, M, cv::Size(crop_w, crop_h), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); - // Extract green grid mask - cv::Mat cropped_f; - cropped.convertTo(cropped_f, CV_32F, 1.0 / 255.0); - - std::vector ch(3); // make a vector for BGR - cv::split(cropped_f, ch); // BGR - - cv::Mat sum = ch[0] + ch[1] + ch[2] + 1e-6f; // avoid division by zero - for (auto& c : ch) - c /= sum; // normalized color values - - // Otsu threshold on normalized green channel - cv::Mat green8; - cv::Mat greenTemp; - greenTemp = ch[1] * 255; - greenTemp.convertTo(green8, CV_8U); + // Detect yellow grid bars via HSV hue range (input is rgb8) + cv::Mat hsv; + cv::cvtColor(cropped, hsv, cv::COLOR_RGB2HSV); cv::Mat grid_mask; - - //cv::threshold(green8, grid_mask, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); - //cv::adaptiveThreshold(green8, grid_mask, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 11, 2); - cv::adaptiveThreshold(green8, grid_mask, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 11, 2); - + cv::inRange(hsv, + cv::Scalar(params_.hsv_hue_low, params_.hsv_sat_low, + params_.hsv_val_low), + cv::Scalar(params_.hsv_hue_high, params_.hsv_sat_high, + params_.hsv_val_high), + grid_mask); + + // Dilate mask to fully cover grid bar edges static const cv::Mat kernel = cv::Mat::ones(3, 3, CV_8U); cv::Mat dilated; cv::dilate(grid_mask, dilated, kernel); - // prevent border leak + // Prevent border leak dilated.row(0).setTo(0); dilated.row(dilated.rows - 1).setTo(0); dilated.col(0).setTo(0); dilated.col(dilated.cols - 1).setTo(0); if (cv::countNonZero(dilated) == 0) { - // If no grid detected, leave image unchanged original.copyTo(filtered); return; } + // Optionally apply binary threshold before inpainting + cv::Mat inpaint_src; + if (params_.use_binary_threshold) { + cv::Mat thresh_gray; + apply_fixed_threshold(cropped, thresh_gray, params_.threshold_binary, + false); + cv::cvtColor(thresh_gray, inpaint_src, cv::COLOR_GRAY2BGR); + } else { + inpaint_src = cropped; + } + // Inpaint grid cv::Mat inpainted; - cv::inpaint(cropped, dilated, inpainted, params_.inpaint_radius, + cv::inpaint(inpaint_src, dilated, inpainted, params_.inpaint_radius, cv::INPAINT_TELEA); - // Binary threshold (on cropped ROI) - cv::Mat thresh_gray; - apply_fixed_threshold(inpainted, thresh_gray, params_.threshold_binary, - false); - - cv::Mat thresh_bgr; - cv::cvtColor(thresh_gray, thresh_bgr, cv::COLOR_GRAY2BGR); - - // Undo rotation & merge (using M) + // Warp inpainted ROI back into full-size image cv::Mat invM; cv::invertAffineTransform(M, invM); - // Warp ROI result back into full-size overlay cv::Mat overlay_full; - cv::warpAffine(thresh_bgr, overlay_full, invM, original.size(), + cv::warpAffine(inpainted, overlay_full, invM, original.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); - // Warp a mask the same way (so black pixels are copied too) - cv::Mat local_mask(thresh_bgr.rows, thresh_bgr.cols, CV_8U, - cv::Scalar(255)); + cv::Mat local_mask(inpainted.rows, inpainted.cols, CV_8U, cv::Scalar(255)); cv::Mat mask_full; cv::warpAffine(local_mask, mask_full, invM, original.size(), cv::INTER_NEAREST, cv::BORDER_CONSTANT, cv::Scalar(0)); - // Merge into the original image filtered = original.clone(); overlay_full.copyTo(filtered, mask_full); } diff --git a/image-filtering/src/ros/image_filtering_ros.cpp b/image-filtering/src/ros/image_filtering_ros.cpp index 8ffa3a8..484e5b5 100644 --- a/image-filtering/src/ros/image_filtering_ros.cpp +++ b/image-filtering/src/ros/image_filtering_ros.cpp @@ -168,12 +168,15 @@ void ImageFilteringNode::set_filter_params() { case FilterType::RemoveGrid: { RemoveGridParams params; - params.threshold_binary = declare_and_get( - "filter_params.remove_grid.threshold_binary"); - params.inpaint_radius = declare_and_get( "filter_params.remove_grid.inpaint_radius"); + params.threshold_binary = declare_and_get( + "filter_params.remove_grid.threshold_binary"); + + params.use_binary_threshold = declare_and_get( + "filter_params.remove_grid.use_binary_threshold"); + params.rotation = declare_and_get("filter_params.remove_grid.rotation"); @@ -183,6 +186,19 @@ void ImageFilteringNode::set_filter_params() { params.height = declare_and_get("filter_params.remove_grid.height"); + params.hsv_hue_low = + declare_and_get("filter_params.remove_grid.hsv_hue_low"); + params.hsv_hue_high = + declare_and_get("filter_params.remove_grid.hsv_hue_high"); + params.hsv_sat_low = + declare_and_get("filter_params.remove_grid.hsv_sat_low"); + params.hsv_sat_high = + declare_and_get("filter_params.remove_grid.hsv_sat_high"); + params.hsv_val_low = + declare_and_get("filter_params.remove_grid.hsv_val_low"); + params.hsv_val_high = + declare_and_get("filter_params.remove_grid.hsv_val_high"); + filter_ptr_ = std::make_unique(params); break; } From e8b5f88da07ae97f969ee59e8a6877afb5590621 Mon Sep 17 00:00:00 2001 From: Anttra Date: Sun, 5 Apr 2026 15:27:26 +0200 Subject: [PATCH 3/6] added tuning comments in params and const in some values --- image-filtering/config/image_filtering_params.yaml | 14 +++++++------- .../image_filtering/lib/filters/remove_grid.hpp | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/image-filtering/config/image_filtering_params.yaml b/image-filtering/config/image_filtering_params.yaml index a954966..7807492 100644 --- a/image-filtering/config/image_filtering_params.yaml +++ b/image-filtering/config/image_filtering_params.yaml @@ -31,17 +31,17 @@ erosion_size: 2 dilation_size: 2 remove_grid: - inpaint_radius: 1.0 - threshold_binary: 90 + inpaint_radius: 1.0 # Increase when it get blurry close + threshold_binary: 90 # Adjust depending on how many black patches obstructs around the marker use_binary_threshold: true rotation: 0 height: 400 - width: 800 - hsv_hue_low: 9 - hsv_hue_high: 40 - hsv_sat_low: 50 + width: 400 + hsv_hue_low: 9 # This value is calibrated for yellow-green grids. Increase this value to ~40 if the grid is more orange + hsv_hue_high: 40 # Increase this value to ~80 if more orange + hsv_sat_low: 50 # Adjust together with value depending on brightness hsv_sat_high: 255 - hsv_val_low: 50 + hsv_val_low: 50 # Adjust together with saturation depending on brightness hsv_val_high: 255 overlap: percentage_threshold: 20.0 # Percentage (0-100) to cap the pixel intensity difference diff --git a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp index 8c18288..f574636 100644 --- a/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp +++ b/image-filtering/include/image_filtering/lib/filters/remove_grid.hpp @@ -51,8 +51,8 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original, } // Rotate/crop to ROI - int crop_w = std::min(params_.width, original.cols); - int crop_h = std::min(params_.height, original.rows); + const int crop_w = std::min(params_.width, original.cols); + const int crop_h = std::min(params_.height, original.rows); if (crop_w != params_.width || crop_h != params_.height) { spdlog::warn( From 1a9e024b378666b1cb194404e4b9cc4c86ba9586 Mon Sep 17 00:00:00 2001 From: Anton Tran <120760136+AntTra@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:48:43 +0200 Subject: [PATCH 4/6] Update comments in image_filtering_params.yaml Clarified comments for hsv saturation and value adjustments. --- image-filtering/config/image_filtering_params.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/image-filtering/config/image_filtering_params.yaml b/image-filtering/config/image_filtering_params.yaml index 7807492..aa9cde8 100644 --- a/image-filtering/config/image_filtering_params.yaml +++ b/image-filtering/config/image_filtering_params.yaml @@ -39,9 +39,9 @@ width: 400 hsv_hue_low: 9 # This value is calibrated for yellow-green grids. Increase this value to ~40 if the grid is more orange hsv_hue_high: 40 # Increase this value to ~80 if more orange - hsv_sat_low: 50 # Adjust together with value depending on brightness + hsv_sat_low: 50 # Adjust together with hsv_val_high depending on brightness hsv_sat_high: 255 - hsv_val_low: 50 # Adjust together with saturation depending on brightness + hsv_val_low: 50 # Adjust together with hsv_sat_low depending on brightness hsv_val_high: 255 overlap: percentage_threshold: 20.0 # Percentage (0-100) to cap the pixel intensity difference From 7d0fd6572ee84e242712e4c4464116ed3778cde8 Mon Sep 17 00:00:00 2001 From: Anton Tran <120760136+AntTra@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:17:12 +0200 Subject: [PATCH 5/6] Update comment for threshold calculation in BinaryThreshold Clarify the calculation of threshold value in the apply_filter method. --- .../include/image_filtering/lib/filters/binary_threshold.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp index 26d174f..6eb6853 100644 --- a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp +++ b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp @@ -30,7 +30,7 @@ inline void BinaryThreshold::apply_filter(const cv::Mat& original, cv::Mat& filtered) const { CV_Assert(!original.empty()); - const double thresh = this->filter_params_.threshold * 255.0 / 100.0; + const double thresh = this->filter_params_.threshold * 255.0 / 100.0; // Params is in percent while cv::threshold takes in values from 0 to 255 const double maxval = this->filter_params_.maxval; const bool invert = this->filter_params_.invert; From 6dd52a2920c2a5751bd16e2dd6e2e6d5cef6d0a6 Mon Sep 17 00:00:00 2001 From: kluge7 <89779148+kluge7@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:01:40 +0200 Subject: [PATCH 6/6] chore: apply pre-commit formatting --- .../include/image_filtering/lib/filters/binary_threshold.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp index 6eb6853..fd1fe0e 100644 --- a/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp +++ b/image-filtering/include/image_filtering/lib/filters/binary_threshold.hpp @@ -30,7 +30,9 @@ inline void BinaryThreshold::apply_filter(const cv::Mat& original, cv::Mat& filtered) const { CV_Assert(!original.empty()); - const double thresh = this->filter_params_.threshold * 255.0 / 100.0; // Params is in percent while cv::threshold takes in values from 0 to 255 + const double thresh = this->filter_params_.threshold * 255.0 / + 100.0; // Params is in percent while cv::threshold + // takes in values from 0 to 255 const double maxval = this->filter_params_.maxval; const bool invert = this->filter_params_.invert;