画像に様々な形のモザイクをかける(Python, OpenCV)

# 画像の好きな場所にモザイクをかける(Python, OpenCV)

## はじめに
[サーバーレスWebアプリ Mosaic](https://mosaic.w2or3w.com "Mosaic")を開発して得た知見を振り返り定着させるための[ハンズオン記事](https://qiita.com/w2or3w/items/87b57dfdbcf218de91e2)セットとは別記事にしてますが、画像にモザイクをかける実装についてです。

最初、検出できるのは顔だけだったのですが、[この記事](https://qiita.com/w2or3w/items/c7288e8ce62d061ff6d2)でも書きましたが、文言も検出できるようにしまして。
モザイク処理、顔だけの時は検出領域が長方形といいますか画像に対して並行だったのですが、文言の場合は検出領域が斜めってたりしてまして。

長方形ではない、斜めってる領域に対して(多角形もイケるかと)モザイクをかけるという実装をしましたので、忘れないうちに備忘録として記事を書いておきます。

## やりたいこと
これがオリジナル画像。
![photo-1484608856193-968d2be4080e.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/676e6c20-967e-0c29-ad6f-f488c0f391a8.jpeg)
この画像の、顔と「LIFEGUARD」という文字にモザイクをかけたい。
![faces-photo-1484608856193-968d2be4080e.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/13e65f9f-9779-d584-4e42-f2e1d1d39c7e.jpeg)
検出結果はこんな感じ。顔は画像に対して並行だけど、文字は斜めってる。
![photo-1484608856193-968d2be4080e-200203132846-458d11b515.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/cf95c201-5087-478d-f25a-fcccceeed3c4.jpeg)
こんな感じのモザイク画像を作りたい。

## どうやったか
1. 画像全体にモザイクをかけた画像を作成する。  
![fullmosaic.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/169196f5-ecae-3d06-35ae-582b676aaa5b.jpeg)
2. モザイクを入れたい箇所のマスク画像を作成する。  
![mask.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/e9a6a793-d5c6-8527-cd60-f979d92d60a1.jpeg)
3. 元画像に対して、マスク処理で、モザイク画像を合成する。  
![photo-1484608856193-968d2be4080e-200203132846-458d11b515.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/cf95c201-5087-478d-f25a-fcccceeed3c4.jpeg)  
こんな感じのモザイク画像ができる。

## コード

```py
    try:
        height = image.shape[0]
        width = image.shape[1]

        # 1. 画像全体にモザイクをかけた画像を作成する。
        ratio=0.1
        imageSmall = cv2.resize(image, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
        imageMosaic = cv2.resize(imageSmall, image.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)

        # 2. モザイクを入れたい箇所のマスク画像を作成する。
        imageMask = np.tile(np.uint8(0), (height, width, 1))
        for points in pointsList:
            pointAry = convertToArray(points)
            contours = np.array(
                [
                    [pointAry[0].x, pointAry[0].y],
                    [pointAry[1].x, pointAry[1].y],
                    [pointAry[2].x, pointAry[2].y],
                    [pointAry[3].x, pointAry[3].y],
                ]
            )
            cv2.fillConvexPoly(imageMask, contours, color=(255, 255, 255))

        # 3. 元画像に対して、マスク処理で、モザイク画像を合成する。
        # for y in range(height) :
        #     for x in range(width) :
        #         color = imageMask[y][x]
        #         if color != 0 :
        #             image[y][x] = imageMosaic[y][x]
        # ↑10秒のコード ↓80ミリ秒のコード (800x600)
        image = np.where(imageMask != 0, imageMosaic, image)

    except Exception as e:
        logger.exception(e)
```

## ゆる募
~~ゆる募 1~~
~~3.元画像に対して、マスク処理で、モザイク画像を合成する。~~
~~これ、ループで1ピクセルずつ処理させてるのですが、遅くて。OpenCVでもっとサクッと効率よく実装できるのではないかと思っておりまして。どなたか。~~
@yousuke_papa さんからアドバイス頂きまして解決しました。800x600の画像での処理時間が10秒が80ミリ秒になりました！Pythonで長いループ、ダメ、絶対！！

ゆる募 2
「長方形といいますか画像に対して並行」や「斜めってる領域」このあたりの正式名称。どなたか！

- [Chuuiさん](https://twitter.com/es_chuui)に[コメント](https://twitter.com/es_chuui/status/1226818924773199872)頂きました！ありがとうございます！  
「長方形といいますか画像に対して並行」-> 「画像に対して並行な矩形」  
「斜めってる領域」-> 「多角形領域」/「ポリゴン」  
fillConvexPoly関数には『凸ポリゴンを描きます』と説明されておりまして。  
アルゴリズムの学術分野(非デジタル)では、多角形領域をConvex(凸)か非Convex(凹)かで大きく区分するそうです・・・:innocent:・・・。