LoginSignup
16
2

More than 1 year has passed since last update.

TL;DR

ノートブックはこちら

はじめに

この記事はこちらの記事の続きだ!
目的としてはWEB会議やビデオ通話などの場面で顔のフィルターを自前で実装したいである。
前回はMediapipeを用いて簡単に口紅を塗ってみたが、今回は顔全体に対して色を塗ってみよう!

手順

今回の「顔フィルターは」は下記の手順で行う。

  1. 画像を読み込む
  2. 「お化粧」する色の定義
  3. 顔認識を行う
  4. 顔のパーツを抽出し色をつける
  5. 入力画像との融合

使用するツール

顔のパーツの色と融合する時の重みをまとめるため、yamlファイルを活用する。

インストール

$ pip install pyyaml

コード

①必要なモジュールをimportする

import cv2
import yaml
import numpy as np
import mediapipe as mp
import matplotlib.pyplot as plt

②画像データを読み込む

前回のイケメン君(笑)
(https://thispersondoesnotexist.com/)

img_path = "../input/image.jpg"

src = cv2.imread(img_path)
src = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)
img = src.copy()
plt.imshow(img)

before.png

③パラメータを定義し、読み込む

今回はyaml形式でパーツの色と重みをmake_up.yamlに保存する。

config/make_up.yaml

# color: [R, G, B]

face:
  color: [165, 57, 0]
  weight: 0.1

eyes:
  color: [52, 124, 44]
  weight: 0.15

lip:
  color: [142, 30, 29]
  weight: 0.2

eyebrow:
  color: [200, 200, 200]
  weight: 0.08

notebook/makeup.ipynb

def read_yaml(path):
  """yamlファイルを読み込むための関数

  入力:yamlファイルのパス
  出力:dict形式のデータ
    """
    with open(path, "r") as f:
        cfg = yaml.safe_load(f)
    return cfg

cfg = read_yaml("../config/make_up.yaml")

簡単な色の確認

# パーツの数に合わせてプロットを初期化する
# 偶数と奇数に注意!
fig, axs = plt.subplots(len(cfg.keys())//2, 2)
axs = np.ravel(axs)

# パーツをループし、色を描画する
for idx, (part, param) in enumerate(cfg.items()):
    tmp = []
    for _ in range(100):
        tmp.append(param["color"])
    axs[idx].set_title(part)
    axs[idx].imshow(np.reshape(tmp, (10, 10, 3)))

color.png

④Mediapipeを用いて顔認識を行う

# 検知器のインスタンス化
drawing = mp.solutions.drawing_utils
drawing_spec = drawing.DrawingSpec(thickness=1, circle_radius=1, color=(0, 255, 0))

drawing_styles = mp.solutions.drawing_styles

face_mesh = mp.solutions.face_mesh
face_detector = face_mesh.FaceMesh(min_detection_confidence=0.5, min_tracking_confidence=0.5)

# 検知処理
results = face_detector.process(img)

# 可視化
if results.multi_face_landmarks:
    for face_landmarks in results.multi_face_landmarks:
        drawing.draw_landmarks(
            image=img,
            landmark_list=face_landmarks,
            connections=face_mesh.FACEMESH_TESSELATION,
            connection_drawing_spec=drawing_styles.get_default_face_mesh_tesselation_style()
            )
plt.imshow(img)

detection.png

⑤パーツを抽出し、マスクを作成する

# マスクの初期化
gray = np.zeros((src.shape[0], src.shape[1], 3), dtype=np.uint8)

# 唇のINDEX
lips = list(face_mesh.FACEMESH_LIPS)
lips = np.ravel(lips)

# 目(左)のINDEX
l_eyes = list(face_mesh.FACEMESH_LEFT_EYE)
l_eyes = np.ravel(l_eyes)

# 目(右)のINDEX
r_eyes = list(face_mesh.FACEMESH_RIGHT_EYE)
r_eyes = np.ravel(r_eyes)

# 眉毛(左)のINDEX
l_eyebrow = list(face_mesh.FACEMESH_LEFT_EYEBROW)
l_eyebrow = np.ravel(l_eyebrow)

# 眉毛(右)のINDEX
r_eyebrow = list(face_mesh.FACEMESH_RIGHT_EYEBROW)
r_eyebrow = np.ravel(r_eyebrow)

# 抽出したパーツをdict形式で保存する
face_dict = {}
if results.multi_face_landmarks:
    for face_landmarks in results.multi_face_landmarks:
        mask_lip = []
        mask_face = []
        mask_l_eyes = []
        mask_r_eyes = []
        mask_l_eyebrow = []
        mask_r_eyebrow = []
        for i in range(face_mesh.FACEMESH_NUM_LANDMARKS):

       # 唇
            if i in lips:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_lip.append((x, y))

            # 目(左)
            elif i in l_eyes:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_l_eyes.append((x, y))

            # 目(右)
            elif i in r_eyes:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_r_eyes.append((x, y))

            # 眉毛(左) 
            elif i in l_eyebrow:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_l_eyebrow.append((x, y))

            # 眉毛(右)
            elif i in r_eyebrow:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_r_eyebrow.append((x, y))

            # それ以外の部分
            else:
                pt1 = face_landmarks.landmark[i]
                x = int(pt1.x * img.shape[1])
                y = int(pt1.y * img.shape[0])
                mask_face.append((x, y))

# 描画のためnumpy配列へ変換する
face_dict["mask_lip"] = np.array(mask_lip)
face_dict["mask_face"] = np.array(mask_face)
face_dict["mask_l_eyes"] = np.array(mask_l_eyes)
face_dict["mask_r_eyes"] = np.array(mask_r_eyes)
face_dict["mask_l_eyebrow"] = np.array(mask_l_eyebrow)
face_dict["mask_r_eyebrow"] = np.array(mask_r_eyebrow)

⑥抽出したパーツに対して色と重みをつける

# パーツの数に合わせてプロットを初期化する
fig, axs = plt.subplots(2, len(face_dict.keys())//2)
axs = np.ravel(axs)

# パーツの融合のベースマスク
full_mask = gray.copy()

# パーツをループし処理を行う
for idx, (part, v) in enumerate(face_dict.items()):
  # マスクをコピーし初期化する
    mask = gray.copy()

  # パーツの範囲を定義する
  convexhull = cv2.convexHull(v)

    # 色と重みを定義する
    if "eyes" in part:
        color = cfg["eyes"]["color"]
        weight = cfg["eyes"]["weight"]
    elif "eyebrow" in part:
        color = cfg["eyebrow"]["color"]
        weight = cfg["eyebrow"]["weight"]
    elif "face" in part:
        color = cfg["face"]["color"]
        weight = cfg["face"]["weight"]
    elif "lip" in part:
        color = cfg["lip"]["color"]
        weight = cfg["lip"]["weight"]
    else:
        color = (0, 0, 0)

    # 色を塗る
    mask = cv2.fillConvexPoly(mask, convexhull, color)
    mask = cv2.GaussianBlur(mask, (7, 7), 20)

  # パーツのマスクを全体のマスクに追加していく
    full_mask = cv2.addWeighted(full_mask, 1, mask, weight, 1)

  # 描画する
    axs[idx].set_title(part)
    axs[idx].imshow(mask)

パーツ毎のマスク
parts_mask.png

全体のマスク
Mask.png

⑦融合する

# 入力画像にマスクをかける
result = cv2.addWeighted(full_mask, 1, src, 1, 1)

# 描画する
fig, (ax0, ax1) = plt.subplots(1,2)
ax0.set_title("Before")
ax0.imshow(src)
ax1.set_title("After")
ax1.imshow(result)

After.png

比較する

comparison.png

あとがき

今回は唇だけではなく、顔全体に対して色を付けてみた!
大まかな範囲で色を塗っているが、本当のお化粧とは細かいところで調整することで、まだまだ本当の目標までは遠い!
ただ、これを通していい土台にはなるかなと思う。

一旦「お化粧」をここまでとして、次回はリアルタイムで「お化粧」してみよう!

16
2
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
16
2