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()
NMS๋ฅผ ์ํํ๋ ํจ์์ ์์ ์ฝ๋์ด๋ฉฐ, iou thresholod๋ฅผ ์กฐ์ ํ์ฌ ๋ฐ์ค๊ฐ ๊ฒน์น๋ ์ ๋์ ๋ฐ๋ผ ํํฐ๋ง์ ์ํํ ์ ์๋ค.