Visual Servoing Platform  version 3.6.1 under development (2024-04-25)
vpCLAHE.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2023 by Inria. All rights reserved.
4  *
5  * This software is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * See the file LICENSE.txt at the root directory of this source
10  * distribution for additional information about the GNU GPL.
11  *
12  * For using ViSP with software that can not be combined with the GNU
13  * GPL, please contact Inria about acquiring a ViSP Professional
14  * Edition License.
15  *
16  * See https://visp.inria.fr for more information.
17  *
18  * This software was developed at:
19  * Inria Rennes - Bretagne Atlantique
20  * Campus Universitaire de Beaulieu
21  * 35042 Rennes Cedex
22  * France
23  *
24  * If you have questions regarding the use of this file, please contact
25  * Inria at visp@inria.fr
26  *
27  * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28  * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29  *
30  * Description:
31  * CLAHE (Contrast Limited Adaptive Histogram Equalization) algorithm.
32  */
79 #include <visp3/core/vpImageConvert.h>
80 #include <visp3/imgproc/vpImgproc.h>
81 
82 namespace
83 {
84 int fastRound(float value) { return static_cast<int>(value + 0.5f); }
85 
86 void clipHistogram(const std::vector<int> &hist, std::vector<int> &clippedHist, int limit)
87 {
88  clippedHist = hist;
89  int clippedEntries = 0, clippedEntriesBefore = 0;
90  int histlength = static_cast<int>(hist.size());
91 
92  do {
93  clippedEntriesBefore = clippedEntries;
94  clippedEntries = 0;
95  for (int i = 0; i < histlength; ++i) {
96  int d = clippedHist[i] - limit;
97  if (d > 0) {
98  clippedEntries += d;
99  clippedHist[i] = limit;
100  }
101  }
102 
103  int d = clippedEntries / histlength;
104  int m = clippedEntries % histlength;
105  for (int i = 0; i < histlength; ++i) {
106  clippedHist[i] += d;
107  }
108 
109  if (m != 0) {
110  int s = (histlength - 1) / m;
111  for (int i = s / 2; i < histlength; i += s) {
112  ++(clippedHist[i]);
113  }
114  }
115  } while (clippedEntries != clippedEntriesBefore);
116 }
117 
118 void createHistogram(int blockRadius, int bins, int blockXCenter, int blockYCenter, const vpImage<unsigned char> &I,
119  std::vector<int> &hist)
120 {
121  std::fill(hist.begin(), hist.end(), 0);
122 
123  int xMin = std::max<int>(0, blockXCenter - blockRadius);
124  int yMin = std::max<int>(0, blockYCenter - blockRadius);
125  int xMax = std::min<int>(static_cast<int>(I.getWidth()), blockXCenter + blockRadius + 1);
126  int yMax = std::min<int>(static_cast<int>(I.getHeight()), blockYCenter + blockRadius + 1);
127 
128  for (int y = yMin; y < yMax; ++y) {
129  for (int x = xMin; x < xMax; ++x) {
130  ++hist[fastRound((I[y][x] / 255.0f) * bins)];
131  }
132  }
133 }
134 
135 std::vector<float> createTransfer(const std::vector<int> &hist, int limit, std::vector<int> &cdfs)
136 {
137  clipHistogram(hist, cdfs, limit);
138  int hMin = static_cast<int>(hist.size()) - 1;
139 
140  for (int i = 0; i < hMin; ++i) {
141  if (cdfs[i] != 0) {
142  hMin = i;
143  }
144  }
145  int cdf = 0;
146  int hist_size = static_cast<int>(hist.size());
147  for (int i = hMin; i < hist_size; ++i) {
148  cdf += cdfs[i];
149  cdfs[i] = cdf;
150  }
151 
152  int cdfMin = cdfs[hMin];
153  int cdfMax = cdfs[hist.size() - 1];
154 
155  std::vector<float> transfer(hist.size());
156  int transfer_size = static_cast<int>(transfer.size());
157  for (int i = 0; i < transfer_size; ++i) {
158  transfer[i] = (cdfs[i] - cdfMin) / static_cast<float>(cdfMax - cdfMin);
159  }
160 
161  return transfer;
162 }
163 
164 float transferValue(int v, std::vector<int> &clippedHist)
165 {
166  int clippedHistLength = static_cast<int>(clippedHist.size());
167  int hMin = clippedHistLength - 1;
168  for (int i = 0; i < hMin; ++i) {
169  if (clippedHist[i] != 0) {
170  hMin = i;
171  }
172  }
173 
174  int cdf = 0;
175  for (int i = hMin; i <= v; ++i) {
176  cdf += clippedHist[i];
177  }
178 
179  int cdfMax = cdf;
180  for (int i = v + 1; i < clippedHistLength; ++i) {
181  cdfMax += clippedHist[i];
182  }
183 
184  int cdfMin = clippedHist[hMin];
185  return (cdf - cdfMin) / static_cast<float>(cdfMax - cdfMin);
186 }
187 
188 float transferValue(int v, const std::vector<int> &hist, std::vector<int> &clippedHist, int limit)
189 {
190  clipHistogram(hist, clippedHist, limit);
191 
192  return transferValue(v, clippedHist);
193 }
194 } // namespace
195 
196 namespace vp
197 {
198 void clahe(const vpImage<unsigned char> &I1, vpImage<unsigned char> &I2, int blockRadius, int bins, float slope, bool fast)
199 {
200  if (blockRadius < 0) {
201  std::cerr << "Error: blockRadius < 0!" << std::endl;
202  return;
203  }
204 
205  if ((bins < 0) || (bins > 256)) {
206  std::cerr << "Error: (bins < 0 || bins > 256)!" << std::endl;
207  return;
208  }
209 
210  if ((static_cast<unsigned int>((2 * blockRadius) + 1) > I1.getWidth()) || (static_cast<unsigned int>((2 * blockRadius) + 1) > I1.getHeight())) {
211  std::cerr << "Error: (unsigned int) (2*blockRadius+1) > I1.getWidth() || "
212  "(unsigned int) (2*blockRadius+1) > I1.getHeight()!"
213  << std::endl;
214  return;
215  }
216 
217  I2.resize(I1.getHeight(), I1.getWidth());
218 
219  if (fast) {
220  int blockSize = (2 * blockRadius) + 1;
221  int limit = static_cast<int>(((slope * blockSize * blockSize) / bins) + 0.5);
222 
223  /* div */
224  int nc = I1.getWidth() / blockSize;
225  int nr = I1.getHeight() / blockSize;
226 
227  /* % */
228  int cm = I1.getWidth() - (nc * blockSize);
229  std::vector<int> cs;
230 
231  switch (cm) {
232  case 0:
233  cs.resize(nc);
234  for (int i = 0; i < nc; ++i) {
235  cs[i] = (i * blockSize) + blockRadius + 1;
236  }
237  break;
238 
239  case 1:
240  cs.resize(nc + 1);
241  for (int i = 0; i < nc; ++i) {
242  cs[i] = (i * blockSize) + blockRadius + 1;
243  }
244  cs[nc] = I1.getWidth() - blockRadius - 1;
245  break;
246 
247  default:
248  cs.resize(nc + 2);
249  cs[0] = blockRadius + 1;
250  for (int i = 0; i < nc; ++i) {
251  cs[i + 1] = (i * blockSize) + blockRadius + 1 + (cm / 2);
252  }
253  cs[nc + 1] = I1.getWidth() - blockRadius - 1;
254  }
255 
256  int rm = I1.getHeight() - (nr * blockSize);
257  std::vector<int> rs;
258 
259  switch (rm) {
260  case 0:
261  rs.resize(static_cast<size_t>(nr));
262  for (int i = 0; i < nr; ++i) {
263  rs[i] = (i * blockSize) + blockRadius + 1;
264  }
265  break;
266 
267  case 1:
268  rs.resize(static_cast<size_t>(nr + 1));
269  for (int i = 0; i < nr; ++i) {
270  rs[i] = (i * blockSize) + blockRadius + 1;
271  }
272  rs[nr] = I1.getHeight() - blockRadius - 1;
273  break;
274 
275  default:
276  rs.resize(static_cast<size_t>(nr + 2));
277  rs[0] = blockRadius + 1;
278  for (int i = 0; i < nr; ++i) {
279  rs[i + 1] = (i * blockSize) + blockRadius + 1 + (rm / 2);
280  }
281  rs[nr + 1] = I1.getHeight() - blockRadius - 1;
282  }
283 
284  std::vector<int> hist(static_cast<size_t>(bins + 1));
285  std::vector<int> cdfs(static_cast<size_t>(bins + 1));
286  std::vector<float> tl;
287  std::vector<float> tr;
288  std::vector<float> br;
289  std::vector<float> bl;
290 
291  int rs_size = static_cast<int>(rs.size());
292  for (int r = 0; r <= rs_size; ++r) {
293  int r0 = std::max<int>(0, r - 1);
294  int r1 = std::min<int>(static_cast<int>(rs.size()) - 1, r);
295  int dr = rs[r1] - rs[r0];
296 
297  createHistogram(blockRadius, bins, cs[0], rs[r0], I1, hist);
298  tr = createTransfer(hist, limit, cdfs);
299  if (r0 == r1) {
300  br = tr;
301  }
302  else {
303  createHistogram(blockRadius, bins, cs[0], rs[r1], I1, hist);
304  br = createTransfer(hist, limit, cdfs);
305  }
306 
307  int yMin = (r == 0 ? 0 : rs[r0]);
308  int yMax = (r < static_cast<int>(rs.size()) ? rs[r1] : I1.getHeight());
309 
310  int cs_size = static_cast<int>(cs.size());
311  for (int c = 0; c <= cs_size; ++c) {
312  int c0 = std::max<int>(0, c - 1);
313  int c1 = std::min<int>(static_cast<int>(cs.size()) - 1, c);
314  int dc = cs[c1] - cs[c0];
315 
316  tl = tr;
317  bl = br;
318 
319  if (c0 != c1) {
320  createHistogram(blockRadius, bins, cs[c1], rs[r0], I1, hist);
321  tr = createTransfer(hist, limit, cdfs);
322  if (r0 == r1) {
323  br = tr;
324  }
325  else {
326  createHistogram(blockRadius, bins, cs[c1], rs[r1], I1, hist);
327  br = createTransfer(hist, limit, cdfs);
328  }
329  }
330 
331  int xMin = (c == 0 ? 0 : cs[c0]);
332  int xMax = (c < static_cast<int>(cs.size()) ? cs[c1] : I1.getWidth());
333  for (int y = yMin; y < yMax; ++y) {
334  float wy = static_cast<float>(rs[r1] - y) / dr;
335 
336  for (int x = xMin; x < xMax; ++x) {
337  float wx = static_cast<float>(cs[c1] - x) / dc;
338  int v = fastRound((I1[y][x] / 255.0f) * bins);
339  float t00 = tl[v];
340  float t01 = tr[v];
341  float t10 = bl[v];
342  float t11 = br[v];
343  float t0 = 0.0f, t1 = 0.0f;
344 
345  if (c0 == c1) {
346  t0 = t00;
347  t1 = t10;
348  }
349  else {
350  t0 = (wx * t00) + ((1.0f - wx) * t01);
351  t1 = (wx * t10) + ((1.0f - wx) * t11);
352  }
353 
354  float t = (r0 == r1) ? t0 : (wy * t0) + ((1.0f - wy) * t1);
355  I2[y][x] = std::max<unsigned char>(0, std::min<unsigned char>(255, fastRound(t * 255.0f)));
356  }
357  }
358  }
359  }
360  }
361  else {
362  std::vector<int> hist(bins + 1), prev_hist(bins + 1);
363  std::vector<int> clippedHist(bins + 1);
364 
365  bool first = true;
366  int xMin0 = 0;
367  int xMax0 = std::min<int>(static_cast<int>(I1.getWidth()), blockRadius);
368 
369  int i1_height = static_cast<int>(I1.getHeight());
370  for (int y = 0; y < i1_height; ++y) {
371  int yMin = std::max<int>(0, y - static_cast<int>(blockRadius));
372  int yMax = std::min<int>(static_cast<int>(I1.getHeight()), y + blockRadius + 1);
373  int h = yMax - yMin;
374 
375 #if 0
376  std::fill(hist.begin(), hist.end(), 0);
377  // Compute histogram for the current block
378  for (int yi = yMin; yi < yMax; ++yi) {
379  for (int xi = xMin0; xi < xMax0; ++xi) {
380  ++hist[fastRound(I1[yi][xi] / 255.0f * bins)];
381  }
382  }
383 #else
384  if (first) {
385  first = false;
386  // Compute histogram for the block at (0,0)
387  for (int yi = yMin; yi < yMax; ++yi) {
388  for (int xi = xMin0; xi < xMax0; ++xi) {
389  ++hist[fastRound((I1[yi][xi] / 255.0f) * bins)];
390  }
391  }
392  }
393  else {
394  hist = prev_hist;
395 
396  if (yMin > 0) {
397  int yMin1 = yMin - 1;
398  // Sliding histogram, remove top
399  for (int xi = xMin0; xi < xMax0; ++xi) {
400  --hist[fastRound((I1[yMin1][xi] / 255.0f) * bins)];
401  }
402  }
403 
404  if ((y + blockRadius) < static_cast<int>(I1.getHeight())) {
405  int yMax1 = yMax - 1;
406  // Sliding histogram, add bottom
407  for (int xi = xMin0; xi < xMax0; ++xi) {
408  ++hist[fastRound((I1[yMax1][xi] / 255.0f) * bins)];
409  }
410  }
411  }
412  prev_hist = hist;
413 #endif
414  int i1_width = static_cast<int>(I1.getWidth());
415  for (int x = 0; x < i1_width; ++x) {
416  int xMin = std::max<int>(0, x - static_cast<int>(blockRadius));
417  int xMax = x + blockRadius + 1;
418 
419  if (xMin > 0) {
420  int xMin1 = xMin - 1;
421  // Sliding histogram, remove left
422  for (int yi = yMin; yi < yMax; ++yi) {
423  --hist[fastRound((I1[yi][xMin1] / 255.0f) * bins)];
424  }
425  }
426 
427  if (xMax <= static_cast<int>(I1.getWidth())) {
428  int xMax1 = xMax - 1;
429  // Sliding histogram, add right
430  for (int yi = yMin; yi < yMax; ++yi) {
431  ++hist[fastRound((I1[yi][xMax1] / 255.0f) * bins)];
432  }
433  }
434 
435  int v = fastRound((I1[y][x] / 255.0f) * bins);
436  int w = std::min<int>(static_cast<int>(I1.getWidth()), xMax) - xMin;
437  int n = h * w;
438  int limit = static_cast<int>((slope * n) / bins + 0.5f);
439  I2[y][x] = fastRound(transferValue(v, hist, clippedHist, limit) * 255.0f);
440  }
441  }
442  }
443 }
444 
445 void clahe(const vpImage<vpRGBa> &I1, vpImage<vpRGBa> &I2, int blockRadius, int bins, float slope, bool fast)
446 {
447  // Split
452 
453  vpImageConvert::split(I1, &pR, &pG, &pB, &pa);
454 
455  // Apply CLAHE independently on RGB channels
456  vpImage<unsigned char> resR, resG, resB;
457  clahe(pR, resR, blockRadius, bins, slope, fast);
458  clahe(pG, resG, blockRadius, bins, slope, fast);
459  clahe(pB, resB, blockRadius, bins, slope, fast);
460 
461  I2.resize(I1.getHeight(), I1.getWidth());
462  unsigned int size = I2.getWidth() * I2.getHeight();
463  unsigned char *ptrStart = (unsigned char *)(I2.bitmap);
464  unsigned char *ptrEnd = ptrStart + (size * 4);
465  unsigned char *ptrCurrent = ptrStart;
466 
467  unsigned int cpt = 0;
468  while (ptrCurrent != ptrEnd) {
469  *ptrCurrent = resR.bitmap[cpt];
470  ++ptrCurrent;
471 
472  *ptrCurrent = resG.bitmap[cpt];
473  ++ptrCurrent;
474 
475  *ptrCurrent = resB.bitmap[cpt];
476  ++ptrCurrent;
477 
478  *ptrCurrent = pa.bitmap[cpt];
479  ++ptrCurrent;
480 
481  ++cpt;
482  }
483 }
484 };
static void split(const vpImage< vpRGBa > &src, vpImage< unsigned char > *pR, vpImage< unsigned char > *pG, vpImage< unsigned char > *pB, vpImage< unsigned char > *pa=nullptr)
unsigned int getWidth() const
Definition: vpImage.h:245
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:783
Type * bitmap
points toward the bitmap
Definition: vpImage.h:139
unsigned int getHeight() const
Definition: vpImage.h:184
VISP_EXPORT void clahe(const vpImage< unsigned char > &I1, vpImage< unsigned char > &I2, int blockRadius=150, int bins=256, float slope=3.0f, bool fast=true)
Definition: vpCLAHE.cpp:198