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::size_t tile_width_x = 20,
142  std::size_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_x2 = (tile_width_x + 1) / 2;
170  coord_t sample_y1 = tile_width_y / 2;
171  coord_t sample_y2 = (tile_width_y + 1) / 2;
172 
173  auto extend_left = tile_width_x;
174  auto extend_top = tile_width_y;
175  auto extend_right = (tile_width_x - width % tile_width_x) % tile_width_x + tile_width_x;
176  auto extend_bottom = (tile_width_y - height % tile_width_y) % tile_width_y + tile_width_y;
177 
178  auto new_width = width + extend_left + extend_right;
179  auto new_height = height + extend_top + extend_bottom;
180 
181  image<typename SrcView::value_type> padded_img(new_width, new_height);
182 
183  auto top_left_x = tile_width_x;
184  auto top_left_y = tile_width_y;
185  auto bottom_right_x = tile_width_x + width;
186  auto bottom_right_y = tile_width_y + height;
187 
188  copy_pixels(src_view, subimage_view(view(padded_img), top_left_x, top_left_y, width, height));
189 
190  for (std::size_t k = 0; k < channels; k++)
191  {
192  std::vector<histogram<source_channel_t>> prev_row(new_width / tile_width_x),
193  next_row((new_width / tile_width_x));
194  std::vector<std::map<source_channel_t, source_channel_t>> prev_map(
195  new_width / tile_width_x),
196  next_map((new_width / tile_width_x));
197 
198  coord_t prev = 0, next = 1;
199  auto channel_view = nth_channel_view(view(padded_img), k);
200 
201  for (std::ptrdiff_t i = top_left_y; i < bottom_right_y; ++i)
202  {
203  if ((i - sample_y1) / tile_width_y >= next || i == top_left_y)
204  {
205  if (i != top_left_y)
206  {
207  prev = next;
208  next++;
209  }
210  prev_row = next_row;
211  prev_map = next_map;
212  for (std::ptrdiff_t j = sample_x1; j < new_width; j += tile_width_x)
213  {
214  auto img_view = subimage_view(
215  channel_view, j - sample_x1, next * tile_width_y,
216  std::max<int>(
217  std::min<int>(tile_width_x + j - sample_x1, bottom_right_x) -
218  (j - sample_x1),
219  0),
220  std::max<int>(
221  std::min<int>((next + 1) * tile_width_y, bottom_right_y) -
222  next * tile_width_y,
223  0));
224 
225  fill_histogram(
226  img_view, next_row[(j - sample_x1) / tile_width_x], bin_width, false,
227  false);
228 
229  detail::clip_and_redistribute(
230  next_row[(j - sample_x1) / tile_width_x],
231  next_row[(j - sample_x1) / tile_width_x], clip_limit);
232 
233  next_map[(j - sample_x1) / tile_width_x] =
234  histogram_equalization(next_row[(j - sample_x1) / tile_width_x]);
235  }
236  }
237  bool prev_row_mask = 1, next_row_mask = 1;
238  if (prev == 0)
239  prev_row_mask = false;
240  else if (next + 1 == new_height / tile_width_y)
241  next_row_mask = false;
242  for (std::ptrdiff_t j = top_left_x; j < bottom_right_x; ++j)
243  {
244  bool prev_col_mask = true, next_col_mask = true;
245  if ((j - sample_x1) / tile_width_x == 0)
246  prev_col_mask = false;
247  else if ((j - sample_x1) / tile_width_x + 1 == new_width / tile_width_x - 1)
248  next_col_mask = false;
249 
250  // Bilinear interpolation
251  point_t top_left(
252  (j - sample_x1) / tile_width_x * tile_width_x + sample_x1,
253  prev * tile_width_y + sample_y1);
254  point_t top_right(top_left.x + tile_width_x, top_left.y);
255  point_t bottom_left(top_left.x, top_left.y + tile_width_y);
256  point_t bottom_right(top_left.x + tile_width_x, top_left.y + tile_width_y);
257 
258  long double x_diff = top_right.x - top_left.x;
259  long double y_diff = bottom_left.y - top_left.y;
260 
261  long double x1 = (j - top_left.x) / x_diff;
262  long double x2 = (top_right.x - j) / x_diff;
263  long double y1 = (i - top_left.y) / y_diff;
264  long double y2 = (bottom_left.y - i) / y_diff;
265 
266  if (prev_row_mask == 0)
267  y1 = 1;
268  else if (next_row_mask == 0)
269  y2 = 1;
270  if (prev_col_mask == 0)
271  x1 = 1;
272  else if (next_col_mask == 0)
273  x2 = 1;
274 
275  long double numerator =
276  ((prev_row_mask & prev_col_mask) * x2 *
277  prev_map[(top_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
278  (prev_row_mask & next_col_mask) * x1 *
279  prev_map[(top_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
280  y2 +
281  ((next_row_mask & prev_col_mask) * x2 *
282  next_map[(bottom_left.x - sample_x1) / tile_width_x][channel_view(j, i)] +
283  (next_row_mask & next_col_mask) * x1 *
284  next_map[(bottom_right.x - sample_x1) / tile_width_x][channel_view(j, i)]) *
285  y1;
286 
287  if (mask && !src_mask[i - top_left_y][j - top_left_x])
288  {
289  dst_view(j - top_left_x, i - top_left_y) =
290  channel_convert<dst_channel_t>(
291  static_cast<source_channel_t>(channel_view(i, j)));
292  }
293  else
294  {
295  dst_view(j - top_left_x, i - top_left_y) =
296  channel_convert<dst_channel_t>(static_cast<source_channel_t>(numerator));
297  }
298  }
299  }
300  }
301 }
302 
303 }} //namespace boost::gil
304 
305 #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:282
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:548
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