Non-Maximum Suppression(NMS)란

지난주 회사분들과 Non-Maximum Suppression 스터디를 진행하였고 좀 더 자세하게 공부하고자 포스팅을 남긴다.

object detectior가 예측한 bounding box중에서 정확한 bounding box를 선택하도록 하는 기법이다.

대표적으로 YOLO에 NMS기법이 적용되어 있다.

NMS_1


NMS 의사코드

NMS_1


알고리즘

  1. Detected 된 bounding box 별로 Confidence threshold 이하의 bounding box는 제거한다.
  2. 가장 높은 confidence score를 가진 box 순으로 내림차순 정렬하고 아래 로직을 모든 box에 순차적으로 적용
    • 가장 높은 confience score를 가진 box와 곂치는 다른 box를 모두 조사하여 IOU가 특정 threshold 이상인 box를 모두 제거. 즉 IOU(아래 설명)가 일정 이상인 boundingbox는 동일한 물체를 detect 했다고 판단하고 곂치는 box를 제거해 주는 과정
  3. 남아 있는 box만 선택한다.

코드 구현

위의 과정을 통해 최대한 하나의 Object에 하나의 Detection box만이 존재하도록 만들게 된다. 이 아이디어는 이미 Tensorflow에 tf.image.non_max_suppression 으로 구현되어있기에 우리가 직접 구현할 필요는 없지만 보다 확실한 이해를 위해서 다음과 같이 Python code로 구현해볼 수 있다.

아래 코드는 github-squeezeDet 에서 참고하였다.


def nms(boxes, probs, threshold):
  """Non-Maximum supression.
  Args:
    boxes: array of [cx, cy, w, h] (center format)
    probs: 오브젝트가 있을 확률 배열
    threshold: two boxes are considered overlapping if their IOU is largher than
        this threshold
    form: 'center' or 'diagonal'
  Returns:
    keep: array of True or False.
  """
  # 내림차순으로 정렬
  order = probs.argsort()[::-1]

  # 개수 대로 true 리스트 생성
  keep = [True]*len(order)
 
  
  for i in range(len(order)-1):
    # IOU 검출, 자세한 코드는 하단 batch_iou코드 참조
    ovps = batch_iou(boxes[order[i+1:]], boxes[order[i]])
    for j, ov in enumerate(ovps):
      if ov > threshold:
        # IOU가 특정 threshold 이상인 box를 False로 세팅
        keep[order[j+i+1]] = False
  return keep


  

코드를 보면 알겠지만 batch_iou(IOU)가 필요하다. IOU는 전체 박스 영역 중 곂치는 부분의 비율이다.

NMS_1

NMS를 하는 가장 큰 이유는 중복 제거이기 때문에 예측한 박스 중 IOU가 일정이상인 것들에 대해서 수행한다.

다음 포스트에서 IOU에 대해 좀 더 자세하게 포스팅 해보겠다.

아래는 IOU의 코드이니 참고하면 좋을 것 같다.

def batch_iou(boxes, box):
  """Compute the Intersection-Over-Union of a batch of boxes with another
  box.
  Args:
    box1: 2D array of [cx, cy, width, height].
    box2: a single array of [cx, cy, width, height]
  Returns:
    ious: array of a float number in range [0, 1].
  """
  lr = np.maximum(
      np.minimum(boxes[:,0]+0.5*boxes[:,2], box[0]+0.5*box[2]) - \
      np.maximum(boxes[:,0]-0.5*boxes[:,2], box[0]-0.5*box[2]),
      0
  )
  tb = np.maximum(
      np.minimum(boxes[:,1]+0.5*boxes[:,3], box[1]+0.5*box[3]) - \
      np.maximum(boxes[:,1]-0.5*boxes[:,3], box[1]-0.5*box[3]),
      0
  )
  inter = lr*tb
  union = boxes[:,2]*boxes[:,3] + box[2]*box[3] - inter
  return inter/union

[참고 자료]

https://go-hard.tistory.com/25

https://dyndy.tistory.com/275

https://donggoolosori.github.io/2020/10/14/dlcv-nms/

https://towardsdatascience.com/non-maximum-suppression-nms-93ce178e177c