정보통신공학전공/캡스톤 디자인

[Python] 강아지 얼굴 인식하고 구분하기 - 1

융J 2024. 4. 7. 18:24

이번에는 강아지 얼굴은 인식하고 구분하는 코드를 만들어 보겠다.
원래 자동 라벨링 기능을 공부했기 때문에 이를 이용해서 지난번 캡스톤 때 공부한 Yolo를 사용할까 했으나, 같은 종을 인식시키면 그리 높지 않은 정확도를 보였고, 무엇보다 생성된 pt파일을 내가 학습시키고 직접 첨부하면 모를까..  자동으로 적용시킬 방법을 찾지 못했다. 또한 정확도를 높이기 위해서는 많은 custom data가 필요한데.. 사용자에게 강아지 사진을 100장을 넣으라고는 할 수 없지 않겠는가...
그래서 적은 data로 real time으로 학습하고 인지까지 해줄 수 있는 방법을 찾다가 강아지 얼굴에 landmark를 생성하고 이 특징을 이용해 비교하는 방법을 사용하기로 했다. 일단 비교는 우리 집 코코 사진과 송이 사진으로 진행했는데, 만약 가능하다면, 같은 종 같은 나이대의 같은 색상의 강아지 사진을 이용해서 진행해볼 예정이다.
아래는 완성된 코드들을 올려둔 github이다.
https://github.com/yunjiJ00/dog_face_recognition/tree/main

 

GitHub - yunjiJ00/dog_face_recognition

Contribute to yunjiJ00/dog_face_recognition development by creating an account on GitHub.

github.com

 
이 링크를 참고해서 만들었고, 나한테 맞게 수정을 진행했다.
 
우선 참고자료와의 비교점을 얘기하자면 나는 class로 구분해서 파일을 나누었다. 참고 코드를 보면 처음 사용하는 find_dog_face 함수가 다른 기능에서는 전혀 역할을 하지 않는 것을 확인했고, IoT를 진행하는 나한테는 한정된 RAM과 GPU로 진행해야 하기에, 필요없는 기능을 밖으로 뺴버렸다.
그리고 나는 이미 기억했던 데이터를 numpy 파일로 저장하고, 판단 부분에서 다시 불러와 수행 시간과 GPU사용량을 최대로 줄였다.
아래는 내가 만든 find_dog_face.py이다.

import cv2
import dlib
import imutils
from imutils import face_utils
import numpy as np
import matplotlib.pyplot as plt
import face_recognition
import time

face_landmark_detector_path = 'dogHeadDetector.dat'
face_landmark_predictor_path = 'landmarkDetector.dat'

detector = dlib.cnn_face_detection_model_v1(face_landmark_detector_path)
predictor = dlib.shape_predictor(face_landmark_predictor_path)

target_path = 'images/song_coco10.jpg'

class Find_dog_face:
    def __init__(self):
        pass
    
    def finding(self, org_image, debug=False):
        image = self.resize_image(org_image, target_width=200)
        gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        dets = detector(gray_image, 1)
        print('Found {} faces.'.format(len(dets)))
        face_images = []
        for (i, det) in enumerate(dets):
            shape = predictor(gray_image, det.rect)
            shape = face_utils.shape_to_np(shape)
            (x, y, w, h) = face_utils.rect_to_bb(det.rect)
            face_images.append(image[y:y+h, x:x+w].copy())

            cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
            cv2.putText(image, "Face #{}".format(i + 1), (x - 10, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

            if debug:
                for (i, (x, y)) in enumerate(shape):
                    cv2.circle(image, (x, y), int(image.shape[1]/250), (0, 0, 255), -1)
                    
        self.plt_imshow(["Original", "Find Faces"], [image, image], figsize=(16,10), result_name='find_face.jpg')
        return face_images
    
    def resize_image(self, image_path, target_width=200):
        img = cv2.imread(image_path)
        height, width = img.shape[:2]
        new_height = int((target_width / width) * height)
        resized_img = cv2.resize(img, (target_width, new_height), interpolation=cv2.INTER_AREA)
        return resized_img

    def plt_imshow(self, title='image', img=None, figsize=(8 ,5), result_name='result.jpg'):
        plt.figure(figsize=figsize)

        if type(img) == list:
            if type(title) == list:
                titles = title
            else:
                titles = []

                for i in range(len(img)):
                    titles.append(title)

            for i in range(len(img)):
                plt.subplot(1, len(img), i + 1), plt.imshow(cv2.cvtColor(img[i], cv2.COLOR_BGR2RGB))
                plt.title(titles[i])
                plt.xticks([]), plt.yticks([])

            plt.show()
        else:
            plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
            plt.title(title)
            plt.xticks([]), plt.yticks([])
            plt.show()

def main():
    finding = Find_dog_face()
    finding.finding(target_path, debug=True)

if __name__ == '__main__':
    main()

위 코드를 실행하면 아래와 같이 나온다.

 
빨간 점이 찾은 landmark이다. 다른 참고자료를 찾아보면, 저 랜드마크를 이용해서 스티커나 이모티콘을 생성하기도 하는 것을 확인할 수 있었다. 이 부분은 나중에 심심하면 진행하기로 하자.
 
두번째로는 내가 사진과 이름을 한번씩 넣어주면, 이 특징점을 찾아 npy파일로 저장하는 add_dog_face.py이다.

import cv2
import dlib
import imutils
from imutils import face_utils
import numpy as np
import matplotlib.pyplot as plt
import face_recognition
from find_dog_face import Find_dog_face
import time

face_landmark_detector_path = 'dogHeadDetector.dat'
face_landmark_predictor_path = 'landmarkDetector.dat'

detector = dlib.cnn_face_detection_model_v1(face_landmark_detector_path)
predictor = dlib.shape_predictor(face_landmark_predictor_path)

known_face = [("images/coco1.jpg", "coco"), ("images/song5.jpg", "song")]

class Add_dog_face:
    def __init__(self):
        self.known_face_encodings = []   
        self.known_face_names = []
    
    def add_known_face(self, known_face):
        Finding = Find_dog_face()
        target_width = 200
        for face_image_path, name in known_face:   
            image = Finding.resize_image(face_image_path, target_width)
            dets_locations = face_locations(image, 1)
            face_encodings = face_recognition.face_encodings(image, dets_locations)

            for face_encoding, location in zip(face_encodings, dets_locations):
                detected_face_image = draw_label(image, location, name)
                self.known_face_encodings.append(face_encoding)
                self.known_face_names.append(name)
                
            Finding.plt_imshow(["Input Image", "Detected Face"], [image, detected_face_image], result_name='known_face.jpg')
        
        np.save('known_faces.npy', self.known_face_encodings)
        np.save('known_names.npy', self.known_face_names)

def _trim_css_to_bounds(css, image_shape):
    return max(css[0], 0), min(css[1], image_shape[1]), min(css[2], image_shape[0]), max(css[3], 0)

def _rect_to_css(rect):
    return rect.top(), rect.right(), rect.bottom(), rect.left()

def _raw_face_locations(img, number_of_times_to_upsample=1):
    return detector(img, number_of_times_to_upsample)

def face_locations(img, number_of_times_to_upsample=1):
    return [_trim_css_to_bounds(_rect_to_css(face.rect), img.shape) for face in _raw_face_locations(img, number_of_times_to_upsample)]    

def draw_label(input_image, coordinates, label):
    image = input_image.copy()
    (top, right, bottom, left) = coordinates
    cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 5)
    cv2.putText(image, label, (left - 10, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 3)
    return image

def main():
    # finding = Find_dog_face(target_image)
    adding = Add_dog_face()
    # finding.finding(debug=True)
    adding.add_known_face(known_face)
    
if __name__ == '__main__':
    main()

이러면 detect된 이미지들을 보여주고, numpy파일로 저장해서, 나중에 구분할 때 똑같은 작업을 또 해야 하는 것을 막아준다. GPU가 연약한 나에게 꼭 필요한 작업이었다.
위 파일을 실행하면 아래와 같이 나온다.

... 폰트 굵기 조정이 좀 필요할 것 같기는 하다..
 
다음은 이렇게 저장된 numpy파일을 불러와서 새 이미지에서 구분해 내는 dog_facial_recognition.py이다.

import cv2
import dlib
import imutils
from imutils import face_utils
import numpy as np
import matplotlib.pyplot as plt
from find_dog_face import Find_dog_face
from add_dog_face import Add_dog_face
import face_recognition
import time

face_landmark_detector_path = 'dogHeadDetector.dat'
face_landmark_predictor_path = 'landmarkDetector.dat'

detector = dlib.cnn_face_detection_model_v1(face_landmark_detector_path)
predictor = dlib.shape_predictor(face_landmark_predictor_path)

image_path = 'images/song_coco10.jpg'

class Dog_facial_recognition:
    def __init__(self):
        self.known_face_encodings = np.load('known_faces.npy')
        self.known_face_names = np.load('known_names.npy')
    
    def detection(self, image_path, size=None):
        finding = Find_dog_face()
        image = finding.resize_image(image_path, target_width=200)
        dets_locations = face_locations(image)
        face_encodings = face_recognition.face_encodings(image, dets_locations)
        
        face_names = []

        for face_encoding in face_encodings:
            matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding, tolerance=0.4)
            name = "Unknown"

            face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
            best_match_index = np.argmin(face_distances)

            if matches[best_match_index]:
                name = self.known_face_names[best_match_index]

            face_names.append(name)

        for (top, right, bottom, left), name in zip(dets_locations, face_names):
            if name != "Unknown":
                color = (0, 255, 0)
            else:
                color = (0, 0, 255)

            cv2.rectangle(image, (left, top), (right, bottom), color, 1)
            cv2.rectangle(image, (left, bottom - 10), (right, bottom), color, cv2.FILLED)
            font = cv2.FONT_HERSHEY_DUPLEX
            cv2.putText(image, name, (left + 3, bottom - 3), font, 0.5, (0, 0, 0), 1)

        finding.plt_imshow("Output", image, figsize=(24, 15), result_name='output.jpg')
        
def _trim_css_to_bounds(css, image_shape):
    return max(css[0], 0), min(css[1], image_shape[1]), min(css[2], image_shape[0]), max(css[3], 0)

def _rect_to_css(rect):
    return rect.top(), rect.right(), rect.bottom(), rect.left()

def _raw_face_locations(img, number_of_times_to_upsample=1):
    return detector(img, number_of_times_to_upsample)

def face_locations(img, number_of_times_to_upsample=1):
    return [_trim_css_to_bounds(_rect_to_css(face.rect), img.shape) for face in _raw_face_locations(img, number_of_times_to_upsample)]

def main():
    detect = Dog_facial_recognition()
    detect.detection(image_path)

if __name__ == '__main__':
    main()

결과가 아주 만족스럽게 나오는 것을 확인할 수 있었다.

이미지를 이용해 구분해 내는 것은 아주 잘 작동하는 것을 볼 수 있었다.
모든 수행을 다했을 때, RAM과 GPU사용량은 아래와 같았다.

그러나 여기에도 단점은 있었는데, 사람 얼굴 인식보다 훨씬 적은 랜드마크 사용 때문에, 각도나 표정에 따라 구분을 못하는 경우가 있었다. 특히 옆모습인 경우에는 아예 강아지라고 인식 자체를 못하는 모습도 보았다.

예를 들면 송이는 아직 어려서 표정이 많이 없어서 항상 송이라고 판단을 했는데, 코코의 경우 웃는 얼굴과 같이 표정이 있는 얼굴은 코코라고 판단하지 못하고 Unkown을 return하기도 했다. 
 
다른 딥러닝 모델들에 비해 학습 데이터가 하나여도 된다는 장점이 있는 만큼, 단점도 확실한 듯 했다.
 
이미지를 구분해 내는 것은 여기서 마치고 다음에는 영상에서 구분해 내는 것을 진행해보도록 하겠다.
곧 시험기간이라 아마 시험 기간 후에 포스팅을 하지 않을까 싶다.