Introduction

OpenCVSharpにPull Requestを投げました。
内容は、OpenCVの拡張モジュールに含まれる、xphoto内に用意されているInpaintingと呼ばれる画像修復関数のOpenCVSharp対応です。

そんな画像修復をC++で試してみたメモになります。

What is Inpainting?

Inpaintingとは聞き慣れない単語ですが、画像修復です。

WikipediaのInpaintingの記事 (英語。日本語記事は無し)によれば

Inpainting is the process of reconstructing lost or deteriorated parts of images and videos. In the museum world, in the case of a valuable painting, this task would be carried out by a skilled art conservator or art restorer. In the digital world, inpainting (also known as image interpolation or video interpolation) refers to the application of sophisticated algorithms to replace lost or corrupted parts of the image data (mainly small regions or to remove small defects).

Inpaintingは画像や映像における消失または悪化した部分を再構成する手順である。美術の世界において、価値ある絵画の場合、この仕事は技術的に優れた修復士または美術修復家によって為される。デジタルの世界において、inpainting (画像補間またはビデオ補間とも知られている) は、画像データ (主に小さな領域または小さな結果を取り除くため) の消失または壊れた部分を置換するために洗練されたアルゴリズムのアプリケーションを示している。

とあります。

有名なPhotoshopもこの機能があります。
流石に、Photoshop程の性能はありませんが、適切な場面で用いればOpenCVのInpaintingは威力を発揮します。

How to use?

Inpaintingは拡張モジュールにもありますが、通常のcv名前空間にもinpaint関数があります。
どちらも、修復対象の画像、マスク画像 (8ビットグレイスケール)、出力先画像、パラメータを指定しますが、両者の違いはマスク画像の指定方法とパラメータの有無にあります。

  • cv::xphoto::inpaint
    • マスク画像の非0 (通常は輝度値255)を有効な画像領域、0 (つまり黒)を修復対象の領域として扱う
  • cv::inpaint
    • マスク画像の0 (つまり黒)を有効な画像領域、非0 (通常は輝度値255)を修復対象の領域として扱う
    • パラメータとして、
    • 修復される各点を中心とする近傍円形領域の半径
    • 修復手法 (INPAINT_NSまたはINPAINT_TELEA) を指定

という違いがあります。
正確には、cv::xphoto::inpaintにも指定できるパラメータはありますが、OpenCV 3.2の時点では利用できるパラメータは1つだけなので、事実上パラメータはありません。
なので、拡張モジュールのcv::xphoto::inpaintは非常に簡単に利用できます。
また、cv::xphoto::inpaintの実装は、Microsoft Research AsiaStatistics of Patch Offsets for Image Completionという論文の実装の模様。

Algorithm

Inpaintingのアルゴリズムは、近隣の領域の複製になります。
近隣の近似している領域を継ぎ目が自然となるように、補正対象領域のピクセルを置換していきます。
当然、壊れた領域はまわりのピクセルから推測されて複製されるため、そこに存在しないものは修復できません。

例えば、野球選手の背番号が完全に隠れていて、それを補正した場合、正しい背番号は修復できない、ということです。
このケースの場合、ユニフォームの布地の大部分で背番号の部分が補完されるでしょう。

Source

下記がサンプルです。
cv::xphoto::inpaintとcv::inpaintを実行し、元画像、マスク画像、修復後の画像を上下に表示して比較します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "stdafx.h"
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/xphoto.hpp>

int main(int argc, const char * argv[])
{
// https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB:Tokyo_Tower_and_around_Skyscrapers.jpg
auto path = "B:\\300px-Tokyo_Tower_and_around_Skyscrapers.jpg";
auto maskpath = "B:\\300px-Tokyo_Tower_and_around_Skyscrapers_mask.bmp";

auto src = cv::imread(path);
auto mask = cv::imread(maskpath, cv::IMREAD_GRAYSCALE);
if (mask.channels() != 1)
return -1;

// xphotoのinpaint
cv::Mat dst(src.rows, src.cols, src.type());
cv::xphoto::inpaint(src, mask, dst, cv::xphoto::INPAINT_SHIFTMAP);

// 従来のinpaint
cv::Mat dst2(src.rows, src.cols, src.type());
cv::Mat mask2(mask.rows, mask.cols, mask.type());
// 従来のinpaintのマスクは白領域が修復対象
cv::bitwise_not(mask, mask2);
cv::inpaint(src, mask2, dst2, 3, cv::INPAINT_TELEA);

// 元画像、マスク画像、修復後の画像の合成画像
cv::Mat combiend(src.rows * 2, src.cols * 3, src.type());

cv::Rect roi_rect;
roi_rect.x = 0;
roi_rect.y = 0;
roi_rect.width = src.cols;
roi_rect.height = src.rows;

cv::Mat roi_src(combiend, roi_rect);
src.copyTo(roi_src);

roi_rect.x = src.cols;
cv::Mat roi_mask(combiend, roi_rect);
cv::Mat colorMask(mask.rows, mask.cols, src.type());
cv::cvtColor(mask, colorMask, cv::COLOR_GRAY2BGR);
colorMask.copyTo(roi_mask);

roi_rect.x = src.cols * 2;
cv::Mat roi_dst(combiend, roi_rect);
dst.copyTo(roi_dst);

roi_rect.x = 0;
roi_rect.y = src.rows;
cv::Mat roi_src2(combiend, roi_rect);
src.copyTo(roi_src2);

roi_rect.x = src.cols;
cv::Mat roi_mask2(combiend, roi_rect);
cv::Mat colorMask2(mask.rows, mask.cols, src.type());
cv::cvtColor(mask2, colorMask2, cv::COLOR_GRAY2BGR);
colorMask2.copyTo(roi_mask2);

roi_rect.x = src.cols * 2;
cv::Mat roi_dst2(combiend, roi_rect);
dst2.copyTo(roi_dst2);

cv::namedWindow("preview");
cv::imshow("preview", combiend);

cv::waitKey();

return 0;
}

サンプルでは、Wikipediaの東京タワーの記事の、ある画像中から東京タワーを除去しています。

上段がcv::xphoto::inpaint、下段がcv::inpaintを実行した結果になります。
ぱっと見た感じ、cv::xphoto::inpaintは良い感じです。
細かく見ると、付近の建物が複製されているのが目立ちます。

一方、cv::inpaintはぼけた感じになってしまいました。
パラメータを調整すれば変わる可能性はあると思いますが、いくつか試してみても、どれも似た感じで大差ありませんでした。

Conclusion

Photoshopが凄すぎるのであって、OSSでここまで実行できるというのは相当凄いことだと実感できます。
流石に、人間の手による修復にはかないませんが、近年のAIやDNNなどで、こうした画像修復の分野も、やがて人間に匹敵する性能を見せることになっていくのでしょう。