Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
visp-compute-eye-in-hand-calibration.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 * Compute eye-in-hand calibration from chessboard poses and robot end-effector poses.
32 */
33
38#include <map>
39
40#include <visp3/core/vpConfig.h>
41#include <visp3/core/vpIoTools.h>
42#include <visp3/vision/vpHandEyeCalibration.h>
43
44void usage(const char *argv[], int error)
45{
46 std::cout << "Synopsis" << std::endl
47 << " " << argv[0]
48 << " [--data-path <path>]"
49 << " [--rPe <generic name>]"
50 << " [--cPo <generic name>]"
51 << " [--output-ePc <filename>]"
52 << " [--help, -h]" << std::endl
53 << std::endl;
54 std::cout << "Description" << std::endl
55 << " Compute eye-in-hand calibration." << std::endl
56 << std::endl
57 << " --data-path <path>" << std::endl
58 << " Path to the folder containing pose_rPe_%d.yaml and pose_cPo_%d.yaml data files." << std::endl
59 << " Default: \"./\"" << std::endl
60 << std::endl
61 << " --rPe <generic name>" << std::endl
62 << " Generic name of the yaml files containing the pose of the end-effector expressed in the robot" << std::endl
63 << " base frame and located in the data path folder." << std::endl
64 << " Default: pose_rPe_%d.yaml" << std::endl
65 << std::endl
66 << " --cPo <generic name>" << std::endl
67 << " Generic name of the yaml files" << std::endl
68 << " containing the pose of the calibration grid expressed in the camera frame and located in the" << std::endl
69 << " data path folder." << std::endl
70 << " Default: pose_cPo_%d.yaml" << std::endl
71 << std::endl
72 << " --output-ePc <filename>" << std::endl
73 << " File in yaml format containing the pose of the camera" << std::endl
74 << " in the end-effector frame. Data are saved as a pose vector with first the 3 translations" << std::endl
75 << " along X,Y,Z in [m] and then the 3 rotations in axis-angle representation (thetaU) in [rad]." << std::endl
76 << " Default: ePc.yaml" << std::endl
77 << std::endl
78 << " --output-rPo <filename>" << std::endl
79 << " File in yaml format containing the pose of the object" << std::endl
80 << " in the robot reference frame. Data are saved as a pose vector with first the 3 translations" << std::endl
81 << " along X,Y,Z in [m] and then the 3 rotations in axis-angle representation (thetaU) in [rad]." << std::endl
82 << " Default: rPo.yaml" << std::endl
83 << std::endl
84 << " --help, -h" << std::endl
85 << " Print this helper message." << std::endl
86 << std::endl;
87 if (error) {
88 std::cout << "Error" << std::endl
89 << " "
90 << "Unsupported parameter " << argv[error] << std::endl;
91 }
92}
93
94int main(int argc, const char *argv[])
95{
96#if defined(ENABLE_VISP_NAMESPACE)
97 using namespace VISP_NAMESPACE_NAME;
98#endif
99
100 std::string opt_data_path = "./";
101 std::string opt_rPe_files = "pose_rPe_%d.yaml";
102 std::string opt_cPo_files = "pose_cPo_%d.yaml";
103 std::string opt_ePc_file = "ePc.yaml";
104 std::string opt_rPo_file = "rPo.yaml";
105
106 for (int i = 1; i < argc; i++) {
107 if (std::string(argv[i]) == "--data-path" && i + 1 < argc) {
108 opt_data_path = std::string(argv[++i]);
109 }
110 else if (std::string(argv[i]) == "--rPe" && i + 1 < argc) {
111 opt_rPe_files = std::string(argv[++i]);
112 }
113 else if (std::string(argv[i]) == "--cPo" && i + 1 < argc) {
114 opt_cPo_files = std::string(argv[++i]);
115 }
116 else if (std::string(argv[i]) == "--output-ePc" && i + 1 < argc) {
117 opt_ePc_file = std::string(argv[++i]);
118 }
119 else if (std::string(argv[i]) == "--output-rPo" && i + 1 < argc) {
120 opt_rPo_file = std::string(argv[++i]);
121 }
122 else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
123 usage(argv, 0);
124 return EXIT_SUCCESS;
125 }
126 else {
127 usage(argv, i);
128 return EXIT_FAILURE;
129 }
130 }
131
132 // Create output folder if necessary
133 std::string output_parent = vpIoTools::getParent(opt_ePc_file);
134 if (!vpIoTools::checkDirectory(output_parent)) {
135 std::cout << "Create output directory: " << output_parent << std::endl;
136 vpIoTools::makeDirectory(output_parent);
137 }
138
139 std::vector<vpHomogeneousMatrix> cMo;
140 std::vector<vpHomogeneousMatrix> rMe;
143
144 std::map<long, std::string> map_rPe_files;
145 std::map<long, std::string> map_cPo_files;
146 std::vector<std::string> files = vpIoTools::getDirFiles(opt_data_path);
147 for (unsigned int i = 0; i < files.size(); i++) {
148 long index_rPe = vpIoTools::getIndex(files[i], opt_rPe_files);
149 long index_cPo = vpIoTools::getIndex(files[i], opt_cPo_files);
150 if (index_rPe != -1) {
151 map_rPe_files[index_rPe] = files[i];
152 }
153 if (index_cPo != -1) {
154 map_cPo_files[index_cPo] = files[i];
155 }
156 }
157
158 if (map_rPe_files.size() == 0) {
159 std::cout << "No " << opt_rPe_files
160 << " files found. Use --data-path <path> or --rPe <generic name> to be able to read your data." << std::endl;
161 std::cout << "Use --help option to see full usage..." << std::endl;
162 return EXIT_FAILURE;
163 }
164 if (map_cPo_files.size() == 0) {
165 std::cout << "No " << opt_cPo_files
166 << " files found. Use --data-path <path> or --cPo <generic name> to be able to read your data." << std::endl;
167 std::cout << "Use --help option to see full usage..." << std::endl;
168 return EXIT_FAILURE;
169 }
170
171 for (std::map<long, std::string>::const_iterator it_rPe = map_rPe_files.begin(); it_rPe != map_rPe_files.end();
172 ++it_rPe) {
173 std::string file_rPe = vpIoTools::createFilePath(opt_data_path, it_rPe->second);
174 std::map<long, std::string>::const_iterator it_cPo = map_cPo_files.find(it_rPe->first);
175 if (it_cPo != map_cPo_files.end()) {
176 vpPoseVector rPe;
177 if (rPe.loadYAML(file_rPe, rPe) == false) {
178 std::cout << "Unable to read data from " << file_rPe << ". Skip data" << std::endl;
179 continue;
180 }
181
182 vpPoseVector cPo;
183 std::string file_cPo = vpIoTools::createFilePath(opt_data_path, it_cPo->second);
184 if (cPo.loadYAML(file_cPo, cPo) == false) {
185 std::cout << "Unable to read data from " << file_cPo << ". Skip data" << std::endl;
186 continue;
187 }
188 std::cout << "Use data from " << opt_data_path << "/" << file_rPe << " and from " << file_cPo << std::endl;
189 rMe.push_back(vpHomogeneousMatrix(rPe));
190 cMo.push_back(vpHomogeneousMatrix(cPo));
191 }
192 }
193
194 if (rMe.size() < 3) {
195 std::cout << "Not enough data pairs found." << std::endl;
196 return EXIT_FAILURE;
197 }
198
199 int ret = vpHandEyeCalibration::calibrate(cMo, rMe, eMc, rMo);
200
201 if (ret == 0) {
202 std::cout << std::endl << "Eye-in-hand calibration succeed" << std::endl;
203 std::cout << std::endl << "Estimated eMc transformation:" << std::endl;
204 std::cout << "-----------------------------" << std::endl;
205 //std::cout << eMc << std::endl << std::endl;
206 vpMatrix(eMc).print(std::cout, 15, "eMc");
207 std::cout << "- Corresponding pose vector [tx ty tz tux tuy tuz] in [m] and [rad]: " << vpPoseVector(eMc).t() << std::endl;
208
210 std::cout << std::endl << "- Translation [m]: " << eMc[0][3] << " " << eMc[1][3] << " " << eMc[2][3] << std::endl;
211 std::cout << "- Rotation (theta-u representation) [rad]: " << erc.t() << std::endl;
212 std::cout << "- Rotation (theta-u representation) [deg]: " << vpMath::deg(erc[0]) << " " << vpMath::deg(erc[1])
213 << " " << vpMath::deg(erc[2]) << std::endl;
214 vpQuaternionVector quaternion(eMc.getRotationMatrix());
215 std::cout << "- Rotation (quaternion representation) [rad]: " << quaternion.t() << std::endl;
217 std::cout << "- Rotation (r-x-y-z representation) [rad]: " << rxyz.t() << std::endl;
218 std::cout << "- Rotation (r-x-y-z representation) [deg]: " << vpMath::deg(rxyz).t() << std::endl;
219
220 std::cout << std::endl << "Estimated rMo transformation:" << std::endl;
221 std::cout << "-----------------------------" << std::endl;
222 //std::cout << rMo << std::endl;
223 vpMatrix(rMo).print(std::cout, 15, "rMo");
224 std::cout << "- Corresponding pose vector [tx ty tz tux tuy tuz] in [m] and [rad]: " << vpPoseVector(rMo).t() << std::endl;
225
227 std::cout << std::endl << "- Translation [m]: " << rMo[0][3] << " " << rMo[1][3] << " " << rMo[2][3] << std::endl;
228 std::cout << "- Rotation (theta-u representation) [rad]: " << wrc.t() << std::endl;
229 std::cout << "- Rotation (theta-u representation) [deg]: " << vpMath::deg(wrc[0]) << " " << vpMath::deg(wrc[1])
230 << " " << vpMath::deg(wrc[2]) << std::endl;
231 vpQuaternionVector quaternion2(rMo.getRotationMatrix());
232 std::cout << "- Rotation (quaternion representation) [rad]: " << quaternion2.t() << std::endl;
233 vpRxyzVector rxyz2(rMo.getRotationMatrix());
234 std::cout << "- Rotation (r-x-y-z representation) [rad]: " << rxyz2.t() << std::endl;
235 std::cout << "- Rotation (r-x-y-z representation) [deg]: " << vpMath::deg(rxyz).t() << std::endl;
236
237 {
238 // save eMc
239 std::string name_we = vpIoTools::createFilePath(vpIoTools::getParent(opt_ePc_file), vpIoTools::getNameWE(opt_ePc_file)) + ".txt";
240 std::cout << std::endl << "Save transformation matrix eMc as an homogeneous matrix in: " << name_we << std::endl;
241
242#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
243 std::ofstream file_eMc(name_we);
244#else
245 std::ofstream file_eMc(name_we.c_str());
246#endif
247
248 eMc.save(file_eMc);
249
250 vpPoseVector pose_vec(eMc);
251 std::string output_filename = vpIoTools::createFilePath(vpIoTools::getParent(opt_ePc_file), vpIoTools::getName(opt_ePc_file));
252 std::cout << "Save transformation matrix eMc as a vpPoseVector in : " << output_filename << std::endl;
253 pose_vec.saveYAML(output_filename, pose_vec);
254 }
255
256 {
257 // save rMo
258 std::string name_we = vpIoTools::createFilePath(vpIoTools::getParent(opt_rPo_file), vpIoTools::getNameWE(opt_rPo_file)) + ".txt";
259 std::cout << std::endl << "Save transformation matrix rMo as an homogeneous matrix in: " << name_we << std::endl;
260
261#if (VISP_CXX_STANDARD > VISP_CXX_STANDARD_98)
262 std::ofstream file_rMo(name_we);
263#else
264 std::ofstream file_rMo(name_we.c_str());
265#endif
266
267 rMo.save(file_rMo);
268
269 vpPoseVector pose_vec(rMo);
270 std::string output_filename = vpIoTools::createFilePath(vpIoTools::getParent(opt_rPo_file), vpIoTools::getName(opt_rPo_file));
271 std::cout << "Save transformation matrix rMo as a vpPoseVector in : " << output_filename << std::endl;
272 pose_vec.saveYAML(output_filename, pose_vec, "Robot reference to object frames transformation (rMo)");
273 }
274 }
275 else {
276 std::cout << std::endl << "** Eye-in-hand calibration failed" << std::endl;
277 std::cout << std::endl << "Check your input data and ensure they are covering the half sphere over the chessboard." << std::endl;
278 std::cout << std::endl << "See https://visp-doc.inria.fr/doxygen/visp-daily/tutorial-calibration-extrinsic-eye-in-hand.html" << std::endl;
279 }
280
281 return EXIT_SUCCESS;
282}
static bool loadYAML(const std::string &filename, vpArray2D< Type > &A, char *header=nullptr)
Definition vpArray2D.h:874
vpArray2D< Type > t() const
Compute the transpose of the array.
Definition vpArray2D.h:1273
static int calibrate(const std::vector< vpHomogeneousMatrix > &cMo, const std::vector< vpHomogeneousMatrix > &rMe, vpHomogeneousMatrix &eMc, vpHomogeneousMatrix &rMo)
Implementation of an homogeneous matrix and operations on such kind of matrices.
vpRotationMatrix getRotationMatrix() const
void save(std::ofstream &f) const
static bool checkDirectory(const std::string &dirname)
static long getIndex(const std::string &filename, const std::string &format)
static std::string createFilePath(const std::string &parent, const std::string &child)
static std::vector< std::string > getDirFiles(const std::string &dirname)
static void makeDirectory(const std::string &dirname)
static std::string getNameWE(const std::string &pathname)
static std::string getParent(const std::string &pathname)
static std::string getName(const std::string &pathname)
static double deg(double rad)
Definition vpMath.h:119
Implementation of a matrix and operations on matrices.
Definition vpMatrix.h:175
int print(std::ostream &s, unsigned int length, const std::string &intro="") const
Definition vpMatrix.cpp:836
Implementation of a pose vector and operations on poses.
vpRowVector t() const
Implementation of a rotation vector as quaternion angle minimal representation.
Implementation of a rotation vector as Euler angle minimal representation.
Implementation of a rotation vector as axis-angle minimal representation.