1212namespace vortex ::image_filtering {
1313
1414struct RemoveGridParams {
15- double threshold_green;
16- int threshold_binary;
1715 double inpaint_radius;
16+ int threshold_binary;
17+ bool use_binary_threshold;
1818 int rotation;
1919 int height;
2020 int width;
21+ int hsv_hue_low;
22+ int hsv_hue_high;
23+ int hsv_sat_low;
24+ int hsv_sat_high;
25+ int hsv_val_low;
26+ int hsv_val_high;
2127};
2228
2329class RemoveGrid : public Filter {
@@ -44,9 +50,9 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original,
4450 return ;
4551 }
4652
47- // Rotate directly into cropped output
48- int crop_w = std::min (params_.width , original.cols );
49- int crop_h = std::min (params_.height , original.rows );
53+ // Rotate/crop to ROI
54+ const int crop_w = std::min (params_.width , original.cols );
55+ const int crop_h = std::min (params_.height , original.rows );
5056
5157 if (crop_w != params_.width || crop_h != params_.height ) {
5258 spdlog::warn (
@@ -58,85 +64,76 @@ inline void RemoveGrid::apply_filter(const cv::Mat& original,
5864 crop_h);
5965 }
6066
61- const cv::Point2f center_src (
62- original.cols * 0 .5f , original.rows * 0 .5f ); // center of source image
63- const cv::Point2f center_dst (crop_w * 0 .5f ,
64- crop_h * 0 .5f ); // center of destination image
67+ const cv::Point2f center_src (original.cols * 0 .5f , original.rows * 0 .5f );
68+ const cv::Point2f center_dst (crop_w * 0 .5f , crop_h * 0 .5f );
6569
66- cv::Mat M = cv::getRotationMatrix2D (center_src, params_.rotation ,
67- 1.0 ); // affine matrix
68- // Ensure type for at<double>
69- if (M.type () != CV_64F ) {
70+ cv::Mat M = cv::getRotationMatrix2D (center_src, params_.rotation , 1.0 );
71+ if (M.type () != CV_64F )
7072 M.convertTo (M, CV_64F );
71- }
7273
73- // Shift translation so original center maps to cropped center
7474 M.at <double >(0 , 2 ) += (center_dst.x - center_src.x );
7575 M.at <double >(1 , 2 ) += (center_dst.y - center_src.y );
7676
7777 cv::Mat cropped;
7878 cv::warpAffine (original, cropped, M, cv::Size (crop_w, crop_h),
7979 cv::INTER_NEAREST , cv::BORDER_CONSTANT , cv::Scalar (0 , 0 , 0 ));
8080
81- // Extract green grid mask
82- cv::Mat cropped_f;
83- cropped.convertTo (cropped_f, CV_32F , 1.0 / 255.0 );
84-
85- std::vector<cv::Mat> ch (3 ); // make a vector for BGR
86- cv::split (cropped_f, ch); // BGR
87-
88- cv::Mat sum = ch[0 ] + ch[1 ] + ch[2 ] + 1e-6f ; // avoid division by zero
89- for (auto & c : ch)
90- c /= sum; // normalized color values
91-
92- // mask the green channel
93- cv::Mat grid_mask = (ch[1 ] > params_.threshold_green );
81+ // Detect yellow grid bars via HSV hue range (input is rgb8)
82+ cv::Mat hsv;
83+ cv::cvtColor (cropped, hsv, cv::COLOR_RGB2HSV );
84+ cv::Mat grid_mask;
85+ cv::inRange (hsv,
86+ cv::Scalar (params_.hsv_hue_low , params_.hsv_sat_low ,
87+ params_.hsv_val_low ),
88+ cv::Scalar (params_.hsv_hue_high , params_.hsv_sat_high ,
89+ params_.hsv_val_high ),
90+ grid_mask);
91+
92+ // Dilate mask to fully cover grid bar edges
9493 static const cv::Mat kernel = cv::Mat::ones (3 , 3 , CV_8U );
9594 cv::Mat dilated;
9695 cv::dilate (grid_mask, dilated, kernel);
9796
98- // prevent border leak
97+ // Prevent border leak
9998 dilated.row (0 ).setTo (0 );
10099 dilated.row (dilated.rows - 1 ).setTo (0 );
101100 dilated.col (0 ).setTo (0 );
102101 dilated.col (dilated.cols - 1 ).setTo (0 );
103102
104103 if (cv::countNonZero (dilated) == 0 ) {
105- // If no grid detected, leave image unchanged
106104 original.copyTo (filtered);
107105 return ;
108106 }
109107
108+ // Optionally apply binary threshold before inpainting
109+ cv::Mat inpaint_src;
110+ if (params_.use_binary_threshold ) {
111+ cv::Mat thresh_gray;
112+ apply_fixed_threshold (cropped, thresh_gray, params_.threshold_binary ,
113+ false );
114+ cv::cvtColor (thresh_gray, inpaint_src, cv::COLOR_GRAY2BGR );
115+ } else {
116+ inpaint_src = cropped;
117+ }
118+
110119 // Inpaint grid
111120 cv::Mat inpainted;
112- cv::inpaint (cropped , dilated, inpainted, params_.inpaint_radius ,
121+ cv::inpaint (inpaint_src , dilated, inpainted, params_.inpaint_radius ,
113122 cv::INPAINT_TELEA );
114123
115- // Binary threshold (on cropped ROI)
116- cv::Mat thresh_gray;
117- apply_fixed_threshold (inpainted, thresh_gray, params_.threshold_binary ,
118- false );
119-
120- cv::Mat thresh_bgr;
121- cv::cvtColor (thresh_gray, thresh_bgr, cv::COLOR_GRAY2BGR );
122-
123- // Undo rotation & merge (using M)
124+ // Warp inpainted ROI back into full-size image
124125 cv::Mat invM;
125126 cv::invertAffineTransform (M, invM);
126127
127- // Warp ROI result back into full-size overlay
128128 cv::Mat overlay_full;
129- cv::warpAffine (thresh_bgr , overlay_full, invM, original.size (),
129+ cv::warpAffine (inpainted , overlay_full, invM, original.size (),
130130 cv::INTER_NEAREST , cv::BORDER_CONSTANT , cv::Scalar (0 , 0 , 0 ));
131131
132- // Warp a mask the same way (so black pixels are copied too)
133- cv::Mat local_mask (thresh_bgr.rows , thresh_bgr.cols , CV_8U ,
134- cv::Scalar (255 ));
132+ cv::Mat local_mask (inpainted.rows , inpainted.cols , CV_8U , cv::Scalar (255 ));
135133 cv::Mat mask_full;
136134 cv::warpAffine (local_mask, mask_full, invM, original.size (),
137135 cv::INTER_NEAREST , cv::BORDER_CONSTANT , cv::Scalar (0 ));
138136
139- // Merge into the original image
140137 filtered = original.clone ();
141138 overlay_full.copyTo (filtered, mask_full);
142139}
0 commit comments