Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
catchRBTDataset.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 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 * Test vpMbGenericTracker JSON parse / save.
32 */
33
39
40#include <visp3/core/vpConfig.h>
41
42#if defined(VISP_HAVE_CATCH2) && defined(VISP_HAVE_DATASET) && (VISP_HAVE_DATASET_VERSION >= 0x030702)
43
44#include <visp3/core/vpIoTools.h>
45#include <visp3/core/vpImageConvert.h>
46#include <visp3/io/vpImageIo.h>
47#include <visp3/rbt/vpRBTracker.h>
48
49#include <visp3/rbt/vpRBSilhouetteMeTracker.h>
50#include <visp3/rbt/vpRBSilhouetteCCDTracker.h>
51#include <visp3/rbt/vpRBKltTracker.h>
52#include <visp3/rbt/vpRBDenseDepthTracker.h>
53#include <visp3/ar/vpPanda3DFrameworkManager.h>
54
55#include "test_utils.h"
56
57#if defined(VISP_HAVE_NLOHMANN_JSON)
58#include VISP_NLOHMANN_JSON(json.hpp)
59#endif
60
61#define CATCH_CONFIG_RUNNER
62#include <catch_amalgamated.hpp>
63
64#ifdef ENABLE_VISP_NAMESPACE
65using namespace VISP_NAMESPACE_NAME;
66#endif
67
68bool opt_no_display = false; // If true, disable display or tests requiring display
69
70struct SequenceFrame
71{
72 vpImage<unsigned char> I;
73 vpImage<vpRGBa> IRGB;
74 vpImage<float> depth;
75};
76
77class Sequence
78{
79public:
80
81 Sequence(const std::string &path)
82 {
83 m_path = path;
84 loadCameraSettings();
85 loadFrames();
86 loadGroundTruth();
87 }
88
89 unsigned int getImageHeight() const { return m_h; }
90 unsigned int getImageWidth() const { return m_w; }
91 vpCameraParameters cam() const { return m_cam; }
92
93 void loadGroundTruth()
94 {
95 const std::string &groundTruthPath = vpIoTools::createFilePath(m_path, "apriltag_data.json");
96 std::ifstream f(groundTruthPath);
97 if (!f.good()) {
98 throw vpException(vpException::ioError, "Could not open ground truth file %s", groundTruthPath.c_str());
99 }
100 nlohmann::json j = nlohmann::json::parse(f);
101 m_cMg = j.at("grid");
102 // std::cout << "Loaded ground truth data from apriltag grid: " << std::endl;
103 // for (const vpHomogeneousMatrix &cMg: m_cMg) {
104 // std::cout << vpPoseVector(cMg).t() << std::endl;
105 // }
106 f.close();
107 }
108
109 void loadCameraSettings()
110 {
111 const std::string camFile = vpIoTools::createFilePath(m_path, "cam.json");
112 if (!vpIoTools::checkFilename(camFile)) {
113 throw vpException(vpException::ioError, "Camera file %s does not exist", camFile.c_str());
114 }
115 std::ifstream cf(camFile);
116
117 if (!cf.good()) {
118 throw vpException(vpException::ioError, "Problem opening %s", camFile.c_str());
119 }
120 const nlohmann::json j = nlohmann::json::parse(cf);
121 m_cam.initPersProjWithoutDistortion(j.at("px"), j.at("py"), j.at("u0"), j.at("v0"));
122 m_h = j.at("h"), m_w = j.at("w");
123 m_depthScale = j.at("depthScale");
124 }
125
126 const std::string getColorFrame(unsigned int index)
127 {
128 std::stringstream colorName;
129 colorName << "color_image_" << std::setfill('0') << std::setw(4) << index << ".png";
130 return vpIoTools::createFilePath(m_path, colorName.str());
131 }
132
133 const std::string getDepthFrame(unsigned int index)
134 {
135 std::stringstream colorName;
136 colorName << "depth_image_" << std::setfill('0') << std::setw(4) << index << ".npz";
137 return vpIoTools::createFilePath(m_path, colorName.str());
138 }
139
140 void initTracker(vpRBTracker &tracker)
141 {
142 tracker.setCameraParameters(m_cam, m_h, m_w);
143 }
144
145 void loadFrames()
146 {
147 unsigned int frameIndex = 0;
148 while (true) {
149 SequenceFrame frame;
150 const std::string colorFramePath = getColorFrame(frameIndex);
151 if (!vpIoTools::checkFilename(colorFramePath)) {
152 break;
153 }
154 vpImageIo::read(frame.IRGB, colorFramePath);
155 vpImageConvert::convert(frame.IRGB, frame.I);
156
157 const std::string depthFramePath = getDepthFrame(frameIndex);
158 if (vpIoTools::checkFilename(depthFramePath)) {
159 visp::cnpy::NpyArray depth_data = visp::cnpy::npz_load(depthFramePath).find("data")->second;
160 vpImage<uint16_t> depthRaw(depth_data.data<uint16_t>(), m_h, m_w, true);
161 frame.depth.resize(m_h, m_w);
162 for (unsigned int i = 0; i < m_h * m_w; ++i) {
163 frame.depth.bitmap[i] = static_cast<float>(depthRaw.bitmap[i]) * m_depthScale;
164 }
165 }
166
167 m_frames.push_back(frame);
168 ++frameIndex;
169 }
170 }
171
172 unsigned int numFrames() const
173 {
174 return m_frames.size();
175 }
176 SequenceFrame getFrame(unsigned int index) const
177 {
178 return m_frames[index];
179 }
180
181 std::map<std::string, vpHomogeneousMatrix> getInitialPoses(vpRBTracker &tracker, const std::string &initsFolder, const std::string &objectsFolder, const std::vector<std::string> &objectNames)
182 {
183 std::map<std::string, vpHomogeneousMatrix> result;
184 for (const std::string &objectName: objectNames) {
185 const std::string objectInitFile = vpIoTools::createFilePath(initsFolder, objectName + ".json");
186 const std::string objectFolder = vpIoTools::createFilePath(objectsFolder, objectName);
187 const std::string objectClickInitFile = vpIoTools::createFilePath(objectFolder, objectName + ".init");
188
189 if (vpIoTools::checkFilename(objectInitFile)) {
190 std::ifstream f(objectInitFile);
191 if (f.good()) {
192 vpHomogeneousMatrix cMo = nlohmann::json::parse(f);
193 result[objectName] = cMo;
194 }
195 else {
196 throw vpException(vpException::ioError, "There was an issue opening init file %s", objectInitFile.c_str());
197 }
198 f.close();
199 }
200 else {
201 tracker.setModelPath(vpIoTools::createFilePath(objectFolder, objectName + ".obj"));
202 tracker.startTracking();
203 tracker.initClick(m_frames[0].IRGB, objectClickInitFile, true);
204 vpHomogeneousMatrix cMo;
205 tracker.getPose(cMo);
206 std::ofstream f(objectInitFile);
207 if (f.good()) {
208 nlohmann::json j = cMo;
209 f << j.dump(2);
210 }
211 }
212 }
213 return result;
214 }
215
216 vpHomogeneousMatrix getGroundTruthGridPose(unsigned int index) const
217 {
218 return m_cMg[index];
219 }
220
221private:
222 std::string m_path;
223 vpCameraParameters m_cam;
224 float m_depthScale;
225 unsigned int m_h, m_w;
226 std::vector<SequenceFrame> m_frames;
227 std::vector<vpHomogeneousMatrix> m_cMg;
228};
229
230struct RunData
231{
232 RunData(const std::string &config, double errorT, double errorR) : configName(config), thresholdErrorT(errorT), thresholdErrorR(errorR) { }
233 const std::string configName;
234 const double thresholdErrorT;
235 const double thresholdErrorR;
236
237};
238
239SCENARIO("Running tracker on sequences with ground truth", "[rbt]")
240{
241 if (opt_no_display) {
242 std::cout << "Display is disabled for tests, skipping..." << std::endl;
243 }
244 else {
245 const std::string datasetPath = vpIoTools::getViSPImagesDataPath();
246 const std::string rbtDatasetPath = vpIoTools::createFilePath(datasetPath, "rbt");
247 const std::string sequencePath = vpIoTools::createFilePath(rbtDatasetPath, "sequence");
248 const std::string initsFolder = vpIoTools::createFilePath(sequencePath, "init");
249 const std::string modelsPath = vpIoTools::createFilePath(rbtDatasetPath, "models");
250 const std::string configsPath = vpIoTools::createFilePath(rbtDatasetPath, "configs");
251
252 GIVEN("A sequence")
253 {
254 Sequence sequence(sequencePath);
256 sequence.initTracker(tracker);
257 unsigned int h = sequence.getImageHeight(), w = sequence.getImageWidth();
258
259 vpImage<unsigned char> displayI(h, w), displayDepth(h, w, 255), displayMask(h, w);
260 vpImage<vpRGBa> displayRGB(h, w);
261
262 std::vector<std::shared_ptr<vpDisplay>> displays = vpDisplayFactory::makeDisplayGrid(2, 2, 0, 0, 70, 70,
263 "Gray", displayI,
264 "Color", displayRGB,
265 "Depth", displayDepth,
266 "Mask", displayMask
267 );
268
269 const std::vector<std::string> objectNames = { "dragon", "cube", "stomach", "lower_teeth" };
270
271 std::map<std::string, vpHomogeneousMatrix> init_cMos = sequence.getInitialPoses(tracker, initsFolder, modelsPath, objectNames);
272 const double EPSILON_M = 0.002, EPSILON_DEG = 0.2;
273 const std::map<std::string, std::vector<RunData>> configMap = {
274 { "dragon", {
275 RunData("ccd.json", 0.03, 10.0),
276 RunData("ccd-temporal-smoothing.json", 0.03, 27.0),
277 RunData("depth-ccd-mask.json", 0.025, 8.0),
278 RunData("depth-ccd.json", 0.025, 8.0),
279#ifdef VP_HAVE_RB_KLT_TRACKER
280 RunData("depth-klt-ccd-mask.json", 0.02, 5.0),
281 RunData("depth-klt.json", 0.02, 5.5),
282 RunData("depth-klt-me-multi.json", 0.02, 5.0),
283 RunData("depth-klt-me-single.json", 0.025, 10.0),
284#endif
285 }
286 },
287 { "cube", {
288 RunData("ccd-temporal-smoothing.json", 0.03, 10.0),
289 RunData("ccd.json", 0.03, 10.0),
290 RunData("depth-ccd.json", 0.03, 10.0),
291 RunData("depth-ccd-mask.json", 0.03, 10.0),
292#ifdef VP_HAVE_RB_KLT_TRACKER
293 RunData("depth-klt.json", 0.03, 10.0),
294 RunData("depth-klt-ccd-mask.json", 0.03, 5.0),
295 RunData("depth-klt-me-multi.json", 0.03, 5.0),
296 RunData("depth-klt-me-single.json", 0.03, 10.0),
297#endif
298 }
299 },
300 { "stomach", {
301 RunData("ccd-temporal-smoothing.json", 0.02, 10.0),
302 RunData("ccd.json", 0.02, 4.0),
303 RunData("depth-ccd.json", 0.025, 5.0),
304 RunData("depth-ccd-mask.json", 0.03, 10.0),
305#ifdef VP_HAVE_RB_KLT_TRACKER
306 RunData("depth-klt-ccd-mask.json", 0.03, 5.0),
307 RunData("depth-klt.json", 0.03, 3.0),
308 RunData("depth-klt-me-multi.json", 0.03, 5.0),
309 RunData("depth-klt-me-single.json", 0.03, 10.0),
310#endif
311
312 }
313 },
314 };
315
316 for (const std::string &objectName: objectNames) {
317
318 const std::string modelFolder = vpIoTools::createFilePath(modelsPath, objectName);
319 const std::string modelPath = vpIoTools::createFilePath(modelFolder, objectName + ".obj");
320 tracker.setModelPath(modelPath);
321 const auto configsIt = configMap.find(objectName);
322 if (configsIt == configMap.end()) {
323 std::cout << "No config found for " << objectName << std::endl;
324 continue;
325 }
326 const std::vector<RunData> objectConfigs = configsIt->second;
327
328 for (const RunData &runConfig: objectConfigs) {
329 const std::string configName = runConfig.configName;
330 std::cout << "Running tracker on object " << objectName << " with configuration " << configName << std::endl;
331
332 const std::string configPath = vpIoTools::createFilePath(configsPath, configName);
333 tracker.loadConfigurationFile(configPath);
334 tracker.reset();
335 tracker.startTracking();
336 tracker.setPose(init_cMos.find(objectName)->second);
337 std::vector<vpHomogeneousMatrix> poses;
338
339 for (unsigned int i = 0; i < sequence.numFrames(); ++i) {
340 SequenceFrame frame = sequence.getFrame(i);
341 vpRBTrackingResult result = tracker.track(frame.I, frame.IRGB, frame.depth);
343 tracker.getPose(cMo);
344 poses.push_back(cMo);
345
346 displayI = frame.I;
347 displayRGB = frame.IRGB;
348 for (unsigned int j = 0; j < frame.depth.getSize(); ++j) {
349 displayDepth.bitmap[j] = static_cast<unsigned char>(std::min(frame.depth.bitmap[j], 1.f) * 255.f);
350 }
351
352
353 vpDisplay::display(displayI); vpDisplay::display(displayRGB); vpDisplay::display(displayDepth);
354 tracker.display(displayI, displayRGB, displayDepth);
355 vpDisplay::displayFrame(displayI, cMo, sequence.cam(), 0.05, vpColor::none);
356 vpDisplay::displayFrame(displayI, sequence.getGroundTruthGridPose(i), sequence.cam(), 0.05, vpColor::yellow);
357
358 tracker.displayMask(displayMask);
359 vpDisplay::flush(displayI);
360 vpDisplay::flush(displayRGB);
361 vpDisplay::flush(displayDepth);
362 vpDisplay::flush(displayMask);
363 }
364 const vpHomogeneousMatrix init_cMo = init_cMos.find(objectName)->second;
365 const vpHomogeneousMatrix init_cMg = sequence.getGroundTruthGridPose(0);
366 std::vector<vpHomogeneousMatrix> first_gMos;
367 for (unsigned int i = 0; i < 10; ++i) {
368 first_gMos.push_back(sequence.getGroundTruthGridPose(0).inverse() * poses[i]);
369 }
370 const vpHomogeneousMatrix gMo = vpHomogeneousMatrix::mean(first_gMos);
371 std::vector<double> errorsT, errorsR;
372 for (unsigned int i = 0; i < sequence.numFrames(); ++i) {
373 const vpHomogeneousMatrix cMo_star = sequence.getGroundTruthGridPose(i) * gMo;
374 const vpHomogeneousMatrix cMo = poses[i];
375 const vpHomogeneousMatrix error = cMo_star.inverse() * cMo;
376 const double errorT = error.getTranslationVector().frobeniusNorm();
377 const double errorR = vpMath::deg(error.getThetaUVector().getTheta());
378 errorsT.push_back(errorT);
379 errorsR.push_back(errorR);
380 }
381 std::sort(errorsT.begin(), errorsT.end()); std::sort(errorsR.begin(), errorsR.end());
382 unsigned int index90p = static_cast<unsigned int>(round(0.9 * errorsT.size()));
383 double error90pT = errorsT[index90p], error90pR = errorsR[index90p];
384
385 std::cout << error90pT << "m, " << error90pR << "°" << std::endl;
386
387 if (error90pT > (runConfig.thresholdErrorT + EPSILON_M) || error90pR > (runConfig.thresholdErrorR + EPSILON_DEG)) {
388 std::stringstream ss;
389 ss << "Using object " << objectName << " with config " << configName << ":" << std::endl;
390 ss << "\tMaximum tolerated median error:\t" << runConfig.thresholdErrorT << "m, " << runConfig.thresholdErrorR << "°" << std::endl;
391 ss << "\tActual median error:\t\t\t" << error90pT << "m, " << error90pR << "°" << std::endl;
392
393 FAIL(ss.str());
394 }
395 }
396 }
397 }
398 }
399}
400
401int main(int argc, char *argv[])
402{
403 Catch::Session session; // There must be exactly one instance
404 auto cli = session.cli()
405 | Catch::Clara::Opt(opt_no_display)["--no-display"]("Disable display");
406 session.cli(cli);
407
408 const int returnCode = session.applyCommandLine(argc, argv);
409 if (returnCode != 0) { // Indicates a command line error
410 return returnCode;
411 }
412
413 const int numFailed = session.run();
415 return numFailed;
416}
417
418#else
419
420int main()
421{
422 return EXIT_SUCCESS;
423}
424
425#endif
static const vpColor none
Definition vpColor.h:210
static const vpColor yellow
Definition vpColor.h:206
static void display(const vpImage< unsigned char > &I)
static void displayFrame(const vpImage< unsigned char > &I, const vpHomogeneousMatrix &cMo, const vpCameraParameters &cam, double size, const vpColor &color=vpColor::none, unsigned int thickness=1, const vpImagePoint &offset=vpImagePoint(0, 0), const std::string &frameName="", const vpColor &textColor=vpColor::black, const vpImagePoint &textOffset=vpImagePoint(15, 15))
static void flush(const vpImage< unsigned char > &I)
@ ioError
I/O error.
Definition vpException.h:67
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpHomogeneousMatrix inverse() const
static vpHomogeneousMatrix mean(const std::vector< vpHomogeneousMatrix > &vec_M)
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Definition of the vpImage class member functions.
Definition vpImage.h:131
static std::string getViSPImagesDataPath()
static bool checkFilename(const std::string &filename)
static std::string createFilePath(const std::string &parent, const std::string &child)
static double deg(double rad)
Definition vpMath.h:119
static vpPanda3DFrameworkManager & getInstance()
Class implementing the Render-Based Tracker (RBT).
Definition vpRBTracker.h:87
VISP_EXPORT npz_t npz_load(const std::string &fname)
std::vector< std::shared_ptr< vpDisplay > > makeDisplayGrid(unsigned int rows, unsigned int cols, unsigned int startX, unsigned int startY, unsigned int paddingX, unsigned int paddingY, Args &... args)
Create a grid of displays, given a set of images. All the displays will be initialized in the correct...