귀퉁이 서재

컴퓨터 비전 - 7. 객체 탐지를 위한 OpenCV 기본 사용법 본문

딥러닝 컴퓨터 비전

컴퓨터 비전 - 7. 객체 탐지를 위한 OpenCV 기본 사용법

Baek Kyun Shin 2022. 5. 8. 20:32

※ 이 글은 권철민 님의 딥러닝 컴퓨터 비전 완벽 가이드 강의를 바탕으로 작성했습니다.

이번 시간에는 객체 탐지를 위한 OpenCV의 기본 사용법을 알아보겠습니다. OpenCV로 이미지와 영상을 처리하기 위한 간단한 메서드를 살펴보죠.

이 글에서 사용한 코드: https://github.com/BaekKyunShin/Deep-Learning-Computer-Vision/blob/master/preliminary/opencv_basic.ipynb


실습은 구글 Colab 기반으로 설명합니다.

OpenCV로 이미지 처리하기

먼저, 간단히 OpenCV를 활용해 이미지를 출력해보겠습니다. 그에 앞서 이미지를 하나 다운로드하겠습니다.

!mkdir ./data # 현재 디렉터리(content) 아래 data 디렉터리 생성
!wget -O /content/data/beatles01.jpg https://raw.githubusercontent.com/chulminkw/DLCV/master/data/image/beatles01.jpg

OpenCV의 imread() 메서드를 활용해 다운로드한 이미지를 출력해보죠. imread() 메서드는 인자로 전달받은 이미지를 읽어 넘파이 배열로 반환합니다. 이때 주의할 점은 imread()가 RGB를 BGR로 변환한다는 점입니다. 원하지 않는 이미지 색상으로 출력하죠. 다음 출력 결과를 봅시다.

import matplotlib.pyplot as plt
import cv2

img = cv2.imread('/content/data/beatles01.jpg')

plt.figure(figsize=(8, 8))
plt.imshow(img);

RGB 색상 배열이 아니라 BGR 색상 배열로 불러오므로 원래 색상과 다릅니다. 빨간색(R)이 파란색(B)으로 바뀌어서, 얼굴이 스머프처럼 됐네요. BGR 형태의 이미지 배열을 RGB 형태로 바꾸려면 cv2.cvtColor(이미지 배열, cv2.COLOR_BGR2RGB)를 호출하면 됩니다.

img = cv2.imread('/content/data/beatles01.jpg')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR을 RGB로 변경 ---①

plt.figure(figsize=(8, 8))
plt.imshow(img);

OpenCV로 이미지를 불러오려면 반드시 코드 ①과 같이 BRG을 RGB 색상 배열로 바꿔야 합니다. 올바른 색상으로 출력됐습니다.


OpenCV로 영상 처리하기

OpenCV는 간편하게 비디오 영상처리를 하는 API를 제공합니다. 이번에는 영상 처리를 위한 간단한 OpenCV 메서드를 알아보겠습니다. 먼저 짧은 동영상을 하나 다운로드하겠습니다.

!mkdir ./data
!wget -O /content/data/Night_Day_Chase.mp4 https://github.com/chulminkw/DLCV/blob/master/data/video/Night_Day_Chase.mp4?raw=true

다운로드한 Night_Day_Chase.mp4 영상을 재생해볼까요? 다음 그림의 번호 순서대로 영상을 다운로드받아보세요. 

다운로드받은 영상을 실행해봅시다. 재생 시간이 49초인 영상이 실행됩니다.

이어서 이 영상에 관한 정보를 출력해볼까요?

video_input_path = '/content/data/Night_Day_Chase.mp4' # 영상 파일 경로

cap = cv2.VideoCapture(video_input_path) # 비디오 캡쳐 객체 생성 ---①
video_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH) # 비디오 캡쳐 객체 프레임의 너비 ---②
video_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT) # 비디오 캡쳐 객체 프레임의 높이 ---③
video_size = (round(video_width), round(video_height)) # 비디오 크기 (200, 400)
video_fps = cap.get(cv2.CAP_PROP_FPS ) # FPS(Frames Per Second) ---④
frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # Frame 갯수

print('총 Frame 갯수:', frame_cnt, '/ FPS:', round(video_fps), '/ Frame 크기:', video_size)

    총 Frame 갯수: 1383 / FPS: 28 / Frame 크기: (1216, 516)

총 프레임 갯수는 1,383개, FPS는 28, 프레임 크기는 (1216, 516)이군요. 코드를 살펴볼까요?

① cv2.VideoCapture() 메서드는 영상을 개별 프레임(Frame)으로 하나씩 읽게끔 하는 비디오 캡쳐 객체를 만듭니다. 비디오 캡쳐 객체는 영상 스트리밍을 프레임별로 캡쳐해 처리하는 기능을 제공합니다. 이 비디오 캡쳐 객체에 get() 메서드를 호출하면 영상 파일이 갖는 다양한 속성을 가져올 수 있습니다. ② cv2.CAP_PROP_FRAME_WIDTH는 영상 프레임의 너비, ③ cv2.CAP_PROP_FRAME_HEIGHT는 영상 프레임의 높이를 뜻합니다. ④ cv2.CAP_PROP_FPS는 영상의 FPS를 뜻합니다. FPS란 Frames Per Second의 약자로 일초에 처리하는 프레임 수를 말합니다. 일초에 처리하는 프레임이 많을수록 영상을 끊김없이 부드럽게 재생합니다. 곧, FPS가 높을수록 고품질 영상이죠. 아래 그림을 보시면 이해가 쉽습니다. 

출처 : https://www.datavideo.com/af/article/486/what-is-frame-rate-and-how-to-set-the-fps-for-your-video

다음으로 영상의 각 프레임에 임의의 사각형을 그려보겠습니다. 나중에는 객체를 탐지해서 해당 객체를 나타내는 경계 박스를 그려볼 겁니다. 여기서는 프레임에 사각형을 그리는 방법만 알아봅니다. 임의의 사각형을 그리는 거죠. 다음 코드는 앞으로 자주 사용할 코드입니다. 잘 익혀두시기 바랍니다.

import time

# 리눅스에서는 video output의 확장자를 반드시 avi로 설정해야 함
video_output_path = '/content/data/Night_Day_Chase_out.mp4' # 영상 output 파일 경로
codec = cv2.VideoWriter_fourcc(*'XVID') # Codec은 *'XVID'로 설정 ---①
# VideoWriter 객체 생성 ---②
video_writer = cv2.VideoWriter(video_output_path, codec, video_fps, video_size) 

green_color=(0, 255, 0)
red_color=(0, 0, 255)

start_time = time.time() # 시작 시간
index = 0 # 인덱스

while True:
    hasFrame, img_frame = cap.read() # 비디오 캡쳐 객체에서 Frame 하나 읽기 ---③
    if not hasFrame:
        break
    index += 1
    # 현재 프레임에 임의의 사각형 그리기 ---④
    cv2.rectangle(img_frame, (300, 100, 800, 400), color=green_color, thickness=2)
    text = f'Frame {index}'
    cv2.putText(img_frame, text, (300, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, red_color, 1)
    
    video_writer.write(img_frame) # 사각형을 그린 프레임을 저장 ---⑤

end_time = time.time() # 완료 시간
print('Write 처리 시간:', round(end_time - start_time, 1))

video_writer.release() # video_writer 닫기
cap.release() # cap 닫기

    Write 처리 시간: 20.7

①은 코덱을 설정하는 코드입니다. 코드 ②에서 VideoWriter 객체를 만드는데, 이때 인코딩 코덱 유형을 전달해야 하기 때문입니다. 영상을 저장할 때 특정 유형으로 동영상을 인코딩할 수 있습니다. 인코딩 유형으로는 DIVX, XVID, MJPG, X264, WMV1, WMV2 등이 있습니다.

②에서는 VideoWriter 객체를 생성합니다. VideoWriter 객체는 VideoCapture로 읽어들인 프레임을 동영상으로 저장하는 기능을 제공합니다. 추후 VideoWriter 객체는 저장할 동영상 파일 위치, 인코딩 코덱 유형, FPS 수치, 프레임 크기를 인자로 받아 이 값에 따라 동영상을 저장합니다.

이어서 While True문이 나오죠? 모든 프레임을 불러와서 처리를 끝내면 이 While문이 끝납니다. 앞서 Night_Day_Chase.mp4 영상을 활용해 비디오 캡쳐 객체를 만들었죠. 이 객체를 cap 변수에 저장했습니다. 그리고 이 영상의 프레임 수는 1,383개였습니다. 다시 말해 프레임 1,383개를 모두 불러오고 일정한 처리를 마친 뒤, 더 이상 불러올 프레임이 없으면 While문은 끝나게 코드를 짰습니다. While문이 어떻게 구성됐는지 보죠.

③ cap.read()는 비디오 캡쳐 객체 cap에서 프레임을 하나 읽습니다. cap.read()는 반환값이 두 개입니다. 첫 번째 반환값(hasFrame)은 프레임이 있는지 여부(True, False)를 뜻하고, 두 번째 반환값(img_frame)은 불러온 프레임을 뜻합니다. 만약 불러올 프레임이 없다면, 곧 hasFrame이 False라면 While문을 끝냅니다. 반대로 불러올 프레임이 있다면 ④ 현재 프레임에 임의의 사각형을 그린 뒤, ⑤ 사각형을 그린 프레임을 저장합니다. ④ cv2.rectangle() 메서드로 현재 프레임(img_frame)에 임의의 사각형을 그리고, cv2.putText() 메서드로 사각형 주변에 프레임 번호도 적어 넣었습니다. ⑤ VideoWriter 객체에 write() 메서드를 호출하면 현재 프레임 이미지를 영상 파일에 저장합니다. 이때 저장할 이미지 프레임과 영상의 프레임 크기가 같아야 합니다.

모든 영상 처리가 끝난 뒤에는 영상 처리 시간을 출력하고, release()를 호출해 VideoWriter 객체와 VideoCapture 객체를 닫습니다.

간단한 영상 처리를 마쳤습니다. 영상에 임의의 사각형을 그린 게 다죠. 영상 처리를 마친 파일을Night_Day_Chase_out.mp4로 저장했으니, 이를 다운로드해 실행해봅시다. 다음과 같이 영상에 사각형이 표시됐네요. 사각형 왼쪽 위에 붉은 글씨로 프레임 번호도 써넣었습니다.

5 Comments
댓글쓰기 폼