40 #include <visp3/visual_features/vpFeatureLuminanceMapping.h>
42 #include <visp3/core/vpSubMatrix.h>
44 #include <visp3/core/vpUniRand.h>
45 #include <visp3/core/vpIoTools.h>
46 #if defined(VISP_HAVE_CATCH2)
48 #include <catch_amalgamated.hpp>
50 #ifdef ENABLE_VISP_NAMESPACE
54 vpMatrix orthogonalBasis(
unsigned n,
unsigned seed)
61 for (
unsigned int row = 0; row < n; ++row) {
63 for (
unsigned int col = 0; col < n; ++col) {
64 basis[row][col] = rand.uniform(-1.0, 1.0);
65 norm += basis[row][col] * basis[row][col];
67 norm = 1.0 / sqrt(norm);
68 for (
unsigned int col = 0; col < n; ++col) {
69 basis[row][col] *= norm;
74 norms[0] = basis.getRow(0).sumSquare();
75 for (
unsigned i = 1; i < n; ++i) {
78 for (
unsigned j = 0; j < i; ++j) {
81 for (
unsigned k = 0; k < n; ++k) {
82 basis[i][k] -= res[k];
87 for (
unsigned int row = 0; row < n; ++row) {
88 double norm = sqrt(norms[row]);
90 for (
unsigned int col = 0; col < n; ++col) {
91 basis[row][col] /= norm;
98 SCENARIO(
"Using PCA features",
"[visual_features]")
100 GIVEN(
"A matrix containing simple data")
102 const unsigned h = 16, w = 16;
103 const unsigned numDataPoints = 4;
104 const unsigned int dataDim = h * w;
105 const unsigned int trueComponents = 3;
108 const vpMatrix orthoFull = (orthogonalBasis(dataDim, 42) +
vpMatrix(dataDim, dataDim, 1.0)) * 127.5;
109 const vpMatrix ortho(orthoFull, 0, 0, trueComponents, dataDim);
110 const vpMatrix coefficients(numDataPoints, trueComponents);
112 for (
unsigned int i = 0; i < coefficients.getRows(); ++i) {
114 for (
unsigned int j = 0; j < coefficients.getCols(); ++j) {
115 coefficients[i][j] = rand.uniform(0.0, 1.0);
116 sum += coefficients[i][j] * coefficients[i][j];
118 const double inv_norm = 1.0 / sqrt(sum);
119 for (
unsigned int j = 0; j < coefficients.getCols(); ++j) {
120 coefficients[i][j] *= inv_norm;
124 vpMatrix data = coefficients * ortho;
126 WHEN(
"Learning PCA basis with too many components")
128 unsigned int k = data.
getCols() + 1;
129 THEN(
"An exception is thrown")
134 WHEN(
"Learning with more images than pixels")
137 THEN(
"An exception is thrown")
142 WHEN(
"Learning PCA basis")
144 for (
unsigned int k = 2; k <= trueComponents; ++k) {
149 THEN(
"Basis has correct dimensions")
152 REQUIRE(basis.
getCols() == dataDim);
154 THEN(
"The basis is orthonormal")
156 const vpMatrix Iapprox = basis * basis.
t();
159 bool matrixSame =
true;
160 for (
unsigned int row = 0; row < I.
getRows(); ++row) {
161 for (
unsigned int col = 0; col < I.
getCols(); ++col) {
162 if (fabs(I[row][col] - Iapprox[row][col]) > 1e-6) {
170 THEN(
"Mean vector has correct dimensions")
172 REQUIRE(pca.
getMean()->getRows() == dataDim);
173 REQUIRE(pca.
getMean()->getCols() == 1);
175 THEN(
"Modifying the basis size (number of inputs) by hand and saving")
182 REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
184 THEN(
"Modifying the mean Columns by hand")
191 std::shared_ptr<vpColVector> mean = pca.
getMean();
192 mean->resize(mean->getRows() + 1,
false);
193 REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
196 THEN(
"Saving and loading pca leads to same basis and mean")
203 pca.
save(basisFile, meanFile, varFile);
209 bool basisSame =
true;
210 bool meanSame =
true;
211 bool explainedVarSame =
true;
213 for (
unsigned int i = 0; i < basisDiff.
getRows(); ++i) {
214 for (
unsigned int j = 0; j < basisDiff.
getCols(); ++j) {
215 if (fabs(basisDiff[i][j]) > 1e-10) {
223 for (
unsigned int i = 0; i < meanDiff.
getRows(); ++i) {
224 if (fabs(meanDiff[i]) > 1e-10) {
225 std::cout << meanDiff << std::endl;
232 for (
unsigned int i = 0; i < explainedVarDiff.
getRows(); ++i) {
233 if (fabs(explainedVarDiff[i]) > 1e-10) {
234 explainedVarSame =
false;
238 REQUIRE(explainedVarSame);
241 THEN(
"Explained variance is below 1 and sorted in descending order")
244 REQUIRE(var.
sum() < 1.0);
245 for (
int i = 1; i < (int)var.
getRows() - 1; ++i) {
246 REQUIRE(var[i] >= var[i + 1]);
249 if (k == trueComponents) {
250 WHEN(
"K is the true manifold dimensionality")
252 THEN(
"explained variance is close to 1")
256 THEN(
"Inverse mapping leads back to the same data")
258 for (
unsigned int i = 0; i < numDataPoints; ++i) {
260 for (
unsigned int j = 0; j < data.
getCols(); ++j) {
261 I.
bitmap[j] =
static_cast<unsigned char>(data[i][j]);
268 for (
unsigned int j = 0; j < data.
getCols(); ++j) {
269 REQUIRE(abs(
static_cast<int>(I.
bitmap[j]) -
static_cast<int>(Irec.
bitmap[j])) < 2);
276 THEN(
"Projecting data is correct")
287 const unsigned border = 3;
298 WHEN(
"Saving unintialized PCA")
305 THEN(
"an exception is thrown")
307 REQUIRE_THROWS(pca.
save(basisFile, meanFile, varFile));
312 #if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_11)
313 SCENARIO(
"Using DCT features",
"[visual_features]")
317 std::vector<std::tuple<vpMatrix, vpColVector, vpMatrix>> data = {
325 { 0.0, 1.0, 3.0, 6.0, 4.0, 2.0, 5.0, 7.0, 8.0 }
334 for (
unsigned int i = 0; i < data.size(); ++i) {
335 WHEN(
"Building the associated zigzag indexing matrix")
338 vpColVector contentAsZigzag = std::get<1>(data[i]);
339 const vpMatrix mAfterWriterVec = std::get<2>(data[i]);
343 THEN(
"Calling getValues with wrong matrix rows throws")
346 REQUIRE_THROWS(zigzag.
getValues(wrongM, 0, 2, s));
348 THEN(
"Calling getValues with wrong matrix cols throws")
351 REQUIRE_THROWS(zigzag.
getValues(wrongM, 0, 2, s));
353 THEN(
"Calling getValues with wrong start and end arguments throws")
355 REQUIRE_THROWS(zigzag.
getValues(m, 2, 1, s));
357 THEN(
"Calling getValues and querying all values returns correct result")
360 REQUIRE(s == contentAsZigzag);
362 THEN(
"Calling getValues and querying a subset of the values is correct")
365 REQUIRE(s == contentAsZigzag.
extract(0, m.
size() / 2));
369 THEN(
"Calling setValues with wrong matrix rows throws")
372 REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, 0, wrongM));
374 THEN(
"Calling setValues with wrong matrix cols throws")
377 REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, 0, wrongM));
380 THEN(
"Calling setValues with wrong start and vector size arguments throws")
382 REQUIRE_THROWS(zigzag.
setValues(contentAsZigzag, m.
size() - contentAsZigzag.
size() + 1, m));
385 THEN(
"Calling setValues leads to expected result")
389 for (
unsigned i = 0; i < powered.
size(); ++i) {
390 powered[i] *= powered[i];
393 REQUIRE_NOTHROW(zigzag.
setValues(powered, 0, mWrite));
394 REQUIRE_NOTHROW(zigzag.
getValues(mWrite, 0, mWrite.size(), poweredRead));
395 REQUIRE(powered == poweredRead);
398 for (
unsigned i = 0; i < powered.
size(); ++i) {
399 indices[i] =
static_cast<double>(i);
402 REQUIRE_NOTHROW(zigzag.
setValues(indices, 0, mWrite));
403 REQUIRE(mWrite == mAfterWriterVec);
410 REQUIRE(s2 == contentAsZigzag.
extract(0, 3));
418 GIVEN(
"A constant image")
421 WHEN(
"Computing DCT")
427 THEN(
"resulting feature vector has correct size")
429 REQUIRE(s.
size() == 32);
431 THEN(
"The only non zero component is the first")
433 REQUIRE(s.
sum() == Catch::Approx(s[0]).margin(1e-5));
439 for (
unsigned i = 0; i < I.
getRows(); ++i) {
440 for (
unsigned j = 0; j < I.
getCols(); ++j) {
441 const int diff = abs(
static_cast<int>(I[i][j]) -
static_cast<int>(Ir[i][j]));
443 INFO(
"i = " + std::to_string(i) +
", j = " + std::to_string(j));
452 int main(
int argc,
char *argv[])
454 Catch::Session session;
455 session.applyCommandLine(argc, argv);
457 int numFailed = session.run();
unsigned int getCols() const
unsigned int size() const
Return the number of elements of the 2D array.
unsigned int getRows() const
Implementation of column vector and the associated operations.
vpColVector extract(unsigned int r, unsigned int colsize) const
unsigned int getCols() const
Type * bitmap
points toward the bitmap
unsigned int getRows() const
Helper class to iterate and get/set the values from a matrix, following a zigzag pattern.
void init(unsigned rows, unsigned cols)
Initialize the ZigZag object. Computes and stores the zigzag indexing for a given matrix size.
void setValues(const vpColVector &s, unsigned int start, vpMatrix &m) const
set the values in the matrix, according to the values stored in the vector s and the zigzag indexing ...
void getValues(const vpMatrix &m, unsigned int start, unsigned int end, vpColVector &s) const
Fill the vector s with (end - start) values, according to the zigzag matrix indexing strategy.
unsigned int getProjectionSize() const
Returns the size of the space to which an image is mapped to.
unsigned int getBorder() const
Returns the number of pixels that are removed by the photometric VS computation.
void setBorder(unsigned border)
Set the number of pixels that are removed by the photometric VS computation This function should be c...
static vpLuminancePCA learn(const std::vector< vpImage< unsigned char >> &images, const unsigned int projectionSize, const unsigned int imageBorder=0)
Compute a new Principal Component Analysis on set of images.
void inverse(const vpColVector &s, vpImage< unsigned char > &I) VP_OVERRIDE
Reconstruct I from a representation s.
void save(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile) const
Save the PCA basis to multiple text files, for later use via the load function.
std::shared_ptr< vpColVector > getMean() const
Get , the mean image computed from the dataset.
void map(const vpImage< unsigned char > &I, vpColVector &s) VP_OVERRIDE
Map an image I to a representation s. This representation s has getProjectionSize() rows.
std::shared_ptr< vpMatrix > getBasis() const
Get , the subspace projection matrix ( )
static vpLuminancePCA load(const std::string &basisFilename, const std::string &meanFileName, const std::string &explainedVarianceFile)
Save the PCA basis to multiple text files, for later use via the load function.
vpColVector getExplainedVariance() const
Get the values of explained variance by each of the eigen vectors.
Implementation of a matrix and operations on matrices.
vpMatrix transpose() const
Implementation of row vector and the associated operations.
Class for generating random numbers with uniform probability density.