[컴퓨터 비전과 딥러닝 - 오일석] 책 내용 입니다.
OpenCV는 인텔 사에서 만들어 공개한 컴퓨터 비전 라이브러리입니다.
이를 구성하는 함수와 클래스는 C와 C++ 언어로 개발했으며, 전체 코드는 180만 라인 이상입니다.
데스크톱 운영체제는 윈도우, 리눅스, macOS를 지원하며, 모바일 운영체제는 안드로이드와 IOS가 있습니다.
처음 해볼 OpenCV 프로그래밍은 폴더에 저장되어 있는 영상 파일을 읽고 화면에 나타내 보겠습니다.
import cv2 as cv # opcencv 라이브러리
import sys # 파이썬 시스템 변수 설정 라이브러리
img = cv.imread('이미지.jpg') # 이미지 변수 생성
if img is None:
sys.exit('파일을 찾을 수 없습니다') # 파일 없을 시 예외처리
cv.imshow('출력', img) # 이미지 화면출력
cv.waitKey() # 키입력 대기
cv.destroyAllWindows() # 종료
우선 cv2와 sys 라이브러리를 불러옵니다. 그 다음 img = cv.imread( '이미지.jpg' ) 로 이미지를 불러옵니다.
이 때 if와 is None을 써서 이미지가 없을 때 예외처리를 할수 있습니다.
cv.imshow로 화면에 변수에 저장된 이미지를 불러오고
cv.waitKey()로 출력 종료 명령을 일단 기다리게 합니다
cv.waitKey 함수는 키보드의 키가 눌릴 때까지 기다리다가. 키가 눌리면 해당 키의 유니코드 값을 반환합니다.
인자값으로 시간을 지정하면 밀리초 단위로 해당 시간을 기다립니다. (정해진 시간 내에 키 입력이 없으면 -1 반환)
형태 변환 및 크기 축소
앞으로 우리가 이미지나 영상의 특정한 처리를 할 때에는 많은 연산이 필요합니다. 이러한 연산의 처리량을 줄이기 위해 이미지의 변환 및 축소를 수행할 필요가 있습니다.
이미지 형태
흔히 OpenCV에서 이미지는 3차원의 numpy.ndarray 클래스 형의 객체로 이루어져 있습니다. ndarray
이미지를 .shape 함수로 확인해 보면 ( 세로길이, 가로길이, 채널수 ) 의 크기를 가지고 있습니다.
여기서 채널수란 색상을 이루는 RGB의 개념입니다. (OpenCV는 BGR로 계산)
이처럼 채널 3개로 모든 색상표현이 가능합니다.
주의할 점은 이를 OpenCV의 ndarray로 표현해보자면
(56, 100, 3)는 파랑 56 초록 100 빨강 3 를 뜻하는 것이 아닌,
행 56, 열 100에 위치한 화소점의 빨강색 값을 뜻합니다.
이렇게 이미지는 위와 같이 각각의 B, G, R(3개)의 채널이 가로 세로의 값을 가집니다, 더 정확히는 " B, G, R 3개가 겹쳐있다 "
이를 처리하기 편하게 채널 수를 1개로 줄이고 (명암 이미지), 가로 세로(사이즈) 의 크기도 줄여보겠습니다.
cv.cvtColor( img, COLOR_BGR2GRAY )
cv.cvtColor 함수는 opencv에서 이미지의 색상을 바꿔줍니다.
인자 값으로 ( 이미지, COLOR_BGR2스케일)를 넣어 다른스케일로 만듭니다.
스케일 값으로는 GRAY, HSV, RGB, LAB 가 있습니다.
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
cv.resize( img, dsize, fx, fy )
cv.resize 함수는 이미지의 크기를 변경할 때 사용합니다.
dsize 는 이미지의 크기를 변경하고 fx와 fy는 각각 이미지의 비율을 결졍합니다.
dsize = (0,0), fx = 0.5, fy = 0.5 는 이미지를 전체적으로 1/2 비율로 축소합니다.
gray_small = cv.resize(gray, dsize=(0,0), fx=0.5, fy=0.5)
웹 캠에서 비디오 읽기
이제 웹 캠을 통해 동영상(video)을 받는 프로그래밍을 해보겠습니다. 노트북은 대부분 웹 캠이 장착되어 있지만 데스크톱에서는 별도로 웹 캠을 설치해야 합니다.
cap = cv.VideoCapture(0, cv.CAP_DSHOW) # 카메라 연결
# 예외처리
if not cap.isOpened(): # 카메라 연결 실패
sys.exit('카메라 연결 실패')
while True:
ret, frame = cap.read() # 비디오 프레임 획득
if not ret: # 프레임 획득 실패
print('프레임 획득에 실패하여 루프를 나갑니다.')
break
cv.imshow('실행이름', frame) # 영상 출력
key = cv.waitKey(1)
if key == ord('q'): # key 유니코드 값 문자열로 변환
break
cap.release() # 카메라와 연결 끊기
cv.destroyAllWindows()
cap = cv.VideoCapture 함수는 웹 캠과 연결을 시도하고 결과를 cap 객체에 저장합니다. 첫 번째 인수로 웹 캠 번호를 지정하고(캠이 하나일 경우 0) 두 번째 인수로 cv.CAP_DSHOW는 비디오가 화면에 바로 나타나게 합니다.
cap.isOpened 함수는 웹 캠과 연결에 실패하는 경우 False를 반환합니다. 이를 이용해 예외처리를 할수 있습니다.
ret, frame = cap.read() 는 ret 객체에 프레임 획득 성공 여부를 저장하고 frame 객체에 직접적인 프레임을 저장합니다.
프레임(한 이미지)을 연결하여 영상과 같은 착시 현상을 만들 수 있습니다.
key = cv.waitKey(1)를 포함한 밑에 2줄의 코드는 키보드로 q를 누를 시 비디오 획득을 종료합니다.
인자 값을 1밀리초로 설정한 이유는 대기 시간 값이 커지면 지연이 발생해 비디오가 매끄럽지 않게 나타나기 때문입니다.
release() 함수를 써야 정상적으로 카메라 연결이 끊어집니다.
이렇게 만들어진 뼈대를 이용하여 밑에서 다양한 기능을 추가해 보겠습니다.
캡쳐기능 추가
frames = [] # 캡쳐본 저장소
while True:
ret, frame = cap.read()
if not ret: # 프레임 획득 실패
print('프레임 획득에 실패하여 루프를 나갑니다.')
break
cv.imshow('실행이름', frame)
key = cv.waitKey(1)
if key == ord('q'):
break
elif key == ord('c'): # c 누를 시 저장소에 해당 프레임(이미지) 추가
frames.append(frame)
cap.release()
cv.destroyAllWindows()
# 이미지 이어붙이기
if len(frames) > 0: # 캡쳐가 하나라도 되면
imgs = frames[0]
for i in range(1, min(3, len(frames)): # 최대 3개(또는 데이터 갯수)까지 붙임
imgs = np.hstack((imgs, frames[i])) #
cv.imshow('collected images', imgs)
cv.waitKey()
cv.destroyAllWindows()
기존 <비디오 읽기> 코드에서 추가된 것은 주석 처리 했습니다.
hstack 함수는 배열을 수평으로 쌓아 붙입니다(vstack은 수직) 이 함수의 실행 횟수를 3으로 제한한 이유는 한 이미지당 480 X 640 X 3 크기이기 때문에 가로가 1920이 넘어가지 않게 하기 위함입니다.
그래픽 기능 추가
이미지에 도형을 그리거나 글씨를 써넣어야 할 때가 있습니다.. 예를 들어
영상에서 얼굴을 검출하면 얼굴 영역을 직사각형으로 표시하고, 얼굴 표정을 인식했다면 어떤 표정인지 써야한다.
아직은 사용자가 좌표를 지정하는 방식으로 프로그래밍한다.
OpenCV에서 도형과 관련한 함수는
line(직선), rectangel(직사각형), polylines(다각형), circle(원), elipse(타원), putText(문자열) 등이 있습니다.
img = cv.imread('girl_laughing.jpg')
if img is None:
sys.exit('파일을 찾을 수 없다')
# 도형 그려넣기
cv.rectangle(img, (830,30), (1000,200), (0,0,255), 2)
# 왼쪽 위 # 오른쪽 아래 # 색 # 두께
# 글씨 써넣기
cv.putText(img, 'laugh', (830,24), cv.FONT_HERSHEY, 1, (255,0,0), 2)
# 출력문자 # 왼쪽아래 # 글꼴 # 크기 # 색 # 두께
cv.waitKey()
cv.destroyAllWindows()
이제 사용자가 마우스로 직접 도형을 추가 하겠습니다.
def draw(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDOWN: # 마우스 왼쪽 클릭
cv.rectangle(img, (x,y), (x+200, y+200), (0,0,255), 2)
cv.imshow('Drawing', img)
cv.namedWindow('Drawing') # 윈도우 생성
cv.imshow('Drawing', img)
cv.setMouseCallback('Drawing', draw) # Drawing 윈도우에 draw 콜백 함수 지정
while(True): # 마우스 이벤트가 언제 발생할지 모르므로 무한 반복
if cv.waitKey(1) == ord('q'):
cv.destroyAllWindows()
break
위에서 콜백함수(callback function) 방식이 필요한 이유는.
보통 프로그램은 정해진 순서에 따라 명령어를 실행하는데, 마우스를 다루는 프로그램은 클릭이나 커서 이동 같은
이벤트가 언제 발생할지 알 수 없기 때문에 콜백 함수가 필요합니다.
draw 콜백 함수의 매개변수로는 event(이벤트의 종류)와 x y(이벤트가 일어난 순간의 커서 위치)
이때 event가 cv.EVENT_LBUTTONDOWN( 마우스 왼쪽 클릭) 이라면 rectangle을 사용해 도형을 그려넣습니다.
while을 이용해 waitKey() 함수를 사용한 이유는. 마우스 이벤트가 언제 발생할지 모르므로
무한 루프를 돌아 프로그램 실행을 지속하기 위함입니다.
위 코드는 클릭을 하기 때문에 크기가 정해져 있습니다. 크기를 지정하며 도형을 그리는 코드는 아래와 같습니다.
def draw(event, x, y, flags, param):
global ix, iy
if event == cv.EVENT_LBUTTONDOWN: # 왼쪽 누르면
ix, iy = x, y # 왼쪽 위 좌표 저장
elif event == cv.EVENT_LBUTTONUP: # 왼쪽 올리면(클릭 때기)
cv.rectangle(img, (ix, iy), (x, y), (0,0,255), 2) # 도형 넣기
cv.imshow('Drawing', img)
draw 함수만 달라졌습니다.
ix와 iy를 전역 함수로 설정하고 왼쪽 클릭을 누를 시 그 좌표를 ix와 iy에 저장합니다.
왼쪽 버튼을 때면 미리 저장된 ix iy 좌표와 버튼을 땔 시 위치한 좌표(x, y) 범위를 잡아 도형을 넣습니다.
페인팅 기능 만들기
# 크기, 색 미리 지정
BrushSize = 5
LColor = (255,0,0)
def painting(event, x, y, flags, param):
if event == cv.EVENT_LBUTTONDOWN:
cv.circle(img, (x,y), BrushSize, LColor, -1)
elif event == cv.EVENT_MOUSEMOVE and flags == cv.EVENT_FLAG_LBUTTON:
cv.circle(img, (x,y), BrushSize, LColor, -1)
cv.imshow('painting', img_soccer)
cv.namedWindow('Painting')
cv.imshow('Painting', img_soccer)
cv.setMouseCallback('Painting',painting)
while True:
key= cv.waitKey(1)
if key == ord('q'):
cv.destroyAllWindows()
break
elif key == ord('b'):
LColor = (255,0,0)
elif key == ord('g'):
LColor = (0,255,0)
elif key == ord('r'):
LColor = (0,0,255)