LoginSignup
33

More than 3 years have passed since last update.

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

Last updated at Posted at 2015-12-05

はじめに

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

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

そこで本稿ではcv::warpPerspective()を用いた重ね合わせを紹介します。
サンプルコード
1. 単純な重ね合わせ(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)が必要です。
スクリーンショット 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);

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

6.おまけ

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33