AI Avatar – 3. Integrate Images and Video Frames to Create the Avatar

이번시간에는 지난 강좌에서 만든 얼굴 이미지와 동영상에서 추출한 키프레임들을 가지고 아바타를 만들어 보도록 하겠습니다.

지난 강좌를 따라서 여기까지 오셨다면 여기에서 추가로 설치해야할 라이브러리는 없습니다. create_avatar.py라는 파일을 하나 생성하고 필요한 라이브러리를 코드에 추가해주세요.

import os
import cv2
import dlib
import numpy as np
from PIL import Image

첫번째 시간에 만들어 두었던 얼굴 추출하는 함수를 여기에서 한번더 활용하도록 하겠습니다. 이번에는 동영상 프레임에서 가져온 이미지의 얼굴을 추출하는데 사용될거에요. 참고로 동영상에서 프레임을 추출해서 아바타에 사용하는 이유는 이미지만 가지고 했을때 자연스럽지 않은 부분들을 보충하기 위함이에요. 이미지만 가지고도 충분히 해결할 수 있지만 동영상에서 추출하는게 아무래도 더욱 풍부한 얼굴표현을 가능하게 해주니까요. 이미지는 많으면 많을수록 좋습니다.

# dlib 얼굴 검출기 로드
detector = dlib.get_frontal_face_detector()

# 이미지에서 얼굴을 추출하는 함수
def extract_face(image_path):
    image = cv2.imread(image_path)
    faces = detector(image)
    if  len(faces) > 0:
        face = faces[0]   # 발견된 첫 번째 얼굴 추출
        x, y, w, h = face.left(), face.top(), face.width(), face.height()
        face_image = image[y:y+h, x:x+w]
        return cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB)
    else :
        return  None

그리고 첫번째 강좌에서 사용했던 코드 일부를 가져옵니다. 이미지경로를 배열에 저장하고, 얼굴들만 추출해서 배열에 담는 코드를 가져와서 추가합니다.

def extract_faces_from_arr(image_paths):
    faces = []
    for path in image_paths:
        face = extract_face(path)
        if face is not None :
            faces.append(face)
    return faces

image_paths = ['image1.png', 'image2.png', 'image3.png']
image_faces = extract_faces_from_arr(image_paths)

그리고 동영상에서 추출한 키프레임이미지들에서 얼굴을 획득하는 함수 extract_faces_from_folder()를 선언합니다. 동영상에서 추출한 프레임 이미지가 들어가있는 폴더 경로를 넘겨받아서 해당 폴더의 이미지파일들을 불러오고, 얼굴들을 전부 추출해서 배열에 담습니다.

def extract_faces_from_folder(frame_dir):
    # 프레임에서 얼굴 추출하여 배열에 저장
    frame_faces = []
    for frame_file in get_files_with_extensions(frame_dir, ['.jpg', '.png']):
        print(frame_file)
        frame_path = os.path.join(frame_dir, frame_file)
        frame_image = extract_face(frame_path)   # extract_face 함수 재사용
        if frame_image is not None :
            frame_faces.append(frame_image)
    return frame_faces

folder_path = 'frames'
frame_faces = extract_faces_from_folder(folder_path)

그리고 넘겨받은 얼굴배열과, 프레임에서 추출한 얼굴배열을 병합하여 하나의 배열로 만듭니다.

def merge_faces(image_faces, frame_faces):
    return image_faces + frame_faces

all_faces = merge_faces(image_faces, frame_faces)

그리고 배열을 수직으로 쌓아 아바타를 만들고, 해당 배열을 이미지로 만들어 아바타를 어떻게 만들었는지 확인합니다.

def store_faces_in_a_vertical_image(all_images):
    if all_images:
        composite_avatar = np.vstack(all_images)   # 이미지를 수직으로 쌓기
        avatar_image = Image.fromarray(composite_avatar)
        avatar_image.save('final_avatar.jpg')
        avatar_image.show()
    else :
        print("이미지나 프레임에서 유효한 얼굴을 찾을 수 없습니다.")

store_faces_in_a_vertical_image(all_faces)

완성된 코드는 다음과 같습니다.

import os
import cv2
import dlib
import numpy as np
from PIL import Image

def get_files_with_extensions(directory, extensions):
    # get multiple files
    files = os.listdir(directory)
    if len(extensions) > 0:
        return [f for f in files if os.path.splitext(f)[1].lower() in extensions]
    else:
        return files

# dlib 얼굴 검출기 로드
detector = dlib.get_frontal_face_detector()

# 이미지에서 얼굴을 추출하는 함수
def extract_face(image_path):
    image = cv2.imread(image_path)
    faces = detector(image)
    if  len(faces) > 0:
        face = faces[0]   # 발견된 첫 번째 얼굴 추출
        x, y, w, h = face.left(), face.top(), face.width(), face.height()
        face_image = image[y:y+h, x:x+w]
        return cv2.cvtColor(face_image, cv2.COLOR_BGR2RGB)
    else :
        return  None

# 여러얼굴을 추출하여 반환
def extract_faces_from_arr(image_paths):
    faces = []
    for path in image_paths:
        face = extract_face(path)
        if face is not None :
            faces.append(face)
    return faces

# 얼굴과 비디오 프레임을 합성 아바타로 통합하는 함수
def extract_faces_from_folder(frame_dir):
    # 프레임에서 얼굴 추출하여 배열에 저장
    frame_faces = []
    for frame_file in get_files_with_extensions(frame_dir, ['.jpg', '.png']):
        print(frame_file)
        frame_path = os.path.join(frame_dir, frame_file)
        frame_image = extract_face(frame_path)   # extract_face 함수 재사용
        if frame_image is not None :
            frame_faces.append(frame_image)
    return frame_faces
    
def merge_faces(image_faces, frame_faces):
    return image_faces + frame_faces

def store_faces_in_a_vertical_image(all_images):
    if all_images:
        composite_avatar = np.vstack(all_images)   # 이미지를 수직으로 쌓기
        avatar_image = Image.fromarray(composite_avatar)
        avatar_image.save('final_avatar.jpg')
        avatar_image.show()
    else :
        print("이미지나 프레임에서 유효한 얼굴을 찾을 수 없습니다.")

# 얼굴을 추출할 이미지 경로
image_paths = ['image1.png', 'image2.png', 'image3.png']
image_faces = extract_faces_from_arr(image_paths)

# 동영상 추출 프레임 이미지 폴더 경로
folder_path = 'frames'
frame_faces = extract_faces_from_folder(folder_path)

all_faces = merge_faces(image_faces, frame_faces)
store_faces_in_a_vertical_image(all_faces)

그럼 한번 실행을 해볼까요? 실행하기 전에 필요한 파일은 얼굴로 사용할 이미지들과, 동영상에서 추출한 프레임이미지들이 들어있는 폴더입니다.

스크립트를 실행합니다.

python create_avatar.py

에러가 중간에 났는데요. 동영상의 얼굴 퀄리티가 살짝 다른 키프레임을 캡쳐했을때 이런 에러가 나는것 같습니다. 저는 1번 프레임이미지에서 추출한 얼굴이 다른 얼굴과 크기가 살짝 달라서 세로로 갖다 붙이는데 문제가 있다고 아래와 같은 에러가 났어요. 그래서 1번 프레임이미지 지우고 다시 실행했더니 되네요. 한번 해보는거니까 그냥 넘어갈게요.

  File "/Users/me/git/python/ai_avatar/3_create_avatar/create_avatar.py", line 66, in <module>
    create_composite_avatar(faces, output_frame_dir)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/me/git/python/ai_avatar/3_create_avatar/create_avatar.py", line 47, in create_composite_avatar
    composite_avatar = np.vstack(all_images)   # 이미지를 수직으로 쌓기
  File "/Users/me/git/sol1000.com/cms/.venv/lib/python3.13/site-packages/numpy/_core/shape_base.py", line 291, in vstack
    return _nx.concatenate(arrs, 0, dtype=dtype, casting=casting)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: all the input array dimensions except for the concatenation axis must match exactly, but along dimension 1, the array at index 0 has size 447 and the array at index 8 has size 536

결과는 다음과 같이 이미지들과 동영상 프레임에서 얼굴만 따서 세로로 주르륵 붙여놓은 형태의 이미지를 만들어 줍니다.

다음 시간에는 이걸 이용해서 아바타를 움직이게 만드는 강좌를 이어서 하도록 하겠습니다.

References