【python】世界チャンピオン「ミスター・サタン」変身アプリをつくった

# はじめに

みんなのあこがれ、世界チャンピオン「ミスター・サタン」。その「ミスター・サタン」に変身できるアプリを作ってみました。特徴的な眉、髭・アフロヘアーを身にまとえば、これであなたも「ミスター・サタン」！

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/7a43d395-d440-784b-9553-72dfa703b08c.jpeg">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/214db16d-4680-7165-4692-e92129e1969c.png">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/cb974f18-306e-12fb-0939-dd961c061de8.jpeg">

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/3347d5cb-5cfb-fcaf-6524-b28c0db4cdb4.png">

### **雑**(w)!

技術的には、face_recognitionというライブラリを使って顔の各種パーツの座標を取得し、それをもとに眉・髭・アフロの位置と大きさを計算し、描画しています。

# face_recognitionとは

Face Recognitionは、顔を認識・操作できる世界で最も単純な顔認識ライブラリです。

### [**Face Recognition**](https://github.com/ageitgey/face_recognition)

> Recognize and manipulate faces from Python or from the command line with the world's simplest face recognition library.

使用するには、まずpipでインストールを行います。

```sh
$ pip install opencv-python
$ pip install opencv-contrib-python
$ pip install cmake
$ pip install face_recognition
```

試しに、下記画像を使って、顔パーツの取得を行ってみます。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/bf9b1392-26fa-b8cb-0033-2f8c484719be.jpeg">


```python
import face_recognition
import cv2

F = "image.jpg"

image = face_recognition.load_image_file(F)
face_landmarks_list = face_recognition.face_landmarks(image)

print(face_landmarks_list)
```

![Img15790044321.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/a265d3a0-34c1-e4e2-1da9-c049b52ff337.jpeg)

各パーツの座標が取得できました。
取得できた座標を実際の画像にプロットしてみます。

```python
bgr = cv2.imread(F)
for face_landmarks in face_landmarks_list:

    for facial_feature in face_landmarks.keys():
        for i in range(len(face_landmarks[facial_feature])):
            cv2.drawMarker(bgr, face_landmarks[facial_feature][i], 
                           color=(255, 0, 0), markerType=cv2.MARKER_CROSS, thickness=1)

cv2.imshow('', bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/4f66b41a-1134-5bc0-8fc9-8fc74a20cee2.jpeg">


おおよそ、正しく認識できていることが分かります。

# 実際のコード

### 1.pipでインストール

```sh
$ pip install opencv-python
$ pip install opencv-contrib-python
$ pip install cmake
$ pip install face_recognition
```

### 2.ライブラリのインポート・定数宣言

```python
import face_recognition
import cv2
from PIL import Image
import numpy as np
```

```python
# 画像の縦幅・横幅の最大サイズ
MAX_IMAGE_WIDTH = 10000
MAX_IMAGE_HEIGHT = 10000
```

### 3.関数の定義(オーバーレイ描画・座標を配列に格納)
```python
class CvOverlayImage(object):
    """
    [summary]
      OpenCV形式の画像に指定画像を重ねる
    """

    def __init__(self):
        pass

    @classmethod
    def overlay(
            cls,
            cv_background_image,
            cv_overlay_image,
            point,
    ):
        """
        [summary]
          OpenCV形式の画像に指定画像を重ねる
        Parameters
        ----------
        cv_background_image : [OpenCV Image]
        cv_overlay_image : [OpenCV Image]
        point : [(x, y)]
        Returns : [OpenCV Image]
        """
        overlay_height, overlay_width = cv_overlay_image.shape[:2]

        # OpenCV形式の画像をPIL形式に変換(α値含む)
        # 背景画像
        cv_rgb_bg_image = cv2.cvtColor(cv_background_image, cv2.COLOR_BGR2RGB)
        pil_rgb_bg_image = Image.fromarray(cv_rgb_bg_image)
        pil_rgba_bg_image = pil_rgb_bg_image.convert('RGBA')
        # オーバーレイ画像
        cv_rgb_ol_image = cv2.cvtColor(cv_overlay_image, cv2.COLOR_BGRA2RGBA)
        pil_rgb_ol_image = Image.fromarray(cv_rgb_ol_image)
        pil_rgba_ol_image = pil_rgb_ol_image.convert('RGBA')

        # composite()は同サイズ画像同士が必須のため、合成用画像を用意
        pil_rgba_bg_temp = Image.new('RGBA', pil_rgba_bg_image.size,
                                     (255, 255, 255, 0))
        # 座標を指定し重ね合わせる
        pil_rgba_bg_temp.paste(pil_rgba_ol_image, point, pil_rgba_ol_image)
        result_image = \
            Image.alpha_composite(pil_rgba_bg_image, pil_rgba_bg_temp)

        # OpenCV形式画像へ変換
        cv_bgr_result_image = cv2.cvtColor(
            np.asarray(result_image), cv2.COLOR_RGBA2BGRA)

        return cv_bgr_result_image
```

```python
def GetPosi(posi_name):
    """
    [summary]
      指定した顔パーツの座標を取得する
    Parameters
    ----------
    posi_name : [str]
    Returns : [left_X、right_X, Top_Y, Bottom_Y]
    """

    for face_landmarks in face_landmarks_list:

        minX = MAX_IMAGE_WIDTH 
        maxX = 0
        minY = MAX_IMAGE_HEIGHT
        maxY = 0

        for i in range(len(face_landmarks[posi_name])):

            if face_landmarks[posi_name][i][0] < minX:
                minX  = face_landmarks[posi_name][i][0]

            if face_landmarks[posi_name][i][0] > maxX:
                maxX  = face_landmarks[posi_name][i][0]

            if face_landmarks[posi_name][i][1] < minY:
                minY = face_landmarks[posi_name][i][1]

            if face_landmarks[posi_name][i][1] > maxY:
                maxY = face_landmarks[posi_name][i][1]

    return [minX, maxX, minY, maxY]

```

### 4.顔のランドマークを取得
```python
# ファイル名の定義
F = "sample.jpg"
```

```python
# 画像から顔のランドマークを取得
image_fl = face_recognition.load_image_file(F)
face_landmarks_list = face_recognition.face_landmarks(image_fl)
```

### 5.眉・髭・アフロの位置を計算
```python
# 各パーツの座標を取得
eye_r = GetPosi('right_eye')
eye_l = GetPosi('left_eye')
mouse = GetPosi('top_lip')
nose  = GetPosi('nose_tip')
chin  = GetPosi('chin')

# 取得座標から眉幅・ひげ幅を算出
brow_h   = int((eye_r[3] - eye_r[2]) * 2) 
face_w   = chin[1] - chin[0]
beard_h  = int((mouse[2] - nose[2]) * 0.7)
beard_w  = int((face_w - (mouse[1] - mouse[0])) * 0.2)
beard_h2 = int((chin[3] - mouse[2]) * 0.6)
beard_h3 = int((chin[3] - mouse[2]) * 0.3)
scale    = int(face_w / 20)
scale2   = scale * 2

# アフロ画像の場所を算出(左右対称じゃないため微調整あり)
hair_w   = int(face_w * 1.83)
hair_h   = int(hair_w * 0.64)
hair_x   = int(chin[0] - (hair_w / 2 - face_w / 2) + scale * 1.5)
hair_y   = eye_l[2] - hair_h
```

```python
# 眉・髭の座標計算
eyeb_r = np.array(
    [
        [eye_r[0] - scale2, eye_r[2] - brow_h], 
        [eye_r[1] + scale2, eye_r[2] - brow_h - scale2], 
        [eye_r[1] + scale2, eye_r[2] - scale * 2] , 
        [eye_r[0] - scale2, eye_r[2]]
    ]
)

eyeb_l = np.array(
    [
        [eye_l[0] - scale2, eye_l[2] - brow_h - scale2], 
        [eye_l[1] + scale2, eye_l[2] - brow_h], 
        [eye_l[1] + scale2, eye_l[2]] , 
        [eye_l[0] - scale2, eye_l[2] - scale * 2]
    ]
)

beard_c = np.array(
    [
        [mouse[0] - scale, mouse[2] - beard_h], 
        [mouse[1] + scale, mouse[2] - beard_h], 
        [mouse[1] + scale, mouse[2] -  0] , 
        [mouse[0] - scale, mouse[2] -  0]
    ]
)

beard_l = np.array(
    [
        [mouse[0] - beard_w, mouse[2] - beard_h - scale], 
        [mouse[0] - 5, mouse[2] - beard_h], 
        [mouse[0] - 5, mouse[2] + beard_h2], 
        [mouse[0] - beard_w, mouse[2] + beard_h3]
    ]
)

beard_r = np.array(
    [
        [mouse[1] + 5, mouse[2] - beard_h], 
        [mouse[1] + beard_w, mouse[2] - beard_h - scale], 
        [mouse[1] + beard_w, mouse[2] + beard_h3], 
        [mouse[1] + 5, mouse[2] + beard_h2]
    ]
)
```

### 6.眉・髭・アフロの描画
```python
# OpenCVでファイル読み込み
image = cv2.imread(F)

# 眉毛・ひげ・描画処理
cv2.fillConvexPoly(image, points =eyeb_r, color=(0, 0, 0))
cv2.fillConvexPoly(image, points =eyeb_l, color=(0, 0, 0))
cv2.fillConvexPoly(image, points =beard_c, color=(0, 0, 0))
cv2.fillConvexPoly(image, points =beard_l, color=(0, 0, 0))
cv2.fillConvexPoly(image, points =beard_r, color=(0, 0, 0))
```

```python
# アフロ画像をオーバーレイ表示
cv_background_image = image
cv_overlay_image = cv2.imread(
    "head.png",
    cv2.IMREAD_UNCHANGED)  # IMREAD_UNCHANGEDを指定しα込みで読み込む

cv_overlay_image = cv2.resize(cv_overlay_image, (hair_w, hair_h))
point = (hair_x, hair_y)

image = CvOverlayImage.overlay(cv_background_image, cv_overlay_image, point)

# 画像描画
cv2.imshow("image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
```

# おまけ

使用したアフロ画像です。背景は透過です。head.pngとし、作業フォルダにおいてください。

![head.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555643/94957f79-2fbd-fe17-cf29-2856011ffd79.png)

# 終わりに

現時点では、正面画像のみ対応しています。Face Recognitionが、いい感じで座標を取得してくれるので、ちょっとの加工で作れました。出力したものの中には個人的に面白い出来のものもありましたが、肖像権の兼ね合いであきらめました。いろんな画像で遊んでもらえたら幸いです。

# 参考サイト

 [**【OpenCV】【Python】画像上に透過付き画像を重ねて描画**](https://qiita.com/Kazuhito/items/ff4d24cd012e40653d0c)