본문 바로가기

8. OpenCV | CV

7/24(월) IT K-DT(96일차) / 1.OpenCV~3.영상

 

 

1. OpenCV(Open Source Computer Vision Library)

컴퓨터 비전과 이미지 처리를 위한 오픈소스 라이브러리 (컴퓨터 비전 =/= OpenCV)
1999년 Intel에서 영상처리 기술을 개발하기 위한 목적으로 만들어짐
2000년 BSD 라이센스로 배포
2011년 이후 OpenCV2로 개발 시작 (현재 버전은 OpenCV4이지만, 지금까지 불리는 명칭은 OpenCV2임)
OpenCV 설치
    pip install opencv-python (cv2라는 라이브러리가 따로 있기 때문에, 확실하게 숙지)

2. 컴퓨터 비전(Computer Vision)

컴퓨터를 이용해서 디지털 이미지나 비디오에서 정보를 추출하고 해석하는 기술과 분야
이미지 처리, 객체 탐지, 패턴 인식, 광학 문자 인식 등이 존재

 

3. 영상(image)

움직이는 것을 의미하는 것이 아님. 움직이는 것은 '동영상'
픽셀(pixel): 이미지를 구성하는 가장 작은 단위
바둑판 모양의 격자에 나열되어있는 형태, 2차원 행렬로 보통 표현됨.

 

영상 형태
    1) 그레이스케일 영상
        흑백사진처럼 색상 정보가 없는 영상.
        밝기 정보만으로 구성된 영상. (밝기 정보는 256단계로 0~255까지 표현. 0이 검은색, 255가 흰색)
        numpy.uint8 = 8bit = 1byte
        (u- : unsigned의 의미. 8비트 부호는 기본적으로 -128~127까지의 숫자를 표현할 수 있는데, 

         unsigned는 부호없는정수를 의미하여 0~255까지를 나타냄)
        가로크기 * 세로크기 = 28 * 28 = 784byte의 용량을 가짐
    2) 트루컬러 영상
        컬러사진처럼 색상 정보를 가지고 있기 때문에, 다양한 색상을 표현할 수 있는 영상.
        red, green, blue 색 성분을 사용하고, 각 256단계로 표현.
        픽셀의 표현 -> 튜플 형태로 (255, 255, 255)
        numpy.ndarray = 3byte(red, green, blue 3색이 겹쳐있으므로)
        가로크기 * 세로크기 * 3 = 28 * 28 * 3 = 2352byte의 용량을 가짐


영상 파일 형식
    1) bmp
        픽셀 데이터를 압축하지 않고 그대로 저장
        용량이 매우 큼(픽셀의 일반적인 용량)
        파일구조가 단순하여 별도의 라이브러리 없이 프로그래밍이 가능.
     2) jpg, jpeg
        압축률이 좋아서 파일 용량이 크게 감소
        사진과 같은 컬러영상을 저장할 때 사용
        손실 압축
     3) gif
        움직이는 영상을 지원
        256색 이하의 영상을 저장
        무손실 압축
     4) png
        웹 이미지용으로 권장
        무손실, 손실 둘 다 압축 가능
        알파채널(투명도)의 지원이 가능 (예: 255, 255, 255, 1)

 

3-1. OpenCV

 

import cv2

print('현재 opencv 버전: ', cv2.__version__)

img = cv2.imread('./dog.bmp') # 아무 옵션을 주지 않음: 컬러 영상으로 읽어옴 
# cv2.IMREAD_GRAYSCALE: 그레이스케일 연산
cv2.imshow('img', img) # 창이름, 영상객체
cv2.waitKey() # 창이 계속 유지

 

 

3-2. matplotlib

 

import cv2
import matplotlib.pyplot as plt

# cv2를 이용하여 그레이스케일 표현
img_gray = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
cv2.imshow('img_gray', img_gray)
cv2.waitKey()

 

 

# matplotlib를 이용하여 그레이스케일 표현
img_gray = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
plt.axis('off') # 격자 제거
plt.imshow(img_gray, cmap='gray')
plt.show()

 

 

# matplotlib를 이용하여 트루컬러 표현
img_rgb = cv2.imread('./dog.bmp', cv2.IMREAD_COLOR) # IMREAD_COLOR는 기본값이므로 생략이 가능.
img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR) # BGR을 RGB로 변환
plt.axis('off') # 격자 제거
plt.imshow(img_rgb) # 기본값이 BGR순이므로 21번 코드가 없는 경우 Blue가 출력
plt.show()

 

 

# matplotlib을 이용하여 흑백, 컬러 모두 표현
img_gray = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
img_rgb = cv2.imread('./dog.bmp', cv2.IMREAD_COLOR)
img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
plt.subplot(121) # 1행 2열 1번째 인덱스
plt.axis('off')
plt.imshow(img_gray, cmap='gray')
plt.subplot(122) # 1행 2열 2번째 인덱스
plt.axis('off')
plt.imshow(img_rgb)
plt.show()

 

 

3-3. image info

 

import cv2

# 이미지의 그레이스케일의 type, shape, dtype를 확인
img_gray = cv2.imread('./dog.bmp', cv2.IMREAD_GRAYSCALE)
print('img_gray type: ', type(img_gray)) # class numpy.ndarray
print('img_gray shape: ', img_gray.shape) # (364, 548) (OpenCV는 height, width 순서)
print('img_gray dtype: ', img_gray.dtype) # uint8

# 이미지의 트루컬러의 type, shape, dtype를 확인
img_color = cv2.imread('./dog.bmp')
print('img_color type: ', type(img_color)) # class numpy.ndarray
print('img_color shape: ', img_color.shape) # (364, 548, 3)
print('img_color dtype: ', img_color.dtype) # uint8

 

 

# img_color를 '가로*세로'의 형식으로 재출력하기
h, w = img_color.shape[:2] # shape로 뽑으면 튜플로 출력
print(f'img_color 사이즈: {w}*{h}')

 

 

# 그레이스케일과 컬러를 구별하는 방법
if len(img_gray.shape) == 2:
    print('img_gray는 그레이스케일 영상입니다.')
elif len(img_gray.shape) == 3:
    print('img_gray는 컬러 영상입니다.')

 

 

# img_color에 특정 색 정보로 영상을 출력하기 // BGR:(255,102,255)
import numpy as np
bgr_color = (255, 102, 255)  # BGR(255, 102, 255) 색상 (핑크색)
img_color = np.full((364, 548, 3), bgr_color, dtype=np.uint8)
cv2.imshow('img_color', img_color)
cv2.waitKey()

 

 

3-4. create image

 

영상 출력
1) 그레이스케일 영상 출력
        cv2.IMREAD_GRAYSCALE을 사용
        img.shape: 차원의 크기. (height, weight)
2) 컬러 영상 출력
        cv2.imread() 메소드로 불러온 영상의 색상 정보는 BGR 순서
        matplotlib에서 출력하려면 RGB 순서로 변경해야 하기 때문에
        cv2.cvtColor() 메소드를 사용하여 변경
        img.shape: 차원의 크기. (height, weight, 3)

 

import cv2
import numpy as np

# 임의의 값을 넣은 영상
img1 = np.empty((240, 320), dtype=np.uint8)
cv2.imshow('img1', img1)
cv2.waitKey()

 

 

# 0을 넣은 컬러 영상
img2 = np.zeros((240, 320), dtype=np.uint8) # 0: black이므로 검은색의 화면이 생성
cv2.imshow('img2', img2)
cv2.waitKey()

 

 

# 1을 넣은 컬러 영상
img3 = np.ones((240, 320), dtype=np.uint8) * 120 # 밝기 정보에 120을 곱해줌 # 회색의 화면이 생성
cv2.imshow('img3', img3)
cv2.waitKey()

 

 

# 특정한 값을 넣은 컬러 영상
img4 = np.full((240, 320, 3), (255,102,255), dtype=np.uint8) # (255,102,255):pink
cv2.imshow('img4', img4)
cv2.waitKey()

 

 

3-5. copy image

 

import cv2

img_origin = cv2.imread('./dog.bmp')

# img_origin에서 일부만 잘라내고 싶은 경우. 동일한 메모리에 사용하는 방법
img_origin = img_origin[91:210, 125:245] # H, W
img_copy = img_origin


# img_copy를 잘라낸 사진으로 변경하고 싶은 경우. 메모리에 다른쪽으로 옮겨서 사용하는 방법
img_copy = img_origin[91:210, 125:245].copy()

cv2.imshow('img_origin', img_origin)
cv2.waitKey()

 

 

3-6. drawing

도형 그리기
1) 직선 그리기
    cv2.line(영상, 직선의 시작점과 끝점(튜플로 저장), 선 색상, 선 두께, 선 타입)
2) 사각형 그리기
    cv2.rectangle(영상, 사각형의 꼭지점 좌표(튜플로 저장), 선 색상, 선 두께) 

    // 선 두께가 '-1'인 경우, 사각형 내부를 색상으로 채움
3) 원 그리기
    cv2.circle(영상, 원의 중앙 좌표(튜플로 저장), 반지름, 선 색상, 선 두께) 

    // 선 두께가 '-1'인 경우, 원 내부를 색상으로 채움
4) 문자열 출력하기
    cv2.putText(영상, 문자열, 영상에서 문자열을 출력할 위치 좌표, 글꼴, 폰트 크기)

 

import cv2
import numpy as np

img = np.full((500, 500, 3), 255, np.uint8) 
# 흰색 배경의 네모 박스 생성
cv2.line(img, (100, 70), (200, 70), (0, 0, 255), 5) 
# 빨간색 선(두께5) 생성
cv2.rectangle(img, (50,200,150,100), (0, 255, 0), -1) 
# 초록색 사각형 생성 # (50,200)의 가로 150 세로 100의 사각형 (x,y,w,h)
cv2.circle(img,(300, 100), 100, (255, 255, 0), -1) 
# 하늘색 원 생성 # (300,100)의 중심점에서 반지름 100의 원

str = "Hello"
cv2.putText(img, str, (30, 350), cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255)) 
# cv2.FONT_HERSHEY_SIMPLE: 글꼴 // 위치: (30, 350) 크기:2 색상 빨강 
cv2.imshow('img', img)
cv2.waitKey()

 

 

3-7. camera in

 

(해당 파트는 웹캠이 없는경우 진행을 할 수 없음)

 

cv2.VideoCapture 클래스
    - 카메라와 동영상으로부터 프레임(frame)을 받아오는 작업을 처리함


카메라 영상 입력
1) cv2.VideoCapture(index)
        - index: 시스템의 기본 카메라를 open하려면 0 또는 카메라 고유의 값
2) cv2.VideoCapture.isOpened(): 카메라를 제대로 불러왔는지의 여부 확인
        - True:성공, False:실패
3) cv2.CAP_PROP_FRAME_WIDTH: 카메라에서 읽어들인 가로사이즈를 확인
4) cv2.CAP_PROP_FRAME_HEIGHT: 카메라에서 읽어들인 세로사이즈를 확인
5) cv2.VideoCapture.read() : 영상을 하나씩 읽어들이는 방법
        ret: 영상이 정상적으로 리턴되었는지 여부를 확인 ( True / False로 return됨)
        frame: 영상(그레이스케일 영상 또는 컬러 영상 자체)

 

import sys
import cv2

cap = cv2.VideoCapture(0)

if not cap.isOpened(): # 카메라 영상을 open할 수 없는 경우
    print('카메라를 열 수 없습니다')
    sys.exit() # 프로그램 종료
print('카메라 연결 성공')

# 카메라의 가로, 세로 사이즈 불러오기
print('가로 사이즈: ', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('세로 사이즈: ', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))

# 카메라 불러오기
while True:
    ret, frame = cap.read()
    if not ret:
        break
    cv2.imshow('frame', frame)
    if cv2.waitKey(10)==27: # 10: 0.01초 / 27:ESC키. ESC키를 누르면 꺼짐
        break

cap.release()  # 메모리를 아낄 수 있음.

 

3-8. video in

동영상 입력
1) cv2.VideoCapture(파일명) // 파일명은 string으로 작성
    나머지는 카메라 영상과 동일함

 

예제로 사용할 무료 동영상 다운로드 웹사이트: Pixabay https://pixabay.com/ko/videos

 

 

예제를 위해 가져온 영상: china.mp4

import cv2
import sys

cap = cv2.VideoCapture('./china.mp4')

if not cap.isOpened(): # 동영상을 open할 수 없는 경우
    print('동영상을 열 수 없습니다')
    sys.exit() # 프로그램 종료

# 동영상의 가로, 세로 사이즈, 프레임 수 불러오기
print('가로 사이즈:', int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
print('세로 사이즈:', int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print('프레임 수:', int(cap.get(cv2.CAP_PROP_FRAME_COUNT)))
print('fps:', int(cap.get(cv2.CAP_PROP_FPS)))

while True:
    ret, frame = cap.read()
    if not ret: # return할 내용이 없다면, break
        break
    cv2.imshow('frame', frame)
    if cv2.waitKey(10) == 27: # 0.01초마다 ESC버튼의 누름이 확인된다면, 창을 닫기
        break

cap.release() # 메모리를 아낄 수 있음.

 

 

3-9. camera out

동영상 출력
1) FourCC(Four Character Code):
        4byte로 된 문자열이며, data 형식을 구분하는 고유 글자
        주로 AVI 파일의 영상 코덱을 구분할때 사용
        Divx, Xvid, H264 ...
            cv2.VideoWriter_fourcc(*'DIVX') // DIVX 포맷으로 저장
            cv2.VideoWriter_fourcc(*'XVID') // XVID 포맷으로 저장
            ...
            cv2.VideoWriter_fourcc(*'MP4V') // MP4V 포맷으로 저장
 

파일 저장
        cv2.VideoWriter(파일명, FourCC객체, fps, 프레임사이즈, 컬러영상여부)
        프레임사이즈: tuple형식(w, h)
        컬러영상여부: 컬러영상이면 True, 그레이스케일이면 False

 

import cv2

cap = cv2.VideoCapture(0)

w = round(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv2.CAP_PROP_FPS)

fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter('output.avi', fourcc, fps, (w, h))

while True:
    ret, frame = cap.read()
    if not ret:  # return할 내용이 없다면, break
        break
    out.write(frame)
    cv2.imshow('frame', frame)
    if cv2.waitKey(10) == 27:  # 0.01초마다 ESC버튼의 누름이 확인된다면, 창을 닫기
        break

cap.release()  # 메모리를 아낄 수 있음.

 

문제

무료 동영상 사이트에서 동영상 2개를 다운받아서 두 동영상을 연결하는 프로그램을 만들어보기 (두 동영상의 해상도는 같아야 함)

 

# 동영상 2개를 하나로 연결해보기

import cv2
import numpy as np

cap1 = cv2.VideoCapture('./airport.mp4')
cap2 = cv2.VideoCapture('./china.mp4')

frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))
print(frame_cnt1)
print(frame_cnt1)

fps1 = cap1.get(cv2.CAP_PROP_FPS)
fps2 = cap2.get(cv2.CAP_PROP_FPS)
print(fps1)
print(fps2)

w = round(cap1.get(cv2.CAP_PROP_FRAME_WIDTH))
h = round(cap1.get(cv2.CAP_PROP_FRAME_HEIGHT))
print('w', w)
print('h', h)

fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter('mix.avi', fourcc, fps1, (w, h))

# 1번 영상 열기
for i in range(frame_cnt1):
    ret1, frame1 = cap1.read()
    cv2.imshow('output', frame1)
    out.write(frame1)
    if cv2.waitKey(10) == 27:
        break


# 2번 영상 열기
for i in range(frame_cnt2):
    ret2, frame2 = cap2.read()
    cv2.imshow('output', frame2)
    out.write(frame2)
    if cv2.waitKey(10) == 27:
        break

cap1.release()
cap2.release()
out.release()


과제

위 문제에서 1번 동영상이 끝나기 2초전부터 2번 동영상이 좌측에서 시작되어 2초가 끝나면 2번 동영상이 모두 보이는 효과의 프로그램 만들기

 

# 동영상 2개를 하나로 연결해보기

import cv2
import numpy as np

cap1 = cv2.VideoCapture('./airport.mp4')
cap2 = cv2.VideoCapture('./china.mp4')

frame_cnt1 = round(cap1.get(cv2.CAP_PROP_FRAME_COUNT))
frame_cnt2 = round(cap2.get(cv2.CAP_PROP_FRAME_COUNT))


fps1 = cap1.get(cv2.CAP_PROP_FPS)
fps2 = cap2.get(cv2.CAP_PROP_FPS)
effect_frames = int(fps1) * 2

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('mix.avi', fourcc, fps1, (w, h))

# 1번 영상 열기
for i in range(frame_cnt1-effect_frames):
    ret1, frame1 = cap1.read()
    if not ret1:
        break
    cv2.imshow('frame', frame1)
    out.write(frame1)
    if cv2.waitKey(10) == 27:
        break

# 1번 영상과 2번 영상 합성
for i, x in zip(range(effect_frames), range(1, w, int(w/effect_frames))):
    ret1, frame1 = cap1.read()
    ret2, frame2 = cap2.read()
    delay_frame = frame1
    delay_frame[:, :x] = frame2[:, :x]
    cv2.imshow('frame', delay_frame)
    out.write(delay_frame)
    if cv2.waitKey(10) == 27:
        break

# 2번 영상 열기
for i in range(effect_frames, frame_cnt2):
    ret2, frame2 = cap2.read()
    if not ret2:
        break
    out.write(frame2)
    cv2.imshow('frame', frame2)
    if cv2.waitKey(10) == 27:
        break

cap1.release()
cap2.release()
out.release()