본문 바로가기
Computer Technology 기록부/코딩기록부 : Python

카메라 스티커 만들기

by Amins 2022. 1. 11.

snow나 snapchat같은 카메라 필터앱처럼 사진에서 얼굴을 인식하고 해당 위치에 스티커 사진을 삽입하는 프로그램을 만들어 보자. 기본 원리는 다음과 같다.

얼굴 검출 face detection

Object detection 기술을 이용해서 얼굴의 위치를 찾는다.

  • dlib의 face detector는 HOG(Histogram of Oriented Gradients)SVM(Support Vector Machine) 사용

 

  • HOG : 이미지에서 색상의 변화량을 나타낸 것
    • Deep-learning 나오기 이전 사용 多
    • 이미지로부터 물체의 특징을 잡아내는 능력 大
    • 얼굴 인식해 카메라 초점잡는 기능에 사용

scikit-image, 오른쪽이 HOQ를 시각화한 이미지

  • SVM : 선형분류기, 한 이미지를 다차원 공간의 한 벡터로 보고, 벡터간 구분하는 방식

붉은 선이 회귀선, 연노랑 부분이 마진

이미지의 색상만 가지고는 SVM이 큰 힘을 발휘 X. 하지만 이미지가 HOG를 통해 벡터로 만들어진다면 SVM이 잘 작동


얼굴 위치찾기 - sliding window 기법

  • 작은 영역(window)을 이동해가며 확인하는 방법
    • 큰 이미지의 작은 영역을 잘라 얼굴이 있는지 확인하고, 다시 작은 영역을 옆으로 옮겨 얼굴이 있는지 확인하는 방식.
    • 이미지가 크면 클수록 오래걸리는 단점 - 딥러닝의 필요 이유

더 정확한 이미지 검출을 위해 - Image pyramid : Upsampling

upsampling이란,
  • 간단하게는 데이터의 크기를 키우는 것. 일반적으로 CNN의 레이어를 통과하면서 이미지의 크기를 줄이는 것을 down sampling이라고 하는데, down sampling의 반대 개념이 upsampling

Image pyramid란?

  • 이미지를 upsampling해서 크기를 키우는 것, 작게 촬영된 얼굴을 크게 보게 만드는것 - 정확한 검출 가능

얼굴 랜드마크 Face Landmark

  • 스티커를 섬세하게 적용하기 위해서는 이목구비의 위치를 아는 것이 중요.
  • 이목구비의 위치를 추론하는 것을 face landmark localization 기술
  • face landmark는 detection 의 결과물인 bounding box 로 잘라낸(crop) 얼굴 이미지를 이용

 

  • Object keypoint estimation 알고리즘 - 객체 내부의 점을 찾는 기술
    • top-down : bounding box를 찾고 box 내부의 keypoint를 예측
    • bottom-up : 이미지 전체의 keypoint를 먼저 찾고 point 관계를 이용해 군집화 해서 box 생성

 

  • Dlib landmark localization

잘라진 얼굴 이미지에서 아래 68개의 이목구비 위치를 찾기

점 위의 숫자는 Dlib에 사용되는 랜드마크 순서

※ 점의 개수는 데이터셋이나 논문마다 다름


아래는 해당 프로그램을 파이썬으로 구현한 알고리즘이다.

바운딩 박스 정 중앙에 스티커를 위치시키는 코드다

# 고양이 스티커 부착하기
## 1. 필요 모듈 임포트

import os
import cv2
import matplotlib.pyplot as plt
import numpy as np
import dlib

## 2. 이미지 경로 설정 및 이미지 전처리

### 이미지를 가져와 사용할 준비를 한다. opencv 특징 상 BGR 색상계 사용하므로 사진 색이 변경될 수 있다. 따라서 cv2.cvtColor() 메서드를 통해 다시 RGB 색상계를  이용한 이미지로 변경해준다.

#이미지 경로설정
my_image_path = os.getenv('HOME')+'/aiffel/camera_sticker/images/image_3.png'
img_bgr = cv2.imread(my_image_path)    # OpenCV로 이미지를 불러옵니다
img_show = img_bgr.copy() #출력용 이미지는 따로 보관함.

print("<Before preprocessing>")
plt.imshow(img_bgr)
plt.show()

##이미지 색상 보정처리
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)

print("<After preprocessing>")
plt.imshow(img_rgb)
plt.show()

## 3. 얼굴 위치  검출기 생성

### 오브젝트  디텍션 기술을  이용해 얼굴의 위치를 찾는다.

### 이미지에서 각 위치마다의 색상 변화량을 HOG를 통해 파악하고 SVM 선형분류기를 이용해 벡터화 시킨다. 이후 sliding window 기법을 이용해 작은 영역을 조금씩 이동해겨며 얼굴을 파악해 정사각형을 그린다.

### 얼굴 탐지기는 dlib의 hog detector를 이용해 구현했다.

# HOG 얼굴 바운딩박스 탐지기 생성 및 할당
detector_hog = dlib.get_frontal_face_detector()

#얼굴 바운딩박스 추출 - 얼굴 검출기를 이미지에 적용
dlib_rects = detector_hog(img_rgb, 1)   # (image, num of image pyramid)

# 이미지 피라미드? - 이미지를 upsampling해서 크기를 키우는 것
# 작게 촬영된 얼굴을 크게 보게 만드는것 - 정확한 검출 가능


# 찾은 얼굴 영역 좌표 
for dlib_rect in dlib_rects: 
    l = dlib_rect.left()
    t = dlib_rect.top()
    r = dlib_rect.right()
    b = dlib_rect.bottom()

    cv2.rectangle(img_show, (l,t), (r,b), (0,255,0), 2, lineType=cv2.LINE_AA)

img_show_rgb =  cv2.cvtColor(img_show, cv2.COLOR_BGR2RGB)
print("<Face detection rsult>")
plt.imshow(img_show_rgb)
plt.show()

## 4. 얼굴 랜드마크 데이터 불러오기

### Object keypoint estimation 알고리즘을 이용해 객체 내부의 점을 찾는다. 이번 프로젝트에서는 top-down형식으로, bounding box를 찾고 그 안에서 keypoint를 예측하는 형식으로 진행했다.

### 모델은 사전에 http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2 에서 
### 다운받아 ~/aiffel/camera_sticker/models 내에 
### shape_predictor_68_face_landmarks.dat 의 이름으로 저장해 놓았다.

#얼굴 랜드마크 예측 모델 불러오기
#지정한 모델 불러오기
#landmark_predictor 는 RGB 이미지와 dlib.rectangle을 입력 받아 dlib.full_object_detection 를 반환
model_path = os.getenv('HOME')+'/aiffel/camera_sticker/models/shape_predictor_68_face_landmarks.dat'
landmark_predictor = dlib.shape_predictor(model_path)

## 5. Bounding Box에서 Face landmark 찾아내기

list_landmarks = []

# 얼굴 영역 박스 마다 face landmark를 찾아냅니다
for dlib_rect in dlib_rects:
    points = landmark_predictor(img_rgb, dlib_rect)
    # face landmark 좌표를 저장해둡니다
    list_points = list(map(lambda p: (p.x, p.y), points.parts()))
    list_landmarks.append(list_points)

print(len(list_landmarks[0]))
print(list_landmarks)

# 찾은 랜드마크 영상에 출력하기
for landmark in list_landmarks:
    for point in landmark:
        cv2.circle(img_show, point, 2, (0, 255, 255), -1)

img_show_rgb = cv2.cvtColor(img_show, cv2.COLOR_BGR2RGB)
plt.imshow(img_show_rgb)
plt.show()

## 6. 코 위치 확인하기

### 해당 고양이 스티커는 코 부분에 위치할 것이므로 코의 좌표를 찾는다.

#코위치확인하기
for dlib_rect, landmark in zip(dlib_rects, list_landmarks):
    print (landmark[30]) # 데이터 셋 인덱스에서 코의 index는 30 입니다
    x = landmark[33][0]
    y = landmark[33][1] #- dlib_rect.height()//2
    w = h = dlib_rect.width()
    print ('(x,y) : (%d,%d)'%(x,y)) #바운딩 박스 내 코의 좌표
    print ('(w,h) : (%d,%d)'%(w,h)) #바운딩 박스 크기

## 7. 스티커 적용하기

### 스티커 이미지를 읽어서 적용 해 보았다.

sticker_path = os.getenv('HOME')+'/aiffel/camera_sticker/images/cat-whiskers.png'
img_sticker = cv2.imread(sticker_path) # 스티커 이미지를 불러옵니다
img_sticker = cv2.resize(img_sticker, (w,h))
print (img_sticker.shape)

### 원본 이미지에 스티커 추가하기 위해 x,y 좌표 수정

refined_x = x//2
refined_y = y//2
print ('(x,y) : (%d,%d)'%(refined_x, refined_y))

### y축 좌표값에 발생하는 음수값 처리해주기
#### 여기서 음수는 바운딩 박스 바깥으로 나간 좌표값을 의미

#음수값 만큼 crop해주기
if refined_x < 0: 
    img_sticker = img_sticker[:, -refined_x:]
    refined_x = 0
if refined_y < 0:
    img_sticker = img_sticker[-refined_y:, :]
    refined_y = 0

print ('(x,y) : (%d,%d)'%(refined_x, refined_y))

## 8. 원본이미지에 스티커 적용 

# 길어서 복잡해 보이지만 img_show[from:to] 형식입니다
sticker_area = img_show[refined_y:refined_y+img_sticker.shape[0], refined_x:refined_x+img_sticker.shape[1]]
img_show[refined_y:refined_y+img_sticker.shape[0], refined_x:refined_x+img_sticker.shape[1]] = \
    np.where(img_sticker==0,img_sticker, sticker_area).astype(np.uint8)

## 9. 스티커 결과이미지 출력(img_show로 출력)

plt.imshow(cv2.cvtColor(img_show, cv2.COLOR_BGR2RGB))

plt.show() #스티커 위치가 제대로 들어갔는지 확인

# 결과 이미지 출력(img_rgb로 출력)

sticker_area = img_bgr[refined_y:refined_y +img_sticker.shape[0], refined_x:refined_x+img_sticker.shape[1]]
img_bgr[refined_y:refined_y +img_sticker.shape[0], refined_x:refined_x+img_sticker.shape[1]] = \
    np.where(img_sticker==0,img_sticker, sticker_area).astype(np.uint8)
plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))

 

댓글