인공지능/OpenCV

[OpenCV] Haar Cascade 사용하여 얼굴에 마스크 합성하기 (스*우)

여니여니_ 2020. 4. 9. 01:20

개념 1. Haar Cascade 

 

Haar Feature

Viola와 Jones가 제안한 개념으로 간단한 특징의 Boosted Cascade로 빠른 오브젝트 검출이 가능하다.

 

원리

1. Feature 선택: 중요한 Features 선택

2. Attention: 잠재적 영역에 집중한다.

3. 빠른 feature 평가를 위해 적분(Integral) 영상을 사용한다. 

 

특정 Feature는 명암 차이가 있다는 이론을 바탕으로 한다.

 

이미지에서 영역과 영역의 밝기차를 이용하여 특징을 찾아낸다.

 

사람을 얼굴, 눈, 코, 입 등 특징적인 밝기 차가 있어서 이를 활용하는 것이다. 

 

OpenCV에서 ML로 훈련시킨 Feature에 따른 Harrdata를 xml 파일 형태로 제공한다. 

 

즉, Haar Cascade는 머신러닝을 기반으로 한 오브젝트(얼굴) 검출 알고리즘 이다.

 

 

개념2. Bitwise Operation 비트 연산

 

Bitwise를 사용하여 영상이나 이미지를 비트 연산 할 수 있다.

 

연산 함수는 cv2.Bitwise함수(이미지1, 이미지2, 결과, 마스크) 형식이다. 

 

 

And 연산

A B A and B
0 0 0
0 1 0
1 0 0
1 1 1

 

Or 연산

A B A or B
0 0 0
0 1 1
1 0 1
1 1 1

 

Xor 연산

A B A xor B
0 0 1
0 1 0
1 0 0
1 1 1

 

Not 연산

A not A
0 1
1 0

 

 

참고로, 컬러 이미지는 한 픽셀당 0~255의 값을 가진다.

 

검정색은 픽셀 값이 0, 흰색은 픽셀 값이 255로 숫자가 커질수록 밝은 색이 나온다.

 

위 연산식에서 1을 0이 아닌 모든 수로 간주하면 된다.

 

즉, 픽셀 값이 0인 곳과 픽셀 값이 100인 것을

 

and 연산 하면 0이 나올 것이고,

 

or 연산 하면 100이 나올 것이다.

 

 

예시. 두 이미지의 bitwise 연산

import cv2

imgFile1='../img/green.jpg'
imgFile2='../img/tree.png'

img1=cv2.imread(imgFile1)
img2=cv2.imread(imgFile2)

cv2.imshow('img1',img1)
cv2.imshow('img2',img2)

img1=cv2.resize(img1,(300,300))
img2=cv2.resize(img2,(300,300))

bit_and=cv2.bitwise_and(img1,img2)
bit_not=cv2.bitwise_not(img2)

cv2.imshow('bit_and',bit_and)
cv2.imshow('bit_not',bit_not)

cv2.waitKey()
cv2.destroyAllWindows()

 

img1
img2

 

결과 이미지

 

not 연산을 했을 때, 왼쪽 사진처럼 이 바뀐 것을 볼 수 있다.

 

and 연산을 했을 때, bit_and 연산을 한 것은 검정색(픽셀 값이 0)이 아닌 부분의 색깔(초록)이 도출되는 것을 볼 수 있다.

 

개념3. Threshold 임계값

 

임계값이란 경계값을 의미한다.

한 기준점을 중심으로 영역이 나뉘게 된다. 

 

영상에서는 cv2.threshold 함수를 사용하는데 그레이 스케일 (흑백) 이미지만 적용이 가능하다. 

 

함수는  cv2.threshold(이미지, 임계값, 최대값, 임계값 적용 스타일) 형식으로 쓰여진다.

 

(자세한 설명은 공식 문서를 참고해주세요.)

 

baboon.jpg

 

 

 

다음 이미지를 그레이스케일로 읽어와서 네번째 매개변수를 변화시키며 결과를 관찰해 보자.

 

import cv2
from matplotlib import pyplot as plt

img = cv2.imread('baboon.jpg', 0)
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

 

 

결과

 

 

 

개념4. 디지털영상 표현과 Zero-based 좌표계

 

이미지 좌표는 다음과 같이 이해하면 된다.

 

  • 이미지의 왼쪽 위가 (0,0)
  • 가로가 x축, 세로가 y축
  • 오른쪽으로 갈수록 x의 값이 커지고 아래쪽으로 갈수록 y값이 커진다.

그래서 얼굴 영역만 슬라이싱 하고싶다면 다음과 같이 하면 될 것이다.

face_img = img[y : y + h , x : x + w]

 

개념 5. 마스크 적용 원리

 

아래 실습 코드에서 적용된 변수의 이름을 적어두었다. 

 

 

img_roi  

img_roi

하르 분류기로 찾아낸 얼굴 영역이다.

얼굴만 슬라이싱 하여 넣어준 것이다.

 

 

  face_mask_small  

face_mask_small

마스크 이미지를 resize 함수로 크기 조절을 해준것이다.

 

  gray _mask  

gray_mask

위 사이즈 조절된 이미지를 흑백 이미지로 바꾼 것이다.

 

mask

mask

threshold 함수로 임계값을 기준으로 이진화한 것이다.

THRESH_BINARY_INV 매개변수로 임계값 이하는 255로, 임계값 이상은 0적용된 것이다.

 

mask_inv  

mask_inv

bitwise_not을 사용하여 흑과 백을 바꾸어준다.

 

masked_face  

masked_face

2번째 이미지(face_mask_small)와 4번째 이미지(mask)를 and 연산 한 것이다.

마스크를 제외한 영역을 검정색으로 바꾸어주는 작업이다.

 

 

masked_frame

1번째 이미지와 5번째 이미지를 and연산한 것이다.

마스크를 씌울 부분에 구멍을 뚫어준 것이다.

 

최종적으로 아래쪽의 두 이미지(masked_face, masked_frame)를 더하여 이미지에 붙여 넣어주면 완성된다!

(궁극적으로 이 두 이미지를 추출하기 위하여 앞선 복잡한 작업들을 해준 것이다.)

 

실습 코드

import cv2
import numpy as np

# face 분류기 로드
face_cascade = cv2.CascadeClassifier('cascade_files/haarcascade_frontalface_alt.xml')
# 가면 영상
face_mask = cv2.imread('../../img/bb.jpg')
#cv2.imshow('test',face_mask)
h_mask, w_mask = face_mask.shape[:2]

if face_cascade.empty():
    raise IOError('Unable to load the face cascade classifier xml file')
cap = cv2.VideoCapture("../../img/faces.mp4")
scaling_factor = 0.1

while True:
    ret, frame = cap.read()
    #cv2.imshow('Original', frame)
    if not ret:
        break
    frame = cv2.resize(frame, None,fx=scaling_factor,fy=scaling_factor, interpolation=cv2.INTER_AREA)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    face_rects = face_cascade.detectMultiScale(gray, 1.3, 5)
   
    for (x, y, w, h) in face_rects:
        
        if h > 0 and w > 0:

            x = int(x-w*0.1 )
            y = int(y -h*0.05)
            w = int(1.2* w)
            h = int(1.2 * h)
          
            frame_roi = frame[y:y + h, x:x + w]
          
            face_mask_small = cv2.resize(face_mask, (w, h), interpolation=cv2.INTER_AREA)
            
            gray_mask = cv2.cvtColor(face_mask_small, cv2.COLOR_BGR2GRAY)
            ret, mask = cv2.threshold(gray_mask, 240, 255, cv2.THRESH_BINARY_INV)
            #cv2.imshow('gray_mask', gray_mask)
            #cv2.imshow('mask', mask)
        mask_inv = cv2.bitwise_not(mask)
        masked_face = cv2.bitwise_and(face_mask_small, face_mask_small, mask=mask)
        masked_frame = cv2.bitwise_and(frame_roi, frame_roi, mask=mask_inv)
        
        #cv2.imshow('masked_face', masked_face)
        #cv2.imshow('masked_frame', masked_frame)
        
        frame[y:y + h, x:x + w] = cv2.add(masked_face, masked_frame)

    cv2.imshow('Face Detector', frame)
    c = cv2.waitKey(1)
    if c == 27:
        break
cap.release()
cv2.destroyAllWindows()

 

결과

 

[합성 전]

 

[합성 후]

이미지에 적용한 결과

 

[합성 전]

동영상 출처:  https://www.pexels.com/ko-kr/video/3960181/

 

[합성 후]

동영상에 적용한 결과

 

 

참고로, 얼굴이 기울어지거나 정면이 아닐 때, 얼굴을 잘 검출하지 못할 수 있다.

 

 

 

 

 

Github: https://github.com/uyeonH/HelloEngineering

 

uyeonH/HelloEngineering

WISET 사업단 - Hello 공학 교육컨텐츠 만들기 . Contribute to uyeonH/HelloEngineering development by creating an account on GitHub.

github.com