귀퉁이 서재

OpenCV - 3. 이미지 및 동영상 읽기 본문

OpenCV

OpenCV - 3. 이미지 및 동영상 읽기

Baek Kyun Shin 2020. 9. 10. 23:35

OpenCV를 활용한 대부분의 작업은 이미지를 읽어서 적절한 연산을 적용한 뒤 결과를 출력하거나 파일로 저장하는 것입니다. 이번 포스팅에서는 OpenCV의 가장 첫 번째 단계인 이미지 입출력에 대해 알아보겠습니다. 이 포스팅은 파이썬으로 만드는 OpenCV 프로젝트(이세우 저)를 정리한 것임을 우선 밝힙니다.

코드: https://github.com/BaekKyunShin/OpenCV_Project_Python/tree/master/02.interface

이미지 읽기

OpenCV를 사용해서 이미지를 화면에 표시하는 가장 간단한 코드는 아래와 같습니다.

이미지 파일은 이곳에서 다운받으시기 바랍니다. 참고로 몇몇 사진(바로 아래 예제 포함)은 제가 직접 찍어서 다운받은 폴더에 없을 겁니다. 가끔 이미지 파일 이름을 바꿔 실행한 것도 있습니다. 파일 이름이 다르다면 결과 이미지를 보고 같은 이미지를 골라주시면 됩니다. 만약 제가 직접 찍은 사진이라 다운받은 폴더에 없다면 아무 사진으로 실습하셔도 무방합니다. 직접 찍은 사진으로 실습하면 더 재미가 있을 겁니다.

# 이미지 파일을 화면에 표시(img_show.py)

import cv2

img_file = "../img/yeosu.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.')
    

코드를 실행하면 멋진 여수 밤바다 사진 새로운 창에 뜹니다. (참고로, 위 사진은 제가 직접 찍었습니다. 직접 찍은 사진를 활용해 코드를 돌려보세요.) cv2.imread() 함수로 이미지를 읽어 올 수 있습니다. cv2.imshow() 함수는 읽어 온 이미지를 화면에 표시해주는 기능을 합니다. 그리고 키보드의 아무 키나 누르면 창이 꺼집니다. 코드에서 cv2.waitKey()가 없으면 창이 떴다가 바로 사라집니다. 키가 입력될 때까지 사진을 뜨게 하는 기능을 cv2.wiatKey()가 하기 때문입니다. 그리고 키가 입력된 후에는 cv2.destroyAllWindows()를 통해 모든 윈도우 창을 끕니다.

이제, 흑백 이미지를 출력해보겠습니다. cv2.imread()의 파라미터로 cv2.IMREAD_GRAYSCALE을 전달하면 흑백 이미지로 불러옵니다. 아래 코드는 위와 동일하고 cv2.IMREAD_GRAYSCALE만 파라미터로 적용해주었습니다.

  • cv2.imread(path, flag)
    path: 이미지 파일 경로
    flag: 이미지를 어떻게 읽을지 방식 설정
    - cv2.IMREAD_COLOR(기본값): 색깔 이미지로 불러옵니다. 이때 투명도(alpha값)는 무시합니다.
    - cv2.IMREAD_GRAYSCALE: 이미지를 흑백톤으로 불러옵니다.
    - cv2.IMREAD_UNCHANGED: 투명도(alpha값)를 포함해 이미지를 그대로 불러옵니다.
# 이미지 파일을 회색으로 화면에 표시(img_show_gray.py)

import cv2

img_file = "../img/yeosu.jpg" 
img = cv2.imread(img_file, cv2.IMREAD_GRAYSCALE)    # 회색으로 읽기

if img is not None:
  cv2.imshow('IMG', img)
  cv2.waitKey()
  cv2.destroyAllWindows()
else:
    print('No image file.')
    

위와 같이 회색톤의 사진이 뜨는 걸 볼 수 있습니다. 다음으로 이미지를 저장하는 방법에 대해 알아보겠습니다.

이미지 저장하기

위 코드에서 cv2.imread() 함수를 호출하면 읽은 사진 파일이 img라는 변수에 담깁니다. cv2.imwrite() 함수를 호출하면 img 변수에 담긴 사진 파일을 자신의 PC에 저장할 수 있습니다. 아래는 원본 컬러 파일인 yeosu.jpg를 읽어 회색으로 변경한 뒤 회색 사진을 yeosu_gray.jpg로 저장하는 코드입니다.

# 이미지 저장하기 (img_write.py)

import cv2

img_file = '../img/yeosu.jpg'
save_file = '../img/yeosu_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 디렉토리에 yeosu_gray.jpg라는 회색 사진이 생성된 것을 볼 수 있습니다.

동영상 파일 읽기

OpenCV는 이미지뿐만 아니라 동영상도 처리할 수 있습니다. 아래는 동영상 파일을 읽기 위한 간단한 코드입니다. 아래 코드를 실행하면 짧은 애니메이션 동영상이 실행될 겁니다.

# 동영상 파일 읽기 (video_play.py)

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(video_file)을 통해 동영상 파일인 video_file의 첫 프레임 읽어 캡처 객체 cap에 저장합니다. 여기서 주의할 점은 동영상 전체가 cap에 담기는 것이 아닙니다. 동영상의 첫 프레임만 담기는 겁니다. cap.isOpened()는 cap 객체가 지정한 파일로 정상적으로 초기화되었는지 확인하는 코드입니다. 초기화가 잘 되었다면 True를 반환하고, 그렇지 않으면 False를 반환합니다. 연속해서 파일의 프레임을 읽어오기 위해 무한루프로 cap.read()를 호출합니다. 프레임을 잘 읽었다면 ret은 True, img는 프레임 이미지가 됩니다. 제대로 읽히지 않았다면 ret은 False, img는 None이 됩니다. 다음 프레임 읽기가 실패하는 경우는 파일이나 장치에 문제가 있거나 파일의 끝에 도달했을 경우입니다. 이제, cv2.imshow(video_file, img)를 호출하여 프레임 이미지를 화면에 표시합니다. imshow의 첫 번째 인자인 video_file은 화면에 나타나는 창의 제목이고, 두 번째 인자인 img는 화면에 표시할 프레임 이미지 객체입니다. 모든 코드가 실행되고 난 뒤에는 cap.release() 함수를 호출해서 자원을 반납해야 합니다.

동영상은 여러 프레임 이미지의 합이라고 생각하시면 위 코드를 이해하기가 쉽습니다. 단편적인 프레임 이미지를 빠른 시간에 보여주면 움직이는 동영상처럼 보이죠? 위 코드도 하나의 프레임 이미지를 빠른 시간에 보여주는 방식으로 동작합니다. cv2.waitKey(25) 코드가 없다면 프레임이 너무 빨리 넘어가 눈으로 볼 수 없습니다. 하지만 25ms의 지연을 주면서 각 프레임을 화면에 표시해주기 때문에 우리는 동영상을 볼 수 있습니다. cv2.waitKey() 안에 들어가는 숫자를 지연 시간이라고 합니다. 25보다 작은 숫자를 넣어주면 화면이 동영상이 빠르게 재생되는 것처럼 보이고, 25보다 큰 숫자를 넣어주면 동영상이 천천히 재생되는 것처럼 보일 것입니다.

카메라(웹캠) 프레임 읽기

이미 저장되어 있는 동영상 파일뿐만 아니라 웹캠으로 라이브 영상을 읽을 수도 있습니다. 만약 사용하시는 노트북이나 PC에 웹캠이 있다면 아래 코드를 실행할 수 있습니다. 아래 코드를 실행하면 웹캠을 통해 자신의 모습이 보이는 창이 하나 뜰 겁니다.

# 카메라(웹캠) 프레임 읽기 (video_cam.py)

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.VideoCapture(0)과 cv.waitKey(1) != -1입니다. 우선, cv2.VideoCapture()은 인자로 동영상 파일 경로를 입력할 수도 있지만 카메라 장치 번호를 입력할 수도 있습니다. 동영상 파일 경로를 입력하면 해당 동영상의 캡처 객체가 return 되지만 카메라 장치 번호를 입력하면 웹캠과 연결됩니다. 카메라 장치 번호는 0부터 시작합니다. 웹캠이 하나밖에 없다면 인자로 0을 넣으면 됩니다.

동영상 파일을 읽는 것과 다르게 카메라로부터 프레임을 읽는 경우 파일의 끝이 정해져 있지 않아 무한 루프를 빠져나올 조건이 없습니다. 그래서 cv.waitKey(1) != -1 코드에서 사용자가 아무 키나 누르면 break가 되어 루프를 빠져나옵니다. cv2.waitKey() 함수는 지정된 시간 동안 아무 키 입력이 없으면 -1을 반환합니다. 아무 키나 입력을 하면 -1이 반환되지 않기 때문에 break가 되는 것입니다.

웹캠으로 사진 찍기

웹캠이나 동영상의 특정 프레임을 이미지로 저장할 수도 있습니다. 특정 프레임을 저장하는 것은 cv2.imwrite() 함수를 활용하면 됩니다. 아래 코드는 웹캠으로 프레임을 화면에 표시하다가 아무 키나 누르면 해당 프레임을 이미지 파일로 저장하는 코드입니다. 카메라로 셀카를 찍는 것과 같다고 보시면 됩니다.

# 웹캠으로 사진찍기 (video_cam_take_pic.py)

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()

위 코드를 실행하면 웹캠이 실행되어 자신의 모습이 화면에 나올 것입니다. 아무 키나 누르면 그 순간의 프레임이 캡처되어 이미지로 저장됩니다.

웹캠으로 녹화하기

셀카를 찍는 것처럼 웹캠으로 하나의 프레임을 이미지로 저장할 수 있다는 것을 배웠습니다. cv2.VideoWriter() 함수를 쓰면 여러 프레임을 동영상으로 저장할 수도 있습니다.

cv2.VideoWriter(file_path, fourcc, fps, (width, height)) 에서 file_path는 동영상 파일을 저장할 경로, fourcc는 동영상 인코딩 형식(codec 정보), fps는 초당 저장될 프레임 수, (width, height)는 프레임의 너비와 높이를 뜻합니다. 아래 코드를 실행한 뒤 아무 키나 누르면 키를 누르기 전까지의 모든 프레임이 record.avi라는 동영상으로 저장됩니다.

# 웹캠으로 녹화하기 (video_cam_rec.py)

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()

cv2.VideoWriter() 객체를 생성하여 out 변수에 저장했습니다. out.write(frame)을 호출하면 현재 frame이 저장됩니다.

cap.get()은 동영상이나 카메라의 속성을 확인하는 함수입니다. cv2.CAP_PROP_FRAME_WIDTH는 프레임 너비, cv2.CAP_PROP_FRAME_HEIGHT는 프레임 높이를 뜻합니다. 따라서 cap.get(cv2.CAP_PROP_FRAME_WIDTH)은 cap 객체의 프레임 너비를 반환합니다. 

참고로, FPS(Frames Per Second)는 초당 프레임 수를 뜻하며 지연 시간은 FPS를 활용하여 구할 수 있습니다.

지연시간 = 1000 / fps

1000으로 계산하는 이유는 1초(1s)가 1,000밀리 초(1,000ms)이기 때문입니다. 따라서 코드에서 지연 시간을 cv2.waitKey(int(1000/fps))로 설정한 것입니다.

Comments