画像中のモザイク箇所を検出する

(2017/05/24追記)
githubにコードを登録しました。本ページに記載したものそのままですが。
https://github.com/summer4an/mosaic_detector

# 前提

https://twitter.com/stsiizk/status/858517661889646592
あれ？これってもしかして…
というわけで来るべき日に備えて(?)準備をすることにしました。

モザイクの種類は複数ありますが、今回は単一の色で塗られた箱が多数並んだものを対象にします。

ググってみましたがモザイク加工する話題ばかりで、モザイク部分を検出する方法については特に見つけられなかったため自力で。

ゴールは画像のモザイク部分を検出し、白塗りすることです。

python3.5.2とOpenCV3.2.0を利用しました。


# 方針

画像加工ソフトやpythonでコードを組んで試行錯誤したところ、以下の手順で満足のいく結果が得られました。
　1. グレースケールに変換
　2. Cannyでエッジ検出
　3. 白黒反転
　4. 少しぼかす
　5. 各種サイズの格子画像でそれぞれパターンマッチング
　6. マッチした箇所を塗りつぶす

4で少しぼかすのは、3の結果で箱の境目の線が1ピクセル幅の直線にはなってくれず、2ピクセルの幅の間を行ったり来たりしたものになったため、パターンマッチングしづらかったためです。
5で各種サイズの格子画像を使うのは、パターンマッチングが拡大縮小に弱いためです。一般的と思われる11ピクセルから20ピクセル四方の格子画像を使いました。


# コード

まず以下で各サイズのパターンマッチング用の格子画像を作ります。

```python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#モザイクのサイズが11から20までだった場合のためのパターン画像ファイルを生成。
#以下参考資料。
#  http://qiita.com/suto3/items/5181b4a3b9ebc206f579

from PIL import Image

def make_image(masksize, filename):
    picturesize = 2+masksize+masksize-1+2
    screen = (picturesize, picturesize)

    img = Image.new('RGB', screen, (0xff,0xff,0xff))

    pix = img.load()

    for i in range(2,picturesize,masksize-1):
        for j in range(2,picturesize,masksize-1):
            for k in range(0,picturesize):
                pix[i, k] = (0,0,0)
                pix[k, j] = (0,0,0)

    img.save(filename)
    return

for i in range(11, 20+1):
    make_image(i, "pattern"+str(i)+"x"+str(i)+".png")
```

実行するとこんな感じの画像が10枚できます。
![pattern11x11.png](https://qiita-image-store.s3.amazonaws.com/0/108313/aa1e4295-ac7d-8b84-e247-e31c4c6098d2.png) 　 ![pattern20x20.png](https://qiita-image-store.s3.amazonaws.com/0/108313/5f0f6a07-922d-f80a-c4cd-8f3f3710e716.png)

この格子画像を使って以下で検出をします。

```python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#モザイクの箇所を検出し、白塗りする。
#以下参考資料。
#  ・Template Matching
#    http://docs.opencv.org/3.2.0/d4/dc6/tutorial_py_template_matching.html
#    http://opencv.jp/cookbook/opencv_img.html#id32

import cv2
import numpy as np

import sys
args = sys.argv

if len(args) != 2:
    print("too few argument.")
    sys.exit(1)

img_rgb = cv2.imread(args[1])

img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) #グレースケールに
img_gray = cv2.Canny(img_gray,10,20) #エッジ検出
img_gray = 255-img_gray #白黒反転
img_gray = cv2.GaussianBlur(img_gray,(3,3),0) #少しぼかす

cv2.imwrite('output_gray.png', img_gray)

for i in range(11,20+1):
    pattern_filename = "pattern"+str(i)+"x"+str(i)+".png"
    template = cv2.imread(pattern_filename, 0)
    w, h = template.shape[::-1]

    img_kensyutu_kekka = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
    threshold = 0.3
    loc = np.where(img_kensyutu_kekka >= threshold)
    for pt in zip(*loc[::-1]):
        #cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,255,255), 1)
        cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (255,255,255), -1)
    cv2.imwrite('output_progress_'+str(i)+'.png', img_rgb)

cv2.imwrite('output_result.png', img_rgb)

cv2.imshow('window1', img_rgb)
cv2.imshow('window2', img_gray)
cv2.waitKey(0)

cv2.destroyAllWindows()
```

# 結果

以下のいろいろなサイズでモザイクを掛けた画像を処理します。
![000_pic8_many_mozaic.jpg](https://qiita-image-store.s3.amazonaws.com/0/108313/47188e23-ceb5-a01b-fec7-20178caa0b37.jpeg)
画像は http://gahag.net/011032-cat-sunflower-smell/ から。

結果は以下です。
![output_result.png](https://qiita-image-store.s3.amazonaws.com/0/108313/b6b1f575-289a-cc04-8e79-26c34000b494.png)

モザイクを掛けた箇所はもれなく塗りつぶすことができました。
余計な箇所も塗りつぶされていますが、どうせ補完されるのでOKでしょう。

他の画像でも結果は同様でしたので満足です。


# 参考資料

- Imageライブラリの使い方
  http://qiita.com/suto3/items/5181b4a3b9ebc206f579

- Template Matching
  http://docs.opencv.org/3.2.0/d4/dc6/tutorial_py_template_matching.html
  http://opencv.jp/cookbook/opencv_img.html#id32