[OpenCV] OpenCV 기초

메르센고수·2023년 12월 25일
0

OpenCV

목록 보기
2/9
post-thumbnail

크리스마스에도 공부를 해야하는 컴공생이라니...슬프지만 쩔수 없는 것 같다 어쩌면 컴공에 들어온 순간 이미 예견된 미래일지도...? 그저 매일 나의 부족함을 느끼기 때문에 더 노력해야지라는 생각 뿐인 것 같다. 크리스마스 선물인지 2학기 성적들이 나오기 시작하는데 크리스마스의 악몽만 아니였음 좋겠다ㅎ


OpenCV와 NumPy의 자료형을 비교해보면 위의 표와 같다. 단순히 표현 방식만 다를 뿐 같은 의미를 갖는다는 것을 알 수 있다.

import cv2

img1=cv2.imread('cat.bmp',cv2.IMREAD_GRAYSCALE)
img2=cv2.imread('cat.bmp',cv2.IMREAD_COLOR)

print('type(img1):', type(img1)) #type(img1): <class 'numpy.ndarray'>
print('img1.shape:',img1.shape) #img1.shape : (480,640)
print('img2.shape:',img2.shape) #img2.shape : (480,640,3)
print('img2.dtype:',img2.dtype) #img2.dtype: uint8

h,w=img2.shape[:,2] #h:480, w:640 -> 3개의 tuple 중에서 앞의 2개만 가져옴
print('img2 size: {} x {}'.format(w,h))

if len(img1.shape)==2:
	print('img1 is a grayscale image')
elif len(img1.shape)==3:
	print('img1 is a truecolor image')


위의 코드를 돌리면 다음과 같이 출력된다.
.shape로 출력했을 때 이미지의 크기가 출력되는 것을 알 수 있는데, 이미지가 흑백영상인지 컬러 영상인지에 따라 튜플의 element가 (h,w)인지 (h,w,3)인지가 달라진다. 그 이유는 컬러 영상의 경우 R,G,B 3요소 만으로 전체의 색상을 나타내기 때문이다.

영상 생성, 복사, 부분 영상 추출

지정한 크기로 새 영상 출력

numpy.empty(shape, dtype=float, ...) -> arr
numpy.zeros(shape, drype=float, ...) -> arr
numpy.ones(shape, dtype=None, ...) -> arr
numpy.full(shape, fill_value, dtype=None, ...) -> arr

1) shape : 각 차원의 크기 {GrayScale : (h,w)}, {Color : (h,w,3)}
2) dtype : 원소의 데이터 타입. 일반적인 영상이면 numpy.uint8
3) arr : 생성된 영상
4) 참고사항

img1 = np.empty((240, 320), dtype=np.uint8)       # grayscale image
img2 = np.zeros((240, 320, 3), dtype=np.uint8)    # color image
img3 = np.ones((240, 320), dtype=np.uint8) * 255  # dark gray
img4 = np.full((240, 320, 3), (0, 255, 255), dtype=np.uint8)  # yellow


여기서 img4를 보면, shape 말고도 (0,255,255)를 지정해준 것을 확인할 수 있다. 그 이유는 노랑색을 RGB로 표현하면, Red+Green인데 OpenCV의 경우 BGR 순으로 색상을 지정하기 때문에 G와 R에 최댓값 255를 지정하게 되면 노랑색이 화면 사이즈에 맞게 출력되게 된다.

부분 영상 추출

# 부분 영상 추출
img1 = cv2.imread('HappyFish.jpg')

img2 = img1[40:120, 30:150]  # numpy.ndarray의 슬라이싱
img3 = img1[40:120, 30:150].copy()

cv2.circle(img2,(50,50),20,(0,0,255),2) 
#(50,50)을 중심으로 하고 반지름이 20이면서 두께가 2인 원

img2.fill(0) # img2에 적용했는데 img1에도 적용됨 (img1을 슬라이싱 해왔기 때문)

코드를 해석해보면, img1은 이미지를 불러오고 img2는 높이가 40~120 너비가 30~150인 사각형 모양을 추출해서 img3에 복사를 하는데, img2.fill(0)으로 인해 img2는 검정 화면이 출력되게 되고, img3는 그대로 출력이 되게 된다.

마스크 연산과 ROI

  • ROI : Region of Interest = 영상에서 특정 연산을 수행하고자 하는 임의의 부분 영역
  • 마스크 연산
    - OpenCV는 일부 함수에 대해 ROI 연산을 지원하며, 이 때 마스크 영상을 인자로 함께 전달해야한다
    (e.g.) cv2.copyTo(), ccv2.calcHist(), cv.bitwise_or(), cv2.matchTemplate(), etc.
    • 마스크 영상은 cv2.CV_8UC1 타입 (그레이스케일 영상)
    • 마스크 영상의 픽셀 값이 0이 아닌 위치에서만 연산이 수행된다

마스크 연산을 지원하는 픽셀 값 복사 함수

cv2.copyTo(src, mask, dst=None) -> dst

1) src : 입력 영상
2) mask : 마스크 영상 ... 0이 아닌 픽셀에 대해서만 복사 연산을 수행
3) dst : 출력 영상. 만약 src와 크기 및 타입이 같은 dst를 입력으로 지정하면 dst를 새로 생성하지 않고 연산을 수행하는데, 그렇지 않다면 dst를 새로 생성하여 연산을 수행한 후 반환한다.

# 마스크 영상을 이용한 영상 합성
src = cv2.imread('airplane.bmp', cv2.IMREAD_COLOR)
mask = cv2.imread('mask_plane.bmp', cv2.IMREAD_GRAYSCALE)
dst = cv2.imread('field.bmp', cv2.IMREAD_COLOR)

cv2.copyTo(src,mask,dst) # dst가 없으면 검정 배경에 비행기만 보임

위의 코드는 비행기 사진을 풀과 맑은 하늘이 있는 배경에 집어 넣으려는 code이다. 결과물을 보면 알겠지만, 결국 이미지 합성에 대한 code이다.

또한 이런식으로도 할 수 있다.

OpenCV 그리기 함수

주의 사항

  • 그리기 알고리즘을 이용하여 영상의 픽셀 값 자체를 변경
    -> 원본 영상이 필요하면 복사본을 만들어서 그리기 & 출력
  • 그레이스케일 영상에는 컬러로 그릴 수 없다.
    -> cv2.cvtColor()로 BGR 컬러영상으로 변환한 뒤 그리기 함수를 호출해야 한다.
    따라서, 복사본을 만들어서 작업을 하거나 원본 데이터를 백업해 놓는 것이 보다 안전하다.

도형

직선 그리기

cv2.line(img, pt1, pt2, color, thickness=None, lineType=None, shift=None) -> img

1) img : 그림을 그릴 영상
2) pt1, pt2 : 직선의 시작점과 끝점. (x,y) 튜플
3) color : 선 색상 또는 밝기. (B,G,R) 튜플 또는 정수값
4) thickness : 선 두께. 기본 값은 1
5) lineType : 선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
6) shift : 그리기 좌표 값의 축소 비율. 기본 값은 0

사각형 그리기

cv2.rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None) -> img
cv2.rectangle(img, rec, color, thickness=None, lineType=None, shift=None) -> img

cv2.rectangle(img, (50, 200, 150, 100), (0, 255, 0), 2)
# 50,200 좌표에서 (0,255,0) [녹색] 으로 두께가 2인 사각형을 그림
cv2.rectangle(img, (70, 220), (180, 280), (0, 128, 0), -1)
# 70,220 좌표에서 180,280 까지 (0,128,0) [연두] 으로 두께가 -1인 사각형을 그리는데 내부를 채움

1) img : 그림을 그릴 영상
2) pt1, pt2 : 사각형의 두 꼭짓점 좌표 (x,y) 튜플
3) rec : 사각형 위치 정보 (x,y,w,h) 튜플
4) color : 선 색상 또는 밝기. (B,G,R) 튜플 또는 정수값
5) thickness : 선 두께. 기본 값은 1, 음수 (-1)를 지정하면 내부를 채움
6) lineType : 선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
7) shift : 그리기 좌표 값의 축소 비율. 기본 값은 0

원 그리기

cv2.circle(img, center, raduis, color, thickness=None, lineType=None, shift=None) ->img

cv2.circle(img, (300, 100), 30, (255, 255, 0), -1, cv2.LINE_AA)
# (300,100)에서 반지름이 30인 원을 하늘색으로 내부를 채워서 LINE_AA로 그림
# LINE_AA : Anti-Aliasing -> 거친 테두리가 부드러워짐
cv2.circle(img, (300, 100), 60, (255, 0, 0), 3, cv2.LINE_AA)

1) img : 그림을 그릴 영상
2) center : 원의 중심 좌표 (x,y) 튜플
3) radius : 원의 반지름
4) color : 선 색상 또는 밝기. (B,G,R) 튜플 또는 정수값
5) thickness : 선 두께. 기본 값은 1, 음수 (-1)를 지정하면 내부를 채움
6) lineType : 선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
7) shift : 그리기 좌표 값의 축소 비율. 기본 값은 0

다각형 그리기

cv2.polylines(img, pts, isClosed, color, thickness=None, lineType=None, shift=None)->img

pts = np.array([[250, 200], [300, 200], [350, 300], [250, 300]])
cv2.polylines(img, [pts], True, (255, 0, 255), 2)
# pts 좌표에 보라색으로 두께가 2인 폐곡선을 그림 => pts를 보면 사다리꼴임을 알 수 있다.

1) img : 그림을 그릴 영상
2) pts : 다각형 외곽 점들의 좌표 배열. numpy.ndarrray의 리스트
(e.g) [np.array([10,10],[50,50],[10,50]],dtype=np.int32)]
3) isClosed : 폐곡선 여부. True or False 지정
4) color : 선 색상 또는 밝기. (B,G,R) 튜플 또는 정수값
5) thickness : 선 두께. 기본 값은 1,선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
6) 음수 (-1)를 지정하면 내부를 채움
7) lineType : 선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
8) shift : 그리기 좌표 값의 축소 비율. 기본 값은 0

예시 코드

위의 예시 코드들을 합치면 아래와 같은 코드가 된다.

import numpy as np
import cv2

img = np.full((400, 400, 3), 255, np.uint8)

cv2.line(img, (50, 50), (200, 50), (0, 0, 255), 5)
cv2.line(img, (50, 60), (150, 160), (0, 0, 128))

cv2.rectangle(img, (50, 200, 150, 100), (0, 255, 0), 2)
cv2.rectangle(img, (70, 220), (180, 280), (0, 128, 0), -1)

cv2.circle(img, (300, 100), 30, (255, 255, 0), -1, cv2.LINE_AA)
cv2.circle(img, (300, 100), 60, (255, 0, 0), 3, cv2.LINE_AA)

pts = np.array([[250, 200], [300, 200], [350, 300], [250, 300]])
cv2.polylines(img, [pts], True, (255, 0, 255), 2)

text = 'Hello? OpenCV ' + cv2.__version__
cv2.putText(img, text, (50, 350), cv2.FONT_HERSHEY_SIMPLEX, 0.8, 
            (0, 0, 255), 1, cv2.LINE_AA)

cv2.imshow("img", img)
cv2.waitKey()
cv2.destroyAllWindows()

문자열

문자열 출력

cv2.putText(img, text, org, fontFace, fontScale, color, thickness=None,
						lineType=None, bottomLeftOrigin=None) -> img

text = 'Hello? OpenCV ' + cv2.__version__
cv2.putText(img, text, (50, 350), cv2.FONT_HERSHEY_SIMPLEX, 0.8, 
            (0, 0, 255), 1, cv2.LINE_AA)
# 50,350 위치에 SIMPLEX 글꼴, 0.8 크기, 두께가 1, 색상은 빨간색인 text를 적음

1) img : 그림을 그릴 영상
2) test : 출력할 문자열
3) org : 영상에서 문자열을 출력할 위치의 좌측 하단 좌표 (x,y) 튜플
4) fontface : 폰트 종류. cv2.FONTHERSHEY로 시작하는 상수 중 선택
5) fontscale : 폰트 크기 확대/축소 비율
6) color : 선 색상 또는 밝기. (B,G,R) 튜플 또는 정수값
7) thickness : 선 두꼐. 기본 값은 1, 음수 (-1)를 지정하면 내부를 채움
8) lineType : 선 타입. cv2.LINE_4, cv2.LINE_8, cv2.LINE_AA 중 선택, 기본 값은 cv2.LINE_8
9) bottomLeftOrigin : True이면 영상의 좌측 하단을 원점으로 간주. 기본값은 False

카메라와 동영상 처리하기

컴퓨터비전은 말 그래도 컴퓨터에게 인간의 눈을 달아준다는 의미이기 때문에 카메라를 다루는 것을 빼놓을 수 없다. OpenCV에서는 카메라와 동영상으로부터 frame을 받아오는 작업을 VideoCapture 클래스 하나로 처리한다.

카메라 열기

cv2.VideoCapture(index, apiPreference=None) -> retval

1) index : camera_id + domain_offset_id == 시스템 기본카메라를 기본 방법으로열려면 index에 0을 전달
2) apiPreference : 선호하는 카메라 처리 방법을 지정
3) retval : cv2.VideoCapture객체

cv2.VideoCapture.open(index, apiPreference=None) -> retval

4) retval : 성공하면 True, 실패하면 False

동영상, 정지 영상 시퀀스, 비디오 스트림 열기

cv2.VideoCapture(filename, apiPreference=None) -> retval

1) filename : 비디오 파일 이름, 정지 영상 시퀀스, 비디오 스트림 URL 등
2) apiPreference : 선호하는 동영상 처리 방법을 지정
3) retval : cv2.VideoCapture 객체

cv2.VideoCapture.open(filename, apiPreference=None) -> retval

4) retval : 성공하면 True, 실패하면 False

비디오 캡쳐가 준비 되었는지 확인

cv2.VideoCapture.isOpened() -> retval

1) retval : 성공하면 True, 실패하면 False

프레임 받아오기

cv2.VideoCapture.read(image=None) -> retval, image

1) retval : 성공하면 True, 실패하면 False
2) image : 현재 프레임

카메라, 비디오 장치 속성 값 참조

cv2.VideoCapture.get(propId) -> retval

카메라, 비디오 장치 속성 값 참조

cv2.VideoCapture.set(propId, value) -> retval

1) propId : 속성 상수
2) value : 속성 값
3) retval : 성공하면 True, 실패하면 False

예시 코드

import sys
import cv2


# 카메라 열기
cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("Camera open failed!")
    sys.exit()

# 카메라 프레임 크기 출력
print('Frame width:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('Frame height:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

# 카메라 프레임 처리
while True:
    ret, frame = cap.read()

    if not ret:
        break

    inversed = ~frame  # 반전

    cv2.imshow('frame', frame)
    cv2.imshow('inversed', inversed)

    if cv2.waitKey(10) == 27:
        break

cap.release()
cv2.destroyAllWindows()

위의 코드를 실행시키면, 현재 이 코드가 실행되는 디바이스에 내장되어 있는 카메라가 켜지면서 실제 화면과 frame이 반전된 화면이 동시에 출력된다. 그리고 waitkey(10)==27의 의미는 이전 게시물에서 언급했지만 ESC를 누르면 카메라가 종료된다는 의미이다.


동영상 저장하기

OpenCV에서는 cv2.VideoWriter 클래스를 이용하여 일련의 프레임을 동영상 파일로 저장할 수 있다. 단, 일련의 프레임은 모두 크기와 데이터 타입이 같아야 한다.

  • Fourcc (4-문자 코드)
    : 동영상 파일의 코덱, 압축 방식, 색상, 픽셀 포맷 등을 정의하는 정수 값

저장을 위한 동영상 파일 열기

cv2.VideoWriter(filename, fourcc, fps, framesize, isColor=None) -> retval

1) filename : 비디오 파일 이름 (e.g., 'video.mp4')
2) fps : 초당 프레임 수 (frame per second)
3) frameSize : 프레임 크기 (width, height) 튜플
4) isColor : 컬러 영상이면 True, 그렇지 않으면 False
5) retval : cv2.VideoWriter 객체

cv2.VideoWriter.open(filename, fourcc, fps, frameSize, isColor=None) -> retval

비디오 파일이 준비되었는지 확인

cv2.VideoWriter.isOpened() -> retval

예시 코드

import sys
import cv2


# 비디오 파일 열기
cap = cv2.VideoCapture('video1.mp4')

if not cap.isOpened():
    print("Video open failed!")
    sys.exit()

# 비디오 프레임 크기, 전체 프레임수, FPS 등 출력
print('Frame width:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('Frame height:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print('Frame count:', int(cap.get(cv2.CAP_PROP_FRAME_COUNT)))

fps = cap.get(cv2.CAP_PROP_FPS)
print('FPS:', fps)

delay = round(1000 / fps)

# 비디오 매 프레임 처리
while True:
    ret, frame = cap.read()

    if not ret:
        break

    inversed = ~frame  # 반전

    cv2.imshow('frame', frame)
    cv2.imshow('inversed', inversed)

    if cv2.waitKey(delay) == 27:
        break

cap.release()
cv2.destroyAllWindows()

위의 코드를 실행시키면 원숭이가 바나나먹는 영상이 화면에 띄워지는데, 동시에 terminal에 frame에 대한 정보가 출력된다.

키보드 이벤트 처리하기

키보드 입력 대기 함수

cv2.waitKey(delay=None) ->retval

1) delay : 밀리초 단위 대기 시간. delay<=0이면 무한히 기다린다. (기본값 = 0)
2) retval : 눌린 키 값 (ASCII code). 키가 눌리지 않으면 -1
3) 참고사항

- cv2.waitKey() 함수는 OpenCV 창이 하나라도 있을 때 동작한다.
- 특정 키의 입력을 확인하여면 ord() 함수를 이용하면 된다.
```python
while True:
if cv2.waitKey()==ord('q'):
	break
```

예시 코드

import sys
import numpy as np
import cv2


img = cv2.imread('cat.bmp', cv2.IMREAD_GRAYSCALE)

if img is None:
    print('Image load failed!')
    sys.exit()

cv2.namedWindow('image')
cv2.imshow('image', img)

while True:
    keycode = cv2.waitKey()
    if keycode == ord('i') or keycode == ord('I'): # i를 누르면 영상 반전
        img = ~img
        cv2.imshow('image', img)
    elif keycode == 27: # ESC 누르면 화면 닫기
        break

cv2.destroyAllWindows()

마우스 이벤트 처리하기

마우스 이벤트 콜백함수 등록 함수

cv2.setMouseCallback(windowName, onMouse, param=None) -> None

1) windowName : 마우스 이벤트 처리를 수행할 창 이름
2) onMouse : 마우스 이벤트 처리를 위한 콜백 함수 이름
3) param : 콜백 함수에 전달할 데이터

마우스 이벤트 처리 함수 (콜백 함수) 형식

onMouse(event,x,y,flags,param) -> None

1) event : 마우스 이벤트 종류. cv2.EVENT로 시작하는 상수

2) x : 마우스 이벤트가 발생한 x좌표
3) y : 마우스 이벤트가 발생한 y좌표
4) flags : 마우스 이벤트 발생 시 상태. cv2.EVENT_FLAG
로 시작하는 상수

5) param : cv2.setMouseCallbask() 함수에서 설정한 데이터

예시 코드

import sys
import numpy as np
import cv2


oldx = oldy = -1

def on_mouse(event, x, y, flags, param):
    global oldx, oldy

    if event == cv2.EVENT_LBUTTONDOWN:
        oldx, oldy = x, y
        print('EVENT_LBUTTONDOWN: %d, %d' % (x, y))

    elif event == cv2.EVENT_LBUTTONUP:
        print('EVENT_LBUTTONUP: %d, %d' % (x, y))

    elif event == cv2.EVENT_MOUSEMOVE:
        if flags & cv2.EVENT_FLAG_LBUTTON:
            cv2.line(img, (oldx, oldy), (x, y), (0, 0, 255), 4, cv2.LINE_AA)
            cv2.imshow('image', img)
            oldx, oldy = x, y


img = np.ones((480, 640, 3), dtype=np.uint8) * 255

cv2.namedWindow('image')
cv2.setMouseCallback('image', on_mouse, img)

cv2.imshow('image', img)
cv2.waitKey()

cv2.destroyAllWindows()

위의 코드를 실행시키면, 마우스 좌클릭을 할 때마다 좌표를 찍는데, line함수로 두께가 4인 빨간색 선을 그으면서 시작점과 끝지점의 x,y 좌표를 return한다.


첫 번째부터 두 번째 줄이 하트 모양, 세 번째부터 네 번째 줄이 별, 나머지 줄이 막 그린 모양의 시작점과 끝점의 x,y좌표 튜플이다.

트랙바 사용하기

Trackbar

: 프로그램 동작 중 사용자가 지정한 범위 안의 값을 선택할 수 있는 컨트롤로 OpenCV에서 제공하는 그래픽 사용자 인터페이스이다.

트랙바 생성 함수

cv2.createTrackbar(trackbarName, windowName, value, count, onChange) -> None

1) trackbarName : 트랙바 이름
2) windowName : 트랙바를 생성할 창 이름
3) value : 트랙바 위치 초기값
4) count : 트랙바 최댓값. 최솟값은 항상 0
5) onChange : 트랙바 위치가 변경될 때마다 호출할 콜백 함수 이름

예시 코드

import numpy as np
import cv2


def on_level_change(pos):
    value = pos * 16
    if value >= 255:
        value = 255
    # value=np.clip(value, 0, 255)

    img[:] = value
    cv2.imshow('image', img)


img = np.zeros((480, 640), np.uint8)
cv2.namedWindow('image')
cv2.createTrackbar('level', 'image', 0, 16, on_level_change)

cv2.imshow('image', img)
cv2.waitKey()
cv2.destroyAllWindows()

위의 코드를 실행시키면 0~16단계로 나뉘어져 있는데, level이 낮을 수록 어두워지고, level이 높아질 수록 흰색에 가까워진다.

연산 시간 측정 방법

: 컴퓨터 비전은 대용량 데이터를 다루고, 일련의 과정을 통해 최종 결과를 얻으므로 매 단계에서 연산 시간을 측정하여 관리할 필요가 있다.

예시 코드

import sys
import time
import numpy as np
import cv2


img = cv2.imread('hongkong.jpg')

tm = cv2.TickMeter() # tm 객체 생성

tm.reset()

tm.start()
t1 = time.time()
edge = cv2.Canny(img, 50, 150)
tm.stop()

print('time:', (time.time() - t1) * 1000)
print('Elapsed time: {}ms.'.format(tm.getTimeMilli()))
# start부터 stop까지 경과된 시간 출력
---------------------------------------------------------------------------------
time: 169.4018840789795
Elapsed time: 169.0095ms.

실습

위의 코드들로 할 수 있는게 동영상 합성이있다. ppt나 영상에서 볼 수 있는 fade-in/out, dissolve등의 기능을 코드로 구현할 수 있다.
먼저, 두 동영상을 동시에 열어서 첫 번째 동영상의 마지막 N개의 프레임과 두 번째 동영상의 처음 N개의 프레임을 합성한 뒤, 저장하면 합성된 영상이 저장되게 된다. 이 과정을 그림과 코드로 나타내면 다음과 같다.

import sys
import numpy as np
import cv2


# 두 개의 동영상을 열어서 cap1, cap2로 지정
cap1 = cv2.VideoCapture('video1.mp4')
cap2 = cv2.VideoCapture('video2.mp4')

if not cap1.isOpened() or not cap2.isOpened():
    print('video open failed!')
    sys.exit()

# 두 동영상의 크기, FPS는 같다고 가정함
frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap1.get(cv2.CAP_PROP_FPS)
effect_frames = int(fps * 2)

print('frame_cnt1:', frame_cnt1)
print('frame_cnt2:', frame_cnt2)
print('FPS:', fps)

delay = int(1000 / fps)

w = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
fourcc = cv2.VideoWriter_fourcc(*'DIVX')

# 출력 동영상 객체 생성
out = cv2.VideoWriter('output.avi', fourcc, fps, (w, h))

# 1번 동영상 복사
for i in range(frame_cnt1 - effect_frames):
    ret1, frame1 = cap1.read()

    if not ret1:
        print('frame read error!')
        sys.exit()

    out.write(frame1)
    print('.', end='')

    cv2.imshow('output', frame1)
    cv2.waitKey(delay)

# 1번 동영상 뒷부분과 2번 동영상 앞부분을 합성
for i in range(effect_frames):
    ret1, frame1 = cap1.read()
    ret2, frame2 = cap2.read()

    if not ret1 or not ret2:
        print('frame read error!')
        sys.exit()

    dx = int(w / effect_frames) * i

    # frame = np.zeros((h, w, 3), dtype=np.uint8)
    # frame[:, 0:dx, :] = frame2[:, 0:dx, :]
    # frame[:, dx:w, :] = frame1[:, dx:w, :]

    alpha = i / effect_frames
    frame = cv2.addWeighted(frame1, 1 - alpha, frame2, alpha, 0) # dissolve

    out.write(frame)
    print('.', end='')

    cv2.imshow('output', frame)
    cv2.waitKey(delay)

# 2번 동영상을 복사
for i in range(effect_frames, frame_cnt2):
    ret2, frame2 = cap2.read()

    if not ret2:
        print('frame read error!')
        sys.exit()

    out.write(frame2)
    print('.', end='')

    cv2.imshow('output', frame2)
    cv2.waitKey(delay)

print('\noutput.avi file is successfully generated!')

cap1.release()
cap2.release()
out.release()
cv2.destroyAllWindows()

여기까지가 두 번째 posting이다. 쫌 길었지만, 이미지와 영상 편집을 하는데 꽤나 유용하게 쓸 수 있는 코드들이 있기 때문에 도움이 많이 될 것이다.
메리 크리스마스~!

profile
블로그 이전했습니다 (https://phj6724.tistory.com/)

0개의 댓글

관련 채용 정보