Boost GIL


adaptive_histogram_equalization.hpp
1 //
2 // Copyright 2020 Debabrata Mandal <mandaldebabrata123@gmail.com>
3 //
4 // Use, modification and distribution are subject to the Boost Software License,
5 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt)
7 //
8 
9 #ifndef BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP
10 #define BOOST_GIL_IMAGE_PROCESSING_ADAPTIVE_HISTOGRAM_EQUALIZATION_HPP
11 
12 #include <boost/gil/algorithm.hpp>
13 #include <boost/gil/histogram.hpp>
14 #include <boost/gil/image.hpp>
15 #include <boost/gil/image_processing/histogram_equalization.hpp>
16 #include <boost/gil/image_view_factory.hpp>
17 
18 #include <cmath>
19 #include <map>
20 #include <vector>
21 
22 namespace boost { namespace gil {
23 
36 
37 namespace detail {
38 
41 
48 template <typename SrcHist>
49 double actual_clip_limit(SrcHist const& src_hist, double cliplimit = 0.03)
50 {
51  double epsilon = 1.0;
52  using value_t = typename SrcHist::value_type;
53  double sum = src_hist.sum();
54  std::size_t num_bins = src_hist.size();
55 
56  cliplimit = sum * cliplimit;
57  long low = 0, high = cliplimit, middle = low;
58  while (high - low >= 1)
59  {
60  middle = (low + high + 1) >> 1;
61  long excess = 0;
62  std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
63  if (v.second > middle)
64  excess += v.second - middle;
65  });
66  if (std::abs(excess - (cliplimit - middle) * num_bins) < epsilon)
67  break;
68  else if (excess > (cliplimit - middle) * num_bins)
69  high = middle - 1;
70  else
71  low = middle + 1;
72  }
73  return middle / sum;
74 }
75 
83 template <typename SrcHist, typename DstHist>
84 void clip_and_redistribute(SrcHist const& src_hist, DstHist& dst_hist, double clip_limit = 0.03)
85 {
86  using value_t = typename SrcHist::value_type;
87  double sum = src_hist.sum();
88  double actual_clip_value = detail::actual_clip_limit(src_hist, clip_limit);
89  // double actual_clip_value = clip_limit;
90  long actual_clip_limit = actual_clip_value * sum;
91  double excess = 0;
92  std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
93  if (v.second > actual_clip_limit)
94  excess += v.second - actual_clip_limit;
95  });
96  std::for_each(src_hist.begin(), src_hist.end(), [&](value_t const& v) {
97  if (v.second >= actual_clip_limit)
98  dst_hist[dst_hist.key_from_tuple(v.first)] = clip_limit * sum;
99  else
100  dst_hist[dst_hist.key_from_tuple(v.first)] = v.second + excess / src_hist.size();
101  });
102  long rem = long(excess) % src_hist.size();
103  if (rem == 0)
104  return;
105  long period = round(src_hist.size() / rem);
106  std::size_t index = 0;
107  while (rem)
108  {
109  if (dst_hist(index) >= clip_limit * sum)
110  {
111  index = (index + 1) % src_hist.size();
112  }
113  dst_hist(index)++;
114  rem--;
115  index = (index + period) % src_hist.size();
116  }
117 }
118 
119 } // namespace detail
120 
121 
137 template <typename SrcView, typename DstView>
138 void non_overlapping_interpolated_clahe(
139  SrcView const& src_view,
140  DstView const& dst_view,
141  std::ptrdiff_t tile_width_x = 20,
142  std::ptrdiff_t tile_width_y = 20,
143  double clip_limit = 0.03,
144  std::size_t bin_width = 1.0,
145  bool mask = false,
146  std::vector<std::vector<bool>> src_mask = {})
147 {
148  gil_function_requires<ImageViewConcept<SrcView>>();
149  gil_function_requires<MutableImageViewConcept<DstView>>();
150 
151  static_assert(
152  color_spaces_are_compatible<
153  typename color_space_type<SrcView>::type,
154  typename color_space_type<DstView>::type>::value,
155  "Source and destination views must have same color space");
156 
157  using source_channel_t = typename channel_type<SrcView>::type;
158  using dst_channel_t = typename channel_type<DstView>::type;
159  using coord_t = typename SrcView::x_coord_t;
160 
161  std::size_t const channels = num_channels<SrcView>::value;
162  coord_t const width = src_view.width();
163  coord_t const height = src_view.height();
164 
165  // Find control points
166 
167  std::vector<coord_t> sample_x;
168  coord_t sample_x1 = tile_width_x / 2;
169  coord_t sample_y1 = tile_width_y / 2;
170 
171  auto extend_left = tile_width_x;
172  auto extend_top = tile_width_y;
173  auto extend_right = (tile_width_x - width % tile_width_x) % tile_width_x + tile_width_x;
174  auto extend_bottom = (tile_width_y - height % tile_width_y) % tile_width_y + tile_width_y;
175 
176  auto new_width = width + extend_left + extend_right;
177  auto new_height = height + extend_top + extend_bottom;
178 
179  image<typename SrcView::value_type> padded_img(new_width, new_height);
180 
181  auto top_left_x = tile_width_x;
182  auto top_left_y = tile_width_y;
183  auto bottom_right_x = tile_width_x + width;
184  auto bottom_right_y = tile_width_y + height;
185 
186  copy_pixels(src_view, subimage_view(view(padded_img), top_left_x, top_left_y, width, height));
187 
188  for (std::size_t k = 0; k < channels; k++)
189  {
190  std::vector<histogram<source_channel_t>> prev_row(new_width / tile_width_x),
191  next_row((new_width / tile_width_x));
192  std::vector<std::map<source_channel_t, source_channel_t>> prev_map(
193  new_width / tile_width_x),
194  next_map((new_width / tile_width_x));
195 
196  coord_t prev = 0, next = 1;
197  auto channel_view = nth_channel_view(view(padded_img), k);
198 
199  for (std::ptrdiff_t i = top_left_y; i < bottom_right_y; ++i)
200  {
201  if ((i - sample_y1) / tile_width_y >= next || i == top_left_y)
202  {
203  if (i != top_left_y)
204  {
205  prev = next;
206  next++;
207  }
208  prev_row = next_row;
209  prev_map = next_map;
210  for (std::ptrdiff_t j = sample_x1; j < new_width; j += tile_width_x)
211  {
212  auto img_view = subimage_view(
213  channel_view, j - sample_x1, next * tile_width_y,
214  std::max<int>(
215  std::min<int>(tile_width_x + j - sample_x1, bottom_right_x) -
216  (j - sample_x1),
217  0),
218  std::max<int>(
219  std::min<int>((next + 1) * tile_width_y, bottom_right_y) -
220  next * tile_width_y,
221  0));
222 
223  fill_histogram(
224  img_view, next_row[(j - sample_x1) / tile_width_x], bin_width, false,
225  false);
226 
227  detail::clip_and_redistribute(
228  next_row[(j - sample_x1) / tile_width_x],
229  next_row[(j - sample_x1) / tile_width_x], clip_limit);
230 
231  next_map[(j - sample_x1) / tile_width_x] =
232  histogram_equalization(next_row[(j - sample_x1) / tile_width_x]);
233  }
234  }
235  bool prev_row_mask = 1, next_row_mask = 1;
236  if (prev == 0)
237  prev_row_mask = false;
238  else if (next + 1 == new_height / tile_width_y)
239  next_row_mask = false;
240  for (std::ptrdiff_t j = top_left_x; j < bottom_right_x; ++j)
241  {
242  bool prev_col_mask = true, next_col_mask = true;
243  if ((j - sample_x1) / tile_width_x == 0)
244  prev_col_mask = false;
245  else if ((j - sample_x1) / tile_width_x + 1 == new_width / tile_width_x - 1)
246  next_col_mask = false;
247 
248  // Bilinear interpolation
249  point_t top_left(
250  (j - sample_x1) / tile_width_x * tile_width_x + sample_x1,
251  prev * tile_width_y + sample_y1);
252  point_t top_right(top_left.x + tile_width_x, top_left.y);
253  point_t bottom_left(top_left.x, top_left.y + tile_width_y);
254  point_t bottom_right(top_left.x + tile_width_x, top_left.y + tile_width_y);
255 
256  long double x_diff = top_right.x - top_left.x;
257  long double y_diff = bottom_left.y - top_left.y;
258 
259  long double x1 = (j - top_left.x) / x_diff;
260  long double x2 = (top_right.x - j) / x_diff;
261  long double y1 = (i - top_left.y) / y_diff;
262  long double y2 = (bottom_left.y - i) / y_diff;
263 
264  if (prev_row_mask == 0)
265  y1 = 1;
266  else if (next_row_mask == 0)
267  y2 = 1;
268  if (prev_col_mask == 0)
269  x1 = 1;
270  else if (next_col_mask == 0)
271  x2 = 1;
272 
273  long double numerator =
274  ((prev_row_mask & prev_col_mask) * x2 *
275  prev_map[(top_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
276  (prev_row_mask & next_col_mask) * x1 *
277  prev_map[(top_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
278  y2 +
279  ((next_row_mask & prev_col_mask) * x2 *
280  next_map[(bottom_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
281  (next_row_mask & next_col_mask) * x1 *
282  next_map[(bottom_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
283  y1;
284 
285  if (mask && !src_mask[i - top_left_y][j - top_left_x])
286  {
287  dst_view(j - top_left_x, i - top_left_y) =
288  channel_convert<dst_channel_t>(
289  static_cast<source_channel_t>(channel_view(i, j)));
290  }
291  else
292  {
293  dst_view(j - top_left_x, i - top_left_y) =
294  channel_convert<dst_channel_t>(static_cast<source_channel_t>(numerator));
295  }
296  }
297  }
298  }
299 }
300 
301 }} //namespace boost::gil
302 
303 #endif
boost::gil::nth_channel_view
nth_channel_view_type< View >::type nth_channel_view(const View &src, int n)
Definition: image_view_factory.hpp:418
boost::gil::copy_pixels
BOOST_FORCEINLINE void copy_pixels(const View1 &src, const View2 &dst)
std::copy for image views
Definition: algorithm.hpp:288
boost::gil::view
const image< Pixel, IsPlanar, Alloc >::view_t & view(image< Pixel, IsPlanar, Alloc > &img)
Returns the non-constant-pixel view of an image.
Definition: image.hpp:549
boost::gil::subimage_view
View subimage_view(View const &src, typename View::point_t const &topleft, typename View::point_t const &dimensions)
Definition: image_view_factory.hpp:254