Ex3-고양이 수염 달기_회전에 강건하게(1)

안 형준·2021년 7월 30일
5

Aiffel/Archives

목록 보기
2/12

스티커를 3 부분으로 나누어 진행, 회전에 대해 강건하게 만들기
고양이 수염을 보고 저것을 왼쪽, 오른쪽, 코로 나누어 진행하면 좋겠다는 생각으로 나누어봤습니다.
또한 회전 후 시작 좌표를 찍을 때, 그동안 삽질을 많이 했는데, 결국 정착한 방법이 여러 사진에 대해 안정적으로 괜찮은 성능을 내는 것 같아 공유드립니다.

0. import dependencies

import numpy as np
import math

landmarks = list_landmarks[0]

1. 코, 왼쪽 수염, 오른쪽 수염 분리

스티커는 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

2. 기준으로 삼을 특징점 선정

  • 코: 29, 27, 31, 35, 30
    (29,27)은 회전각을 구하기 위해, (31, 35)는 코의 크기를 상대적으로 구하기 위해,
    30은 중심점으로 활용

  • 왼쪽 수염: 2, 29
    회전각과 크기에 활용

  • 오른쪽 수염: 14, 29
    회전각과 크기에 활용

3. 회전시키기

arctan을 이용하여 한 특징점에서 다른 특징점까지의 벡터로부터 각도를 추출했습니다.
회전에서 주의해 주어야 할 것이 arctan의 치역은 (-pi/2, pi/2)인데, 이로 인해 사람의 얼굴이 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 코를 회전시키는 각도를 다르게 해야 하는 문제가 생깁니다.

코:

  • 27번 특징점에서 29번 특징점으로의 벡터 활용:
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

왼쪽 수염:

  • 2번 특징점에서 29번 특징점으로의 벡터 활용:
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)  # 머리가 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 각의 부호가 달라짐

오른쪽 수염:

  • 14번 특징점에서 29번 특징점으로의 벡터 활용:
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)  # 머리가 오른쪽으로 기울었는지, 왼쪽으로 기울었는지에 따라 각의 부호가 달라짐

4. 이미지 내 지표를 이용하여 크기 구하기

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, 볼 밖으로 나오지 않게 하기 위해 사이즈를 줄였습니다

5. 기준점 찾기

중심점을 찾는게 가장 편하고, 여러 사진에 안정적으로 작동했습니다.
이후 스티커를 붙일 때, 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

6. 스티커 붙이기

코:

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 사용

7. 결과


8. 예상되는 문제점

  • 90도 이상 회전된 사진에는 작동하지 않을 수 있습니다.
profile
물리학과 졸업/ 인공지능 개발자로의 한 걸음

0개의 댓글