web-dev-qa-db-ja.com

OpenCVワープパースペクティブ

何らかの理由で、OpenCVのwarpPerspective()関数を使用すると、最終的なワープ画像に元の画像のすべてが含まれるわけではありません。画像の左側が途切れているようです。これが発生する理由は、warpPerspective()のキャンバスの左端にワープ画像が作成されるためだと思います。これを修正する方法はありますか?ありがとう

17
Hien

この問題は、ホモグラフィが画像の一部を画像領域の外側にある負のx、y値にマッピングするため、プロットできないために発生します。ワープした出力全体をいくつかのピクセルだけオフセットして、ワープした画像全体を正の座標(つまり、画像領域内)に「分路」します。

ホモグラフィは、行列乗算を使用して組み合わせることができます(これが非常に強力な理由です)。 AとBがホモグラフィである場合、ABは最初にBを適用し、次にAを適用するホモグラフィを表します。

このため、出力をオフセットするために必要なのは、変換用のホモグラフィマトリックスをオフセットで作成し、それを元のホモグラフィマトリックスで事前に乗算することだけです。

2Dホモグラフィ行列は次のようになります。

[R11,R12,T1]
[R21,R22,T2]
[ P , P , 1]

ここで、Rは回転行列を表し、Tは並進を表し、Pは透視ワープを表します。したがって、純粋な並進ホモグラフィは次のようになります。

[ 1 , 0 , x_offset]
[ 0 , 1 , y_offset]
[ 0 , 0 ,    1    ]

したがって、ホモグラフィに上記と同様の行列を事前に乗算するだけで、出力画像がオフセットされます。

(要素ごとの乗算ではなく、行列の乗算を使用するようにしてください!)

34
Matt Freeman

その秘密は、変換行列(ホモグラフィ)と結果の画像サイズの2つの部分に分かれています。

  • getPerspectiveTransform()を使用して、正しい変換を計算します。元の画像から4つのポイントを取得し、目的地での正しい位置を計算し、それらを同じ順序で2つのベクトルに配置し、それらを使用して遠近変換行列を計算します。

  • 宛先の画像サイズ(warpPerspective()の3番目のパラメーター)が正確に希望どおりであることを確認してください。 Size(myWidth、myHeight)として定義します。

4
Sam

1つの方法を実行しました...機能しています。

  perspectiveTransform(obj_corners,scene_corners,H);
int maxCols(0),maxRows(0);

 for(int i=0;i<scene_corners.size();i++)
{
   if(maxRows < scene_corners.at(i).y)
        maxRows = scene_corners.at(i).y;
   if(maxCols < scene_corners.at(i).x)
        maxCols = scene_corners.at(i).x;
}

それぞれx点とy点の最大値を見つけてそれを置くだけです

warpPerspective( tmp, transformedImage, homography, Size( maxCols, maxRows ) );
4

マットの答えは良い出発点であり、ホモグラフィを掛ける必要があると彼は言っています。

[ 1 , 0 , x_offset]
[ 0 , 1 , y_offset]
[ 0 , 0 ,    1    ]

しかし、彼はx_offsetとy_offsetが何であるかを指定していません。他の回答では、遠近法を変換するだけだと言われていますが、それは正しくありません。あなたは逆遠近法変換をしたいと思っています。

点0,0がたとえば-10、-10に変換されるからといって、画像を10,10だけシフトしても画像がトリミングされないというわけではありません。これは、ポイント10,10が必ずしも0,0にマップされるとは限らないためです。
あなたがしたいのは、どのポイントを0,0にマッピングするかを見つけて、画像をその分シフトすることです。そのためには、ホモグラフィの逆( cv2.invert )を取り、perspectiveTransformを適用します。

enter image description here は意味しません: enter image description here

正しいポイントを見つけるには、逆変換を適用する必要があります。

enter image description here

これにより、正しいx_offsetとy_offsetが取得され、左上のポイントが整列されます。そこから正しいバウンディングボックスを見つけて画像全体を完全にフィットさせるには、スキュー(通常の非逆変換の後に画像がどの程度傾いたか)を把握し、その量をx_offsetとy_offsetに追加する必要があります同じように。

編集:これはすべて理論です。私のテストでは、画像が数ピクセルずれています。理由はわかりません。

2
john ktejik

以下をお試しくださいhomography_warp

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

srcはソース画像です。

Hはホモグラフィです。

dstは歪んだ画像です。

homography_warphttps://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());
}
2

warpPerspective()は正常に機能します。書き直す必要はありません。あなたはおそらくそれを間違って使用しています。

次のヒントを覚えておいてください。

  1. (0,0)ピクセルは中央ではなく、左上隅にあります。したがって、画像を2倍に拡大すると、境界ではなく、下部と右側の部分が失われます(matlabのように)。
  2. 画像を2回ワープする場合は、変換を乗算して関数を1回アクティブにすることをお勧めします。
  3. これはchar/int行列でのみ機能し、float/doubleでは機能しないと思います。
  4. 変換がある場合、最初にズーム/スキュー/回転/パースペクティブが適用され、最後に平行移動が適用されます。したがって、画像の一部が欠落している場合は、マトリックスの変換(最後の列の上の2行)を変更するだけです。
1
DanielHsH

これは私の解決策です

「warpPerspective()」の3番目のパラメータは変換行列なので、

最初に画像を後方に移動し、次に画像を回転し、最後に画像を前方に移動する変換行列を作成できます。

私の場合、高さ160ピクセル、幅160ピクセルの画像があります。 [0,0]の代わりに[80,80]を中心に画像を回転させたい

まず、画像を後方に移動します(つまり、T1)

次に、画像を回転させます(つまりR)

最後に画像を前方に移動します(つまりT2を意味します)

void rotateImage(Mat &src_img,int degree)
{
float radian=(degree/180.0)*M_PI;
Mat R(3,3,CV_32FC1,Scalar(0));
R.at<float>(0,0)=cos(radian);R.at<float>(0,1)=-sin(radian);
R.at<float>(1,0)=sin(radian);R.at<float>(1,1)=cos(radian);
R.at<float>(2,2)=1;
Mat T1(3,3,CV_32FC1,Scalar(0));
T1.at<float>(0,2)=-80;
T1.at<float>(1,2)=-80;
T1.at<float>(0,0)=1;
T1.at<float>(1,1)=1;
T1.at<float>(2,2)=1;
Mat T2(3,3,CV_32FC1,Scalar(0));
T2.at<float>(0,2)=80;
T2.at<float>(1,2)=80;
T2.at<float>(0,0)=1;
T2.at<float>(1,1)=1;
T2.at<float>(2,2)=1;

std::cerr<<T1<<std::endl;
std::cerr<<R<<std::endl;
std::cerr<<T2<<std::endl;
std::cerr<<T2*R*T1<<"\n"<<std::endl;

cv::warpPerspective(src_img, src_img, T2*R*T1, src_img.size(), cv::INTER_LINEAR);
}
0
user3094631