๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“– Theory/Computer Vision

NMS (Non-Maximum Suppression) | ๊ฐ์ฒด ๊ฒ€์ถœ์—์„œ ๊ฒน์น˜๋Š” bbox๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ๋ฒ•

by ๋ญ…์ฆค 2023. 11. 25.
๋ฐ˜์‘ํ˜•

 

NMS ์˜ˆ์‹œ


Non-Maximum Suppression(NMS)์€ ๊ฐ์ฒด ๊ฐ์ง€ ๋ชจ๋ธ์—์„œ ๊ฒน์น˜๋Š” Bounding Box๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ์ •๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ ์ด๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ชจ๋ธ์˜ ์ถœ๋ ฅ์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•˜๊ณ  ์ค‘๋ณต๋œ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ฑฐํ•จ์œผ๋กœ์จ ์ •ํ™•ํ•œ ๊ฐ์ฒด ๊ฐ์ง€๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. 

 

NMS์˜ ์›๋ฆฌ๋Š” ์—ฌ๋Ÿฌ ํ›„๋ณด bbox ์ค‘์—์„œ ๊ฒน์น˜๋Š” ์ƒ์ž๋“ค์„ ํ•„ํ„ฐ๋งํ•˜๋Š” ๊ฒƒ์ธ๋ฐ, ๊ฒน์น˜๋Š” ์ƒ์ž๋“ค ์ค‘์—์„œ ๊ฐ€์žฅ ํ™•๋ฅ ์ด ๋†’์€ ์ƒ์ž๋ฅผ ์„ ํƒํ•˜๊ณ  ๊ทธ์™€ ๊ฒน์น˜๋Š” ์ƒ์ž๋“ค์„ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

์ด๋Ÿฌํ•œ NMS๋Š” ๋ชจ๋ธ์˜ ์ถœ๋ ฅ์„ ์ •๋ฆฌํ•˜๊ณ  ์ค‘๋ณต๋œ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ๋” ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉฐ ํŠนํžˆ, ํ•œ ๊ฐ์ฒด์— ๋Œ€ํ•ด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒน์น˜๋Š” ๊ฒฝ๊ณ„ ์ƒ์ž๊ฐ€ ์ƒ์„ฑ๋œ ๊ฒฝ์šฐ ์ด๋ฅผ ์ •๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. Object Detection ๋ชจ๋ธ ์ค‘ Faster R-CNN, YOLO์™€ ๊ฐ™์€ ๋ชจ๋ธ์—์„œ ํ™œ์šฉ๋œ๋‹ค.

 

์กฐ๊ธˆ ๋” ์ž์„ธํ•œ ๋™์ž‘ ์›๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

NMS

  • ์ ์ˆ˜ ๋ถ€์—ฌ (Scoring) : ๊ฐ ๊ฒฝ๊ณ„ ์ƒ์ž์— ๋Œ€ํ•ด ๋ชจ๋ธ์€ ํ•ด๋‹น ์ƒ์ž์— ์†ํ•œ ๊ฐ์ฒด์ผ ํ™•๋ฅ  ๋˜๋Š” ์ ์ˆ˜๋ฅผ ๋ถ€์—ฌ
  • ์ •๋ ฌ (Sorting): ๊ฒฝ๊ณ„ ์ƒ์ž๋“ค์€ ๋ถ€์—ฌ๋œ ์ ์ˆ˜์— ๋”ฐ๋ผ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ. ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๊ฐ–๋Š” ์ƒ์ž๊ฐ€ ๋ฆฌ์ŠคํŠธ์˜ ๋งจ ์œ„์— ์œ„์น˜.
  • ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜ ์ƒ์ž ์„ ํƒ : ์ •๋ ฌ๋œ ์ƒ์ž ์ค‘์—์„œ ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ƒ์ž๋ฅผ ์„ ํƒํ•˜๊ณ  ์ตœ์ข… ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€
  • ์ž„๊ณ„๊ฐ’ ์„ค์ • : ์ผ์ •ํ•œ IoU(Intersection over Union) ์ž„๊ณ„๊ฐ’์„ ์„ค์ •. 
  • ์ž„๊ณ„๊ฐ’ ์ดํ•˜ ์ƒ์ž ์ œ๊ฑฐ : ์„ ํƒํ•œ ์ƒ์ž์™€ IoU๊ฐ€ ์„ค์ •ํ•œ ์ž„๊ณ„๊ฐ’ ์ด์ƒ์ธ ๋‹ค๋ฅธ ์ƒ์ž๋“ค์„ ์ฐพ๊ณ , ์ด ์ค‘์—์„œ IoU๊ฐ€ ๋†’์€ ์ƒ์ž๋“ค์„ ์ œ๊ฑฐ
  • ๋ฐ˜๋ณต : ์œ„์˜ ๊ณผ์ •์„ ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๊ฐ€์ง„ ์ƒ์ž๋ฅผ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ ์ƒ์ž์— ๋Œ€ํ•ด ๋ฐ˜๋ณต
  • ์ตœ์ข… ๊ฒฐ๊ณผ : ๋ฐ˜๋ณต์„ ๊ฑฐ์ณ ์–ป์€ ์ƒ์ž๋“ค์ด ์ตœ์ข… ๊ฐ์ฒด ๊ฐ์ง€ ๊ฒฐ๊ณผ๊ฐ€ ๋จ. ์ด๋•Œ ๊ฐ ์ƒ์ž๋Š” ๊ฒน์น˜์ง€ ์•Š๋„๋ก ์ตœ์ข…์ ์œผ๋กœ ์„ ํƒ

 

NMS ์˜ˆ์ œ์ฝ”๋“œ

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

def non_max_suppression(boxes, scores, iou_threshold):
    # ๋ฐ•์Šค์˜ ๋„“์ด ๊ณ„์‚ฐ
    areas = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)

    # ์ ์ˆ˜์— ๋”ฐ๋ผ ๋ฐ•์Šค ์ •๋ ฌ (๋†’์€ ์ ์ˆ˜๋ถ€ํ„ฐ)
    order = np.argsort(scores)[::-1]

    # ์ตœ์ข… ์„ ํƒ๋œ ๋ฐ•์Šค์˜ ์ธ๋ฑ์Šค ๋ฆฌ์ŠคํŠธ
    selected_boxes = []

    while order.size > 0:
        # ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜๋ฅผ ๊ฐ–๋Š” ๋ฐ•์Šค ์„ ํƒ
        index = order[0]
        selected_boxes.append(index)

        # ์„ ํƒ๋œ ๋ฐ•์Šค์™€์˜ IoU๋ฅผ ๊ณ„์‚ฐ
        x1 = np.maximum(boxes[index, 0], boxes[order[1:], 0])
        y1 = np.maximum(boxes[index, 1], boxes[order[1:], 1])
        x2 = np.minimum(boxes[index, 2], boxes[order[1:], 2])
        y2 = np.minimum(boxes[index, 3], boxes[order[1:], 3])

        # ๊ฒน์น˜๋Š” ์˜์—ญ ๊ณ„์‚ฐ
        intersection = np.maximum(0.0, x2 - x1 + 1) * np.maximum(0.0, y2 - y1 + 1)

        # IoU ๊ณ„์‚ฐ
        iou = intersection / (areas[index] + areas[order[1:]] - intersection)

        # IoU๊ฐ€ ์ž„๊ณ„๊ฐ’๋ณด๋‹ค ์ž‘์€ ๋ฐ•์Šค๋“ค๋งŒ ๋‚จ๊ธฐ๊ธฐ
        inds = np.where(iou <= iou_threshold)[0]
        order = order[inds + 1]

    return selected_boxes

boxes = np.array([[30, 20, 80, 70], [40, 30, 90, 80], [25, 15, 75, 65]])
scores = np.array([0.9, 0.75, 0.6])
iou_threshold = 0.5

selected_indices = non_max_suppression(boxes, scores, iou_threshold)
selected_boxes = boxes[selected_indices]

print("์„ ํƒ๋œ ๋ฐ•์Šค ์ธ๋ฑ์Šค:", selected_indices)
print("์„ ํƒ๋œ ๋ฐ•์Šค ์ขŒํ‘œ:", selected_boxes)

# ์‹œ๊ฐํ™”
fig, ax = plt.subplots(1, 2, figsize=(10, 5))

# NMS ์ด์ „์˜ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์‹œ๊ฐํ™”
for box, score in zip(boxes, scores):
    rect = patches.Rectangle((box[0], box[1]), box[2] - box[0], box[3] - box[1],
                             linewidth=1, edgecolor='r', facecolor='none')
    ax[0].add_patch(rect)
    ax[0].text(box[0], box[1], f'{score:.2f}', color='r')

ax[0].set_title('Before NMS')
ax[0].set_xlim(0, 100)
ax[0].set_ylim(0, 80)

# NMS ์ดํ›„์˜ ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ์‹œ๊ฐํ™”
for box, score in zip(selected_boxes, scores[selected_indices]):
    rect = patches.Rectangle((box[0], box[1]), box[2] - box[0], box[3] - box[1],
                             linewidth=1, edgecolor='b', facecolor='none')
    ax[1].add_patch(rect)
    ax[1].text(box[0], box[1], f'{score:.2f}', color='b')

ax[1].set_title('After NMS')
ax[1].set_xlim(0, 100)
ax[1].set_ylim(0, 80)

plt.show()

์ขŒ : threshold = 0.5, ์šฐ : threshold = 0.3

 

NMS๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜์™€ ์˜ˆ์ œ ์ฝ”๋“œ์ด๋ฉฐ, iou thresholod๋ฅผ ์กฐ์ •ํ•˜์—ฌ ๋ฐ•์Šค๊ฐ€ ๊ฒน์น˜๋Š” ์ •๋„์— ๋”ฐ๋ผ ํ•„ํ„ฐ๋ง์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜•