Ask Your Question

Revision history [back]

I am getting less calibration error with simpler models. How, and am I doing it wrong?

I'm getting results I don't expect when I use OpenCV 3.0 calibrateCamera. Here is my algorithm:

  1. Load in 30 image points
  2. Load in 30 corresponding world points (coplanar in this case)
  3. Use points to calibrate the camera, just for un-distorting
  4. Un-distort the image points, but don't use the intrinsics (coplanar world points, so intrinsics are dodgy)
  5. Use the undistorted points to find a homography, transforming to world points (can do this because they are all coplanar)
  6. Use the homography and perspective transform to map the undistorted points to the world space
  7. Compare the original world points to the mapped points

The points I have are noisy and only a small section of the image. There are 30 coplanar points from a single view so I can't get camera intrinsics, but should be able to get distortion coefficients and a homography to create a fronto-parallel view.

As expected, the error varies depending on the calibration flags. However, it varies opposite to what I expected. If I allow all variables to adjust, I would expect error to come down. I am not saying I expect a better model; I actually expect over-fitting, but that should still reduce error given my test set is also my learning set. What I see though is that the fewer variables I use, the lower my error. The best result is with a straight homography.

The code doesn't appear to have bugs; I've used "better" points and it works perfectly. I want to emphasize that the solution here can't be to use better points or perform a better calibration; the whole point of the exercise is to see how the various calibration models respond to different qualities of calibration data. The scenarios I'm looking at often have distorted lenses and only a few points in a single region to calibrate on.

Any ideas?

I've included code here. The different models are achieved by commenting out lines as indicated.

// Load image points
std::vector<cv::Point2f> im_points;
im_points.push_back(cv::Point2f(1206, 1454));
im_points.push_back(cv::Point2f(1245, 1443));
im_points.push_back(cv::Point2f(1284, 1429));
im_points.push_back(cv::Point2f(1315, 1456));
im_points.push_back(cv::Point2f(1352, 1443));
im_points.push_back(cv::Point2f(1383, 1431));
im_points.push_back(cv::Point2f(1431, 1458));
im_points.push_back(cv::Point2f(1463, 1445));
im_points.push_back(cv::Point2f(1489, 1432));
im_points.push_back(cv::Point2f(1550, 1461));
im_points.push_back(cv::Point2f(1574, 1447));
im_points.push_back(cv::Point2f(1597, 1434));
im_points.push_back(cv::Point2f(1673, 1463));
im_points.push_back(cv::Point2f(1691, 1449));
im_points.push_back(cv::Point2f(1708, 1436));
im_points.push_back(cv::Point2f(1798, 1464));
im_points.push_back(cv::Point2f(1809, 1451));
im_points.push_back(cv::Point2f(1819, 1438));
im_points.push_back(cv::Point2f(1925, 1467));
im_points.push_back(cv::Point2f(1929, 1454));
im_points.push_back(cv::Point2f(1935, 1440));
im_points.push_back(cv::Point2f(2054, 1470));
im_points.push_back(cv::Point2f(2052, 1456));
im_points.push_back(cv::Point2f(2051, 1443));
im_points.push_back(cv::Point2f(2182, 1474));
im_points.push_back(cv::Point2f(2171, 1459));
im_points.push_back(cv::Point2f(2164, 1446));
im_points.push_back(cv::Point2f(2306, 1474));
im_points.push_back(cv::Point2f(2292, 1462));
im_points.push_back(cv::Point2f(2278, 1449));

// Create corresponding world / object points
std::vector<cv::Point3f> world_points;
for (int i = 0; i < 30; i++) {
    world_points.push_back(cv::Point3f(5 * (i / 3), 4 * (i % 3), 0.0f));
}

// Perform calibration
// Flags are set out so they can be commented out and "freed" easily
int calibration_flags = 0
    | cv::CALIB_FIX_K1
    | cv::CALIB_FIX_K2
    | cv::CALIB_FIX_K3
    | cv::CALIB_FIX_K4
    | cv::CALIB_FIX_K5
    | cv::CALIB_FIX_K6
    | cv::CALIB_ZERO_TANGENT_DIST
    | 0;

// Initialise matrix
cv::Mat intrinsic_matrix = cv::Mat(3, 3, CV_64F);
intrinsic_matrix.ptr<float>(0)[0] = 1;
intrinsic_matrix.ptr<float>(1)[1] = 1;
cv::Mat distortion_coeffs = cv::Mat::zeros(5, 1, CV_64F);

// Rotation and translation vectors
std::vector<cv::Mat> undistort_rvecs;
std::vector<cv::Mat> undistort_tvecs;

// Wrap in an outer vector for calibration
std::vector<std::vector<cv::Point2f>>im_points_v(1, im_points);
std::vector<std::vector<cv::Point3f>>w_points_v(1, world_points);

// Calibrate; only 1 plane, so intrinsics can't be trusted
cv::Size image_size(4000, 3000);
calibrateCamera(w_points_v, im_points_v,
    image_size, intrinsic_matrix, distortion_coeffs, 
    undistort_rvecs, undistort_tvecs, calibration_flags);

// Undistort im_points
std::vector<cv::Point2f> ud_points;
cv::undistortPoints(im_points, ud_points, intrinsic_matrix, distortion_coeffs);

// ud_points have been "unintrinsiced", but we don't know the intrinsics, so reverse that   
double fx = intrinsic_matrix.at<double>(0, 0);
double fy = intrinsic_matrix.at<double>(1, 1);
double cx = intrinsic_matrix.at<double>(0, 2);
double cy = intrinsic_matrix.at<double>(1, 2);

for (std::vector<cv::Point2f>::iterator iter = ud_points.begin(); iter != ud_points.end(); iter++) {
    iter->x = iter->x * fx + cx;
    iter->y = iter->y * fy + cy;
}

// Find a homography mapping the undistorted points to the known world points, ground plane
cv::Mat homography = cv::findHomography(ud_points, world_points);

// Transform the undistorted image points to the world points (2d only, but z is constant)
std::vector<cv::Point2f> estimated_world_points;    
std::cout << "homography" << homography << std::endl;
cv::perspectiveTransform(ud_points, estimated_world_points, homography);

// Work out error
double sum_sq_error = 0;
for (int i = 0; i < 30; i++) {
    double err_x = estimated_world_points.at(i).x - world_points.at(i).x;
    double err_y = estimated_world_points.at(i).y - world_points.at(i).y;

    sum_sq_error += err_x*err_x + err_y*err_y;
}
std::cout << "Sum squared error is: " << sum_sq_error << std::endl;

I am getting less calibration error with simpler models. How, and am I doing it wrong?

Crosspost at SO: http://stackoverflow.com/questions/31292266/opencv-3-0-calibration-not-fitting-as-expected

I'm getting results I don't expect when I use OpenCV 3.0 calibrateCamera. Here is my algorithm:

  1. Load in 30 image points
  2. Load in 30 corresponding world points (coplanar in this case)
  3. Use points to calibrate the camera, just for un-distorting
  4. Un-distort the image points, but don't use the intrinsics (coplanar world points, so intrinsics are dodgy)
  5. Use the undistorted points to find a homography, transforming to world points (can do this because they are all coplanar)
  6. Use the homography and perspective transform to map the undistorted points to the world space
  7. Compare the original world points to the mapped points

The points I have are noisy and only a small section of the image. There are 30 coplanar points from a single view so I can't get camera intrinsics, but should be able to get distortion coefficients and a homography to create a fronto-parallel view.

As expected, the error varies depending on the calibration flags. However, it varies opposite to what I expected. If I allow all variables to adjust, I would expect error to come down. I am not saying I expect a better model; I actually expect over-fitting, but that should still reduce error given my test set is also my learning set. What I see though is that the fewer variables I use, the lower my error. The best result is with a straight homography.

The code doesn't appear to have bugs; I've used "better" points and it works perfectly. I want to emphasize that the solution here can't be to use better points or perform a better calibration; the whole point of the exercise is to see how the various calibration models respond to different qualities of calibration data. The scenarios I'm looking at often have distorted lenses and only a few points in a single region to calibrate on.

Any ideas?

I've included code here. The different models are achieved by commenting out lines as indicated.

// Load image points
std::vector<cv::Point2f> im_points;
im_points.push_back(cv::Point2f(1206, 1454));
im_points.push_back(cv::Point2f(1245, 1443));
im_points.push_back(cv::Point2f(1284, 1429));
im_points.push_back(cv::Point2f(1315, 1456));
im_points.push_back(cv::Point2f(1352, 1443));
im_points.push_back(cv::Point2f(1383, 1431));
im_points.push_back(cv::Point2f(1431, 1458));
im_points.push_back(cv::Point2f(1463, 1445));
im_points.push_back(cv::Point2f(1489, 1432));
im_points.push_back(cv::Point2f(1550, 1461));
im_points.push_back(cv::Point2f(1574, 1447));
im_points.push_back(cv::Point2f(1597, 1434));
im_points.push_back(cv::Point2f(1673, 1463));
im_points.push_back(cv::Point2f(1691, 1449));
im_points.push_back(cv::Point2f(1708, 1436));
im_points.push_back(cv::Point2f(1798, 1464));
im_points.push_back(cv::Point2f(1809, 1451));
im_points.push_back(cv::Point2f(1819, 1438));
im_points.push_back(cv::Point2f(1925, 1467));
im_points.push_back(cv::Point2f(1929, 1454));
im_points.push_back(cv::Point2f(1935, 1440));
im_points.push_back(cv::Point2f(2054, 1470));
im_points.push_back(cv::Point2f(2052, 1456));
im_points.push_back(cv::Point2f(2051, 1443));
im_points.push_back(cv::Point2f(2182, 1474));
im_points.push_back(cv::Point2f(2171, 1459));
im_points.push_back(cv::Point2f(2164, 1446));
im_points.push_back(cv::Point2f(2306, 1474));
im_points.push_back(cv::Point2f(2292, 1462));
im_points.push_back(cv::Point2f(2278, 1449));

// Create corresponding world / object points
std::vector<cv::Point3f> world_points;
for (int i = 0; i < 30; i++) {
    world_points.push_back(cv::Point3f(5 * (i / 3), 4 * (i % 3), 0.0f));
}

// Perform calibration
// Flags are set out so they can be commented out and "freed" easily
int calibration_flags = 0
    | cv::CALIB_FIX_K1
    | cv::CALIB_FIX_K2
    | cv::CALIB_FIX_K3
    | cv::CALIB_FIX_K4
    | cv::CALIB_FIX_K5
    | cv::CALIB_FIX_K6
    | cv::CALIB_ZERO_TANGENT_DIST
    | 0;

// Initialise matrix
cv::Mat intrinsic_matrix = cv::Mat(3, 3, CV_64F);
intrinsic_matrix.ptr<float>(0)[0] = 1;
intrinsic_matrix.ptr<float>(1)[1] = 1;
cv::Mat distortion_coeffs = cv::Mat::zeros(5, 1, CV_64F);

// Rotation and translation vectors
std::vector<cv::Mat> undistort_rvecs;
std::vector<cv::Mat> undistort_tvecs;

// Wrap in an outer vector for calibration
std::vector<std::vector<cv::Point2f>>im_points_v(1, im_points);
std::vector<std::vector<cv::Point3f>>w_points_v(1, world_points);

// Calibrate; only 1 plane, so intrinsics can't be trusted
cv::Size image_size(4000, 3000);
calibrateCamera(w_points_v, im_points_v,
    image_size, intrinsic_matrix, distortion_coeffs, 
    undistort_rvecs, undistort_tvecs, calibration_flags);

// Undistort im_points
std::vector<cv::Point2f> ud_points;
cv::undistortPoints(im_points, ud_points, intrinsic_matrix, distortion_coeffs);

// ud_points have been "unintrinsiced", but we don't know the intrinsics, so reverse that   
double fx = intrinsic_matrix.at<double>(0, 0);
double fy = intrinsic_matrix.at<double>(1, 1);
double cx = intrinsic_matrix.at<double>(0, 2);
double cy = intrinsic_matrix.at<double>(1, 2);

for (std::vector<cv::Point2f>::iterator iter = ud_points.begin(); iter != ud_points.end(); iter++) {
    iter->x = iter->x * fx + cx;
    iter->y = iter->y * fy + cy;
}

// Find a homography mapping the undistorted points to the known world points, ground plane
cv::Mat homography = cv::findHomography(ud_points, world_points);

// Transform the undistorted image points to the world points (2d only, but z is constant)
std::vector<cv::Point2f> estimated_world_points;    
std::cout << "homography" << homography << std::endl;
cv::perspectiveTransform(ud_points, estimated_world_points, homography);

// Work out error
double sum_sq_error = 0;
for (int i = 0; i < 30; i++) {
    double err_x = estimated_world_points.at(i).x - world_points.at(i).x;
    double err_y = estimated_world_points.at(i).y - world_points.at(i).y;

    sum_sq_error += err_x*err_x + err_y*err_y;
}
std::cout << "Sum squared error is: " << sum_sq_error << std::endl;