스티커를 3 부분으로 나누어 진행, 회전에 대해 강건하게 만들기
고양이 수염을 보고 저것을 왼쪽, 오른쪽, 코로 나누어 진행하면 좋겠다는 생각으로 나누어봤습니다.
또한 회전 후 시작 좌표를 찍을 때, 그동안 삽질을 많이 했는데, 결국 정착한 방법이 여러 사진에 대해 안정적으로 괜찮은 성능을 내는 것 같아 공유드립니다.
import numpy as np
import math
landmarks = list_landmarks[0]
스티커는 512x512의 이미지로, 그림판에서 좌표를 측정하여 각각 정사각형의 스티커로 분해합니다
ORIGINAL_WIDTH = 512
sticker_nose = img_sticker[nose_top:nose_top+nose_height, nose_left:nose_left+nose_width]
sticker_r_whisker = img_sticker[r_whisker_top:r_whisker_top+r_whisker_height, r_whisker_left:r_whisker_left+r_whisker_width]
sticker_l_whisker = img_sticker[l_whisker_top:l_whisker_top+l_whisker_height, l_whisker_left:l_whisker_left+l_whisker_width]
nose_left = 193
nose_width = 128
nose_top = 173
nose_height = 128
l_whisker_left = 0
l_whisker_width = 193
l_whisker_top = 166
l_whisker_height = 193
r_whisker_left = 319
r_whisker_width = 193
r_whisker_top = 166
r_whisker_height = 193
코: 29, 27, 31, 35, 30
(29,27)은 회전각을 구하기 위해, (31, 35)는 코의 크기를 상대적으로 구하기 위해,
30은 중심점으로 활용
왼쪽 수염: 2, 29
회전각과 크기에 활용
오른쪽 수염: 14, 29
회전각과 크기에 활용
arctan을 이용하여 한 특징점에서 다른 특징점까지의 벡터로부터 각도를 추출했습니다.
회전에서 주의해 주어야 할 것이 arctan의 치역은 (-pi/2, pi/2)인데, 이로 인해 사람의 얼굴이 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 코를 회전시키는 각도를 다르게 해야 하는 문제가 생깁니다.
dy = landmarks[29][1] - landmarks[27][1]
dx = landmarks[29][0] - landmarks[27][0]
angle = math.atan(dy/dx)
angle = angle * 180 / math.pi
sign = angle / abs(angle)
if angle > 0:
nose_angle = 90 - angle
else:
nose_angle = -90 - angle
left_whisker_vector = [landmarks[29][0] - landmarks[2][0] ,landmarks[29][1] - landmarks[2][1]]
l_angle = math.atan(left_whisker_vector[1]/left_whisker_vector[0])
l_angle = l_angle * 180 / math.pi
l_angle = sign * abs(l_angle) # 머리가 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 각의 부호가 달라짐
right_whisker_vector = [landmarks[29][0] - landmarks[14][0], landmarks[29][1] - landmarks[14][1]]
r_angle = math.atan(right_whisker_vector[1]/right_whisker_vector[0])
r_angle = r_angle * 180 / math.pi
r_angle = sign * abs(r_anlge) # 머리가 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 각의 부호가 달라짐
np.linalg.norm(vector)
을 이용하면 vector
의 크기를 구할 수 있습니다.
resize()
에 인자로 넣어줄 것이므로, tuple[int]
형태로 관리합니다.
perp_nose_vector = [landmarks[35][0] - landmarks[31][0], landmarks[35][1] - landmarks[31][1]] # 입술의 양단을 잇습니다
nose_length = int(np.linalg.norm(perp_nose_vector)) # 슬라이싱에 사용되므로 좌표의 경우 int형으로 변환해 줍니다
nose_size = (nose_length, nose_length)
left_whisker_length = np.linalg.norm(left_whisker_vector)
left_whisker_size = (int(left_whisker_length * 0.6),
int(left_whisker_length * l_whisker_height / l_whisker_width * 0.6))
# scale factor = 0.6, 볼 밖으로 나오지 않게 하기 위해 사이즈를 줄였습니다
right_whisker_length = np.linalg.norm(right_whisker_vector)
right_whisker_size = (int(right_whisker_length * 0.6),
int(right_whisker_length * r_whisker_height / r_whisker_width * 0.6))
# scale factor = 0.6, 볼 밖으로 나오지 않게 하기 위해 사이즈를 줄였습니다
중심점을 찾는게 가장 편하고, 여러 사진에 안정적으로 작동했습니다.
이후 스티커를 붙일 때, left, top 좌표는 중심점에서 각각 크기의 절반을 뺀 좌표를 이용했습니다.
nose_x, nose_y = landmarks[30]
l_whisker_x, l_whisker_y =(landmarks[29][0] + landmarks[2][0]) // 2,
(landmarks[29][1] + landmarks[2][1]) // 2 # 몫 연산 후 int가 됩니다
r_whisker_x, r_whisker_y = (landmarks[14][0] + landmarks[29][0]) // 2,
(landmarks[14][1] + landmarks[29][1]) // 2
sticker_nose = cv2.resize(sticker_nose, nose_size)
rows, cols = sticker_nose.shape[:2]
# 이미지에서 스티커를 붙일 시작점의 좌표를 pin_nose_start로 한다.
pin_nose_start = (nose_x - rows // 2, nose_y - cols //2)
M= cv2.getRotationMatrix2D((cols//2, rows//2), nose_angle, 1)
dst = cv2.warpAffine(sticker_nose, M,(cols, rows), borderValue=(255,255,255)) # 경계는 흰색
sticker_area = \
img_show[pin_nose_start[1] : pin_nose_start[1] + cols, pin_nose_start[0] : pin_nose_start[0] + rows]
# img_show에서 원본으로 사용할 부분을 변수로 저장한다.
img_show[pin_nose_start[1] : pin_nose_start[1] + cols, pin_nose_start[0] : pin_nose_start[0] + rows] =\
np.where(dst==255, sticker_area, dst).astype(np.uint8)
# 스티커 붙이기. 스티커가 완전한 백색인 부분은 붙이지 않는다. sticker_area 사용
sticker_l_whisker = cv2.resize(sticker_l_whisker, left_whisker_size)
rows, cols = sticker_l_whisker.shape[:2]
# 이미지에서 스티커를 붙일 시작점의 좌표를 pin_l_whisker_start로 한다.
pin_l_whisker_start = (l_whisker_x - rows // 2, l_whisker_y - cols // 2)
M= cv2.getRotationMatrix2D((cols//2, rows//2),l_angle, 1)
dst = cv2.warpAffine(sticker_l_whisker, M,(cols, rows), borderValue=(255,255,255))
sticker_area = img_show[pin_l_whisker_start[1]: pin_l_whisker_start[1] + cols,\
pin_l_whisker_start[0]: pin_l_whisker_start[0] + rows]
# img_show에서 원본으로 사용할 부분을 변수로 저장한다.
img_show[pin_l_whisker_start[1]: pin_l_whisker_start[1] + cols,\
pin_l_whisker_start[0]: pin_l_whisker_start[0] + rows] = \
np.where(dst==255, sticker_area,dst).astype(np.uint8)
# 스티커 붙이기. 스티커가 완전한 백색인 부분은 붙이지 않는다. sticker_area 사용
sticker_r_whisker = cv2.resize(sticker_r_whisker, right_whisker_size)
rows, cols = sticker_r_whisker.shape[:2]
# 이미지에서 스티커를 붙일 시작점의 좌표를 pin_r_whisker_start로 한다.
pin_r_whisker_start = (r_whisker_x - rows // 2, r_whisker_y - cols // 2)
M= cv2.getRotationMatrix2D((cols//2, rows//2),r_angle, 1)
dst = cv2.warpAffine(sticker_r_whisker, M,(cols, rows), borderValue=(255,255,255))
sticker_area = img_show[pin_r_whisker_start[1]: pin_r_whisker_start[1] + cols,\
pin_r_whisker_start[0]: pin_r_whisker_start[0] + rows]
# img_show에서 원본으로 사용할 부분을 변수로 저장한다.
img_show[pin_r_whisker_start[1]: pin_r_whisker_start[1] + cols,\
pin_r_whisker_start[0]: pin_r_whisker_start[0] + rows] = \
np.where(dst==255, sticker_area,dst).astype(np.uint8)
# 스티커 붙이기. 스티커가 완전한 백색인 부분은 붙이지 않는다. sticker_area 사용