This app allows to calibrate camera intrinsic parameters.
#include <iostream>
#include <visp3/core/vpConfig.h>
#if defined(VISP_HAVE_OPENCV) && defined(HAVE_OPENCV_HIGHGUI) && defined(HAVE_OPENCV_IMGPROC) && defined(VISP_HAVE_PUGIXML) \
&& (((VISP_HAVE_OPENCV_VERSION < 0x050000) && defined(HAVE_OPENCV_CALIB3D)) || ((VISP_HAVE_OPENCV_VERSION >= 0x050000) && defined(HAVE_OPENCV_3D)))
#include <map>
#if defined(HAVE_OPENCV_CALIB3D)
#include <opencv2/calib3d/calib3d.hpp>
#elif defined(HAVE_OPENCV_CALIB)
#include <opencv2/calib.hpp>
#endif
#if defined(HAVE_OPENCV_CONTRIB)
#include <opencv2/contrib/contrib.hpp>
#endif
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <visp3/vision/vpCalibration.h>
#include <visp3/core/vpImageTools.h>
#include <visp3/core/vpIoTools.h>
#include <visp3/core/vpMeterPixelConversion.h>
#include <visp3/core/vpPixelMeterConversion.h>
#include <visp3/core/vpPoint.h>
#include <visp3/core/vpXmlParserCamera.h>
#include <visp3/gui/vpDisplayFactory.h>
#include <visp3/io/vpVideoReader.h>
#include "calibration-helper.hpp"
using namespace calib_helper;
void usage(const char *argv[], int error)
{
std::cout << "Synopsis" << std::endl
<< " " << argv[0]
<< " <configuration file>.cfg"
<< " [--init-from-xml <camera-init.xml>]"
<< " [--camera-name <name>]"
<< " [--aspect-ratio <ratio>]"
<< " [--output <file.xml>]"
<< " [--help, -h]" << std::endl
<< std::endl;
std::cout << "Description" << std::endl
<< " <configuration file>.cfg" << std::endl
<< " Configuration file. See example in \"default-chessboard.cfg\" or in \"default-circles.cfg\"." << std::endl
<< " Default: \"default.cfg\"." << std::endl
<< std::endl
<< " --init-from-xml <camera-init.xml>" << std::endl
<< " XML file that contains camera parameters used to initialize the calibration process." << std::endl
<< std::endl
<< " --camera-name <name>" << std::endl
<< " Camera name in the XML file set using \"--init-from-xml\" option." << std::endl
<< " Default: \"Camera\"." << std::endl
<< std::endl
<< " --aspect-ratio <ratio>" << std::endl
<< " Pixel aspect ratio. To estimate px = py, use \"--aspect-ratio 1\" option. Set to -1" << std::endl
<< " to unset any constraint for px and py parameters. " << std::endl
<< " Default: -1." << std::endl
<< std::endl
<< " --output <file.xml>" << std::endl
<< " XML file containing estimated camera parameters." << std::endl
<< " Default: \"camera.xml\"." << std::endl
<< std::endl
<< " --help, -h" << std::endl
<< " Print this helper message." << std::endl
<< std::endl;
if (error) {
std::cout << "Error" << std::endl
<< " "
<< "Unsupported parameter " << argv[error] << std::endl;
}
}
int main(int argc, const char *argv[])
{
#if defined(ENABLE_VISP_NAMESPACE)
#endif
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
#endif
try {
if (argc == 1) {
usage(argv, 0);
return EXIT_SUCCESS;
}
std::string opt_output_file_name = "camera.xml";
const std::string opt_inputSettingsFile = argc > 1 ? argv[1] : "default.cfg";
std::string opt_init_camera_xml_file;
std::string opt_camera_name = "Camera";
double opt_aspect_ratio = -1;
for (
int i = 2;
i < argc;
i++) {
if (std::string(argv[i]) == "--init-from-xml" && i + 1 < argc) {
opt_init_camera_xml_file = std::string(argv[++i]);
}
else if (std::string(argv[i]) == "--camera-name" && i + 1 < argc) {
opt_camera_name = std::string(argv[++i]);
}
else if (std::string(argv[i]) == "--output" && i + 1 < argc) {
opt_output_file_name = std::string(argv[++i]);
}
else if (std::string(argv[i]) == "--aspect-ratio" && i + 1 < argc) {
opt_aspect_ratio = std::atof(argv[++i]);
}
else if (std::string(argv[i]) == "--help" || std::string(argv[i]) == "-h") {
usage(argv, 0);
return EXIT_SUCCESS;
}
else {
usage(argv, i);
return EXIT_FAILURE;
}
}
std::cout << "Settings from config file: " << argv[1] << std::endl;
if (!
s.read(opt_inputSettingsFile)) {
std::cout << "Could not open the configuration file: \"" << opt_inputSettingsFile << "\"" << std::endl;
usage(argv, 0);
return EXIT_FAILURE;
}
std::cout << "Invalid input detected. Application stopping. " << std::endl;
return EXIT_FAILURE;
}
std::cout << "\nSettings from command line options: " << std::endl;
if (!opt_init_camera_xml_file.empty()) {
std::cout << "Init parameters: " << opt_init_camera_xml_file << std::endl;
}
std::cout << "Ouput xml file : " << opt_output_file_name << std::endl;
std::cout << "Camera name : " << opt_camera_name << std::endl;
std::cout << "\nOutput file name " << opt_output_file_name << " already exists." << std::endl;
std::cout << "Remove this file or change output file name using [--output <file.xml>] command line option."
<< std::endl;
return EXIT_SUCCESS;
}
try {
}
std::cout <<
"Catch an exception: " <<
e.getStringMessage() << std::endl;
std::cout <<
"Check if input images name \"" <<
s.input <<
"\" set in " << opt_inputSettingsFile
<< " config file is correct..." << std::endl;
return EXIT_FAILURE;
}
#if (VISP_CXX_STANDARD >= VISP_CXX_STANDARD_11)
#else
#endif
bool init_from_xml = false;
if (!opt_init_camera_xml_file.empty()) {
std::cout << "Input camera file \"" << opt_init_camera_xml_file << "\" doesn't exist!" << std::endl;
std::cout << "Modify [--init-from-xml <camera-init.xml>] option value" << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
init_from_xml = true;
}
if (init_from_xml) {
std::cout << "Initialize camera parameters from xml file: " << opt_init_camera_xml_file << std::endl;
if (
parser.parse(cam_init, opt_init_camera_xml_file, opt_camera_name,
std::cout << "Unable to find camera with name \"" << opt_camera_name
<< "\" in file: " << opt_init_camera_xml_file << std::endl;
std::cout << "Modify [--camera-name <name>] option value" << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
}
else {
std::cout << "Initialize camera parameters with default values " << std::endl;
double px = cam_init.
get_px();
double py = cam_init.
get_py();
double u0 = I.getWidth() / 2;
double v0 = I.getHeight() / 2;
}
std::cout << "Camera parameters used for initialization:\n" << cam_init << std::endl;
std::vector<vpPoint> model;
std::vector<vpCalibration> calibrator;
for (
int i = 0;
i <
s.boardSize.height;
i++) {
for (
int j = 0;
j <
s.boardSize.width;
j++) {
model.push_back(
vpPoint(j *
s.squareSize, i *
s.squareSize, 0));
}
}
std::vector<CalibInfo> calib_info;
std::multimap<double, vpCameraParameters, std::less<double> > map_cam_sorted;
map_cam_sorted.insert(std::make_pair(1000, cam_init));
do {
cv::Mat cvI;
std::vector<cv::Point2f> pointBuf;
std::cout << "Process frame: " << frame_name << std::flush;
bool found = extractCalibrationPoints(s, cvI, pointBuf);
std::cout << ", grid detection status: " << found;
if (!found)
std::cout << ", image rejected" << std::endl;
else
std::cout << ", image used as input data" << std::endl;
if (found) {
std::vector<vpImagePoint>
data;
for (
unsigned int i = 0;
i < pointBuf.size();
i++) {
}
std::vector<vpPoint> calib_points;
for (
unsigned int i = 0;
i < model.size();
i++) {
calib.
addPoint(model[i].get_oX(), model[i].get_oY(), model[i].get_oZ(), data[i]);
calib_points.push_back(
vpPoint(model[i].get_oX(), model[i].get_oY(), model[i].get_oZ()));
calib_points.back().set_x(data[i].get_u());
calib_points.back().set_y(data[i].get_v());
}
bool calib_status = false;
std::multimap<double, vpCameraParameters>::const_iterator it_cam;
for (it_cam = map_cam_sorted.begin(); it_cam != map_cam_sorted.end(); ++it_cam) {
calibrator.push_back(calib);
calib_info.push_back(CalibInfo(I, calib_points, data, frame_name));
calib_status = true;
map_cam_sorted.insert(std::make_pair(residual, cam));
break;
}
}
if (!calib_status) {
std::cout << "frame: " << frame_name << ", unable to calibrate from single image, image rejected"
<< std::endl;
found = false;
}
}
if (found)
else
}
else {
}
if (calibrator.empty()) {
std::cerr << "Unable to calibrate. Image processing failed !" << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
drawCalibrationOccupancy(I, calib_info,
s.boardSize.width);
cv::Mat1b img(I.getHeight(), I.getWidth());
cv::Mat3b imgColor(I.getHeight(), I.getWidth());
cv::applyColorMap(img, imgColor, cv::COLORMAP_JET);
for (size_t idx1 = 0; idx1 < calib_info.size(); idx1++) {
const CalibInfo &calib = calib_info[idx1];
for (size_t idx2 = 0; idx2 < calib.m_imPts.size(); idx2++) {
cv::rectangle(imgColor,
cv::Rect(
static_cast<int>(imPt.
get_u() - 2),
static_cast<int>(imPt.
get_v() - 2),
cv::Scalar(0, 0, 0), -1);
}
}
d->init(I_color, 0, 0,
"Calibration pattern occupancy");
}
else {
}
std::stringstream ss_additional_info;
ss_additional_info << "<nb_calibration_images>" << calibrator.size() << "</nb_calibration_images>";
ss_additional_info << "<calibration_pattern_type>";
switch (
s.calibrationPattern) {
case Settings::CHESSBOARD:
ss_additional_info << "Chessboard";
break;
case Settings::CIRCLES_GRID:
ss_additional_info << "Circles grid";
break;
case Settings::UNDEFINED:
default:
ss_additional_info << "Undefined";
break;
}
ss_additional_info << "</calibration_pattern_type>";
ss_additional_info <<
"<board_size>" <<
s.boardSize.width <<
"x" <<
s.boardSize.height <<
"</board_size>";
ss_additional_info <<
"<square_size>" <<
s.squareSize <<
"</square_size>";
std::cout << "\nCalibration without distortion in progress on " << calibrator.size() << " images..." << std::endl;
EXIT_SUCCESS) {
std::cout <<
cam << std::endl;
ss_additional_info << "<reprojection_error><without_distortion>";
for (
size_t i = 0;
i < calibrator.size();
i++) {
double reproj_error = sqrt(calibrator[i].getResidual() / calibrator[i].get_npt());
const CalibInfo &calib = calib_info[
i];
std::cout << "Image " << calib.m_frame_name << " reprojection error: " << reproj_error << std::endl;
ss_additional_info << "<image>" << reproj_error << "</image>";
I = calib.m_img;
std::ostringstream ss;
ss << "Reprojection error: " << reproj_error;
for (
size_t idx = 0;
idx < calib.m_points.size();
idx++) {
}
}
else {
}
}
std::cout <<
"\nGlobal reprojection error: " <<
error << std::endl;
ss_additional_info <<
"<global_reprojection_error>" <<
error <<
"</global_reprojection_error>";
ss_additional_info << "</without_distortion>";
if (xml.
save(cam, opt_output_file_name.c_str(), opt_camera_name, I.getWidth(), I.getHeight()) ==
std::cout << "Camera parameters without distortion successfully saved in \"" << opt_output_file_name << "\""
<< std::endl;
else {
std::cout << "Failed to save the camera parameters without distortion in \"" << opt_output_file_name << "\""
<< std::endl;
std::cout << "A file with the same name exists. Remove it to be able "
"to save the parameters..."
<< std::endl;
}
}
else {
std::cout << "Calibration without distortion failed." << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
std::vector<vpCalibration> calibrator_without_dist = calibrator;
std::cout << "\n\nCalibration with distortion in progress on " << calibrator.size() << " images..." << std::endl;
EXIT_SUCCESS) {
std::cout <<
cam << std::endl;
ss_additional_info << "<with_distortion>";
for (
size_t i = 0;
i < calibrator.size();
i++) {
double reproj_error = sqrt(calibrator[i].getResidual_dist() / calibrator[i].get_npt());
const CalibInfo &calib = calib_info[
i];
std::cout << "Image " << calib.m_frame_name << " reprojection error: " << reproj_error << std::endl;
ss_additional_info << "<image>" << reproj_error << "</image>";
I = calib.m_img;
std::ostringstream ss;
ss << "Reprojection error: " << reproj_error;
for (
size_t idx = 0;
idx < calib.m_points.size();
idx++) {
pt.
project(calibrator[i].cMo_dist);
}
}
else {
}
}
std::cout <<
"\nGlobal reprojection error: " <<
error << std::endl;
ss_additional_info <<
"<global_reprojection_error>" <<
error <<
"</global_reprojection_error>";
ss_additional_info << "</with_distortion></reprojection_error>";
d->init(I_dist_undist, 0, 0,
"Straight lines have to be straight - distorted image / undistorted image");
for (
size_t idx = 0;
idx < calib_info.size();
idx++) {
std::cout << "\nThis tool computes the line fitting error (mean distance error) on image points extracted from "
"the raw distorted image."
<< std::endl;
I = calib_info[
idx].m_img;
I_dist_undist.insert(I_undist,
vpImagePoint(0, I.getWidth()));
calib_info[idx].m_frame_name + std::string(
" distorted"),
vpColor::red);
std::vector<vpImagePoint> grid_points = calib_info[
idx].m_imPts;
for (
int i = 0;
i <
s.boardSize.height;
i++) {
std::vector<vpImagePoint> current_line(grid_points.begin() + i *
s.boardSize.width,
grid_points.begin() + (i + 1) *
s.boardSize.width);
std::vector<vpImagePoint> current_line_undist = undistort(cam, current_line);
double a = 0, b = 0, c = 0;
std::cout << calib_info[
idx].m_frame_name <<
" line " <<
i + 1
<< " fitting error on distorted points: " << line_fitting_error
<< " ; on undistorted points: " << line_fitting_error_undist << std::endl;
}
std::cout << "\nThis tool computes the line fitting error (mean distance error) on image points extracted from "
"the undistorted image"
<< " (vpImageTools::undistort())." << std::endl;
cv::Mat cvI;
std::vector<cv::Point2f> pointBuf;
bool found = extractCalibrationPoints(s, cvI, pointBuf);
if (found) {
std::vector<vpImagePoint> grid_points;
for (
unsigned int i = 0;
i < pointBuf.size();
i++) {
grid_points.push_back(ip);
}
calib_info[idx].m_frame_name + std::string(
" undistorted"),
vpColor::red);
for (
int i = 0;
i <
s.boardSize.height;
i++) {
std::vector<vpImagePoint> current_line(grid_points.begin() + i *
s.boardSize.width,
grid_points.begin() + (i + 1) *
s.boardSize.width);
double a = 0, b = 0, c = 0;
std::cout << calib_info[
idx].m_frame_name <<
" undistorted image, line " <<
i + 1
<< " fitting error: " << line_fitting_error << std::endl;
}
}
else {
std::string msg("Unable to detect grid on undistorted image");
std::cout << msg << std::endl;
std::cout << "Check that the grid is not too close to the image edges" << std::endl;
calib_info[idx].m_frame_name + std::string(
" undistorted"),
vpColor::red);
}
}
else {
}
}
std::cout << std::endl;
ss_additional_info << "<camera_poses>";
for (
size_t i = 0;
i < calibrator.size();
i++) {
ss_additional_info << "<cMo>" << pose.t() << "</cMo>";
}
for (
size_t i = 0;
i < calibrator.size();
i++) {
ss_additional_info << "<cMo_dist>" << pose.t() << "</cMo_dist>";
}
ss_additional_info << "</camera_poses>";
if (xml.
save(cam, opt_output_file_name.c_str(), opt_camera_name, I.getWidth(), I.getHeight(),
std::cout << "Camera parameters without distortion successfully saved in \"" << opt_output_file_name << "\""
<< std::endl;
else {
std::cout << "Failed to save the camera parameters without distortion in \"" << opt_output_file_name << "\""
<< std::endl;
std::cout << "A file with the same name exists. Remove it to be able "
"to save the parameters..."
<< std::endl;
}
std::cout << std::endl;
std::cout << "Estimated pose using vpPoseVector format: [tx ty tz tux tuy tuz] with translation in meter and "
"rotation in rad"
<< std::endl;
for (
unsigned int i = 0;
i < calibrator.size();
i++)
std::cout << "Estimated pose on input data extracted from " << calib_info[i].m_frame_name << ": "
}
else {
std::cout << "Calibration with distortion failed." << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
std::cout << "\nCamera calibration succeeded. Results are saved in " << "\"" << opt_output_file_name << "\"" << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_SUCCESS;
}
std::cout <<
"Catch an exception: " <<
e << std::endl;
#if (VISP_CXX_STANDARD < VISP_CXX_STANDARD_11)
if (display != nullptr) {
delete display;
}
#endif
return EXIT_FAILURE;
}
}
#else
int main()
{
#if !defined(HAVE_OPENCV_IMGPROC)
std::cout << "This example requires OpenCV imgproc module." << std::endl;
#endif
#if !defined(HAVE_OPENCV_HIGHGUI)
std::cout << "This example requires OpenCV highgui module." << std::endl;
#endif
#if defined(VISP_HAVE_OPENCV) && (VISP_HAVE_OPENCV_VERSION < 0x050000) && !defined(HAVE_OPENCV_CALIB3D)
std::cout << "This example requires OpenCV calib3d module." << std::endl;
#endif
#if defined(VISP_HAVE_OPENCV) && (VISP_HAVE_OPENCV_VERSION >= 0x050000) && !defined(HAVE_OPENCV_3D)
std::cout << "This example requires OpenCV 3d module." << std::endl;
#endif
#if !defined(VISP_HAVE_PUGIXML)
std::cout << "pugixml built-in 3rdparty is requested to run the calibration." << std::endl;
#endif
return EXIT_SUCCESS;
}
#endif
Tools for perspective camera intrinsic parameters calibration.
static void setLambda(const double &lambda)
int computeCalibration(vpCalibrationMethodType method, vpHomogeneousMatrix &cMo_est, vpCameraParameters &cam_est, bool verbose=false)
double getResidual(void) const
int addPoint(double X, double Y, double Z, vpImagePoint &ip)
static int computeCalibrationMulti(vpCalibrationMethodType method, std::vector< vpCalibration > &table_cal, vpCameraParameters &cam_est, double &globalReprojectionError, bool verbose=false)
void setAspectRatio(double aspect_ratio)
Generic class defining intrinsic camera parameters.
void initPersProjWithoutDistortion(double px, double py, double u0, double v0)
@ perspectiveProjWithoutDistortion
Perspective projection without distortion model.
static const vpColor green
Class that defines generic functionalities for display.
static bool getClick(const vpImage< unsigned char > &I, bool blocking=true)
static void display(const vpImage< unsigned char > &I)
static void displayLine(const vpImage< unsigned char > &I, const vpImagePoint &ip1, const vpImagePoint &ip2, const vpColor &color, unsigned int thickness=1, bool segment=true)
static void displayCross(const vpImage< unsigned char > &I, const vpImagePoint &ip, unsigned int size, const vpColor &color, unsigned int thickness=1)
static void setTitle(const vpImage< unsigned char > &I, const std::string &windowtitle)
static void flush(const vpImage< unsigned char > &I)
unsigned int getDownScalingFactor()
static void displayText(const vpImage< unsigned char > &I, const vpImagePoint &ip, const std::string &s, const vpColor &color)
error that can be emitted by ViSP classes.
Implementation of an homogeneous matrix and operations on such kind of matrices.
static void convert(const vpImage< unsigned char > &src, vpImage< vpRGBa > &dest)
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.
unsigned int getHeight() const
static double lineFitting(const std::vector< vpImagePoint > &imPts, double &a, double &b, double &c)
static void convertPoint(const vpCameraParameters &cam, const double &x, const double &y, double &u, double &v)
Class that defines a 3D point in the object frame and allows forward projection of a 3D point in the ...
double get_y() const
Get the point y coordinate in the image plane.
double get_x() const
Get the point x coordinate in the image plane.
Implementation of a pose vector and operations on poses.
Class that enables to manipulate easily a video file or a sequence of images. As it inherits from the...
void open(vpImage< vpRGBa > &I) VP_OVERRIDE
void setFileName(const std::string &filename)
long getFrameIndex() const
void acquire(vpImage< vpRGBa > &I) VP_OVERRIDE
XML parser to load and save intrinsic camera parameters.
int save(const vpCameraParameters &cam, const std::string &filename, const std::string &camera_name, unsigned int image_width=0, unsigned int image_height=0, const std::string &additionalInfo="", bool verbose=true)
std::shared_ptr< vpDisplay > createDisplay()
Return a smart pointer vpDisplay specialization if a GUI library is available or nullptr otherwise.
vpDisplay * allocateDisplay()
Return a newly allocated vpDisplay specialization if a GUI library is available or nullptr otherwise.
VISP_EXPORT int wait(double t0, double t)
VISP_EXPORT std::string getDateTime(const std::string &format="%Y/%m/%d %H:%M:%S")