AI

[머신러닝] YOLO ver.3 code

Patti Smith 2024. 6. 14.

서론

Yolo는 one stage detector 실시간으로 탐지될 만큼 성능이 좋으며, ver.2의 경우 relet 잔차 개념을 뒷단에 적극적으로 사용. 26x26 13x13 조합하여 작은 객체로 탐지할 수 있다. ver.3부터 실무에 이용하며, 속도(inference time)역시 월등하게 빠름. 그러나 mAP는 그다지 향상되지 않는다.

Abstract

  • 작은 변화가 있었다.
  • RetinaNet보다 3.8배 더 빠르다.
  • 57.9 AP50 획득 및, 51ms 속도를 얻었다.

Backbone model

  • conv층이 53개
  • 작은 물체 탐지를 위해 스케일이 3개로 나누어졌다.
    컨볼루션 층의 보폭을 조절해 나가며 디테일하게 스케일을 조정하여 피쳐맵을 만들어 tensor를 생성하였다.
  • tensor의 구조는 yolo ver.1과 동일

network architecture

  • tensor 구성 시 13x13, 26x26, 52x52 세 개의 스케일로 만들어지도록 컨볼루션 블럭을 구성하였다.
  • 중간에 up sampling 해서 크기를 2배로 키워주는 layer
  • Resnet에서 자주 사용된 skip connection 사용하여 Depth concatenation(앞 부분에서 사용된 특징이 뒷 부분에서도 고려됨) (gradiant 소멸 문제 해결)
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky

# Downsample

[convolutional]
batch_normalize=1
filters=64
size=3
stride=2
pad=1
activation=leaky

출처: darknet github

grid

  • 격자는 같은 방식으로 나누어진다.
  • bound box predict할 때 3개의 스케일 박스로 나누어지도록 설정하였다.
  • anchor box 개념이 추가 되었다.
  • 각 격자당 스케일을 나누어 사용하였다.
  • 박스의 중심은 격자의 상대좦와 폭을 가지고 있다.
  • 하나의 격자에 anchor box가 세 개가 있고 출력도 3개가 된다.
    (13, 13, 3x(4+1+80))

loss function

  • Classification loss을 Binary cross entropy로 변경하였다.

Code Review

yolo v3 gitgub 출처

이미지 전처리

def preprocess_input(image, net_h, net_w):
    new_h, new_w, _ = image.shape

    # determine the new size of the image
    if (float(net_w)/new_w) < (float(net_h)/new_h):
        new_h = (new_h * net_w)/new_w
        new_w = net_w
    else:
        new_w = (new_w * net_h)/new_h
        new_h = net_h

    resized = cv2.resize(image[:,:,::-1]/255., (int(new_w), int(new_h)))

    # embed the image into the standard letter box
    new_image = np.ones((net_h, net_w, 3)) * 0.5
    new_image[int((net_h-new_h)//2):int((net_h+new_h)//2), int((net_w-new_w)//2):int((net_w+new_w)//2), :] = resized
    new_image = np.expand_dims(new_image, 0)

    return new_image

resized = cv2.resize(image[:,:,::-1]/255., (int(new_w), int(new_h)))

  • 정사각형으로 resize해줘야 하는데, 무작정 늘이면 문제가 되므로 남는 공간을 0번 색깔로 칠해준다.

decoding

  • tensor를 해석하여 원하는 정보로 추출시켜준다.
def decode_netout(netout, anchors, obj_thresh, nms_thresh, net_h, net_w):
    grid_h, grid_w = netout.shape[:2]
    nb_box = 3
    netout = netout.reshape((grid_h, grid_w, nb_box, -1))
    nb_class = netout.shape[-1] - 5

    boxes = []

    netout[..., :2]  = _sigmoid(netout[..., :2])
    netout[..., 4:]  = _sigmoid(netout[..., 4:])
    netout[..., 5:]  = netout[..., 4][..., np.newaxis] * netout[..., 5:]
    netout[..., 5:] *= netout[..., 5:] > obj_thresh

    for i in range(grid_h*grid_w):
        row = i / grid_w
        col = i % grid_w
        
        for b in range(nb_box):
            # 4th element is objectness score
            objectness = netout[int(row)][int(col)][b][4]
            #objectness = netout[..., :4]
            
            if(objectness.all() <= obj_thresh): continue
            
            # first 4 elements are x, y, w, and h
            x, y, w, h = netout[int(row)][int(col)][b][:4]

            x = (col + x) / grid_w # center position, unit: image width
            y = (row + y) / grid_h # center position, unit: image height
            w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width
            h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height  
            
            # last elements are class probabilities
            classes = netout[int(row)][col][b][5:]
            
            box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes)
            #box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, None, classes)

            boxes.append(box)

    return boxes

전처리된 이미지를 원래 해상도로 변경

def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w):
   if (float(net_w)/image_w) < (float(net_h)/image_h):
       new_w = net_w
       new_h = (image_h*net_w)/image_w
   else:
       new_h = net_w
       new_w = (image_w*net_h)/image_h
       
   for i in range(len(boxes)):
       x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w
       y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h
       
       boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w)
       boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w)
       boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h)
       boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h)
       

NMS 알고리즘

def do_nms(boxes, nms_thresh):
   if len(boxes) > 0:
       nb_class = len(boxes[0].classes)
   else:
       return
       
   for c in range(nb_class):
       sorted_indices = np.argsort([-box.classes[c] for box in boxes])

       for i in range(len(sorted_indices)):
           index_i = sorted_indices[i]

           if boxes[index_i].classes[c] == 0: continue

           for j in range(i+1, len(sorted_indices)):
               index_j = sorted_indices[j]

               if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_thresh:
                   boxes[index_j].classes[c] = 0

컨볼루션 블럭

def _conv_block(inp, convs, do_skip=True):
    x = inp
    count = 0
    
    for conv in convs:
        if count == (len(convs) - 2) and do_skip:
            skip_connection = x
        count += 1
        
        if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # unlike tensorflow darknet prefer left and top paddings
        x = Conv2D(conv['filter'], 
                   conv['kernel'], 
                   strides=conv['stride'], 
                   padding='valid' if conv['stride'] > 1 else 'same', # unlike tensorflow darknet prefer left and top paddings
                   name='conv_' + str(conv['layer_idx']), 
                   use_bias=False if conv['bnorm'] else True)(x)
        if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm_' + str(conv['layer_idx']))(x)
        if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x)

    return add([skip_connection, x]) if do_skip else x        

for conv in convs:

convs에 4개의 딕셔너리가 들어왔다.

if count == (len(convs) - 2) and do_skip:
skip_connection = x
...
return add([skip_connection, x]) if do_skip else x

-2 지점의 layer를 임시로 skip connection에 보관한 뒤, 마지막줄에 add해서 붙인다.

if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # unlike tensorflow darknet prefer left and top paddings

stride 변수가 1보다 크면 padding한다. (왼쪽 위 오버만 padding하도록)

x = Conv2D(conv['filter'],
conv['kernel'],
strides=conv['stride'],
padding='valid' if conv['stride'] > 1 else 'same', # unlike tensorflow darknet prefer left and top paddings
name='conv' + str(conv['layer_idx']),
use_bias=False if conv['bnorm'] else True)(x)
if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm' + str(conv['layer_idx']))(x)
if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x)

x가 쌓이며 동시에 batchNormalzation와 leakyReLU에 return되어 입력된다.

padding='valid' if conv['stride'] > 1 else 'same', # unlike tensorflow darknet prefer left and top paddings

보폭이 1 이상이면 padding한다.

use_bias=False if conv['bnorm'] else True)(x)

batchnormalization이 있을 경우 bias를 false로 준다.

for 루프를 돌며 딕셔너리 각각을 꺼내 Con2D라는 케라스에 넣고 인스턴스화 한다. 배치사이즈와 leakyReLU로 층을 계속 쌓아나간다.

모델 생성

def make_yolov3_model():
    input_image = Input(shape=(None, None, 3))

    # Layer  0 => 4
    x = _conv_block(input_image, [{'filter': 32, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 0},
                                  {'filter': 64, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 1},
                                  {'filter': 32, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 2},
                                  {'filter': 64, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 3}])

    # Layer  5 => 8
    x = _conv_block(x, [{'filter': 128, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 5},
                        {'filter':  64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 6},
                        {'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 7}])

    # Layer  9 => 11
    x = _conv_block(x, [{'filter':  64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 9},
                        {'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 10}])

    # Layer 12 => 15
    x = _conv_block(x, [{'filter': 256, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 12},
                        {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 13},
                        {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 14}])

    # Layer 16 => 36
    for i in range(7):
        x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 16+i*3},
                            {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 17+i*3}])
        
    skip_36 = x
        
    # Layer 37 => 40
    x = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 37},
                        {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 38},
                        {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 39}])

    # Layer 41 => 61
    for i in range(7):
        x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 41+i*3},
                            {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 42+i*3}])
        
    skip_61 = x
        
    # Layer 62 => 65
    x = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 62},
                        {'filter':  512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 63},
                        {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 64}])

    # Layer 66 => 74
    for i in range(3):
        x = _conv_block(x, [{'filter':  512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 66+i*3},
                            {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 67+i*3}])
        
    # Layer 75 => 79
    x = _conv_block(x, [{'filter':  512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 75},
                        {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 76},
                        {'filter':  512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 77},
                        {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 78},
                        {'filter':  512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 79}], skip=False)

    # Layer 80 => 82
    yolo_82 = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 80},
                              {'filter':  255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 81}], skip=False)

    # Layer 83 => 86
    x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 84}], skip=False)
    x = UpSampling2D(2)(x)
    x = concatenate([x, skip_61])

    # Layer 87 => 91
    x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 87},
                        {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 88},
                        {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 89},
                        {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 90},
                        {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 91}], skip=False)

    # Layer 92 => 94
    yolo_94 = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 92},
                              {'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 93}], skip=False)

    # Layer 95 => 98
    x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True,   'layer_idx': 96}], skip=False)
    x = UpSampling2D(2)(x)
    x = concatenate([x, skip_36])

    # Layer 99 => 106
    yolo_106 = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 99},
                               {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 100},
                               {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 101},
                               {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 102},
                               {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 103},
                               {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True,  'leaky': True,  'layer_idx': 104},
                               {'filter': 255, 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 105}], skip=False)

    model = Model(input_image, [yolo_82, yolo_94, yolo_106])    
    return model

model = Model(input_image, [yolo_82, yolo_94, yolo_106])

각 층 scale을 넣었다. output으로 나오면 loss값으로 나온 것과 계산하여 학습을 한다.

'AI' 카테고리의 다른 글

[AI] gradCam code 분석  (0) 2024.06.14
[빅데이터] 하둡 기본 세팅  (0) 2024.06.14
[빅데이터] Hadoop  (1) 2024.06.14
[머신러닝] object detection  (0) 2024.06.14
자살 고위험군 예측 실습을 위한 데이터 전처리  (1) 2024.06.14

댓글