【问题标题】:Transform a frame to be as if it was taken from above using OpenCV使用 OpenCV 将帧转换为好像从上方拍摄的一样
【发布时间】:2016-05-09 14:09:21
【问题描述】:

我正在开展一个使用光流技术估计无人机(四轴飞行器)位置的项目。我目前有一个使用 OpenCV 的 farneback 算法的代码。当相机始终指向地面时,当前代码工作正常。

现在,我想在相机未指向正下方的情况下添加支持 - 这意味着四轴飞行器现在具有俯仰/滚动/偏航(欧拉角)。四轴飞行器欧拉角是已知的,我正在寻找一种方法来计算和应用基于已知的当前欧拉角所需的变换。这样结果图像就好像是从上面拍摄的一样(见下图)。

我通过来自 OpenCV 的 findHomographygetPerspectiveTransform 函数找到了在具有 4 个角的 2 组(源和目标)时计算转换的方法。但是我找不到任何方法可以只知道欧拉角(因为我不知道目标图像核心)。

所以我的问题是我可以使用什么方法以及如何将帧转换为好像它是从上面拍摄的,如果需要的话,只使用欧拉角和相机距地面的高度?

为了展示我的需要:

我当前代码的相关部分如下:

for(;;)
{
    Mat m, disp, warp;
    vector<Point2f> corners;
    // take out frame- still distorted
    cap >> origFrame;
    // undistort the frame using the calibration parameters
    cv::undistort(origFrame, undistortFrame, cameraMatrix, distCoeffs, noArray());
    // lower the process effort by transforming the picture to gray
    cvtColor(undistortFrame, gray, COLOR_BGR2GRAY);

    if( !prevgray.empty() )
    {
        // calculate flow
        calcOpticalFlowFarneback(prevgray, gray, uflow, 0.5, 3/*def 3 */, 10/* def 15*/, 3, 3, 1.2 /* def 1.2*/, 0);
        uflow.copyTo(flow);

        // get average
        calcAvgOpticalFlow(flow, 16, corners);

        // calculate range of view - 2*tan(fov/2)*distance
        rovX = 2*0.44523*distanceSonar*100;     // 2 * tan(48/2) * dist(cm)
        rovY = 2*0.32492*distanceSonar*100;     // 2 * tan(36/2) * dist(cm)

        // calculate final x, y location
        location[0] += (currLocation.x/WIDTH_RES)*rovX;
        location[1] += (currLocation.y/HEIGHT_RES)*rovY;
    }
    //break conditions
    if(waitKey(1)>=0)
        break;
    if(end_run)
        break;
    std::swap(prevgray, gray);
}  

更新:

成功添加旋转后,我仍然需要将图像居中(并且不要超出框架窗口,如下所示)。我想我需要某种翻译。 我希望源图像的中心位于目标图像的中心。我该如何添加它?

有效的旋转功能:

void rotateFrame(const Mat &input, Mat &output, Mat &A , double roll, double pitch, double yaw){
    Mat Rx = (Mat_<double>(3, 3) <<
              1,          0,           0,
              0, cos(roll), -sin(roll),
              0, sin(roll),  cos(roll));
    Mat Ry = (Mat_<double>(3, 3) <<
              cos(pitch), 0, sin(pitch),
              0, 1,          0,
              -sin(pitch), 0,  cos(pitch));
    Mat Rz = (Mat_<double>(3, 3) <<
              cos(yaw), -sin(yaw), 0,
              sin(yaw),  cos(yaw), 0,
              0,          0,           1);

    Mat R = Rx*Ry*Rz;
    Mat trans = A*R*A.inv();

    warpPerspective(input, output, trans, input.size());
}

当我使用rotateFrame(origFrame, processedFrame, cameraMatrix, 0, 0, 0); 运行它时,我得到了预期的图像:

但是当我以 10 度滚动运行它时 rotateFrame(origFrame, processedFrame, cameraMatrix, 20*(M_PI/180), 0, 0);。图像超出框架窗口:

【问题讨论】:

  • "OpenCV 中的 FindHomography 或 getPerspectiveTransform 函数。但是没有一个在不知道角位置但已知角度的情况下这样做" 你确定它们不适用于角位置吗?因为我使用四个角进行转换
  • 不够清楚,我已经编辑了我的帖子。我的意思是我不知道目标角,但我确实知道欧拉角。

标签: c++ opencv image-processing computer-vision image-rotation


【解决方案1】:

如果您有一个calibration intrinsics matrix A (3x3),并且相机姿势之间没有平移,那么您只需找到单应性 H (3x3) 到 construct rotation matrix R (3x3) from euler angles 并应用以下公式:

H = A * R * A.inv()

.inv() 是矩阵求逆。

更新:

如果你想使图像居中,你应该这样添加翻译: (这是找到中心的扭曲位置并将该点平移回中心)

|dx|       | 320 / 2 |
|dy| = H * | 240 / 2 |
|1 |       | 1       |

    | 1 0 (320/2-dx) | 
W = | 0 1 (240/2-dy) | * H
    | 0 0 1          |

W 是你的最终转变。

【讨论】:

  • 谢谢!你能解释更多关于如何创建校准内在矩阵的信息吗?我还需要添加翻译吗? (不仅仅是轮换)。
  • @A.Sarid 内在函数矩阵由内在函数构成:焦距、偏斜和主点。您需要校准相机以获得这些值。您可以找到更多信息there。关于第二个问题-如果您想从同一点但从不同角度查看飞机,则无需添加翻译。
  • 我已经更新了我的帖子 - 请看一下。即使在小角度,图像也会超出框架。你确定我不需要任何翻译吗?
  • 首先,您在旋转矩阵构造中存在错误。请参阅字符串 (48) here。其次,你的A矩阵是什么?你是怎么得到它的?
  • 这有关系吗?最后的R 很重要,例如here 他们使用不同的约定。其次,我有我之前为这台相机所做的校准的内在矩阵。
【解决方案2】:

我得出一个结论,我必须使用 4x4 单应矩阵才能得到我想要的。为了找到正确的单应矩阵,我们需要:

  1. 3D 旋转矩阵R
  2. 相机标定本征矩阵A1及其逆矩阵A2
  3. 平移矩阵T

我们可以通过乘以绕 X、Y、Z 轴的旋转矩阵来组成 3D 旋转矩阵 R

Mat R = RZ * RY * RX  

为了对图像应用变换并使其居中,我们需要添加由 4x4 矩阵给出的平移,其中dx=0; dy=0; dz=1

Mat T = (Mat_<double>(4, 4) <<
         1, 0, 0, dx,
         0, 1, 0, dy,
         0, 0, 1, dz,
         0, 0, 0, 1);

给定所有这些矩阵,我们可以组成我们的单应矩阵H

Mat H = A2 * (T * (R * A1))

有了这个单应矩阵,我们就可以使用 OpenCV 中的warpPerspective 函数来应用转换。

warpPerspective(input, output, H, input.size(), INTER_LANCZOS4);

对于这个解决方案的结论和完整性,这里是完整的代码:

void rotateImage(const Mat &input, UMat &output, double roll, double pitch, double yaw,
                 double dx, double dy, double dz, double f, double cx, double cy)
  {
    // Camera Calibration Intrinsics Matrix
    Mat A2 = (Mat_<double>(3,4) <<
              f, 0, cx, 0,
              0, f, cy, 0,
              0, 0, 1,  0);
    // Inverted Camera Calibration Intrinsics Matrix
    Mat A1 = (Mat_<double>(4,3) <<
              1/f, 0,   -cx/f,
              0,   1/f, -cy/f,
              0,   0,   0,
              0,   0,   1);
    // Rotation matrices around the X, Y, and Z axis
    Mat RX = (Mat_<double>(4, 4) <<
              1, 0,         0,          0,
              0, cos(roll), -sin(roll), 0,
              0, sin(roll), cos(roll),  0,
              0, 0,         0,          1);
    Mat RY = (Mat_<double>(4, 4) <<
              cos(pitch),  0, sin(pitch), 0,
              0,           1, 0,          0,
              -sin(pitch), 0, cos(pitch), 0,
              0,           0, 0,          1);
    Mat RZ = (Mat_<double>(4, 4) <<
              cos(yaw), -sin(yaw), 0, 0,
              sin(yaw),  cos(yaw), 0, 0,
              0,          0,       1, 0,
              0,          0,       0, 1);
    // Translation matrix
    Mat T = (Mat_<double>(4, 4) <<
             1, 0, 0, dx,
             0, 1, 0, dy,
             0, 0, 1, dz,
             0, 0, 0, 1);
    // Compose rotation matrix with (RX, RY, RZ)
    Mat R = RZ * RY * RX;
    // Final transformation matrix
    Mat H = A2 * (T * (R * A1));
    // Apply matrix transformation
    warpPerspective(input, output, H, input.size(), INTER_LANCZOS4);
}

结果:

【讨论】:

  • 我在做类似的事情时偶然发现了您的解决方案,您是否想出了解决裁剪问题的方法?
  • @DeusExMachina25 裁剪问题是什么意思?上面的这个解决方案是我用过的最新的解决方案。也许还有其他更好/更简单的方法可以做到这一点,但上面的这个完成了问题中描述的工作。
  • 我最终开发了一个不同的解决方案,但用你的来学习。我指的是图像被放置在一个不够大的盒子中,如您的帖子中所示。部分图像被边界框剪裁。
【解决方案3】:

这就是我在Eigen 中使用 4 个角的方法:

// Desired four corners
std::vector<Eigen::Vector2f> Normalized_Reference_Pattern = { Eigen::Vector2f(0, 0), Eigen::Vector2f(0, 2), Eigen::Vector2f(2, 0), Eigen::Vector2f(2, 2) }; 

// Current four points
std::vector<Eigen::Vector2f> CurrentCentroids = { /* Whatever four corners you want, but relative sueqnece to above */ };

// Transform for current to desired
auto Master_Transform = get_perspective_transform(CurrentCentroids, Normalized_Reference_Pattern);

// abilitu to use the same tranformation for other points (other than the corners) in the image
Eigen::Vector2f Master_Transform_Centroid = Master_Transform * Eigen::Vector2f(currentX, currentY);

这是我的黑匣子:

Eigen::Matrix3f get_perspective_transform(const std::vector<Eigen::Vector2f>& points_from,const std::vector<Eigen::Vector2f>& points_to)
{
    cv::Mat transform_cv = cv::getPerspectiveTransform(
        convert::to_cv(points_from), 
        convert::to_cv(points_to));

    Eigen::Matrix3f transform_eigen;
    cv::cv2eigen(transform_cv, transform_eigen);
    return transform_eigen;
}

【讨论】:

  • 感谢分享!但是,如果我只有四轴飞行器的欧拉角,我仍然无法理解如何知道所需的 4 个角。
  • @A.您如何将图像存储在系统中?你不能只取角点[0,0][0, WidthSize-1][Height-1, 0][Height-1, Width-1]吗?
  • 是的,但这是我的来源,如果我理解正确的话,目标角落必须是某种飞人。
  • 您可以选择所需的角,它可以是任何角。在我的示例中,我选择新的“图像”为 2x2; Eigen::Vector2f(0, 0), Eigen::Vector2f(0, 2), Eigen::Vector2f(2, 0), Eigen::Vector2f(2, 2) 是我的“目的地”角落,那么我当前的 ROI 点将被转换为那个。在您的情况下,您的投资回报率就是整个图像
  • 好的。那么变换怎么知道图像的角度呢?例如,如果我的相机距离地面 30 度。我想必须有一些使用旋转矩阵或类似使用欧拉角的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-16
  • 1970-01-01
  • 2022-01-01
相关资源
最近更新 更多