📖

顔交換の実装方法をみる

2023/12/24に公開

この記事はmob Advent Calendar 24日目の記事です。

顔交換ができるカメラアプリなどが巷にありますよね。どのような実装をしているんだろうって気になったので調べてみることにしました。

参考コードを読んでみる

roopという、 Stable Diffusion の文脈などで出てくる顔交換ができるソフトウェアがあります。

https://github.com/s0md3v/roop

このコードを読んでどのような流れで実装しているのかを見ていきます。

重要なコードは下記のファイルになります。

https://github.com/s0md3v/roop/blob/0497d0ba029ba24881c8c4d8644189909dc0def8/roop/processors/frame/face_swapper.py

重要な部分を抜粋するとこのようになります。一部わかりやすいようにコードを改変しています。


def pre_check() -> bool:
    download_directory_path = resolve_relative_path('../models')
    conditional_download(download_directory_path, ['https://huggingface.co/CountFloyd/deepfake/resolve/main/inswapper_128.onnx'])
    return True

def get_face_swapper() -> Any:
    global FACE_SWAPPER

    with THREAD_LOCK:
        if FACE_SWAPPER is None:
            model_path = resolve_relative_path('../models/inswapper_128.onnx')
            FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=roop.globals.execution_providers)
    return FACE_SWAPPER


def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
    return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True)

def process_image(source_path: str, target_path: str, output_path: str) -> None:
    source_face = get_one_face(cv2.imread(source_path))
    target_frame = cv2.imread(target_path)
    reference_face = None if roop.globals.many_faces else get_one_face(target_frame, roop.globals.reference_face_position)
    result = swap_face(source_face, reference_face, target_frame)
    cv2.imwrite(output_path, result)

順番的には pre_check が呼ばれて、その後 process_image が呼ばれる。
process_image の中で swap_face が、 swap_face の中で get_face_swapper が呼ばれています。

pre_check

pre_check 関数では inswapper_128.onnx というモデルの存在確認をしています。なければダウンロードします。

process_image

process_image関数内では、次のようなことをやっています。

  1. 置き換えたいソースになる顔を取得
  2. 置き換えたい画像を OpenCV で読み込む
  3. 置き換えたい顔を画像から取得
  4. swap_face(顔の入れ替え関数) を呼びだす
  5. 顔を入れ替えた結果を書き出す

swap_face

get_face_swapper を呼び出して、顔入れ替えモデルに置き換えたい顔、置き換えられる顔、置き換える画像を渡します。

get_face_swapper

inswapper_128.onnx モデルを返します。

と、このような流れで実装していることがわかります。

ミニマムなコードを作ってみる

上記のコードからミニマムで試せるコードを作ってみるとこんなコードになりました。

from typing import Any

import cv2
import insightface
import numpy
from insightface.app.common import Face

Face = Face
Frame = numpy.ndarray[Any, Any]

FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l')
FACE_ANALYSER.prepare(ctx_id=0)
FACE_SWAPPER = insightface.model_zoo.get_model('inswapper_128.onnx')


def swap_face(source_path: str, target_path: str, output_path: str) -> None:
    source_face = FACE_ANALYSER.get(cv2.imread(source_path))[0]
    target_frame = cv2.imread(target_path)
    reference_face = FACE_ANALYSER.get(target_frame)[0]
    result = FACE_SWAPPER.get(target_frame, reference_face, source_face, paste_back=True)
    cv2.imwrite(output_path, result)

swap_face("source.jpeg", "target.jpeg", "output.png")

これらの画像を用意して上記のコードを実行してみます。

source.jpeg target.jpeg

実行したところ下記のコードが生成されました。

しっかり顔が交換されてることが確認できました。 insightface が Python にしかないのでいくつか壁はありそうですが、同様のことをすれば例えばモバイルアプリのカメラアプリなどで同様のことができそうです。

Discussion