OpenCV 3.0:校准不符合预期

tim*_*mbo 41 c++ opencv camera-calibration

当我使用OpenCV 3.0 calibrateCamera时,我得到的结果是我没想到的.这是我的算法:

  1. 加载30个图像点
  2. 加载30个相应的世界点(在这种情况下是共面的)
  3. 使用点校准相机,只是为了不扭曲
  4. 不扭曲图像点,但不使用内在函数(共面世界点,因此内在函数是狡猾的)
  5. 使用未失真的点来找到单应性,转换为世界点(可以这样做,因为它们都是共面的)
  6. 使用单应性和透视变换将未失真的点映射到世界空间
  7. 将原始世界点与映射点进行比较

我所拥有的点是嘈杂的,只有一小部分图像.单个视图中有30个共面点,因此我无法获得相机内在函数,但应该能够获得失真系数和单应性以创建前平行视图.

正如预期的那样,误差取决于校准标志.但是,它与我的预期相反.如果我允许所有变量调整,我会期望错误降低.我不是说我期待一个更好的模型; 我实际上期望过度拟合,但这仍然应该减少错误.我看到的是,我使用的变量越少,我的错误越低.最好的结果是直接的单应性.

我有两个可疑的原因,但它们似乎不太可能,我想在播出它们之前听到一个纯粹的答案.我已经拿出代码来做我正在谈论的事情.它有点长,但它包括加载点.

代码似乎没有错误; 我使用了"更好"的点,它完美无缺.我想强调的是,这里的解决方案不能是使用更好的点或执行更好的校准; 练习的重点是看各种校准模型如何响应不同质量的校准数据.

有任何想法吗?

添加

要清楚,我知道结果会很糟糕,我希望如此.我也明白,我可能会学习不良的失真参数,这会导致在测试尚未用于训练模型的点时导致更糟糕的结果.我不明白的是当使用训练集作为测试集时,失真模型如何有更多的错误.也就是说,如果cv :: calibrateCamera应该选择参数来减少所提供的训练点集的误差,那么它产生的错误比它刚为K!,K2,... K6,P1选择0时产生的误差更大. ,P2.不管数据与否,它至少应该在训练集上做得更好.在我说数据不适合这个模型之前,我必须确保我能用尽可能多的数据做到最好,而且我现在不能说这个.

这是一个示例图像

标有绿色针脚的点.这显然只是一个测试图像. 示例图片

这是更多的例子

在下文中,图像是从上面的大图像中裁剪出来的.该中心没有改变.当我只用绿色引脚手动标记的点并且允许K1(仅K1)从0变化时,会发生这种情况:

之前 设置图像,1920 x 1080,失真

将图像设置为1920 x 1080,不失真

我会把它归结为一个bug,但是当我使用更大的一组点来覆盖更多的屏幕时,即使是在一个平面上,它的工作也相当不错.这看起来很可怕.但是,错误并不像你从图片中看到的那样糟糕.

// 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;
Run Code Online (Sandbox Code Playgroud)

小智 1

我会随机抽取 30 个输入点并计算每种情况下的单应性以及估计单应性下的误差(RANSAC 方案),并验证错误级别和单应性参数之间的一致性,这可以只是全局优化的验证过程。我知道这似乎没有必要,但这只是对程序对输入(噪音水平、位置)敏感程度的健全性检查

此外,修复大多数变量可以使错误最少,这似乎是合乎逻辑的,因为最小化过程中的自由度较小。我会尝试修复不同的问题以建立另一个共识。至少这会让您知道哪些变量对输入的噪声水平最敏感。

希望图像的一小部分靠近图像中心,因为它会产生最少的镜头畸变。在您的情况下可以使用不同的失真模型吗?一种更可行的方法是在给定图案相对于图像中心的位置的情况下调整失真参数的数量。

在不知道算法的约束的情况下,我可能会误解这个问题,这也是一个选择,在这种情况下我可以回滚。

我更想将此作为评论,但我没有足够的观点。