세번째 이야기
0.
그간 시험기간이 있어서 꽤나 정체기가 있었으나 나름 잘 마무리가 되었다. SW 코딩은 전부 끝난 상태인데 그 과정을 두~세 글로 나누어서 써볼 심산이다.
이번 글에는 추가된 효과들을 설명하겠다.
음 설명에 앞서 어떤 것 들이 있는지 영상으로 보고 하나하나 설명해 주겠다.
위 영상 멘트는 회사에서 제출한 비디오 일부라서.. 안들어도 된당.
먼저 왜 검은 사진으로 된 화면이냐면, 비디오로 내 얼굴이 나오게 했었지만, 상단 부에 있는 동영상 광고와 내 캠 화면이 동시에 따로 움직이다 보니 렉이 걸리는 모습이 있었으나 사진으로 이를 보완했고, 스마트 미러답게 화면이 나올 필요 없이 거울 뒤에 나오는 다양한 효과를 보여주는 것이 핵심이니 거울 역할을 잘 할 수 있게끔 검정색 화면으로 했다.
아무튼 본론으로 들어가기 앞서 각 효과들을 정리해 주자면,
1. 배터리 모양과 배터리 차는 효과
2. 미소 완료 후 플래시 화면(번쩍이는 화면)
3. 배터리 모양 반대편에 지금 껏 몇명의 사람이 웃었는지 보여주는 스택
4. 미소 완료 후 미소 이모티콘이 스택으로 날아가는 효과
하나씩 살펴보자!
1. 배터리
import cv2
import math
X = 1280
Y = 960
def gauge(total, cur, img): # 웃으면서 배터리 채우기
# total:총 웃을 시간 cur: 누적 웃음 시간
p_x = int(X - X / 5) - 25 # 955
p_y = int(Y / 4) + 60 # 300
d_y = 18
d_x = -23
if cur >= total * (1 / 3): # 1/3 기준치 웃었을 때
cv2.rectangle(img, (p_x, p_y), (p_x + 90, p_y + d_y), tupleGreen, -1)
if cur > total * (2 / 3): # 2/3 기준치 웃었을 때
cv2.rectangle(img, (p_x, p_y + d_x), (p_x + 90, p_y + d_x + d_y), tupleGreen, -1)
if cur >= total: # 기준치 이상 웃을 때
cv2.rectangle(img, (p_x, p_y + 2 * d_x), (p_x + 90, p_y + 2 * d_x
+ d_y), tupleGreen, -1)
def blank(img):
p_x = int(X - X / 5) # 980
p_y = int(Y / 4) + 1 # 230
cv2.rectangle(img, (p_x, p_y), (p_x + 40, p_y + 6), tupleWhite, 8, -1)
# 껍데기. -1로 비워 놓음
cv2.rectangle(img, (p_x - 30, p_y + 6), (p_x + 70, p_y + 82), tupleWhite, 8, 0)
# 껍데기 위 조그마한 사각형. 0 으로 채워 넣음
cv2.rectangle(img, (p_x - 30, p_y + 53), (p_x + 70, p_y + 56), tupleWhite, 3, 0)
# 배터리 구분 칸 1
cv2.rectangle(img, (p_x - 30, p_y + 33), (p_x + 70, p_y + 30), tupleWhite, 3, 0)
# 배터리 구분 칸 2
코드가 길어 보이지만 그러하지 않다. 주석이 많당.
어떤 화면 크기에 동영상 화면이 비춰질지 몰라서 X,Y 처럼 임의의 값을 기준으로 계산했다. 무슨 의미냐면, 디스플레이 가로와 세로 화질에 따라서 픽셀 값이 바뀌는데 사전에는 몇 픽셀일지 몰라 어떤 크기가 와도 능동적으로 바뀌게끔 X,Y 값만 바꾸면 위치가 조절 되게끔 했다.
아무튼! 봐야할 코드는 rectangle로 그림을 그렸는데 맨 위에 꼬다리 부분 작은거 하나랑 배터리 몸통 그리고 배터리 몸통을 3등분할 작은 두 개의 사각형으로 총 4개로 구성했다. (blank 함수)
gauge 함수를 보면 총 웃어야 하는 시간 중에서 3등분 해서 채워지게 끔 했다. 저번 글에서 시간 측정이 다르게 돌아가서 애를 먹었다고 했는데 사실 다른 방법이 있었다.
main 함수의 하단부에 아래 와 같은 코드 가 있다.
while True:
...
cv2.waitKey(20)
20ms 마다 잠시 재운다는 의미이다. 즉, 20ms 주기로 코드가 반복 된다는 의미. 이를 이용해서 시간 계산을 하는 것이다. 즉, 2초간 웃게 하고 싶다면 count 변수를 두어서 100번 ++되면 2초 지난 거라고 처리하면 된다아!
2. 미소 완료 후 플래시 화면
X = ui.X
Y = ui.Y
cap.set(3, X)
cap.set(4, Y)
flash = False #미소 완료 후 찰칵 화면
flashImg = ['darkgray.PNG','lightgray.PNG','white.PNG','lightgray.PNG','darkgray.PNG']
while 1:
for (fx,fy,fw,fh) in faces:
...
얼굴인식
...
smile = smile_cascade.detectMultiScale( roi_gray,scaleFactor= 1.2,minNeighbors=int(smileFactor), minSize=(20, 20))
for (x, y, w, h) in smile:
...
미소인식
...
if total_smile >= PIVOT:
if flash == False:
for i in flashImg:
whiteCameraImg = cv2.imread(i)
whiteCameraImg = cv2.resize(whiteCameraImg, dsize = (X, Y), interpolation = cv2.INTER_LINEAR)
#print(whiteCameraImg) #이게 왜 돼?
flash = True
...
각종 효과
...
if interval >= TEXT_TIME:#미소 완료문구 띄우는 시간 지나가면
...
미소 후처리
...
flash = False
cv2.imshow('smiller', smiller)
k = cv2.waitKey(20) & 0xff
if k == 27:
break
total_smile == PIVOT이 되는 그 순간. 즉, 기준치 웃음 시간을 채웠을 때! 찰칵! 효과를 내민다. flashImg에 우리가 필요한 화면 사진목록들이 있는 것을 볼 수 있다. 검정에서 하얀색 가는 모습을 자연스럽게 띄우기 위해 어둡 회색-> 밝은 회색 -> 흰색 -> 밝은 회색->어둡 회색 순으로 움직이게 했다.
근데 아마 이 코드를 보고 당황했을 분들이 있을 것이다. 처음 터지는 그 순간 총 5장의 사진(배경)이 지나가야 하는데, 위 방식은 그냥 for문 다 돌아서 마지막 사진만 가져가니 즉 어둡 회색만 잠깐 비추고 끝나는 거 아냐?
근데 된다. 정말 넌센스..
사실 저 코드가 나온 이유는 코드를 완성하는 과정 중에서 되버렸다. 원래는 웃음 이후에 5개의 화면이 스샤샤샥 지나가게끔 해야 하지만 그 전에 imread()로 정말 사진이 바뀔까? 만 하려 했는데, 되더라~ 아직도 모르겠다. 출력해보니 아래와 같이 나왔다.
![]() |
![]() |
참말로 이상하다. 당연히 원래 구현하려는 대로 해보았지만, 이 구현 방식이 훨씬 깔끔하고 좋아서 이대로 하기로 했다..ㅋㅋㅋ
3. 누적 미소 인원 수 스택
이 스택같은 경우는 간단하게 하얀색 껍데기와 그 내부 초록 부분을 그려내면 되었다. 이때 openCV가 사각형을 그릴때 특이한 점이 바로
img 다음에 두 개의 지점을 주어야 하는데, 좌상단 우 하단 두 점을 주면 되어서 좌 상단의 높이값을 점점 감소 시켜 사각형이 커지게 하여 스택이 쌓이는 효과를 내었다. (화면의 제일 좌상단이 (0,0)이고 제일 밑 오른쪽이 (X,Y)이다.)
base = int(X / 12) # 106
top = int(Y / 10) * 8 # 576
def stack(coin, img): # 미소 인원 수 보여주기
cv2.rectangle(img, (base - 8, top + 1), (base + 18, top + 8), tupleWhite, -1)
cv2.rectangle(img, (base - 8, top - DONATE * 10), (base - 1, top + 2), tupleWhite, -1)
cv2.rectangle(img, (base + 11, top - DONATE * 10), (base + 18, top + 2), tupleWhite, -1)
# 스택 껍데기
cv2.rectangle(img, (base, top - coin * 10), (base + 10, top), tupleCoin, -1)
# coin으로 좌측 상단의 y값을 감소 = 위로 쌓이는 효과
4. 미소 이모티콘 날아가는 효과
코드 부터 보여주겠다!
#main 부분
...
얼굴 감지
...
...
미소 감지
...
if total_smile >= PIVOT:
if flash == False:
#플래시 화면
nx = top - 50*10 - 10
ny = base
#nx,ny -> 최종 위치(스택 맨 윗 부분)
tx = x+fx/2+w
ty = y+fy+(h/2*3)
#tx,ty -> 시작 위치 (미소 부분, 즉 빨간색 사각형)
cur_x = tx
cur_y = ty
#cur_x, cur_y 현재 이미지 위치
a = (ty-ny)/((cur_x-nx)*(cur_x-nx))
#그래프 기울기
dx = (nx-tx)/20 #x 변화량
flash = True
ui.movSmile(int(cur_x),int(cur_y),whiteCameraImg)
cur_x += dx #x값 움직이고~
cur_y = a*(cur_x-nx)*(cur_x-nx)+ny #y에 반영하고
#ui의 movSmile 함수
smiling = cv2.imread('smiling.PNG') # 미소 이모티콘
def movSmile(cur_x, cur_y,img):#x,y가 원점인 이차함수
'''
y = a(x-tx)^2+ty
dx = (nx-tx) * temp / movframe
dy = f(dx)
a = (ty-ny)/((cur_x-nx)*(cur_x-nx))
'''
smaller = cv2.resize(smiling, (50,50), interpolation=cv2.INTER_LINEAR)
#크기 조정
width, height, channel = smaller.shape
width = int(width)
height = int(height)
img[cur_x:cur_x+width, cur_y:cur_y+height] = smaller
#img값은 컴퓨터 입장에서 하나의 숫자 배열 이니 슬라이싱 해서 그 값을 바꾸기
전반적으로 말해주자면, 미소의 위치에서 부터 스택의 상단 부분 까지 이차함수를 따라가게끔 하여 ui.movSmile에 그때의 위치 값을 주어 본래의 이미지 위에 미소 이모티콘을 덧붙였다.
이차함수 그래프는 y = a(x-b)^2 + c로 했고 b와 c는 최종 목적지인 nx,ny 이고 a 값은 그래프에 시작 위치 (tx,ty)를 넣었을때 등식이 성립하게끔 하는 y값을 고르게 했다. (별거 아니죵?)
dx 부분을 보면 20으로 나누는 것을 볼 수 있는데, frame 값이 20이기 때문이다.
마무리
아무튼 이렇게 까지 효과를 추가한 부분이고 다음 글에서는 상황에 맞게 인식 기능을 개선한 모습들을 보여주겠다. 아마 스마트 미러 완성된 모습도 함께 할 것 같당.