본문 바로가기

Deep Learning(강의 및 책)/OpenCV

[OpenCV] 기본 입출력

728x90

이 글은 '파이썬으로 만드는 OpenCV 프로젝트'를 보고 작성했습니다.

2.1 이미지와 비디오 입출력

■ 이미지 읽기

OpenCV를 이용해 이미지를 읽는 방법에 대해서 설명하겠습니다.

 

import cv2

img_file = "../img/girl.jpg" # 표시할 이미지 경로            ---①
img = cv2.imread(img_file)  # 이미지를 읽어서 img 변수에 할당 ---②

if img is not None:
  cv2.imshow('IMG', img)   # 읽은 이미지를 화면에 표시      --- ③
  cv2.waitKey()           # 키가 입력될 때 까지 대기      --- ④
  cv2.destroyAllWindows()  # 창 모두 닫기            --- ⑤
else:
    print('No image file.')

위 코드를 보면 '../img/girl.jpg'라는 파일이 있다면 불러올 수 있습니다. 경로에 있는 파일을 cv2.imread()라는 함수를 이용해 읽어옵니다. 이 함수는 numpy 배열을 return 합니다. if문을 이용해 불러온 파일이 정상이라면 cv2.imshow()라는 함수를 이용해 화면에 표시합니다. cv2.waitKey()는 키보드 입력이 있을 때까지 프로그램을 기다리게 하는 함수입니다. 키보드 입력이 들어오면 cv2.destroyAllWindows() 함수를 만나 창을 모두 닫고 프로그램이 종료됩니다.

이 코드에서 사용한 함수들을 정리하면 다음과 같습니다. 대괄호안에 있는 값들은 필수적으로 존재해야 하는 값은 아닙니다.

  • img = cv2.imread(file_name [, mode_flag]): 파일로부터 이미지 읽기
    • file_name: 이미지 경로, 문자열
    • mode_flag = cv2.IMREAD_COLOR: 읽기 모드 지정
      • cv2.IMREAD_COLOR: 컬러(BGR) 스케일로 읽기, 기본 값
      • cv2.IMREAD_UNCHANGED: 파일 그대로 읽기
      • cv2.IMREAD_GRAYSCALE: 그레이(흑백) 스케일로 읽기
    • img: 읽은 이미지, Numpy 배열
  • cv2.imshow(title, img): 이미지를 화면에 표시
    • title: 창 제목, 문자열
    • img: 표시할 이미지, Numpy 배열
  • key = cv2.waitKey([delay]): 키보드 입력 대기
    • delay = 0: 키보드 입력을 대기할 시간(ms), 0: 무한대(기본 값)
    • key: 사용자가 입력한 키 값, 정수
      • -1: 대기시간 동안 키 입력 없음

컬러이미지를 그레이 스케일로 읽어올 순 있지만, 그레이 이미지 파일을 cv2.IMREAD_COLOR로 지정해 불러온다 해도 컬러 이미지로 읽어올 수 있는 것은 아닙니다.

 

■ 이미지 저장하기

OpenCV에서 이미지를 다시 파일로 저장하는 함수는 cv2.imwrite()입니다.

  • cv2.imwrite(file_path, img): 이미지를 파일에 저장
    • file_path: 저장할 파일 경로 이름, 문자열
    • img: 저장할 영상, Numpy 배열

 

import cv2

img_file = '../img/girl.jpg'
save_file = '../img/girl_gray.jpg'

img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)
cv2.imshow(img_file, img)
cv2.imwrite(save_file, img) #파일로 저장, 포맷은 확장에 따름
cv2.waitKey()
cv2.destroyAllWindows()

 

이와 같이 코드를 작성해 볼 수 있습니다. 그레이 스케일로 읽어온 이미지를 '../img/girl_gray.jpg'에 저장하는 모습입니다.

 

■ 동영상 및 카메라 프레임 읽기

OpenCV는 동영상 파일이나 컴퓨터에 연결한 카메라 장치로부터 연속된 이미지 프레임을 읽을 수 있는 API를 제공합니다.

 

  • cap = cv2.VideoCapture(file_path 또는 index): 비디오 캡처 객체 생성자
    • file_path: 동영상 파일 경로
    • index: 카메라 장치 번호, 0부터 순차적으로 증가
    • cap: VideoCapture 객체
  • ret = cap.isOpened(): 객체 초기화 확인
    • ret: 초기화 여부, True / False
  • ret, igm = cap.read(): 영상 프레임 읽기
    • ret: 프레임 읽기 성공 또는 실패 여부, True / False
    • img: 프레임 이미지, Numpy 배열 또는 None
  • cap.set(id, value): 프로퍼티 변경
  • cap.get(id): 프로퍼티 확인
  • cap.release(): 캡처 자원 반납

동영상 파일이나 컴퓨터에 연결한 카메라 장치로부터 영상 프레임을 읽기 위해서는 cv2.VideoCapture() 생성자 함수를 사용하여 객체를 생성해야 합니다. 이 함수에 동영상 파일 경로 이름을 전달하면 동영상 파일에 저장된 프레임을 읽을 수 있고, 카메라 장치 번호를 전달하면 카메라로 촬영하는 프레임을 읽을 수 있습니다.

비디오 캡처 객체의 set(), get() 함수를 이용하면 여러 가지 속성을 얻거나 지정할 수 있고, 프로그램을 종료하기 전에 release() 함수를 호출해 자원을 반납해야 합니다.

 

import cv2

video_file = "../img/big_buck.avi" # 동영상 파일 경로

cap = cv2.VideoCapture(video_file) # 동영상 캡쳐 객체 생성  ---①
if cap.isOpened():                 # 캡쳐 객체 초기화 확인
    while True:
        ret, img = cap.read()      # 다음 프레임 읽기      --- ②
        if ret:                     # 프레임 읽기 정상
            cv2.imshow(video_file, img) # 화면에 표시  --- ③
            cv2.waitKey(25)            # 25ms 지연(40fps로 가정)   --- ④
        else:                       # 다음 프레임 읽을 수 없슴,
            break                   # 재생 완료
else:
    print("can't open video.")      # 캡쳐 객체 초기화 실패
cap.release()                       # 캡쳐 자원 반납
cv2.destroyAllWindows()

위 코드를 보면 동영상을 불러오는 모습입니다. cv2.VideoCapture() 함수에 동영상 파일 경로를 전달해 캡체 객체 cap을 생성합니다. 정상적으로 객체가 생성되었다면 조건문 안으로 들어가 ret, img 값을 얻게 됩니다. 동영상 끝이 된다면 ret은 False가 저장되면서 무한루프가 종료됩니다. cv2.imshow() 함수를 통해 화면에 프레임을 출력하게 됩니다. cv2.waitKey(25)를 작성한 이유는 각 프레임을 화면에 표시하는 시간이 너무 빠르면 우리 눈으로 볼 수 없기 때문입니다. FPS(Frames Per Seconds, 초당 프레임 수)에 맞게 지연시간을 조정해 적절한 속도로 영상을 재생해야 우리가 눈으로 볼 수 있습니다. 보통 동영상 파일의 FPS가 40인 경우가 많아서 지연시간을 25ms으로 지정해 사용한 모습입니다.

만약 웹캠 프레임을 읽고 싶다면 코드를 아주 조금만 수정하면 됩니다.

 

import cv2

cap = cv2.VideoCapture(0)               # 0번 카메라 장치 연결 ---①
if cap.isOpened():                      # 캡쳐 객체 연결 확인
    while True:
        ret, img = cap.read()           # 다음 프레임 읽기
        if ret:
            cv2.imshow('camera', img)   # 다음 프레임 이미지 표시
            if cv2.waitKey(1) != -1:    # 1ms 동안 키 입력 대기 ---②
                break                   # 아무 키라도 입력이 있으면 중지
        else:
            print('no frame')
            break
else:
    print("can't open camera.")
cap.release()                           # 자원 반납
cv2.destroyAllWindows()

이와 같은 코드를 작성하면 웹캠 프레임을 읽어올 수 있습니다. cv2.waitKey(1) != -1이라는 조건문을 걸어준 이유는 키보드 입력이 아무거나 하나 들어오게 되면 반복문을 종료하고 프로그램을 종료하기 위해서입니다.

 

■ 카메라 비디오 속성 제어

캡처 객체에는 영상 또는 카메라의 여러 가지 속성을 확인하고 설정할 수 있는 get(id), set(id, value) 함수가 있습니다. 속성을 나타내는 아이디는 cv2.CAP_PROP_FRAME_으로 시작하는 상수로 정의되어 있습니다. 다음 나오는 속성들은 이 책에서 주로 사용하는 속성들입니다.

  • 속성 ID: 'cv2.CAP_PROP_'으로 시작하는 상수
    • cv2.CAP_PROP_FRAME_WIDTH: 프레임 폭
    • cv2.CAP_PROP_FRAME_HEIGHT: 프레임 높이
    • cv2.CAP_PROP_FPS: 초당 프레임 수
    • cv2.CAP_PROP_POS_MSEC: 동영상 파일의 프레임 위치(ms)
    • cv2.CAP_PROP_POS_AVI_RATIO: 동영상 파일의 상대 위치(0: 시작, 1: 끝)
    • cv2.CAP_PROP_FOURCC: 동영상 파일 코덱 문자
    • cv2.CAP_PROP_AUTOFOCUS: 카메라 자동 초점 조절
    • cv2.CAP_PROP_ZOOM: 카메라 줌

각 속성 아이디를 get()에 전달하면 해당 속성의 값을 구할 수 있고, set() 함수에 id와 value를 전달하면 값을 지정할 수 있습니다.

 

■ 비디오 파일 저장하기

카메라나 동영상 파일을 재생하는 도중 특정한 프레임만 이미지로 저장하거나 특정 구간을 동영상 파일로 저장하는 법에 대한 설명입니다.

한 개의 프레임을 저장하는 것은 앞서 봤던 cv2.imwrite() 함수를 사용하면 됩니다. 그럼 코드를 작성해보겠습니다.

 

import cv2

cap = cv2.VideoCapture(0)                       # 0번 카메라 연결
if cap.isOpened() :
    while True:
        ret, frame = cap.read()                 # 카메라 프레임 읽기
        if ret:
            cv2.imshow('camera',frame)          # 프레임 화면에 표시
            if cv2.waitKey(1) != -1:            # 아무 키나 누르면
                cv2.imwrite('photo.jpg', frame) # 프레임을 'photo.jpg'에 저장
                break
        else:
            print('no frame!')
            break
else:
    print('no camera!')
cap.release()
cv2.destroyAllWindows()

이와 같이 작성하면 키보드 입력이 들어올 때 프레임 사진이 'photo.jpg'라는 이름으로 저장됩니다.

하나의 프레임이 아닌 여러 프레임을 동영상으로 저장하려면 cv2.VideoWriter()라는 새로운 API를 사용합니다.

  • writer = cv2.VideoWriter(file_path, fourcc, fps, (width, height)): 비디오 저장 클래스 생성자 함수
    • file_path: 비디오 파일 저장 경로
    • fourcc: 비디오 인코딩 형식 4글자
    • fps: 초당 프레임 수
    • (width, height): 프레임 폭과 프레임 높이
    • writer: 생성된 비디오 저장 객체
  • writer.write(frame): 프레임 저장
    • frame: 저장할 프레임, Numpy 배열
  • writer.set(id, value): 프로퍼티 변경
  • write.get(id): 프로퍼티 확인
  • ret = writer.fourcc(c1, c2, c3, c4): fourcc 코드 생성
    • c1, c2, c3, c4: 인코딩 형식 4글자, 'MJPG', 'DIVX' 등
    • ret: fourcc 코드
  • cv2.VideoWriter_fourcc(c1, c2, c3, c4): cv2.VideoWriter.foucc()와 동일

cv2.VideoWriter() 생성자 함수에 저장할 파일 이름과 인코딩 포맷 문자, fps, 프레임 크기를 지정해서 객체를 생성하고 write() 함수로 프레임을 파일에 저장하면 됩니다.

 

import cv2

cap = cv2.VideoCapture(0)    # 0번 카메라 연결
if cap.isOpened:
    file_path = './record.avi'    # 저장할 파일 경로 이름 ---①
    fps = 30.0                     # FPS, 초당 프레임 수
    fourcc = cv2.VideoWriter_fourcc(*'DIVX') # 인코딩 포맷 문자
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    size = (int(width), int(height))                        # 프레임 크기
    out = cv2.VideoWriter(file_path, fourcc, fps, size) # VideoWriter 객체 생성
    while True:
        ret, frame = cap.read()
        if ret:
            cv2.imshow('camera-recording',frame)
            out.write(frame)                        # 파일 저장
            if cv2.waitKey(int(1000/fps)) != -1: 
                break
        else:
            print("no frame!")
            break
    out.release()                                   # 파일 닫기
else:
    print("can't open camera!")
cap.release()
cv2.destroyAllWindows()

이와 같이 코드를 작성하면 카메라 영상이 화면에 나오고 지정한 경로에 동영상이 녹화되어 저장되기 시작하고 키보드의 아무 키나 누르면 종료됩니다.

 

2.2 그림 그리기

■ 직선 그리기

OpenCV를 이용해 이미지나 비디오에 그림을 그리는 법에 대한 설명입니다.

  • cv2.line(img, start, end, color [, thickness, lineType]): 직선 그리기
    • img: 그림 그릴 대상 이미지, Numpy 배열
    • start: 선 시작 지점 좌표 (x, y)
    • end: 선 끝 지점 좌표 (x, y)
    • color: 선 색상, (Blue, Green, Red), 0 ~ 255
    • thickness = 1: 선 두께
    • lineType: 선 그리기 형식
      • cv2.LINE_4: 4 연결 선 알고리즘
      • cv2.LINE_8: 8 연결 선 알고리즘
      • cv2.LINE_AA: 안티에일리어싱(antialiasing, 계단 현상 없는 선)

img에 선을 그리는 함수에 대한 설명입니다. img 이미지에 start 지점에서 end 지점까지 선을 그립니다. color는 선의 색상을 표현하는 것으로 0 ~ 255 사이의 값 3개로 구성해 표현합니다. 일반적으로 웹에서 사용하는 (R, G, B)와 순서가 반대입니다. thickness는 선의 두께를 의미하고 값을 따로 지정하지 않으면 1이 됩니다. 마지막 lineType은 선을 표현하는 3개의 상수를 선택하는 부분인데, cv2.LINE_4와 8은 각각 브레젠햄 알고리즘의 4연결, 8연결을 의미하고 cv2.LINE_AA는 가우시안 필터를 이용합니다.

 

import cv2

img = cv2.imread('../img/blank_500.jpg')

cv2.line(img, (50, 50), (150, 50), (255,0,0))   # 파란색 1픽셀 선
cv2.line(img, (200, 50), (300, 50), (0,255,0))  # 초록색 1픽셀 선
cv2.line(img, (350, 50), (450, 50), (0,0,255))  # 빨간색 1픽셀 선

# 하늘색(파랑+초록) 10픽셀 선      
cv2.line(img, (100, 100), (400, 100), (255,255,0), 10)          
# 분홍(파랑+빨강) 10픽셀 선      
cv2.line(img, (100, 150), (400, 150), (255,0,255), 10)          
# 노랑(초록+빨강) 10픽셀 선      
cv2.line(img, (100, 200), (400, 200), (0,255,255), 10)          
# 회색(파랑+초록+빨강) 10픽셀 선  
cv2.line(img, (100, 250), (400, 250), (200,200,200), 10)        
# 검정 10픽셀 선    
cv2.line(img, (100, 300), (400, 300), (0,0,0), 10)                    

# 4연결 선
cv2.line(img, (100, 350), (400, 400), (0,0,255), 20, cv2.LINE_4)   
# 8연결 선
cv2.line(img, (100, 400), (400, 450), (0,0,255), 20, cv2.LINE_8)    
# 안티에일리어싱 선 
cv2.line(img, (100, 450), (400, 500), (0,0,255), 20, cv2.LINE_AA)   
# 이미지 전체에 대각선 
cv2.line(img, (0,0), (500,500), (0,0,255))                      

cv2.imshow('lines', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

위 코드를 보면 '../img/blank_500.jpg'라는 흰색의 500x500 크기 이미지를 불러옵니다. 선을 그어주는 모습인데 실행결과를 보면

 

이와 같은 모습입니다. LINE_4와 8은 큰 차이를 느낄 수 없지만, LINE_AA는 계단 현상이 없는 을 볼 수 있습니다.

 

■ 사각형 그리기

사각형을 그리는 함수는 cv2.rectangle()입니다.

  • cv2.rectangle(img, start, end, color [, thickness, lineType]): 사각형 그리기
    • img: 그림 그릴 대상 이미지, Numpy 배열
    • start: 사각형 시작 꼭짓점 (x, y)
    • end: 사각형 끝 꼭짓점 (x, y)
    • coor: 색상(Blue, Green, Red)
    • thickness: 선 두께
      • -1: 색 채우기
    • lineType: 선 타입, cv2.line()과 동일

cv2.line()과 거의 유사한 형태를 보이지만 thickness에 약간 차이가 있습니다. -1을 지정하면 사각형 면 전체를 color로 채웁니다. 사각형을 그리기 위한 좌표는 시작 지점의 좌표 두 쌍과 그 반대 지점의 좌표 두 쌍으로 표현합니다.

 

import cv2

img = cv2.imread('../img/blank_500.jpg')

# 좌상, 우하 좌표로 사각형 그리기
cv2.rectangle(img, (50, 50), (150, 150), (255,0,0) )        
# 우하, 좌상 좌표로 사각형 그리기
cv2.rectangle(img, (300, 300), (100, 100), (0,255,0), 10 )  
# 우상, 좌하 좌표로 사각형 채워 그리기 ---①
cv2.rectangle(img, (450, 200), (200, 450), (0,0,255), -1 )  

cv2.imshow('rectangle', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

이 코드 실행 결과

 

이와 같이 사각형 3개가 그려지는 것을 볼 수 있습니다. 보통 많은 그리기 도구들은 사각형을 그릴 때 좌상단 꼭짓점과 우하단 꼭짓점 좌표를 사용하는데, cv2.rectangle() 함수는 어느 지점이든 시작 지점과 그 반대 지점을 사용한다는 것이 특징입니다. 사각형의 크기는 두 좌표의 차이만큼 이 됩니다. thickness를 -1로 지정해 사각형이 채워지는 모습도 볼 수 있습니다.

 

■ 다각형 그리기

다각형을 그리는 함수는 cv2.polylines()입니다.

  • cv2.polylines(img, points, isClosed, color [, thickness, lineType): 다각형 그리기
    • img: 그림 그릴 대상 이미지
    • points: 꼭짓점 좌표, Numpy 배열 리스트
    • isClosed: 닫힌 도형 여부, True / False
    • color: 색상(Blue, Green, Red)
    • thickness: 선 두께
    • lineType: 선 타입, cv2.line()과 동일

points 인자는 다각형을 그리기 위한 여러 개의 꼭짓점 좌표를 전달합니다. 형태는 Numpy 배열입니다. isClosed 인자는 Boolean 타입인데, True는 첫 꼭짓점과 마지막 꼭짓점을 연결해서 닫힌 도형(면)을 그리게 하고, False는 단순히 여러 꼭짓점을 잇는 선을 그리게 합니다.

 

import cv2
import numpy as np                          # 좌표 표현을 위한 numpy 모듈  ---①

img = cv2.imread('../img/blank_500.jpg')

# Numpy array로 좌표 생성 ---②
# 번개 모양 선 좌표
pts1 = np.array([[50,50], [150,150], [100,140],[200,240]], dtype=np.int32) 
# 삼각형 좌표
pts2 = np.array([[350,50], [250,200], [450,200]], dtype=np.int32) 
# 삼각형 좌표
pts3 = np.array([[150,300], [50,450], [250,450]], dtype=np.int32) 
# 5각형 좌표
pts4 = np.array([[350,250], [450,350], [400,450], [300,450], [250,350]],\
                 dtype=np.int32) 

# 다각형 그리기 ---③
cv2.polylines(img, [pts1], False, (255,0,0))       # 번개 모양 선 그리기
cv2.polylines(img, [pts2], False, (0,0,0), 10)     # 3각형 열린 선 그리기 ---④
cv2.polylines(img, [pts3], True, (0,0,255), 10)    # 3각형 닫힌 도형 그리기 ---⑤
cv2.polylines(img, [pts4], True, (0,0,0))          # 5각형 닫힌 도형 그리기

cv2.imshow('polyline', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

실행 결과를 보면 

 

이와 같이 나옵니다. 다각형의 좌표들을 Numpy 배열로 묶은 모습을 볼 수 있습니다. 열린 도형과 닫힌 도형의 차이도 볼 수 있습니다. cv2.polylines() 함수는 선의 굵기를 표현하는 인자에 -1을 지정해서 채우기 효과를 지원하지 않습니다.

 

■ 원, 타원, 호 그리기

원과 타원 그리고 호를 그리기 위한 함수는 다음과 같습니다.

  • cv2.circle(img, center, radius, color [, thickness, lineType]): 원 그리기 함수
    • img: 그림 대상 이미지
    • center: 원점 좌표(x, y)
    • radius: 원의 반지름
    • color: 색상(Blue, Green, Red)
    • thickness: 선 두께(-1: 채우기)
    • lineType: 선 타입, cv2.line()과 동일
  • cv2.ellipse(img, center, axes, angle, from, to, color [, thickness, lineType]): 호나 타원 그리기 함수
    • img: 그림 대상 이미지
    • center: 원점 좌표(x, y)
    • axes: 기준 축 길이
    • angle: 기준 축 회전 각도
    • from, to: 호를 그릴 시작 각도와 끝 각도

타원이나 호를 그리거나 할 때 사용하는 것은 ellipse입니다.

 

import cv2

img = cv2.imread('../img/blank_500.jpg')

# 원점(150,150), 반지름 100 ---①
cv2.circle(img, (150, 150), 100, (255,0,0))     
# 원점(300,150), 반지름 70 ---②
cv2.circle(img, (300, 150), 70, (0,255,0), 5)   
# 원점(400,150), 반지름 50, 채우기 ---③
cv2.circle(img, (400, 150), 50, (0,0,255), -1)  

# 원점(50,300), 반지름(50), 회전 0, 0도 부터 360도 그리기 ---④
cv2.ellipse(img, (50, 300), (50, 50), 0, 0, 360, (0,0,255))    
# 원점(150, 300), 아래 반원 그리기 ---⑤
cv2.ellipse(img, (150, 300), (50, 50), 0, 0, 180, (255,0,0))    
#원점(200, 300), 윗 반원 그리기 ---⑥
cv2.ellipse(img, (200, 300), (50, 50), 0, 181, 360, (0,0,255))    

# 원점(325, 300), 반지름(75,50) 납작한 타원 그리기 ---⑦
cv2.ellipse(img, (325, 300), (75, 50), 0, 0, 360, (0,255,0))    
# 원점(450,300), 반지름(50,75) 홀쭉한 타원 그리기 ---⑧
cv2.ellipse(img, (450, 300), (50, 75), 0, 0, 360, (255,0,255))    

# 원점(50, 425), 반지름(50,75), 회전 15도 ---⑨
cv2.ellipse(img, (50, 425), (50, 75), 15, 0, 360, (0,0,0))    
# 원점(200,425), 반지름(50,75), 회전 45도 ---⑩
cv2.ellipse(img, (200, 425), (50, 75), 45, 0, 360, (0,0,0))    

# 원점(350,425), 홀쭉한 타원 45도 회전 후 아랫 반원 그리기 ---⑪
cv2.ellipse(img, (350, 425), (50, 75), 45, 0, 180, (0,0,255))    
# 원점(400,425), 홀쭉한 타원 45도 회전 후 윗 반원 그리기 ---⑫
cv2.ellipse(img, (400, 425), (50, 75), 45, 181, 360, (255,0,0))    

cv2.imshow('circle', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

이와 같이 코드를 작성해 실행시키면

 

이와 같은 결과를 얻을 수 있습니다. cv2.circle()을 이용해 원을 그리는 모습을 볼 수 있고 cv2.ellipse()를 이용해 타원, 호, 원을 그리는 모습도 볼 수 있습니다. 반지름 비율을 동일하게 가져가면서 시작 각과 끝 각을 0, 360으로 지정하면 원을 그릴 수 있습니다.

 

■ 글씨 그리기

문자열을 이미지에 표시하는 함수는 cv2.putText()입니다.

  • cv2.putText(img, text, point, fontFace, fontSize, color [, thickness, lineType])
    • img: 글씨를 표시할 이미지
    • text: 표시할 문자열
    • point: 글씨를 표시할 좌표(좌측 하단 기준) (x, y)
    • fontFace: 글꼴
      • cv2.FONT_HERSHEY_PLAIN: 산세리프체 작은 글꼴
      • cv2.FONT_HERSHEY_SIMPLE: 산세리프체 일반 글꼴
      • cv2.FONT_HERSHEY_DUPLEX: 산세리프체 진한 글꼴
      • cv2.FONT_HERSHEY_COMPLEX_SMALL: 세리프체 작은 글꼴
      • cv2.FONT_HERSHEY_COMPLEX: 세리프체 일반 글꼴
      • cv2.FONT_HERSHEY_TRIPLEX: 세리프체 진한 글꼴
      • cv2.FONT_HERSHEY_SCRIPT_SIMPLEX: 필기체 산세리프 글꼴
      • cv2.FONT_HERSHEY_SCRIPT_COMPLEX: 필기체 세리프 글꼴
      • cv2.FONT_ITALIC: 이탤릭체 플래그
    • fontSize: 글꼴 크기
    • color, thickness, lineType: cv2.rectangle()과 동일

point 좌표는 문자열의 좌측 하단을 기준으로 합니다.

 

실행결과를 보면 이와 같이 여러 종류의 글자들이 출력되는 것을 볼 수 있습니다.

 

2.3 창 관리

한 개 이상의 이미지를 여러 창에 띄우거나 각 창에 키보드와 마우스 이벤트를 처리하려면 창을 관리하는 기능도 필요합니다.

  • cv2.namedWindow(title [, option]): 이름을 갖는 창 열기
    • title: 창 이름, 제목 줄에 표시
    • option: 창 옵션, 'cv2.WINDOW_'로 시작
      • cv2.WINDOW_NORMAL: 임의의 크기, 사용자 창 크기 조정 가능
      • cv2.WINDOW_AUTOSIZE: 이미지와 같은 크기, 창 크기 재조정 불가능
  • cv2. moveWindow(title, x, y): 창 위치 이동
    • title: 위치를 변경할 창의 이름
    • x, y: 이동할 창의 위치
  • cv2.resizeWindow(title, width, height): 창 크기 변경
    • title: 크기를 변경할 창의 이름
    • width, height: 크기를 변경할 창의 폭과 높이
  • cv2.destroyWindow(title): 창 닫기
    • title: 닫을 대상 창 이름
  • cv2.destroyAllWindow(): 열린 모든 창 닫기
import cv2

file_path = '../img/girl.jpg'
img = cv2.imread(file_path)                            # 이미지를 기본 값으로 읽기
img_gray = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE) # 이미지를 그레이 스케일로 읽기

cv2.namedWindow('origin')                               # origin 이름으로 창 생성
cv2.namedWindow('gray', cv2.WINDOW_NORMAL)              # gray 이름으로 창 생성
cv2.imshow('origin', img)                               # origin 창에 이미지 표시
cv2.imshow('gray', img_gray)                            # gray 창에 이미지 표시

cv2.moveWindow('origin', 0, 0)                          # 창 위치 변경
cv2.moveWindow('gray', 100, 100)                        # 창 위치 변경

cv2.waitKey(0)                                          # 아무키나 누르면
cv2.resizeWindow('origin', 200, 200)                    # 창 크기 변경 (변경 안됨)
cv2.resizeWindow('gray', 100, 100)                      # 창 크기 변경 (변경 됨))

cv2.waitKey(0)                                          # 아무키나 누르면
cv2.destroyWindow("gray")                               # gray 창 닫기

cv2.waitKey(0)                                          # 아무키나 누르면
cv2.destroyAllWindows()                                 # 모든 창 닫기

'origin', 'gray'라는 이름을 가진 창을 만들어주고 해당 창에 각각 이미지들을 표시합니다. 각 창을 해당 좌표에 이동을 한 후 아무 키나 입력을 하면 창들의 크기가 변경됩니다. 'origin'창은 cv2.WINDOW_AUTOSIZE로 열었기 때문에 창의 크기는 변경되지 않습니다. 'gray' 창은 크기가 변경됩니다.

 

2.4 이벤트 처리

■ 키보드 이벤트

cv2.waitKey(delay) 함수를 사용하면 키보드의 입력을 알아낼 수 있습니다. delay 인자에 밀리초(ms, 0.001초) 단위로 숫자를 전달하면 해당 시간 동안 프로그램을 멈추고 대기하다가 키보드의 눌린 키에 대응하는 코드 값을 정수로 반환합니다. 지정한 시간까지 키보드 입력이 없다면 -1을 반환합니다. delay가 0이라면 무한대로 기다리겠다는 뜻이고 이 경우 -1을 반환하는 일은 없습니다.

출력되는 키 값을 보면 ASCII 코드와 같고 ord() 함수를 이용해 특정 문자와 비교 가능합니다. 몇몇 64bit 환경에서 cv2.waitKey() 함수는 8bit(ASCII코드 크기)보다 큰 32bit 정수를 반환해서 그 값을 ord()함수를 통해 비교하면 서로 다른 값으로 판단할 때가 있습니다. 그래서 하위 8비트를 제외한 비트를 지워야 하는 경우가 있으며 이때 & 0xFF를 해주면 됩니다.

 

import cv2

img_file = "../img/girl.jpg"
img = cv2.imread(img_file)
title = 'IMG'  # 창 이름
x, y = 100, 100  # 최초 좌표

while True:
    cv2.imshow(title, img)
    cv2.moveWindow(title, x, y)
    key = cv2.waitKey(0) & 0xFF  # 키보드 입력을 무한 대기, 8비트 마스크처리
    print(key, chr(key))  # 키보드 입력 값,  문자 값 출력
    if key == ord('h'):  # 'h' 키 이면 좌로 이동
        x -= 10
    elif key == ord('j'):  # 'j' 키 이면 아래로 이동
        y += 10
    elif key == ord('k'):  # 'k' 키 이면 위로 이동
        y -= 10
    elif key == ord('l'):  # 'l' 키 이면 오른쪽으로 이동
        x += 10
    elif key == ord('q') or key == 27:  # 'q' 이거나 'esc' 이면 종료
        break
        cv2.destroyAllWindows()
    cv2.moveWindow(title, x, y)  # 새로운 좌표로 창 이동

해당 코드는 이미지를 표시하고 'h', 'j', 'k', 'l'을 입력하면 해당 경우에 맞춰 창이 이동합니다. 'q' 또는 'esc'를 누른 경우 창이 전부 사라집니다.

 

■ 마우스 이벤트

마우스에서 입력을 받기 위해서는 이벤트를 처리할 함수를 미리 선언해 놓고 cv2.setMouseCallback() 함수에 그 함수를 전달해야 합니다.

 

def onMouse(event, x, y, flags, param):
	# 여기에 마우스 이벤트에 맞게 해야 할 작업을 작성합니다.
    pass
    
cv2.setMouseCallback('title', onMouse)

간단하게 표현하면 위와 같은 형식입니다.

  • cv2.setMouseCallback(win_name, onMouse [, param]): onMouse 함수를 등록
    • win_name: 이벤트를 등록할 윈도 이름
    • onMouse: 이벤트 처리를 위해 미리 선언해 놓은 콜백 함수
    • param: 필요에 따라 onMouse 함수에 전달할 인자
  • MouseCallback(event, x, y, flags, param): 콜백 함수 선언부
    • event: 마우스 이벤트 종류, cv2.EVENT_ 로 시작하는 상수(12가지)
      • cv2.EVENT_MOUSEMOVE: 마우스 움직임
      • cv2.EVENT_LBUTTONDOWN: 왼쪽 버튼 누름
      • cv2.EVENT_RBUTTONDOWN: 오른쪽 버튼 누름
      • cv2.EVENT_MBUTTONDOWN: 가운데 버튼 누름
      • cv2.EVENT_LBUTTONUP: 왼쪽 버튼 뗌
      • cv2.EVENT_RBUTTONUP: 오른쪽 버튼 뗌
      • cv2.EVENT_MBUTTONUP: 가운데 버튼 뗌
      • cv2.EVENT_LBUTTONDBLCLK: 왼쪽 버튼 더블 클릭
      • cv2.EVENT_RBUTTONDBLCLK: 오른쪽 버튼 더블 클릭
      • cv2.EVENT_MBUTTONDBLCLK: 가운데 버튼 더블 클릭
      • cv2.EVENT_MOUSEWHEEL: 휠 스크롤
      • cv2.EVENT_MOUSEHWHEEL: 휠 가로 스크롤
    • x, y: 마우스 좌표
    • flags: 마우스 동작과 함께 일어난 상태, cv2.EVENT_FLAG_로 시작하는 상수(6가지)
      • cv2.EVENT_FLAG_LBUTTON(1): 왼쪽 버튼 누름
      • cv2.EVENT_FLAG_RBUTTON(2): 오른쪽 버튼 누름
      • cv2.EVENT_FLAG_MBUTTON(3): 가운데 버튼 누름
      • cv2.EVENT_FLAG_CTRLKEY(8): ctrl키 누름
      • cv2.EVENT_FLAG_SHIFTKEY(16): shift키 누름
      • cv2.EVENT_FLAG_ALTKEY(32): alt키 누름
    • param: cv2.setMouseCallback 함수에서 전달한 인자
import cv2

title = 'mouse event'                   # 창 제목
img = cv2.imread('../img/blank_500.jpg') # 백색 이미지 읽기
cv2.imshow(title, img)                  # 백색 이미지 표시

def onMouse(event, x, y, flags, param): # 아무스 콜백 함수 구현 ---①
    print(event, x, y, )                # 파라미터 출력
    if event == cv2.EVENT_LBUTTONDOWN:  # 왼쪽 버튼 누름인 경우 ---②
        cv2.circle(img, (x,y), 30, (0,0,0), -1) # 지름 30 크기의 검은색 원을 해당 좌표에 그림
        cv2.imshow(title, img)          # 그려진 이미지를 다시 표시 ---③

cv2.setMouseCallback(title, onMouse)    # 마우스 콜백 함수를 GUI 윈도우에 등록 ---④

while True:
    if cv2.waitKey(0) & 0xFF == 27:     # esc로 종료
        break
cv2.destroyAllWindows()

이렇게 코드를 작성하면 마우스를 클릭한 곳에 지름이 30픽셀인 동그라미가 그려집니다.

 

이와 같이 좌클릭을 하면 그려집니다. esc를 누르면 프로그램이 종료됩니다. 이 코드의 중요한 부분은 이벤트 내에서 그리기를 했다면 반드시 그림이 그려진 이미지를 다시 화면에 표시해야 한다는 것입니다.

그리고 onMouse(event, x, y, flags, param) 함수에 5개의 인자를 선언해줘야만 합니다. 그렇지 않으면 오류가 뜹니다.

첫 번째 인자 event는 발생한 이벤트의 종류를 나타내는 것으로 cv2.EVENT_로 시작하는 상수 값 중 하나입니다. x, y는 마우스의 좌표입니다. flags는 이벤트가 발생할 때 키보드나 마우스의 추가적인 상태를 알려줍니다. 이 값과 비교할 상수는 이름이 cv2.EVENT_FLAG_로 시작하는 선언되어 있는 상수들입니다. 선언된 상수들이 실제로 갖는 값은 0, 1, 2, 3, 4처럼 순차적으로 증가하는 값이 아니라 1, 2, 4, 8, 16, 32 순으로 2진수 비트 자릿수에 맞는 값을 각각 갖습니다. 따라서 함수의 인자로 전달되는 값은 여러 상태를 나타내는 값을 조합한 값으로, 어떤 상태인지 알기 위해서는 비트 단위 &(논리 곱) 또는 |(논리 합) 연산을 써서 알아내야 합니다.

예를 들어 flag 값이 8이라면 cv2.EVENT_FLAG_CTRLKEY의 값과 같습니다. 이런 경우 flag 값과 관심 있는 상수를 비교해서 맞으면 컨트롤 키가 눌러진 상태로 판단하면 됩니다. 만약 flag 값이 25라면 어떤 플래그 상수와 비교해도 맞는 것을 찾을 수 없습니다. 이 경우 25 = 1 + 8 + 16 이므로 1, 8, 16에 맞는 플래그 상수와 따로따로 비교해서 찾아내야 합니다. 이것은 각각 cv2.EVENT_FLAG_LBUTTON, cv2.EVENT_FLAG_CTRLKEY, cv2.EVENT_FLAG_SHIFTKEY에 해당합니다.

 

import cv2

title = 'mouse event'                   # 창 제목
img = cv2.imread('../img/blank_500.jpg') # 백색 이미지 읽기
cv2.imshow(title, img)                  # 백색 이미지 표시

colors = {'black':(0,0,0),
         'red' : (0,0,255),
         'blue':(255,0,0),
         'green': (0,255,0) } # 색상 미리 정의

def onMouse(event, x, y, flags, param): # 아무스 콜백 함수 구현 ---①
    print(event, x, y, flags)                # 파라미터 출력
    color = colors['black']
    if event == cv2.EVENT_LBUTTONDOWN:  # 왼쪽 버튼 누름인 경우 ---②
        # 컨트롤키와 쉬프트 키를 모두 누른 경우
        if flags & cv2.EVENT_FLAG_CTRLKEY and flags & cv2.EVENT_FLAG_SHIFTKEY : 
            color = colors['green']
        elif flags & cv2.EVENT_FLAG_SHIFTKEY : # 쉬프트 키를 누른 경우
            color = colors['blue']
        elif flags & cv2.EVENT_FLAG_CTRLKEY : # 컨트롤 키를 누른 경우
            color = colors['red']
        # 지름 30 크기의 검은색 원을 해당 좌표에 그림
        cv2.circle(img, (x,y), 30, color, -1) 
        cv2.imshow(title, img)          # 그려진 이미지를 다시 표시 ---③

cv2.setMouseCallback(title, onMouse)    # 마우스 콜백 함수를 GUI 윈도우에 등록 ---④

while True:
    if cv2.waitKey(0) & 0xFF == 27:     # esc로 종료
        break
cv2.destroyAllWindows()

위와 같이 코드를 작성하면 컨트롤 키를 누르면 빨간색, 시프트 키를 누르면 파란색, 두 키를 동시에 누르면 초록색으로 그리게 됩니다.

조건문을 통해 어떤 키를 눌렀는지 확인할 수 있습니다.

 

■ 트랙바(track-bar)

트랙바는 슬라이드 모양의 인터페이스를 마우스로 움직여서 값을 입력받는 GUI 요소입니다. cv2.createTrack() 함수로 생성하면서 보이기를 원하는 창의 이름을 지정합니다. 마우스 이벤트의 방식과 마찬가지로 트랙바를 움직였을 때 동작할 함수를 미리 준비해서 함께 전달합니다.

트랙바의 값을 얻기 위해서 cv2.getTrackbarPos() 함수를 사용하면 됩니다.

def onChange(value):
	v = cv2.getTrackbarPos("trackbar", "win_name")
   
cv2.createTrackbar("trackbar", "win_name", 0, 100, onChange)

이와 같은 형태로 사용하면 됩니다.

  • cv2.createTrackbar(trackbar_name, win_name, value, count, onchange): 트랙바 생성
    • trackbar_name: 트랙바 이름
    • win_name: 트랙바를 표시할 창 이름
    • value: 트랙바 초기 값, 0 ~ count 사이의 값
    • count: 트랙바 눈금의 개수, 트랙바가 표시할 수 있는 최대 값
    • onChange: TrackbarCallback, 트랙바 이벤트 핸들러 함수
  • TrackbarCallback(value): 트랙바 이벤트 콜백 함수
    • value: 트랙바가 움직인 새 위치 값
  • pos = cv2.getTrackbarPos(trackbar_name, win_name)
    • trackbar_name: 찾고자 하는 트랙바 이름
    • win_name: 트랙바가 있는 창의 이름
    • pos: 트랙바 위치 값
import cv2
import numpy as np

win_name = 'Trackbar'                                   # 창 이름

img = cv2.imread('../img/blank_500.jpg')
cv2.imshow(win_name,img)                                # 초기 이미지를 창에 표시

# 트랙바 이벤트 처리 함수 선언 ---①
def onChange(x):                                        
    print(x)                                            # 트랙바 새로운 위치 값 --- ②
    # 'R', 'G', 'B' 각 트랙바 위치 값    --- ③
    r = cv2.getTrackbarPos('R',win_name)               
    g = cv2.getTrackbarPos('G',win_name)               
    b = cv2.getTrackbarPos('B',win_name)               
    print(r, g, b)
    img[:] = [b,g,r]                                    # 기존 이미지에 새로운 픽셀 값 적용 --- ④
    cv2.imshow(win_name, img)                           # 새 이미지 창에 표시

# 트랙바 생성    --- ⑤
cv2.createTrackbar('R', win_name, 255, 255, onChange)  
cv2.createTrackbar('G', win_name, 255, 255, onChange)
cv2.createTrackbar('B', win_name, 255, 255, onChange)

while True:
    if cv2.waitKey(1) & 0xFF == 27:
        break
cv2.destroyAllWindows()        

위 코드는 트랙바 3개를 생성하여 각 트랙바를 움직여 이미지의 색상을 조정하는 코드입니다.

 

이와 같이 트랙바를 조정해 색을 변경할 수 있습니다. 트랙바 3개를 만들고 값이 변경되면 cv2.getTrackbarPos() 함수로 트랙바의 위치를 얻어 색을 변경해줍니다.

'Deep Learning(강의 및 책) > OpenCV' 카테고리의 다른 글

[OpenCV] 영상 매칭과 추적  (0) 2022.08.20
[OpenCV] 영상 분할  (0) 2022.08.04
[OpenCV] 영상 필터  (0) 2022.08.01
[OpenCV] 기하학적 변환  (0) 2022.07.30
[OpenCV] 이미지 프로세싱 기초  (0) 2022.07.23