cv::warpPerspective の使い方【OpenCV/C++】〜射影変換で画像を補正する〜

OpenCV for C++
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイドC#(OpenCvSharp)セットアップ

cv::warpPerspective の使い方【OpenCV/C++】〜射影変換で画像を補正する〜

cv::warpPerspective は、4点の対応関係から求めた 3×3 のホモグラフィ行列を使って画像に射影変換を適用する関数です。斜めから撮影した書類の「正面補正」や、QRコード・マーカーの切り出しといった実務用途で頻繁に登場します。

最小構成はこれだけです。

cv::Mat H = cv::getPerspectiveTransform(src_pts, dst_pts);
cv::warpPerspective(src, dst, H, cv::Size(width, height));

動作環境

  • OpenCV 4.x(4.5 以降推奨)
  • コンパイル例: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)

環境構築がまだの方は環境構築ガイドを先にどうぞ。


基本の使い方

斜め撮影された四角形領域を正面から見た矩形に変換するサンプルです。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    // 入力画像の読み込み
    cv::Mat src = cv::imread("input.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // 射影変換の入力側 4 点(時計回り: 左上, 右上, 右下, 左下)
    std::vector<cv::Point2f> src_pts = {
        {120.0f,  80.0f},
        {520.0f,  60.0f},
        {560.0f, 420.0f},
        { 80.0f, 440.0f}
    };

    // 出力先の矩形 4 点(幅 400 × 高さ 360 の正面矩形)
    const float W = 400.0f, H = 360.0f;
    std::vector<cv::Point2f> dst_pts = {
        {  0.0f,   0.0f},
        {  W,      0.0f},
        {  W,      H   },
        {  0.0f,   H   }
    };

    // 3×3 ホモグラフィ行列を取得
    cv::Mat M = cv::getPerspectiveTransform(src_pts, dst_pts);

    // 射影変換を適用
    cv::Mat dst;
    cv::warpPerspective(src, dst, M, cv::Size(static_cast<int>(W), static_cast<int>(H)));

    cv::imwrite("output.jpg", dst);
    std::cout << "出力サイズ: " << dst.cols << " x " << dst.rows << std::endl;
    return 0;
}

実行結果(標準出力)

出力サイズ: 400 x 360

斜めに写っていた四角形領域が、400×360 ピクセルの正面矩形として output.jpg に書き出されます。

行ごとの解説

ポイント
getPerspectiveTransform 入出力の 4 点対応から 3×3 行列を算出。点は 時計回り で揃えること
warpPerspective 第3引数 変換行列(CV_64F・3×3)
第4引数 dsize 出力画像のサイズ。入力画像のサイズではないことに注意

引数と戻り値

void cv::warpPerspective(
    InputArray  src,
    OutputArray dst,
    InputArray  M,
    Size        dsize,
    int         flags     = INTER_LINEAR,
    int         borderMode = BORDER_CONSTANT,
    const Scalar& borderValue = Scalar()
);
引数 説明
src InputArray 入力画像(任意チャンネル・任意深度)
dst OutputArray 出力画像(dsize のサイズで作られる)
M InputArray 3×3 変換行列(CV_64F 推奨)
dsize Size 出力画像のサイズ
flags int 補間方法 + WARP_INVERSE_MAP 指定可
borderMode int 境界補完モード(BORDER_CONSTANT など)
borderValue Scalar BORDER_CONSTANT 時の埋め色(デフォルト: 黒)

主な flags の値

定数 内容
INTER_NEAREST 最近傍補間(高速・低品質)
INTER_LINEAR バイリニア補間(デフォルト・汎用)
INTER_CUBIC バイキュービック補間(高品質・低速)
WARP_INVERSE_MAP M を逆変換として扱う

実践例

例1: findHomography を使った複数点対応(外れ値に強い)

4点ちょうどではなく、特徴点マッチングで得た多数の対応点からホモグラフィを推定する場合は cv::findHomography を使います。RANSAC で外れ値を除去できます。

#include <iostream>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat src = cv::imread("book.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // 特徴点マッチング等で得た多数の対応点(例: 手動入力で代替)
    std::vector<cv::Point2f> src_pts = {
        {100.0f,  50.0f}, {400.0f,  45.0f},
        {420.0f, 310.0f}, { 85.0f, 320.0f},
        {250.0f, 180.0f}  // 中間点を追加しても RANSAC が対応
    };
    std::vector<cv::Point2f> dst_pts = {
        {  0.0f,   0.0f}, {350.0f,   0.0f},
        {350.0f, 280.0f}, {  0.0f, 280.0f},
        {175.0f, 140.0f}
    };

    // RANSAC で外れ値を排除しながらホモグラフィを推定
    cv::Mat H = cv::findHomography(src_pts, dst_pts, cv::RANSAC, 3.0);
    if (H.empty()) {
        std::cerr << "ホモグラフィ推定に失敗しました" << std::endl;
        return 1;
    }

    cv::Mat dst;
    cv::warpPerspective(src, dst, H, cv::Size(350, 280));
    cv::imwrite("output_homography.jpg", dst);
    std::cout << "完了: output_homography.jpg" << std::endl;
    return 0;
}

例2: 輪郭検出と組み合わせた書類自動補正

cv::findContours + cv::approxPolyDP で検出した四角形輪郭を直接 warpPerspective に渡すパターンです。実務でよく使うパイプラインです。

#include <iostream>
#include <algorithm>
#include <opencv2/opencv.hpp>

// 四角形の4点を「左上・右上・右下・左下」の順に並べる
std::vector<cv::Point2f> sortCorners(std::vector<cv::Point2f>& pts)
{
    // x+y が最小 → 左上、最大 → 右下
    // x-y が最小 → 右上、最大 → 左下
    std::vector<cv::Point2f> ordered(4);
    std::vector<float> sum, diff;
    for (auto& p : pts) { sum.push_back(p.x + p.y); diff.push_back(p.x - p.y); }

    ordered[0] = pts[std::min_element(sum.begin(),  sum.end())  - sum.begin()];
    ordered[2] = pts[std::max_element(sum.begin(),  sum.end())  - sum.begin()];
    ordered[1] = pts[std::max_element(diff.begin(), diff.end()) - diff.begin()];
    ordered[3] = pts[std::min_element(diff.begin(), diff.end()) - diff.begin()];
    return ordered;
}

int main()
{
    cv::Mat src = cv::imread("document.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // グレースケール → エッジ検出 → 輪郭抽出
    cv::Mat gray, edges;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0);
    cv::Canny(gray, edges, 50, 150);

    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    // 面積最大の四角形輪郭を探す
    std::vector<cv::Point2f> docCorners;
    double maxArea = 0.0;
    for (auto& c : contours) {
        std::vector<cv::Point> approx;
        cv::approxPolyDP(c, approx, cv::arcLength(c, true) * 0.02, true);
        if (approx.size() == 4) {
            double area = std::abs(cv::contourArea(approx));
            if (area > maxArea) {
                maxArea = area;
                docCorners.clear();
                for (auto& p : approx)
                    docCorners.push_back(cv::Point2f(static_cast<float>(p.x),
                                                     static_cast<float>(p.y)));
            }
        }
    }

    if (docCorners.size() != 4) {
        std::cerr << "書類の四角形を検出できませんでした" << std::endl;
        return 1;
    }

    auto ordered = sortCorners(docCorners);

    const float W = 595.0f, H = 842.0f; // A4 比率(px)
    std::vector<cv::Point2f> dst_pts = {
        {0.0f, 0.0f}, {W, 0.0f}, {W, H}, {0.0f, H}
    };

    cv::Mat M = cv::getPerspectiveTransform(ordered, dst_pts);
    cv::Mat dst;
    cv::warpPerspective(src, dst, M, cv::Size(static_cast<int>(W), static_cast<int>(H)));
    cv::imwrite("doc_corrected.jpg", dst);
    std::cout << "書類補正完了: doc_corrected.jpg" << std::endl;
    return 0;
}

cv::findContourscv::approxPolyDP の詳細はそれぞれの記事を参照してください。


つまずきポイント

⚠️ getPerspectiveTransform の点順序が合っていない

入力側と出力側の点が「同じ順序」で対応していなければなりません。時計回りに揃える・揃えないが混在すると、変換後の画像が裏返しになったり極端に歪んだりします。順序を間違えてもコンパイルエラーは出ないため、目視確認が必須です。

⚠️ dsize は出力サイズであり入力サイズではない

warpPerspective の第4引数に src.size() をそのまま渡すのはよくある間違いです。正面補正後は縦横比が変わることが多く、意図したサイズを明示的に指定する必要があります。

⚠️ 変換行列の型が CV_32F になっている

getPerspectiveTransformCV_64F の行列を返します。一方、手動で cv::Mat に変換行列を入れる場合に float 配列から構築すると CV_32F になり、warpPerspective 内でアサーションエラーが発生することがあります。変換行列は必ず CV_64F で渡してください。

// NG: CV_32F になる
cv::Mat M = (cv::Mat_<float>(3,3) << ...);

// OK: CV_64F
cv::Mat M = (cv::Mat_<double>(3,3) << ...);

関連する関数


まとめ

cv::warpPerspectivecv::getPerspectiveTransform(または cv::findHomography)で得た 3×3 行列を受け取り、射影変換を画像全体に適用します。点の対応順序と出力サイズの指定、変換行列の型(CV_64F)の3点を正しく押さえれば、書類補正・マーカー切り出し・俯瞰変換など幅広い用途にそのまま使えます。

🛠 画像処理のプロが開発するSDK/API

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

タイトルとURLをコピーしました