파이썬 insightface로 이미지의 얼굴 바꾸기 (파이썬 딥페이크 합성)
insightface는 얼굴분석과 관련된 라이브러리로 얼굴탐지, 얼굴인식 등의 기능을 제공합니다. 이 글에서는 insightface를 이용해 이미지의 얼굴을 다른 얼굴로 바꿔보겠습니다. (얼굴 합성, 딥페이크)
목차
라이브러리 설치
우선 insightface 라이브러리를 설치합니다. 그리고 AI모델 사용을 위해 onnxruntime 라이브러리도 설치하는데 사용환경이 CPU인지 GPU인지 CUDA 버전이 몇인지에 따라 방법이 약간 다릅니다. 이미지를 다루기 위해 pillow, opencv, matplotlib도 설치해줍니다.
라이브러리 설치
pip install numpy insightface pillow opencv-python matplotlib
사용하는 환경에 맞게 onnxruntime 라이브러리 설치
pip install onnxruntime
pip install onnxruntime-gpu
pip install onnxruntime-gpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/
pip install ort-nightly-gpu --index-url=https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-12-nightly/pypi/simple/
얼굴 합성 모델 다운로드
얼굴을 바꾸기 위한 inswapper 모델을 다운로드 받고 작업할 폴더를 만들어 그 안에 넣어줍니다.
다운로드 링크 : https://huggingface.co/ezioruan/inswapper_128.onnx/resolve/main/inswapper_128.onnx?download=true
리눅스나 코랩의 경우 wget을 이용해 다운로드 받을 수 있습니다. 작업폴더의 경로에서 아래 명령을 실행해줍니다.
wget https://huggingface.co/ezioruan/inswapper_128.onnx/resolve/main/inswapper_128.onnx?download=true -O inswapper_128.onnx
다른 이미지를 얼굴을 참조해 얼굴 합성
이제 설치한 라이브러리를 불러오고 이미지의 얼굴을 바꿔보겠습니다. 결과를 보기 쉬운 주피터노트북에서 코드를 실행하겠습니다.
from PIL import Image
import cv2
import matplotlib.pyplot as plt
from insightface.app import FaceAnalysis
import insightface
app = FaceAnalysis(allowed_modules=['detection','recognition'], providers=['CPUExecutionProvider', 'CUDAExecutionProvider'])
app.prepare(ctx_id=0, det_thresh=0.5, det_size=(640, 640))
swapper = insightface.model_zoo.get_model('inswapper_128.onnx',providers=['CPUExecutionProvider', 'CUDAExecutionProvider'])
FaceAnalysis를 이용해 얼굴탐지와 인식을 하는 모델을 불러옵니다. 처음에는 모델을 다운로드 받는데 이 코드에서는 경로를 지정안했기 때문에 insightface의 기본 경로에 모델을 다운로드 받습니다. 아래 이미지처럼 실행결과를 보면 어떤 경로에 다운로드가 되었는지 볼 수 있습니다. 아까 다운로드받은 inswapper 모델은 swapper라는 변수명으로 insightface.model_zoo.get_model을 이용해 불러왔습니다.

FaceAnalysis의 prepare 메소드의 인자는 아래와 같습니다.
- ctx_id : 어떤 gpu를 사용할 지 지정합니다
- det_thresh : 얼굴탐지임계값으로 값이 높으면 오탐지가 적어지지만 진짜 얼굴도 탐지가 안 될수 있습니다. 0~1의 범위를 가집니다.
- det_size : 탐지와 관련된 영역의 크기에 대한 것 같은데 (640,640)으로 사용했을 때 문제가 생긴적은 없습니다
이제 이미지를 불러오고 얼굴탐지와 인식을 하겠습니다. 작업 폴더에 이미지를 넣어줍니다. 그러면 작업폴더에는 inswapper 모델 파일과 이미지가 들어있게 됩니다. 코랩이여서 파일에 한글이 들어가도 상관없었지만 영어로 해줘야 다른 환경에서 이미지를 잘 불러옵니다.

얼굴탐지, 얼굴인식
img1 = cv2.imread('설국열차.jpg')
faces1 = app.get(img1)
img2 = cv2.imread('오징어게임2.webp')
faces2 = app.get(img2)
opencv를 이용해 이미지를 불러오고 FaceAnalysis를 이용해 얼굴 탐지와 인식을 수행합니다. 그 결과로 나온 faces1과 faces2는 파이썬 리스트로 탐지된 얼굴 수만큼의 Face 객체가 들어있습니다. Face 클래스는 파이썬 딕셔너리를 상속받아 만들어진 것으로 이미지의 얼굴에 대한 정보가 다음과 같이 들어있습니다.
- bbox : 얼굴을 포함하는 사각형의 꼭짓점 좌표 4개
- kps : 눈, 코, 입에 대한 좌표 5개
- det_score : 탐지점수로 값이 높을수록 얼굴일 확률이 크고 작을수록 얼굴이 아닐 확률이 큽니다
- embedding : 얼굴인식에서 나온 512개의 성분을 가진 벡터로 이 벡터를 이용해 얼굴을 비교하거나 합성합니다
얼굴을 바꾸기 전에 탐지된 얼굴들을 확인해보겠습니다. 아래 코드의 함수로 matplot.pyplot의 subplots를 이용해 시각화하겠습니다.
def faceplot(img, faces):
l = len(faces)
x = l//5+1
fig,axes=plt.subplots(nrows=x,ncols=5,figsize=(10,x*2))
if x==1:
for col in range(5):
ax=axes[col]
if col < l:
box = faces[col].bbox
roi = img[int(box[1]):int(box[3]), int(box[0]):int(box[2])]
roi = cv2.cvtColor(roi,cv2.COLOR_BGR2RGB)
ax.imshow(roi)
ax.set_xlabel(str(col),fontsize=20)
else:
fig.delaxes(ax)
else:
for row in range(x):
for col in range(5):
idx = row*5+col
ax=axes[row][col]
if idx < l:
box = faces[idx].bbox
roi = img[int(box[1]):int(box[3]), int(box[0]):int(box[2])]
roi = cv2.cvtColor(roi,cv2.COLOR_BGR2RGB)
ax.imshow(roi)
ax.set_xlabel(str(idx),fontsize=20)
else:
fig.delaxes(ax)
plt.tight_layout()
plt.show()
Face 객체의 bbox를 이용했고 얼굴의 인덱스를 표시했습니다. 변수로 cv2를 통해 불러왔던 img와 app.get을 통해 나온 faces를 넣어주면 됩니다.
faceplot(img1,faces1)

faceplot(img2,faces2)

얼굴 바꾸기
얼굴을 바꿀 때 이미지 아래 표시된 인덱스번호를 이용하게 됩니다. 얼굴은 swapper.get을 이용해 바꿀 수 있습니다.
result = swapper.get(img2, faces2[6], faces1[2], paste_back=True)
temp = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
rimg = Image.fromarray(temp)
display(rimg)
인수로 얼굴을 바꾸고 싶은 이미지, 바꾸고 싶은 얼굴에 대한 Face 객체, 바꾸기 위해 참조되는 Face 객체가 들어가고 paste_back이 True면 인수로 준 이미지에 얼굴을 합성해주고 False면 바뀐 얼굴 부분만 결과로 나옵니다. 인수에는 참조되는 얼굴이 포함된 이미지가 들어있지 않은데 Face객체의 위의 코드를 이용해 faces2의 6번 인덱스 얼굴을 faces1의 2번 얼굴로 바꿨습니다.

함수를 만들어 swapper.get이 여러번 실행되도록 하겠습니다.
def faceswap(img,faces1,faces2,l):
for t in l:
img = swapper.get(img, faces1[t[0]], faces2[t[1]], paste_back=True)
temp = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
rimg = Image.fromarray(temp)
display(rimg)
인수로 얼굴을 바꾸고 싶은 이미지, 바꾸고 싶은 얼굴에 대한 Face 객체, 바꾸기 위해 참조되는 Face 객체가 들어가고 바꿀 얼굴에 대한 정보를 튜플의 리스트로 만들어 넣어줍니다.
faceswap(img2,faces2,faces1,[(6,2),(0,2),(4,1),(5,1),(1,0),(2,0),(3,0),(7,0)])
위와 같은 코드를 이용하면 faces2의 6번, 0번 → faces1의 2번, faces2의 4번, 5번 → faces1의1번, faces2의 1번, 2번, 3번, 7번 → faces1의 0번 얼굴로 바꾸게 됩니다.

임베딩 벡터 만들어서 얼굴 합성하기
얼굴을 합성하는데 참조되는 이미지가 없어도 Face 객체의 정보가 있다면 얼굴합성이 가능합니다. 그래서 Face객체만 따로 저장하고 불러와 얼굴합성을 할 수도 있습니다. 임의의 임베딩 벡터를 만들어 새로운 Face 객체를 만드는 것도 가능합니다.
import numpy as np
from insightface.app.common import Face
vector = np.random.normal(loc=0.0, scale=1.0, size=512).astype(np.float32)
base_face = faces1[0].copy()
random_face = Face(**base_face)
random_face.embedding = vector
랜덤으로 512개 성분을 가지는 벡터를 만들었습니다. faces[0]을 복사해 새로운 Face객체를 만든 후 새로운 Face객체의 embedding을 랜덤으로 만든 벡터로 바꿔줍니다.
result = swapper.get(img2, faces2[1], random_face, paste_back=True)
temp = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
rimg = Image.fromarray(temp)
display(rimg)

img2의 1번 얼굴을 랜덤한 얼굴로 바꿨습니다.
위의 내용에 대한 주피터노트북 파일입니다. onnxruntime 설치부분은 CPU 기준으로 되어있으므로 GPU를 사용하려면 주석을 하고 다른 줄의 주석을 해제하면 됩니다.
화질 개선하기
이미지의 크기 등의 속성에 따라 합성 화질이 안 좋을 때가 많습니다. 그런 경우 gfpgan과 인페인팅을 이용해 화질을 개선하고 이상한 부분을 바꿀 수 있습니다. 다음 글에서 그와 관련된 것을 다루겠습니다.