CVB++ 15.0
histogram_analyzer.hpp
1#pragma once
2
3#include "../_cexports/c_light_meter.h"
4
5#include "../global.hpp"
6#include "../image.hpp"
7#include "../exception.hpp"
8#include "../rect.hpp"
9#include "../area_2d.hpp"
10
11#include <vector>
12#include <cstdint>
13#include <cmath>
14#include <iterator>
15#include <algorithm>
16
17namespace Cvb
18{
19 CVB_BEGIN_INLINE_NS
20
21 namespace Foundation
22 {
23
25
30 {
31
32 // forward declarations
33 class Histogram;
34 namespace Private
35 {
36 std::vector<Histogram> CreateHistograms(const Image &image, Area2D aoi, double density, bool ignoreImageCs);
37 }
38
40
42 class Histogram
43 {
44 public:
45 Histogram() noexcept
46 : numPixels_(0)
47 , median_(0)
48 , mean_(0.0)
49 , mode_(0.0)
50 , min_(0.0)
51 , max_(0.0)
52 , variance_(0.0)
53 , standardDeviation_(0.0)
54 , values_()
55 {
56 }
57
58 private:
59 Histogram(CExports::LMH lmHandle, int plane)
60 : numPixels_(0)
61 , median_(0)
62 , mean_(0.0)
63 , mode_(0.0)
64 , min_(0.0)
65 , max_(0.0)
66 , variance_(0.0)
67 , standardDeviation_(0.0)
68 , values_()
69 {
70 // retrieve the histogram data
71 size_t numEntries = 0;
72 CVB_CALL_CAPI_CHECKED(LMGetDataBufferEntries(lmHandle, plane, &numEntries));
73 values_.resize(numEntries);
74 for (size_t i = 0; i < numEntries; ++i)
75 {
77 CVB_CALL_CAPI_CHECKED(LMGetSingleHistogramEntry(lmHandle, plane, i, &val));
78 values_[i] = static_cast<uint64_t>(std::llround(val));
79 }
80
81 // retrieve the statistic results
83 CVB_CALL_CAPI_CHECKED(LMGetStatisticTotal(lmHandle, plane, &out));
84 numPixels_ = static_cast<uint64_t>(std::llround(out));
85 CVB_CALL_CAPI_CHECKED(LMGetStatisticMean(lmHandle, plane, &mean_));
86 CVB_CALL_CAPI_CHECKED(LMGetStatisticMode(lmHandle, plane, &mode_));
87 CVB_CALL_CAPI_CHECKED(LMGetStatisticMin(lmHandle, plane, &min_));
88 CVB_CALL_CAPI_CHECKED(LMGetStatisticMax(lmHandle, plane, &max_));
89 CVB_CALL_CAPI_CHECKED(LMGetStatisticVariance(lmHandle, plane, &variance_));
90 CVB_CALL_CAPI_CHECKED(LMGetStatisticStdDev(lmHandle, plane, &standardDeviation_));
91 median_ = CalculateMedianIndex();
92 }
93
94 friend std::vector<Histogram> Private::CreateHistograms(const Image &image, Area2D aoi, double density,
95 bool ignoreImageCs);
96
97 int CalculateMedianIndex() const noexcept
98 {
99 std::size_t idx = 0;
100 for (uint64_t countBelow = 0; (idx < values_.size()) && (countBelow < (numPixels_ / 2)); ++idx)
101 {
102 countBelow += values_[idx];
103 }
104 return static_cast<int>(idx);
105 }
106
107 public:
109
113 uint64_t NumPixels() const noexcept
114 {
115 return numPixels_;
116 }
117
120
124 uint64_t Median() const noexcept
125 {
126 // we decided not to call it median index
127 return median_;
128 }
129
131
135 double Mean() const noexcept
136 {
137 return mean_;
138 }
139
141
145 double Mode() const noexcept
146 {
147 return mode_;
148 }
149
151
155 double Min() const noexcept
156 {
157 return min_;
158 }
159
161
165 double Max() const noexcept
166 {
167 return max_;
168 }
169
171
175 double Variance() const noexcept
176 {
177 return variance_;
178 }
179
181
185 double StandardDeviation() const noexcept
186 {
187 return standardDeviation_;
188 }
189
191
195 int Count() const noexcept
196 {
197 return static_cast<int>(values_.size());
198 }
199
201
206 {
207 return values_;
208 }
209
211
216 uint64_t operator[](int index) const
217 {
218 if (static_cast<std::size_t>(index) >= values_.size())
219 {
220 throw std::out_of_range("histogram index out of range");
221 }
222
223 return values_[index];
224 }
225
226 private:
227 uint64_t numPixels_;
228 int median_;
229 double mean_;
230 double mode_;
231 double min_;
232 double max_;
233 double variance_;
234 double standardDeviation_;
235 std::vector<uint64_t> values_;
236 };
237
238 namespace Private
239 {
240 typedef HandleGuard<void, CVB_CALL_CAPI(ReleaseLMVoid)> ReleaseLMGuard;
241
242 /* Worker of the histogram creation functions */
243 inline std::vector<Histogram> CreateHistograms(const Image &image, Area2D aoi, double density,
244 bool ignoreImageCs)
245 {
246 std::vector<Histogram> histograms;
247
248 CExports::LMH hLM = 0;
249 CVB_CALL_CAPI_CHECKED(LMCreate(&hLM));
250 ReleaseLMGuard hLMHolder(reinterpret_cast<void *>(hLM));
251
252 CVB_CALL_CAPI_CHECKED(LMSetImage(hLM, image.Handle()));
253 for (CExports::cvbdim_t i = 0; i < image.PlanesCount(); ++i)
254 {
255 CVB_CALL_CAPI_CHECKED(LMSetEntireImageFlag(hLM, i, false));
256 CVB_CALL_CAPI_CHECKED(LMSetProcessFlag(hLM, i, true));
257 }
258
259 for (CExports::cvbdim_t i = 0; i < image.PlanesCount(); ++i)
260 {
261 CVB_CALL_CAPI_CHECKED(LMSetDensity(hLM, i, std::lround(density * 1000.0)));
262 CVB_CALL_CAPI_CHECKED(LMSetUseCSFlag(hLM, i, !ignoreImageCs));
263 CVB_CALL_CAPI_CHECKED(LMSetArea(hLM, i, reinterpret_cast<CExports::TArea &>(aoi)));
264 }
265 CVB_CALL_CAPI_CHECKED(LMExecute(hLM));
266
267 for (CExports::cvbdim_t i = 0; i < image.PlanesCount(); ++i)
268 {
269 histograms.push_back(Histogram(hLM, i));
270 }
271
272 return histograms;
273 }
274
275 } /* namespace Private */
276
278
288 inline std::vector<Histogram> CreateImageHistograms(const Image &image, Rect<int> aoi, double density = 1.0)
289 {
290 return Private::CreateHistograms(image, Area2D(static_cast<Rect<double>>(aoi)), density, true);
291 }
292
294
304 inline std::vector<Histogram> CreateImageHistograms(const Image &image, Area2D aoi, double density = 1.0)
305 {
306 return Private::CreateHistograms(image, aoi, density, false);
307 }
308
310
317 inline std::vector<Histogram> CreateImageHistograms(const Image &image, double density = 1.0)
318 {
319 return CreateImageHistograms(image, image.Bounds(), density);
320 }
321
323
333 inline Histogram CreatePlaneHistogram(const ImagePlane &imagePlane, Rect<int> aoi, double density = 1.0)
334 {
335 return CreateImageHistograms(*imagePlane.Map(), aoi, density)[0];
336 }
337
339
349 inline Histogram CreatePlaneHistogram(const ImagePlane &imagePlane, Area2D aoi, double density = 1.0)
350 {
351 return CreateImageHistograms(*imagePlane.Map(), aoi, density)[0];
352 }
353
355
362 inline Histogram CreatePlaneHistogram(const ImagePlane &imagePlane, double density = 1.0)
363 {
364 return CreateImageHistograms(*imagePlane.Map(), density)[0];
365 }
366
367 namespace Private
368 {
369
370 struct PeakData
371 {
372 int GrayValue;
373 double Quality;
374 };
375
376 inline std::vector<double> MakeDerivationFilter()
377 {
378 return std::vector<double>{-1.0, 0.0, 1.0};
379 }
380
381 inline std::vector<double> MakeBlurFilter(int blurSize)
382 {
383 return std::vector<double>(blurSize, 1.0 / blurSize);
384 }
385
386 template <class HISTVAL>
387 inline std::vector<double> MakeBorderedHistogram(const std::vector<HISTVAL> &histogram, size_t kernelSize)
388 {
389 std::vector<double> result(histogram.size() + kernelSize - 1);
390
391 size_t j = 0;
392 auto firstValue = histogram[0];
393 for (size_t i = 0, bckOffset = kernelSize / 2; i < bckOffset; ++i)
394 {
395 result[j++] = static_cast<double>(firstValue);
396 }
397
398 for (size_t i = 0; i < histogram.size(); ++i)
399 {
400 result[j++] = static_cast<double>(histogram[i]);
401 }
402
403 auto lastValue = histogram[histogram.size() - 1];
404 for (; j < result.size(); ++j)
405 {
406 result[j] = static_cast<double>(lastValue);
407 }
408
409 return result;
410 }
411
412 template <class RANGE>
413 inline typename TypedRange<std::vector<double>, double, RANGE>::type
414 ApplyKernel(const std::vector<double> &borderedHistogram, const RANGE &kernel)
415 {
416 auto kernelRange = MakeRangeAdapter<double>(kernel, 1);
417 std::vector<double> result(borderedHistogram.size() - kernelRange.Size() + 1);
418
419 for (size_t i = 0; i < result.size(); ++i)
420 {
421 double val = 0.0;
422 for (size_t k = 0; k < kernelRange.Size(); ++k)
423 {
424 val += borderedHistogram[i + k] * kernel[k];
425 }
426 result[i] = val;
427 }
428 return result;
429 }
430
431 inline std::vector<PeakData> FindZeroCrossings(const std::vector<double> &blurredHistogram,
432 const std::vector<double> &derivedHistogram)
433 {
434 // for initialization we need to make sure that a value != 0 is loaded
435 auto itNonZero =
436 std::find_if(derivedHistogram.begin(), derivedHistogram.end(), [](double val) { return val != 0.0; });
437 if (itNonZero == derivedHistogram.end())
438 {
439 return std::vector<PeakData>();
440 }
441 // turn the iterator to index
442 size_t i = std::distance(derivedHistogram.begin(), itNonZero);
443
444 auto sign = [](double val) { return (val == 0.0) ? 0 : ((val < 0.0) ? -1 : 1); };
445
446 std::vector<PeakData> positions;
447 auto lastStart = i++;
448 auto lastSign = sign(derivedHistogram[lastStart]);
449 for (; i < derivedHistogram.size(); i++)
450 {
451 // three cases sign before == sign after; sign before != sign after;
452 // sign after == 0
453 auto thisSign = sign(derivedHistogram[i]);
454 if (thisSign != 0)
455 {
456 if (thisSign != lastSign)
457 {
458 auto peakPos = (i + lastStart) / 2;
459 positions.push_back(PeakData{static_cast<int>(peakPos), blurredHistogram[peakPos]});
460 }
461 lastSign = thisSign;
462 lastStart = i;
463 }
464 }
465 return positions;
466 }
467
468 inline std::vector<int> FilterByMinDistance(const std::vector<PeakData> &zeroCrossings, int minDiff)
469 {
470 std::vector<PeakData> descendingCrossings(zeroCrossings);
471 std::sort(descendingCrossings.begin(), descendingCrossings.end(),
472 [](PeakData first, PeakData second) { return first.Quality > second.Quality; });
473 std::vector<int> values(descendingCrossings.size());
474 std::transform(descendingCrossings.begin(), descendingCrossings.end(), values.begin(),
475 [](PeakData peak) { return peak.GrayValue; });
476
477 for (size_t i = 0; i < values.size() - 1; ++i)
478 {
479 for (size_t k = i + 1; k < values.size(); ++k)
480 {
481 auto diff = std::abs(values[i] - values[k]);
482 if (diff < minDiff)
483 {
484 values.erase(values.begin() + k--); // NOLINT
485 }
486 }
487 }
488
489 return values;
490 }
491
492 } /* namespace Private */
493
496
503 template <class HISTVAL, class RANGE>
504 inline typename TypedRange<std::vector<double>, double, RANGE>::type
505 FilterHistogram(const std::vector<HISTVAL> &histogram, const RANGE &kernel)
506 {
507 auto kernelSize = std::distance(std::begin(kernel), std::end(kernel));
508 if ((kernelSize < 1) || (static_cast<size_t>(kernelSize) > histogram.size()))
509 {
510 throw std::out_of_range("histogram filter kernel size out of range");
511 }
512
513 auto borderedHistogram = Private::MakeBorderedHistogram(histogram, kernelSize);
514 return Private::ApplyKernel(borderedHistogram, kernel);
515 }
516
518
526 inline std::vector<int> FindHistogramPeaks(const std::vector<uint64_t> &histogram, int blurSize, int minDiff)
527 {
528 if ((blurSize < 1) || (static_cast<size_t>(blurSize) > histogram.size()) || (minDiff < 1))
529 {
530 throw std::out_of_range("histogram peak search parameters out of range");
531 }
532
533 auto blurredHistogram = FilterHistogram(histogram, Private::MakeBlurFilter(static_cast<int>(blurSize)));
534 auto derivedHistogram = FilterHistogram(blurredHistogram, Private::MakeDerivationFilter());
535 auto zeroCrossings = Private::FindZeroCrossings(blurredHistogram, derivedHistogram);
536 return Private::FilterByMinDistance(zeroCrossings, minDiff);
537 }
538
540
548 inline uint64_t SumHistogramBetween(const std::vector<uint64_t> &histogram, int lowerLimit, int upperLimit)
549 {
550 if ((lowerLimit < 0) || (static_cast<size_t>(upperLimit) >= histogram.size()))
551 {
552 throw std::out_of_range("histogram peak search parameters out of range");
553 }
554
555 uint64_t nres = 0;
556 for (int i = lowerLimit; i <= upperLimit; ++i)
557 {
558 nres += histogram[i];
559 }
560 return nres;
561 }
562
563 } /* namespace HistogramAnalyzer */
564
565 using HistogramAnalyzer::Histogram;
566
572
573 } /* namespace Foundation */
574 CVB_END_INLINE_NS
575} /* namespace Cvb */
T begin(T... args)
Structure that represents an area of interest in the image.
Definition area_2d.hpp:21
A single histogram result.
Definition histogram_analyzer.hpp:43
double Mean() const noexcept
Get the mean value of all pixels.
Definition histogram_analyzer.hpp:135
double Min() const noexcept
Get the minimum gray value of the histogram.
Definition histogram_analyzer.hpp:155
uint64_t Median() const noexcept
Get the median index (index of the histogram slot, at which roughly 50% of the pixels are darker and ...
Definition histogram_analyzer.hpp:124
int Count() const noexcept
The number of elements corresponds to the number of possible gray values.
Definition histogram_analyzer.hpp:195
std::vector< uint64_t > Values() const
The actual histogram values.
Definition histogram_analyzer.hpp:205
double Variance() const noexcept
Get the variance of the histogram.
Definition histogram_analyzer.hpp:175
uint64_t operator[](int index) const
Gets the histogram value at given index.
Definition histogram_analyzer.hpp:216
double Max() const noexcept
Get the maximum gray value of the histogram.
Definition histogram_analyzer.hpp:165
double StandardDeviation() const noexcept
Get the standard deviation of the histogram.
Definition histogram_analyzer.hpp:185
uint64_t NumPixels() const noexcept
Total number of pixels taken into account.
Definition histogram_analyzer.hpp:113
double Mode() const noexcept
Get the mode (the most common gray value) of the histogram.
Definition histogram_analyzer.hpp:145
The Common Vision Blox image.
Definition decl_image.hpp:45
int PlanesCount() const noexcept
Get the number of planes for this image.
Definition decl_image.hpp:242
Rect< int > Bounds() const noexcept
Bounding rectangle of the image in pixels.
Definition decl_image.hpp:433
void * Handle() const noexcept
Classic API image handle.
Definition decl_image.hpp:232
Image plane information container.
Definition decl_image_plane.hpp:29
std::unique_ptr< Image > Map() const
Create a map from a single image plane that shares its memory with the original plane.
Definition detail_image_plane.hpp:100
Rectangle object.
Definition rect.hpp:24
T distance(T... args)
T end(T... args)
Collection of functions for analyzing the image histogram.
Definition histogram_analyzer.hpp:30
std::vector< int > FindHistogramPeaks(const std::vector< uint64_t > &histogram, int blurSize, int minDiff)
Find peaks in the histogram identified by the supplied criteria.
Definition histogram_analyzer.hpp:526
uint64_t SumHistogramBetween(const std::vector< uint64_t > &histogram, int lowerLimit, int upperLimit)
Count the number of pixels that lie between two limits in the histogram.
Definition histogram_analyzer.hpp:548
std::vector< Histogram > CreateImageHistograms(const Image &image, Rect< int > aoi, double density=1.0)
Creates a histogram for each plane of the aoi in the given image.
Definition histogram_analyzer.hpp:288
TypedRange< std::vector< double >, double, RANGE >::type FilterHistogram(const std::vector< HISTVAL > &histogram, const RANGE &kernel)
Filter a histogram array with the given kernel. At the beginning and end of the histogram,...
Definition histogram_analyzer.hpp:505
Histogram CreatePlaneHistogram(const ImagePlane &imagePlane, Rect< int > aoi, double density=1.0)
Creates a histogram for the aoi in the given plane.
Definition histogram_analyzer.hpp:333
Namespace for the Foundation package.
Definition decl_metric_aqs12_calibration_piece.hpp:11
Root namespace for the Image Manager interface.
Definition c_bayer_to_rgb.h:17
std::vector< int > Histogram(const ImagePlane &plane, Area2D aoi, double density=1.0)
Gather and return the histogram from an 8 bits per pixel unsigned image.
Definition analyze.hpp:30
T quiet_NaN(T... args)
T llround(T... args)