Visual Servoing Platform  version 3.6.1 under development (2024-12-17)
vpCLAHE.cpp
1 /*
2  * ViSP, open source Visual Servoing Platform software.
3  * Copyright (C) 2005 - 2024 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 VISP_NAMESPACE_NAME
83 {
84 
85 int fastRound(float value) { return static_cast<int>(value + 0.5f); }
86 
87 void clipHistogram(const std::vector<int> &hist, std::vector<int> &clippedHist, int limit)
88 {
89  clippedHist = hist;
90  int clippedEntries = 0, clippedEntriesBefore = 0;
91  int histlength = static_cast<int>(hist.size());
92 
93  do {
94  clippedEntriesBefore = clippedEntries;
95  clippedEntries = 0;
96  for (int i = 0; i < histlength; ++i) {
97  int d = clippedHist[i] - limit;
98  if (d > 0) {
99  clippedEntries += d;
100  clippedHist[i] = limit;
101  }
102  }
103 
104  int d = clippedEntries / histlength;
105  int m = clippedEntries % histlength;
106  for (int i = 0; i < histlength; ++i) {
107  clippedHist[i] += d;
108  }
109 
110  if (m != 0) {
111  int s = (histlength - 1) / m;
112  for (int i = s / 2; i < histlength; i += s) {
113  ++(clippedHist[i]);
114  }
115  }
116  } while (clippedEntries != clippedEntriesBefore);
117 }
118 
119 void createHistogram(int blockRadius, int bins, int blockXCenter, int blockYCenter, const vpImage<unsigned char> &I,
120  std::vector<int> &hist)
121 {
122  std::fill(hist.begin(), hist.end(), 0);
123 
124  int xMin = std::max<int>(0, blockXCenter - blockRadius);
125  int yMin = std::max<int>(0, blockYCenter - blockRadius);
126  int xMax = std::min<int>(static_cast<int>(I.getWidth()), blockXCenter + blockRadius + 1);
127  int yMax = std::min<int>(static_cast<int>(I.getHeight()), blockYCenter + blockRadius + 1);
128 
129  for (int y = yMin; y < yMax; ++y) {
130  for (int x = xMin; x < xMax; ++x) {
131  ++hist[fastRound((I[y][x] / 255.0f) * bins)];
132  }
133  }
134 }
135 
136 std::vector<float> createTransfer(const std::vector<int> &hist, int limit, std::vector<int> &cdfs)
137 {
138  clipHistogram(hist, cdfs, limit);
139  int hMin = static_cast<int>(hist.size()) - 1;
140 
141  int stopIdx = hMin;
142  bool hasNotFoundFirstNotZero = true;
143  int i = 0;
144  while ((i < stopIdx) && hasNotFoundFirstNotZero) {
145  if (cdfs[i] != 0) {
146  hMin = i;
147  hasNotFoundFirstNotZero = false;
148  }
149  ++i;
150  }
151  int cdf = 0;
152  int hist_size = static_cast<int>(hist.size());
153  for (int i = hMin; i < hist_size; ++i) {
154  cdf += cdfs[i];
155  cdfs[i] = cdf;
156  }
157 
158  int cdfMin = cdfs[hMin];
159  int cdfMax = cdfs[hist.size() - 1];
160 
161  std::vector<float> transfer(hist.size());
162  int transfer_size = static_cast<int>(transfer.size());
163  for (int i = 0; i < transfer_size; ++i) {
164  transfer[i] = (cdfs[i] - cdfMin) / static_cast<float>(cdfMax - cdfMin);
165  }
166 
167  return transfer;
168 }
169 
170 float transferValue(int v, std::vector<int> &clippedHist)
171 {
172  int clippedHistLength = static_cast<int>(clippedHist.size());
173  int hMin = clippedHistLength - 1;
174  int idxStop = hMin;
175  int i = 0;
176  bool hasNotFoundFirstNotZero = true;
177  while ((i<idxStop) && hasNotFoundFirstNotZero) {
178  if (clippedHist[i] != 0) {
179  hMin = i;
180  hasNotFoundFirstNotZero = false;
181  }
182  ++i;
183  }
184 
185  int cdf = 0;
186  for (int i = hMin; i <= v; ++i) {
187  cdf += clippedHist[i];
188  }
189 
190  int cdfMax = cdf;
191  for (int i = v + 1; i < clippedHistLength; ++i) {
192  cdfMax += clippedHist[i];
193  }
194 
195  int cdfMin = clippedHist[hMin];
196  return (cdf - cdfMin) / static_cast<float>(cdfMax - cdfMin);
197 }
198 
199 float transferValue(int v, const std::vector<int> &hist, std::vector<int> &clippedHist, int limit)
200 {
201  clipHistogram(hist, clippedHist, limit);
202 
203  return transferValue(v, clippedHist);
204 }
205 
206 bool checkClaheInputs(const int &blockRadius, const int &bins, const unsigned int &width, const unsigned int &height)
207 {
208  if (blockRadius < 0) {
209  std::cerr << "Error: blockRadius < 0!" << std::endl;
210  return false;
211  }
212 
213  const int maxBins = 256;
214  if ((bins < 0) || (bins > maxBins)) {
215  std::cerr << "Error: (bins < 0 || bins > " << maxBins << ")!" << std::endl;
216  return false;
217  }
218 
219  const int twice = 2;
220  if ((static_cast<unsigned int>((twice * blockRadius) + 1) > width) || (static_cast<unsigned int>((twice * blockRadius) + 1) > height)) {
221  std::cerr << "Error: (unsigned int) (2*blockRadius+1) > I1.getWidth() || "
222  "(unsigned int) (2*blockRadius+1) > I1.getHeight()!"
223  << std::endl;
224  return false;
225  }
226  return true;
227 }
228 
229 void clahe(const vpImage<unsigned char> &I1, vpImage<unsigned char> &I2, int blockRadius, int bins, float slope, bool fast)
230 {
231  if (!checkClaheInputs(blockRadius, bins, I1.getWidth(), I1.getHeight())) { return; }
232 
233  I2.resize(I1.getHeight(), I1.getWidth());
234  if (fast) {
235  const int val_2 = 2;
236  int blockSize = (val_2 * blockRadius) + 1;
237  int limit = static_cast<int>(((slope * blockSize * blockSize) / bins) + 0.5);
238  /* div */
239  int nc = I1.getWidth() / blockSize;
240  int nr = I1.getHeight() / blockSize;
241  /* % */
242  int cm = I1.getWidth() - (nc * blockSize);
243  std::vector<int> cs;
244  switch (cm) {
245  case 0:
246  cs.resize(nc);
247  for (int i = 0; i < nc; ++i) {
248  cs[i] = (i * blockSize) + blockRadius + 1;
249  }
250  break;
251  case 1:
252  cs.resize(nc + 1);
253  for (int i = 0; i < nc; ++i) {
254  cs[i] = (i * blockSize) + blockRadius + 1;
255  }
256  cs[nc] = I1.getWidth() - blockRadius - 1;
257  break;
258  default:
259  cs.resize(nc + val_2);
260  cs[0] = blockRadius + 1;
261  for (int i = 0; i < nc; ++i) {
262  cs[i + 1] = (i * blockSize) + blockRadius + 1 + (cm / val_2);
263  }
264  cs[nc + 1] = I1.getWidth() - blockRadius - 1;
265  }
266 
267  int rm = I1.getHeight() - (nr * blockSize);
268  std::vector<int> rs;
269  switch (rm) {
270  case 0:
271  rs.resize(static_cast<size_t>(nr));
272  for (int i = 0; i < nr; ++i) {
273  rs[i] = (i * blockSize) + blockRadius + 1;
274  }
275  break;
276  case 1:
277  rs.resize(static_cast<size_t>(nr + 1));
278  for (int i = 0; i < nr; ++i) {
279  rs[i] = (i * blockSize) + blockRadius + 1;
280  }
281  rs[nr] = I1.getHeight() - blockRadius - 1;
282  break;
283  default:
284  rs.resize(static_cast<size_t>(nr + val_2));
285  rs[0] = blockRadius + 1;
286  for (int i = 0; i < nr; ++i) {
287  rs[i + 1] = (i * blockSize) + blockRadius + 1 + (rm / val_2);
288  }
289  rs[nr + 1] = I1.getHeight() - blockRadius - 1;
290  }
291 
292  std::vector<int> hist(static_cast<size_t>(bins + 1)), cdfs(static_cast<size_t>(bins + 1));
293  std::vector<float> tl, tr, br, bl;
294  int rs_size = static_cast<int>(rs.size());
295  for (int r = 0; r <= rs_size; ++r) {
296  int r0 = std::max<int>(0, r - 1);
297  int r1 = std::min<int>(static_cast<int>(rs.size()) - 1, r);
298  int dr = rs[r1] - rs[r0];
299  createHistogram(blockRadius, bins, cs[0], rs[r0], I1, hist);
300  tr = createTransfer(hist, limit, cdfs);
301  if (r0 == r1) {
302  br = tr;
303  }
304  else {
305  createHistogram(blockRadius, bins, cs[0], rs[r1], I1, hist);
306  br = createTransfer(hist, limit, cdfs);
307  }
308 
309  int yMin = (r == 0 ? 0 : rs[r0]);
310  int yMax = (r < static_cast<int>(rs.size()) ? rs[r1] : I1.getHeight());
311  int cs_size = static_cast<int>(cs.size());
312  for (int c = 0; c <= cs_size; ++c) {
313  int c0 = std::max<int>(0, c - 1);
314  int c1 = std::min<int>(static_cast<int>(cs.size()) - 1, c);
315  int dc = cs[c1] - cs[c0];
316  tl = tr;
317  bl = br;
318  if (c0 != c1) {
319  createHistogram(blockRadius, bins, cs[c1], rs[r0], I1, hist);
320  tr = createTransfer(hist, limit, cdfs);
321  if (r0 == r1) {
322  br = tr;
323  }
324  else {
325  createHistogram(blockRadius, bins, cs[c1], rs[r1], I1, hist);
326  br = createTransfer(hist, limit, cdfs);
327  }
328  }
329 
330  int xMin = (c == 0 ? 0 : cs[c0]);
331  int xMax = (c < static_cast<int>(cs.size()) ? cs[c1] : I1.getWidth());
332  for (int y = yMin; y < yMax; ++y) {
333  float wy = static_cast<float>(rs[r1] - y) / dr;
334  for (int x = xMin; x < xMax; ++x) {
335  float wx = static_cast<float>(cs[c1] - x) / dc;
336  int v = fastRound((I1[y][x] / 255.0f) * bins);
337  float t00 = tl[v];
338  float t01 = tr[v];
339  float t10 = bl[v];
340  float t11 = br[v];
341  float t0 = (c0 == c1) ? t00 : ((wx * t00) + ((1.0f - wx) * t01));
342  float t1 = (c0 == c1) ? t10 : ((wx * t10) + ((1.0f - wx) * t11));
343  float t = (r0 == r1) ? t0 : ((wy * t0) + ((1.0f - wy) * t1));
344  const int maxPixelIntensity = 255;
345  I2[y][x] = std::max<unsigned char>(0, std::min<unsigned char>(maxPixelIntensity, fastRound(t * 255.0f)));
346  }
347  }
348  }
349  }
350  }
351  else {
352  std::vector<int> hist(bins + 1), prev_hist(bins + 1), clippedHist(bins + 1);
353  bool first = true;
354  int xMin0 = 0;
355  int xMax0 = std::min<int>(static_cast<int>(I1.getWidth()), blockRadius);
356  int i1_height = static_cast<int>(I1.getHeight());
357  for (int y = 0; y < i1_height; ++y) {
358  int yMin = std::max<int>(0, y - static_cast<int>(blockRadius));
359  int yMax = std::min<int>(static_cast<int>(I1.getHeight()), y + blockRadius + 1);
360  int h = yMax - yMin;
361 
362  if (first) {
363  first = false;
364  // Compute histogram for the block at (0,0)
365  for (int yi = yMin; yi < yMax; ++yi) {
366  for (int xi = xMin0; xi < xMax0; ++xi) {
367  ++hist[fastRound((I1[yi][xi] / 255.0f) * bins)];
368  }
369  }
370  }
371  else {
372  hist = prev_hist;
373 
374  if (yMin > 0) {
375  int yMin1 = yMin - 1;
376  // Sliding histogram, remove top
377  for (int xi = xMin0; xi < xMax0; ++xi) {
378  --hist[fastRound((I1[yMin1][xi] / 255.0f) * bins)];
379  }
380  }
381 
382  if ((y + blockRadius) < static_cast<int>(I1.getHeight())) {
383  int yMax1 = yMax - 1;
384  // Sliding histogram, add bottom
385  for (int xi = xMin0; xi < xMax0; ++xi) {
386  ++hist[fastRound((I1[yMax1][xi] / 255.0f) * bins)];
387  }
388  }
389  }
390  prev_hist = hist;
391 
392  int i1_width = static_cast<int>(I1.getWidth());
393  for (int x = 0; x < i1_width; ++x) {
394  int xMin = std::max<int>(0, x - static_cast<int>(blockRadius));
395  int xMax = x + blockRadius + 1;
396 
397  if (xMin > 0) {
398  int xMin1 = xMin - 1;
399  // Sliding histogram, remove left
400  for (int yi = yMin; yi < yMax; ++yi) {
401  --hist[fastRound((I1[yi][xMin1] / 255.0f) * bins)];
402  }
403  }
404 
405  if (xMax <= static_cast<int>(I1.getWidth())) {
406  int xMax1 = xMax - 1;
407  // Sliding histogram, add right
408  for (int yi = yMin; yi < yMax; ++yi) {
409  ++hist[fastRound((I1[yi][xMax1] / 255.0f) * bins)];
410  }
411  }
412 
413  int v = fastRound((I1[y][x] / 255.0f) * bins);
414  int w = std::min<int>(static_cast<int>(I1.getWidth()), xMax) - xMin;
415  int n = h * w;
416  int limit = static_cast<int>(((slope * n) / bins) + 0.5f);
417  I2[y][x] = fastRound(transferValue(v, hist, clippedHist, limit) * 255.0f);
418  }
419  }
420  }
421 }
422 
423 void clahe(const vpImage<vpRGBa> &I1, vpImage<vpRGBa> &I2, int blockRadius, int bins, float slope, bool fast)
424 {
425  // Split
430 
431  vpImageConvert::split(I1, &pR, &pG, &pB, &pa);
432 
433  // Apply CLAHE independently on RGB channels
434  vpImage<unsigned char> resR, resG, resB;
435  clahe(pR, resR, blockRadius, bins, slope, fast);
436  clahe(pG, resG, blockRadius, bins, slope, fast);
437  clahe(pB, resB, blockRadius, bins, slope, fast);
438 
439  const unsigned int sizeRGBa = 4;
440  I2.resize(I1.getHeight(), I1.getWidth());
441  unsigned int size = I2.getWidth() * I2.getHeight();
442  unsigned char *ptrStart = reinterpret_cast<unsigned char *>(I2.bitmap);
443  unsigned char *ptrEnd = ptrStart + (size * sizeRGBa);
444  unsigned char *ptrCurrent = ptrStart;
445 
446  unsigned int cpt = 0;
447  while (ptrCurrent != ptrEnd) {
448  *ptrCurrent = resR.bitmap[cpt];
449  ++ptrCurrent;
450 
451  *ptrCurrent = resG.bitmap[cpt];
452  ++ptrCurrent;
453 
454  *ptrCurrent = resB.bitmap[cpt];
455  ++ptrCurrent;
456 
457  *ptrCurrent = pa.bitmap[cpt];
458  ++ptrCurrent;
459 
460  ++cpt;
461  }
462 }
463 
464 } // namespace
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:242
void resize(unsigned int h, unsigned int w)
resize the image : Image initialization
Definition: vpImage.h:544
Type * bitmap
points toward the bitmap
Definition: vpImage.h:135
unsigned int getHeight() const
Definition: vpImage.h:181
VISP_EXPORT void clahe(const VISP_NAMESPACE_ADDRESSING vpImage< unsigned char > &I1, VISP_NAMESPACE_ADDRESSING vpImage< unsigned char > &I2, int blockRadius=150, int bins=256, float slope=3.0f, bool fast=true)
bool checkClaheInputs(const int &blockRadius, const int &bins, const unsigned int &width, const unsigned int &height)
Definition: vpCLAHE.cpp:206
void createHistogram(int blockRadius, int bins, int blockXCenter, int blockYCenter, const vpImage< unsigned char > &I, std::vector< int > &hist)
Definition: vpCLAHE.cpp:119
std::vector< float > createTransfer(const std::vector< int > &hist, int limit, std::vector< int > &cdfs)
Definition: vpCLAHE.cpp:136
float transferValue(int v, std::vector< int > &clippedHist)
Definition: vpCLAHE.cpp:170
int fastRound(float value)
Definition: vpCLAHE.cpp:85
void clipHistogram(const std::vector< int > &hist, std::vector< int > &clippedHist, int limit)
Definition: vpCLAHE.cpp:87