A.I
moviepy와 opencv로 동영상 처리(칼만필터 적용) 본문
Camera Sticker¶
- mkdir -p ~/aiffel/video_sticker_app/models
- mkdir -p ~/aiffel/video_sticker_app/images
- cd ~/aiffel/video_sticker_app
- wget https://aiffelstaticprd.blob.core.windows.net/media/documents/new_video_sticker_app.zip
- unzip new_video_sticker_app.zip
프로젝트 (1) moviepy로 비디오 처리하기¶
1. moviepy를 이용해서 주피터 노트북 상에서 비디오를 읽고 쓰는 프로그램을 작성¶
In [1]:
from moviepy.editor import VideoClip, VideoFileClip
from moviepy.editor import ipython_display
import cv2
import numpy as np
import os
In [2]:
# 읽기
video_path = os.getenv('HOME')+'/aiffel/video_sticker_app/images/video2.mp4'
clip = VideoFileClip(video_path)
clip = clip.resize(width=640)
clip.ipython_display(fps=30, loop=True, autoplay=True, rd_kwargs=dict(logger=None))
# 쓰기
result_video_path = os.getenv('HOME')+'/aiffel/video_sticker_app/images/mvpyresult.mp4'
clip.write_videofile(result_video_path)
chunk: 18%|█▊ | 53/297 [00:00<00:00, 529.04it/s, now=None]
Moviepy - Building video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4. MoviePy - Writing audio in mvpyresultTEMP_MPY_wvf_snd.mp3
t: 5%|▌ | 22/404 [00:00<00:01, 218.01it/s, now=None]
MoviePy - Done. Moviepy - Writing video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4
Moviepy - Done ! Moviepy - video ready /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4
2. moviepy 로 읽은 동영상을 numpy 형태로 변환하고 영상 밝기를 50% 어둡게 만든 후에 저장¶
In [3]:
# 읽기
video_path = os.getenv('HOME')+'/aiffel/video_sticker_app/images/video2.mp4'
clip = VideoFileClip(video_path)
clip = clip.resize(width=640)
clip.ipython_display(fps=30, loop=True, autoplay=True, rd_kwargs=dict(logger=None))
# clip 에서 numpy 로 데이터 추출
vlen = int(clip.duration*clip.fps)
video_container = np.zeros((vlen, clip.size[1], clip.size[0], 3), dtype=np.uint8)
for i in range(vlen):
img = clip.get_frame(i/clip.fps)
video_container[i] = (img * 0.5).astype(np.uint8)
# 새 clip 만들기
dur = vlen / clip.fps
outclip = VideoClip(lambda t: video_container[int(round(t*clip.fps))], duration=dur)
# 쓰기
result_video_path2 = os.getenv('HOME')+'/aiffel/video_sticker_app/images/mvpyresult2.mp4'
outclip.write_videofile(result_video_path2, fps=30)
t: 12%|█▏ | 48/403 [00:00<00:00, 476.71it/s, now=None]
Moviepy - Building video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult2.mp4. Moviepy - Writing video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult2.mp4
Moviepy - Done ! Moviepy - video ready /home/ssac24/aiffel/video_sticker_app/images/mvpyresult2.mp4
3. 영상을 읽고 쓰는 시간을 측정해 주세요. OpenCV를 사용할 때와 차이를 측정¶
In [4]:
# CASE 1 : moviepy 사용
start = cv2.getTickCount()
clip = VideoFileClip(video_path)
clip = clip.resize(width=640)
vlen = int(clip.duration*clip.fps)
video_container = np.zeros((vlen, clip.size[1], clip.size[0], 3), dtype=np.uint8)
for i in range(vlen):
img = clip.get_frame(i/clip.fps)
video_container[i] = (img * 0.5).astype(np.uint8)
dur = vlen / clip.fps
outclip = VideoClip(lambda t: video_container[int(round(t*clip.fps))], duration=dur)
mvpy_video_path = os.getenv('HOME')+'/aiffel/video_sticker_app/images/mvpyresult.mp4'
outclip.write_videofile(mvpy_video_path, fps=30)
time = (cv2.getTickCount() - start) / cv2.getTickFrequency()
print (f'[INFO] moviepy time : {time:.2f}ms')
t: 12%|█▏ | 48/403 [00:00<00:00, 411.31it/s, now=None]
Moviepy - Building video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4. Moviepy - Writing video /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4
Moviepy - Done ! Moviepy - video ready /home/ssac24/aiffel/video_sticker_app/images/mvpyresult.mp4 [INFO] moviepy time : 3.07ms
In [5]:
# CASE 2 : OpenCV 사용
start = cv2.getTickCount()
vc = cv2.VideoCapture(video_path)
cv_video_path = os.getenv('HOME')+'/aiffel/video_sticker_app/images/cvresult.mp4'
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
vw = cv2.VideoWriter(cv_video_path, fourcc, 30, (640,360))
vlen = int(vc.get(cv2.CAP_PROP_FRAME_COUNT))
for i in range(vlen):
ret, img = vc.read()
if ret == False: break
img_result = cv2.resize(img, (640, 360)) * 0.5
vw.write(img_result.astype(np.uint8))
time = (cv2.getTickCount() - start) / cv2.getTickFrequency()
print (f'[INFO] cv time : {time:.2f}ms')
[INFO] cv time : 1.60ms
4. moviepy 를 이용할 때의 장단점을 분석¶
- OpenCV의 읽기, 쓰기 속도가 moviepy보다 더 빠르다
- moviepy는 저장된 영상 분석만 가능한 것 같다.
프로젝트 (2) 어디까지 만들고 싶은지 정의하기¶
1. 실시간 카메라 스티커앱을 만들기¶
- cd ~/aiffel/video_sticker_app && python webcam_sticker.py
In [ ]:
# webcam_sticker.py
import numpy as np
import cv2
import dlib
from newaddsticker import img2sticker
detector_hog = dlib.get_frontal_face_detector()
landmark_predictor = dlib.shape_predictor('./models/shape_predictor_68_face_landmarks.dat')
def main():
cv2.namedWindow('show', 0)
cv2.resizeWindow('show', 640, 360)
vc = cv2.VideoCapture(0) # 연결된 영상 장치의 인덱스, 하나만 있는 경우 0을 사용
img_sticker = cv2.imread('./images/king.png')
vlen = int(vc.get(cv2.CAP_PROP_FRAME_COUNT))
print (vlen) # 웹캠은 video length 가 0 입니다.
# 정해진 길이가 없기 때문에 while 을 주로 사용합니다.
# for i in range(vlen):
while True:
ret, img = vc.read()
if ret == False:
break
start = cv2.getTickCount()
img = cv2.flip(img, 1) # 보통 웹캠은 좌우 반전
# 스티커 메소드를 사용
img_result = img2sticker(img, img_sticker.copy(), detector_hog, landmark_predictor)
time = (cv2.getTickCount() - start) / cv2.getTickFrequency() * 1000
print ('[INFO] time: %.2fms'%time)
cv2.imshow('show', img_result)
key = cv2.waitKey(1)
if key == 27:
break
if __name__ == '__main__':
main()
2. 스티커앱을 실행하고 카메라를 고정하고 서서히 멀어져봅니다. 혹은 아주 가까이 다가가 봅니다. 얼굴을 찾지 못하는 거리를 기록¶
- 거리측정시 30cm이상부터 최대 2m정도까지는 인식이 가능했다
3. 다시 자리로 돌아온 후 고개를 상하좌우로 움직여주세요. yaw, pitch, roll 각도의 개념을 직접 실험해 보고 각각 몇 도까지 정상적으로 스티커앱이 동작하는지 기록¶
- yaw : y축 기준 회전 → 높이 축 -45 ~ 45도
- picth : x축 기준 회전 → 좌우 축 -30 ~ 30도
- roll : z축 기준 회전 → 거리 축 roll : -45 ~ 45도
4. 만들고 싶은 스티커앱의 스펙(허용 거리, 허용 인원 수, 허용 각도, 안정성)¶
- 거리 : 40cm ~ 1.5m → 셀카를 찍기 위한 성인 기준 팔길이 평균치를 최소로 잡고 단체사진을 위한 거리를 최대로
- 인원 수 : 1인 ~ 4인 → 5인 이상 집합금지 기준 모임 최대 수
- 허용 각도 : yaw : -45 ~ 45도, roll: -45 ~ 45도, pitch -45 ~ 45도 → 일반적으로 얼굴의 반정도가 보이는 내에서 자유롭게 찍을수 있는 각도 설정
- 안정성 : 위 조건을 만족하면서 칼만 필터를 적용하여 노이즈를 최소화한다
프로젝트 (3) 스티커 Out Bound 예외처리 하기¶
1. 지금까지 만든 스티커앱을 이용해서 예외 상황을 찾아주세요. 특히 서서히 영상에서 좌우 경계 밖으로 나가며 코드의 행동을 확인¶
Traceback (most recent call last):
File "webcam_sticker.py", line 42, in <module>
main()
File "webcam_sticker.py", line 30, in main
img_result = img2sticker(img, img_sticker.copy(), detector_hog, landmark_predictor)
File "/home/ssac24/aiffel/video_sticker_app/newaddsticker.py", line 42, in img2sticker
cv2.addWeighted(sticker_area, 1.0, img_sticker, 0.7, 0)
cv2.error: OpenCV(4.5.1) /tmp/pip-req-build-hj027r8z/opencv/modules/core/src/arithm.cpp:666: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array' in function 'arithm_op'
- 에러가 나면서 종료된다. 이유는 얼굴을 인식하는 설정값을 벗어나기때문인 듯
2. 문제가 어디에서 발생하는지 코드에서 확인¶
- 얼굴이 카메라 왼쪽 경계를 벗어나서 detection 되는 경우 refined_x 의 값이 음수가 되어 img_bgr[..., refined_x:...] 에서 numpy array의 음수 index에 접근하게 되므로 예외가 발생
if refined_x < 0: img_sticker = img_sticker[:, -refined_x:] refined_x = 0 elif refined_x + img_sticker.shape[1] >= img_orig.shape[1]: img_sticker = img_sticker[:, :-(img_sticker.shape[1]+refined_x-img_orig.shape[1])]
3. Out bound 오류(경계 밖으로 대상이 나가서 생기는 오류)를 해결¶
In [ ]:
# newaddsticker.py의 img2sticker 메소드에서 아래 부분을 수정
import dlib
import cv2
def img2sticker(img_orig, img_sticker, detector_hog, landmark_predictor):
# preprocess
img_rgb = cv2.cvtColor(img_orig, cv2.COLOR_BGR2RGB)
# detector
dlib_rects = detector_hog(img_rgb, 0)
if len(dlib_rects) < 1:
return img_orig
# landmark
list_landmarks = []
for dlib_rect in dlib_rects:
points = landmark_predictor(img_rgb, dlib_rect)
list_points = list(map(lambda p: (p.x, p.y), points.parts()))
list_landmarks.append(list_points)
# head coord
for dlib_rect, landmark in zip(dlib_rects, list_landmarks):
x = landmark[30][0] # nose
y = landmark[30][1] - dlib_rect.width()//2
w = dlib_rect.width()
h = dlib_rect.width()
break
# sticker
img_sticker = cv2.resize(img_sticker, (w,h), interpolation=cv2.INTER_NEAREST)
refined_x = x - w // 2
refined_y = y - h
if refined_x < 0:
img_sticker = img_sticker[:, -refined_x:]
refined_x = 0
elif refined_x + img_sticker.shape[1] >= img_orig.shape[1]:
img_sticker = img_sticker[:, :-(img_sticker.shape[1]+refined_x-img_orig.shape[1])]
img_bgr = img_orig.copy()
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]] = \
cv2.addWeighted(sticker_area, 1.0, img_sticker, 0.7, 0)
return img_bgr
4. 다른 예외는 어떤 것들이 있는지 정의¶
- 얼굴이 정면 인식이 아닐 경우에 스티커또한 그에 맞게 설정가능 해야한다.
프로젝트 (4) 스티커앱 분석 - 거리, 인원 수, 각도, 시계열 안정성¶
1. 멀어지는 경우에 왜 스티커앱이 동작하지 않는지 분석해주세요. detection, landmark, blending 단계 중 문제인 곳 찾기¶
- dlib detection이 문제다. 멀어지면 detector_hog 단계에서 bbox가 출력되지 않는다.
- 얼굴을 검출하는 사각형보다 얼굴이 작아서 인식하지 못하는 것 같다.
2. detector_hog의 문제를 해결하기 위해, 이미지 피라미드를 조절하여 성능을 향상시키는 간단한 방법을 활용하여 img2sticker 메소드를 간단히 고치기¶
In [ ]:
def img2sticker(img_orig, img_sticker, detector_hog, landmark_predictor):
# preprocess
img_rgb = cv2.cvtColor(img_orig, cv2.COLOR_BGR2RGB)
# detector
img_rgb_vga = cv2.resize(img_rgb, (640, 360))
dlib_rects = detector_hog(img_rgb_vga, 1) # <- 이미지 피라미드 수 변경
if len(dlib_rects) < 1:
return img_orig
# landmark
list_landmarks = []
for dlib_rect in dlib_rects:
points = landmark_predictor(img_rgb_vga, dlib_rect)
list_points = list(map(lambda p: (p.x, p.y), points.parts()))
list_landmarks.append(list_points)
3. 위에서 새롭게 시도한 방법의 문제점¶
- 기존 30ms/frame에서 120ms/frame으로 느려짐 → 실시간 구동이 불가능
4. 실행시간을 만족할 수 있는 방법 찾기¶
hog 디텍터를 딥러닝 기반 디텍터로 변경할 수 있다. hog 학습 단계에서 다양한 각도에 대한 hog 특징을 모두 추출해서 일반화 하기 어렵기 때문에 딥러닝 기반 검출기의 성능이 훨씬 좋기때문.
opencv는 intel cpu 을 사용할 때 dnn 모듈이 가속화를 지원하고 있으므로 mobilenet과 같은 작은 backbone 모델을 사용하고 ssd 를 사용한다면 충분히 만족할 만한 시간과 성능을 얻을 수 있다.
5. 인원 수, 각도 등 각 문제에 대해서 1-4번을 반복한다¶
- Facial Landmark Detection
- 인원 수가 2명 이상일 경우, landmark 문제가 생길 수 있다.
- 각도가 설정 이상일 경우, landmark를 검출할 수 없는 각도일 때는 인식이 되지 않을 수 있다.
- 이미지피라미드 조절 같은 모델 변경의 경우 사람이 증가할 수록 처리 시간이 증가하여, 동시 스티커출력에 문제가 생긴다.
프로젝트 (5) 칼만 필터 적용하기¶
1. 카메라 앞에서 가만히 있을 때 스티커의 움직임을 관찰할 때 어떤 문제가 발생하는가?¶
- 가만히 있어도 스티커의 크리가 일정하게 유지되지 않고, 떨리는 것처럼 보이는 현상이 발생
2. 이론 강의에서 배운 칼만 필터를 적용해서 스티커 움직임을 안정화¶
In [ ]:
import numpy as np
import cv2
import dlib
from kfaddsticker import img2sticker_kf
detector_hog = dlib.get_frontal_face_detector()
landmark_predictor = dlib.shape_predictor('./models/shape_predictor_68_face_landmarks.dat')
def main():
cv2.namedWindow('show', 0)
cv2.resizeWindow('show', 640, 360)
#vc = cv2.VideoCapture(0)
vc = cv2.VideoCapture('./images/video2.mp4')
img_sticker = cv2.imread('./images/king.png')
vlen = int(vc.get(cv2.CAP_PROP_FRAME_COUNT))
print (vlen) # 웹캠은 video length 가 0 입니다.
# 정해진 길이가 없기 때문에 while 을 주로 사용합니다.
# for i in range(vlen):
while True:
ret, img = vc.read()
if ret == False:
break
start = cv2.getTickCount()
img = cv2.flip(img, 1) # 보통 웹캠은 좌우 반전
# 스티커 메소드를 사용
img_result = img2sticker_kf(img, img_sticker.copy(), detector_hog, landmark_predictor)
time = (cv2.getTickCount() - start) / cv2.getTickFrequency() * 1000
print ('[INFO] time: %.2fms'%time)
cv2.imshow('show', img_result)
key = cv2.waitKey(1)
if key == 27:
break
if __name__ == '__main__':
main()
'Going Deeper' 카테고리의 다른 글
Human-in-the-loop & Active Learning (0) | 2021.05.05 |
---|---|
OCR을 활용하여 글자 검출 (0) | 2021.05.05 |
동영상에 스티커 붙이기 (0) | 2021.05.05 |
Camera Sticker 붙여보기 (0) | 2021.04.26 |
OCR의 개요 (0) | 2021.04.24 |