Opencv 실시간 얼굴인식 - opencv silsigan eolgul-insig

참고 사이트 1

얼굴 검출

import numpy as np
import cv2
# Cascades 디렉토리의 haarcascade_frontalface_default.xml 파일을 Classifier로 사용
# faceCascade는 이미 학습 시켜놓은 XML 포멧이고, 이를 불러와서 변수에 저장함.
faceCascade = cv2.CascadeClassifier('D:\python\Cascade\haarcascade_frontalface_default.xml')

# 비디오의 setting을 준비함.
cap = cv2.VideoCapture(0) #0번이 내장카메라, 1번이 외장카메라
cap.set(3,1280) # set Width
cap.set(4,720) # set Height


while True:
    # video의 이미지를 읽어옴
    ret, img = cap.read()
    #img = cv2.flip(img, 1) # 상하반전
    # 이후 얼굴을 검출할 gray scale을 만듦
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #make grayscale
    faces = faceCascade.detectMultiScale( #이미지에서 얼굴을 검출
        gray, #grayscale로 이미지 변환한 원본.
        scaleFactor=1.2, #이미지 피라미드에 사용하는 scalefactor
        #scale 안에 들어가는 이미지의 크기가 1.2씩 증가 즉 scale size는 그대로
        # 이므로 이미지가 1/1.2 씩 줄어서 scale에 맞춰지는 것이다.
        minNeighbors=3, #최소 가질 수 있는 이웃으로 3~6사이의 값을 넣어야 detect가 더 잘된다고 한다.
        #Neighbor이 너무 크면 알맞게 detect한 rectangular도 지워버릴 수 있으며,
        #너무 작으면 얼굴이 아닌 여러개의 rectangular가 생길 수 있다.
        #만약 이 값이 0이면, scale이 움직일 때마다 얼굴을 검출해 내는 rectangular가 한 얼굴에
        #중복적으로 발생할 수 있게 된다.
        minSize=(20, 20) #검출하려는 이미지의 최소 사이즈로 이 크기보다 작은 object는 무시
        #maxSize도 당연히 있음.
    )
    for (x,y,w,h) in faces: #좌표 값과 rectangular의 width height를 받게 된다.
        #x,y값은 rectangular가 시작하는 지점의 좌표
        #원본 이미지에 얼굴의 위치를 표시하는 작업을 함.
        #for문을 돌리는 이유는 여러 개가 검출 될 수 있기 때문.
        cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,255),2)
        #다른 부분, 얼굴 안에 들어있는 눈과 입 등을 검출할 때 얼굴 안엣 검출하라는 의미로 이용되는 것
        roi_gray = gray[y:y+h, x:x+w] #눈,입을 검출할 때 이용
        roi_color = img[y:y+h, x:x+w] #눈,입등을 표시할 때 이용
    #영상에 img 값을 출력
    cv2.imshow('video',img) # video라는 이름으로 출력
    k = cv2.waitKey(1) & 0xff #time값이 0이면 무한 대기, waitKey는 키가 입력 받아 질때까지 기다리는 시간을 의미한다.
    #FF는 끝의 8bit만을 이용한다는 뜻으로 ASCII 코드의 0~255값만 이용하겠다는 의미로 해석됨. (NumLock을 켰을때 또한 )
    if k == 27: # press 'ESC' to quit # ESC를 누르면 종료
        break
cap.release() #비디오 끄기   (카메라 리소스 헤제)
cv2.destroyAllWindows()

대부분의 내용은 주석에 설명이 되어있음. 다음에 만들 얼굴 검출이나, dataset을 만드는 것도 Video는 같은 세팅을 요하게 되므로, 따로 주석으로 설명하지는 않을 것이다.

우리는 roi_gray, roi_color같은 경우 딱히 쓸 필요가 없다. 왜냐하면 눈과 입을 검출하지는 않을 것이기 때문이다.

detectMultiScale같은 경우 중요한 변수 몇 가지를 가지고 있는데, 대부분 주석에 설명이 되어있지만 참고자료를 통해서 추가 설명을 해보겠다.

detectMultiScaleparameter 설명 1, 설명 2

Scalefactor의 이미지 피라미드
Scalefactor가 크면 커질수록 스케일의 크기가 더욱 광범위하게 피라미드가 쌓이게 되면서 조그마한 이미지의 face를 찾기가 힘들어진다. 그러나, scale이 쌓이는 크기는 더 크기 때문에 빠르게 detection을 할 수 있게 된다.
1.05면 scale size를 5%씩 키우면서 이미지 피라미드를 쌓게 되는 것이다.

minNeighbor의 영향
minNeighbor가 줄어들면 그만큼 detect하는 face의 양이 많아진다. 즉, face를 찾는 조건이 쉬워진다는 의미로 해석하면 된다. minNeighbor가 커지면, 찾을 수 있는 face의 양은 줄어들지만 더 확실한 face를 찾을 수 있게 된다.

얼굴 검출 후 학습 데이터 생성

import numpy as np
import cv2

#데이터베이스에 올릴  이름과 id를 입력받음
user_name = input("Please write your name : ")
user_id = input("Please write your id : ")
detected_data = cv2.CascadeClassifier('D:\python\Cascade\haarcascade_frontalface_default.xml')

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

count = 0
interrupt_flag = 0 # ESC를 입력했을 경우에 flag가 활성화 됨
while True:
    ret, img = cap.read()
    #img = cv2.flip(img, 1) # 상하반전
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = detected_data.detectMultiScale(
        gray,
        scaleFactor=1.2,
        minNeighbors=9, #조금 더 확실하게 검출하기 위해 Neighbor 값을 증가시킴
        minSize=(20, 20)
    )
    for (x,y,w,h) in faces:
        cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,255),2)
        count+=1 # 총 100장의 사진을 찍게 되는데, 하나를 찍을 때마다 count가 늘어난다.
        #imwrite를 통해서 이미지를 저장한다. 이때 저장되는 이미지는 _로 나뉘어지며, grayscale 이미지가 저장된다.
        cv2.imwrite("userdata/User_"+str(user_id)+'_'+str(user_name)+'_'+str(count)+'.jpg', gray[y:y+h,x:x+w])
        cv2.imshow('image', img) #이미지가 찍힐때 마다 찍힌 사진이 출력되어 보여준다. video가 나오지 않고 이미지로 나온다.
    k = cv2.waitKey(50) & 0xff # waitkey안의 time을 늘리면 조금 더 느리게 찍힌다.
    if k == 27: # press 'ESC' to quit # ESC를 누르면 종료
        interrupt_flag = 1
        break
    elif count >= 100: #100장의 이미지를 모두 찍으면 종료한다.
        break

if interrupt_flag == 1:
    print("\nFinish by interrupt ESC.\n")
else :
    print("\nComplete to save data.\n")
cap.release()
cv2.destroyAllWindows()

이미 언급한 얼굴 인식과정과 거의 일치한다. 하지만, 여기에서는 동영상을 표현하는 것이 아닌, 인식한 이미지를 띄우게 된다.

주석에 대부분 설명을 해놨다. intid를 왜 추가하는지에 대한 의문이 생길 것 같아서 말하면, id를 추가해야 나중에 이미지를 읽을 때 해당 이미지를 array로 저장하기 편하고, recognizer에서도 intid를 이용하기 때문에, 이미지 파일에도 해당 이미지의 id를 저장하는 것이다.

얼굴 학습

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

path = 'userdata'
detector = cv2.CascadeClassifier("D:\python\Cascade\haarcascade_frontalface_default.xml")
recognizer = cv2.face.LBPHFaceRecognizer_create() #LBP알고리즘을 이용하기 위한 새 변수를 생성

def getImagesAndLabels(path):
    imagePaths = [os.path.join(path,file) for file in os.listdir(path)]#이미지 파일들을 안에 넣음
    faceSamples=[] #각 이미지의 얼굴 값을 array uint8 형태로 저장한것을 dictionary 형태로 저장
    ids = [] #여러개의 id값을 배열로 저장
    for imagePath in imagePaths: #이미지 파일을 하나씩 받아 옴
        PIL_img = Image.open(imagePath).convert('L') #image를 grayscale로 변환 시킨다고 함 (굳이..? 이미 되어있는데)
        img_numpy = np.array(PIL_img,'uint8') #np.array로 img 파일을 int형으로 변환시켜 저
        id = int(os.path.split(imagePath)[-1].split("_")[1])#파일의 id를 추출
        faces = detector.detectMultiScale(img_numpy)#다시 얼굴 이미지에서 또 얼굴을 추출 (얼굴의 크기를 알기 위함)
        for (x,y,w,h) in faces:
            faceSamples.append(img_numpy[y:y+h,x:x+w])#img를 int형으로 바꾼 sample들을 넣은 배열
            ids.append(id)#id값을 쭉 넣어서 배열로 만듦
    return faceSamples, ids
print("\nPlease wait for a second...")
faces,ids = getImagesAndLabels(path)
recognizer.train(faces, np.array(ids)) #LBP matrix를 만듦. 이에 대한 추가설명은 블로그

recognizer.write('trainer/trainer.yml') #만든 LBP matrix를 yml 파일 형태로 저장
print("\n {0} faces trained.\n".format(len(np.unique(ids))))#ids 배열의 개수만큼 훈련되었다고 표시함. 

faceRecognizer을 훈련시킬 때 우리는 LBP (local binary pattern) facerecognizer을 이용하게 된다.

Opencv 실시간 얼굴인식 - opencv silsigan eolgul-insig

이런식으로 해당 이미지에서 부분적인 int형의 데이터만을 가져와서 이진수로 바꿔주는 역할을 한다. 이런식으로 히스토그램을 생성하고, 이 데이터들을 훈련시켜주어 만약 이 데이터들과 비슷한 데이터 int값을 가진 이미지가 나타나면 이름을 띄워주는 기능을 만들게 되는 것이다.

LBP의 특징은 밝기가 달라져도 문제가 없다는 것이다.

Opencv 실시간 얼굴인식 - opencv silsigan eolgul-insig

또한 위 코드에서 convert('L')이라는 코드가 있는데, 이 코드는 이미지를 grayscale로 바꿔준다. 하지만 우리는 이러한 과정이 필요가 없기 때문에, 삭제해도 아무런 문제가 생기지 않는다.

만약 위 코드에서 PIL과 관련된 에러가 발생할 경우, pillow라는 라이브러리를 설치하시고, cv2.face.LBPH에서 에러가 발생할 경우, opencv-contrib.python을 설치하시면 됩니다.

얼굴 인식

import cv2
import numpy as np
import os

recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read('trainer/trainer.yml')
detector = cv2.CascadeClassifier("D:\python\Cascade\haarcascade_frontalface_default.xml")
font = cv2.FONT_HERSHEY_SIMPLEX #opencv에서 지원하는 font

id = 0

names = ['None','HwangDongJun','KimSuMin']
cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)

while True:
    ret, img = cap.read()
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    faces = detector.detectMultiScale(
        gray,
        scaleFactor = 1.2,
        minNeighbors = 5,
        minSize = (20, 20)
    )
    for (x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w,y+h), (255,255,255), 2)
        #predict에 대한 설명은 blog
        id, confidence = recognizer.predict(gray[y:y+h,x:x+w])
        if (confidence < 55):
            id = names[id]
            confidence = " {0}%".format(round(100 - confidence))
        else:
            id = "unknown"
            confidence = " {0}%".format(round(100 - confidence))
        #일치 확률과 이름을 화면에 출력
        cv2.putText(img, str(id), (x+5,y-5), font, 1, (0,255,0),2)
        cv2.putText(img, str(confidence), (x+5,y+h-5), font, 1, (0,255,0),2)

    cv2.imshow('camera',img)
    #최대한 자주 Key를 획득할 수 있도록 wait time을 줄임
    k = cv2.waitKey(1) & 0xff
    if k == 27:
        break

print("\nExisting Program.")
cap.release()
cv2.destroyAllWindows()

보면 confidence와 id라는 변수가 새롭게 생겨난 것을 볼 수 있다. id값은 각각의 이미지에 대한 id값을 받아오게 된다. 이때 predict라는 함수를 이용하게 된다.

참고 사이트

Opencv 실시간 얼굴인식 - opencv silsigan eolgul-insig

opencv에는 facerecognize하는 class를 여러 개 가지고 있는데, 이 클래스들은 모두 공통적인 FaceRecognize의 함수를 가지고 있다. 그중 하나가 predict라는 함수인데, 이는 이미 만들어진 yml 파일을 불러와서 만든 recognizer을 이용해서 받아온 이미지와 일치하는 값이 있는지 알아보고 id, confidencereturn 하게 된다. 다음과 같은 구조를 가진다.

Opencv 실시간 얼굴인식 - opencv silsigan eolgul-insig

보면 intlabeldoubleconfidencereturn한다는 것을 알 수 있다. confidence의 값은 작을 수록 얼굴이 일치한다는 것을 알 수 있으며, id는 그 비슷한 얼굴의 id값을 return 하게 되는 것이다.

따라서 confidence의 확률을 표현하기 위해서 round(반올림) 100%면 일치하게 하기 위해서 100 - confidence를 하게 되는 것이다. 정확도를 올리기 위해서 minNeighborscalefactor를 조정할 수도 있을 것이다.