LoginSignup
13
11

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-15

はじめに

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

(w)!

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

face_recognitionとは

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

Face Recognition

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

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

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

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

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

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

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()

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

実際のコード

1.pipでインストール

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

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

import face_recognition
import cv2
from PIL import Image
import numpy as np
# 画像の縦幅・横幅の最大サイズ
MAX_IMAGE_WIDTH = 10000
MAX_IMAGE_HEIGHT = 10000

3.関数の定義(オーバーレイ描画・座標を配列に格納)

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
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.顔のランドマークを取得

# ファイル名の定義
F = "sample.jpg"
# 画像から顔のランドマークを取得
image_fl = face_recognition.load_image_file(F)
face_landmarks_list = face_recognition.face_landmarks(image_fl)

5.眉・髭・アフロの位置を計算

# 各パーツの座標を取得
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
# 眉・髭の座標計算
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.眉・髭・アフロの描画

# 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))
# アフロ画像をオーバーレイ表示
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

終わりに

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

参考サイト

【OpenCV】【Python】画像上に透過付き画像を重ねて描画

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11