OpenCV Darknet Yolo 실습

2022. 1. 21. 01:35computervision/섹션 6. YOLO(You Only Look Once)

Darknet Yolo 사이트에서 coco dataset으로 학습된 inference 모델과 config파일을 이용하여 OpenCV에서 inference모델을 생성해보는 실습이다.

https://pjreddie.com/darknet/yolo/ 에서 weight파일과 config파일을 다운받을 수 있다.

 

!mkdir ./pretrained
!echo "##### downloading pretrained yolo/tiny-yolo weight file and config file"
!wget -O /content/pretrained/yolov3.weights https://pjreddie.com/media/files/yolov3.weights
!wget -O /content/pretrained/yolov3.cfg https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg?raw=true 

!wget -O /content/pretrained/yolov3-tiny.weights https://pjreddie.com/media/files/yolov3-tiny.weights
!wget -O /content/pretrained/yolov3-tiny.cfg https://github.com/pjreddie/darknet/blob/master/cfg/yolov3-tiny.cfg?raw=true

!ls /content/pretrained

pretrained 폴더를 생성후 yolo v3 weight config파일과 tiny yolo v3 weight config파일을 pretrained 폴더에 다운로드 받는다.

import os
import cv2

weights_path = '/content/pretrained/yolov3.weights'
config_path =  '/content/pretrained/yolov3.cfg'

cv_net_yolo = cv2.dnn.readNetFromDarknet(config_path, weights_path)

weight와 config파일의 경로를 설정후 readNetFromDarknet함수를 이용하여 모델을 로딩한다.

이때 config, weight 순서를 주의해야한다.

labels_to_names_seq = {0:'person',1:'bicycle',2:'car',3:'motorbike',4:'aeroplane',5:'bus',6:'train',7:'truck',8:'boat',9:'traffic light',10:'fire hydrant',
                        11:'stop sign',12:'parking meter',13:'bench',14:'bird',15:'cat',16:'dog',17:'horse',18:'sheep',19:'cow',20:'elephant',
                        21:'bear',22:'zebra',23:'giraffe',24:'backpack',25:'umbrella',26:'handbag',27:'tie',28:'suitcase',29:'frisbee',30:'skis',
                        31:'snowboard',32:'sports ball',33:'kite',34:'baseball bat',35:'baseball glove',36:'skateboard',37:'surfboard',38:'tennis racket',39:'bottle',40:'wine glass',
                        41:'cup',42:'fork',43:'knife',44:'spoon',45:'bowl',46:'banana',47:'apple',48:'sandwich',49:'orange',50:'broccoli',
                        51:'carrot',52:'hot dog',53:'pizza',54:'donut',55:'cake',56:'chair',57:'sofa',58:'pottedplant',59:'bed',60:'diningtable',
                        61:'toilet',62:'tvmonitor',63:'laptop',64:'mouse',65:'remote',66:'keyboard',67:'cell phone',68:'microwave',69:'oven',70:'toaster',
                        71:'sink',72:'refrigerator',73:'book',74:'clock',75:'vase',76:'scissors',77:'teddy bear',78:'hair drier',79:'toothbrush' }

이후 coco class id와 class명을 알맞게 mapping시킨다.

 

YOLO V3 network 구조

YOLO V3 Network구조에서 위의 그림에서의 82, 94, 106번째 3개의 scale output layer에서 결과 데이터를 추출해야 한다.

layer_names = cv_net_yolo.getLayerNames()
print('final output layer id:', cv_net_yolo.getUnconnectedOutLayers())
print('final output layer name:', [layer_names[i[0] - 1] for i in cv_net_yolo.getUnconnectedOutLayers()])

다음과 같은 코드로 위의 3개의 layer를 찾아 낼 수 있다.

 

 

#전체 Darknet layer에서 13x13 grid, 26x26, 52x52 grid에서 detect된 Output layer만 filtering
layer_names = cv_net_yolo.getLayerNames()
outlayer_names = [layer_names[i[0] - 1] for i in cv_net_yolo.getUnconnectedOutLayers()]
print('output_layer name:', outlayer_names)

img = cv2.imread('./data/beatles01.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

cv_net_yolo.setInput(cv2.dnn.blobFromImage(img, scalefactor=1/255.0, size=(416, 416), swapRB=True, crop=False))

# Object Detection 수행하여 결과를 cvOut으로 반환 
cv_outs = cv_net_yolo.forward(outlayer_names)
print('cv_outs type:', type(cv_outs), 'cv_outs의 내부 원소개수:', len(cv_outs))
print(cv_outs[0].shape, cv_outs[1].shape, cv_outs[2].shape)

classification을 위해서는 위의 3개의 layer에서의 결과값을 반환하여야 한다.

또한 로딩한 모델은 YOLO V3 416*416모델이므로 원본 이미지 배열 사이즈를 416*416으로 reshape해주어야한다.

 위의 코드의 결과는 왼쪽과 같은데 총 3개의 원하는 layer가 cv_outs의 객체로 들어간 것을 알 수 있고

 

첫번째, 두번째, 세번째 feature map의 크기가 각각 (13*13*3(3개의 anchor box)), (26*26*3), (52*52*3)으로 잘 출력된 것을 볼 수 있다.

import numpy as np

rows = img.shape[0]
cols = img.shape[1]

conf_threshold = 0.5
nms_threshold = 0.4

# bounding box의 테두리와 caption 글자색 지정
green_color=(0, 255, 0)
red_color=(0, 0, 255)

class_ids = []
confidences = []
boxes = []

# 3개의 개별 output layer별로 Detect된 Object들에 대해서 Detection 정보 추출 및 시각화 
for ix, output in enumerate(cv_outs):
    print('output shape:', output.shape)
    # feature map에 있는 anchor 갯수만큼 iteration하면서 Detected 된 Object 추출.(13x13x3, 26x26x3, 52x52x3)
    for jx, detection in enumerate(output):
        # class score는 detetection배열에서 5번째 이후 위치에 있는 값. 
        class_scores = detection[5:]
        # class_scores배열에서 가장 높은 값을 가지는 값이 class confidence, 그리고 그때의 위치 인덱스가 class id
        class_id = np.argmax(class_scores)
        confidence = class_scores[class_id]

        # confidence가 지정된 conf_threshold보다 작은 값은 제외 
        if confidence > conf_threshold:
            print('ix:', ix, 'jx:', jx, 'class_id', class_id, 'confidence:', confidence)
            # detection은 scale된 좌상단, 우하단 좌표를 반환하는 것이 아니라, detection object의 중심좌표와 너비/높이를 반환
            # 원본 이미지에 맞게 scale 적용 및 좌상단, 우하단 좌표 계산
            center_x = int(detection[0] * cols)
            center_y = int(detection[1] * rows)
            width = int(detection[2] * cols)
            height = int(detection[3] * rows)
            left = int(center_x - width / 2)
            top = int(center_y - height / 2)
            # 3개의 개별 output layer별로 Detect된 Object들에 대한 class id, confidence, 좌표정보를 모두 수집
            class_ids.append(class_id)
            confidences.append(float(confidence))
            boxes.append([left, top, width, height])

3개의 scale output layer에서 object detection 정보(class_id, confidence score, bbox)를 추출한다.

추출된 정보는 class_ids, confidences, boxes 배열에 저장된다.

 

conf_threshold = 0.5
nms_threshold = 0.4
idxs = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold)

이후 confidence_threshold와 nms_threshold의 값을 설정후 NMS를 이용하여 output layer에서 detected된 object의 겹치는 bbox를 제외시킨다.

import matplotlib.pyplot as plt

draw_img = img.copy()

# NMS로 최종 filtering된 idxs를 이용하여 boxes, classes, confidences에서 해당하는 Object정보를 추출하고 시각화.
if len(idxs) > 0:
    for i in idxs.flatten():
        box = boxes[i]
        left = box[0]
        top = box[1]
        width = box[2]
        height = box[3]
        # labels_to_names 딕셔너리로 class_id값을 클래스명으로 변경. opencv에서는 class_id + 1로 매핑해야함.
        caption = "{}: {:.4f}".format(labels_to_names_seq[class_ids[i]], confidences[i])
        #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
        cv2.rectangle(draw_img, (int(left), int(top)), (int(left+width), int(top+height)), color=green_color, thickness=2)
        cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, red_color, 1)
        print(caption)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

NMS로 최종 filtering된 idxs 정보를 이용하여 class_ids, confidences, boxes 배열에서 해당하는 object정보를 추출하여 시각화한다.

YOLO V3의 특성상 CPU에서는 inference 속도가 원하는 만큼의 성능을 내기가 어려웠다. CPU환경에서는 YOLO의 사용이 크게 효과를 내지 못하는 것 같다.

 

 

 

 

 

 

 

 


tiny yolo에서는 CPU환경에서도 위의 yolo v3보다 빠른 성능을 보여주었다. 하지만 confidence_threshold의 값을 0.5정도로 주었을때

 다음과 같은 성능을 보여주었다. CPU를 사용해서 학습시 inference time 시간에서는 tiny yolo의 사용이 맞지만 예측 성능이 좋지 못하였다. 

'computervision > 섹션 6. YOLO(You Only Look Once)' 카테고리의 다른 글

CVAT 사용해보기  (0) 2022.01.27
Ultralytics Yolo 실습(Oxford pet dataset)  (0) 2022.01.25
YOLO(V3) 이해  (0) 2022.01.20
YOLO(V2) 이해  (0) 2022.01.20
YOLO(V1) 이해  (0) 2022.01.20