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

# はじめに

アルファチャンネル付きPNGを重ね合わせる関数を作成しました。
![001.png](https://qiita-image-store.s3.amazonaws.com/0/98965/d976b98b-7f95-0ac7-e1bc-b1c059b9a3e3.png)

OpenCV 2.3.1から透過pngが読み込めるようになりましたが、画像を透過して重ねる機能は用意されていません。
ネット上の記事では[１画素ずつ演算する](http://jepsonsblog.blogspot.jp/2012/10/overlay-transparent-image-in-opencv.html)方法や、ROIを使って紹介されることが多いようですが、領域サイズの指定にシビアなことや、元画像の範囲外への描画ができないなど取り回しが悪くなります。

そこで本稿ではcv::warpPerspective()を用いた重ね合わせを紹介します。
[サンプルコード](https://github.com/locutus1990/ocv_misc/tree/master/pictureInPicture)：
1. 単純な重ね合わせ(transparent.cpp)
2.GUIで位置を調整(transparent2.cpp)

# 1.透過画像の読み込み

第２引数に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](https://qiita-image-store.s3.amazonaws.com/0/98965/11a34ffd-98b1-253f-36da-e9f8f722d58a.png)

# 3.前景画像の作成

前景画像(1)とマスク画像(2)を作成します。
画像(1)は読み込んだサイズのままで良いような気もしますが、出力画像と同じ幅・高さの黒画像に貼り付けることで後の演算が楽になります。
ついでに、画像の変形も行えるようにします。

## 3-1. 前景画像の変形行列

画像の変形にはcv::warpPerspective()を使います。
元画像の矩形の４点と、変形後の座標４点を指定すれば、cv::getPerspectiveTransform()で行列が得られます。

```cpp
//座標指定(初期位置は背景画像の中心にする）
int ltx = (img3.cols - img3.cols)/2;
int lty = (img3.rows - img3.rows)/2;
int ww  = img3.cols;
int hh  = img3.rows;

//元画像の４点
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) );

//変形後の４点
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. 前景画像の変形

前景画像を出力画像と同じ幅・高さの画像に出力します。
この時にアルファチャンネルも一緒に変形してしまうのが、この手法の肝心な点です。

```cpp
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](https://qiita-image-store.s3.amazonaws.com/0/98965/7b926b42-d859-a522-2c70-59c3fc7c2f7a.png)

## 3-3 マスクの作成

3-2で作成した画像から、前景画像(1)、前景画像マスク(2)を作ります。
ここでは地道に cv::split()とcv::merge()を使います

```cpp
//チャンネルに分解
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)の白黒反転です。

```cpp
//背景用アルファチャンネル   
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);`

以上の処理を実装した[サンプルコード](https://github.com/locutus1990/ocv_misc/blob/master/pictureInPicture/transparent.cpp)を置いておきます。(OpenCV4）

# 6.おまけ

サンプルコードでは真ん中に矩形画像を配置しているので、cv::warpPerspective()を使った恩恵があまり見えません。
そこで、ウィンドウ上で自由に変形できる[サンプル](https://github.com/locutus1990/ocv_misc/blob/master/pictureInPicture/transparent2.cpp)を作りました。
前景画像の周囲に表示されるコントロールポイントをドラッグすると、前景画像が自由変形します。
![dst.gif](https://qiita-image-store.s3.amazonaws.com/0/98965/22868499-ab77-e192-5706-4b455fe2b2b7.gif)