Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
tutorial-pose-from-planar-object.cpp
1
2
3// Core
4#include <visp3/core/vpColorDepthConversion.h>
5#include <visp3/core/vpConfig.h>
6#include <visp3/core/vpIoTools.h>
7#include <visp3/core/vpMeterPixelConversion.h>
8#include <visp3/core/vpXmlParserCamera.h>
9
10// Vision
11#include <visp3/vision/vpPlaneEstimation.h>
12#include <visp3/vision/vpPose.h>
13
14// IO
15#include <visp3/io/vpImageIo.h>
16
17// GUI
18#include <visp3/gui/vpDisplayD3D.h>
19#include <visp3/gui/vpDisplayGDI.h>
20#include <visp3/gui/vpDisplayGTK.h>
21#include <visp3/gui/vpDisplayOpenCV.h>
22#include <visp3/gui/vpDisplayX.h>
23
24// Check if std:c++17 or higher
25#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L))) && defined(VISP_HAVE_DISPLAY) && defined(VISP_HAVE_PUGIXML)
26
27#ifdef ENABLE_VISP_NAMESPACE
28using namespace VISP_NAMESPACE_NAME;
29#endif
30
31// Local helper
32namespace
33{
34// Display
35#if defined(VISP_HAVE_X11)
36using Display = vpDisplayX;
37#elif defined(VISP_HAVE_GDI)
38using Display = vpDisplayGDI;
39#elif defined(VISP_HAVE_OPENCV)
40using Display = vpDisplayOpenCV;
41#elif defined(VISP_HAVE_GTK)
42using Display = vpDisplayGTK;
43#elif defined(VISP_HAVE_D3D9)
44using Display = vpDisplayD3D;
45#endif
46
47constexpr auto DispScaleType { vpDisplay::SCALE_AUTO };
48
49// Model
50constexpr auto ModelCommentHeader { "#" };
51constexpr auto ModelKeypointsHeader { "Keypoints" };
52constexpr auto ModelBoundsHeader { "Bounds" };
53constexpr auto ModelDataHeader { "data:" };
54
55// Depth
56constexpr auto DepthScale { 0.001 };
57} // namespace
58
59#ifndef DOXYGEN_SHOULD_SKIP_THIS
60class Model
61{
62public:
63 Model() = delete;
64 ~Model() = default;
65 Model(const Model &) = default;
66 Model(Model &&) = default;
67 Model &operator=(const Model &) = default;
68 Model &operator=(Model &&) = default;
69
70 explicit Model(const std::string &model_filename);
71
72public:
73 using Id = unsigned long int;
74 static inline std::string to_string(const Id &id) { return std::to_string(id); };
75
76 std::map<Id, vpPoint> keypoints(const vpHomogeneousMatrix &cMo = {}) const;
77 std::map<Id, vpPoint> bounds(const vpHomogeneousMatrix &cMo = {}) const;
78
79private:
80 std::map<Id, vpPoint> m_keypoints {};
81 std::map<Id, vpPoint> m_bounds {};
82};
83
84inline Model::Model(const std::string &model_filename)
85{
86 std::fstream file;
87 file.open(model_filename.c_str(), std::fstream::in);
88
89 std::string line {}, subs {};
90 bool in_model_bounds { false };
91 bool in_model_keypoints { false };
92 unsigned int data_curr_line { 0 };
93 unsigned int data_line_start_pos { 0 };
94
95 auto reset = [&]() {
96 in_model_bounds = false;
97 in_model_keypoints = false;
98 data_curr_line = 0;
99 data_line_start_pos = 0;
100 };
101
102 while (getline(file, line)) {
103 if (line.substr(0, std::string(ModelCommentHeader).size()) == ModelCommentHeader || line == ModelDataHeader) {
104 continue;
105 }
106 else if (line == ModelBoundsHeader) {
107 reset();
108 in_model_bounds = true;
109 continue;
110 }
111 else if (line == ModelKeypointsHeader) {
112 reset();
113 in_model_keypoints = true;
114 continue;
115 }
116
117 if (data_curr_line == 0) {
118 // Get indentation level which is common to all lines
119 data_line_start_pos = static_cast<unsigned int>(line.find("[")) + 1;
120 }
121
122 try {
123 std::stringstream ss(line.substr(data_line_start_pos, line.find("]") - data_line_start_pos));
124 unsigned int data_on_curr_line = 0;
125 vpColVector oXYZ({ 0, 0, 0, 1 });
126 while (getline(ss, subs, ',')) {
127 oXYZ[data_on_curr_line++] = std::atof(subs.c_str());
128 }
129 if (in_model_bounds) {
130 m_bounds.try_emplace(data_curr_line, oXYZ[0], oXYZ[1], oXYZ[2]);
131 }
132 else if (in_model_keypoints) {
133 m_keypoints.try_emplace(data_curr_line, oXYZ[0], oXYZ[1], oXYZ[2]);
134 }
135 data_curr_line++;
136 }
137 catch (...) {
138 // Line is empty or incomplete. We skeep it
139 }
140 }
141
142 file.close();
143}
144
145inline std::map<Model::Id, vpPoint> Model::bounds(const vpHomogeneousMatrix &cMo) const
146{
147 auto bounds = m_bounds;
148 std::for_each(begin(bounds), end(bounds), [&cMo](auto &bound) { bound.second.project(cMo); });
149
150 return bounds;
151}
152
153inline std::map<Model::Id, vpPoint> Model::keypoints(const vpHomogeneousMatrix &cMo) const
154{
155 auto keypoints = m_keypoints;
156 std::for_each(begin(keypoints), end(keypoints), [&cMo](auto &keypoint) { keypoint.second.project(cMo); });
157
158 return keypoints;
159}
160
161std::ostream &operator<<(std::ostream &os, const Model &model)
162{
163 os << "-Bounds:" << std::endl;
164 for (const auto &[id, bound] : model.bounds()) {
165 // clang-format off
166 os << std::setw(4) << std::setfill(' ') << id << ": "
167 << std::setw(6) << std::setfill(' ') << bound.get_X() << ", "
168 << std::setw(6) << std::setfill(' ') << bound.get_Y() << ", "
169 << std::setw(6) << std::setfill(' ') << bound.get_Z() << std::endl;
170 // clang-format on
171 }
172
173 os << "-Keypoints:" << std::endl;
174 for (const auto &[id, keypoint] : model.keypoints()) {
175 // clang-format off
176 os << std::setw(4) << std::setfill(' ') << id << ": "
177 << std::setw(6) << std::setfill(' ') << keypoint.get_X() << ", "
178 << std::setw(6) << std::setfill(' ') << keypoint.get_Y() << ", "
179 << std::setw(6) << std::setfill(' ') << keypoint.get_Z() << std::endl;
180 // clang-format on
181 }
182
183 return os;
184}
185
187readData(const std::string &input_directory, const unsigned int cpt = 0)
188{
189 const std::string filename_color = vpIoTools::formatString(input_directory + "/color_image_%04d.jpg", cpt);
190 const std::string filename_depth = vpIoTools::formatString(input_directory + "/depth_image_%04d.bin", cpt);
191
192 // Read color
193 vpImage<vpRGBa> I_color {};
194 vpImageIo::read(I_color, filename_color);
195
196 // Read raw depth
197 vpImage<uint16_t> I_depth_raw {};
198 std::ifstream file_depth(filename_depth.c_str(), std::ios::in | std::ios::binary);
199 if (file_depth.is_open()) {
200 unsigned int height = 0, width = 0;
201 vpIoTools::readBinaryValueLE(file_depth, height);
202 vpIoTools::readBinaryValueLE(file_depth, width);
203 I_depth_raw.resize(height, width);
204
205 for (auto i = 0u; i < height; i++) {
206 for (auto j = 0u; j < width; j++) {
207 vpIoTools::readBinaryValueLE(file_depth, I_depth_raw[i][j]);
208 }
209 }
210 }
211
212 // Read camera parameters (intrinsics)
213 std::stringstream ss;
214 ss << input_directory << "/camera.xml";
215
217 vpCameraParameters color_param {}, depth_param {};
218 parser.parse(color_param, ss.str(), "color_camera", vpCameraParameters::perspectiveProjWithDistortion);
219 parser.parse(depth_param, ss.str(), "depth_camera", vpCameraParameters::perspectiveProjWithDistortion);
220
221 // Read camera parameters (extrinsics)
222 ss.str("");
223 ss << input_directory << "/depth_M_color.txt";
224 std::ifstream file_depth_M_color(ss.str().c_str(), std::ios::in | std::ios::binary);
225
227 depth_M_color.load(file_depth_M_color);
228
229 return { I_color, I_depth_raw, color_param, depth_param, depth_M_color };
230}
231
232std::vector<vpImagePoint> getRoiFromUser(vpImage<vpRGBa> color_img)
233{
234 // Init displays
235 Display disp_color(color_img, 0, 0, "Roi bounds", DispScaleType);
236 disp_color.display(color_img);
237 disp_color.flush(color_img);
238
239 std::vector<vpImagePoint> v_ip {};
240 do {
241 // Prepare display
242 disp_color.display(color_img);
243 auto disp_lane { 0 };
244
245 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Select point along the d435 box boundary", vpColor::green);
246 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Left click to select a point", vpColor::green);
247 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Middle click to remove the last point", vpColor::green);
248 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Right click to finish/quit", vpColor::green);
249
250 // Display already selected points
251 for (auto j = 0u; j < v_ip.size(); j++) {
252 vpDisplay::displayCross(color_img, v_ip.at(j), 15, vpColor::green);
253 vpDisplay::displayText(color_img, v_ip.at(j) + vpImagePoint(10, 10), std::to_string(j), vpColor::green);
254 }
255 disp_color.flush(color_img);
256
257 // Wait for new point
258 vpImagePoint ip {};
260 vpDisplay::getClick(color_img, ip, button, true);
261
262 switch (button) {
264 if (v_ip.size() > 0) {
265 v_ip.erase(std::prev(end(v_ip)));
266 }
267 break;
268 }
269
271 return v_ip;
272 }
273
274 default: {
275 v_ip.push_back(ip);
276 break;
277 }
278 }
279
280 } while (1);
281}
282
283std::map<Model::Id, vpImagePoint> getKeypointsFromUser(vpImage<vpRGBa> color_img, const Model &model,
284 const std::string &parent_data)
285{
286 // Init displays
287 Display disp_color(color_img, 0, 0, "Keypoints", DispScaleType);
288 disp_color.display(color_img);
289 disp_color.flush(color_img);
290
291 vpImage<vpRGBa> I_help {};
292 vpImageIo::read(I_help, parent_data + "/data/d435_box_keypoints_user_helper.jpg");
293 Display disp_help(I_help, disp_color.getWindowXPosition() + color_img.getWidth(), disp_color.getWindowYPosition(),
294 "Keypoints [help]", DispScaleType);
295 disp_help.display(I_help);
296 disp_help.flush(I_help);
297
298 std::map<Model::Id, vpImagePoint> keypoints {};
299 // - The next line produces an internal compiler error with Visual Studio 2017:
300 // tutorial-pose-from-planar-object.cpp(304): fatal error C1001: An internal error has occurred in the compiler.
301 // [C:\projects\visp\build\tutorial\computer-vision\tutorial-pose-from-planar-object.vcxproj] (compiler file
302 // 'd:\agent\_work\8\s\src\vctools\compiler\cxxfe\sl\p1\cxx\grammar.y', line 12721)
303 // To work around this problem, try simplifying or changing the program near the locations listed above.
304 // Please choose the Technical Support command on the Visual C++
305 // Help menu, or open the Technical Support help file for more information
306 // - Note that the next line builds with Visual Studio 2022.
307 // for ([[maybe_unused]] const auto &[id, _] : model.keypoints()) {
308 for (const auto &[id, ip_unused] : model.keypoints()) {
309 (void)ip_unused;
310 // Prepare display
311 disp_color.display(color_img);
312 auto disp_lane { 0 };
313
314 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Select the keypoints " + Model::to_string(id),
316 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Click to select a point", vpColor::green);
317
318 // Display already selected points
319 for (const auto &[id, keypoint] : keypoints) {
320 vpDisplay::displayCross(color_img, keypoint, 15, vpColor::green);
321 vpDisplay::displayText(color_img, keypoint + vpImagePoint(10, 10), Model::to_string(id), vpColor::green);
322 }
323 disp_color.flush(color_img);
324
325 // Wait for new point
326 vpImagePoint ip {};
327 vpDisplay::getClick(color_img, ip, true);
328 keypoints.try_emplace(id, ip);
329 }
330
331 return keypoints;
332}
333#endif // DOXYGEN_SHOULD_SKIP_THIS
334#endif
335
336int main(int, char *argv[])
337{
338#if ((__cplusplus >= 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L)))
339
340#if defined(VISP_HAVE_DISPLAY) && defined(VISP_HAVE_PUGIXML)
341
342 // Get prior data
344 auto [color_img, depth_raw, color_param, depth_param, depth_M_color] =
345 readData(vpIoTools::getParent(argv[0]) + "/data/d435_not_align_depth", 0);
346 const auto model = Model(vpIoTools::getParent(argv[0]) + "/data/d435_box.model");
348
349 std::cout << "color_param:" << std::endl << color_param << std::endl;
350 std::cout << "depth_param:" << std::endl << depth_param << std::endl;
351 std::cout << "depth_M_color:" << std::endl << depth_M_color << std::endl;
352 std::cout << std::endl << "Model:" << std::endl << model << std::endl;
353
354 // Init display
355 Display display_color(color_img, 0, 0, "Color", DispScaleType);
356 display_color.display(color_img);
357 display_color.flush(color_img);
358
359 vpImage<unsigned char> depth_img {};
360 vpImageConvert::createDepthHistogram(depth_raw, depth_img);
361 Display display_depth(depth_img, display_color.getWindowXPosition() + display_color.getWidth(), 0, "Depth",
362 DispScaleType);
363 display_depth.display(depth_img);
364 display_depth.flush(depth_img);
365
366 // Ask roi for plane estimation
368 vpPolygon roi_color_img {};
369 roi_color_img.buildFrom(getRoiFromUser(color_img), true);
370
371 std::vector<vpImagePoint> roi_corners_depth_img {};
372 std::transform(
373 cbegin(roi_color_img.getCorners()), cend(roi_color_img.getCorners()), std::back_inserter(roi_corners_depth_img),
374 std::bind((vpImagePoint(*)(const vpImage<uint16_t> &, const double &, const double &, const double &, const vpCameraParameters &,
376 const vpImagePoint &)) &
378 depth_raw, DepthScale, 0.1, 0.6, depth_param, color_param, depth_M_color.inverse(), depth_M_color,
379 std::placeholders::_1));
380 const vpPolygon roi_depth_img { roi_corners_depth_img };
382
383 vpDisplay::displayPolygon(depth_img, roi_depth_img.getCorners(), vpColor::green);
384 display_depth.flush(depth_img);
385
386 // Estimate the plane
387 vpImage<vpRGBa> heat_map {};
389 const auto obj_plane_in_depth =
390 vpPlaneEstimation::estimatePlane(depth_raw, DepthScale, depth_param, roi_depth_img, 1000, heat_map);
391 if (!obj_plane_in_depth) {
392 return EXIT_FAILURE;
393 }
394
395 // Get the plane in color frame
396 auto obj_plane_in_color = *obj_plane_in_depth;
397 obj_plane_in_color.changeFrame(depth_M_color.inverse());
399
400 Display display_heat_map(heat_map, display_depth.getWindowXPosition(),
401 display_depth.getWindowYPosition() + display_depth.getHeight(), "Plane Estimation Heat map",
402 DispScaleType);
403 display_heat_map.display(heat_map);
404 display_heat_map.flush(heat_map);
405
406 // Ask user to click on keypoints
407 const auto keypoint_color_img = getKeypointsFromUser(color_img, model, vpIoTools::getParent(argv[0]));
408
410 const auto cMo = vpPose::computePlanarObjectPoseWithAtLeast3Points(obj_plane_in_color, model.keypoints(),
411 keypoint_color_img, color_param);
412 if (!cMo) {
413 return EXIT_FAILURE;
414 }
416
417 // Display the model
418 std::vector<vpImagePoint> d435_box_bound {};
419 // - The next line produces an internal compiler error with Visual Studio 2017:
420 // tutorial-pose-from-planar-object.cpp(428): fatal error C1001: An internal error has occurred in the compiler.
421 // [C:\projects\visp\build\tutorial\computer-vision\tutorial-pose-from-planar-object.vcxproj] (compiler file
422 // 'd:\agent\_work\8\s\src\vctools\compiler\cxxfe\sl\p1\cxx\grammar.y', line 12721)
423 // To work around this problem, try simplifying or changing the program near the locations listed above.
424 // Please choose the Technical Support command on the Visual C++
425 // Help menu, or open the Technical Support help file for more information
426 // - Note that the next line builds with Visual Studio 2022.
427 //
428 // for ([[maybe_unused]] const auto &[_, bound] : model.bounds(*cMo)) {
429 for (const auto &[id_unused, bound] : model.bounds(*cMo)) {
430 (void)id_unused;
431 vpImagePoint ip {};
432 vpMeterPixelConversion::convertPoint(color_param, bound.get_x(), bound.get_y(), ip);
433 d435_box_bound.push_back(ip);
434 }
435 vpDisplay::displayPolygon(color_img, d435_box_bound, vpColor::blue);
436
437 for (const auto &[id, keypoint] : model.keypoints(*cMo)) {
438 vpImagePoint ip {};
439 vpMeterPixelConversion::convertPoint(color_param, keypoint.get_x(), keypoint.get_y(), ip);
440 vpDisplay::displayCross(color_img, ip, 15, vpColor::orange);
441 vpDisplay::displayText(color_img, ip + vpImagePoint(10, 10), Model::to_string(id), vpColor::orange);
442 }
443 vpDisplay::flush(color_img);
444
445 // Display the frame
446 vpDisplay::displayFrame(color_img, *cMo, color_param, 0.05, vpColor::none, 3);
447
448 // Wait before exiting
449 auto disp_lane { 0 };
450 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "D435 box boundary [from model]", vpColor::blue);
451 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Keypoints [from model]", vpColor::orange);
452 vpDisplay::displayText(color_img, 15 * ++disp_lane, 15, "Click to quit", vpColor::green);
453 vpDisplay::flush(color_img);
454
455 vpDisplay::getClick(color_img);
456
457#else
458 (void)argv;
459 std::cout << "There is no display and pugixml available to run this tutorial." << std::endl;
460#endif // defined(VISP_HAVE_DISPLAY)
461#else
462 (void)argv;
463 std::cout << "c++17 should be enabled to run this tutorial." << std::endl;
464#endif
465
466 return EXIT_SUCCESS;
467}
Generic class defining intrinsic camera parameters.
@ perspectiveProjWithDistortion
Perspective projection with distortion model.
Implementation of column vector and the associated operations.
static vpImagePoint projectColorToDepth(const vpImage< uint16_t > &I_depth, const double &depth_scale, const double &depth_min, const double &depth_max, const vpCameraParameters &depth_intrinsics, const vpCameraParameters &color_intrinsics, const vpHomogeneousMatrix &color_M_depth, const vpHomogeneousMatrix &depth_M_color, const vpImagePoint &from_pixel)
static const vpColor none
Definition vpColor.h:210
static const vpColor orange
Definition vpColor.h:208
static const vpColor blue
Definition vpColor.h:204
static const vpColor green
Definition vpColor.h:201
Display for windows using Direct3D 3rd party. Thus to enable this class Direct3D should be installed....
Display for windows using GDI (available on any windows 32 platform).
The vpDisplayGTK allows to display image using the GTK 3rd party library. Thus to enable this class G...
The vpDisplayOpenCV allows to display image using the OpenCV library. Thus to enable this class OpenC...
Use the X11 console to display images on unix-like OS. Thus to enable this class X11 should be instal...
Definition vpDisplayX.h:135
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
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 displayCross(const vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness=1)
static void flush(const vpImage< unsigned char > &I)
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
static void displayPolygon(const vpImage< unsigned char > &I, const std::vector< vpImagePoint > &vip, const vpColor &color, unsigned int thickness=1, bool closed=true)
Implementation of an homogeneous matrix and operations on such kind of matrices.
static void createDepthHistogram(const vpImage< uint16_t > &src_depth, vpImage< vpRGBa > &dest_rgba)
static void read(vpImage< unsigned char > &I, const std::string &filename, int backend=IO_DEFAULT_BACKEND)
Class that defines a 2D point in an image. This class is useful for image processing and stores only ...
Definition of the vpImage class member functions.
Definition vpImage.h:131
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
static void readBinaryValueLE(std::ifstream &file, int16_t &short_value)
static std::string formatString(const std::string &name, unsigned int val)
static std::string getParent(const std::string &pathname)
static void convertPoint(const vpCameraParameters &cam, const double &x, const double &y, double &u, double &v)
static std::optional< vpPlane > estimatePlane(const vpImage< uint16_t > &I_depth_raw, double depth_scale, const vpCameraParameters &depth_intrinsics, const vpPolygon &roi, const unsigned int avg_nb_of_pts_to_estimate=500, std::optional< std::reference_wrapper< vpImage< vpRGBa > > > heat_map={})
Defines a generic 2D polygon.
Definition vpPolygon.h:103
vpPolygon & buildFrom(const std::vector< vpImagePoint > &corners, const bool &create_convex_hull=false)
const std::vector< vpImagePoint > & getCorners() const
Definition vpPolygon.h:140
static std::optional< vpHomogeneousMatrix > computePlanarObjectPoseWithAtLeast3Points(const vpPlane &plane_in_camera_frame, const std::map< DataId, vpPoint > &pts, const std::map< DataId, vpImagePoint > &ips, const vpCameraParameters &camera_intrinsics, std::optional< vpHomogeneousMatrix > cMo_init=std::nullopt, bool enable_vvs=true)
Definition vpPose.h:708
XML parser to load and save intrinsic camera parameters.