본문 바로가기
On Going/Computer Vision

[Computer Vision] Face detector

by 에아오요이가야 2022. 3. 16.

https://github.com/kairess/face_detector

 

GitHub - kairess/face_detector

Contribute to kairess/face_detector development by creating an account on GitHub.

github.com

https://www.youtube.com/watch?v=tpWVyJqehG4 

 

오늘 해볼것은 FACE DETECT한뒤 다른 이미지로 대체하는 project입니다.

우선 유튜브와 github 동시에 공유 해드리겠습니다. 직접 확인해보시는것도 좋겠죠?

 

terminal 에서 

git clone https://github.com/kairess/face_detector.git

입력해주시면 됩니다

그러면 이런 폴더가 생성되는데요!

들어가 보시면 다음과 같이 간단하게 samples폴더와 main.py 코드로 이루어져있습니다.

 

 

samples에 들어가보시면 예시로 사용할 동영상 세개와 이미지 두개가 있는데요

이걸 main.py에서 살짝씩 변경하면서 테스트 해줘보도록하겠습니다.

 

우선 main.py가 어떻게 이뤄져있는지 이해해봐야겠죠?

 

아래는 기본적인 package 다운로드와 오늘우리가 사용할 클래스들, 함수들을 초기화 해주는 부분입니다.

 

import cv2, dlib, sys
import numpy as np

# face detector 와 shape predictor 초기화해줍니다.
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('./shape_predictor_68_face_landmarks.dat')

 

그런데 여기까지만 보면 shap_predictor_68_face_landmarks.dat 이게 있어야겠다는 생각이 들어야합니다

저 file을 http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2

압축이 해제된 파일 자체를 업로드 해드리고 싶은데 20MB까지 업로드 제한이 있어서 안되네요 ㅜㅜ

정 귀찮으시면 확인하고 메일로 보내드리겠습니다 ㅋㅋ

이 사이트에서 다운받고 압축해제 해준뒤 main.py와 같은 directory에 넣어줘야합니다. 다음과 같이 되겠네요!

 

 

자 이제 다시 코드로 돌아가볼게요 

face detect할 동영상과 face를 대체할 image를 받아와보겠습니다.

 

# 비디오 받아오기
cap = cv2.VideoCapture('samples/girl.mp4')
# 덮어쓸 이미지 받아오기
overlay = cv2.imread('samples/ryan_transparent.png', cv2.IMREAD_UNCHANGED)

 

자 그럼 할일이 뭘까요?

얼굴부분을 찾고 그 부분을 다른 이미지로 덮어쓰면 되겠죠? 그러기 위해 얼굴을 이미지로 덮는 함수를 먼저 정의해줍니다.

 

# 덮어쓰기 함수
def overlay_transparent(background_img, img_to_overlay_t, x, y, overlay_size=None):
  bg_img = background_img.copy()
  # 3 channels 로 입력된 이미지를 4 channels 로 변환해 줍니다! 왜일까요?
  # BGR로 들어온 이미지를 BGRA로 변환해준 것인데 일반적으로 우리가 알고있는것으로 설명하자면
  # RGB 이미지를 ARGB로 변환한 것입니다. ARGB = Addressable RGB입니다.
  # 저는 잘 모르겠지만 ARGB이미지로 변환을 할경우 각각의 채널내부의 픽셀들을 자유롭게 변경할수 있는 장점이 있답니다. 
  if bg_img.shape[2] == 3:
    bg_img = cv2.cvtColor(bg_img, cv2.COLOR_BGR2BGRA)

  # 덮어쓰기 할 이미지의 크기를 따로 지정해 준 경우에 그에 맞는 변환을 해주는 부분입니다.
  if overlay_size is not None:
    img_to_overlay_t = cv2.resize(img_to_overlay_t.copy(), overlay_size)

  # 궁금해서 b,g,r,a를 각각 다 찍어봤습니다
  # a는 덮어쓸 이미지의 테두리를 받아온것 같은 이미지였습니다
  b, g, r, a = cv2.split(img_to_overlay_t)
  
  # 그래서 a를 덮는 의미의 마스크로 쓰고
  mask = cv2.medianBlur(a, 5)

  # 덮을 이미지의 위치를 찾은뒤
  h, w, _ = img_to_overlay_t.shape
  roi = bg_img[int(y-h/2):int(y+h/2), int(x-w/2):int(x+w/2)]

  # 뒷부분 이미지에 구멍이 뚫린 이미지를 만들고
  img1_bg = cv2.bitwise_and(roi.copy(), roi.copy(), mask=cv2.bitwise_not(mask))
  # 구멍 뚫린부분의 이미지를 덮을 이미지를 만든뒤
  img2_fg = cv2.bitwise_and(img_to_overlay_t, img_to_overlay_t, mask=mask)
  
  #원본에 뚫리고 덮는 이미지를 더해줍니다
  bg_img[int(y-h/2):int(y+h/2), int(x-w/2):int(x+w/2)] = cv2.add(img1_bg, img2_fg)

  # 시각화 하기 위해 4 channels 로 변환했었던 to 3 channels 다시 변환하여 줍니다.
  bg_img = cv2.cvtColor(bg_img, cv2.COLOR_BGRA2BGR)

  return bg_img

 

얼굴의 부분이 되는 좌표를 받아줄 list : face_roi와

얼굴의 크기를 받아줄 list : face_size 를 선언해줍니다.

 

face_roi = []
face_sizes = []

 

 

scaler=0.3

# 영상이 재생되는 동안!
while True:
  # 영상의 frame을 읽어주겠다
  ret, img = cap.read()
  # 끝나면 while 반복문을 멈추겠다
  if not ret:
    break

  # 영상으로 받아오는 이미지를 적절한(정말 주관적으로)사이즈로 변환해준뒤 그것을 ori이미지로 사용합니다.
  img = cv2.resize(img, (int(img.shape[1] * scaler), int(img.shape[0] * scaler)))
  ori = img.copy()

  # 첫 frame에서 face를 detector 함수를 사용하여 찾아주겠습니다.
  if len(face_roi) == 0:
    #faces는 [(x1,y1),(x2,y2)]형태의 사각형으로 찾아집니다.
    faces = detector(img, 1)

  # 그 이후에 face_roi로 동영상에 알맞게 얼굴을 업데이트 해줍니다
  else:
    roi_img = img[face_roi[0]:face_roi[1], face_roi[2]:face_roi[3]]
    # 실제로 아래 이미지를 찍어보면 얼굴이 엄청나게 흔들리는 것을 볼 수 있습니다. 개선할 방법들이 있겠죠?
    # cv2.imshow('roi', roi_img)
    faces = detector(roi_img)

  # 얼굴을 찾지 못했을 때의 예외 처리입니다.
  if len(faces) == 0:
    print('no faces!')

  # 얼굴의 landmarks을 predictor 함수를 사용하여 찾아주겠습니다. 
  # predictor함수[따로 다운로드 받아준 파일]는 총 68개의 landmark를 찾아주는 함수입니다.
  for face in faces:
    if len(face_roi) == 0:
      dlib_shape = predictor(img, face)
      shape_2d = np.array([[p.x, p.y] for p in dlib_shape.parts()])
    else:
      dlib_shape = predictor(roi_img, face)
      shape_2d = np.array([[p.x + face_roi[2], p.y + face_roi[0]] for p in dlib_shape.parts()])

    # 찾아진 landmark 들에 하얀색 작은 원을 그려주겠습니다.
    for s in shape_2d:
      cv2.circle(img, center=tuple(s), radius=1, color=(255, 255, 255), thickness=2, lineType=cv2.LINE_AA)

    # landmark들의 평균을 계산하여 얼굴의 중심 (center)의 좌표를 구해줍니다.
    center_x, center_y = np.mean(shape_2d, axis=0).astype(np.int)

    # 모든 landmark들의 x좌표의 최소값과 y좌표의 최소값을 계산하여 얼굴 사각형의 왼쪽 위 좌표를 구합니다.
    min_coords = np.min(shape_2d, axis=0)
    
    # 모든 landmark들의 x좌표의 최대값과 y좌표의 최대값을 계산하여 얼굴 사각형의 오른쪽 아래 좌표를 구합니다.    
    max_coords = np.max(shape_2d, axis=0)

    # 얼굴 사각형의 왼쪽 위 좌표에 빨간 점을 찍어줍니다.
    cv2.circle(img, center=tuple(min_coords), radius=1, color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)

    # 얼굴 사각형의 오른쪽 아래 좌표에 빨간 점을 찍어줍니다.
    cv2.circle(img, center=tuple(max_coords), radius=1, color=(0, 0, 255), thickness=2, lineType=cv2.LINE_AA)

    # 동영상에서의 얼굴을 덮을 이미지의 크기를 조정하기위해 동영상에서의 얼굴 크기를 계산합니다
    # 아래줄과 같은 계산을 하면 face_size는 sclar 값이 나옵니다.
    face_size = max(max_coords - min_coords)
    # 그 값들을 face_sizes list에 하나씩 쌓아주고
    face_sizes.append(face_size)

    # face_sizes list의 원소들이 10개가 되도록 선입선출 방식으로 유지하여 최신의 face_size를 유지합니다.
    if len(face_sizes) > 10:
      del face_sizes[0]
    # face_sizes list의 평균을 기준으로 실제 동영상에 맞추어 얼굴 사이즈를 미량조절해줍니다. (곱하기 1.8)
    mean_face_size = int(np.mean(face_sizes) * 1.8)

    # face의 정확한 영역을 업데이트 해줍니다. 이부분때문에 face가 엄청나게 흔들리는 것으로 추정됩니다.
    # 간단한 해결방법은 face_roi자체를 업데이트 하지 않아도 되겠지만, 그건 얼굴이 고정된 상태에서나 통하는 논리이고
    # 좀 천천히? mild하게 움직이는 방법을 찾아볼수있으면 좋겠습니다.
    face_roi = np.array([int(min_coords[1] - face_size / 2), int(max_coords[1] + face_size / 2), int(min_coords[0] - face_size / 2), int(max_coords[0] + face_size / 2)])
    face_roi = np.clip(face_roi, 0, 10000)

    # 위에서 정의했던 overlay_transparent 함수를 사용하면 됩니다!
    # 기본 이미지에, 덮을 이미지를, x좌표와, y좌표에, 얼굴사이즈만하게 덮어줘라! 로 읽힙니다.
    result = overlay_transparent(ori, overlay, center_x + 8, center_y - 25, overlay_size=(mean_face_size, mean_face_size))

 

실제로 보여주는 파트 입니다! 이게 없으면 아무런 창도 뜨지 않아요 ㅎㅎ

  # 시각화 파트
  # input으로 들어온 영상을 보여줌
  cv2.imshow('original', ori)
  
  # 얼굴의 landmark를 찍은 영상을 보여줌  
  cv2.imshow('facial landmarks', img)
  
  # 얼굴을 다른 이미지로 덮어쓴 영상을 보여줌
  cv2.imshow('result', result)
  
  # 영상이 끝날때까지 보여줌~  
  if cv2.waitKey(1) == ord('q'):
    sys.exit(1)

그럼 결과는 다음과 같이 나옵니다!!

 

 

cv2.imshow('original', ori)

 

 

cv2.imshow('facial landmarks', img) 

 

cv2.imshow('result', result)

 

사실 왜 나누는지 잘 모르겠는데 궁금해서 한번 찍어봤습니다.

'On Going > Computer Vision' 카테고리의 다른 글

Landmark localization  (1) 2023.12.05
Segmentations(Instance & Panoptic)  (1) 2023.12.05
Object detection  (2) 2023.12.05
[Computer Vision] Super Resolution  (0) 2022.04.11
[Computer Vision] 나의 관심 분야  (0) 2022.03.15

댓글