크리스마스에도 공부를 해야하는 컴공생이라니...슬프지만 쩔수 없는 것 같다 어쩌면 컴공에 들어온 순간 이미 예견된 미래일지도...? 그저 매일 나의 부족함을 느끼기 때문에 더 노력해야지라는 생각 뿐인 것 같다. 크리스마스 선물인지 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는 그대로 출력이 되게 된다.
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이다.
또한 이런식으로도 할 수 있다.
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 클래스를 이용하여 일련의 프레임을 동영상 파일로 저장할 수 있다. 단, 일련의 프레임은 모두 크기와 데이터 타입이 같아야 한다.
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좌표 튜플이다.
: 프로그램 동작 중 사용자가 지정한 범위 안의 값을 선택할 수 있는 컨트롤로 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이다. 쫌 길었지만, 이미지와 영상 편집을 하는데 꽤나 유용하게 쓸 수 있는 코드들이 있기 때문에 도움이 많이 될 것이다.
메리 크리스마스~!