OpenCVでアルファチャンネル付きpngを表示する

  • 11
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

アルファチャンネル付きPNGを重ね合わせる関数を作成しました。
001.png

OpenCV 2.3.1から透過pngが読み込めるようになりましたが、画像を透過して重ねる機能は用意されていません。
ネット上の記事では1画素ずつ演算する方法や、ROIを使って紹介されることが多いようですが、領域サイズの指定にシビアなことや、元画像の範囲外への描画ができないなど取り回しが悪くなります。

そこで本稿ではcv::warpPerspective()を用いた重ね合わせを紹介します。
サンプルコード1単純な重ね合わせ
サンプルコード2GUIで位置を調整

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)が必要です。
スクリーンショット 2015-12-05 14.28.22.png

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);

スクリーンショット 2015-12-05 15.00.20.png

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);

以上の処理を実装したサンプルコード1を置いておきます。

6.おまけ

サンプルコードでは真ん中に矩形画像を配置しているので、cv::warpPerspective()を使った恩恵があまり見えません。
そこで、ウィンドウ上で自由に変形できるサンプルを作りました。
前景画像の周囲に表示されるコントロールポイントをドラッグすると、前景画像が自由変形します。
dst.gif