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