Ex5-가장 닮은 얼굴 고르는 게임 만들기

안 형준·2021년 8월 2일
1

Aiffel/Archives

목록 보기
5/12
post-thumbnail

Ex5를 진행하다가, 어떤 얼굴이 가장 닮은 얼굴인지 고르는 게임이 있으면 재밌겠다는 생각이 들어서, 만들어보았습니다. 만들면서 개선한 점에 대해 중점으로 적어보겠습니다.

깃헙 링크

0. 준비물

  • 이름.jpg, 이름.png 등으로 저장된 인물사진을 구글 드라이브에 업로드
  • 얼굴 인식을 위한 라이브러리와 함수

깃헙 링크를 참고해주세요. 종합하여 거리 순으로 나열하기 부분까지의 내용입니다.

1. 설계 아이디어

  1. 원하는 이미지(타깃)로 게임을 진행하는 게임 객체를 만든다
  2. 이미지 파일 중에서 3장을 랜덤으로 뽑아, 그 중 가장 가깝거나, 이미지를 골라내는 것이 목표
  3. 가깝거나 먼 평가는 embedding vector 간의 L2 distance로 한다
  4. matplotlib으로 시각화한다
  5. 점수를 외부에서 마음대로 수정하면 안 되므로, set_something(), get_something()을 정의해본다
  6. 맞추기 어려운 문제의 경우 배점을 세게 하고, 맞추기 쉬운 문제는 배점을 약하게 한다

모든 목표를 달성하지 못 했지만, 이러한 아이디어로 진행했습니다.

2. 버전 1 - 아직 개선점이 많습니다

1. 도우미 함수

  • get_nearest_face_mod: 타깃과 가까운 순서대로 top개, 혹은 먼 순서대로 top 개의 (이름, 타깃과의 거리) 순서쌍을 갖는 리스트를 반환합니다. 먼 쪽을 반환하는지 가까운 쪽을 반환하는지는 키워드 인자 closest 로 제어합니다. (mod는 함수를 임의로 수정했다는 의미로 넣었습니다)
# 먼 이미지를 골라내기 위해 closest라는 키워드 인자를 추가한다. closest가 False이면 "먼" 이라는 문자열
# 과 key에 거리에 -1을 곱한 것이 기준이 되어 가장 먼 사람이 앞에 오게 된다.
def get_nearest_face_mod(name, top=5, *, closest=True):
    near_faces = []
    sort_name_func = get_sort_key_func(name)
    if closest:
        sorted_list = sorted(embedding_dict.keys(), key=lambda x: sort_name_func(x))
    else:
        sorted_list = sorted(embedding_dict.keys(), key=lambda x: -sort_name_func(x))
     
    for idx, name in enumerate(sorted_list, start=1):
        if idx > 1:
            near_faces.append((name, sort_name_func(name)))
        if idx > top: 
            return near_faces
  • plot_closest_mod:
    get_nearest_face_mod(타깃, top=10)이 반환하는 리스트leaderboard를 입력으로 받아, 순서대로 이미지를 표시합니다. x_tick 과 y_tick을 없애고, xlabel은 사람의 (이름, 거리)로 설정합니다. 제목을 추가합니다.
# x, y축 틱을 없애고, 이름을 표시합니다.
def plot_closest_mod(leaderboard, title):
    #Set figsize here
    fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(24,10))


    # flatten axes for easy iterating
    for i, ax in enumerate(axes.flatten()):
        ax.axes.xaxis.set_ticks([])
        ax.axes.yaxis.set_ticks([])
        ax.set_xlabel(leaderboard[i])
        image = mapping[leaderboard[i][0]]
        image_path = img.imread(os.path.join(dir_path, image),0)  # png
        ax.imshow(image_path)

    plt.suptitle(f"{title[0]}{title[1]} 상위 10명", fontweight="bold")
    plt.show()
    fig.tight_layout()

2. class GuessingGame():


풀입스쿨에서 배웠던 private member를 사용하여 게임의 중요한 정보를 감추어보았습니다.
파이썬 클래스에서 private 변수 및 함수 사용하기

  • attributes
    name: 타깃의 이름입니다. (ex. 장동건.jpg -> name=='장동건')
    rounds: 게임이 진행될 라운드입니다
    closest: Boolean으로, True라면 가까운 것을 찾는 게임으로, False라면 먼 것을 찾는 게임으로 초기화합니다
    self.filename: 타깃의 파일명입니다. 미리 정의해둔 mapping을 통해 접근합니다 (ex. '장동건.jpg')
    self.mode: closest의 값에 따라 "먼"과 "가까운"으로 정해집니다
  • private attributes
    self.__cand_list: 게임에 사용될 이미지 인덱스의 배열입니다. np.random.randint로 구현했습니다. (self.rounds, 3) 의 형태로, 매 라운드마다 하나(axis=0)씩 쓰입니다
    self.__rank: 타깃과 거리가 가까운(먼) 200위까지의 (이름, 거리) 순서쌍의 리스트입니다.
    self.__point: 현재 점수입니다
    self.__leaderboard: 타깃과 거리가 가까운(먼) 10위까지의 (이름, 거리) 순서쌍의 리스트입니다

  • methods
    set_val(): private variable을 초기화합니다
    get_val(): 현재 라운드에 사용될 (이름, 거리) 순서쌍의 리스트를 반환합니다
    cands도 누출되지 않도록 했어야 하는지 의문
    earn_point(): 점수를 초기화하고, 정답을 맞춘 경우 점수를 추가합니다
    play(): 게임을 시작합니다. 선택지를 그림으로 시각화하고, input으로 사용자로부터 받은 문자열과 정답을 비교합니다. 게임이 끝나면 plot_closest_mod를 실행합니다.

  • 개선할 점
    정답을 찾아내는 알고리즘에 오류가 있어 self.closest와 무관하게 가장 먼 사람을 정답으로 선정했습니다

    타깃이 객체 생성시에만 보여, 문제를 푸는것이 불편합니다. 타깃의 사진을 매 라운드 보여주면 좋겠습니다

    self.__point의 값을 반환하는 함수를 만드는 것이 좋겠습니다

    동일한 사람이 중복되어 나오는 경우가 있는데, 해결해야 할 것 같습니다

from time import sleep

class GuessingGame():
    def __init__(self, name, *, rounds=8, closest=True):
        self.__cand_list = None
        self.__rank = None
        self.__point = None

        self.closest = closest
        self.name = name
        self.filename = mapping[name]
        self.rounds = rounds
        mode_list = ["먼", "가까운"]
        self.mode = mode_list[closest]
        
        fig_0, ax_0 = plt.subplots(figsize=(4,5))
        ax_0.axes.xaxis.set_ticks([])
        ax_0.axes.yaxis.set_ticks([])
        plt.xlabel(name)

        
        image_path = img.imread(os.path.join(dir_path, self.filename),0)  # png
        ax_0.imshow(image_path)
        print(f"{name}{self.mode} 사람 맞추기 게임이 준비되었습니다. ({rounds} 라운드)")
        print("시작하시려면 .play()를 실행하세요")
        self.earn_point()

    def play(self):
        self.set_val()

        print(f"게임 시작")
        for i in range(1, self.rounds+1):
            fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(12,5))
            candidates = self.get_val(i)
            for j, ax in enumerate(axes.flatten()):
                ax.axes.xaxis.set_ticks([])
                ax.axes.yaxis.set_ticks([])
                ax.set_xlabel(f"{candidates[j][0]}")
                img_path = mapping[candidates[j][0]]
                image_path = img.imread(os.path.join(dir_path, img_path),0)  # png
                ax.imshow(image_path)
            plt.suptitle(f"라운드 {i}: 3명 중 가장 {self.name}{self.mode} 사람을 골라주세요.", fontweight="bold")
            plt.show()
            sleep(8)
            
            picked = input("정답이라고 생각되는 이름을 정확히 입력해주세요.")
            correct = sorted(candidates, key=lambda candidates: -candidates[1])[0][0]
            if picked == correct:
                print("정답입니다. 포인트 1점을 얻으셨습니다.")
                self.earn_point()
                print(f"현재 포인트: {self.__point}.")
            else: 
                print(f"오답입니다... 정답: {correct}")

        # 게임 끝
        print(f'게임이 끝났습니다. 최종 스코어는 {self.__point}점 입니다')
        plot_closest_mod(self.__leaderboard, (self.name, self.mode))
        
    def set_val(self):
        self.__cand_list = np.random.randint(1, 201 , (self.rounds,3))
        self.__rank = get_nearest_face_mod(name=self.name, top=200, closest=self.closest)
        self.__leaderboard = self.__rank[:10]

    def get_val(self, cur_round):
        cands = []
        for i in range(3):
            cands.append(self.__rank[self.__cand_list[cur_round-1][i]])
        return cands

    def earn_point(self):
        if self.__point == None:
            self.__point = 0
        else:
            self.__point += 1

버전 3

1. 도우미 함수


등수, 이름, 거리를 표시하도록 수정했습니다

# x, y축 틱을 없애고, 이름을 표시한다.

def plot_closest(leaderboard, title):
    # Set figsize here
    fig, axes = plt.subplots(nrows=2, ncols=5, figsize=(24,10))

    # flatten axes for easy iterating
    for i, ax in enumerate(axes.flatten()):
        ax.axes.xaxis.set_ticks([])
        ax.axes.yaxis.set_ticks([])
        ax.set_xlabel(f"{i+1}등: {leaderboard[i][0]},  거리: {leaderboard[i][1]}")
        image = mapping[leaderboard[i][0]]
        image_path = img.imread(os.path.join(dir_path, image),0)  # png
        ax.imshow(image_path)

    plt.suptitle(f"{title[0]}와(과) {title[1]} 상위 10명", fontweight="bold")
    plt.show()
    fig.tight_layout()

2. GuessingGameV3():

self.closest에 따라 정답이 제대로 골라지게 수정했습니다

우측에 타깃의 이미지가 표시되도록 수정했습니다

정답 입력 문구를 수정하고, 오답이더라도 현재 점수를 출력합니다

현재 점수를 반환하는 get_point()를 정의하여 사용했습니다

from time import sleep

#우측에 비교할 이미지를 표시한다.
#비복원추출로 변경한다.
#정답을 골라내는데 문제가 있어 수정했다.
class GuessingGameV3():
    def __init__(self, name, *, rounds=8, closest=True):
        self.__cand_list = None
        self.__rank = None
        self.__point = None
        self.__leaderboard = None

        self.closest = closest
        self.name = name
        self.filename = mapping[name]
        self.rounds = rounds
        mode_list = ["먼", "가까운"]
        self.mode = mode_list[closest]
        
        fig, ax = plt.subplots(figsize=(4,5))
        ax.axes.xaxis.set_ticks([])
        ax.axes.yaxis.set_ticks([])
        plt.xlabel(name)

        image_path = img.imread(os.path.join(dir_path, self.filename),0)  # png
        ax.imshow(image_path)
        print(f"{name}와(과) {self.mode} 사람 맞추기 게임이 준비되었습니다. ({rounds} 라운드)")
        print("시작하시려면 .play()를 실행하세요")
        self.earn_point()

    def play(self):
        self.set_val()

        print(f"게임 시작")
        for cur_round in range(1, self.rounds+1):
            self.__round_cand_list = None
            self.__correct = None

            self.__round_cand_list = self.get_val(cur_round)
            self.plot_candidates(cur_round)
            
            print("정답이라고 생각되는 이름을 정확히 입력해주세요.")
            picked = input("당신의 선택: --------->")
            
            self.__correct = self.get_correct_answer(self.closest, self.__round_cand_list)

            if picked == self.__correct:
                print("정답입니다. 포인트 1점을 얻으셨습니다.")
                self.earn_point()
                print(f"현재 포인트: {self.get_point()}.")
            else: 
                print(f"오답입니다... 정답: {self.__correct}")
                print(f"현재 포인트: {self.get_point()}.")

        # 게임 끝
        print(f'게임이 끝났습니다. 최종 스코어는 {self.get_point()}점 입니다')
        plot_closest(self.__leaderboard, (self.name, self.mode)) # 변경

    def get_correct_answer(self, closest, round_cand_list):
        if closest: return sorted(round_cand_list, key=lambda round_cand_list: round_cand_list[1])[0][0]
        else: return sorted(round_cand_list, key=lambda round_cand_list: -round_cand_list[1])[0][0]

    def plot_candidates(self, cur_round):
        fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(16,5))
        for j, ax in enumerate(axes.flatten()):
            ax.axes.xaxis.set_ticks([])
            ax.axes.yaxis.set_ticks([])
            if j != 3:
                ax.set_xlabel(f"{self.__round_cand_list[j][0]}")
                img_path = mapping[self.__round_cand_list[j][0]]
            else:
                ax.set_xlabel(f"{self.name}")
                img_path = self.filename
            image_path = img.imread(os.path.join(dir_path, img_path),0)  # png
            ax.imshow(image_path)
        plt.suptitle(f"라운드 {cur_round}:   3명 중 가장 {self.name}와(과) {self.mode} 사람을 골라주세요.", fontweight="bold")
        plt.show()
        sleep(4)
        
    def set_val(self):
        # self.__cand_list = np.random.randint(1, 201 , (self.rounds,3))
        self.__cand_list = np.random.choice(200, size=(self.rounds,3), replace=False )
        self.__rank = get_nearest_face_mod(name=self.name, top=200, closest=self.closest)
        self.__leaderboard = self.__rank[:10]

    def get_val(self, cur_round):
        cands = []
        for i in range(3):
            cands.append(self.__rank[self.__cand_list[cur_round-1][i]])
        return cands

    def earn_point(self):
        if self.__point == None:
            self.__point = 0
        else:
            self.__point += 1
    
    def get_point(self):
        return self.__point

profile
물리학과 졸업/ 인공지능 개발자로의 한 걸음

0개의 댓글