귀퉁이 서재

컴퓨터 비전 - 1. 하르 캐스케이드 얼굴 검출 (Haar Cascade Face Detection) 본문

컴퓨터 비전

컴퓨터 비전 - 1. 하르 캐스케이드 얼굴 검출 (Haar Cascade Face Detection)

Baek Kyun Shin 2023. 3. 3. 00:08

하르 캐스케이드는 'Rapid Object Detection using a Boosted Cascade of Simple Features' 논문(2001년 발표)에서 제안한 객체 검출기입니다. 상세한 이론은 논문 리뷰 - 캐스케이드 검출기 (Cascade Detector) 톺아보기 게시글에 설명해놨습니다. 궁금하신 분은 참고해보세요. 여기서는 OpenCV 라이브러리를 활용해 캐스케이드 검출기로 얼굴을 검출하는 실습을 해보겠습니다. 구글 코랩 환경에서 구현했습니다. 아래 코드는 모두 구글 코랩 바탕이므로, 다른 에디터에서 작업하신다면 적당히 가감하시면 되겠습니다. 

코드 링크 : https://github.com/BaekKyunShin/Computer-Vision-Basic/blob/main/Project1-Face_Detection/Haar_Cascade_Face_Detection.ipynb


1. 구글 드라이브 마운트

가장 먼저 구글 드라이브를 마운트를 합니다. 구글 드라이브에 저장한 이미지와 prebuilt xml을 불러오기 위해서입니다. 이와 관련해서는 뒤에서 설명하겠습니다.

from google.colab import drive
drive.mount('/content/drive')

2. 이미지 불러오기

다음으로 이미지를 불러올 겁니다. 그에 앞서 구글 드라이브에 'colab/Computer-Vision-Course/Data/Images' 디렉터리를 만듭니다. 그런 다음 이 디렉터리 아래 이곳에서 다운로드한 이미지를 저장해둡니다. 이미지 이름은 people.jpg로 해두고요. 그다음 아래 코드로 해당 이미지를 불러옵니다.

import cv2

# image source : https://unsplash.com/ko/%EC%82%AC%EC%A7%84/hOF1bWoet_Q
image = cv2.imread('/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Images/people.jpg')

3. 이미지 출력

cv2_imshow() 메서드를 활용해 불러온 이미지를 출력해보겠습니다.

from google.colab.patches import cv2_imshow

cv2_imshow(image)

image.shape를 실행해보면 (756, 1134, 3)이 출력됩니다. 높이가 756 픽셀, 너비가 1134 픽셀, 채널이 3개라는 말입니다. 색깔이 있는 이미지라면 채널은 R,G,B로 3개입니다. 이미지 크기가 조금 커서 줄여보겠습니다. OpenCV의 resize() 메서드를 활용하면 됩니다. 첫 번째 파라미터에는 이미지 변수(image)를 전달하고, 두 번째 파라미터에는 바꿀 이미지 크기를 전달합니다. 주의할 점이 있습니다. 이미지 크기를 (너비, 높이) 순서로 전달해야 한다는 점입니다. image.shape를 실행할 때 출력된 순서와는 다르죠?

image_resized = cv2.resize(image, (755, 500))

이미지 크기가 조정됐습니다. 다시 image_resized.shape를 실행해보죠. (500, 755, 3)이 출력됩니다. 크기가 잘 조정됐죠?

4. 하르 캐스케이드 검출기로 얼굴 검출하기

이제는 하르 캐스케이드 검출기로 얼굴을 검출해볼 차례입니다. 그런데 검출기를 활용하려면 검출기를 먼저 훈련해야 합니다. 처음부터 훈련하려면 시간도 오래 걸리고, 이미지 데이터도 많이 필요하겠죠? 그렇기 때문에 사전 훈련된 검출기들이 이미 있습니다. opencv/data/haarcascades/ 깃헙에서 사전 훈련된 검출기 xml 파일을 제공하니 여기서 다운로드하시면 됩니다.

이번 실습 때 사용해볼 사전 훈련 xml은 전면 얼굴 기본 검출기 xml입니다. 이곳에서 전면 얼굴 기본 검출기 xml 파일을 다운로드해 'colab/Computer-Vision-Course/Data/Weights/Cascades' 디렉터리 아래 저장하세요. cv2.CascadeClassifier() 메서드에 해당 xml을 전달하면 사전 훈련된 얼굴 검출기를 불러옵니다.

# Load prebuilt cascade classifier for detecting fontalfaces
cascade_face_detector = cv2.CascadeClassifier('/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Weights/Cascades/haarcascade_frontalface_default.xml')

cascade_face_detector에는 사전 훈련된 얼굴 검출기가 저장됐습니다. 이 검출기를 활용해 이미지에서 얼굴을 검출해보겠습니다. 검출할 땐 detectMultiScale() 메서드를 이용하면 됩니다.

face_detections = cascade_face_detector.detectMultiScale(image_resized)

face_detections를 출력해볼까요? 다음과 같이 출력됩니다.

array([[149,  44, 102, 102],
             [205, 122, 150, 150],
             [324, 156,  75,  75],
             [230, 173,  95,  95],
             [507, 217,  97,  97],
             [388, 229,  92,  92]], dtype=int32)

행이 6개, 열이 4개입니다. 행이 6개라는 건 검출된 얼굴이 6개라는 뜻입니다. 첫 번째 열은 검출된 얼굴을 둘러싼 경계 박스(bounding-box)의 왼쪽 상단 모서리의 x 좌표, 두 번째 열은 검출된 얼굴을 둘러싼 경계 박스의 왼쪽 상단 모서리의 y 좌표입니다. 세 번째/네 번째 열은 각각 경계 박스의 너비와 높이입니다.

이 정보를 활용해 이미지에 경계 박스를 그려보겠습니다. cv2.rectangle()이라는 메서드로 이미지에 사각형을 그릴 수 있습니다. 첫 번째 파라미터에 이미지를, 두 번째 파라미터에 사각형 좌상단 x, y 좌표를, 세 번째 파라미터에 사각형 우하단 x, y 좌표를 전달하면 됩니다. 앞서 face_detections에서 첫 번째/두 번째 열이 경계 박스 좌상단 x, y 좌표라고 했습니다. 세 번째/네 번째 열은 경계 박스의 너비와 높이라고 했고요. 그러면 경계 박스의 우하단 x, y 좌표는 각각 (좌상단 x 좌표 + 경계 박스 너비)와 (좌상단 y 좌표 + 경계 박스 높이)입니다.

이미지 좌표는 좌상단이 (0, 0)이고 x 값이 커지면 오른쪽으로, y 값이 커지면 아래쪽으로 이동합니다.

이 원리를 생각하며 경계 박스를 그려보죠. 참고로, cv2.rectangle() 메서드의 네 번째 파라미터 (0, 255, 0)은 사각형 색상의 RGB 값을, 다섯 번째 파라미터 2는 사각형 굵기를 뜻합니다.

for (x, y, w, h) in face_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2_imshow(image_resized)

캐스케이드 검출기로 검출한 얼굴입니다. 실제로는 5명이 있는데, 경계 박스는 6개를 그렸네요. 한 개는 잘못 그린 거죠. 아무래도 캐스케이드 검출기가 20년 전에 나온 알고리즘이라 성능이 썩 좋진 않습니다.

개선할 방법은 있습니다. 캐스케이드 검출기의 파라미터를 이용하는 방법이에요. scaleFactor와 minNeighbors 파라미터가 있습니다. 하나씩 알아보죠. 먼저 scaleFactor 파라미터부터 알아봅시다.

scaleFactor 파라미터는 이미지 스케일과 관련 있는 파라미터입니다. 이미지에서 먼 거리에 있는 얼굴은 작게 보이고, 가까이 있는 얼굴은 크게 보이겠죠? 작은 얼굴은 감지하기가 어렵습니다. 그래서 scaleFactor 파라미터를 조정해서 크기를 조정하며 얼굴을 감지할 수 있습니다. 큰 얼굴은 작게, 작은 얼굴을 크게 조정하면서 감지하는 겁니다. scaleFactor의 기본값은 1.1인데, 이보다 작으면 더 많은 검출을 합니다. 클수록 검출하는 개수가 적어지고요. 가령, scaleFactor=10으로 크게 설정하면 아무런 얼굴도 검출하지 못할 겁니다. 1.03을 전달해서 어떻게 검출하는지 볼까요?

# Reload the resized image since the bounding-box is already drawn in the resized image.
image_resized = cv2.resize(image, (755, 500))

face_detections = cascade_face_detector.detectMultiScale(image_resized, scaleFactor=1.03)

for (x, y, w, h) in face_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2_imshow(image_resized)

검출한 게 훨씬 많아졌습니다. 잘못된 검출들이 많아진 것이죠. false positive(거짓 양성 : 참이 아닌데 참이라고 판단한 오류)가 많아진 겁니다. 그럼 scaleFactor를 2로 키워보면 어떨까요?

image_resized = cv2.resize(image, (755, 500))

face_detections = cascade_face_detector.detectMultiScale(image_resized, scaleFactor=2)

for (x, y, w, h) in face_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2_imshow(image_resized)

false positive는 한 개도 없지만, 검출하지 못한 얼굴도 2개 있네요. 그래서 적절한 값을 설정해주는 게 중요합니다. 

다음으로는 minNeighbors 파라미터를 살펴볼게요. 캐스케이드 검출기로 검출을 하면 얼굴 주변에 여러 개의 후보 경계 박스(candidate bounding boxes)를 생성합니다. 여러 후보 경계 박스 가운데 가장 얼굴을 잘 둘러싸는 경계 박스를 최종적으로 선택합니다. minNeighbors 파라미터는 최종 경계 박스를 선택하기 위해 얼굴 주변에 존재해야 하는 최소 후보 경계 박스 개수입니다. 만약에 minNeighbors=5라면 한 얼굴에 최소한 5개의 후보 경계 박스가 있어야 해당 얼굴을 검출합니다. 

scaleFactor=1.1, minNeighbors=4로 전달해서 검출해보겠습니다.

image_resized = cv2.resize(image, (755, 500))

face_detections = cascade_face_detector.detectMultiScale(image_resized, scaleFactor=1.1, minNeighbors=4)

for (x, y, w, h) in face_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv2_imshow(image_resized)

5명을 모두 잘 검출했네요. 

6. 눈 검출

이번에는 눈을 검출해보겠습니다. 아까와 마찬가지로 opencv/data/haarcascades/ 깃헙에서 사전 훈련된 눈 검출기 xml 파일을 다운로드하겠습니다. 구체적인 링크는 이곳입니다. 다운로드한 눈 검출기 xml 파일을 'colab/Computer-Vision-Course/Data/Weights/Cascades' 디렉터리 아래 저장하세요. 그다음 아래 코드를 실행합니다.

여기서 minSize와 maxSize 파라미터를 추가로 썼습니다. minSize는 경계 박스의 최소 크기, maxSize는 경계 박스의 최대 크기를 지정하는 파라미터입니다. minSize의 기본값은 (30, 30)입니다. 눈은 (30, 30)보다 픽셀이 작으므로 minSize를 (30, 30)보다 작은 값으로 설정해야 합니다. 그래야 눈을 검출할 수 있습니다.
image_resized = cv2.resize(image, (755, 500))

# Load prebuilt cascade classifier for detecting eye
cascade_eye_detector = cv2.CascadeClassifier('/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Weights/Cascades/haarcascade_eye.xml')

eye_detections = cascade_eye_detector.detectMultiScale(image_resized, scaleFactor=1.05, minNeighbors=6,
                                                       minSize=(10, 10), maxSize=(30, 30))

# Draw face detection bounding-boxs 
for (x, y, w, h) in face_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

# Draw eye detection bounding-boxs 
for (x, y, w, h) in eye_detections:
    cv2.rectangle(image_resized, (x, y), (x + w, y + h), (0, 255, 255), 2)
    
cv2_imshow(image_resized)

초록색 경계 박스로 얼굴을 검출하고, 노란색 경계 박스로 눈을 검출했습니다. 그런데 눈을 검출하는 데 false positive 한 개가 있네요. 입 주변을 눈으로 잘못 검출했습니다. 파라미터를 조정해도 모든 얼굴과 눈을 제대로 검출 못할 수도 있습니다. 모든 얼굴과 눈을 검출하려면 false positive가 늘어나고, false positive를 줄이면 모든 얼굴과 눈을 검출하지 못할 수도 있습니다. 트레이드오프 관계죠.

7. 다른 이미지로 얼굴과 눈 검출해보기

지금까지 한 작업을 다른 이미지로도 해보겠습니다. 여기서 이미지를 다운로드한 뒤 실습해보세요.

# image source : https://unsplash.com/ko/%EC%82%AC%EC%A7%84/Q_Sei-TqSlc
image2 = cv2.imread('/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Images/people2.jpg')

image2_resized = cv2.resize(image2, (700, 500))

cv2_imshow(image2_resized)

image2_resized = cv2.resize(image2, (700, 500))

face_detections = cascade_face_detector.detectMultiScale(image2_resized, scaleFactor=1.1, minNeighbors=7)

eye_detections = cascade_eye_detector.detectMultiScale(image2_resized, scaleFactor=1.06, minNeighbors=3,
                                                       maxSize=(32, 32))

# Draw face detection bounding-boxs 
for (x, y, w, h) in face_detections:
    cv2.rectangle(image2_resized, (x, y), (x + w, y + h), (0, 255, 0), 2)

# Draw eye detection bounding-boxs 
for (x, y, w, h) in eye_detections:
    cv2.rectangle(image2_resized, (x, y), (x + w, y + h), (0, 255, 255), 2)
    
cv2_imshow(image2_resized)

이번에는 눈이 대체로 작고, 웃는 눈이라 동공이 잘 보이지 않아 제대로 검출되지 않았습니다.

지금까지 하르 캐스케이드를 활용해 얼굴과 눈을 검출해봤습니다. 초기 객체 검출 모델이라 성능이 썩 좋진 않습니다.


참고 자료

Jones Granatyr(Udemy) - "Computer Vision: Master Class"

OpenCV haarcascades Github

Mohammad Waseem - "Object Detection with OpenCV-Python Using a Haar-Cascade Classifier"

Comments