Face and Background Blurring with OpenCV in Python
때때로 얼굴을 흐릿하게 만드는 것(blurring)과 익명화(anonymizing)가 구글의 스트릿뷰 특성처럼 공공장소의 사진에서 신분을 인식하지 않도록해야할 필요가 있다. 그리고 때때로 얼굴 영역에 초점을 맞추기 위해 이미지의 배경(background)를 흐릿하게 만들 필요도 있다. 두가지 모두 실제 동일 절차가 필요하다. 이 글에서는 파이썬 OpenCV로 어ㄸ허게 얼굴과 배경을 흐릿하게 만드는지를 다룬다.
Anonymous in Mr. Robot
Objective
이 글에서는 얼굴 또는 배경을 흐릿하게 만들기를 적용해 본다. 이 글의 끝에서는 실시간 비디오에도 이를 적용해 본다.
Face detection
얼굴과 배경을 흐릿하게 만드는 것은 우선 얼굴 탐지가 필요하다. 가장 일반적인 얼굴 탐지기는 opencv haar cascade, SSD, Dlib, MTCNN이 있다. 여기서 SSD는 가장 빠르고 MTCNN은 가장 강력하다. 각 탐지기의 성능은 아래 비디오에서 확인할 수 있다.
아래 비디오는 deepface를 이용해 이들 탐지를 사용하는 방법을 보여준다.
Generic blurring function
여기서는 opencv의 Gaussian Blur function을 사용한다. 이 함수는 각각 원시 이미지와 가우시안 커널 크기(gaussian kernel size)가 필요하다. 여기서 커널 크기는 홀수(odd)여야 한다. 그렇기 때문에 짝수일 경우 1을 뺀다. 커널 크기는 이미지의 크기와 인자 변수(factor variable)의 비율로 계산하였다. 여기서 작은 인자는 얼굴을 더 많이 흐릿하게 하면서 더 많은 처리 시간이 필요하다.
def blur_img(img, factor = 20):
kW = int(img.shape[1] / factor)
kH = int(img.shape[0] / factor)
#ensure the shape of the kernel is odd
if kW % 2 == 0: kW = kW - 1
if kH % 2 == 0: kH = kH - 1
blurred_img = cv2.GaussianBlur(img, (kW, kH), 0)
return blurred_img
아래 이미지처럼 인자 크기가 어떻게 흐릿하게 하는 것에 영향을 미치는지 볼 수 있다.
Blurring with different factors
Blurring the background
배경을 흐릿하게 하고 얼굴 영역에 조첨을 맞추어야 한다. 우선 기본 이미지를 흐릿하게 하고 두번째로 기본 이미지에서 얼굴을 탐지한다. 그리고 기본이미지에 탐지된 얼굴을 붙힌다.
img = cv2.imread("deepface/tests/dataset/img1.jpg")
blurred_img = blur_img(img, factor = 10)
face_detector_path = "haarcascade_frontalface_default.xml"
faces = face_detector.detectMultiScale(img, 1.3, 5)
for x, y, w, h in faces:
detected_face = img[int(y):int(y+h), int(x):int(x+w)]
blurred_img[y:y+h, x:x+w] = detected_face
plt.imshow(blurred_img[:,:,::-1])
다음은 배경을 흐릿하게 한 결과이다.
Background blurring
Blurring the face
탐지된 얼굴을 흐릿하게 하고 기본 이미지에 붙힌다.
img = cv2.imread("deepface/tests/dataset/img1.jpg")
face_detector_path = "haarcascade_frontalface_default.xml"
faces = face_detector.detectMultiScale(img, 1.3, 5)
for x, y, w, h in faces:
detected_face = img[int(y):int(y+h), int(x):int(x+w)]
detected_face_blurred = blur_img(detected_face, factor = 3)
img[y:y+h, x:x+w] = detected_face_blurred
plt.imshow(img[:,:,::-1])
다음은 얼굴을 흐릿하게 한 결과로 실제 안젤리나졸리를 알아볼 수 없다.
Face blurring
Pixelated blurring
좀 더 우아한 방법으로 얼굴을 흐릿하게 할 수 있다. 탐지된 얼굴을 더 작은 조각으로 나누고 여기에 가우시안 블러(Gaussian blur)를 적용한다.
아래 코드는 이미지를 더 작은 조각으로 나눈다.
def extract_indexes(length, step_size):
indexes = []
cycles = int(length / step_size)
for i in range(cycles):
begin = i * step_size; end = i * step_size+step_size
#print(i, ". [",begin,", ",end,")")
index = []
index.append(begin)
index.append(end)
indexes.append(index)
if begin >= length: break
if end >length: end = length
if end < length:
#print(i+1,". [", end,", ",length,")")
index = []
index.append(end)
index.append(length)
indexes.append(index)
return indexes
그리고 탐지된 얼굴의 더 작은 조각을 처리하기 위해 for 루프를 사용한다. 작은 조각을 흐릿하게 만들기 위해 generic blur 함수를 호출한다. 마지막으로 원본 이미지에 흐릿하게 된 작은 조각들을 붙힌다.
pixelated_face = detected_face.copy()
width = pixelated_face.shape[0]
height = pixelated_face.shape[1]
step_size = 80
for wi in extract_indexes(width, step_size):
for hi in extract_indexes(height, step_size):
detected_face_area = detected_face[wi[0]:wi[1], hi[0]:hi[1]]
if detected_face_area.shape[0] > 0 and detected_face_area.shape[1] > 0:
detected_face_area = blur_img(detected_face_area, factor = 0.5)
pixelated_face[wi[0]:wi[1], hi[0]:hi[1]] = detected_face_area</code></pre>
img[y:y+h, x:x+w] = pixelated_face
plt.imshow(base_img[:,:,::-1])
다음은 pixelated face의 결과로 좀 더 세련되 보이면서 여전히 안젤리나 졸리를 알아볼 수 없다.
Pixelated face
Real time implementation
실시간에서도 흐릿하게 만들 수 있다. 여기[[1]
]에서 이 구현에서 사용된 소스코드를 찾을 수 있다. 여기서 background, face, pixelate fate와 opencv haar cascade, mtccn을 설정할 수 있다.
아래 비디오는 MTCNN과 pixelated 쌍으로 마크 주커버그(Mark Zuckerberg)의 증언 비디오를 흐리게한 것을 보여준다. 그리고 얼굴 탐지의 신뢰도 점수는 90%로 설정했다. 여러분은 몇몇 프레임에서 탐지되지 않는 얼굴 탐지를 위해 이 값을 줄일 수 있다.
case = 'pixelated'
대신에 얼굴 영역 모두를 흐리게 만들 수도 있다. case 변수를 face로 설정한다.
case = 'face'
이것은 화소로 나누는 대신 모든 얼굴을 흐리게 만든다.
>
Future work
Dlib는 하관(jaw), 턱, 눈, 눈썹, 입술의 안과 바깥 영역, 코를 포함한 68개 지점에 대한 얼굴 랜드마크 탐지기(facial landmarks detector)를 제공한다. 이런 방법으로 더 감각적으로 얼굴 또는 배경을 흐리게 할 수 있다. 게다가 얼굴인식 파이프라인에 노지즈가 없는 이미지를 전달하기 위해 얼굴 이외의 영역을 제거할 수 도 있다.
Conclusion
여기서는 파이썬 OPencv로 주어진 이미지에서 얼굴과 배경을 흐리게 만드는 방법을 알아보았다. 이 작업에는 단지 기본적인 얼굴 탐지와 Gaussian blur가 필요했다.
또한 몇가지 부가적인 특성을 더해 연구를 확대할 수 있다. 이미 딥러닝으로 나이를 분석할 수 있다. 다른 시나리오로 미성년의 얼굴만 흐리게 할 수도 있다. 게다가 회사 로고를 탐지하고 광고를 숨기기 위해 이를 흐리게 할 수도 있다. 배경을 흐리게 하는 것도 이를 만족시킬 수 있다.
이 글에서 사용된 소스는 여기에 있다.
import os
from os import listdir
import numpy as np
import cv2
from mtcnn import MTCNN
from PIL import Image
import matplotlib.pyplot as plt
#-----------------------
case = 'background' #background, face, pixelated
backend = 'mtcnn' #haar, mtcnn
mode = 'display' #record, display
#-----------------------
def blur_img(img, factor = 20):
kW = int(img.shape[1] / factor)
kH = int(img.shape[0] / factor)
#ensure the shape of the kernel is odd
if kW % 2 == 0: kW -= 1
if kH % 2 == 0: kH -= 1
blurred_img = cv2.GaussianBlur(img, (kW, kH), 0)
return blurred_img
def extract_indexes(length, step_size):
indexes = []
cycles = int(length / step_size)
for i in range(cycles):
begin = i * step_size; end = i * step_size+step_size
#print(i, ". [",begin,", ",end,")")
index = []
index.append(begin)
index.append(end)
indexes.append(index)
if begin >= length: break
if end > length: end = length
if end < length:
#print(i+1,". [", end,", ",length,")")
index = []
index.append(end)
index.append(length)
indexes.append(index)
return indexes
detector = MTCNN()
#-----------------------
if backend == 'haar':
#OpenCV haarcascade module
opencv_home = cv2.__file__
folders = opencv_home.split(os.path.sep)[0:-1]
path = folders[0]
for folder in folders[1:]:
path = path + "/" + folder
detector_path = path+"/data/haarcascade_frontalface_default.xml"
if os.path.isfile(detector_path) != True:
raise ValueError("Confirm that opencv is installed on your environment! Expected path ",detector_path," violated.")
else:
face_cascade = cv2.CascadeClassifier(detector_path)
#------------------------
cap = cv2.VideoCapture("zuckerberg.mp4") #0 for webcam or video
frame = 0
while(True):
ret, img = cap.read()
if backend == 'haar':
faces = face_cascade.detectMultiScale(img, 1.3, 5)
elif backend == 'mtcnn':
faces = detector.detect_faces(img)
base_img = img.copy()
if case == 'background':
img = blur_img(img, factor = 70)
for detection in faces:
if backend == 'mtcnn':
score = detection["confidence"]
x, y, w, h = detection["box"]
elif backend == 'haar':
x,y,w,h = detection
if (backend == 'haar' and w > 0) or (backend == 'mtcnn' and w > 0 and score >= 0.90):
detected_face = base_img[int(y):int(y+h), int(x):int(x+w)]
if detected_face.shape[0] > 0 and detected_face.shape[1] > 0:
if case == 'background':
img[y:y+h, x:x+w] = detected_face
elif case == 'face':
detected_face_blurred = blur_img(detected_face, factor = 3)
img[y:y+h, x:x+w] = detected_face_blurred
elif case == 'pixelated':
pixelated_face = detected_face.copy()
step_size = 25
width = pixelated_face.shape[0]
height = pixelated_face.shape[1]
#---------------------------------
iteration = 0
for wi in extract_indexes(width, step_size):
for hi in extract_indexes(height, step_size):
detected_face_area = detected_face[wi[0]:wi[1], hi[0]:hi[1]]
#print(width,"x",height,": ",wi,", ",hi," (",detected_face_area.shape)
factor = 0.5# if iteration % 1 == 0 else 1
if detected_face_area.shape[0] > 0 and detected_face_area.shape[1] > 0:
detected_face_area = blur_img(detected_face_area, factor = factor)
pixelated_face[wi[0]:wi[1], hi[0]:hi[1]] = detected_face_area
iteration = iteration + 1
img[y:y+h, x:x+w] = pixelated_face
if mode == 'display':
cv2.imshow('img',img)
elif mode == 'record':
cv2.imwrite('outputs_%s/%d.png' % (backend, frame), img)
if frame % 50 == 0: print(frame)
frame = frame + 1
if cv2.waitKey(1) & 0xFF == ord('q'): #press q to quit
break
#kill open cv things
cap.release()
cv2.destroyAllWindows()