[OpenCV] 카메라 스티커앱 만들기

6734 단어 opencvopencv

1. 사진 준비

$ mkdir -p ~/aiffel/camera_sticker/models  #디렉토리 생성
$ mkdir -p ~/aiffel/camera_sticker/images  #디렉토리 생성
$ ln -s ~/data/* ~/aiffel/camera_sticker/images  #이미지 연결
import os
import cv2  #이미지 처리를 위한 OpenCV
import matplotlib.pyplot as plt  #이미지 출력을 위한 matplotlib
import numpy as np
import dlib
print("🌫🛸")
#이미지 읽기

my_image_path = os.getenv('HOME')+'/aiffel/camera_sticker/images/image.png'
img_bgr = cv2.imread(my_image_path)    # OpenCV로 이미지 불러오기
img_show = img_bgr.copy()      # 출력용 이미지 따로 보관
plt.imshow(img_bgr)
plt.show()

>>출력 결과, 얼굴에 푸른빛이 감돈다. 
OpenCV는 BGR(파랑, 녹색, 빨강)을 사용. 사진에서 붉은색<->푸른색으로 바뀌어 출력됨
# RGB로 이미지 보정
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()

OpenCV 이미지 읽기 flag

  • cv2.imread('img.png',1) : IMREAD_COLOR - 이미지 파일을 Color로 읽어들입니다. 투명한 부분은 무시되며, Default값입니다.
  • cv2.imread('img.png',0) : IMREAD_GRAYSCALE - 이미지를 Grayscale로 읽어 들입니다. 실제 이미지 처리시 중간단계로 많이 사용합니다.
  • cv2.imread('img.png',-1) IMREAD_UNCHANGED - 이미지파일을 alpha channel까지 포함하여 읽어 들입니다.

2. face detection

dlib의 face detector

  1. HOG(Histogram of Oriented Gradients) : 이미지에서 색상의 변화량을 나타낸 것. 딥러닝 이전에 사용되던 방식
  2. SVM(Support Vector Machine) : 선형분류기.
  3. sliding window : 큰 이미지의 작은 영역을 잘라 얼굴이 있는지 확인하고, 다시 작은 영역을 옆으로 옮겨 얼굴이 있는지 확인하며 얼굴 위치 찾기.
# dlib를 활용해 hog detector 선언
detector_hog = dlib.get_frontal_face_detector()
print("🌫🛸")

img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)  #cvtColor()을 이용해 OpenCV bgr이미지 rgb로 변환
dlib_rects = detector_hog(img_rgb, 1)   #두번째 파라미터인 1은 이미지 피라미드의 수
print("🌫🛸")

찾은 얼굴 화면에 출력

# 찾은 얼굴 영역 박스 리스트
# 여러 얼굴이 있을 수 있습니다
print(dlib_rects)   

for dlib_rect in dlib_rects:  #dlib detector은 dlib.rectangels 타입 객체 반환, left(), top(), right(), bottom(), height(), width() 등 멤버 함수 포함
    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)
plt.imshow(img_show_rgb)
plt.show()

3. face landmark

face landmark localization 기술 : 이목구비의 위치 추론. detection 의 결과물인 bounding box 로 잘라낸(crop) 얼굴 이미지를 이용

Object keypoint estimation 알고리즘 : Face landmark와 같이 객체 내부의 점을 찾는 기술

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

Dlib landmark localization

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

$ wget http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2  #weight file 다운로드
$ mv shape_predictor_68_face_landmarks.dat.bz2 ~/aiffel/camera_sticker/models  #압축 풀어주기
$ cd ~/aiffel/camera_sticker && bzip2 -d ./models/shape_predictor_68_face_landmarks.dat.bz2
#저장한 landmark 모델 불러오기
model_path = os.getenv('HOME')+'/aiffel/camera_sticker/models/shape_predictor_68_face_landmarks.dat'
landmark_predictor = dlib.shape_predictor(model_path)
print("🌫🛸")

list_landmarks = []

# 얼굴 영역 박스 마다 face landmark 찾아내기
for dlib_rect in dlib_rects:
    points = landmark_predictor(img_rgb, dlib_rect)#rgb이미지, dlib.rectangle을 입력받아 dlib.full_object_detection를 반환
    # face landmark 좌표 저장
    list_points = list(map(lambda p: (p.x, p.y), points.parts()))  #point는 dlib.full_object_detection 객체로 parts() 함수로 개별 위치에 접근 가능.
    list_landmarks.append(list_points)

print(len(list_landmarks[0]))

#랜드마크 영상에 출력
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()

4. 스티커 적용하기

랜드마크를 기준으로 눈썹 위 얼굴 중앙에 스티커를 씌우기

  1. 스티커 위치
  2. 스티커 크기
#좌표확인
for dlib_rect, landmark in zip(dlib_rects, list_landmarks):
    print (landmark[30]) # 코의 index는 30 입니다
    x = landmark[30][0]
    y = landmark[30][1] - dlib_rect.height()//2
    w = h = dlib_rect.width()
    print ('(x,y) : (%d,%d)'%(x,y))
    print ('(w,h) : (%d,%d)'%(w,h))
    
>> (437, 182)  #얼굴이미지에서 코의 중심점이 (437,182)
(x,y) : (437,89)
(w,h) : (187,187)

#스티커 이미지를 읽어서 적용
sticker_path = os.getenv('HOME')+'/aiffel/camera_sticker/images/king.png'
img_sticker = cv2.imread(sticker_path) # 스티커 이미지 읽기
img_sticker = cv2.resize(img_sticker, (w,h))  #윗 단계에서 계산한 크기로 리사이즈
print (img_sticker.shape)

>> (187, 187, 3)

#스티커 이미지 추가를 위해 x,y좌표 조정
refined_x = x - w // 2
refined_y = y - h
print ('(x,y) : (%d,%d)'%(refined_x, refined_y))

>> (x,y) : (344,-98)  #y축좌표의 값이 음수로 계산됨 문제 발생

->음수로 계산된 이유 : 스티커의 시작점이 얼굴 사진의 영역을 벗어났기 때문에.

스티커의 시작점 + 스티커 사진의 크기가 원본 이미지의 크기보다 큰 경우 문제 해결 방법

#-y 크기만큼 스티커를 crop, top 의 x좌표와 y 좌표를 각각의 경우에 맞춰 원본 이미지의 경계 값으로 수정
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))


# 길어서 복잡해 보이지만 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,sticker_area,img_sticker).astype(np.uint8)  #sticker_area:원본이미지에서 스티커를 적용할 위치를 crop한 이미지
print("슝~")

#결과 수행
plt.imshow(cv2.cvtColor(img_show, cv2.COLOR_BGR2RGB))
plt.show()

#bounding box, landmark 제거
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,sticker_area,img_sticker).astype(np.uint8)
plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
plt.show()

📆 2022-01-17

📚 EXPLORATION NODE3

좋은 웹페이지 즐겨찾기