This sample application performs a fisheye lens calibration using input images taken with the same camera/lens. Then it uses Image 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.3/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.
#include <opencv2/core/version.hpp>
#if CV_MAJOR_VERSION >= 3
# include <opencv2/imgcodecs.hpp>
#else
# include <opencv2/highgui/highgui.hpp>
#endif
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <string.h>
#include <unistd.h>
#include <iostream>
#define CHECK_STATUS(STMT) \
do \
{ \
VPIStatus status = (STMT); \
if (status != VPI_SUCCESS) \
{ \
throw std::runtime_error(vpiStatusGetName(status)); \
} \
} while (0);
static void PrintUsage(const char *progname, std::ostream &out)
{
out << "Usage: " << progname << " <-c W,H> [-s win] <image1> [image2] [image3] ...\n"
<< " where,\n"
<< " W,H\tcheckerboard with WxH squares\n"
<< " win\tsearch window width around checkerboard vertex used\n"
<< "\tin refinement, default is 0 (disable refinement)\n"
<< " imageN\tinput images taken with a fisheye lens camera" << std::endl;
}
struct Params
{
cv::Size vtxCount;
int searchWinSize;
std::vector<const char *> images;
};
static Params ParseParameters(int argc, char *argv[])
{
Params params = {};
cv::Size cbSize;
opterr = 0;
int opt;
while ((opt = getopt(argc, argv, "hc:s:")) != -1)
{
switch (opt)
{
case 'h':
PrintUsage(basename(argv[0]), std::cout);
return {};
case 'c':
if (sscanf(optarg, "%u,%u", &cbSize.width, &cbSize.height) != 2)
{
throw std::invalid_argument("Error parsing checkerboard information");
}
params.vtxCount.width = cbSize.width - 1;
params.vtxCount.height = cbSize.height - 1;
break;
case 's':
if (sscanf(optarg, "%d", ¶ms.searchWinSize) != 1)
{
throw std::invalid_argument("Error parseing search window size");
}
if (params.searchWinSize < 0)
{
throw std::invalid_argument("Search window size must be >= 0");
}
break;
case '?':
throw std::invalid_argument(std::string("Option -") + (char)optopt + " not recognized");
}
}
for (int i = optind; i < argc; ++i)
{
params.images.push_back(argv[i]);
}
if (params.images.empty())
{
throw std::invalid_argument("At least one image must be defined");
}
if (cbSize.width <= 3 || cbSize.height <= 3)
{
throw std::invalid_argument("Checkerboard size must have at least 3x3 squares");
}
if (params.searchWinSize == 1)
{
throw std::invalid_argument("Search window size must be 0 (default) or >= 2");
}
return params;
}
int main(int argc, char *argv[])
{
try
{
Params params = ParseParameters(argc, argv);
if (params.images.empty())
{
return 0;
}
std::vector<std::vector<cv::Point2f>> corners2D;
cv::Size imgSize = {};
for (unsigned i = 0; i < params.images.size(); ++i)
{
cv::Mat img = cv::imread(params.images[i]);
if (img.empty())
{
throw std::runtime_error("Can't read " + std::string(params.images[i]));
}
if (imgSize == cv::Size{})
{
imgSize = img.size();
}
else if (imgSize != img.size())
{
throw std::runtime_error("All images must have same size");
}
std::vector<cv::Point2f> cbVertices;
if (findChessboardCorners(img, params.vtxCount, cbVertices,
cv::CALIB_CB_ADAPTIVE_THRESH + cv::CALIB_CB_NORMALIZE_IMAGE))
{
if (params.searchWinSize >= 2)
{
cv::Mat gray;
cvtColor(img, gray, cv::COLOR_BGR2GRAY);
cornerSubPix(gray, cbVertices, cv::Size(params.searchWinSize / 2, params.searchWinSize / 2),
cv::Size(-1, -1),
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 30, 0.0001));
}
corners2D.push_back(std::move(cbVertices));
}
else
{
std::cerr << "Warning: checkerboard pattern not found in image " << params.images[i] << std::endl;
}
}
std::vector<cv::Point3f> initialCheckerboard3DVertices;
for (int i = 0; i < params.vtxCount.height; ++i)
{
for (int j = 0; j < params.vtxCount.width; ++j)
{
initialCheckerboard3DVertices.emplace_back(j, i, 0);
}
}
std::vector<std::vector<cv::Point3f>> corners3D(corners2D.size(), initialCheckerboard3DVertices);
using Mat3 = cv::Matx<double, 3, 3>;
Mat3 camMatrix = Mat3::eye();
std::vector<double> coeffs(4);
int flags = cv::fisheye::CALIB_FIX_SKEW;
{
cv::Mat rvecs, tvecs;
double rms = cv::fisheye::calibrate(corners3D, corners2D, imgSize, camMatrix, coeffs, rvecs, tvecs, flags);
printf("rms error: %lf\n", rms);
}
printf("Fisheye coefficients: %lf %lf %lf %lf\n", coeffs[0], coeffs[1], coeffs[2], coeffs[3]);
printf("Camera matrix:\n");
printf("[%lf %lf %lf; %lf %lf %lf; %lf %lf %lf]\n", camMatrix(0, 0), camMatrix(0, 1), camMatrix(0, 2),
camMatrix(1, 0), camMatrix(1, 1), camMatrix(1, 2), camMatrix(2, 0), camMatrix(2, 1), camMatrix(2, 2));
distModel.
k1 = coeffs[0];
distModel.
k2 = coeffs[1];
distModel.
k3 = coeffs[2];
distModel.
k4 = coeffs[3];
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < 3; ++j)
{
K[i][j] = camMatrix(i, j);
}
}
X[0][0] = X[1][1] = X[2][2] = 1;
for (unsigned i = 0; i < params.images.size(); ++i)
{
cv::Mat img = cv::imread(params.images[i]);
assert(!img.empty());
{
memset(&imgData, 0, sizeof(imgData));
assert(img.type() == CV_8UC3);
}
char buf[64];
snprintf(buf, sizeof(buf), "undistort_%03d.jpg", i);
imwrite(buf, img);
}
}
catch (std::exception &e)
{
std::cerr << "Error: " << e.what() << std::endl;
PrintUsage(basename(argv[0]), std::cerr);
if (ctx != nullptr)
{
}
return 1;
}
if (ctx != nullptr)
{
}
return 0;
}