욜로(YOLO) 알고리즘은 실시간으로 물체의 둘레 상자(bounding box)를 찾는데 쓰인다. 욜로 알고리즘의 결과값은 로 표현이 된다.
같은 그리드 안에 다른 클래스의 둘레 상자가 있을 수 있기 때문에 이를 고정 상자(ancor boxes)를 이용해서 표현을 한다. 이후 비최댓값 제거(non-max suppresion)를 이용해서 가장 정확한 박스의 필터링 과정을 거친다.
위의 그림은 5개의 고정 박스를 인코딩한 결과이다. 위의 방식을 평탄화 과정을 통해서 에서 로 변경을 시켜주도록 하자.
욜로 알고리즘에서의 점수 할당은 위의 그림과 같이 의 각 클래스에 곱(elementwise)을 실행하고, 최댓값에 해당하는 요소가 점수로 기록된다. 최댓값이 위치해있던 위치의 클래스가 욜로 알고리즘을 통해 도출된 클래스의 결과이다.
def yolo_filter_boxes(boxes, box_confidence, box_class_probs, threshold = .6):
# Step 1: Compute box scores
box_scores = box_class_probs * box_confidence
# Step 2: Find the box_classes using the max box_scores
box_classes = tf.math.argmax(box_scores, -1) # shape = (19, 19, 5)
box_class_scores = tf.math.reduce_max(box_scores, -1) # shape = (19, 19, 5)
# Step 3: Create a filtering mask
filtering_mask = box_class_scores >= threshold # shape = (19, 19, 5)
# Step 4: Apply the mask to box_class_scores, boxes and box_classes
scores = tf.boolean_mask(box_class_scores, filtering_mask)
boxes = tf.boolean_mask(boxes, filtering_mask)
classes = tf.boolean_mask(box_classes, filtering_mask)
return scores, boxes, classes
결국 클래스 점수가 가장 높은 대상을 그 둘레 상자의 클래스로 설정을 하는 과정이다. 위의 결과로 반환값은 다음과 같은 차원을 가진다.
scores
: (17789, )
boxes
: (17789, 4)
classes
: (17789, )
모든 박스에 대한 데이터와 관련된 내용이 순서대로 정리되어있는 형태로 반환이 되는 모습을 볼 수 있다.
지금까지는 결과값에서 가장 높은 점수를 기록하고 있는 클래스로 둘레 상자를 설정한 상태이다. 하지만 욜로 알고리즘에서 우리는 한 그리드에서 중복될 수 있는 박스(anchor boxex)를 5개로 설정하였기 때문에 실제로 같은 대상을 가리키는 여러개의 박스가 나올 수 있다. 비최댓값 제거를 통해서 가장 정확도가 높은 박스를 제외한 나머지 박스들을 제거할 필요가 있다.
비최댓값 제거를 위해서는 합집합에서 교집합이 차지하는 비율(Intersection over Union)을 계산을 통해 둘레 상자의 정확도를 측정할 수 있다.
위의 IoU값을 바탕으로 알고리즘의 결과 중, 가장 정확한 값을 추출해 낼 수 있게 된다. 욜로 알고리즘에서 비최댓값 제거를 사용하는 예시는 다음과 같다.
def yolo_non_max_suppression(scores, boxes, classes, max_boxes = 10, iou_threshold = 0.5):
max_boxes_tensor = tf.Variable(max_boxes, dtype='int32')
nms_indices = tf.image.non_max_suppression(boxes, scores, max_boxes, iou_threshold)
scores = tf.gather(scores, nms_indices)
boxes = tf.gather(boxes, nms_indices)
classes = tf.gather(classes, nms_indices)
return scores, boxes, classes
실제로는 프레임워크의 함수를 이용하여 비최댓값 제거를 할 수 있다. non_max_suppression()
을 이용하여 욜로 알고리즘으로 필터링된 상자의 정보를 인자값으로 넘겨주면 처리가 진행된다. 기본적인 알고리즘의 흐름의 위의 순서와 같다. 위의 함수는 인자값으로 넣어준 변수들을 색인으로 등록하게 되는데 결과값을 gather()
과 색인의 이름을 통해서 얻을 수 있다.
욜로 모델을 통해 나온 결과는 각 그리드당 4개의 텐서를 가지게 된다.
box_xy
: (None, 19, 19, 5, 2)
box_wh
: (None, 19, 19, 5, 2)
box_confidence
: (None, 19, 19, 5, 1)
box_class_probs
: (None, 19, 19, 5, 80)
기본적으로 의 그리드 안에서 둘레 상자에 대한 인식이 이뤄지며 위에서 서술한 과정을 이용해서 둘레 상자를 필터링하게 된다.
def yolo_eval(yolo_outputs, image_shape = (720, 1280), max_boxes=10, score_threshold=.6, iou_threshold=.5):
box_xy, box_wh, box_confidence, box_class_probs = yolo_outputs
boxes = yolo_boxes_to_corners(box_xy, box_wh)
scores, boxes, classes = yolo_filter_boxes(boxes, box_confidence, box_class_probs, score_threshold)
boxes = scale_boxes(boxes, image_shape)
scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes, max_boxes, iou_threshold)
return scores, boxes, classes
scale_box()
를 하는이유는 사용하는 모델이 인식하는 사진의 크기와 실제 적용하려는 사진의 크기가 다르기 때문이다. 이를 통해 실제 사용하려는 사진의 크기에 맞추어 둘레 상자를 표시해야 하기 때문이다.