Visual Servoing Platform  version 3.6.1 under development (2025-03-16)
vpColorHistogram.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 
31 #include <visp3/rbt/vpColorHistogram.h>
32 
33 #include <visp3/core/vpImage.h>
34 #include <visp3/core/vpRect.h>
35 
36 BEGIN_VISP_NAMESPACE
37 
39 {
40  if (histogram.getBinNumber() != m_N) {
41  throw vpException(vpException::dimensionError, "Different number of bins between builder and histogram when building histogram");
42  }
43  unsigned int count = 0;
44  for (unsigned int i = 0; i < m_counts.size(); ++i) {
45  count += m_counts[i];
46  }
47  const float countFloat = static_cast<float>(count);
48  for (unsigned int i = 0; i < m_counts.size(); ++i) {
49  histogram.m_probas[i] = static_cast<float>(m_counts[i]) / countFloat;
50  }
51  histogram.m_numPixels = count;
52 }
53 
54 vpColorHistogram::vpColorHistogram() : m_N(0), m_binSize(0), m_numPixels(0)
55 { }
56 
58 {
59  setBinNumber(N);
60 }
61 
62 void vpColorHistogram::setBinNumber(unsigned int N)
63 {
64  if (N != 1 && N != 2 && N != 4 && N != 8 && N != 16 && N != 32 && N != 64 && N != 128) {
65  throw vpException(vpException::badValue, "The number of bins per component should be a power of 2 (below or equal to 128)");
66  }
67  m_N = N;
68  m_binSize = 256 / m_N;
69  m_numPixels = 0;
70  m_probas = std::vector<float>(N * N * N, 0.f);
71 }
72 
73 
74 
75 void vpColorHistogram::build(const vpImage<vpRGBa> &image, const vpImage<bool> &mask)
76 {
77  std::vector<unsigned int> histo(m_N * m_N * m_N, 0);
78  m_probas.resize(m_N * m_N * m_N);
79  unsigned int pixels = 0;
80  for (unsigned int i = 0; i < image.getSize(); ++i) {
81  if (mask.bitmap[i]) {
82  unsigned int index = colorToIndex(image.bitmap[i]);
83  ++histo[index];
84  ++pixels;
85  }
86  }
87  m_numPixels = pixels;
88  for (unsigned int i = 0; i < histo.size(); ++i) {
89  m_probas[i] = static_cast<float>(histo[i]) / pixels;
90  }
91 }
92 
93 void vpColorHistogram::build(const std::vector<unsigned int> &counts)
94 {
95  if (m_probas.size() != counts.size()) {
96  throw vpException(vpException::dimensionError, "Number of bins are not the same");
97  }
98  m_probas.resize(m_N * m_N * m_N);
99  m_numPixels = 0;
100  for (unsigned int count : counts) {
101  m_numPixels += count;
102  }
103  for (unsigned int i = 0; i < m_probas.size(); ++i) {
104  m_probas[i] = static_cast<float>(counts[i]) / m_numPixels;
105  }
106 }
107 
108 void vpColorHistogram::merge(const vpColorHistogram &other, float alpha)
109 {
110  if (other.m_N != m_N) {
111  throw vpException(vpException::badValue, "Histograms should have same dimensions");
112  }
113  float malpha = 1.f - alpha;
114 
115  for (unsigned int i = 0; i < m_probas.size(); ++i) {
116  m_probas[i] = malpha * m_probas[i] + alpha * other.m_probas[i];
117  }
118 
119 }
120 
122 {
123  proba.resize(image.getHeight(), image.getWidth());
124 
125 #pragma omp parallel for schedule(static)
126  for (unsigned int i = 0; i < image.getSize(); ++i) {
127  proba.bitmap[i] = m_probas[colorToIndex(image.bitmap[i])];
128  }
129 }
130 
131 void vpColorHistogram::computeProbas(const vpImage<vpRGBa> &image, vpImage<float> &proba, const vpRect &bb) const
132 {
133  proba.resize(image.getHeight(), image.getWidth(), 0.f);
134  const int h = static_cast<int>(image.getHeight()), w = static_cast<int>(image.getWidth());
135  const int top = static_cast<int>(bb.getTop());
136  const int left = static_cast<int>(bb.getLeft());
137  const int bottom = std::min(h- 1, static_cast<int>(bb.getBottom()));
138  const int right = std::min(w - 1, static_cast<int>(bb.getRight()));
139 #pragma omp parallel for
140  for (unsigned int i = top; i <= static_cast<unsigned int>(bottom); ++i) {
141  const vpRGBa *colorRow = image[i];
142  float *probaRow = proba[i];
143  for (unsigned int j = left; j <= static_cast<unsigned int>(right); ++j) {
144  probaRow[j] = m_probas[colorToIndex(colorRow[j])];
145  }
146  }
147 }
148 
149 
150 
151 double vpColorHistogram::kl(const vpColorHistogram &other) const
152 {
153  if (other.m_N != m_N) {
154  throw vpException(vpException::badValue, "Histograms should have same dimensions");
155  }
156  double divergence = 0.0;
157  for (unsigned int i = 0; i < m_probas.size(); ++i) {
158  if (other.m_probas[i] > 0.0 && m_probas[i] > 0.0) {
159  divergence += m_probas[i] * log(m_probas[i] / other.m_probas[i]);
160  }
161  }
162  return divergence;
163 }
164 
165 double vpColorHistogram::jsd(const vpColorHistogram &other) const
166 {
167  vpColorHistogram mixture(m_N);
168 
169  for (unsigned int i = 0; i < m_probas.size(); ++i) {
170  mixture.m_probas[i] = m_probas[i] * 0.5 + other.m_probas[i] * 0.5;
171  }
172  // JSD = 0.5KL(P || M) + 0.5(Q||M) where M is the average mixture distrib of P and Q
173  return (kl(mixture) + other.kl(mixture)) / 2.0;
174 }
175 
177 {
178  double bcoeff = 0.0;
179 
180  for (unsigned int i = 0; i < m_probas.size(); ++i) {
181  bcoeff += sqrt(m_probas[i] * other.m_probas[i]);
182  }
183 
184  return sqrt(1.0 - bcoeff);
185 }
186 
188 {
189  if (insideMask.m_N != outsideMask.m_N) {
190  throw vpException(vpException::badValue, "Histograms should have same number of bins");
191  }
192 
193  unsigned int bins = insideMask.m_probas.size();
194 
195  std::vector<unsigned int> countsIn(bins, 0), countsOut(bins, 0);
196 
197 //#pragma omp parallel
198  {
199  std::vector<unsigned int>localCountsIn(bins, 0), localCountsOut(bins, 0);
200 //#pragma omp for schedule(static, 1024)
201  for (unsigned int i = 0; i < image.getSize(); ++i) {
202  unsigned int index = insideMask.colorToIndex(image.bitmap[i]);
203  localCountsIn[index] += mask.bitmap[i] > 0;
204  localCountsOut[index] += mask.bitmap[i] == 0;
205  }
206 //#pragma omp critical
207  {
208  for (unsigned int i = 0; i < bins; ++i) {
209  countsIn[i] += localCountsIn[i];
210  countsOut[i] += localCountsOut[i];
211  }
212  }
213  }
214  insideMask.build(countsIn);
215  outsideMask.build(countsOut);
216 }
217 
218 void vpColorHistogram::computeSplitHistograms(const vpImage<vpRGBa> &image, const vpImage<bool> &mask, const vpRect &bbInside, vpColorHistogram &insideMask, vpColorHistogram &outsideMask)
219 {
220  if (insideMask.m_N != outsideMask.m_N) {
221  throw vpException(vpException::badValue, "Histograms should have same number of bins");
222  }
223 
224  const unsigned int bins = insideMask.m_probas.size();
225 
226  std::vector<unsigned int> countsIn(bins, 0), countsOut(bins, 0);
227 
228  const unsigned int beforeBBStart = static_cast<unsigned int>(bbInside.getTop()) * image.getWidth() + static_cast<unsigned int>(bbInside.getLeft());
229  const unsigned int afterBBEnd = static_cast<unsigned int>(bbInside.getBottom()) * image.getWidth() + static_cast<unsigned int>(bbInside.getRight());
230 
231 #pragma omp parallel
232  {
233  std::vector<unsigned int>localCountsIn(bins, 0), localCountsOut(bins, 0);
234 #pragma omp for schedule(static, 64)
235  for (unsigned int i = 0; i < beforeBBStart; ++i) {
236  const unsigned int index = insideMask.colorToIndex(image.bitmap[i]);
237  ++localCountsOut[index];
238  }
239 #pragma omp for schedule(static, 64)
240  for (unsigned int i = afterBBEnd; i < image.getSize(); ++i) {
241  const unsigned int index = insideMask.colorToIndex(image.bitmap[i]);
242  ++localCountsOut[index];
243  }
244 #pragma omp for schedule(static, 64)
245  for (unsigned int i = static_cast<unsigned int>(bbInside.getTop()); i < static_cast<unsigned int>(round(bbInside.getBottom())); ++i) {
246  for (unsigned int j = static_cast<unsigned int>(bbInside.getLeft()); j < static_cast<unsigned int>(round(bbInside.getRight())); ++j) {
247  const unsigned int bitmapIndex = i * image.getWidth() + j;
248  const unsigned int index = insideMask.colorToIndex(image.bitmap[bitmapIndex]);
249  const bool pixelInMask = mask.bitmap[bitmapIndex] > 0;
250  localCountsIn[index] += static_cast<unsigned int>(pixelInMask);
251  localCountsOut[index] += static_cast<unsigned int>(!pixelInMask);
252  }
253  }
254 #pragma omp critical
255  {
256  for (unsigned int i = 0; i < bins; ++i) {
257  countsIn[i] += localCountsIn[i];
258  countsOut[i] += localCountsOut[i];
259  }
260  }
261  }
262  insideMask.build(countsIn);
263  outsideMask.build(countsOut);
264 
265 }
266 
267 END_VISP_NAMESPACE
void build(vpColorHistogram &histogram)
double kl(const vpColorHistogram &other) const
void build(const vpImage< vpRGBa > &image, const vpImage< bool > &mask)
double hellinger(const vpColorHistogram &other) const
unsigned int getBinNumber() const
static void computeSplitHistograms(const vpImage< vpRGBa > &image, const vpImage< bool > &mask, vpColorHistogram &inMask, vpColorHistogram &outsideMask)
void setBinNumber(unsigned int N)
Change the number of bins per color component that the histogram has After calling this method,...
void computeProbas(const vpImage< vpRGBa > &image, vpImage< float > &proba) const
unsigned int colorToIndex(const vpRGBa &p) const
void merge(const vpColorHistogram &other, float alpha)
double jsd(const vpColorHistogram &other) const
error that can be emitted by ViSP classes.
Definition: vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition: vpException.h:73
@ dimensionError
Bad dimension.
Definition: vpException.h:71
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
unsigned int getSize() const
Definition: vpImage.h:221
Type * bitmap
points toward the bitmap
Definition: vpImage.h:135
unsigned int getHeight() const
Definition: vpImage.h:181
Definition: vpRGBa.h:70
Defines a rectangle in the plane.
Definition: vpRect.h:79
double getLeft() const
Definition: vpRect.h:173
double getRight() const
Definition: vpRect.h:179
double getBottom() const
Definition: vpRect.h:97
double getTop() const
Definition: vpRect.h:192