OpenCV 3.0:校准不符合预期

时间:2015-07-08 12:10:42

标签: c++ opencv camera-calibration

我在使用OpenCV 3.0 calibrateCamera时得到的结果我没想到。这是我的算法:

  1. 加载30个图像点
  2. 加载30个相应的世界点(在这种情况下为共面)
  3. 使用点校准相机,仅用于不扭曲
  4. 不扭曲图像点,但不要使用内在函数(共面世界点,因此内在函数是狡猾的)
  5. 使用未失真的点来找到单应性,转换为世界点(可以这样做,因为它们都是共面的)
  6. 使用单应性和透视变换将未失真的点映射到世界空间
  7. 将原始世界点与映射点进行比较
  8. 我所拥有的点很吵,只有图像的一小部分。单个视图中有30个共面点,因此我无法获得相机内在函数,但应该能够获得失真系数和单应性来创建前后平行视图。

    正如所料,错误因校准标志而异。但是,它与我的预期相反。如果我允许所有变量调整,我会期望错误降低。我说我希望有更好的模特;我实际上期望过度拟合,但这仍然应该减少错误。我看到的是,我使用的变量越少,我的错误越低。最好的结果是直接的单应性。

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

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

    有什么想法吗?

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

    此处为示例图片

    标有绿色针脚的点。这显然只是一个测试图像。 Example image

    以下是更多示例内容

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

    Set A image, 1920 by 1080, distorted

    Set A image, 1920 by 1080, undistorted

    我会把它归结为一个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;
    

2 个答案:

答案 0 :(得分:1)

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

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

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

在不知道算法的限制的情况下,我可能误解了这个问题,这也是一个选项,在这种情况下我可以回滚。

我想将此作为评论,但我没有足够的分数。

答案 1 :(得分:1)

OpenCV在校准相机内运行Levenberg-Marquardt算法。

https://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm/

这个算法在一个最小的问题上工作正常。在单个图像的情况下,点彼此靠近并且许多维度问题(n =系数的数量)算法可能是不稳定的(特别是对于相机矩阵的错误的初始猜测。算法的收敛在这里被很好地描述:

https://na.math.kit.edu/download/papers/levenberg.pdf/

正如您所写,错误取决于校准标志 - 标志数量会改变要优化的问题的维度。

摄像机校准还可以计算摄像机的姿态,这在校准矩阵错误的模型中会很差。

作为一种解决方案,我建议改变方法。您不需要在此步骤中计算相机矩阵和姿势。因为您知道,这些点位于一个平面上,您可以使用3d-2d平面投影方程来确定点的分布类型。通过分布我的意思是,所有点都将在某种梯形上同等地定位。

然后,您可以在测试图像上使用带有不同distCoeff的cv :: undistort,并计算图像点分布和分布错误。

最后一步是将这些步骤作为优化算法的目标函数,优化失真系数。

这不是最简单的解决方案,但我希望它会对您有所帮助。