Logical Scribbles

[OpenCV] 이미지에 Selective Search 적용하고 IoU 계산해보기 본문

딥러닝/OpenCV

[OpenCV] 이미지에 Selective Search 적용하고 IoU 계산해보기

KimJake 2023. 11. 21. 00:03
 

[객체 인식] Selective Search란? (Selective search for object recognition. IJCV, 2013)

[객체 탐지] 2-Stage Dectector 와 1-Stage Detector 이번 포스팅에서는 객체 탐지 논문을 읽다보면 많이 등장하는 2 stage detector와 1 stage detector에 대해 알아보자. 2 stage detector와 1 stage detector의 가장 큰 차이

stydy-sturdy.tistory.com

 

Selective Search 알고리즘에 대해 설명하는 위의 글을 보고 이 글을 보면 더 재미있다.

 

이번에는 이미지에 selective search를 적용하여, 정말로 region proposal이 되는지 확인해보자. 이후, 제안된 영역에 대해 IoU를 계산해보자.

 

1. Selective Search 계산하기

 

먼저 필요한 모듈을 가져오고 이미지를 가져오자. selectivesearch를 설치하면 간단하게 구현이 가능하다.

from google.colab import drive
drive.mount('Selective_Search')

!pip install selectivesearch
import selectivesearch
import cv2
import matplotlib.pyplot as plt
import os

img = cv2.imread('/content/Selective_Search/MyDrive/pandd.jpg')
img_rgb = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
print("image shape : ",img.shape)

plt.figure(figsize=(8,8))
plt.imshow(img_rgb)

plt.show()

 

 

강아지와 사람이 함께 있는 사진에서 강아지에 대한 영역을 제안 받고싶다.

_, regions = selectivesearch.selective_search(img_rgb, scale=100, min_size=2000)

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

#Output=<class 'list'> 51

 

위의 코드를 통해 region의 타입과 길이를 알아보자. selectivesearch.selective_search 함수 안에는 이미지, scale, min_size 변수가 입력 되어야한다. scale은 알고리즘이 선택하는 객체의 크기를 조정하는 값이고, min_size는 제안되는 영역의 가로*세로 값의 최소값이다.

 

출력 결과 51개의 region이 제안됨을 알 수 있다.

print(regions)

# 제안된 상위 11개 rect
[{'rect': (0, 0, 158, 183), 'size': 11240, 'labels': [0.0]}, 
{'rect': (32, 0, 85, 57), 'size': 3037, 'labels': [1.0]},
{'rect': (88, 0, 61, 126), 'size': 2071, 'labels': [2.0]},
{'rect': (122, 0, 187, 94), 'size': 7302, 'labels': [3.0]},
{'rect': (200, 0, 302, 141), 'size': 24837, 'labels': [4.0]},
{'rect': (460, 0, 133, 70), 'size': 5630, 'labels': [5.0]}, 
{'rect': (431, 25, 66, 135), 'size': 4932, 'labels': [6.0]}, 
{'rect': (461, 28, 69, 82), 'size': 3204, 'labels': [7.0]},
{'rect': (128, 53, 134, 143), 'size': 9867, 'labels': [8.0]},
{'rect': (402, 58, 154, 262), 'size': 15453, 'labels': [9.0]}, 
{'rect': (508, 59, 85, 91), 'size': 4691, 'labels': [10.0]}

 

 

반환된 region 변수

  • 리스트 타입
  • 세부 원소로 사전(dictionary)형 가지고 있다
  • 개별 사전 내 Key값 별 의미
    • rect 키 값은 x,y 시작 좌표와 너비, 높이 값을 가진다
    • rect 키 값은 Detected Object 후보를 나타내는 Bounding Box
    • rect의 첫번째와 세번째 항을 더해 가로 길이 계산
    • rect의 두번째와 네번째 항을 더해 세로 길이 계산
    • size는 Object의 크기
    • labels는 해당 rect로 지정된 Bounding Box 내에 있는 Object들의 고유 ID
  • 아래로 내려갈수록
    • 너비와 높이 값이 큰 Bounding Box
    • 하나의 Bounding Box에 여러 개의 Object가 있을 확률 증가

 

이제 제안된 영역에 대한 정보만 출력해서보자.

# rect정보만 출력해서 보기
cand_rects = [cand['rect'] for cand in regions]

print(cand_rects)

[(0, 0, 158, 183), (32, 0, 85, 57), (88, 0, 61, 126), (122, 0, 187, 94), 
(200, 0, 302, 141), (460, 0, 133, 70), (431, 25, 66, 135), (461, 28, 69, 82), 
(128, 53, 134, 143), (402, 58, 154, 262), (508, 59, 85, 91),...

 

잘 출력이 된다.

 

이제 proposal된 box들을 빨간색 테두리로 출력해보자.

# opencv의 rectangle()을 이용하여 시각화
# rectangle()은 이미지와 좌상단 좌표, 우하단 좌표, box컬러색, 두께등을 인자로 입력하면 원본 이미지에 box를 그려줌.

red_rgb = (255, 51, 51)
img_rgb_copy = img_rgb.copy()
for rect in cand_rects:

    left = rect[0] # 좌
    top = rect[1] # 상단
    # rect[2], rect[3]은 너비와 높이이므로 우하단 좌표를 구하기 위해 좌상단 좌표에 각각을 더함.
    right = left + rect[2] # 우
    bottom = top + rect[3] # 하단

    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=red_rgb, thickness=2)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy)
plt.show()

 

 

 

이제 한번 box의 사이즈를 줄여보자! 나는 제안된 영역 중 사이즈가 40000보다 큰 영역들을 선택 해보았다.

cand_rects_over_40000 = [cand['rect'] for cand in regions if cand['size'] > 40000]

cand_rects_over_40000

print(len(cand_rects_over_40000))

#Output 3

 

40000보다 사이즈가 큰 영역은 총 3개가 있다고 한다. 이들을 다시 출력해보자.

red_rgb = (255, 51, 51)
img_rgb_copy = img_rgb.copy()
for rect in cand_rects_over_40000:
    left = rect[0] # 좌
    top = rect[1] # 상단
    # rect[2], rect[3]은 너비와 높이이므로 우하단 좌표를 구하기 위해 좌상단 좌표에 각각을 더함.
    right = left + rect[2] # 우
    bottom = top + rect[3] # 하단

    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=red_rgb, thickness=2)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy)
plt.show()

 

 

 

2. IoU 계산해보기

IoU에 대해 잘 모르면 다음의 글을 참고바란다.

 

 

[딥러닝 Shorts] IoU(Intersection over Union), mAP(Mean Average Precision)이란?

쇼츠가 대세인 요즘, 딥러닝 관련 개념을 짤막하게 설명하는 '딥러닝 쇼츠' 시리즈를 만들어 보려고 한다. 독자가 짧은 시간 안에 읽고 이해할 수 있도록 하는 것이 목표이다. Object Detection 관련

stydy-sturdy.tistory.com

 

기본적으로 IoU를 계산하는 함수를 정의해야 한다. 그 전에, 강아지의 Ground Truth(정답 테두리)를 찾아야한다. 이미지를 구글링해서 얻은 것이기 때문에, 내가 스스로 정해보았다.

gt_box = [140, 85, 250, 325]
gr_rgb = (51, 255, 51)
img_rgb_copy2 = img_rgb.copy()
left = gt_box[0] # 좌
top = gt_box[1] # 상단
# gt_box[2], gt_box[3]은 너비와 높이이므로 우하단 좌표를 구하기 위해 좌상단 좌표에 각각을 더함.
right =  left + gt_box[2] # 우
bottom = top + gt_box[3] # 하단

img_rgb_copy = cv2.rectangle(img_rgb_copy2, (left, top), (right, bottom), color=gr_rgb, thickness=2)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy)
plt.show()

 

 

이정도면 Ground Truth로 설정하기 적당해보인다.

 

이제 IoU함수를 정의하자. 방금 정의한 Ground Truth와 제안된 영역에 대한 정보가 들어가야한다.

IoU는 교집합/합집합이므로 각각의 영역을 구하는 식이 필요하다. 

 

교집합의 가로는 (오른쪽 좌표 중 작은 좌표 - 왼쪽 좌표 중 큰 좌표)로 설정하면 되고, 세로는 (위 좌표 중 작은 좌표 - 아래 좌표 중 큰 좌표)로 설정하면 될 것이다.

 

합집합은 교집합을 이용하여 구하는데, ground truth의 영역과 제안된 영역의 넓이에서 교집합을 한번 빼주면 된다.

 

따라서 코드는 다음과 같다.

import numpy as np

# input에는 (x1 y1 x2 x2) 이미지의 좌상단, 우하단 좌표가 들어가 있다.
def compute_iou(cand_box, gt_box):

    # Calculate intersection areas
    x1 = np.maximum(cand_box[0], gt_box[0])
    y1 = np.maximum(cand_box[1], gt_box[1])
    x2 = np.minimum(cand_box[2], gt_box[0]+gt_box[2])
    y2 = np.minimum(cand_box[3], gt_box[1]+gt_box[3])

    intersection = np.maximum(x2 - x1, 0) * np.maximum(y2 - y1, 0) # 혹시 모르게 음수가 나올 수 있으니까..

    cand_box_area = (cand_box[2] - cand_box[0]) * (cand_box[3] - cand_box[1])
    gt_box_area = (gt_box[2]) * (gt_box[3] )
    union = cand_box_area + gt_box_area - intersection

    iou = intersection / union
    return iou

 

이제 IoU가 0.7 이상인 제안 영역만을 출력해보자. 또한, 영역의 사이즈가 30000인 박스만 출력되도록 하였다.

blue_rgb = (51, 51, 252)
cand_rects = [cand['rect'] for cand in regions if cand['size'] > 30000]
gt_box = [140, 85, 250, 325]
img_rgb_copy3 = img_rgb.copy()
img_rgb_copy3 = cv2.rectangle(img_rgb_copy3, (gt_box[0], gt_box[1]), ( gt_box[0] + gt_box[2], gt_box[1] +gt_box[3]), color=gr_rgb, thickness=2)

for index, cand_box in enumerate(cand_rects):

    cand_box = list(cand_box)
    cand_box[2] += cand_box[0]
    cand_box[3] += cand_box[1]

    iou = compute_iou(cand_box, gt_box)

    if iou > 0.7:
        print('index:', index, "iou:", iou, 'rectangle:',(cand_box[0], cand_box[1], cand_box[2], cand_box[3]) )
        cv2.rectangle(img_rgb_copy3, (cand_box[0], cand_box[1]), (cand_box[2], cand_box[3]), color=blue_rgb, thickness=2)
        text = "{}: {:.2f}".format(index, iou)
        cv2.putText(img_rgb_copy3, text, (cand_box[0]+ 100, cand_box[1]+10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color=blue_rgb, thickness=2)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy3)
plt.show()

 

딱 1개의 영역만을 제외한 모든 영역이 삭제되었다. IoU는 0.81872 정도라고 한다. 

 

끝!