【Python-Module】 비전 처리 라이브러리 활용 - OpenCV 뼈대 코드

다양한 비전 라이브러리를 잠시만 공부해보고, OpenCV를 집중적으로 공부해보자. 이미지 뿐만 아니라 동영상까지 다루는 방법에 대해서, 앞으로 계속 사용할 뼈대 코드들에 대해서 공부해본다.

DLCV/Detection/preliminary/OpenCV이미지와 영상처리 개요 파일 참조

1. Python 기반 주요 이미지 라이브러리

  1. PIL(Python Image Library) : 주요 이미지 처리만. 처리 성능이 상대적으로 느림. 망해가는 모듈.
  2. Scikit-Image : Scipy기반. Numpy기반.
  3. OpenCV
    • 최고 인기 컴퓨터 비전 라이브러리. 컴퓨터 비전 기능 일반화에 크게 기여. 대중화.
    • cv2.imread(“파일명”) -> numpy array로 변환해 저장 -> RGB형태가 아닌 BGR형태로 로딩!!
    • 따라서 image를 바꿔줘야 한다.

        import cv2
        import matplotlib.pyplot as plt
        bgr_img_array = cv2.imread('file Name')
        rgb_img_array = cv2.cvtColor(bgr_img_arry, cv2.COLOR.BGR2RGB)
        plt.imshow(rgb_img_array)
      
    • 근데 imwrite에서는 내부적으로 또 RGB로 바꾸기 때문에 주의해야한다.

        bgr_img_array = cv2.imread('file Name')
        rgb_img_array = cv2.cvtColor(bgr_img_arry, cv2.COLOR.BGR2RGB)
        cv2.imwirte('저장할 파일 이름', bgr_img_array)  # 따라서 bgr이미지 넣어야함
      
    • OpenCV Windows Frame 인터페이스 : 아래와 같은 기능들은 GUI 윈도우 우분투 환경에서만 쓸 수 있기 때문에 주피터 노트북 기반에서는 사용시 오류가 발생한다. 따라서 matplotlib를 사용해서 주피터 노트북 기반 이미지 시각화를 해야한다.
      1. cv2.imshow() : 윈도우 frame에 보여줌(따라서 주피터에서는 안보인다.)
      2. cv2.waitKey() : 키보드 입력이 있을때 까지 무한 대기
      3. cv2.destroyAllWindows() : 화면의 윈도우 프레임 모두 종료

2. OpenCV의 이미지 처리

  • Python에서 사용되는 여러 image라이브러리를 간단히 살펴보고 OpenCV와의 차이 이해
  • OpenCV의 단일 이미지 처리 방식 이해
  • OpenCV의 비디오 영상 처리 방식 이해

OpenCV 이미지 처리 이해 및 타 패키지 비교

PIL 패키지를 이용하여 이미지 로드하기

import matplotlib.pyplot as plt
%matplotlib inline  
# https://korbillgates.tistory.com/85 - Jupyter Line에 그림 표등이 출력될 수 있게 하는 것 

from PIL import Image

# PIL은 oepn()으로 image file을 읽어서 ImageFile객체로 생성. 
pil_image = Image.open("../../data/image/beatles01.jpg")
print('image type:', type(pil_image))

plt.figure(figsize=(10, 10))
plt.imshow(pil_image)
#plt.show()
image type: <class 'PIL.JpegImagePlugin.JpegImageFile'>

drawing

skimage(사이킷이미지)로 이미지 로드 하기

  • skimage는 imread()를 이용하여 RGB 원본 이미지를 RGB 형태의 넘파이 배열로 반환함.
from skimage import io

#skimage는 imread()를 이용하여 image를 numpy 배열로 반환함. 
sk_image = io.imread("../../data/image/beatles01.jpg")
print('sk_image type:', type(sk_image), ' sk_image shape:', sk_image.shape)

plt.figure(figsize=(10, 10))
plt.imshow(sk_image)
#plt.show()
sk_image type: <class 'numpy.ndarray'>  sk_image shape: (633, 806, 3)

drawing

OpenCV, matplotlib으로 이미지 로드하기

  • OpenCV는 imread()를 이용하여 원본 RGB 이미지를 BGR 형태의 넘파이 배열로 반환함.
  • OpenCV의 imwrite()를 이용한다면 BGR 형태의 이미지 배열을 파일에 기록할 때 다시 RGB형태로 변환하므로 사용자는 RGB->BGR->RGB 변환에 신경쓰지 않아도 됨.
import cv2

cv2_image = cv2.imread("../../data/image/beatles01.jpg")  # cv2_image 여기서는 BGR이지만
cv2.imwrite("../../data/output/beatles02_cv.jpg", cv2_image) # 여기서는 RGB로 자동으로 다시 바뀐다
print('cv_image type:', type(cv2_image), ' cv_image shape:', cv2_image.shape)  

plt.figure(figsize=(10, 10))
img = plt.imread("../../data/output/beatles02_cv.jpg")
plt.imshow(img)
#plt.show()

cv_image type: <class 'numpy.ndarray'>  cv_image shape: (633, 806, 3)

drawing

OpenCV의 imread()로 반환된 BGR 이미지 넘파이 배열을 그대로 시각화 하기

  • OpenCV의 imread()는 RGB를 BGR로 변환하므로 원하지 않는 이미지가 출력됨
cv2_image = cv2.imread("../../data/image/beatles01.jpg")

plt.figure(figsize=(10, 10))
plt.imshow(cv2_image)
plt.show()

drawing

sk_image = io.imread("../../data/image/beatles01.jpg")
print(sk_image.shape)
sk_image[:, :, 0]
(633, 806, 3)





array([[ 18,  17,  18, ...,  46,  38,  63],
       [ 18,  18,  18, ...,  72,  41,  37],
       [ 18,  18,  18, ...,  84,  56,  42],
       ...,
       [225, 226, 228, ..., 231, 228, 229],
       [225, 225, 226, ..., 229, 229, 227],
       [225, 225, 224, ..., 227, 227, 227]], dtype=uint8)
cv2_image = cv2.imread("../../data/image/beatles01.jpg")
print(type(cv2_image))
print(cv2_image.shape)
cv2_image[:, :, 0]  # BGR - R값만 print한다. (633, 806, 3) -> (633, 806, 0)
<class 'numpy.ndarray'>
(633, 806, 3)





array([[ 19,  19,  20, ...,  47,  39,  64],
       [ 20,  20,  20, ...,  71,  40,  36],
       [ 20,  20,  20, ...,  82,  54,  40],
       ...,
       [198, 199, 201, ..., 190, 189, 188],
       [198, 198, 199, ..., 188, 188, 186],
       [199, 199, 198, ..., 186, 186, 186]], dtype=uint8)
 cv2_image[:, :, 2]  # BRG - G값만 print한다. (633, 806, 3) -> (633, 806, 0)
array([[ 18,  18,  18, ...,  47,  39,  64],
       [ 19,  19,  18, ...,  72,  41,  37],
       [ 18,  18,  18, ...,  84,  56,  41],
       ...,
       [225, 226, 228, ..., 231, 230, 229],
       [225, 225, 226, ..., 229, 229, 227],
       [225, 225, 224, ..., 227, 227, 227]], dtype=uint8)
cv2_image = cv2.imread("../../data/image/beatles01.jpg")
draw_image = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)  # 이 작업에 시간 많이 안든다. 단순 메모리 포인트 변화만 하므로. 
cv2.imwrite("../../data/output/beatles02_cv.jpg", draw_image)
# 이렇게 저장된 것을 확인해보면 색이 엉망인것을 확인할 수 있다. 따라서 imwirte를 사용해서 저장한다면 반드시 BGR 이미지가 들어가게 하도록! 

plt.figure(figsize=(10, 10))
plt.imshow(draw_image)
plt.show()

drawing

3. OpenCV 영상처리

  • 뼈대 코드 이다. 나중에 Object Detection 결과를 동영상으로 저장할 때 이와 같은 방법을 사용할 것이므로, 꼭 잘 알아두어야 한다.
  • OpenCV는 간편하게 비디오 영상처리를 할 수 있는 API를 제공
  • VideoCapture 객체는 Video Streaming을 Frame 별로 Capture하여 처리할 수 있는 기능 제공
  • VideoWriter 객체는 VideoCapture로 읽어들인 Frame을 동영상으로 Write하는 기능 제공
# 일단 시각화만 해본다. 
# wget https://github.com/chulminkw/DLCV/blob/master/data/video/Night_Day_Chase.mp4?raw=true 으로 다운로드 가능. 
from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/Night_Day_Chase.mp4') # 소리도 있음
  • 여기서부터 Video를 사용하는 방법이다.
  • Linux에서 video output의 확장자는 반드시 avi 로 설정 필요. (mp4로 재생되긴 되더라..)

OpenCV 영상 처리 개요

  • VideoCapture클래스는 동영상을 개별 Frame으로 하나씩 읽어(wideoCapture.read()) 들이는 기능을 제공한다.
  • VideoWriter는 VideoCapture로 읽어들인 개별 Frame을 차곡차곡 쌓아서 Write 수행하며 하나의 동영상을 만든다.
  • get 메소드를 사용해서 동영상으 상세 정보를 하나하나 불러들여올 수 있다.

VideoWriter(저장위치, 코덱, 프레임, 사이즈(1x2 배열))

  • 코덱 포멧을 다양하게 Encoding할 수 있다. (DIVX, XVID, MJPG, X264, WMV1, WMV2)
  • 하지만 가상환경의 우리의 리눅스 환경에서는 XVID 코덱을 사용해야 하고, write시 동영상 인코더는 avi로 해야한다. input을 mp4를 했다고 하더라도. ㅈ
# 여기서부터 Video를 사용하는 방법이다.
import cv2

video_input_path = '../../data/video/Night_Day_Chase.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/Night_Day_Chase_output.avi'

cap = cv2.VideoCapture(video_input_path) # <- 앞으로 이 변수를 중심으로 사용할 것
# Codec은 *'XVID'로 설정. 
codec = cv2.VideoWriter_fourcc(*'XVID')  # 코덱 객체를 생성

vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) #(200, 400)
vid_fps = cap.get(cv2.CAP_PROP_FPS )
frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))    
    
vid_writer = cv2.VideoWriter(video_output_path, codec, vid_fps, vid_size) # 처음에는 아무것도 없는 동영상
print('총 Frame 갯수:', frame_cnt, 'FPS:', round(vid_fps), 'Frame 크기:', vid_size)
총 Frame 갯수: 1383 FPS: 28 Frame 크기: (1216, 516)
import time

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

start = time.time()
index=0
while True:
    hasFrame, img_frame = cap.read() 
    # cap.read가 frame 하나하나를 순서대로 뱉어준다. 한 프래임씩 읽어주는 아주 좋은 매소드함수라고 할 수 있다.
    if not hasFrame:
        print('더 이상 처리할 frame이 없습니다.')
        break
    index += 1
    print('frame :', index, '처리 완료')
    cv2.rectangle(img_frame, (300, 100, 800, 400), color=green_color, thickness=2)  # 동영상 프래임 이미지 각각에 박스 넣기
    caption = "frame:{}".format(index)
    cv2.putText(img_frame, caption, (300, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, red_color, 1)  # 동영상 프레임 이미지 각각에 몇번째 Frame인지 적기
    
    vid_writer.write(img_frame) 
    # 동영상에 차곡차곡 동영상프래임이미지를 쌓아올린다. 

print('write 완료 시간:', round(time.time()-start,4))
vid_writer.release()  # 나중에 저장한 동영상. file.close() 와 같은 느낌
cap.release()         # 처음에 가져온 이미지. file.close() 와 같은 느낌
frame : 1 처리 완료
frame : 2 처리 완료
frame : 3 처리 완료
frame : 4 처리 완료
fr...
frame : 1381 처리 완료
frame : 1382 처리 완료
frame : 1383 처리 완료
더 이상 처리할 frame이 없습니다.
write 완료 시간: 12.3946
Video('../../data/output/Night_Day_Chase_output.avi') 
  • avi 파일은 주피터 노트북에서 실행할 수 없다. 그래서 파일을 만들었으면, 위와 같이 실행할 수 없다. mp4 파일만 실행할 수 있음을 알아두자.
  • 그래서 avi 파일은 다운을 받아서 나의 컴퓨터에 실행해봐야 한다. 다운을 받는 방법은 그냥 jupyter notebook에서 download를 하면 된다. 그리고 아래와 같이 object storage에 넣고, 다운 받는 방법 또한 있다.
  • 당연히 동영상에 소리는 없다. Detection을 위한다면 소리는 필요없다.
!gsutil cp ../../data/output/Night_Day_Chase_output.avi gs://my_bucket_dlcv/data/output/Night_Day_Chase_output.avi

© All rights reserved By Junha Song.