CVB++ 14.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
17
18namespace Cvb
19{
20CVB_BEGIN_INLINE_NS
21
22
23
24namespace Foundation
25{
26
28
32namespace HistogramAnalyzer
33{
34
35// forward declarations
36class Histogram;
37namespace Private
38{
39 std::vector<Histogram> CreateHistograms (const Image & image, Area2D aoi, double density, bool ignoreImageCs);
40}
41
43
46{
47public:
48 Histogram () noexcept
49 : numPixels_(0), median_(0), mean_(0.0), mode_ (0.0), min_(0.0), max_(0.0), variance_(0.0), standardDeviation_(0.0), values_()
50 {}
51
52private:
53 Histogram (CExports::LMH lmHandle, int plane)
54 : numPixels_(0), median_(0), mean_(0.0), mode_ (0.0), min_(0.0), max_(0.0), variance_(0.0), standardDeviation_(0.0), values_()
55 {
56 // retrieve the histogram data
57 size_t numEntries;
58 CVB_CALL_CAPI_CHECKED (LMGetDataBufferEntries (lmHandle, plane, &numEntries));
59 values_.resize (numEntries);
60 for (size_t i = 0; i < numEntries; ++i)
61 {
62 double val;
63 CVB_CALL_CAPI_CHECKED (LMGetSingleHistogramEntry(lmHandle, plane, i, &val));
64 values_[i] = static_cast<uint64_t> (std::llround (val));
65 }
66
67 // retrieve the statistic results
68 double out;
69 CVB_CALL_CAPI_CHECKED (LMGetStatisticTotal (lmHandle, plane, &out));
70 numPixels_ = static_cast<uint64_t> (std::llround (out));
71 CVB_CALL_CAPI_CHECKED (LMGetStatisticMean (lmHandle, plane, &mean_));
72 CVB_CALL_CAPI_CHECKED (LMGetStatisticMode (lmHandle, plane, &mode_));
73 CVB_CALL_CAPI_CHECKED (LMGetStatisticMin (lmHandle, plane, &min_));
74 CVB_CALL_CAPI_CHECKED (LMGetStatisticMax (lmHandle, plane, &max_));
75 CVB_CALL_CAPI_CHECKED (LMGetStatisticVariance (lmHandle, plane, &variance_));
76 CVB_CALL_CAPI_CHECKED (LMGetStatisticStdDev (lmHandle, plane, &standardDeviation_));
77 median_ = CalculateMedianIndex();
78 }
79
80 friend std::vector<Histogram> Private::CreateHistograms (const Image & image, Area2D aoi, double density, bool ignoreImageCs);
81
82 int CalculateMedianIndex() const noexcept
83 {
84 std::size_t idx = 0;
85 for (uint64_t countBelow = 0; (idx < values_.size ()) && (countBelow < (numPixels_ / 2)); ++idx)
86 {
87 countBelow += values_[idx];
88 }
89 return static_cast<int>(idx);
90 }
91
92public:
94
98 uint64_t NumPixels () const noexcept
99 {
100 return numPixels_;
101 }
102
104
108 uint64_t Median () const noexcept
109 {
110 // we decided not to call it median index
111 return median_;
112 }
113
115
119 double Mean () const noexcept
120 {
121 return mean_;
122 }
123
125
129 double Mode () const noexcept
130 {
131 return mode_;
132 }
133
135
139 double Min () const noexcept
140 {
141 return min_;
142 }
143
145
149 double Max () const noexcept
150 {
151 return max_;
152 }
153
155
159 double Variance () const noexcept
160 {
161 return variance_;
162 }
163
165
169 double StandardDeviation () const noexcept
170 {
171 return standardDeviation_;
172 }
173
175
179 int Count () const noexcept
180 {
181 return static_cast<int>(values_.size());
182 }
183
185
190 {
191 return values_;
192 }
193
195
200 uint64_t operator[](int index) const
201 {
202 if (static_cast<std::size_t>(index) >= values_.size())
203 {
204 throw std::out_of_range ("histogram index out of range");
205 }
206
207 return values_[index];
208 }
209
210private:
211 uint64_t numPixels_;
212 int median_;
213 double mean_;
214 double mode_;
215 double min_;
216 double max_;
217 double variance_;
218 double standardDeviation_;
219 std::vector<uint64_t> values_;
220};
221
222namespace Private
223{
224typedef HandleGuard<void, CVB_CALL_CAPI(ReleaseLMVoid)> ReleaseLMGuard;
225
226/* Worker of the histogram creation functions */
227inline std::vector<Histogram> CreateHistograms (const Image & image, Area2D aoi, double density, bool ignoreImageCs)
228{
229 std::vector<Histogram> histograms;
230
231 CExports::LMH hLM;
232 CVB_CALL_CAPI_CHECKED (LMCreate (&hLM));
233 ReleaseLMGuard hLMHolder (reinterpret_cast<void*>(hLM));
234
235 CVB_CALL_CAPI_CHECKED (LMSetImage(hLM, image.Handle()));
236 for (CExports::cvbdim_t i = 0; i < image.PlanesCount (); ++i)
237 {
238 CVB_CALL_CAPI_CHECKED (LMSetEntireImageFlag (hLM, i, false));
239 CVB_CALL_CAPI_CHECKED (LMSetProcessFlag (hLM, i, true));
240 }
241
242 for (CExports::cvbdim_t i = 0; i < image.PlanesCount(); ++i)
243 {
244 CVB_CALL_CAPI_CHECKED (LMSetDensity(hLM, i, std::lround(density * 1000.0)));
245 CVB_CALL_CAPI_CHECKED (LMSetUseCSFlag(hLM, i, !ignoreImageCs));
246 CVB_CALL_CAPI_CHECKED (LMSetArea(hLM, i, reinterpret_cast<CExports::TArea&>(aoi)));
247 }
248 CVB_CALL_CAPI_CHECKED (LMExecute(hLM));
249
250 for (CExports::cvbdim_t i = 0; i < image.PlanesCount (); ++i)
251 {
252 histograms.push_back (Histogram (hLM, i));
253 }
254
255 return histograms;
256}
257
258} /* namespace Private */
259
261
271inline std::vector<Histogram> CreateImageHistograms (const Image & image, Rect<int> aoi, double density = 1.0)
272{
273 return Private::CreateHistograms (image, Area2D(static_cast<Rect<double>>(aoi)), density, true);
274}
275
277
287inline std::vector<Histogram> CreateImageHistograms (const Image & image, Area2D aoi, double density = 1.0)
288{
289 return Private::CreateHistograms (image, aoi, density, false);
290}
291
293
300inline std::vector<Histogram> CreateImageHistograms (const Image & image, double density = 1.0)
301{
302 return CreateImageHistograms (image, image.Bounds(), density);
303}
304
306
316inline Histogram CreatePlaneHistogram (const ImagePlane & imagePlane, Rect<int> aoi, double density = 1.0)
317{
318 return CreateImageHistograms (*imagePlane.Map(), aoi, density) [0];
319}
320
322
332inline Histogram CreatePlaneHistogram (const ImagePlane & imagePlane, Area2D aoi, double density = 1.0)
333{
334 return CreateImageHistograms (*imagePlane.Map(), aoi, density) [0];
335}
336
338
345inline Histogram CreatePlaneHistogram (const ImagePlane & imagePlane, double density = 1.0)
346{
347 return CreateImageHistograms (*imagePlane.Map(), density) [0];
348}
349
350namespace Private
351{
352
353struct PeakData
354{
355 int GrayValue;
356 double Quality;
357};
358
359inline std::vector<double> MakeDerivationFilter ()
360{
361 return std::vector<double> { -1.0, 0.0, 1.0 };
362}
363
364inline std::vector<double> MakeBlurFilter (int blurSize)
365{
366 return std::vector<double> (blurSize, 1.0 / blurSize);
367}
368
369template <class HISTVAL>
370inline std::vector<double> MakeBorderedHistogram (const std::vector<HISTVAL> &histogram, size_t kernelSize)
371{
372 std::vector<double> result (histogram.size() + kernelSize - 1);
373
374 size_t j = 0;
375 auto firstValue = histogram[0];
376 for (size_t i = 0, bckOffset = kernelSize / 2; i < bckOffset; ++i)
377 {
378 result[j++] = static_cast<double> (firstValue);
379 }
380
381 for (size_t i = 0; i < histogram.size(); ++i)
382 {
383 result[j++] = static_cast<double> (histogram[i]);
384 }
385
386 auto lastValue = histogram[histogram.size() - 1];
387 for (; j < result.size (); ++j)
388 {
389 result[j] = static_cast<double> (lastValue);
390 }
391
392 return result;
393}
394
395template <class RANGE>
396inline typename TypedRange<std::vector<double>, double, RANGE>::type ApplyKernel (const std::vector<double> &borderedHistogram, const RANGE &kernel)
397{
398 auto kernelRange = MakeRangeAdapter<double> (kernel, 1);
399 std::vector<double> result (borderedHistogram.size() - kernelRange.Size() + 1);
400
401 for (size_t i = 0; i < result.size(); ++i)
402 {
403 double val = 0.0;
404 for (size_t k = 0; k < kernelRange.Size (); ++k)
405 {
406 val += borderedHistogram[i + k] * kernel[k];
407 }
408 result[i] = val;
409 }
410 return result;
411}
412
413inline std::vector<PeakData> FindZeroCrossings (const std::vector<double> &blurredHistogram, const std::vector<double> &derivedHistogram)
414{
415 // for initialization we need to make sure that a value != 0 is loaded
416 auto itNonZero = std::find_if (derivedHistogram.begin (), derivedHistogram.end (), [](double val) {return val != 0.0; });
417 if (itNonZero == derivedHistogram.end())
418 {
419 return std::vector<PeakData> ();
420 }
421 // turn the iterator to index
422 size_t i = std::distance (derivedHistogram.begin(), itNonZero);
423
424 auto sign = [](double val) {return (val == 0.0) ? 0 : ((val < 0.0) ? -1 : 1); };
425
426 std::vector<PeakData> positions;
427 auto lastStart = i++;
428 auto lastSign = sign (derivedHistogram[lastStart]);
429 for (; i < derivedHistogram.size(); i++)
430 {
431 // three cases sign before == sign after; sign before != sign after;
432 // sign after == 0
433 auto thisSign = sign (derivedHistogram[i]);
434 if (thisSign != 0)
435 {
436 if (thisSign != lastSign)
437 {
438 auto peakPos = (i + lastStart) / 2;
439 positions.push_back(PeakData{static_cast<int>(peakPos), blurredHistogram[peakPos]});
440 }
441 lastSign = thisSign;
442 lastStart = i;
443 }
444 }
445 return positions;
446}
447
448inline std::vector<int> FilterByMinDistance (const std::vector<PeakData> &zeroCrossings, int minDiff)
449{
450 std::vector<PeakData> descendingCrossings (zeroCrossings);
451 std::sort (descendingCrossings.begin (), descendingCrossings.end (), [](PeakData first, PeakData second) { return first.Quality > second.Quality; });
452 std::vector<int> values (descendingCrossings.size());
453 std::transform (descendingCrossings.begin (), descendingCrossings.end (), values.begin (), [](PeakData peak) { return peak.GrayValue; });
454
455 for (size_t i = 0; i < values.size() - 1; ++i)
456 {
457 for (size_t k = i + 1; k < values.size(); ++k)
458 {
459 auto diff = std::abs (values[i] - values[k]);
460 if (diff < minDiff)
461 {
462 values.erase (values.begin() + k--);
463 }
464 }
465 }
466
467 return values;
468}
469
470} /* namespace Private */
471
472
474
481template <class HISTVAL, class RANGE>
482inline typename TypedRange<std::vector<double>, double, RANGE>::type FilterHistogram (const std::vector<HISTVAL> &histogram, const RANGE &kernel)
483{
484 auto kernelSize = std::distance(std::begin(kernel), std::end(kernel));
485 if ((kernelSize < 1) || (static_cast<size_t>(kernelSize) > histogram.size()))
486 {
487 throw std::out_of_range ("histogram filter kernel size out of range");
488 }
489
490 auto borderedHistogram = Private::MakeBorderedHistogram (histogram, kernelSize);
491 return Private::ApplyKernel (borderedHistogram, kernel);
492}
493
495
503inline std::vector<int> FindHistogramPeaks (const std::vector<uint64_t> &histogram, int blurSize, int minDiff)
504{
505 if ((blurSize < 1) || (static_cast<size_t>(blurSize) > histogram.size()) || (minDiff < 1))
506 {
507 throw std::out_of_range ("histogram peak search parameters out of range");
508 }
509
510 auto blurredHistogram = FilterHistogram (histogram, Private::MakeBlurFilter(static_cast<size_t>(blurSize)));
511 auto derivedHistogram = FilterHistogram (blurredHistogram, Private::MakeDerivationFilter());
512 auto zeroCrossings = Private::FindZeroCrossings (blurredHistogram, derivedHistogram);
513 return Private::FilterByMinDistance (zeroCrossings, minDiff);
514}
515
517
525inline uint64_t SumHistogramBetween (const std::vector<uint64_t> &histogram, int lowerLimit, int upperLimit)
526{
527 if ((lowerLimit < 0) || (static_cast<size_t>(upperLimit) >= histogram.size()))
528 {
529 throw std::out_of_range ("histogram peak search parameters out of range");
530 }
531
532 uint64_t nres = 0;
533 for (int i = lowerLimit; i <= upperLimit; ++i)
534 {
535 nres += histogram[i];
536 }
537 return nres;
538}
539
540} /* namespace HistogramAnalyzer */
541
542using HistogramAnalyzer::Histogram;
543
549
550
551} /* namespace Foundation */
552CVB_END_INLINE_NS
553} /* namespace Cvb */
554
Structure that represents an area of interest in the image.
Definition: area_2d.hpp:21
A single histogram result.
Definition: histogram_analyzer.hpp:46
double Mean() const noexcept
Get the mean value of all pixels.
Definition: histogram_analyzer.hpp:119
double Min() const noexcept
Get the minimum gray value of the histogram.
Definition: histogram_analyzer.hpp:139
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:108
int Count() const noexcept
The number of elements corresponds to the number of possible gray values.
Definition: histogram_analyzer.hpp:179
std::vector< uint64_t > Values() const
The actual histogram values.
Definition: histogram_analyzer.hpp:189
double Variance() const noexcept
Get the variance of the histogram.
Definition: histogram_analyzer.hpp:159
uint64_t operator[](int index) const
Gets the histogram value at given index.
Definition: histogram_analyzer.hpp:200
double Max() const noexcept
Get the maximum gray value of the histogram.
Definition: histogram_analyzer.hpp:149
double StandardDeviation() const noexcept
Get the standard deviation of the histogram.
Definition: histogram_analyzer.hpp:169
uint64_t NumPixels() const noexcept
Total number of pixels taken into account.
Definition: histogram_analyzer.hpp:98
double Mode() const noexcept
Get the mode (the most common gray value) of the histogram.
Definition: histogram_analyzer.hpp:129
The Common Vision Blox image.
Definition: decl_image.hpp:45
Image plane information container.
Definition: decl_image_plane.hpp:33
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
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:503
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:525
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:271
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:482
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:316
Root namespace for the Image Manager interface.
Definition: c_barcode.h:24
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:31