はじめに
アルファチャンネル付きPNGを重ね合わせる関数を作成しました。
OpenCV 2.3.1から透過pngが読み込めるようになりましたが、画像を透過して重ねる機能は用意されていません。
ネット上の記事では1画素ずつ演算する方法や、ROIを使って紹介されることが多いようですが、領域サイズの指定にシビアなことや、元画像の範囲外への描画ができないなど取り回しが悪くなります。
そこで本稿ではcv::warpPerspective()を用いた重ね合わせを紹介します。
サンプルコード:
- 単純な重ね合わせ(transparent.cpp)
2.GUIで位置を調整(transparent2.cpp)
1.透過画像の読み込み
第2引数にIMREAD_UNCHANGEDを指定するとアルファチャンネルが読み込まれます。
cv::Mat transimg = cv::imread("transparent.png",cv::IMREAD_UNCHANGED);
RGB画像の場合transimg.channels();
を表示すると'4'になっているはずです。
背景画像は普通に読み込んでください。
cv::Mat img3 = cv::imread("bg.png");
2.重ね合わせの概要
透過画像の重ね合わせは下図のように行います。
前景画像(1)と背景画像(3)に加えて、それぞれのマスク(2)(4)が必要です。
3.前景画像の作成
前景画像(1)とマスク画像(2)を作成します。
画像(1)は読み込んだサイズのままで良いような気もしますが、出力画像と同じ幅・高さの黒画像に貼り付けることで後の演算が楽になります。
ついでに、画像の変形も行えるようにします。
3-1. 前景画像の変形行列
画像の変形にはcv::warpPerspective()を使います。
元画像の矩形の4点と、変形後の座標4点を指定すれば、cv::getPerspectiveTransform()で行列が得られます。
//座標指定(初期位置は背景画像の中心にする)
int ltx = (img3.cols - img3.cols)/2;
int lty = (img3.rows - img3.rows)/2;
int ww = img3.cols;
int hh = img3.rows;
//元画像の4点
vector<cv::Point2f>srcPt;
srcPt.push_back( cv::Point2f(0, 0) );
srcPt.push_back( cv::Point2f(transimg.cols-1, 0) );
srcPt.push_back( cv::Point2f(transimg.cols-1, transimg.rows-1) );
srcPt.push_back( cv::Point2f(0, transimg.rows-1) );
//変形後の4点
vector<cv::Point2f>tgtPt;
tgtPt.push_back(cv::Point2f(ltx , lty));
tgtPt.push_back(cv::Point2f(ltx+ww, lty));
tgtPt.push_back(cv::Point2f(ltx+ww, lty+hh));
tgtPt.push_back(cv::Point2f(ltx , lty+hh));
cv::Mat mat = cv::getPerspectiveTransform(srcPt, tgtPt);
3-2. 前景画像の変形
前景画像を出力画像と同じ幅・高さの画像に出力します。
この時にアルファチャンネルも一緒に変形してしまうのが、この手法の肝心な点です。
cv::Mat alpha0(img3.rows, img3.cols, transImg.type() );
alpha0 = cv::Scalar::all(0);
cv::warpPerspective(transimg, alpha0, mat,alpha0.size(), cv::INTER_CUBIC, cv::BORDER_TRANSPARENT);
3-3 マスクの作成
3-2で作成した画像から、前景画像(1)、前景画像マスク(2)を作ります。
ここでは地道に cv::split()とcv::merge()を使います
//チャンネルに分解
cv::split(alpha0, planes_rgba);
//RGBA画像をRGBに変換
planes_rgb.push_back(planes_rgba[0]);
planes_rgb.push_back(planes_rgba[1]);
planes_rgb.push_back(planes_rgba[2]);
merge(planes_rgb, img1);
//RGBA画像からアルファチャンネル抽出
planes_aaa.push_back(planes_rgba[3]);
planes_aaa.push_back(planes_rgba[3]);
planes_aaa.push_back(planes_rgba[3]);
merge(planes_aaa, img2);
4.背景画像の作成
背景のRGB画像(3)は読み込んだままを利用します。
背景マスク(4)は前景マスク(2)の白黒反転です。
//背景用アルファチャンネル
planes_1ma.push_back(255-planes_rgba[3]);
planes_1ma.push_back(255-planes_rgba[3]);
planes_1ma.push_back(255-planes_rgba[3]);
merge(planes_1ma, img4);
5.透過度を考慮した重ね合わせ
画像(1)〜(4)は同じ画像幅・高さ、ビット深度、チャンネル数にしたので、次のように簡単な式で透過度を考慮した重ね合わせが行えます。
img5 = img1.mul(img2) + img3.mul(img4);
以上の処理を実装したサンプルコードを置いておきます。(OpenCV4)
6.おまけ
サンプルコードでは真ん中に矩形画像を配置しているので、cv::warpPerspective()を使った恩恵があまり見えません。
そこで、ウィンドウ上で自由に変形できるサンプルを作りました。
前景画像の周囲に表示されるコントロールポイントをドラッグすると、前景画像が自由変形します。