귀퉁이 서재

컴퓨터 비전 - 4. 슬라이딩 윈도우(Sliding Window)와 영역 추정(Region Proposal) 본문

딥러닝 컴퓨터 비전

컴퓨터 비전 - 4. 슬라이딩 윈도우(Sliding Window)와 영역 추정(Region Proposal)

Baek Kyun Shin 2022. 4. 26. 23:40

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

이전 글에서 객체 Localization 작동 방식을 알아봤습니다. 이미지와 annotation을 데이터로 주면 Localization을 잘 수행하도록 학습한다고 했습니다. 하지만 여러 객체를 탐지할 때는 Localization에서 사용하는 방식을 그대로 사용하면 문제가 생깁니다. 이미지 안에 객체가 여러 개 있으면 경계 좌표만 데이터로 입력한다고 해서 여러 객체 위치를 올바르게 학습하지 못합니다. 이런 문제를 해결하려고 나온 방식이 바로 슬라이딩 윈도우 방식입니다.

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


슬라이딩 윈도우 방식

슬라이딩 윈도우(Sliding Window) 방식이란 사각형 윈도우를 이미지 왼쪽 위부터 오른쪽 아래까지 이동하면서 객체를 탐지하는 방식입니다. 아래는 오드리 햅번 이미지에 슬라이딩 윈도우를 적용한 예시입니다.

출처 https://www.pyimagesearch.com/2015/03/23/sliding-windows-for-object-detection-with-python-and-opencv/

윈도우가 이동하면서 윈도우 안에 있는 객체를 탐지하는 방식이죠. 이러한 슬라이딩 윈도우 방식은 초기 객체 탐지 기법으로 활용되었습니다. 하지만 이 또한 문제점이 있습니다.

슬라이딩 윈도우 크기에 딱 맞지 않는 객체가 있을 수 있다는 점입니다. 가령 물체가 윈도우 크기보다 클 수 있고, 반대로 작아서 윈도우 안에 여러 물체가 있을 수도 있죠. 이를 해결하기 위해 다양한 크기의 윈도우를 사용하는 방법이 있습니다. 또한 윈도우 크기는 고정하되 이미지 크기를 바꿔가면서 슬라이딩 윈도우를 적용할 수도 있습니다. 아래와 같이 말입니다.

다만, 여러 크기의 윈도우와 여러 크기의 이미지를 활용해 스캔하므로 검출 성능도 떨어지고, 시간도 오래 걸립니다. 게다가 윈도우가 이미지의 모든 영역을 훑기 때문에 더욱 시간이 오래 걸립니다. 객체가 없는 영역(배경)도 무조건 훑어야 해서 효율이 떨어지기도 하죠.

슬라이딩 윈도우의 문제를 개선한 방식이 바로 다음에 알아볼 영역 추정 방식입니다. 영역 추정 기법이 나오면서 슬라이딩 윈도우 방식은 더 이상 활용되지 않았지만 객체 탐지 발전에 기술적인 뿌리가 됐습니다.


선택적 탐색(Selective Search)

영역 추정(Region Proposal) 혹은 영역 제안이란 객체가 있을 만한 후보 영역을 먼저 찾아주는 방법입니다. 이후에 그 영역을 바탕으로 객체를 찾습니다. 모든 영역을 훑는 슬라이딩 윈도우보다 효율적이죠. 대표적인 영역 추정 기법으로는 선택적 탐색(Selective Search)이 있습니다.

선택적 탐색은 속도도 빠르고 예측 성능도 좋은 영역 추정 알고리즘입니다. 먼저 이미지에서 비슷한 색상, 무늬, 크기, 형태에 따라 영역을 그룹핑니다. 이렇게 그룹핑한 영역을 바탕으로 경계 박스를 추정하죠.

비슷한 영역을 그룹핑하는 작업을 딱 한 번만 하면 너무 많은 경계 박스를 생성합니다. 그렇기 때문에 비슷한 영역끼리 그룹핑하는 작업을 여러 차례 반복하며 영역을 추정합니다. 다음 그림은 선택적 탐색 기법으로 경계 박스를 추정하는 예시입니다.

출처: https://www.geeksforgeeks.org/selective-search-for-object-detection-r-cnn/


선택적 탐색(Selective Search) 실습

선택적 탐색에 관해 간단히 알아봤으니, 이제 실습하며 선택적 탐색을 구현해보겠습니다.

실습은 구글 Colab에서 했습니다. 구글 Colab 사용 방법은 아주 간단하니 검색하면 금방 익힐 수 있습니다.

1. 패키지 및 이미지 불러오기, 이미지 출력

그럼 본격적으로 선택적 탐색 실습을 해보겠습니다. 먼저 selectivesearch 패키지를 설치합니다(주피터 환경에서 shell 명령어를 사용하려면 명령어 앞에 느낌표(!)를 같이 써주면 됩니다).

!pip install selectivesearch

이어서 선택적 탐색에 사용할 이미지를 다운로드합니다. 먼저 content 디렉터리 아래에 data 폴더를 만듭니다. 참고로, 구글 Colab은 기본적으로 content 디렉터리가 현재 디렉터리입니다. data 디렉터리를 만든 후 그곳에 오드리햅번 이미지를 다운로드하겠습니다.

# content 디렉터리 아래 data 폴더 생성
!mkdir /content/data
# data 디렉터리 아래 audrey01.jpg라는 이름으로 오드리햅번 이미지 다운로드하기
!wget -O /content/data/audrey01.jpg https://raw.githubusercontent.com/chulminkw/DLCV/master/data/image/audrey01.jpg

이미지를 잘 다운로드했는지 확인해보죠. 

① Colab의 왼쪽 목차를 엽니다. ② 이어서 파일 목록을 클릭합니다. 그러면 현재 디렉터리인 content 디렉터리 안에 있는 폴더 및 파일을 보여줍니다. ③ 새로고침을 누른 뒤, ④ data 디렉터리를 엽니다. data 디렉터리에 방금 다운로드한 audrey01.jpg가 있군요. ⑤ audrey01.jpg를 더블 클릭하면 화면 오른쪽에 해당 이미지를 보여줍니다. 오드리햅번 사진이 잘 나왔네요.

이어서 다운로드한 이미지를 Colab에서 출력해볼까요? 이미지 파일을 읽어서 출력하기 위해 OpenCV 라이브러리를 사용하겠습니다. OpenCV는 오픈소스 컴퓨터 비전 라이브러리(Open source Computer Vision library)로, 영상 처리에 자주 사용됩니다.

다음 코드를 실행합니다.

import selectivesearch
import cv2
import matplotlib.pyplot as plt
import os
%matplotlib inline

# 오드리헵번 이미지를 cv2로 로드하고 matplotlib으로 시각화 
img = cv2.imread('./data/audrey01.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR을 RGB 색상으로 변경 ---①
print('이미지 크기:', img.shape)

plt.figure(figsize=(7, 7))
plt.imshow(img_rgb);

그러면 오드리햅번 이미지가 출력될 겁니다. 여기서 cv2.imread()는 이미지 파일을 읽는 메서드입니다. cv2.imread()로 이미지 파일을 읽으면 기본적으로 BGR 색상으로 읽어옵니다. 코드 ①은 BGR 색상 타입을 RGB 색상 타입으로 바꿉니다. 이렇게 색상 타입을 바꾼 이미지(img_rbg)를 출력했습니다.

2. 선택적 탐색(Selective Seach) 수행

이미지를 준비했으니, 이 이미지를 활용해 선택적 탐색을 실행해보죠. selectivesearch 패키지의 selective_search() 메서드로 수행할 수 있습니다.

import selectivesearch 

# selectivesearch.selective_search()는 이미지의 영역 추정 정보를 반환 
_, regions = selectivesearch.selective_search(img_rgb, scale=100, min_size=2000)

print(type(regions), len(regions))

     <class 'list'> 41

selectivesearch.selective_search()는 이미지의 영역 추정(Region Proposal) 정보를 반환합니다. 첫 번째 매개변수인 img_rgb 이미지에서 영역 추정을 한다는 뜻입니다. scale 매개변수에 입력한 숫자가 크면 큰 객체 위주로 후보 영역을 추정하라는 의미이고, 반대로 숫자가 작으면 작은 객체 위주로 추정하라는 말입니다. min_size=2000은 후보 영역의 크기가 2000이상인 것들만 추정하라는 뜻입니다. 따라서 scale이나 min_size 값을 크게 설정하면 크기가 큰 객체 위주로 후보 영역을 추정하기 때문에, 후보 영역 개수가 줄어듭니다.

selectivesearch.selective_search() 메서드의 반환값은 두 개입니다. 첫 번째 반환값은 별로 중요치 않아 _로 받았습니다. 두 번째 반환값인 regions에는 예측한 후보 영역들이 저장돼 있습니다. 타입은 리스트이며, 여기서는 후보 영역을 41개 예측했네요. regions를 출력해보죠.

regions

     [{'labels': [0.0], 'rect': (0, 0, 107, 167), 'size': 11166},
      {'labels': [1.0], 'rect': (15, 0, 129, 110), 'size': 8771},
      {'labels': [2.0], 'rect': (121, 0, 253, 133), 'size': 17442},
      ...생략...

여러 값들이 출력될 겁니다. labels는 경계 박스 안에 있는 객체의 고유 ID값입니다. rect는 예측한 후보 영역의 경계 박스 좌표입니다. (좌상단 x 좌표, 좌상단 y 좌표, 너비, 높이) 순서로 기재돼 있습니다. 다시 말해, 좌표가 (0, 0, 107, 167)인 경계 박스는 좌상단 (0, 0)에서 시작해 너비가 107, 높이가 167인 영역입니다. 참고로 이미지에서는 맨 왼쪽 상단 좌표가 (0, 0)입니다. size는 객체 크기를 뜻합니다. 경계 박스 크기와 비례한다고 보시면 됩니다. 

출력된 regions 결과를 보면, 아래로 내려갈수록 너비와 높이가 큰 경계 박스가 있습니다. 그래서 하나의 경계 박스에 여러 객체가 있을 확률이 높습니다.

regions 변수를 활용해 경계 박스 좌표(rect)만 출력하려면 다음과 같이 실행하면 됩니다.

# rect 정보만 출력해서 보기
rects = [region['rect'] for region in regions]
print(rects)

    [(0, 0, 172, 214), (16, 0, 117, 51), (118, 0, 91, 79), (201, 0, 173, 226), ...생략...

3. 경계 박스 시각화

다음으로 경계 박스를 시각화해보겠습니다. 앞서 출력했던 img_rgb를 활용하겠습니다.

green_rgb = (125, 255, 51) # 경계 박스 색상
img_rgb_copy = img_rgb.copy() # 복사

for rect in rects:
    left = rect[0] # 좌상단 x 좌표
    top = rect[1] # 좌상단 y 좌표
    width, height = rect[2], rect[3] # 너비 및 높이
    right = left + width # 우하단 x 좌표  ---①
    bottom = top + height # 우하단 y 좌표 ---②
    
    # 경계 박스 시각화를 위한 네모 박스 ---③
    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=green_rgb, thickness=2)
    
plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy);

여러 경계 박스가 표시됐네요. 코드 동작 방식을 알아볼까요?

경계 박스 좌표값을 요소로 갖는 rects를 순회하면서 경계 박스의 좌상단, 우하단 좌표를 구합니다. ① 경계 박스의 우하단 x 좌표는 '좌상단 x 좌표에 너비(width)만큼 더한 값'입니다. ② 비슷하게 우하단 y 좌표는 '좌상단 y 좌표에 높이(height)만큼 더한 값'입니다. 이렇게 구한 경계 박스 좌표값을 활용해 cv2.rectangle() 메서드로 경계 박스를 그립니다. cv2.rectangle() 메서드는 이미지와 좌상단 좌표, 우하단 좌표, 색상, 두께 등을 인자로 입력하면 원본 이미지에 네모 상자를 그려줍니다. 첫 번째 매개변수인 img_rgb_copy 사진 위에 두 번째 매개변수인 좌상단부터 세 번째 매개변수인 우하단까지 경계 박스를 그립니다. 이때 색상을 나타내는 color 매개변수에 green_rgb 색상값을 전달했습니다. thickness 매개변수는 경계 박스의 두께를 나타냅니다.

그런데 경계 박스가 너무 많은 느낌입니다. 이번엔 크기가 20,000보다 큰 경계 박스만 표시해보겠습니다. 첫 줄 코드만 더해졌고, 나머지 코드는 앞선 코드와 똑같습니다.

rects = [regions['rect'] for regions in regions if regions['size'] > 20000]

green_rgb = (125, 255, 51) # 경계 박스 색상
img_rgb_copy = img_rgb.copy() # 복사

for rect in rects:
    left = rect[0] # 좌상단 x 좌표
    top = rect[1] # 좌상단 y 좌표
    width, height = rect[2], rect[3] # 너비 및 높이
    right = left + width # 우하단 x 좌표
    bottom = top + height # 우하단 y 좌표 
    
    # 경계 박스 시각화를 위한 네모 박스
    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=green_rgb, thickness=2)
    
plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy);

원하던 대로 크기가 큰 경계 박스만 표시되었네요.

이상으로 슬라이딩 윈도우 방식과 영역 추정 방식(구체적으로는 선택적 탐색)에 관해 알아봤습니다.

0 Comments
댓글쓰기 폼