Skip to content

Commit 071d513

Browse files
committed
added additional filter, plus compare filter launch file
1 parent 1b51807 commit 071d513

11 files changed

Lines changed: 727 additions & 0 deletions

File tree

image-filtering/config/image_filtering_params.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@
1717
size: 3
1818
white_balancing:
1919
contrast_percentage: 10.0
20+
gray_world:
21+
max_gain: 4.0 # caps per-channel gain to [1/max_gain, max_gain]; guards against black/white flicker
22+
shades_of_gray:
23+
norm_p: 6.0 # 1 = gray-world, larger -> white-patch; 6 is a robust sweet spot
24+
max_gain: 4.0
25+
white_patch:
26+
percentile: 0.97 # [0, 1] channel white reference; <1 is robust to noise
27+
max_gain: 4.0
28+
underwater_compensation:
29+
alpha: 1.0 # strength of green->red(/blue) reconstruction (1.0 = Ancuti 2018)
30+
compensate_blue: false # also reconstruct blue (deeper / very blue water)
31+
max_gain: 4.0
32+
fixed_gain: # constant, scene-independent per-channel gains (calibrate once)
33+
gain_r: 1.8 # red is most attenuated underwater -> boost most
34+
gain_g: 1.0
35+
gain_b: 1.2
36+
offset: 0.0 # constant added to every channel, in 8-bit units
37+
clahe:
38+
clip_limit: 2.0 # higher -> more contrast (and more noise)
39+
tile_grid_size: 8 # image split into tile_grid_size x tile_grid_size tiles
2040
ebus:
2141
erosion_size: 2
2242
blur_size: 30

image-filtering/include/image_filtering/lib/filters/all_filters.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22
#define IMAGE_FILTERING__LIB__FILTERS__ALL_FILTERS_HPP_
33

44
#include "image_filtering/lib/filters/binary_threshold.hpp"
5+
#include "image_filtering/lib/filters/clahe_lab.hpp"
56
#include "image_filtering/lib/filters/dilation.hpp"
67
#include "image_filtering/lib/filters/ebus.hpp"
78
#include "image_filtering/lib/filters/erosion.hpp"
9+
#include "image_filtering/lib/filters/fixed_gain.hpp"
810
#include "image_filtering/lib/filters/flip.hpp"
11+
#include "image_filtering/lib/filters/gray_world_white_balancing.hpp"
912
#include "image_filtering/lib/filters/median_binary.hpp"
1013
#include "image_filtering/lib/filters/no_filter.hpp"
1114
#include "image_filtering/lib/filters/otsu.hpp"
1215
#include "image_filtering/lib/filters/overlap.hpp"
1316
#include "image_filtering/lib/filters/remove_grid.hpp"
17+
#include "image_filtering/lib/filters/shades_of_gray.hpp"
1418
#include "image_filtering/lib/filters/sharpening.hpp"
1519
#include "image_filtering/lib/filters/temporal_noise.hpp"
20+
#include "image_filtering/lib/filters/underwater_red_compensation.hpp"
1621
#include "image_filtering/lib/filters/unsharpening.hpp"
1722
#include "image_filtering/lib/filters/white_balancing.hpp"
23+
#include "image_filtering/lib/filters/white_patch.hpp"
1824

1925
#endif // IMAGE_FILTERING__LIB__FILTERS__ALL_FILTERS_HPP_
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#ifndef IMAGE_FILTERING__LIB__FILTERS__CLAHE_LAB_HPP_
2+
#define IMAGE_FILTERING__LIB__FILTERS__CLAHE_LAB_HPP_
3+
4+
#include <opencv2/imgproc.hpp>
5+
#include <vector>
6+
#include "abstract_filter_class.hpp"
7+
/////////////////////////////
8+
// CLAHE on LAB color space
9+
/////////////////////////////
10+
// Contrast Limited Adaptive Histogram Equalization applied only to the L
11+
// (lightness) channel of the CIELAB color space. Working in LAB keeps the
12+
// chrominance (a, b) untouched, so local contrast/visibility is boosted without
13+
// shifting hues - useful for hazy, low-contrast underwater imagery.
14+
namespace vortex::image_filtering {
15+
struct ClaheLabParams {
16+
// Threshold for contrast limiting. Higher values give more contrast (and
17+
// more amplified noise).
18+
double clip_limit;
19+
// Number of tiles per row/column. The image is divided into a
20+
// tile_grid_size x tile_grid_size grid and equalized per tile.
21+
int tile_grid_size;
22+
};
23+
24+
class ClaheLab : public Filter {
25+
public:
26+
explicit ClaheLab(ClaheLabParams params) : filter_params_(params) {}
27+
void apply_filter(const cv::Mat& original,
28+
cv::Mat& filtered) const override;
29+
30+
private:
31+
ClaheLabParams filter_params_;
32+
};
33+
34+
inline void ClaheLab::apply_filter(const cv::Mat& original,
35+
cv::Mat& filtered) const {
36+
cv::Ptr<cv::CLAHE> clahe =
37+
cv::createCLAHE(this->filter_params_.clip_limit,
38+
cv::Size(this->filter_params_.tile_grid_size,
39+
this->filter_params_.tile_grid_size));
40+
41+
// Grayscale input has no color space to convert; equalize directly.
42+
if (original.channels() == 1) {
43+
clahe->apply(original, filtered);
44+
return;
45+
}
46+
47+
// The node delivers images in RGB order (input_encoding: rgb8).
48+
cv::Mat lab;
49+
cv::cvtColor(original, lab, cv::COLOR_RGB2Lab);
50+
51+
std::vector<cv::Mat> lab_channels;
52+
cv::split(lab, lab_channels);
53+
54+
clahe->apply(lab_channels[0], lab_channels[0]);
55+
56+
cv::merge(lab_channels, lab);
57+
cv::cvtColor(lab, filtered, cv::COLOR_Lab2RGB);
58+
}
59+
} // namespace vortex::image_filtering
60+
#endif // IMAGE_FILTERING__LIB__FILTERS__CLAHE_LAB_HPP_
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#ifndef IMAGE_FILTERING__LIB__FILTERS__FIXED_GAIN_HPP_
2+
#define IMAGE_FILTERING__LIB__FILTERS__FIXED_GAIN_HPP_
3+
4+
#include <opencv2/imgproc.hpp>
5+
#include <vector>
6+
#include "abstract_filter_class.hpp"
7+
/////////////////////////////
8+
// Fixed Gain White Balance
9+
/////////////////////////////
10+
// Applies constant, manually-calibrated per-channel gains (and an optional
11+
// brightness offset). Unlike the adaptive methods, the correction does NOT
12+
// depend on scene content, so the output colour profile is perfectly stable
13+
// frame-to-frame. Calibrate the gains once for your water/lighting (e.g. so a
14+
// known grey/white reference reads neutral) and the restored colours stay
15+
// consistent - ideal for matching a fixed training distribution.
16+
// out_c = gain_c * in_c + offset
17+
namespace vortex::image_filtering {
18+
struct FixedGainParams {
19+
// Per-channel multipliers, in RGB order (matches the node's rgb8 encoding).
20+
double gain_r;
21+
double gain_g;
22+
double gain_b;
23+
// Constant added to every channel after scaling, in 8-bit units [0, 255].
24+
double offset;
25+
};
26+
27+
class FixedGain : public Filter {
28+
public:
29+
explicit FixedGain(FixedGainParams params) : filter_params_(params) {}
30+
void apply_filter(const cv::Mat& original,
31+
cv::Mat& filtered) const override;
32+
33+
private:
34+
FixedGainParams filter_params_;
35+
};
36+
37+
inline void FixedGain::apply_filter(const cv::Mat& original,
38+
cv::Mat& filtered) const {
39+
if (original.channels() != 3) {
40+
original.copyTo(filtered);
41+
return;
42+
}
43+
44+
cv::Mat img;
45+
original.convertTo(img, CV_32F);
46+
47+
// Channel order is RGB (the node delivers rgb8): 0 = R, 1 = G, 2 = B.
48+
std::vector<cv::Mat> channels;
49+
cv::split(img, channels);
50+
51+
const double offset = this->filter_params_.offset;
52+
channels[0] = channels[0] * this->filter_params_.gain_r + offset;
53+
channels[1] = channels[1] * this->filter_params_.gain_g + offset;
54+
channels[2] = channels[2] * this->filter_params_.gain_b + offset;
55+
56+
cv::merge(channels, img);
57+
58+
// convertTo saturates to the output type's range (e.g. [0, 255] for CV_8U).
59+
img.convertTo(filtered, original.type());
60+
}
61+
} // namespace vortex::image_filtering
62+
#endif // IMAGE_FILTERING__LIB__FILTERS__FIXED_GAIN_HPP_
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#ifndef IMAGE_FILTERING__LIB__FILTERS__GRAY_WORLD_WHITE_BALANCING_HPP_
2+
#define IMAGE_FILTERING__LIB__FILTERS__GRAY_WORLD_WHITE_BALANCING_HPP_
3+
4+
#include <algorithm>
5+
#include <opencv2/imgproc.hpp>
6+
#include <vector>
7+
#include "abstract_filter_class.hpp"
8+
/////////////////////////////
9+
// Gray World White Balance
10+
/////////////////////////////
11+
// Assumes the average colour of the scene is gray and scales each channel to
12+
// enforce it. Tends to remove the global colour cast (e.g. the blue/green tint
13+
// of underwater imagery) more aggressively than SimpleWB.
14+
//
15+
// This is a robust variant of the classic Gray World algorithm, tailored for
16+
// the extreme, near-uniform colour casts seen in underwater footage where the
17+
// stock OpenCV GrayworldWB is unstable:
18+
// * Channel gains are computed over the WHOLE frame (no saturation cutoff),
19+
// so the result does not flicker as scene brightness jitters frame-to-frame
20+
// across a threshold.
21+
// * Gains are clamped to [1/max_gain, max_gain] so a degenerate frame cannot
22+
// collapse the image to black (or blow it out to white).
23+
// * Overall luminance is preserved, so correcting the colour cast does not
24+
// darken the image.
25+
namespace vortex::image_filtering {
26+
struct GrayWorldWhiteBalanceParams {
27+
// Upper bound on any per-channel gain (and lower bound 1/max_gain). Caps
28+
// how aggressively a weak channel is amplified; the main guard against the
29+
// black/white flicker on degenerate frames. Typical range 2-6.
30+
double max_gain;
31+
};
32+
33+
class GrayWorldWhiteBalance : public Filter {
34+
public:
35+
explicit GrayWorldWhiteBalance(GrayWorldWhiteBalanceParams params)
36+
: filter_params_(params) {}
37+
void apply_filter(const cv::Mat& original,
38+
cv::Mat& filtered) const override;
39+
40+
private:
41+
GrayWorldWhiteBalanceParams filter_params_;
42+
};
43+
44+
inline void GrayWorldWhiteBalance::apply_filter(const cv::Mat& original,
45+
cv::Mat& filtered) const {
46+
if (original.channels() != 3) {
47+
original.copyTo(filtered);
48+
return;
49+
}
50+
51+
cv::Mat img;
52+
original.convertTo(img, CV_32F);
53+
54+
// Per-channel means over the whole frame (stable frame-to-frame).
55+
const cv::Scalar means = cv::mean(img);
56+
const double mean_gray = (means[0] + means[1] + means[2]) / 3.0;
57+
58+
const double eps = 1e-6;
59+
const double max_gain = std::max(this->filter_params_.max_gain, 1.0);
60+
const double min_gain = 1.0 / max_gain;
61+
62+
std::vector<cv::Mat> channels;
63+
cv::split(img, channels);
64+
65+
for (int c = 0; c < 3; ++c) {
66+
double gain = mean_gray / std::max(means[c], eps);
67+
gain = std::clamp(gain, min_gain, max_gain);
68+
channels[c] *= gain;
69+
}
70+
71+
cv::merge(channels, img);
72+
73+
// Preserve overall luminance: rescale so the post-correction mean matches
74+
// the input mean, so fixing the colour cast doesn't darken the image.
75+
const cv::Scalar new_means = cv::mean(img);
76+
const double new_mean_gray =
77+
(new_means[0] + new_means[1] + new_means[2]) / 3.0;
78+
if (new_mean_gray > eps) {
79+
img *= mean_gray / new_mean_gray;
80+
}
81+
82+
img.convertTo(filtered, original.type());
83+
}
84+
} // namespace vortex::image_filtering
85+
#endif // IMAGE_FILTERING__LIB__FILTERS__GRAY_WORLD_WHITE_BALANCING_HPP_
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#ifndef IMAGE_FILTERING__LIB__FILTERS__SHADES_OF_GRAY_HPP_
2+
#define IMAGE_FILTERING__LIB__FILTERS__SHADES_OF_GRAY_HPP_
3+
4+
#include <algorithm>
5+
#include <cmath>
6+
#include <opencv2/imgproc.hpp>
7+
#include <vector>
8+
#include "abstract_filter_class.hpp"
9+
/////////////////////////////
10+
// Shades of Gray
11+
/////////////////////////////
12+
// Generalised Gray-World colour constancy (Finlayson & Trezzi, 2004). The
13+
// illuminant of each channel is estimated with a Minkowski p-norm:
14+
// illum_c = ( mean( I_c^p ) )^(1/p)
15+
// p = 1 -> classic Gray-World (channel mean)
16+
// p -> inf -> White-Patch / Max-RGB (channel max)
17+
// p = 6 -> common sweet spot, more robust than either extreme.
18+
// Channels are then von-Kries scaled so their illuminant estimates match.
19+
namespace vortex::image_filtering {
20+
struct ShadesOfGrayParams {
21+
// Minkowski norm exponent. 1 = Gray-World, larger leans toward White-Patch.
22+
double norm_p;
23+
// Clamp on per-channel gain ([1/max_gain, max_gain]) for stability.
24+
double max_gain;
25+
};
26+
27+
class ShadesOfGray : public Filter {
28+
public:
29+
explicit ShadesOfGray(ShadesOfGrayParams params) : filter_params_(params) {}
30+
void apply_filter(const cv::Mat& original,
31+
cv::Mat& filtered) const override;
32+
33+
private:
34+
ShadesOfGrayParams filter_params_;
35+
};
36+
37+
inline void ShadesOfGray::apply_filter(const cv::Mat& original,
38+
cv::Mat& filtered) const {
39+
if (original.channels() != 3) {
40+
original.copyTo(filtered);
41+
return;
42+
}
43+
44+
cv::Mat img;
45+
original.convertTo(img, CV_32F);
46+
47+
const double p = std::max(this->filter_params_.norm_p, 1.0);
48+
const double eps = 1e-6;
49+
50+
std::vector<cv::Mat> channels;
51+
cv::split(img, channels);
52+
53+
// Per-channel Minkowski-norm illuminant estimate.
54+
double illum[3];
55+
for (int c = 0; c < 3; ++c) {
56+
cv::Mat powered;
57+
cv::pow(channels[c], p, powered);
58+
illum[c] = std::pow(std::max(cv::mean(powered)[0], eps), 1.0 / p);
59+
}
60+
const double illum_gray = (illum[0] + illum[1] + illum[2]) / 3.0;
61+
62+
const double max_gain = std::max(this->filter_params_.max_gain, 1.0);
63+
const double min_gain = 1.0 / max_gain;
64+
65+
for (int c = 0; c < 3; ++c) {
66+
double gain = illum_gray / std::max(illum[c], eps);
67+
gain = std::clamp(gain, min_gain, max_gain);
68+
channels[c] *= gain;
69+
}
70+
71+
cv::merge(channels, img);
72+
73+
// Preserve overall luminance so the colour correction doesn't dim/brighten.
74+
const cv::Scalar in_mean = cv::mean(original);
75+
const double in_gray = (in_mean[0] + in_mean[1] + in_mean[2]) / 3.0;
76+
const cv::Scalar out_mean = cv::mean(img);
77+
const double out_gray = (out_mean[0] + out_mean[1] + out_mean[2]) / 3.0;
78+
if (out_gray > eps) {
79+
img *= in_gray / out_gray;
80+
}
81+
82+
img.convertTo(filtered, original.type());
83+
}
84+
} // namespace vortex::image_filtering
85+
#endif // IMAGE_FILTERING__LIB__FILTERS__SHADES_OF_GRAY_HPP_

0 commit comments

Comments
 (0)