画像修復(inpaint)を使ってオブジェクト消去したった(OpenCV:C++)

# Background
[画像修復(inpaint)を使ってオブジェクト消去したった(OpenCV:Python)](https://qiita.com/satsukiya/items/8f6d33257056fff275f6)のつづきでC++のみに存在するinpaint関数を紹介します。

# Introduction

表にざっくりとまとめてみました。

| inpaint type |year|python|include |library |thesis|note|
|:-----------------|:--------|:---------------------|:------------------|:--|:--|:--|
|ナビエ・ストークス（Navier-Stokes）法|2001|○|`#include <opencv2/photo.hpp>`| `opencv_photo` | [Navier-stokes, fluid dynamics, and image and video inpainting](https://www.math.ucla.edu/~bertozzi/papers/cvpr01.pdf)|流体力学に基づくアルゴリズム|
|Alexandru Teleaによる手法|2004|○|`#include <opencv2/photo.hpp>`| `opencv_photo` | [An image inpainting technique based on the fast marching method.](https://www.rug.nl/research/portal/files/14404904/2004JGraphToolsTelea.pdf)|領域の境界から内側に向かって徐々に傷を修復するFast Marching Methodを基にしたアルゴリズム|
| Patch Offsetによる手法 | 2012 |×| `#include <opencv2/xphoto.hpp>` | `opencv_xphoto` |[Statistics of Patch Offsets for Image Completion](http://kaiminghe.com/publications/eccv12completion.pdf)|画像内の類似した画像を推測して修復するアルゴリズム|
| ファジィフーリエ変換による手法 | 2014 |×| `#include <opencv2/fuzzy.hpp>` | `opencv_fuzzy`|[Image reconstruction by means of F-transform](http://irafm.osu.cz/f/PhD_theses/Vlasanek.pdf)| フーリエ変換を使った手法。 細かい傷用`ONE_STEP`、 大きな傷用`MULTI_STEP`、 細かい傷・大きな傷併用`ITERATIVE`の3パターンが用意されている|


# Development

```makefile:GNUmakefile
CXX = c++
CXXFLAGS =  -I/usr/local/Cellar/opencv/4.1.1_2/include/opencv4/
LDFLAGS = -L/usr/local/Cellar/opencv/4.1.1_2/lib/
LDLIBS =  -lopencv_core -lopencv_highgui -lopencv_imgcodecs -lopencv_imgproc -lopencv_fuzzy -lopencv_photo -lopencv_xphoto
CXXVERSION = -std=c++11

inpaint: inpaint.cpp
	$(CXX) $< -o $@ $(CXXFLAGS) $(CXXVERSION) $(LDFLAGS) $(LDLIBS)

clean :
	rm inpaint
```


```c++:inpaint.cpp
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/fuzzy.hpp>
#include <opencv2/photo.hpp>
#include <opencv2/xphoto.hpp>


int main(int argc, const char* argv[]) {
	cv::Mat src = cv::imread("./capture_2.png");


	cv::Mat resizeMat, dst, mergeMat;
	cv::resize(src, resizeMat, cv::Size(), 0.5, 0.5);

	//マスク画像 背景黒 - 補修区画白
	cv::Mat mask_img = cv::Mat::zeros(resizeMat.size(), CV_8UC3);
	cv::rectangle(mask_img, cv::Point(185,255), cv::Point(470,300), cv::Scalar(255,255,255), -1, cv::LINE_AA); 
	cv::rectangle(mask_img, cv::Point(40,25), cv::Point(115,65), cv::Scalar(255,255,255), -1, cv::LINE_AA);
	cv::rectangle(mask_img, cv::Point(200,40), cv::Point(440,110), cv::Scalar(255,255,255), -1, cv::LINE_AA);

	cv::cvtColor(mask_img,mask_img,cv::COLOR_BGR2GRAY);

	cv::imwrite("mask.png", mask_img);

	// ns
	cv::inpaint(resizeMat, mask_img, dst, 3, cv::INPAINT_NS);
	cv::imwrite("output_ns.png", dst);
	dst.release();

	// telea
	cv::inpaint(resizeMat, mask_img, dst, 3, cv::INPAINT_TELEA);
	cv::imwrite("output_telea.png", dst);
	dst.release();

	cv::Mat mask_inv = ~mask_img;
	resizeMat.copyTo(mergeMat, mask_inv);

	// xphoto
	cv::xphoto::inpaint(mergeMat, mask_inv, dst, cv::xphoto::INPAINT_SHIFTMAP);
	cv::imwrite("output_xphoto_shiftmap.png", dst);
	dst.release();

	// fuzzy
	cv::ft::inpaint(mergeMat, mask_inv, dst, 3, cv::ft::LINEAR, cv::ft::ONE_STEP);
	cv::imwrite("output_fuzzy_linear_one_step.png", dst);
	dst.release();

	cv::ft::inpaint(mergeMat, mask_inv, dst, 3, cv::ft::LINEAR, cv::ft::MULTI_STEP);
	cv::imwrite("output_fuzzy_linear_multi.png", dst);
	dst.release();

	cv::ft::inpaint(mergeMat, mask_inv, dst, 3, cv::ft::LINEAR, cv::ft::ITERATIVE);
	cv::imwrite("output_fuzzy_linear_iterative.png", dst);
	dst.release();

	src.release();
	mergeMat.release();
	resizeMat.release();
	mask_img.release();
	mask_inv.release();

   return 0;
}
```

```bash:実行
make
./inpaint
```

#### 補足

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/ba1e1767-215e-386a-7d4c-55d788a7763b.png" width="50%" alt="mask.png" >

マスク画像の読み込ませ方がそれぞれの関数で異なっています。
`cv::inpaint`では修復する区画を白にしますが、`cv::xphoto::inpaint` `cv::ft::inpaint`は黒にする。そのため、`cv::Mat mask_inv = ~mask_img;`で反転させています。


# Result

| library |parameter|     image     | odds |
|:-----------------|:------------------|:------------------|:---|
| photo | INPAINT_NS | ![output_ns.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/32078b53-f435-dc99-60d8-42a4530c9b87.png) ||
| photo | INPAINT_TELEA | ![output_telea.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/cd1c0a17-6805-ec97-2400-d05fcdd9a637.png) ||
| xphoto | INPAINT_SHIFTMAP | ![output_xphoto_shiftmap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/3c923cc9-10fb-7c29-f7fc-3b4bfb9ed80d.png) ||
| fuzzy |  ONE_STEP | ![output_fuzzy_linear_one_step.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/56222ca4-f625-3319-a043-1fdf2c7b9c12.png)|×|
| fuzzy |  MULTI_STEP | ![output_fuzzy_linear_multi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/9dab1473-af35-7102-33f0-5054e3a33a28.png)|◎|
| fuzzy |  ITERATIVE | ![output_fuzzy_linear_iterative.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288206/ab30a2a8-0932-b2d5-3388-e61dfaab926b.png)|○|

上の時刻を消す処理をする場合は背景画像を元に修復をするため`cv::inpaint`でも問題はないです。下の字幕ですが`cv::inpaint`を使うと真ん中で折り目がくっきりとついてしまいますが、`cv::ft::inpaint`の方が少し緩和されているっぽい印象です。他の箇所よりぼやけていますが`MULTI_STEP`が全体的に自然に仕上がっています。

`cv::xphoto::inpaint INPAINT_SHIFTMAP`は画像内の区画を元にペーストしていて、背景画像がビル街や森林など同一のオブジェクトがたくさんあるときに威力が発揮できそうです。

`cv::ft::inpaint ONE_STEP`は、細かい傷用のパラメータなので対応できていないことがわかります。

# Future
次回は動画で字幕を消去できるかをtryしてみます:smirk_cat:


# Reference

- [画像のInpainting - opencv-python doc](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_photo/py_inpainting/py_inpainting.html)
- [ 画像の修復・不要オブジェクトを除去する - opencv cookbook](http://opencv.jp/cookbook/opencv_img.html#id25)
- [開発メモ その69 OpenCV 3.2 with ContribモジュールでInpaintを使用する](https://taktak.jp/2017/05/16/2291)
- [Inpainting using F-transform - opencv doc](https://docs.opencv.org/4.2.0/d3/db8/tutorial_fuzzy_inpainting.html)