This sample application performs a fisheye lens calibration using input images taken with the same camera/lens. Then it uses Remap and the calibration data to correct fisheye lens distortion of these images and save the result to disk. The mapping used for distortion correction is VPI_FISHEYE_EQUIDISTANT, which maps straight lines in the scene to straight lines in the corrected image.
Lens calibration uses a set of images taken by the same camera/lens, each one showing a checkerboard pattern in a different position, so that taken collectively, the checkerboard appears in almost entire field of view. The more images, the more accurate the calibration will be, but typically 10 to 15 images suffice.
VPI samples include a set of input images that can be used. They are found in /opt/nvidia/vpi-0.4/samples/assets/fisheye directory.
This will correct the included set of calibration images, all captured using the checkerboard pattern also included. It's using a 22x22 window around each checkerboard internal vertex to refine the vertex position.
For convenience, here's the code that is also installed in the samples directory.
29 #include <opencv2/core/version.hpp>
31 #if CV_MAJOR_VERSION >= 3
32 # include <opencv2/imgcodecs.hpp>
34 # include <opencv2/highgui/highgui.hpp>
37 #include <opencv2/calib3d/calib3d.hpp>
38 #include <opencv2/imgproc/imgproc.hpp>
53 #define CHECK_STATUS(STMT) \
56 VPIStatus status = (STMT); \
57 if (status != VPI_SUCCESS) \
59 char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
60 vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
61 std::ostringstream ss; \
62 ss << vpiStatusGetName(status) << ": " << buffer; \
63 throw std::runtime_error(ss.str()); \
67 static void PrintUsage(
const char *progname, std::ostream &out)
69 out <<
"Usage: " << progname <<
" <-c W,H> [-s win] <image1> [image2] [image3] ...\n"
71 <<
" W,H\tcheckerboard with WxH squares\n"
72 <<
" win\tsearch window width around checkerboard vertex used\n"
73 <<
"\tin refinement, default is 0 (disable refinement)\n"
74 <<
" imageN\tinput images taken with a fisheye lens camera" << std::endl;
81 std::vector<const char *> images;
84 static Params ParseParameters(
int argc,
char *argv[])
92 while ((opt = getopt(argc, argv,
"hc:s:")) != -1)
97 PrintUsage(basename(argv[0]), std::cout);
101 if (sscanf(optarg,
"%u,%u", &cbSize.width, &cbSize.height) != 2)
103 throw std::invalid_argument(
"Error parsing checkerboard information");
108 params.vtxCount.width = cbSize.width - 1;
109 params.vtxCount.height = cbSize.height - 1;
113 if (sscanf(optarg,
"%d", ¶ms.searchWinSize) != 1)
115 throw std::invalid_argument(
"Error parseing search window size");
117 if (params.searchWinSize < 0)
119 throw std::invalid_argument(
"Search window size must be >= 0");
123 throw std::invalid_argument(std::string(
"Option -") + (
char)optopt +
" not recognized");
127 for (
int i = optind; i < argc; ++i)
129 params.images.push_back(argv[i]);
132 if (params.images.empty())
134 throw std::invalid_argument(
"At least one image must be defined");
137 if (cbSize.width <= 3 || cbSize.height <= 3)
139 throw std::invalid_argument(
"Checkerboard size must have at least 3x3 squares");
142 if (params.searchWinSize == 1)
144 throw std::invalid_argument(
"Search window size must be 0 (default) or >= 2");
150 int main(
int argc,
char *argv[])
160 Params params = ParseParameters(argc, argv);
161 if (params.images.empty())
167 std::vector<std::vector<cv::Point2f>> corners2D;
170 cv::Size imgSize = {};
172 for (
unsigned i = 0; i < params.images.size(); ++i)
175 cv::Mat img = cv::imread(params.images[i]);
178 throw std::runtime_error(
"Can't read " + std::string(params.images[i]));
181 if (imgSize == cv::Size{})
183 imgSize = img.size();
185 else if (imgSize != img.size())
187 throw std::runtime_error(
"All images must have same size");
193 std::vector<cv::Point2f> cbVertices;
195 if (findChessboardCorners(img, params.vtxCount, cbVertices,
196 cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE))
199 if (params.searchWinSize >= 2)
202 cvtColor(img, gray, cv::COLOR_BGR2GRAY);
204 cornerSubPix(gray, cbVertices, cv::Size(params.searchWinSize / 2, params.searchWinSize / 2),
206 cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.0001));
210 corners2D.push_back(std::move(cbVertices));
214 std::cerr <<
"Warning: checkerboard pattern not found in image " << params.images[i] << std::endl;
221 std::vector<cv::Point3f> initialCheckerboard3DVertices;
222 for (
int i = 0; i < params.vtxCount.height; ++i)
224 for (
int j = 0; j < params.vtxCount.width; ++j)
228 initialCheckerboard3DVertices.emplace_back(j, i, 0);
233 std::vector<std::vector<cv::Point3f>> corners3D(corners2D.size(), initialCheckerboard3DVertices);
236 using Mat3 = cv::Matx<double, 3, 3>;
237 Mat3 camMatrix = Mat3::eye();
240 std::vector<double> coeffs(4);
244 int flags = cv::fisheye::CALIB_FIX_SKEW;
248 cv::Mat rvecs, tvecs;
249 double rms = cv::fisheye::calibrate(corners3D, corners2D, imgSize, camMatrix, coeffs, rvecs, tvecs, flags);
250 printf(
"rms error: %lf\n", rms);
254 printf(
"Fisheye coefficients: %lf %lf %lf %lf\n", coeffs[0], coeffs[1], coeffs[2], coeffs[3]);
256 printf(
"Camera matrix:\n");
257 printf(
"[%lf %lf %lf; %lf %lf %lf; %lf %lf %lf]\n", camMatrix(0, 0), camMatrix(0, 1), camMatrix(0, 2),
258 camMatrix(1, 0), camMatrix(1, 1), camMatrix(1, 2), camMatrix(2, 0), camMatrix(2, 1), camMatrix(2, 2));
275 distModel.
k1 = coeffs[0];
276 distModel.
k2 = coeffs[1];
277 distModel.
k3 = coeffs[2];
278 distModel.
k4 = coeffs[3];
282 for (
int i = 0; i < 2; ++i)
284 for (
int j = 0; j < 3; ++j)
286 K[i][j] = camMatrix(i, j);
292 X[0][0] = X[1][1] = X[2][2] = 1;
322 for (
unsigned i = 0; i < params.images.size(); ++i)
325 cv::Mat img = cv::imread(params.images[i]);
326 assert(!img.empty());
331 memset(&imgData, 0,
sizeof(imgData));
333 assert(img.type() == CV_8UC3);
371 snprintf(buf,
sizeof(buf),
"undistort_%03d.jpg", i);
375 catch (std::exception &e)
377 std::cerr <<
"Error: " << e.what() << std::endl;
378 PrintUsage(basename(argv[0]), std::cerr);