cv::warpAffine の使い方【OpenCV/C++】〜アフィン変換で画像を回転・平行移動する〜

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

cv::warpAffine の使い方【OpenCV/C++】〜アフィン変換で画像を回転・平行移動する〜

cv::warpAffine は、2×3 のアフィン変換行列を使って画像の回転・平行移動・拡縮・せん断を一括で適用する関数です。
画像の向き補正や Data Augmentation など、現場で最も使われる幾何変換関数のひとつです。
最小構成は次のとおりです。

cv::Mat M = cv::getRotationMatrix2D(center, angle, scale);
cv::warpAffine(src, dst, M, dst.size());

動作環境

  • OpenCV 4.x
  • コンパイル: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4) -o main
  • 実行: ./main

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


基本の使い方

画像を中心基準で 45° 回転させるサンプルです。

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

int main()
{
    // 画像読み込み
    cv::Mat src = cv::imread("input.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした。パスを確認してください。" << std::endl;
        return 1;
    }

    // 回転中心を画像の中央に設定
    cv::Point2f center(src.cols / 2.0f, src.rows / 2.0f);

    // 2×3 の回転行列を生成(角度45°、スケール等倍)
    cv::Mat M = cv::getRotationMatrix2D(center, 45.0, 1.0);

    // アフィン変換を適用。出力サイズは入力と同じ
    cv::Mat dst;
    cv::warpAffine(src, dst, M, src.size());

    cv::imwrite("output_rotate45.jpg", dst);
    std::cout << "変換後サイズ: " << dst.cols << "x" << dst.rows << std::endl;

    return 0;
}

実行結果

変換後サイズ: 640x480

入力画像が 640×480 であれば、同サイズの output_rotate45.jpg が生成されます。
45° 回転した画像が保存されており、画面外にはみ出した領域は黒(デフォルトの境界値)で埋められます。

コードのポイント

説明
cv::getRotationMatrix2D(center, 45.0, 1.0) 回転中心・角度・スケールから 2×3 行列を生成。角度は反時計回りが正
cv::warpAffine(src, dst, M, src.size()) 変換行列 M を適用。第4引数で出力サイズを指定

引数と戻り値

cv::warpAffine

void cv::warpAffine(
    InputArray  src,       // 入力画像
    OutputArray dst,       // 出力画像
    InputArray  M,         // 2×3 アフィン変換行列(CV_64F)
    Size        dsize,     // 出力画像のサイズ
    int         flags      = INTER_LINEAR,  // 補間方法
    int         borderMode = BORDER_CONSTANT, // 境界処理方法
    Scalar      borderValue = Scalar()      // borderMode が BORDER_CONSTANT のときの埋め値
);
引数 説明
src InputArray 入力画像(チャンネル数制限なし)
dst OutputArray 出力画像(dsize と同型で自動生成)
M InputArray 2×3 アフィン変換行列(通常 CV_64F
dsize Size 出力サイズ。入力と異なるサイズも指定可
flags int 補間方法。INTER_LINEAR(双線形)/ INTER_NEAREST(最近傍)/ INTER_CUBIC など
borderMode int 境界外の画素の扱い。BORDER_CONSTANT / BORDER_REFLECT など
borderValue Scalar BORDER_CONSTANT 時の埋め色。デフォルトは黒 Scalar(0,0,0)

戻り値はなし(dst に結果が書き込まれます)。


実践例

実践例 1: 回転後のはみ出しをなくす(出力サイズを拡張する)

単純に元サイズへ収めると画像の四隅が欠けます。回転後の外接矩形に合わせてキャンバスを拡張すれば欠けません。

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

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

    const double angle = 30.0;
    const double rad   = angle * CV_PI / 180.0;

    // 回転後の外接矩形サイズを計算
    int newW = static_cast<int>(std::abs(src.cols * std::cos(rad))
                              + std::abs(src.rows * std::sin(rad)));
    int newH = static_cast<int>(std::abs(src.cols * std::sin(rad))
                              + std::abs(src.rows * std::cos(rad)));

    // 新しいキャンバスの中心を回転中心にして平行移動成分を調整
    cv::Point2f center(src.cols / 2.0f, src.rows / 2.0f);
    cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);
    M.at<double>(0, 2) += (newW / 2.0 - center.x);
    M.at<double>(1, 2) += (newH / 2.0 - center.y);

    cv::Mat dst;
    cv::warpAffine(src, dst, M, cv::Size(newW, newH),
                   cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));

    cv::imwrite("output_no_crop.jpg", dst);
    std::cout << "拡張後サイズ: " << dst.cols << "x" << dst.rows << std::endl;

    return 0;
}

実行結果

拡張後サイズ: 748x583

元画像(640×480)が 30° 回転した外接矩形サイズに拡張されます。四隅の欠けがなくなります。


実践例 2: 平行移動のみ(シフト処理)

平行移動だけが目的なら、変換行列を直接 Mat_<double> で作ると明快です。

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

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

    // X方向に +50px、Y方向に +30px シフトする変換行列
    // [ 1  0  tx ]
    // [ 0  1  ty ]
    cv::Mat M = (cv::Mat_<double>(2, 3) <<
        1.0, 0.0, 50.0,
        0.0, 1.0, 30.0);

    cv::Mat dst;
    cv::warpAffine(src, dst, M, src.size(),
                   cv::INTER_LINEAR, cv::BORDER_REFLECT);

    cv::imwrite("output_shift.jpg", dst);
    std::cout << "シフト完了" << std::endl;

    return 0;
}

BORDER_REFLECT を指定することで、シフトで生じた境界領域を反転した画素で埋めます。Data Augmentation でよく使います。


つまずきポイント

⚠️ 回転方向が「反時計回りが正」

cv::getRotationMatrix2D の角度は 数学的座標系(Y 軸上向き) に基づき、正値が反時計回りです。
一方、画像座標は Y 軸が下向きのため、画面上では 正値が時計回りに見えます。
「なぜ逆に回るのか」と混乱しやすい点です。時計回りにしたければ角度に負値を渡してください。

⚠️ 変換行列の型は必ず CV_64F(double)

cv::warpAffine に渡す行列は CV_64F でなければなりません。
cv::Mat_<float>CV_32F で作ると実行時に assertion エラーが発生します。

// NG: float で作るとエラー
cv::Mat M = (cv::Mat_<float>(2, 3) << 1,0,0, 0,1,0);

// OK: double で作る
cv::Mat M = (cv::Mat_<double>(2, 3) << 1.0,0.0,0.0, 0.0,1.0,0.0);

⚠️ 出力サイズ dsize の指定ミス

dsizecv::Size(width, height) の順です。rows/cols と混同して縦横が逆になるミスが多いです。
src.size() をそのまま渡せばサイズ取り違えは防げますが、拡張キャンバスを手計算する場合は cv::Size(newW, newH) の順序を必ず確認してください。


関連する関数

  • cv::getRotationMatrix2D — 回転行列を手軽に生成する関数。本記事とほぼ必ずセットで使います(上記サンプル参照)。
  • cv::getAffineTransform — 3点対応からアフィン行列を推定する関数。手動で行列を作らずに済みます。
  • cv::warpPerspective — アフィン変換の上位互換にあたる透視変換。4点対応が必要な場合に使います。

まとめ

  • cv::warpAffine はアフィン変換行列(2×3)を使って回転・平行移動・拡縮をまとめて適用できます。
  • 回転は cv::getRotationMatrix2D で行列を生成し、出力サイズを拡張することで四隅の欠けを防げます。
  • 変換行列は CV_64F(double)で渡す・回転の正方向は画面上で時計回り、の2点を押さえておけば実装上のハマりはほぼなくなります。

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

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

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

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