【问题标题】:cv::warpPerspective only shows part of warped imagecv::warpPerspective 只显示扭曲图像的一部分
【发布时间】:2014-04-08 20:08:18
【问题描述】:

我正在使用 getHomography 和 warpPerspective 将图像从正面视角更改为投标视角。

它的工作原理是图像扭曲到所需的视角,但裁剪已关闭。它将扭曲的图像大部分移到图像框之外。我认为原因是因为操作导致负坐标。

我已经手动计算了用于计算平移矩阵的点,而不是使用任何 opencv:s 函数来计算,因为即棋盘函数未能检测到正确的点。

我想这可以通过对转换矩阵进行额外的更改来解决。但是这是怎么做到的呢?另外,有没有办法确保转换后的图像沿x轴居中,然后让y轴调整到所需的位置?

现在可以完成这项工作的代码 sn-p:

cv::Mat image; // image is loaded with the original image

cv::Mat warpPers; // The container for the resulting image
cv::Mat H;

std::vector<cv::Point2f> src;
std::vector<cv::Point2f> dst;

// In reality several more points.
src.push_back(cv::Point2f(264,301));
src.push_back(cv::Point2f(434,301));
src.push_back(cv::Point2f(243,356));
src.push_back(cv::Point2f(476,356));

dst.push_back(cv::Point2f(243,123));
dst.push_back(cv::Point2f(476,123));
dst.push_back(cv::Point2f(243,356));
dst.push_back(cv::Point2f(476,356));

H = cv::findHomography(src, dst, CV_RANSAC);

cv::warpPerspective(image, 
newPers,
H,
cv::Size(3000,3000),
cv::INTER_NEAREST | CV_WARP_FILL_OUTLIERS
);

cv::namedWindow("Warped persp", cv::WINDOW_AUTOSIZE );
cv::imshow( "Warped persp", newPers);

【问题讨论】:

  • 您可以手动转换图像的边界点cv::Point2f(0,0)``cv::Point2f(image.cols, 0)``cv::Point2f(image.cols, image.rows)cv::Point2f(0, image.rows)(与您的单应性进行多重播放)并检查它们是否适合您的 3000,3000 大小的 dst 图像。计算它们的最小/最大位置并相应地修改单应性的平移部分和/或比例(或 dst 图像大小)。不过,我没有检查您的其余代码是否正常;)
  • @Einar 如果你想让findHomography 函数完成这项工作,你必须仔细定义你的目标点,以便扭曲的图像与你的需要相对应。但是,在扭曲图像之前使用自定义翻译(如 Micka 所说)组成单应性并没有什么错。

标签: c++ opencv image-processing perspectivecamera


【解决方案1】:

如果我理解正确,基本上问题需要计算正确偏移量以平移扭曲图像的方法。我将解释如何获得正确的翻译偏移量。想法是两个图像中的匹配特征在最终拼接的图像中应该具有相同的坐标。

假设我们按如下方式引用图像:

  • 'source image' (si):需要变形的图片
  • 'destination image' (di): 视角'source image' 将被扭曲的图像
  • '扭曲的源图像'(wsi): 源图像 将其扭曲到目标图像透视图后

为了计算平移偏移量,您需要执行以下操作:

  1. 在您对好的匹配进行采样并从单应性中找到掩码后,将最佳匹配的关键点(一个具有最小距离并且是内点的关键点(应该从单应性计算获得的掩码中获得 1 的值))存储在 @987654324 @ 和 di。让我们分别在si and diisbm_siandbm_di`中说最佳匹配的关键点..

    bm_si = [x1, y1,1]

    bm_di = [x2, y2, 1]

  2. 只需将 bm_si 与单应矩阵 (H) 相乘,即可找到 wsi 中的位置。 bm_wsi = np.dot(H,bm_si)

    bm_wsi = [x/bm_wsi[2] for x in bm_wsi]

  3. 根据您将在 si 变形 (=wsi) 的输出上放置 di 的位置,调整 bm_di

    假设您要从左图像变形到右图像(例如左图像是si,右图像是di),那么您将把di 放在右侧wsi 上,因此bm_di[0] += si.shape[0]

  4. 经过以上步骤

    x_offset = bm_di[0] - bm_si[0]

    y_offset = bm_di[1] - bm_si[1]

  5. 使用计算的偏移量找到新的单应矩阵并扭曲si

    T = np.array([[1, 0, x_offset], [0, 1, y_offset], [0, 0, 1]])

    translated_H = np.dot(T.H)

    wsi_frame_size = tuple(2*x for x in si.shape)

    stitched = cv2.warpPerspective(si, translated_H, wsi_frame_size)

    stitched[0:si.shape[0],si.shape[1]:] = di

【讨论】:

  • 感谢您的回答。我的问题是几年前提出的,我今天没有合理的方法来验证你的建议。我的实际解决方案是手动将其调整为已知大小。毕竟这只是为了一个 PoC。
【解决方案2】:

我觉得这个问题OpenCV warpperspective和现在的问题cv::warpPerspective only shows part of warped image类似

所以我也在这里给你我的答案https://stackoverflow.com/a/37275961/15485

试试下面的homography_warp

void homography_warp(const cv::Mat& src, const cv::Mat& H, cv::Mat& dst);

src 是源图片。

H 是你的单应性。

dst 是扭曲的图像。

homography_warp 按照https://stackoverflow.com/users/1060066/matt-freeman 在他的回答https://stackoverflow.com/a/8229116/15485 中的描述调整你的单应性

// Convert a vector of non-homogeneous 2D points to a vector of homogenehous 2D points.
void to_homogeneous(const std::vector< cv::Point2f >& non_homogeneous, std::vector< cv::Point3f >& homogeneous)
{
    homogeneous.resize(non_homogeneous.size());
    for (size_t i = 0; i < non_homogeneous.size(); i++) {
        homogeneous[i].x = non_homogeneous[i].x;
        homogeneous[i].y = non_homogeneous[i].y;
        homogeneous[i].z = 1.0;
    }
}

// Convert a vector of homogeneous 2D points to a vector of non-homogenehous 2D points.
void from_homogeneous(const std::vector< cv::Point3f >& homogeneous, std::vector< cv::Point2f >& non_homogeneous)
{
    non_homogeneous.resize(homogeneous.size());
    for (size_t i = 0; i < non_homogeneous.size(); i++) {
        non_homogeneous[i].x = homogeneous[i].x / homogeneous[i].z;
        non_homogeneous[i].y = homogeneous[i].y / homogeneous[i].z;
    }
}

// Transform a vector of 2D non-homogeneous points via an homography.
std::vector<cv::Point2f> transform_via_homography(const std::vector<cv::Point2f>& points, const cv::Matx33f& homography)
{
    std::vector<cv::Point3f> ph;
    to_homogeneous(points, ph);
    for (size_t i = 0; i < ph.size(); i++) {
        ph[i] = homography*ph[i];
    }
    std::vector<cv::Point2f> r;
    from_homogeneous(ph, r);
    return r;
}

// Find the bounding box of a vector of 2D non-homogeneous points.
cv::Rect_<float> bounding_box(const std::vector<cv::Point2f>& p)
{
    cv::Rect_<float> r;
    float x_min = std::min_element(p.begin(), p.end(), [](const cv::Point2f& lhs, const cv::Point2f& rhs) {return lhs.x < rhs.x; })->x;
    float x_max = std::max_element(p.begin(), p.end(), [](const cv::Point2f& lhs, const cv::Point2f& rhs) {return lhs.x < rhs.x; })->x;
    float y_min = std::min_element(p.begin(), p.end(), [](const cv::Point2f& lhs, const cv::Point2f& rhs) {return lhs.y < rhs.y; })->y;
    float y_max = std::max_element(p.begin(), p.end(), [](const cv::Point2f& lhs, const cv::Point2f& rhs) {return lhs.y < rhs.y; })->y;
    return cv::Rect_<float>(x_min, y_min, x_max - x_min, y_max - y_min);
}

// Warp the image src into the image dst through the homography H.
// The resulting dst image contains the entire warped image, this
// behaviour is the same of Octave's imperspectivewarp (in the 'image'
// package) behaviour when the argument bbox is equal to 'loose'.
// See http://octave.sourceforge.net/image/function/imperspectivewarp.html
void homography_warp(const cv::Mat& src, const cv::Mat& H, cv::Mat& dst)
{
    std::vector< cv::Point2f > corners;
    corners.push_back(cv::Point2f(0, 0));
    corners.push_back(cv::Point2f(src.cols, 0));
    corners.push_back(cv::Point2f(0, src.rows));
    corners.push_back(cv::Point2f(src.cols, src.rows));

    std::vector< cv::Point2f > projected = transform_via_homography(corners, H);
    cv::Rect_<float> bb = bounding_box(projected);

    cv::Mat_<double> translation = (cv::Mat_<double>(3, 3) << 1, 0, -bb.tl().x, 0, 1, -bb.tl().y, 0, 0, 1);

    cv::warpPerspective(src, dst, translation*H, bb.size());
}

【讨论】:

【解决方案3】:

Opencv 提供了非常方便的方法来进行透视变换。您唯一需要做的就是通过 findHomography 处理单应性返回。 实际上,您提供的图像的某些点可能位于 x 或 y 轴的负值部分。 所以你必须在扭曲图像之前做一些检查。

第 1 步:使用 findHomography 找到单应性 H 你会得到一个经典的单应性结构

H = [ h00, h01, h02;
      h10, h11, h12;
      h20, h21,   1];

第二步:搜索变形后图片的边角位置

所以让我定义角的顺序:

(0,0) ________ (0, w)
     |        |
     |________|
(h,0)          (h,w)

为此,只需创建一个这样的矩阵:

P = [0, w, w, 0;
     0, 0, h, h;
     1, 1, 1, 1]

用H制作产品并得到扭曲坐标:

P' = H * P

第 3 步:用这 4 个新点检查 x 和 y 中的最小值,并获得扭曲图像的大小 完成产品后,您将收到类似的东西:

P' = [s1*x1, s2*x2, s3*x3, s4*x4;
      s1*y1, s2*y2, s3*y3, s4*y4;
      s1   , s2   , s3   , s4]

所以要获得,新的有效坐标只需将第 1 行和第 2 行除以第 3 行

然后检查第一行的列的最小值,以及第二行的行的最小值(使用 cvReduce)

要找到包含图像的边界框(即 warpPerspective 函数的 dst 矩阵的维度),只需使用 cvReduce 找到每行的最大值

让 minx 为第一行的最小值(即列),maxx (第一行的最大值) 第二行的最小和最大。

所以扭曲图像的大小应该是 cvSize(maxx-minx, maxy-miny)

第 4 步:对单应性添加校正 检查 minx 和/或 miny 是否为负数,如果 minx

所以 H 应该是:

H = [ h00, h01, h02-minx; //if minx <0
      h10, h11, h12-miny; //if miny <0
      h20, h21,   1];

第 5 步:扭曲图像

【讨论】:

  • @Einar :您是否尝试过此解决方案。这对你有用吗?
  • 只是一个评论。如果您使用 H.inv() 进行转换,则应该更改倒置单应性而不是主单应性矩阵中的翻译。我犯了这个错误。我希望没有人花更多的时间在这种愚蠢的努力上:)
  • 这在第 4 步之前是正确的。将偏移量添加到单应变换矩阵不起作用。相反,创建一个 3x3 单位矩阵(我称之为T),将转换值放入t02t12,然后创建一个新的单应矩阵H' = T * H。在对warpPerspective 的调用中使用H'
  • 你能在计算 P' 后提供更多信息吗?请在代码示例中输入文字
猜你喜欢
  • 2019-07-27
  • 1970-01-01
  • 2012-02-20
  • 2012-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-24
相关资源
最近更新 更多