OpenCVで画像上に別の画像を描画する

# はじめに
OpenCVで画像上に別の画像を重ねる方法を紹介します。
![001_4.jpg](https://qiita-image-store.s3.amazonaws.com/0/98965/03c911b0-bb0c-4004-d96f-9513300bf78d.jpeg)

OpenCV.jpでは[複数の画像を連結する方法](http://opencv.jp/cookbook/opencv_img.html#id35)が紹介されていますが、一つの画像の上に別の画像を重ねる方法（PiP：ピクチャ・イン・ピクチャ）は無いようです。
また、Stack Overflowでは[ROI手法](http://stackoverflow.com/questions/10991523/opencv-draw-an-image-over-another-image)が紹介されていますが、領域サイズを間違えるとエラーになることや、元画像を変形して重ね合わせることができないなど、取り回しが悪いように思います。

そこで、本稿ではcv::warpAffineを使います。
行列を理解する手間がありますが、画像の変形に対応していることと、背景画像の外に描画してもエラーにならないなどの利点があります。
[サンプルコード](https://github.com/locutus1990/ocv_misc/blob/master/pictureInPicture/overlay.cpp)

# アフィン変換

「[アフィン変換](http://opencv.jp/cookbook/opencv_img.html#image-affine-transform)」は画像を拡縮、回転、剪断、平行移動する処理です。
詳しくは[OpenCV.jp](http://opencv.jp/opencv-2svn/cpp/geometric_image_transformations.html#cv-warpaffine)の`cv::warpAffine`を参照してください。

## アフィン行列

アフィン変換は２行３列の行列で表されます。
a〜dが拡縮・回転・剪断を、tx, tyが平行移動を表します。

|   |   |   |
|:-----------:|:------------:|:------------:|
| a | b | tx |
| c | d | ty |

次のように記述すると任意のパラメータを指定できます。

```cpp
cv::Mat mat = (cv::Mat_<double>(2,3)<<a, b, tx, c, d, ty);
```
より簡単にアフィン行列を作成する方法もあります。
回転行列は`cv::getRotationMatrix2D`（サンプル２参照）
対応点からのアフィン行列は`cv::getAffineTransform`（サンプル３参照）

## アフィン行列の適用

背景画像の上にアフィン変形画像を重ねるには次のように記述します。

```cpp
cv::warpAffine(smallImg, dstImg, mat, dstImg.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
```
`cv::warpAffine`は第１引数が前景画像、第２引数が背景画像となっています。
第６引数にBORDER_TRANSPARENTを指定することで背景画像が保存されます（下図左）。第６引数を指定しないと前景画像以外が黒で塗りつぶされます（下図右）。
このBORDER_TRANSPARENTが本手法のキモです。

![001_0.jpg](https://qiita-image-store.s3.amazonaws.com/0/98965/e2f4b04b-6b79-968e-744e-804dc7f1ed1c.jpeg)
【左：第６引数にBORDER_TRANSPARENTを指定。右：第６引数未指定】

参考までに、第６引数の値と処理結果です

| 第６引数の値 | 背景画像の処理 |
|:--|:--|
| 空欄 | 黒（または第７引数）で塗りつぶされる |
| BORDER_TRANSPARENT | 背景画像を残して前景画像を重ねる |
| BORDER_WRAP | 前景画像を繰り返す |
| BORDER_REFLECT_101 | 前景画像の鏡像を繰り返す |


# サンプル
## サンプル１（平行移動）

画像を平行移動して貼り付けるサンプルです。
tx, tyが平行移動量。
この値が背景画像の範囲外になってもエラーは出ません。

```cpp
 void PinP_tr(const cv::Mat &srcImg, const cv::Mat &smallImg, const int tx, const int ty)
 {
	//背景画像の作成
	cv::Mat dstImg;
	srcImg.copyTo(dstImg);

	//前景画像の変形行列
	cv::Mat mat = (cv::Mat_<double>(2,3)<<1.0, 0.0, tx, 0.0, 1.0, ty);

	//アフィン変換の実行
	cv::warpAffine(smallImg, dstImg, mat, dstImg.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
	imshow("affine", dstImg);
 }

PinP_tr(srcImg, smallImg, 10, 10);

```
![001.jpg](https://qiita-image-store.s3.amazonaws.com/0/98965/9cb81010-5759-460f-e451-a6dd674a68bd.jpeg)


## サンプル２（回転と平行移動）

画像を回転・平行移動して貼り付けるサンプルです。
angleが回転量（度）、tx, tyが平行移動量。

```cpp
void PinP_rot_tr(const cv::Mat &srcImg, const cv::Mat &smallImg, const double angle, const double tx, const double ty)
 {
	//背景画像の作成
	cv::Mat dstImg;
	srcImg.copyTo(dstImg);

	//前景画像の変形行列
	cv::Point2d ctr(smallImg.cols/2, smallImg.rows/2);//前景画像の回転中心
	cv::Mat mat = cv::getRotationMatrix2D(ctr, angle, 1.0);//回転行列の作成
	mat.at<double>(0,2) +=tx;//回転後の平行移動量
	mat.at<double>(1,2) +=ty;//回転後の平行移動量

	//アフィン変換の実行
	cv::warpAffine(smallImg, dstImg, mat, dstImg.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
	imshow(winName, dstImg);
 }

PinP_rot_tr(srcImg, smallImg, 45, 300, 100);
```
![003.jpg](https://qiita-image-store.s3.amazonaws.com/0/98965/eb9570de-bec3-0557-4738-23b2a716e429.jpeg)


## サンプル３　（貼り付け座標の指定）

前景画像の貼り付け先を座標で指定するサンプルです。
p0, p1が左上と右下の座標。
この２点で定義される長方形に前景画像を貼り付けます。

```cpp
/**------------------------------------------------------------*
 * @fn			OpenCVのピクチャーインピクチャ
 * @brief		画像内に画像を貼り付ける（位置を座標で指定）
 * @par[in ]	srcImg	背景画像
 * @par[in ]	smallImg	前景画像
 * @par[in ]	p0	前景画像の左上座標
 * @par[in ]	p1	前景画像の右下座標
 *------------------------------------------------------------*/
 void PinP_point(const cv::Mat &srcImg, const cv::Mat &smallImg, const cv::Point2f p0, const cv::Point2f p1)
 {
	//背景画像の作成
	cv::Mat dstImg;
	srcImg.copyTo(dstImg);

	//３組の対応点を作成
	vector<cv::Point2f> src, dst;
	src.push_back(cv::Point2f(0, 0));
	src.push_back(cv::Point2f(smallImg.cols, 0));
	src.push_back(cv::Point2f(smallImg.cols, smallImg.rows));

	dst.push_back(p0);
	dst.push_back(cv::Point2f(p1.x, p0.y));
	dst.push_back(p1);

	//前景画像の変形行列
	cv::Mat mat = cv::getAffineTransform(src, dst);

	//アフィン変換の実行
	cv::warpAffine(smallImg, dstImg, mat, dstImg.size(), CV_INTER_LINEAR, cv::BORDER_TRANSPARENT);
	imshow(winName, dstImg);
 }

//座標指定
cv::Point2f p0(100,100);
cv::Point2f p1(450,380);
PinP_point(srcImg, smallImg, p0, p1);
```
![004.jpg](https://qiita-image-store.s3.amazonaws.com/0/98965/c7fdc407-ef23-6a00-fd64-a2383133124d.jpeg)