실시간 얼굴인식(Face Recognition) with Raspberry Pi 3






(Source : https://www.hackster.io/mjrobot/real-time-face-recognition-an-end-to-end-project-a10826)






이번 블로그 포스팅은 MJRoBot의 포스트를 정리하는 차원에서 작성됨.

영어를 한국어로 번역하는 과정에서 오역이 생길 수도 있고 나의 주관적인 의견이 들어가거나

생략된 부분이 있거나 정확하지 않은 정보가 포함될 수 있음.


먼저 MJRoBot의 튜토리얼을 보기를 권장함.



이미 SD카드에 라즈비안 OS가 설치되었다는 가정하에 시작한다.

(위의 튜토리얼에 따르면 라즈비안 Stretch에서 작업했으나 Jessie에서 작업해도 이상없음을 확인)



1. OpenCV 설치


운영체제를 설치하였고 필요한 물품(Pi Camera)이 준비되었다면 PyImageSearch를 운영하고 있는

Adrian Rosebrock의 OpenCV 설치 튜토리얼을 따라서 그대로 수행한다.

(4시간 이상의 상당한 시간 소요)




2. source ~/.profile 그리고 workon cv


Adrian은 새로 터미널을 시작할 때마다 source ~/.profile 명령어를 수행하여

시스템변수들이 정확하게 설정되었는지 확인하도록 장려한다.


이후 workon cv 명령어를 통해 가상환경으로 접속한다.





3. 라즈베리 파이에서 파이 카메라가 정상작동하는지 확인한다.


파일명은 자유롭게 해도 되지만 MJ는 simpleCamTest.py라고 명명함.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import cv2
cap = cv2.VideoCapture(0)
cap.set(3,640# set Width
cap.set(4,480# set Height
while(True):
    ret, frame = cap.read()
    frame = cv2.flip(frame, -1# Flip camera vertically
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    cv2.imshow('frame', frame)
    cv2.imshow('gray', gray)
    
    k = cv2.waitKey(30& 0xff
    if k == 27# press 'ESC' to quit
        break
cap.release()
cv2.destroyAllWindows()

cs


frame = cv2.flip(frame, -1)


위 코드는 카메라를 상하반전 시키는 코드로써 카메라가 가리키는

방향에 따라 불필요 할 수 있음.





4. 얼굴 검출(Face Detection)


※ 여기서부터는 Recognition(인식)과 Detection(검출)을 명확하게 구분해야 한다. 


얼굴을 인식(Recognition)하기 전에 video frame 상에서 얼굴이 어디있는지를 먼저 찾을 수

있도록 해야 한다. 얼굴 검출에 있어 가장 많이 쓰이는 방법은 

"Haar Cascade classifier"이다. 요즘에는 HOG와 같은 더 좋은 알고리즘이 

만들어져 Haar Cascade classifier이 덜 쓰이게 되었지만 

라즈베리파이상에서는 여전히 잘 작동한다.


다음 단계로 진행하기 전에 구글링을 통해 Haar Cascade classifier을 먼저

이해하는 것이 도움이 된다. (그닥 어려운 개념이 아님)


Haar Cascade classifier를 학습시키기 위해서는 수많은 Positive 이미지와

negative 이미지가 필요하지만 OpenCV에서는 기 학습된 모델을 제공하기 때문에

모델을 학습시킬 여건이 안되거나 데이터가 부족한 경우

우리가 직접 학습시킬 필요는 없이 OpenCV에서 제공하는 xml파일 형태의

classifier을 그대로 이용하면 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import cv2
faceCascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture(0)
cap.set(3,640# set Width
cap.set(4,480# set Height
while True:
    ret, img = cap.read()
    img = cv2.flip(img, -1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = faceCascade.detectMultiScale(
        gray,     
        scaleFactor=1.2,
        minNeighbors=5,     
        minSize=(2020)
    )
    for (x,y,w,h) in faces:
        cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]  
    cv2.imshow('video',img)
    k = cv2.waitKey(30& 0xff
    if k == 27# press 'ESC' to quit
        break
cap.release()
cv2.destroyAllWindows()
cs


위 코드를 faceDetection.py라는 이름으로 작성한다.


faceCascade = cv2.CascadeClassifier('Cascades/haarcascade_frontalface_default.xml')


위코드는 얼굴 전면부를 검출하는 기학습된 모델을 사용하는 것으로

faceDetection.py가 있는 디렉토리에 Cascades라는 이름의 디렉토리를 만들어

그 하위에 xml파일을 넣어주어야 한다.


이전 코드에 추가된 부분만 살펴보면


faces = faceCascade.detectMultiScale(
        gray,     
        scaleFactor=1.2,
        minNeighbors=5,     
        minSize=(2020)
    )


detectMultiScale()은 입력 이미지에서 얼굴을 검출하는 함수이다.


gray는 input 값으로 이전에 얻은 grayscale image입력 하는 것을 의미한다.

scaleFactor는 각 이미지 스케일마다 이미지 사이즈를 얼마나 줄일 것인지를 의미한다.

minNeighbors는 무슨말인지 잘 모르겠지만 직역해보면,

각 후보 rectangle이 얼마나 많은 neighbors를 가져야하는지를 명시하는 파라미터이다.


A higher number gives lower false positives.


숫자가 높을 수록 얼굴이 아닌 것이 얼굴로 잘못 인식 될 확률을 낮춰준다는 것인듯..


minSize는 얼굴로 간주될 최소 사각형 크기를 의미.


detectMultiScale()의 return값은 void이나 a list of rectangles 로 return된다.


즉, detectMultiScale()는 검출된 얼굴이미지(사각형)에 대한 x, y, width, height 로 구성된 list를 반환하는 듯.


(The detected objects are returned as a list of rectangles.")



for (x,y,w,h) in faces:


video frame상에 얼굴이 여러개 있으면 얼굴 각각에 대한 x,y,w,h 값을 가져온다.

얼굴이 하나라면 for문은 한 번만 수행된다.


cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)


video frame에 사각형을 표현하는 부분. 정확히 말하면 이 함수를 호출하면 frame에 사각형을 set하고

아래의 cv2.imshow('video',img)를 이용하여 화면에 보여주는 것이다.





============= 본격적인 단계의 시작 ==============


5. 데이터 수집 (Data Gathering)


(※openface로 face image data를 추출할 때는 haar_cascade로 추출한 것과 같은 수준으로 픽셀 값을 크게 할 것) 



MJRobot의 포스팅에 따라 필요한 디렉토리를 만들어주고 필요한 classifier을

디렉토리 밑에 위치시켜준다. 


01_face_dataset.py를 실행시켜 얻은 얼굴 데이터를 저장하기 위한 폴더를 만들어 준다.

mkdir dataset


다음은 01_face_dataset.py라는 이름의 Pi Camera를 이용하여 얼굴을 추출하여

dataset 폴더에 저장하는 소스코드이다. 이러한 사진 정보는 분류기를 학습시키는데 사용된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import cv2
import os
cam = cv2.VideoCapture(0)
cam.set(3640# set video width
cam.set(4480# set video height
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# For each person, enter one numeric face id
face_id = input('\n enter user id end press <return> ==>  ')
print("\n [INFO] Initializing face capture. Look the camera and wait ...")
# Initialize individual sampling face count
count = 0
while(True):
    ret, img = cam.read()
    img = cv2.flip(img, -1# flip video image vertically
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_detector.detectMultiScale(gray, 1.35)
    for (x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)     
        count += 1
        # Save the captured image into the datasets folder
        cv2.imwrite("dataset/User." + str(face_id) + '.' + str(count) + ".jpg", gray[y:y+h,x:x+w])
        cv2.imshow('image', img)
    k = cv2.waitKey(100& 0xff # Press 'ESC' for exiting video
    if k == 27:
        break
    elif count >= 30# Take 30 face sample and stop video
         break
# Do a bit of cleanup
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
cs


이전의 소스코드와 거의 비슷하다 !

추가된 부분만 살펴보면 다음과 같다.


8번줄 face_id = input('\n enter user id end press <return> ==>  ')


사람을 구분해 줄 숫자 id를 입력받는다.


cv2.imwrite("dataset/User." + str(face_id) + '.' + str(count) + ".jpg", gray[y:y+h,x:x+w])


소스코드를 돌려서 Pi Camera로 부터 얻은 이미지를 dataset폴더 내에 User.(얼굴 id).(count number).jpg로 저장한다.

1번 User에 대해 현재 찍힌 사진에 7번째 사진이라면

User.1.7.jpg 와 같이 저장된다.


count가 30이상이면 while문을 빠져나간다.





6. 모델 트레이닝시키기 (Trainer 단계)


앞의 코드를 실행시켜 얻은 User별 이미지 데이터를 이용하여 Recognizer을 훈련시키는 단계이다.

MJRobot의 지시에 따라 trainined된 data를 저장할 폴더를 만들고 다음 코드를 실행시킨다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import cv2
import numpy as np
from PIL import Image
import os
# Path for face image database
path = 'dataset'
recognizer = cv2.face.LBPHFaceRecognizer_create()
detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml");
# function to get the images and label data
def getImagesAndLabels(path):
    imagePaths = [os.path.join(path,f) for f in os.listdir(path)]     
    faceSamples=[]
    ids = []
    for imagePath in imagePaths:
        PIL_img = Image.open(imagePath).convert('L'# convert it to grayscale
        img_numpy = np.array(PIL_img,'uint8')
        id = int(os.path.split(imagePath)[-1].split(".")[1])
        faces = detector.detectMultiScale(img_numpy)
        for (x,y,w,h) in faces:
            faceSamples.append(img_numpy[y:y+h,x:x+w])
            ids.append(id)
    return faceSamples,ids
print ("\n [INFO] Training faces. It will take a few seconds. Wait ...")
faces,ids = getImagesAndLabels(path)
recognizer.train(faces, np.array(ids))
# Save the model into trainer/trainer.yml
recognizer.write('trainer/trainer.yml'# recognizer.save() worked on Mac, but not on Pi
# Print the numer of faces trained and end program
print("\n [INFO] {0} faces trained. Exiting Program".format(len(np.unique(ids))))
cs


recognizer는 얼굴 이미지를 숫자로 변환한 값과 그에 해당하는 id만을 필요로 한다. 이에 여기서는

getImagesAndLabels라는 함수를 이용하여 recognizer를 학습시키기 전 dataset폴더 내의 사진들을 전처리 한다.


간단하게 살펴보면 


recognizer = cv2.face.LBPHFaceRecognizer_create()


OpenCV에서 제공하는 LBPHFaceRecognizer을 recognizer로 사용한다.


imagePaths = [os.path.join(path,f) for f in os.listdir(path)]


os.listdir(path)을 사용하면 path로 들어오는 dataset 폴더 내의 파일명을 list로 만든다.

하지만 여기서 얻어지는 list는 전체경로가 아닌 파일명만 포함하므로 전체 경로를 포함한 

파일명을 구하기 위해서는 os.path.join으로 경로와 파일명을 join시켜준다.


 id = int(os.path.split(imagePath)[-1].split(".")[1])


imagePath[-1]로 파일명만을 추출한뒤 split(".")[1]을 이용하여 파일명을

.으로 구분하여 리스트화한 뒤 1번 index에 해당하는 값을 뽑아내면 user id를 추출할 수 있다.

   


결과적으로 trainer폴더 내부에 trainer.yml이라는 trained된 model이 저장된다.





7. 얼굴 인식하기 (Recognizer)


자세한 과정은 MJRoBot의 포스팅을 참조하기로 한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import cv2
import numpy as np
import os 
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read('trainer/trainer.yml')
cascadePath = "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascadePath);
font = cv2.FONT_HERSHEY_SIMPLEX
#iniciate id counter
id = 0
# names related to ids: example ==> Marcelo: id=1,  etc
names = ['None''Marcelo''Paula''Ilza''Z''W'
# Initialize and start realtime video capture
cam = cv2.VideoCapture(0)
cam.set(3640# set video widht
cam.set(4480# set video height
# Define min window size to be recognized as a face
minW = 0.1*cam.get(3)
minH = 0.1*cam.get(4)
while True:
    ret, img =cam.read()
    img = cv2.flip(img, -1# Flip vertically
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    faces = faceCascade.detectMultiScale( 
        gray,
        scaleFactor = 1.2,
        minNeighbors = 5,
        minSize = (int(minW), int(minH)),
       )
    for(x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
        id, confidence = recognizer.predict(gray[y:y+h,x:x+w])
        # Check if confidence is less them 100 ==> "0" is perfect match 
        if (confidence < 100):
            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, (255,255,255), 2)
        cv2.putText(img, str(confidence), (x+5,y+h-5), font, 1, (255,255,0), 1)  
    
    cv2.imshow('camera',img) 
    k = cv2.waitKey(10& 0xff # Press 'ESC' for exiting video
    if k == 27:
        break
# Do a bit of cleanup
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
cs


위 코드는 face_detection 단계에서 사용했던 코드를 바탕으로 실시간으로 video frame에서 얼굴을 검출하여

.yml 에 학습된 recognizer를 이용하여 얼굴을 매칭해보는(인식하는) 코드이다. 


코드를 간단히 분석해보면,


names = ['None''Marcelo''Paula''Ilza''Z''W']


위 List에 1번 인덱스부터 이전에 Data Gathering단계에서 얻은 사진과 매칭되는

인물들의 실제 이름을 써주면 된다.


cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)


video frame상의 얼굴에 사각형 박스를 그려주기 위함.


cv2.rectangle(img, start, end, color, thickness)


- img : 그림을 그릴 이미지            

- start : 사각형 시작 좌표(왼쪽 위)   

- end : 사각형 끝 좌표(오른쪽 아래) 

- color : 사각형 frame 색(BGR color)

- thickness : 선 두께 (pixel 단위)     


id, confidence = recognizer.predict(gray[y:y+h,x:x+w])


입력 이미지를 gray로 변환한 것에서 얼굴을 검출한 rectangle 정보를 이용하여 

기존에 만든 trainer.yml을 통과시켜 분류(인식)하여 해당하는 id값과 그 때의

confidence값을 반환한다. (confidence = 0이면 동일인이다.)






처음 제대로 만들어본 포스팅이라 표현이 어색한 것도 많고 전체적으로 이해가 안될 수도 있습니다.

궁금한 점이나 어색한 부분 또는 잘못된 부분은 언제든지 댓글 달아주세요 :)




※ Color Scripter로 작성한 소스코드를 가운데 정렬하려면 

우측 상단 HTML 에서 해당 Table 태그의 margin 값을 auto로 설정하면 됨.

더보기

댓글,

jayharvey

머신러닝/딥러닝 관련 글을 포스팅할 예정입니다 :)