cv::getPerspectiveTransform の使い方【OpenCV/C++】〜射影変換行列を求める〜

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

cv::getPerspectiveTransform の使い方【OpenCV/C++】〜射影変換行列を求める〜

cv::getPerspectiveTransform は、4点の対応関係から 3×3 の射影変換行列(ホモグラフィ行列)を求める関数です。斜めから撮影した書類や看板を正面視に補正する「射影補正(台形補正)」の実装で中心的な役割を担います。求めた行列は cv::warpPerspective に渡すことで実際の変換を適用できます。

cv::Mat H = cv::getPerspectiveTransform(srcPoints, dstPoints);
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` -o main

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


基本の使い方

以下のサンプルは、画像内の四角形領域を正面から見た矩形に補正するシンプルな例です。

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

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

    // 変換元の4点(画像内の四角形の頂点: 左上・右上・右下・左下の順)
    std::array<cv::Point2f, 4> srcPts = {
        cv::Point2f(120.0f,  80.0f),   // 左上
        cv::Point2f(480.0f,  60.0f),   // 右上
        cv::Point2f(510.0f, 380.0f),   // 右下
        cv::Point2f( 90.0f, 400.0f)    // 左下
    };

    // 変換先の4点(出力画像上の矩形)
    const float W = 400.0f, H = 300.0f;
    std::array<cv::Point2f, 4> dstPts = {
        cv::Point2f(  0.0f,   0.0f),
        cv::Point2f(W - 1.f,  0.0f),
        cv::Point2f(W - 1.f, H - 1.f),
        cv::Point2f(  0.0f,  H - 1.f)
    };

    // 3×3 射影変換行列を取得
    cv::Mat perspMat = cv::getPerspectiveTransform(srcPts.data(), dstPts.data());

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

    cv::imwrite("output.jpg", dst);
    std::cout << "射影変換行列:\n" << perspMat << std::endl;
    std::cout << "変換完了: output.jpg に保存しました" << std::endl;

    return 0;
}

実行結果(標準出力の例):

射影変換行列:
[ 1.0234...,  0.0312..., -123.4...;
 -0.0121...,  0.9876...,  -78.5...;
  0.0001...,  0.0002...,    1.0000]
変換完了: output.jpg に保存しました

perspMatCV_64F の 3×3 行列として返されます。実際の数値は入力4点の座標によって変わります。output.jpg を確認すると、台形状だった領域が 400×300 の正面視矩形に補正されています。

各処理の解説

処理 ポイント
srcPts の指定 左上 → 右上 → 右下 → 左下の順(時計回り)で統一すると混乱しにくい
dstPts の指定 出力画像の四隅を -1.f して端ピクセルを含める
getPerspectiveTransform 4点から一意に 3×3 行列を算出する。点が5点以上あるなら cv::findHomography が適切
warpPerspective 求めた行列と出力サイズを渡して実際の変換を実行する

引数と戻り値

cv::Mat cv::getPerspectiveTransform(
    InputArray src,       // 変換元の4点座標 (Point2f×4)
    InputArray dst,       // 変換先の4点座標 (Point2f×4)
    int solveMethod = DECOMP_LU  // 連立方程式の求解法 (OpenCV 4.5以降)
);
引数 説明
src InputArray 変換元の4点。std::vector<cv::Point2f> または cv::Point2f[4] を渡せる
dst InputArray 変換先の4点。src と同じ形式
solveMethod int 求解法。DECOMP_LU(デフォルト)または DECOMP_SVD。OpenCV 4.5 以降で利用可能
戻り値 cv::Mat CV_64F の 3×3 射影変換行列

⚠️ solveMethod 引数は OpenCV 4.5 以降で追加されました。4.4 以前のビルドではコンパイルエラーになるため注意してください。


実践例

実践例 1: 輪郭検出と組み合わせた自動台形補正

書類などを自動で検出・補正する典型的なパイプラインです。cv::findContourscv::approxPolyDP と組み合わせて使います。

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

// 輪郭の4頂点を左上・右上・右下・左下の順に並べ替えるヘルパー
std::vector<cv::Point2f> sortCorners(std::vector<cv::Point2f>& pts)
{
    // x+y が最小 → 左上、最大 → 右下
    // x-y が最小 → 左下、最大 → 右上
    std::vector<cv::Point2f> sorted(4);
    std::sort(pts.begin(), pts.end(), [](const cv::Point2f& a, const cv::Point2f& b){
        return (a.x + a.y) < (b.x + b.y);
    });
    sorted[0] = pts[0]; // 左上
    sorted[2] = pts[3]; // 右下

    // 残り2点を x で判定
    if (pts[1].x < pts[2].x) {
        sorted[3] = pts[1]; // 左下
        sorted[1] = pts[2]; // 右上
    } else {
        sorted[3] = pts[2]; // 左下
        sorted[1] = pts[1]; // 右上
    }
    return sorted;
}

int main()
{
    cv::Mat src = cv::imread("document.jpg");
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return 1;
    }

    // グレースケール → 二値化 → 輪郭検出
    cv::Mat gray, binary;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0);
    cv::Canny(gray, binary, 50, 150);

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

    // 面積最大の輪郭を選択して4角形近似
    std::sort(contours.begin(), contours.end(), [](const auto& a, const auto& b){
        return cv::contourArea(a) > cv::contourArea(b);
    });

    std::vector<cv::Point2f> srcPts;
    for (const auto& c : contours) {
        std::vector<cv::Point> approx;
        cv::approxPolyDP(c, approx, cv::arcLength(c, true) * 0.02, true);
        if (approx.size() == 4) {
            for (const auto& p : approx) srcPts.emplace_back(p);
            break;
        }
    }

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

    // 頂点を時計回りに整列
    auto sorted = sortCorners(srcPts);

    // 出力サイズ(A4 比率: 210:297 を縮小)
    const float W = 420.0f, H = 594.0f;
    std::vector<cv::Point2f> dstPts = {
        {0.f, 0.f}, {W-1, 0.f}, {W-1, H-1}, {0.f, H-1}
    };

    // 射影変換行列を求めて適用
    cv::Mat H_mat = cv::getPerspectiveTransform(sorted, dstPts);
    cv::Mat dst;
    cv::warpPerspective(src, dst, H_mat, cv::Size(static_cast<int>(W), static_cast<int>(H)));

    cv::imwrite("corrected.jpg", dst);
    std::cout << "補正完了: corrected.jpg" << std::endl;
    return 0;
}

実践例 2: アフィン変換との使い分けを確認する

アフィン変換(cv::warpAffine)は3点で決まる変換であり、射影変換とは異なります。平行四辺形状のゆがみはアフィン変換、台形状のゆがみは射影変換が適切です。

// アフィン変換(3点対応・平行線が保たれる)
cv::Mat A = cv::getAffineTransform(src3pts, dst3pts);  // 2×3行列
cv::warpAffine(src, dst, A, dst.size());

// 射影変換(4点対応・平行線は保たれない)
cv::Mat H = cv::getPerspectiveTransform(src4pts, dst4pts);  // 3×3行列
cv::warpPerspective(src, dst, H, dst.size());

つまずきポイント

⚠️ 点の順序が揃っていないと破綻する

srcPtsdstPts の各インデックスは「同じ物理点の対応」でなければなりません。順序が時計回り・反時計回りで混在すると、計算は成功しても変換結果がねじれた画像になります。デバッグ時は cv::circle で各点を番号付きで描画して確認するのが最速です。

⚠️ 4点が共線・重複すると行列が求まらない

4点のうち3点以上が一直線上にある場合、方程式が退化して正しい行列を返しません(OpenCV はエラーを投げず、数値的に破綻した行列を返すことがあります)。実用コードでは cv::contourArea 等で四角形の面積が十分あることを事前チェックしてください。

⚠️ Point2f と Point の型違いによる assertion

cv::getPerspectiveTransform の引数は Point2f(浮動小数点)を期待しています。std::vector<cv::Point>(整数型)を直接渡すとランタイムに assertion が発生します。cv::approxPolyDP の結果は cv::Point 型なので、cv::Point2f へのキャストを忘れずに行ってください。

// NG: cv::Point のまま渡す
std::vector<cv::Point> approx; // approxPolyDP の出力
cv::getPerspectiveTransform(approx, dstPts); // assertion 失敗

// OK: Point2f に変換してから渡す
std::vector<cv::Point2f> srcPts;
for (const auto& p : approx) srcPts.emplace_back(p); // 暗黙変換でキャスト

関連する関数


まとめ

cv::getPerspectiveTransform は4点の対応から射影変換行列を一発で求められる関数です。cv::warpPerspective と組み合わせることで台形補正・書類スキャン補正を数十行で実装できます。点の順序・型(Point2f)・4点の非共線性の3点に注意すれば、実務ですぐに使えます。

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

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

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

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