OpenCV:solvePnP检测问题

时间:2013-11-08 00:49:21

标签: ios opencv camera markers camera-calibration

我在使用OpenCV精确检测标记时遇到了问题。

我录制了该问题的视频:http://youtu.be/IeSSW4MdyfU

如你所见,我正在检测的标记会在某些摄像机角度稍微移动。我在网上看到这可能是相机校准问题,所以我会告诉大家我是如何校准相机的,也许你能告诉我我做错了什么?

在开始时我正在从各种图像中收集数据,并将校准角存储在_imagePoints向量中

std::vector<cv::Point2f> corners;
_imageSize = cvSize(image->size().width, image->size().height);

bool found = cv::findChessboardCorners(*image, _patternSize, corners);

if (found) {
    cv::Mat *gray_image = new cv::Mat(image->size().height, image->size().width, CV_8UC1);
    cv::cvtColor(*image, *gray_image, CV_RGB2GRAY);

    cv::cornerSubPix(*gray_image, corners, cvSize(11, 11), cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_EPS+ CV_TERMCRIT_ITER, 30, 0.1));

    cv::drawChessboardCorners(*image, _patternSize, corners, found);
}

_imagePoints->push_back(_corners);

然后,在收集到足够的数据后,我正在用这段代码计算相机矩阵和系数:

std::vector< std::vector<cv::Point3f> > *objectPoints = new std::vector< std::vector< cv::Point3f> >();

for (unsigned long i = 0; i < _imagePoints->size(); i++) {
    std::vector<cv::Point2f> currentImagePoints = _imagePoints->at(i);
    std::vector<cv::Point3f> currentObjectPoints;

    for (int j = 0; j < currentImagePoints.size(); j++) {
        cv::Point3f newPoint = cv::Point3f(j % _patternSize.width, j / _patternSize.width, 0);

        currentObjectPoints.push_back(newPoint);
    }

    objectPoints->push_back(currentObjectPoints);
}

std::vector<cv::Mat> rvecs, tvecs;

static CGSize size = CGSizeMake(_imageSize.width, _imageSize.height);
cv::Mat cameraMatrix = [_userDefaultsManager cameraMatrixwithCurrentResolution:size]; // previously detected matrix
cv::Mat coeffs = _userDefaultsManager.distCoeffs; // previously detected coeffs
cv::calibrateCamera(*objectPoints, *_imagePoints, _imageSize, cameraMatrix, coeffs, rvecs, tvecs);

结果就像你在视频中看到的一样。

我做错了什么?这是代码中的问题吗?我应该使用多少图像来执行校准(现在我正在尝试在校准结束前获得20-30张图像)。

我是否应该使用包含错误检测到的棋盘角落的图像,如下所示:

photo 1

或者我应该只使用正确检测到的棋盘:

photo 2 photo 3

我一直在尝试使用圆形网格而不是棋盘,但现在结果要差得多。

如果有问题我如何检测标记:我正在使用solvepnp函数:

solvePnP(modelPoints, imagePoints, [_arEngine currentCameraMatrix], _userDefaultsManager.distCoeffs, rvec, tvec);

使用如下指定的modelPoints:

    markerPoints3D.push_back(cv::Point3d(-kMarkerRealSize / 2.0f, -kMarkerRealSize / 2.0f, 0));
    markerPoints3D.push_back(cv::Point3d(kMarkerRealSize / 2.0f, -kMarkerRealSize / 2.0f, 0));
    markerPoints3D.push_back(cv::Point3d(kMarkerRealSize / 2.0f, kMarkerRealSize / 2.0f, 0));
    markerPoints3D.push_back(cv::Point3d(-kMarkerRealSize / 2.0f, kMarkerRealSize / 2.0f, 0));

imagePoints是处理图像中标记角的坐标(我使用自定义算法来执行此操作)

3 个答案:

答案 0 :(得分:4)

为了正确调试您的问题,我需要所有代码: - )

我假设您正在遵循calibrationpose引用的教程(@kobejohncomment)中建议的方法,以便您的代码遵循以下步骤:

  1. 收集棋盘目标的各种图像
  2. 在点1)的图像中找到棋盘角落
  3. 校准相机(使用cv::calibrateCamera),以便获得内在的相机参数(让我们称之为intrinsic)和镜头失真参数(让我们调用)他们distortion
  4. 收集您自己的自定义目标的图片(目标位于0:57 in your video),如下图Axadiw's own custom target所示,并在其中找到一些相关的点(让我们来电您在图片image_custom_target_verticesworld_custom_target_vertices找到相应3D点的点。
  5. 根据您自定义目标的图像估算旋转矩阵(让我们称之为R)和相机的翻译向量(让我们称之为t)你得到第4点的观点,打电话给cv::solvePnP就像这个cv::solvePnP(world_custom_target_vertices,image_custom_target_vertices,intrinsic,distortion,R,t)
  6. 以3D形式给出8角立方体(让我们称之为world_cube_vertices)你可以通过电话获得8个2D图像点(让他们称之为image_cube_vertices)像cv2::projectPoints
  7. 这样的cv::projectPoints(world_cube_vertices,R,t,intrinsic,distortion,image_cube_vertices)
  8. 使用您自己的draw函数绘制多维数据集。
  9. 现在,绘制过程的最终结果取决于所有先前的计算数据,我们必须找到问题所在:

    校准:正如您在answer中观察到的那样,3)您应该丢弃未正确检测到角落的图像。您需要一个重投影错误的阈值才能丢弃&#34; bad&#34;棋盘目标图像。引自calibration tutorial

      

    重新投影错误

         

    重新投影误差可以很好地估计出有多精确   找到参数。这应该尽可能接近零。特定   我们首先是内在的,扭曲的,旋转的和平移的矩阵   使用cv2.projectPoints()将对象点转换为图像点。   然后我们计算出我们得到的绝对规范   变换和角点查找算法。找到平均值   错误我们计算误差计算的算术平均值   所有校准图像。

    通常,您会通过一些实验找到合适的阈值。通过这一额外步骤,您将获得intrinsicdistortion的更好价值。

    找到您自己的自定义目标:在我看来,您在标记为第4点的步骤中解释了如何找到自己的自定义目标。你得到了预期的image_custom_target_vertices吗?你丢弃图像的结果是&#34;坏&#34;?

    相机的姿势:我认为在5)你使用3)中找到的intrinsic,你确定同时相机中没有任何变化吗?参考Callari's Second Rule of Camera Calibration

      

    相机校准的第二条规则:&#34;不要触摸镜头   校准后#34;特别是,你可能不会重新聚焦或改变   f-stop,因为聚焦和光圈都会影响非线性镜头   失真和(虽然不那么,取决于镜头)的领域   视图。当然,你可以完全自由地改变曝光时间,   因为它根本不会影响镜片的几何形状。

    然后draw函数可能存在一些问题。

答案 1 :(得分:1)

所以,我已经用我的代码进行了很多实验,而且我还没有修复主要问题(移位的对象),但我已经设法回答了我提出的一些校准问题。

首先 - 为了获得良好的校准结果,您必须使用具有正确检测到的网格元素/圆圈位置的图像!。在校准过程中使用所有捕获的图像(即使那些未正确检测到的图像)也会导致校准错误。

我已尝试过各种校准模式:

  • 非对称圆圈模式CALIB_CB_ASYMMETRIC_GRID),比其他任何模式都要糟糕得多。更糟糕的结果我的意思是它产生了许多错误检测到的角落,如:

photo 1 photo 2

我已经尝试了CALIB_CB_CLUSTERING并且它没有多大帮助 - 在某些情况下(不同的光环境),它变得更好,但并不多。

  • 对称圆圈模式CALIB_CB_SYMMETRIC_GRID) - 比非对称网格效果更好,但我的结果仍然比标准网格(棋盘)差得多。它经常会产生如下错误:

photo 3

  • 棋盘(使用findChessboardCorners函数找到) - 此方法可以产生最佳效果 - 它不会经常产生错位角,并且几乎每次校准都会产生类似的效果 - 对称圆网格
  • 的可能结果

对于每次校准,我一直在使用来自不同角度的20-30张图像。我甚至尝试过100多张图像,但校准结果没有产生明显的变化,而不是少量的图像。值得注意的是,大量的测试图像增加了以非线性方式计算相机参数所需的时间(在480x360分辨率下的100个测试图像在iPad4中计算25分钟,而使用~50个图像计算4分钟)

我还尝试了solvePNP参数 - 但也没有给我任何可接受的结果:我尝试了所有3种检测方法(ITERATIVEEPNP和{ {1}}),但我没有看到明显的变化。

此外,我尝试将P3P设置为useExtrinsicGuess,并且我在之前的检测中使用了truervec,但是这个结果完全消失了检测到立方体。

我已经没有想法 - 还有什么可能影响这些转变的问题?

答案 2 :(得分:0)

对于那些仍然感兴趣的人: 这是一个老问题,但我认为你的问题不是校准错误。 我使用OpenCV和SceneKit为iOS开发了一个AR应用程序,我遇到了同样的问题。

我认为你的问题是多维数据集的错误渲染位置: OpenCV的solvePnP返回标记中心的X,Y,Z坐标,但是您想要在标记上,沿着标记的Z轴的特定距离渲染立方体,正好在立方体边长的一半处。因此,您需要改善此距离的标记平移向量的Z坐标。

实际上,当您从顶部看到立方体时,立方体将正确渲染。 我已经做了一个图像来解释这个问题,但是我的声誉无法发布它。