【Python-Module】 YOLO 수행하기 - OpenCV DNN 모듈

주의 사항 : 지금까지의 OpenCV DNN 모듈 이용과는 흐름이 다르다. 이전 Post를 통해서 YOLO의 이론에 대해서 공부했다. OpenCV DNN 모델을 사용해서 YOLO를 수행해보자.

OpenCV DNN 모듈을 사용해서 YOLO 수행하기
주의 사항 : 지금까지의 OpenCV DNN 모듈 이용과는 흐름이 다르다.
OpenCV 사용과정 요약은 이전 Post 참조

참고 파일 (conda tf113 환경)
/DLCV/Detection/yolo/OpenCV_Yolo_이미지와_영상_Detection.ipynb

1. OpenCV DNN Yolo 사용방법

  • 핵심 사항
    • 지금까지는 Tensorflow 기반 pretrain 모델을 가져왔다.
    • Tensorflow에서 Yolo를 지원하지 않는다. Yolo의 창시자 사이트에 가서 weight와 conf파일을 받아와야 한다.
  • DarkNet 구성 환경 및 Yolo 아키텍처에 따라서 사용자가 직접 Object Detection 정보를 추출해야한다.
    • 아래와 같이 사용자가 Scale이 다른 Feature Map에서 Detection결과 추출과 NMS를 직접 해야한다. OpenCV에서 해주지 않는다.
    • 82번 94번 106번 (13 x 13, 26 x 26, 52 x 52) Layer에 직접 접근해서 Feature Map정보를 직접 가져와야 한다.
    • NMS 도 직접 해야한다. OpenCV NMS 함수를 호출해야한다.
    • 이때 Feature Map정보에서 Depth인 4(bounding box regression) + 1(confidence=Objectness Score(객체인지 아닌지)) + 80(class logistic 확률값).
      • 80개의 값 중에서 가장 높은 값을 가지는 위치 인덱스가 객체의 Label값이다.
      • 4개의 값이 t인 (offset값인지 알았는데… 좌표값이다.) 근데 그 좌표값은 (center_X, center_Y, width, hight)이다. 이 값을 좌상단 우하단(X1 Y1 X2 Y2) 좌표로 변환을 해줘야 한다.

drawing

  • Pretrained 된 Inferecne 모델 로딩 주의 사항
    • 대부분이 Darknet 사이트에서 직접 weight와 config파일을 다운받와야 한다.
    • 그것을 통해서 cv.dnn.readNetFromDarknet(confg, weight)
      (지금까지는 cv.dnn.readNetFromTensorflow(weight, confg) 했었지만.. )
  • OpenCV로 Yolo inference 구현 절차 (위의 내용 전체 요약)

drawing

2. OpenCV DNN Yolo Inference 구현 코드 - 단일 이미지

- 아래의 코드에서 계속 yolo-416 과 tiny-yolo 를 이용
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

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

print('image shape:', img.shape)
plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
image shape: (365, 550, 3)

drawing

  • Darknet Yolo사이트에서 coco로 학습된 Inference모델와 환경파일을 다운로드 받은 후 이를 이용해 OpenCV에서 Inference 모델 생성
    • https://pjreddie.com/darknet/yolo/ 에 다운로드 URL 있음.
    • YOLOv3-416 사용할 에정
    • pretrained 모델은 wget https://pjreddie.com/media/files/yolov3.weights 에서 다운로드
    • pretrained 모델을 위한 환경 파일은 https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg 에서 다운로드
    • wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg?raw=true -O ./yolov3.cfg
    • 주의 필요 : readNetFromDarknet(config파일, weight파일)로 config파일 인자가 weight파일 인자보다 먼저 옴. 주의 필요.
  • readNetFromDarknet(config파일, weight파일)을 이용하여 yolo inference network 모델을 로딩
!pwd
# /home/sb020518/DLCV/Detection/yolo

import os
#가급적 절대 경로 사용. 
CUR_DIR = os.path.abspath('.')
weights_path = os.path.join(CUR_DIR, './pretrained/yolov3.weights')
config_path =  os.path.join(CUR_DIR, './pretrained/yolov3.cfg')
#config 파일 인자가 먼저 옴. 
cv_net_yolo = cv2.dnn.readNetFromDarknet(config_path, weights_path)
/home/sb020518/DLCV/Detection/yolo
  • COCO class id와 class 명 매핑
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' }
labels_to_names = {1:'person',2:'bicycle',3:'car',4:'motorcycle',5:'airplane',6:'bus',7:'train',8:'truck',9:'boat',10:'traffic light',
                    11:'fire hydrant',12:'street sign',13:'stop sign',14:'parking meter',15:'bench',16:'bird',17:'cat',18:'dog',19:'horse',20:'sheep',
                    21:'cow',22:'elephant',23:'bear',24:'zebra',25:'giraffe',26:'hat',27:'backpack',28:'umbrella',29:'shoe',30:'eye glasses',
                    31:'handbag',32:'tie',33:'suitcase',34:'frisbee',35:'skis',36:'snowboard',37:'sports ball',38:'kite',39:'baseball bat',40:'baseball glove',
                    41:'skateboard',42:'surfboard',43:'tennis racket',44:'bottle',45:'plate',46:'wine glass',47:'cup',48:'fork',49:'knife',50:'spoon',
                    51:'bowl',52:'banana',53:'apple',54:'sandwich',55:'orange',56:'broccoli',57:'carrot',58:'hot dog',59:'pizza',60:'donut',
                    61:'cake',62:'chair',63:'couch',64:'potted plant',65:'bed',66:'mirror',67:'dining table',68:'window',69:'desk',70:'toilet',
                    71:'door',72:'tv',73:'laptop',74:'mouse',75:'remote',76:'keyboard',77:'cell phone',78:'microwave',79:'oven',80:'toaster',
                    81:'sink',82:'refrigerator',83:'blender',84:'book',85:'clock',86:'vase',87:'scissors',88:'teddy bear',89:'hair drier',90:'toothbrush',
                    91:'hair brush'}
  • 3개의 scale Output layer에서 결과 데이터 추출
  • 82번 94번 106번 (13 x 13, 26 x 26, 52 x 52) Layer에 직접 접근해서 Feature Map정보를 직접 가져와야 한다.
#전체 Darknet layer에서 13x13 grid, 26x26, 52x52 grid에서 detect된 Output layer만 filtering
layer_names = cv_net_yolo.getLayerNames()
# layer_names : ['conv_0', 'bn_0', 'relu_1', 'conv_1', 'bn_1', 'relu_2', 'conv_ ....
outlayer_names = [layer_names[i[0] - 1] for i in cv_net_yolo.getUnconnectedOutLayers()]
# cv_net_yolo.getUnconnectedOutLayers() : [[200], [227], [254]]

print('output_layer name:', outlayer_names)

# Yolov3 416는 416 x 416 Input을 받는다. 원본 이미지 배열을 사이즈 (416, 416)으로, BGR을 RGB로 변환하여 배열 입력
# blobFromImage를 사용해서 CNN에 넣어야할 이미지 전처리를 쉽게 한다.
# DarkNet에서는 scalefactor를 꼭 처리해줘야한다!
out = cv2.dnn.blobFromImage(img, scalefactor=1/255.0, size=(416, 416), swapRB=True, crop=False)
print('type : ', type(out))
print('shape :', out.shape)
print('size :', out.size)

 # 말그대로 신경망에 넣을 사진만 Setting 해준다. 
cv_net_yolo.setInput(out)  
# Object Detection 수행하여 결과를 cv_out으로 반환 
cv_outs = cv_net_yolo.forward(outlayer_names)  # inference를 돌려서 원하는 layer의 Feature Map 정보만 뽑아 낸다. 
print('cv_outs type : list // cv_outs length :', len(cv_outs))
print("cv_outs[0] : 첫번째 FeatureMap 13 x 13 x 85, cv_outs[1] : 두번째 FeatureMap 26 x 26 x 85 ")

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

output_layer name: ['yolo_82', 'yolo_94', 'yolo_106']
type :  <class 'numpy.ndarray'>
shape : (1, 3, 416, 416)
size : 519168
cv_outs type : list // cv_outs length : 3
cv_outs[0] : 첫번째 FeatureMap 13 x 13 x 85, cv_outs[1] : 두번째 FeatureMap 26 x 26 x 85 
  • 위에서 뽑은 FeatureMap 정보를 사용해서…
    • 3개의 scale output layer에서 Object Detection 정보를 모두 수집.
    • center와 width,height좌표는 모두 좌상단, 우하단 좌표로 변경.
  • 아래 과정을 통해서 ((13 x 13) + (26 x 26) + (52 x 52)) x 3(anchor 갯수) = 10,647 개의 예측한 Bounding Box중에서 confidence가 threshold 보다 높은 Bounding box만을 추출 했다.
  • 아래 코드의 결과 값으로 나오는 (507, 85) = (13 x 13 x 3(하나의 cell마다 3개의 Anchor에 대한), 85) 이다
import numpy as np

# 원본 이미지를 네트웍에 입력시에는 (416, 416)로 resize 함. 
# 이후 결과가 출력되면 resize된 이미지 기반으로 bounding box 위치가 예측 되므로 이를 다시 원복하기 위해 원본 이미지 shape정보 필요
rows = img.shape[0]
cols = img.shape[1]

conf_threshold = 0.5
nms_threshold = 0.4   # 이 값이 클 수록 box가 많이 사라짐. 조금만 겹쳐도 NMS로 둘 중 하나 삭제하므로

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

# 3개의 개별 output layer별로 Detect된 Object들에 대해서 Detection 정보 추출 및 시각화 
for ix, output in enumerate(cv_outs):
    print('output shape:', output.shape)
    # Detected된 Object별 iteration
    for jx, detection in enumerate(output):
        # class score는 detetection배열에서 5번째 이후 위치에 있는 값. 즉 6번쨰~85번째 까지의 값
        scores = detection[5:]
        # scores배열에서 가장 높은 값을 가지는 값이 class confidence, 그리고 그때의 위치 인덱스가 class id
        class_id = np.argmax(scores)
        confidence = scores[class_id] # 5번쨰 값은 objectness score이다. 객체인지 아닌지의 확률이다. 6번쨰~85번째 까지의 값이 그 객체일 확률 값이다. 

        # 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])
output shape: (507, 85)
ix: 0 jx: 324 class_id 0 confidence: 0.780293
ix: 0 jx: 325 class_id 0 confidence: 0.70145583
ix: 0 jx: 328 class_id 0 confidence: 0.95964307
ix: 0 jx: 331 class_id 0 confidence: 0.8294637
ix: 0 jx: 367 class_id 0 confidence: 0.83721125
ix: 0 jx: 370 class_id 0 confidence: 0.62093467
output shape: (2028, 85)
ix: 1 jx: 978 class_id 0 confidence: 0.9954639
ix: 1 jx: 1262 class_id 0 confidence: 0.8263151
ix: 1 jx: 1274 class_id 0 confidence: 0.8833774
ix: 1 jx: 1331 class_id 0 confidence: 0.7529699
ix: 1 jx: 1334 class_id 0 confidence: 0.9874747
ix: 1 jx: 1337 class_id 0 confidence: 0.5676544
ix: 1 jx: 1340 class_id 0 confidence: 0.99014115
ix: 1 jx: 1343 class_id 0 confidence: 0.9779411
ix: 1 jx: 1352 class_id 0 confidence: 0.59539807
ix: 1 jx: 1361 class_id 0 confidence: 0.62599
ix: 1 jx: 1409 class_id 0 confidence: 0.97582513
ix: 1 jx: 1412 class_id 0 confidence: 0.9940848
ix: 1 jx: 1415 class_id 0 confidence: 0.62239736
ix: 1 jx: 1418 class_id 0 confidence: 0.8629691
ix: 1 jx: 1421 class_id 0 confidence: 0.84953487
ix: 1 jx: 1487 class_id 0 confidence: 0.80823416
output shape: (8112, 85)
ix: 2 jx: 3848 class_id 0 confidence: 0.85432345
ix: 2 jx: 3851 class_id 0 confidence: 0.9162883
ix: 2 jx: 4007 class_id 0 confidence: 0.53006333
  • 위에서 뽑은 몇가지 Box에 대해서 NMS적용하기
    • cv2.dnn.NMSBoxes 모듈을 사용하기 위한 파라미터는 위에서 잘 만들어 놓았다,
    • 파라메터 : (bounding box의 좌상단 우하단 4개의 좌표, 같은 인덱스에 위치한 confidence값)
    • output은 진짜 필요한 Bounding box의 indexes를 반환한다!
conf_threshold = 0.5
nms_threshold = 0.4
idxs = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold)

print(idxs) # 이 index의 box만 살아남았음을 의미한다.
[[ 6]
 [17]
 [12]
 [ 2]
 [23]
 [ 8]
 [21]]
  • NMS로 최종 filtering된 idxs를 이용하여 boxes, classes, confidences에서 해당하는 Object정보를 추출하고 시각화.
# cv2의 rectangle()은 인자로 들어온 이미지 배열에 직접 사각형을 업데이트 하므로 그림 표현을 위한 별도의 이미지 배열 생성. 
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)

person: 0.9955
person: 0.9941
person: 0.9901
person: 0.9596
person: 0.9163
person: 0.8834
person: 0.8082

drawing

3. 2에서 한 작업을 함수로 def해서 사용하기 - 단일 이미지

def get_detected_img(cv_net, img_array, conf_threshold, nms_threshold, use_copied_array=True, is_print=True):
    
    # 원본 이미지를 네트웍에 입력시에는 (416, 416)로 resize 함. 
    # 이후 결과가 출력되면 resize된 이미지 기반으로 bounding box 위치가 예측 되므로 이를 다시 원복하기 위해 원본 이미지 shape정보 필요
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    draw_img = None
    if use_copied_array:
        draw_img = img_array.copy()
    else:
        draw_img = img_array
    
    #전체 Darknet layer에서 13x13 grid, 26x26, 52x52 grid에서 detect된 Output layer만 filtering
    layer_names = cv_net.getLayerNames()
    outlayer_names = [layer_names[i[0] - 1] for i in cv_net.getUnconnectedOutLayers()]
    
    # 로딩한 모델은 Yolov3 416 x 416 모델임. 원본 이미지 배열을 사이즈 (416, 416)으로, BGR을 RGB로 변환하여 배열 입력
    cv_net.setInput(cv2.dnn.blobFromImage(img_array, scalefactor=1/255.0, size=(416, 416), swapRB=True, crop=False))
    start = time.time()
    # Object Detection 수행하여 결과를 cvOut으로 반환 
    cv_outs = cv_net.forward(outlayer_names)
    layerOutputs = cv_net.forward(outlayer_names)
    # 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):
        # Detected된 Object별 iteration
        for jx, detection in enumerate(output):
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = 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])
    
    # NMS로 최종 filtering된 idxs를 이용하여 boxes, classes, confidences에서 해당하는 Object정보를 추출하고 시각화.
    idxs = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold)
    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)

    if is_print:
        print('Detection 수행시간:',round(time.time() - start, 2),"초")
    return draw_img
  • 방금 위에서 정의한 함수를 사용해서 Detection을 수행해보자!
  • Open CV에서 Yolo를 시행하는 것이 생각보다 Detection 수행시간이 긴것을 알 수 있다.
  • 그럼에도 불구하고 검출 성능은 나쁘지 않다.
  • 이것은 OpenCV에서 YOLO가 최적화되어 있지 않기 때문이라고 한다.
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import time


# image 로드 
img = cv2.imread('../../data/image/john_wick01.jpg')

#coco dataset 클래스명 매핑

import os
#가급적 절대 경로 사용. 
CUR_DIR = os.path.abspath('.')
weights_path = os.path.join(CUR_DIR, './pretrained/yolov3.weights')
config_path =  os.path.join(CUR_DIR, './pretrained/yolov3.cfg')

# tensorflow inference 모델 로딩
cv_net_yolo = cv2.dnn.readNetFromDarknet(config_path, weights_path)
    
conf_threshold = 0.5
nms_threshold = 0.4
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net_yolo, img, conf_threshold=conf_threshold, nms_threshold=nms_threshold, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
Detection 수행시간: 2.8 초

drawing

4. 3에서 def한 함수사용해서 YOLO - video object detection

  • 지금까지 ouput을 .avi형식으로 저장했지만, .mp4로 저장해도 상관없고, mp4를 사용하면 Video모듈을 사용해서 볼수도 있다.
  • avi를 사용하면 Video 모듈을 사용할 수 없고, 다운로드해서 봐야한다는 점을 깨달았으니 잘 알아두자.
def do_detected_video(cv_net, input_path, output_path,conf_threshold,nms_threshold, is_print):
    
    cap = cv2.VideoCapture(input_path)

    codec = cv2.VideoWriter_fourcc(*'XVID')

    vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    vid_fps = cap.get(cv2.CAP_PROP_FPS)

    vid_writer = cv2.VideoWriter(output_path, codec, vid_fps, vid_size) 

    frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print('총 Frame 갯수:', frame_cnt)

    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break
        
        returned_frame = get_detected_img(cv_net, img_frame, conf_threshold=conf_threshold, nms_threshold=nms_threshold, \
                                          use_copied_array=False, is_print=is_print)
        vid_writer.write(returned_frame)
    # end of while loop

    vid_writer.release()
    cap.release()
do_detected_video(cv_net_yolo, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_small_yolo01.mp4', conf_threshold,
                  nms_threshold, True)
!gsutil cp ../../data/output/John_Wick_small_yolo01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_yolo01.avi

5. 지금까지의 모든 작업 tiny Yolo로 다시 수행하기

  • 속도가 많이 빠르지만, 정확도가 많~이 떨어진다.
  • tiny yolo의 pretrained된 weight파일은 wget https://pjreddie.com/media/files/yolov3-tiny.weights 에서 download 가능.
  • config 파일은 wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3-tiny.cfg?raw=true -O ./yolov3-tiny.cfg 로 다운로드
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import time

# image 로드 
img = cv2.imread('../../data/image/beatles01.jpg')

#coco dataset 클래스명 매핑

import os
#가급적 절대 경로 사용. 
CUR_DIR = os.path.abspath('.')
weights_path = os.path.join(CUR_DIR, './pretrained/yolov3-tiny.weights')
config_path =  os.path.join(CUR_DIR, './pretrained/yolov3-tiny.cfg')

# tensorflow inference 모델 로딩
cv_net_yolo = cv2.dnn.readNetFromDarknet(config_path, weights_path)

#tiny yolo의 경우 confidence가 일반적으로 낮음. 
conf_threshold = 0.3
nms_threshold = 0.4
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net_yolo, img, conf_threshold=conf_threshold, nms_threshold=nms_threshold, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
  • tiny yolo로 영상 Detection
do_detected_video(cv_net_yolo, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_small_tiny_yolo01.avi', conf_threshold,
                  nms_threshold, True)
!gsutil cp ../../data/output/John_Wick_small_tiny_yolo01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_tiny_yolo01.avi

【Paper】 YOLO (You Only Live Once) V1 V2 V3 핵심정리

One Stage Detector의 시작이라고 할 수 있는 Yolo, SSD에 대해서 다시 공부하고 정리해본다.

YOLO (You Only Live Once)

1. Yolo의 이해

drawing

  • V3의 abstract에서 COCO dataset에 대해서 0.5 IOU를 기준으로 하면 RetinaNet보다 성능도 좋고 빠르다고 변명하고 있다.
  • RetinaNet의 장점 - 작은 Object에 대해서도 Bounding Box Object Detection을 잘 수행한 한다는 것을 할 수 있다.
  • Version에 따른 특징
    • Version1 : 빠른 Detection 시간. 그러나 낮은 정확도. 그 뒤 SSD가 더 좋은 성능과 속도
    • Version2 : 수행시간과 성능 모두 개선. SSD보다 빠르지만 성능은 아주 조금 개선
    • Version3 : 수행시간은 조금 느려졌으나 성능은 대폭 개선

2. YOLO Version 1

  • 학습 절차
    1. 어떤 입력 이미지 크기든 상관없이, 이미지를 S x S Grid Cell(7 x 7)로 나눈다.
    2. 하나의 Cell이 하나의 Object에 대한 Detection을 수행한다.
    3. 각 Grid Cell이 2개의 Bounding Box 후보를 기반으로 Object의 Bounding Box를 예측한다.
      (Anchor Box 기반은 아니다. Cell을 기반으로 한다. 2개의 Box를 활용해 Cell의 Classificatino, Box 추측을 수행한다.)

drawing

  • 위 이미지 설명
    • S x S x (B x 5 + C) : S x S = 7 x 7 , B = bounding box + Confidence Score(아래의 값이 학습 위한 GT) , C = Class 갯수
    • Confidence Score : (Object 인지, 아닌지에 대한 확률 = Objectness Score) x (Grid Cell에 대한 Bounding Box와 GT Box와의 IOU값) 이 나오도록 학습된다.
    • bounding box : x, y, w, h 는 Grid Cell에 대한 예측되 나와야 하는 Bounding Box 좌표값이다.
    • 20개의 Class에 대한 Softmax값이 나오도록 학습되는 값이다. Bounding Box가 2개인데 (20개 Class)x2 인 40개가 있는게 아니고 20개만 있다. 그 이유는 한 Grid Cell에 대해서 한 Object만 Detect하겠다는 의지를 담고 있다. (Yolo V1의 단점이라고 할 수 있다. Version2에서 바뀐다.)

drawing

  • 위 이미지 설명
    • 위위 이미지의 흐름을 통해서 첫번째 보라색 박스의 결과가 추출 된다.
    • 가운데 위 : 7 x 7 x 2 개의 bounding box값 [or에 대한 offset값], Confidence 값
    • 가운데 아래 : Class에 대한 20개 Softmax값에 대해, 가장 큰 확률값을 가지는 Object probability 값.
    • NMS 과정을 거쳐, 오른쪽 최종 결과를 추출한다. (NMS 과정 정리 이전 게시물)
      <NMS 과정>
      1. 높은 confidence를 가지는 Bbox를 정렬
      2. 높은 Confidence Score의 Box부터 겹치는 다른 Box를 모두 조사하여 특정 IOU 이상인 Box를 모두 제거 한다(IOU Threshold > 0.4, [주의] 이 값이 낮을 수록 많은 Box가 제거 된다. )
      3. 위의 과정을 반복해서 최종적으로 남은 Box만 return 한다.
  • YOLO v1 문제점
    • Detection 시간은 빠르나 성능은 떨어짐. 특히 작은 물체에 대한 성능 나쁨.
    • Anchor Box를 사용하지 않으므로, 어떤 힌트가 없다. Cell에 대해서 Bounding Box를 단순 예측 한다.
      • 추가 보충 설명 : Anchor box하고 유사한 후보 box를 사용하기는 한다. 7x7 형태의 Grid Cell 마다 중심점 만으로는 오보젝트의 형태를 제대로 예측하기 어렵습니다. 후보 bounding box와 같이 일정한 bounding box 형태가 일단 있어야 합니다. 그리고 이 후보 bounding box와 Ground Truth bounding box 간의 offset값을 학습할 수 있어야 합니다. 결국은 학습된 offset값에 기반해서 새로운 이미지 오브젝트의 offset을 예측하는 것입니다. anchor box도 이와 비슷한 방식이지만, V1은 많은 anchor box가 기준이 되는 게 아니라 개별 Grid Cell이 기준이 되어서 후보 bounding box와 GT Bounding box의 offset을 학습하기 때문에 anchor box 기반 예측이라고 간주하기가 조금은 어렵다.
    • 하나의 Cell에서 하나의 Object만 예측하므로, 겹친 Object는 Detect하지 못한다.

3. YOLO Version 2

  • 핵심 특징
    1. Darknet 19
    2. Grid Cell 당 Anchor Box 수 : 5
    3. Anchor box 사용 - 결정 방법 : K-means Clustering. Anchor box 크기와 ratio를 정하는 방법. GT Box를 그룹핑 한 후, 그것을 이용해서 Anchor box 크기와 ratio 결정.
    4. Output Peature Map 크기 : 13 x 13
    5. Batch Normaliztion 을 적극적으로 활용하기 시작
    6. High Resolution Classifier : 처음에 Input 224 x 224로 학습하다가, input 448 x 448로 Fine tuning 하여 성능향상을 얻음.
    7. 서로 다른 크기의 Image들 섞어서 배치하여 작은 Object도 Detect할 수 있도록 노력.

drawing

  • Network 구조도
    • Version 1에서 사용되던 Fully connected 구조가 사라짐

drawing

  • Anchor Box
    • 1개의 Cell에서 여러개의 Anchor를 통해 개별 Cell에서 여러개 Object Detection이 가능해졌다. 이전에는 Cell 중심 Detection이었다면, 이제는 Anchor 중심 Detecion을 수행한다.
    • K-Means Clustering 을 통해 데이터 세트의 GT의 크기와 Ratio에 대해서 군집화 분류를 하고, 학습에 사용할 Anchor Box의 크기와 Ratio를 선정했다.
    • 위의 사진을 보면 K-Means Clustering의 결과로 5개 색깔의 묶음이 나왔다. 1묶음이 1개의 Anchor Box의 평균사이즈가 되어 총 5개의 Anchor box를 Yolo2에서 사용한다.

drawing

  • Output
    • 13 x 13 의 최종 Feature Map에는 5개의 Anchor Box에 대한 정보가 들어가 있다.
    • 1개의 Anchor box에 대해서 25개의 정보를 가진다. 4(bounding box regression) + 1(Objectness Score x IOU)) + 20(class softmax)

3. YOLO Version 3

drawing

  • 핵심 정리
    • DarkNet 53 : ResNet을 사용하면 속도가 느린것을 할 수 있다. ResNet과 같이 Classification에서 사용하는 Backbone을 사용하는게 아니라, 실시간성을 높이기 위해서 자신들만의 Classification Baxkcone인 Darknet을 만드는 것을 알 수 있다. (Bn Ops/BFLOP/s 를 보아도 수행 성능이 좋음을 확인할 수 있다.)
    • Grid Cell 당 Anchor Box 수 : Output Feature map 당 3개. 서로 다른 크기와 스케일로 총 9개
    • Anchor box 사용 - 결정 방법 : K-means Clustering
    • Output Peature Map 크기 : 13 x 13, 26 x 26, 52 x 52 (3개의 다른 크기의 Ouput - Multi Scale Detection 가능)
    • Feature Map Scalling 기법 : FPN(Feature Pyramid Network)

drawing

  • 주요 특징
    • FPN와 유사한 기법을 적용하여 3개의 Feature Map Output에서(13x13 26x26 52x52), 각각 3개의 서로 다른 크기의 scale을 가진 anchor box로 Detection
    • Darknet 53 을 통해서 좀 더 강한 Sementic한 Feature Map추출
    • Multi Labels (사람 - 여성 - 빨간 옷 입음 - 커피 듬 이런 것) 예측하기 위해 Softmax가 아닌 Sigmoid 기반의 Logistic Regression Classifier로 개별 Object의 Multi labels 예측
    • 수행 속도는 Version2보다는 줄었지만 수행 성능을 높아 졌다.

drawing

  • 네트워크 구조도
    • input은 416 x 416 의 이미지가 들어간다. 원본이미지가 이것보다 크면 그 이미지를 Resize한다.
    • Skip Connection 과 Upsampling(초록색)이 적극적으로 활용되는 것을 볼 수 있다.
    • 82번 Layer에서 나오는 결과가 13 x 13 -> Anchor 3개 사용. 1번 2번 3번
    • 94번 Layer에서 나오는 결과가 26 x 26 -> Anchor 3개 사용. 4번 5번 6번
    • 106번 Layer에서 나오는 결과가 52 x 52 -> Anchor 3개 사용. 7번 8번 9번
    • 구조도를 보면 모르겠지만, 구조도 모양을 조금 바꾸면 FPN과 똑같다.
      Feature Pyramid Network 사진도 간략히 참고해 놓음.

drawing

  • Ouput
    • Output된 결과가 Box 하나당 4(bounding box regression) + 1(Objectness Score x IOU) + 80(class Logistic 값)이다.
    • Box는 총 9개니까 (4 x 1 x 80) x 9 의 ouput feature map depth
    • Multi Scale Detection에서 좋은 성능을 내었다.
  • Training
    • Multi-scale Training : 다양한 크기의 이미지를 배치로써 학습시킨다.
    • Data Augmentation 적극 이용

drawing

  • Location Prediction(아래 사진)
    • b는 ground True와 동일하게 내가 예측해야하는 박스의 크기와 좌표
      = 이게 Inference에서 최종 Output으로 나와야 한다. (아직 헷갈림)
    • c는 Anchor 박스의 좌표
    • p는 Anchor 박스의 크기
    • t는 offset 값 = 신경망의 역할 (아직 헷갈림)
    • 좌표 offset으로 0~1사이 값인 시그모이드 값 사용. Cell의 중심으로 부터bounding box가 너무 옮겨지지 않도록 해야한다.
    • Objectness Score = object이냐 아니냐 확률 x GT와 나의 최종 예측 boundingbox와의 IOU 이 나와야 한다.

drawing

  • Logistic Classifier 사용
    • Softmax사용하지 않음. 20개의 class가 있다면 다 합쳐서 1이 나오게 하는 softmax값 사용하지 않는다.
    • 보통은 Logistic Classifier 값(0.3, 0.6, 0.7)를 Softmax로 바꾸는데 (0.26, 0.35, 0.39) 이런 행동을 하지 않는다.
    • 그래서 사진과 같이 woman 이면서 person일 수 있다고 할 수 있다. = Multi Labels 예측 가능.
  • 결론
    • coco에서 IOU를 0.5~0.95를 사용하지 않고, 0.5만 고려한다면 Yolo3는 성능이 RetinaNet과 비슷하면서 속도가 굉장히 빠르다.
    • Focal Loss 를 사용해봤지만, 성능 향상을 그리 많이 되지 않았다. (이미 Focal Loss가 추구하는 방향이 Yolo에 들어가 있으므로)
    • RetinaNet은 Focal Loss와 FPN를 적극적으로 활용하고 있는 것은 안다.

drawing

【Tensorflow】SSD Inference 수행하기

Tensorflow v1.3 API로, SSD Inference 수행하기

/DLCV/Detection/ssd/Tensorflow_SSD_이미지와_영상_Detection.ipynb 참조

/DLCV/Detection/ssd/SSD_Face_Detection.md

이번 post를 참고해서, Tensorflofw inference 흐름 파악하기

1. Tensorflow로 SSD - Object Detection 수행하기

  • 이전 Post에서 받은,
    download 받은 SSD+Inception, SSD+Mobilenet 그래프 모델을 이용하여 Tensorflow에서 이미지와 비디오 Object Detectio 수행

  • SSD+Inception, SSD+Mobilenet의 config파일은 각각 /DLCV/Detection/ssd/pretrained/ “graph.pbtxt”, “graph2.pbtxt” 이다.
    하지만 여기서는 graph.pbtxt와 graph2.pbtxt를 사용할 필요가 없다. pb 파일만 필요하다^^

#### ms coco 클래스 아이디와 클래스 명 매핑
labels_to_names = {1:'person',2:'bicycle',3:'car',4:'motorcycle',5:'airplane',6:'bus',7:'train',8:'truck',9:'boat',10:'traffic light',
                    11:'fire hydrant',12:'street sign',13:'stop sign',14:'parking meter',15:'bench',16:'bird',17:'cat',18:'dog',19:'horse',20:'sheep',
                    21:'cow',22:'elephant',23:'bear',24:'zebra',25:'giraffe',26:'hat',27:'backpack',28:'umbrella',29:'shoe',30:'eye glasses',
                    31:'handbag',32:'tie',33:'suitcase',34:'frisbee',35:'skis',36:'snowboard',37:'sports ball',38:'kite',39:'baseball bat',40:'baseball glove',
                    41:'skateboard',42:'surfboard',43:'tennis racket',44:'bottle',45:'plate',46:'wine glass',47:'cup',48:'fork',49:'knife',50:'spoon',
                    51:'bowl',52:'banana',53:'apple',54:'sandwich',55:'orange',56:'broccoli',57:'carrot',58:'hot dog',59:'pizza',60:'donut',
                    61:'cake',62:'chair',63:'couch',64:'potted plant',65:'bed',66:'mirror',67:'dining table',68:'window',69:'desk',70:'toilet',
                    71:'door',72:'tv',73:'laptop',74:'mouse',75:'remote',76:'keyboard',77:'cell phone',78:'microwave',79:'oven',80:'toaster',
                    81:'sink',82:'refrigerator',83:'blender',84:'book',85:'clock',86:'vase',87:'scissors',88:'teddy bear',89:'hair drier',90:'toothbrush',
                    91:'hair brush'}

1-1 SSD + Inception으로 단일 이미지 Object Detection

import numpy as np
import tensorflow as tf
import cv2
import time
import matplotlib.pyplot as plt
%matplotlib inline


#inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/ssd_inception_v2_coco_2017_11_17/frozen_inference_graph.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    
    # 입력 이미지 생성 및 BGR을 RGB로 변경 
    img = cv2.imread('../../data/image/beatles01.jpg')
    draw_img = img.copy()
    rows = img.shape[0]
    cols = img.shape[1]
    inp = cv2.resize(img, (300, 300))
    # OpenCV로 입력받은 BGR 이미지를 RGB 이미지로 변환 
    inp = inp[:, :, [2, 1, 0]] 

    start = time.time()
    # Object Detection 수행. 
    out = sess.run([sess.graph.get_tensor_by_name('num_detections:0'),
                    sess.graph.get_tensor_by_name('detection_scores:0'),
                    sess.graph.get_tensor_by_name('detection_boxes:0'),
                    sess.graph.get_tensor_by_name('detection_classes:0')],
                   feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    # Bounding Box 시각화 
    # Detect된 Object 별로 bounding box 시각화 
    num_detections = int(out[0][0])
    for i in range(num_detections):
        # class id와 object class score, bounding box정보를 추출
        classId = int(out[3][0][i])
        score = float(out[1][0][i])
        bbox = [float(v) for v in out[2][0][i]]
        if score > 0.4:
            left = bbox[1] * cols
            top = bbox[0] * rows
            right = bbox[3] * cols
            bottom = bbox[2] * rows
            # cv2의 rectangle(), putText()로 bounding box의 클래스명 시각화 
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), green_color, thickness=2)
            caption = "{}: {:.4f}".format(labels_to_names[classId], score)
            print(caption)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, 1)
    
    print('Detection 수행시간:',round(time.time() - start, 2),"초")
    
img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
        

drawing

1-2 위에서 한작업 함수로 def 하기. Session 내부에 있는 일부 작업만 def한 것!

def get_tensor_detected_image(sess, img_array, use_copied_array):
    
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    if use_copied_array:
        draw_img = img_array.copy()
    else:
        draw_img = img_array
    
    inp = cv2.resize(img_array, (300, 300))
    inp = inp[:, :, [2, 1, 0]]  # BGR2RGB
    
    start = time.time()
    # Object Detection 수행. 
    out = sess.run([sess.graph.get_tensor_by_name('num_detections:0'),
                    sess.graph.get_tensor_by_name('detection_scores:0'),
                    sess.graph.get_tensor_by_name('detection_boxes:0'),
                    sess.graph.get_tensor_by_name('detection_classes:0')],
                   feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    # Bounding Box 시각화 
    # Detect된 Object 별로 bounding box 시각화 
    num_detections = int(out[0][0])
    for i in range(num_detections):
        # class id와 object class score, bounding box정보를 추출
        classId = int(out[3][0][i])
        score = float(out[1][0][i])
        bbox = [float(v) for v in out[2][0][i]]
        if score > 0.4:
            left = bbox[1] * cols
            top = bbox[0] * rows
            right = bbox[3] * cols
            bottom = bbox[2] * rows
            # cv2의 rectangle(), putText()로 bounding box의 클래스명 시각화 
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), green_color, thickness=2)
            caption = "{}: {:.4f}".format(labels_to_names[classId], score)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, 1)
    
    print('Detection 수행시간:',round(time.time() - start, 3),"초")
    return draw_img

1-3 위에서 만든 함수 사용해서 Inference 수행하기

#inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/ssd_inception_v2_coco_2017_11_17/frozen_inference_graph.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    
    # 입력 이미지 생성, Object Detection된 image 반환, 반환된 image의 BGR을 RGB로 변경 
    img = cv2.imread('../../data/image/beatles01.jpg')
    draw_img = get_tensor_detected_image(sess, img, True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

2. tensorflow로 SSD+ Inception 기반 video Object Detection 수행

  • 위에서 만든 함수 사용
video_input_path = '../../data/video/Night_Day_Chase.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/Night_Day_Chase_tensor_ssd_incep01.avi'

cap = cv2.VideoCapture(video_input_path)

codec = cv2.VideoWriter_fourcc(*'XVID')

vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
vid_fps = cap.get(cv2.CAP_PROP_FPS)
    
vid_writer = cv2.VideoWriter(video_output_path, codec, vid_fps, vid_size) 

frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print('총 Frame 갯수:', frame_cnt, 'FPS:', vid_fps )

green_color=(0, 255, 0)
red_color=(0, 0, 255)

# SSD+Inception inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/ssd_inception_v2_coco_2017_11_17/frozen_inference_graph.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    index = 0
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break

        draw_img_frame = get_tensor_detected_image(sess, img_frame, False)
        vid_writer.write(draw_img_frame)
    # end of while loop

vid_writer.release()
cap.release()  

  • SSD가 CPU에서 매우 빠르고 잘 되는 것을 확인할 수 있다.

drawing

!gsutil cp ../../data/output/Night_Day_Chase_tensor_ssd_incep01.avi gs://my_bucket_dlcv/data/output/Night_Day_Chase_tensor_ssd_incep01.avi

3. SSD+Mobilenet Video Object Detection

  • 아래 코드는 커널을 재 기동후 바로 수행해야 합니다.
  • Graph를 가져오는 것에 대해서 위에서 했던 것과 중첩되면 오류가 날 수 있다.
  • inceptionV2 과 성능차이가 없지만, 수행시간이 CPU에서 더더 빠른 것을 확인할 수 있다.

3-1 함수정의하기

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

def get_tensor_detected_image(sess, img_array, use_copied_array):
    
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    if use_copied_array:
        draw_img = img_array.copy()
    else:
        draw_img = img_array
    
    inp = cv2.resize(img_array, (300, 300))
    inp = inp[:, :, [2, 1, 0]]  # BGR2RGB
    

    start = time.time()
    # Object Detection 수행. 
    out = sess.run([sess.graph.get_tensor_by_name('num_detections:0'),
                    sess.graph.get_tensor_by_name('detection_scores:0'),
                    sess.graph.get_tensor_by_name('detection_boxes:0'),
                    sess.graph.get_tensor_by_name('detection_classes:0')],
                   feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    # Bounding Box 시각화 
    # Detect된 Object 별로 bounding box 시각화 
    num_detections = int(out[0][0])
    for i in range(num_detections):
        # class id와 object class score, bounding box정보를 추출
        classId = int(out[3][0][i])
        score = float(out[1][0][i])
        bbox = [float(v) for v in out[2][0][i]]
        if score > 0.3:
            left = bbox[1] * cols
            top = bbox[0] * rows
            right = bbox[3] * cols
            bottom = bbox[2] * rows
            # cv2의 rectangle(), putText()로 bounding box의 클래스명 시각화 
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), green_color, thickness=2)
            caption = "{}: {:.4f}".format(labels_to_names[classId], score)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, 1)
    
    print('Detection 수행시간:',round(time.time() - start, 3),"초")
    return draw_img

3-2 정의한 함수사용해서 비디오 Detection 수행하기

import numpy as np
import tensorflow as tf
import cv2
import time
import matplotlib.pyplot as plt
%matplotlib inline

video_input_path = '../../data/video/Night_Day_Chase.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/Night_Day_Chase_tensor_ssd_mobile_01.avi'

cap = cv2.VideoCapture(video_input_path)

codec = cv2.VideoWriter_fourcc(*'XVID')

vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
vid_fps = cap.get(cv2.CAP_PROP_FPS)
    
vid_writer = cv2.VideoWriter(video_output_path, codec, vid_fps, vid_size) 

frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print('총 Frame 갯수:', frame_cnt, 'FPS:', vid_fps )

green_color=(0, 255, 0)
red_color=(0, 0, 255)

# SSD+Mobilenet inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    index = 0
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break

        draw_img_frame = get_tensor_detected_image(sess, img_frame, False)
        vid_writer.write(draw_img_frame)
    # end of while loop

vid_writer.release()
cap.release()  

!gsutil cp ../../data/output/Night_Day_Chase_tensor_ssd_mobile_01.avi gs://my_bucket_dlcv/data/output/Night_Day_Chase_tensor_ssd_mobile_01.avi

4. Tensorflow Inference.pb 파일가져와 사용하기(Face Detection)

  • WiderFace 데이터세트로 Pretrained된 Tensorflow graph 모델을 다운로드 받아 이를 이용해 Face Detection 수행.

  • Github : https://github.com/yeephycho/tensorflow-face-detection

  • SSD + MobileNet기반으로 Pretrained된 모델 다운로드

  • https://github.com/yeephycho/tensorflow-face-detection/raw/master/model/frozen_inference_graph_face.pb

drawing

4-1 SSD + Mobilenet Pretrained된 모델 로딩하여 Face Detection

import numpy as np
import tensorflow as tf
import cv2
import time
import matplotlib.pyplot as plt
%matplotlib inline
        
def get_tensor_detected_image(sess, img_array, use_copied_array):
    
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    if use_copied_array:
        draw_img = img_array.copy()
    else:
        draw_img = img_array
    
    inp = cv2.resize(img_array, (300, 300))
    inp = inp[:, :, [2, 1, 0]]  # BGR2RGB
    
    start = time.time()
    # Object Detection 수행. 
    out = sess.run([sess.graph.get_tensor_by_name('num_detections:0'),
                    sess.graph.get_tensor_by_name('detection_scores:0'),
                    sess.graph.get_tensor_by_name('detection_boxes:0'),
                    sess.graph.get_tensor_by_name('detection_classes:0')],
                   feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    # Bounding Box 시각화 
    # Detect된 Object 별로 bounding box 시각화 
    num_detections = int(out[0][0])
    for i in range(num_detections):
        # class id와 object class score, bounding box정보를 추출
        classId = int(out[3][0][i])
        score = float(out[1][0][i])
        bbox = [float(v) for v in out[2][0][i]]
        if score > 0.4:
            left = bbox[1] * cols
            top = bbox[0] * rows
            right = bbox[3] * cols
            bottom = bbox[2] * rows
            # cv2의 rectangle(), putText()로 bounding box의 클래스명 시각화 
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), green_color, thickness=2)
            caption = "face: {:.4f}".format(score)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, red_color, 2)
    
    print('Detection 수행시간:',round(time.time() - start, 3),"초")
    return draw_img

#inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/frozen_inference_graph_face.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    
    # 입력 이미지 생성, Object Detection된 image 반환, 반환된 image의 BGR을 RGB로 변경 
    img = cv2.imread('../../data/image/EPL01.jpg')
    draw_img = get_tensor_detected_image(sess, img, True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

drawing

4-2 tensorflow로 SSD+ Inception 기반 video Object Detection 수행

from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/InfiniteWar01.mp4')
video_input_path = '../../data/video/InfiniteWar01.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/InfiniteWar01_ssd.avi'

cap = cv2.VideoCapture(video_input_path)

codec = cv2.VideoWriter_fourcc(*'XVID')

vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
vid_fps = cap.get(cv2.CAP_PROP_FPS)
    
vid_writer = cv2.VideoWriter(video_output_path, codec, vid_fps, vid_size) 

frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print('총 Frame 갯수:', frame_cnt, 'FPS:', vid_fps )

green_color=(0, 255, 0)
red_color=(0, 0, 255)

# SSD+Inception inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/frozen_inference_graph_face.pb', 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    
with tf.Session() as sess:
    # Session 시작하고 inference graph 모델 로딩 
    sess.graph.as_default()
    tf.import_graph_def(graph_def, name='')
    index = 0
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break

        draw_img_frame = get_tensor_detected_image(sess, img_frame, False)
        vid_writer.write(draw_img_frame)
    # end of while loop

vid_writer.release()
cap.release()  

Detection 수행시간: 0.307 초
Detection 수행시간: 0.294 초
Detection 수행시간: 0.296 초
Detection 수행시간: 0.304 초
Detection 수행시간: 0.296 초
Detection 수행시간: 0.294 초
Detection 수행시간: 0.293 초
Detection 수행시간: 0.31 초
더 이상 처리할 frame이 없습니다.
!gsutil cp ../../data/output/InfiniteWar01_ssd.avi gs://my_bucket_dlcv/data/output/InfiniteWar01_ssd.avi

drawing

【Python-Module】 SSD 수행하기 - OpenCV DNN 모듈

이전 Post를 통해서 SSD의 이론에 대해서 공부했다. OpenCV DNN 모델을 사용해서 SSD를 수행해보자.

OpenCV DNN 모듈을 사용해서 SSD 수행하기
/DLCV/Detection/SSD/OpenCV_SSD_이미지와_영상_Detection.ipynb 참조
OpenCV 사용과정 요약은 이전 Post 참조

1. OpenCV DNN 패키지를 이용하여 SSD 수행하기

  • Tensorflow 에서 Pretrained 된 모델 파일을 OpenCV에서 로드하여 이미지와 영상에 대한 Object Detection 수행.
### 입력 이미지로 사용될 이미지 보기
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

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

print('image shape:', img.shape)
plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
image shape: (330, 499, 3)

drawing

1-1 Tensorflow에서 Pretrained 된 Inference모델(Frozen graph)와 환경파일을 다운로드 받은 후 이를 이용해 OpenCV에서 Inference 모델 생성

  • https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API 에 다운로드 URL 있음.
  • Inception-SSD v2 2017_11_17 이것 사용할 예정
  • pretrained 모델은 http://download.tensorflow.org/models/object_detection/ssd_inception_v2_coco_2017_11_17.tar.gz 에서 다운로드 후 압축 해제
  • pretrained 모델을 위한 환경 파일은 https://github.com/opencv/opencv_extra/blob/master/testdata/dnn/ssd_inception_v2_coco_2017_11_17.pbtxt 에서 다운로드
  • download된 모델 파일과 config 파일을 인자로 하여 inference 모델을 DNN에서 로딩함.
# %cd /home/sb020518/DLCV/Detection/ssd
# !mkdir pretrained 
# %cd ./pretrained
# !wget http://download.tensorflow.org/models/object_detection/ssd_inception_v2_coco_2017_11_17.tar.gz
# !wget https://raw.githubusercontent.com/opencv/opencv_extra/master/testdata/dnn/ssd_inception_v2_coco_2017_11_17.pbtxt
# !tar -xvf ssd*.tar.gz
# !mv ssd_inception_v2_coco_2017_11_17.pbtxt graph.pbtxt
# cd ../
!pwd
!ls pretrained/ssd_inception_v2_coco_2017_11_17
/home/sb020518/DLCV/Detection/ssd
checkpoint		   model.ckpt.data-00000-of-00001  model.ckpt.meta
frozen_inference_graph.pb  model.ckpt.index		   saved_model
cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/ssd_inception_v2_coco_2017_11_17/frozen_inference_graph.pb', 
                                     './pretrained/graph.pbtxt')
  • coco 데이터 세트의 클래스id별 클래스명 지정.
  • 왜 90번 까지 하는지는 여기 참조
  • 90번까지 해야할지 91번까지 해야할지는 실험적으로 확인하는 수 밖에 없다.
labels_to_names = {1:'person',2:'bicycle',3:'car',4:'motorcycle',5:'airplane',6:'bus',7:'train',8:'truck',9:'boat',10:'traffic light',
                    11:'fire hydrant',12:'street sign',13:'stop sign',14:'parking meter',15:'bench',16:'bird',17:'cat',18:'dog',19:'horse',20:'sheep',
                    21:'cow',22:'elephant',23:'bear',24:'zebra',25:'giraffe',26:'hat',27:'backpack',28:'umbrella',29:'shoe',30:'eye glasses',
                    31:'handbag',32:'tie',33:'suitcase',34:'frisbee',35:'skis',36:'snowboard',37:'sports ball',38:'kite',39:'baseball bat',40:'baseball glove',
                    41:'skateboard',42:'surfboard',43:'tennis racket',44:'bottle',45:'plate',46:'wine glass',47:'cup',48:'fork',49:'knife',50:'spoon',
                    51:'bowl',52:'banana',53:'apple',54:'sandwich',55:'orange',56:'broccoli',57:'carrot',58:'hot dog',59:'pizza',60:'donut',
                    61:'cake',62:'chair',63:'couch',64:'potted plant',65:'bed',66:'mirror',67:'dining table',68:'window',69:'desk',70:'toilet',
                    71:'door',72:'tv',73:'laptop',74:'mouse',75:'remote',76:'keyboard',77:'cell phone',78:'microwave',79:'oven',80:'toaster',
                    81:'sink',82:'refrigerator',83:'blender',84:'book',85:'clock',86:'vase',87:'scissors',88:'teddy bear',89:'hair drier',90:'toothbrush',
                    91:'hair brush'}

1-2 이미지를 preprocessing 수행하여 Network에 입력하고 Object Detection 수행 후 결과를 이미지에 시각화

# 원본 이미지 (633, 806)를 네트웍에 입력시에는 (300, 300)로 resize 함. 
# 이후 결과가 출력되면 resize된 이미지 기반으로 bounding box 위치가 예측 되므로 이를 다시 원복하기 위해 원본 이미지 shape정보 필요
rows = img.shape[0]
cols = img.shape[1]
# cv2의 rectangle()은 인자로 들어온 이미지 배열에 직접 사각형을 업데이트 하므로 그림 표현을 위한 별도의 이미지 배열 생성. 
draw_img = img.copy()

# 원본 이미지 배열을 사이즈 (300, 300)으로, BGR을 RGB로 변환하여 배열 입력
cv_net.setInput(cv2.dnn.blobFromImage(img,  size=(300, 300), swapRB=True, crop=False))
# Object Detection 수행하여 결과를 cv_out으로 반환 
cv_out = cv_net.forward()
print(cv_out.shape)

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

# detected 된 object들을 iteration 하면서 정보 추출
for detection in cv_out[0,0,:,:]:
    score = float(detection[2])
    class_id = int(detection[1])
    # detected된 object들의 score가 0.3 이상만 추출
    if score > 0.3:
        # detected된 object들은 image 크기가 (300, 300)으로 scale된 기준으로 예측되었으므로 다시 원본 이미지 비율로 계산
        left = detection[3] * cols
        top = detection[4] * rows
        right = detection[5] * cols
        bottom = detection[6] * rows
        # labels_to_names 딕셔너리로 class_id값을 클래스명으로 변경. opencv에서는 class_id + 1로 매핑해야함.
        caption = "{}: {:.4f}".format(labels_to_names[class_id], score)
        
        #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
        cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), color=green_color, thickness=2)
        cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, red_color, 2)
        print(caption, class_id)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
(1, 1, 100, 7)

drawing

1-3 위에서 한 작업 그대로 함수로 def

import time

def get_detected_img(cv_net, img_array, score_threshold, use_copied_array=True, is_print=True):
    
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    
    draw_img = None
    if use_copied_array:
        draw_img = img_array.copy()
        #draw_img = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)
    else:
        draw_img = img_array
    
    cv_net.setInput(cv2.dnn.blobFromImage(img_array, size=(300, 300), swapRB=True, crop=False))
    
    start = time.time()
    cv_out = cv_net.forward()
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)

    # detected 된 object들을 iteration 하면서 정보 추출
    for detection in cv_out[0,0,:,:]:
        score = float(detection[2])
        class_id = int(detection[1])
        # detected된 object들의 score가 0.4 이상만 추출
        if score > score_threshold:
            # detected된 object들은 image 크기가 (300, 300)으로 scale된 기준으로 예측되었으므로 다시 원본 이미지 비율로 계산
            left = detection[3] * cols
            top = detection[4] * rows
            right = detection[5] * cols
            bottom = detection[6] * rows
            # labels_to_names 딕셔너리로 class_id값을 클래스명으로 변경. opencv에서는 class_id + 1로 매핑해야함.
            caption = "{}: {:.4f}".format(labels_to_names[class_id], score)

            #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), color=green_color, thickness=2)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, red_color, 2)
    if is_print:
        print('Detection 수행시간:',round(time.time() - start, 2),"초")

    return draw_img

drawing

# 위에서 def 한 함수 사용해서 Detection 수행하기.
# 0.23초 지린다. 
img = cv2.imread('../../data/image/EPL01.jpg')

#coco dataset 클래스명 매핑

# tensorflow inference 모델 로딩
cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/ssd_inception_v2_coco_2017_11_17/frozen_inference_graph.pb', 
                                     './pretrained/graph.pbtxt')
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net, img, score_threshold=0.4, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
Detection 수행시간: 0.27 초

2. Video Object Detection 수행

원본 영상 보기

from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/John_Wick_small.mp4')
  • VideoCapture와 VideoWriter 설정하기
  • VideoCapture를 이용하여 Video를 frame별로 capture 할 수 있도록 설정
  • VideoCapture의 속성을 이용하여 Video Frame의 크기 및 FPS 설정.
  • VideoWriter를 위한 인코딩 코덱 설정 및 영상 write를 위한 설정
  • 총 Frame 별로 iteration 하면서 Object Detection 수행. 개별 frame별로 단일 이미지 Object Detection과 유사
  • video detection 전용 함수 생성.
def do_detected_video(cv_net, input_path, output_path, score_threshold, is_print):
    
    cap = cv2.VideoCapture(input_path)

    codec = cv2.VideoWriter_fourcc(*'XVID')

    vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    vid_fps = cap.get(cv2.CAP_PROP_FPS)

    vid_writer = cv2.VideoWriter(output_path, codec, vid_fps, vid_size) 

    frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print('총 Frame 갯수:', frame_cnt, )

    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break
        
        returned_frame = get_detected_img(cv_net, img_frame, score_threshold=score_threshold, use_copied_array=True, is_print=True)
        vid_writer.write(returned_frame)
    # end of while loop

    vid_writer.release()
    cap.release()
do_detected_video(cv_net, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_small_ssd01.avi', 0.4, True)
!gsutil cp ../../data/output/John_Wick_small_ssd01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_ssd01.avi

3. SSD+Mobilenet으로 Object Detection 수행.

  • https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API 에 다운로드 URL 있음.
  • weight파일은 http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz 에서 다운로드

  • 수행결과 : 위에서는 SSD+inceptionV2를 사용했다. 확실히 MobileNet의 속도가 훨씬 빠른것을 확인할 수 있다.
# !mkdir pretrained; cd pretrained
# !wget  http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz
# !wget https://raw.githubusercontent.com/opencv/opencv_extra/master/testdata/dnn/ssd_mobilenet_v2_coco_2018_03_29.pbtxt
# cd ssd_mobilenet_v2_coco_2018_03_29; mv ssd_mobilenet_v2_coco_2018_03_29.pbtxt graph.pbtxt
cv_net_mobile = cv2.dnn.readNetFromTensorflow('./pretrained/ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb', 
                                     './pretrained/ssd_mobilenet_v2_coco_2018_03_29/graph.pbtxt')
#### 영상 Detection
do_detected_video(cv_net_mobile, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_small_ssd_mobile01.avi', 0.2, True)
!gsutil cp ../../data/output/John_Wick_small_ssd_mobile01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_ssd_mobile01.avi
#### Image Detection
img = cv2.imread('../../data/image/avengers.jpg')

#coco dataset 클래스명 매핑

cv_net_mobile = cv2.dnn.readNetFromTensorflow('./pretrained/ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb', 
                                     './pretrained/ssd_mobilenet_v2_coco_2018_03_29/graph.pbtxt')
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net_mobile, img, score_threshold=0.4, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

【Paper】 SSD(SIngle Shot Multibox Detector)

One Stage Detector의 시작이라고 할 수 있는 Yolo, SSD에 대해서 다시 공부하고 정리해본다.

SSD(SIngle Shot Multibox Detector)

1. Objecet Detection History 그리고 SSD

drawing

  • Region Proposal + Detection 을 한방에 하는 One Stage detector
  • Yolo가 처음 나와서 Faster RCNN보다 4배 속도는 빠르지만 정확도는 떨어졌다.
  • SSD는 Faster RCNN보다 mAP보다 좋고, Yolo보다 빠른 object detection 모델이다. 한때 SOTA
  • 그 후 Yolo에서 SSD 기법을 차용하며 지금은 version 4 까지 나와서 속도와 정확도 모두를 가진 모델이 되었다.

  • SSD Network의 핵심
    • BackBone을 통과시키고 점점 featrue map resolution을 낮춰가면서 그 모든 Feature map에 대해서 Detection + Box regressoin을 수행한다.
    • 핵심은 2가지이다
      1. Multi Scale Feature Layer
      2. Default (Anchor) Box : 수행 성능과 속도의 2마리 토끼를 잡게 해준 포인트!

2. Multi Scale Feature Layer

  • 과거 Multi Scale Detector = Image Pyramid를 이용하여 Sliding Windows 기법을 수행한다.

drawing

  • SSD는 이 Image Pyramid 기법을 차용해서 Object Detection을 수행한다. Feature Map을 convNet을 통과시켜 크기를 줄이고 줄여가며 각각 Detection을 수행한다.
  • 아래의 이미지에서 32x32에서는 작은 object, 4x4에서는 큰 object를 Detect 할 수 있다.

drawing

3. Default (Anchor) Box

  • Faster RCNN의 RPN Network에 대해서 다시 생각해보자. Anchor 박스를 잘 활용해서 좋은 Detection을 수행할 수 있었다. 하지만 RPN에서도 그리고 Classification의 뒷단(Object Detection)에서도 Bounding Box Regression이 2번 수행되는 것은 약간 불 필요하지 않나? 라는 생각을 할 수 있다.

  • Anchor Box 개념은 One stage Detector에서도 좋은 성능 향상을 가져와 주었다.

  • Anchor Box 개념을 RPN 뿐만 아니라 뒷단의 Object Detection에서도 수행하면 좋지 않을까? 라는 생각을 하게 되었다. 그래서 Yolo와 SSD에서 Default Anchor Box를 사용하게 되었다.

  • Anchor 박스를 통해서 학습해야 하는 것.

    1. Anchor box와 겹치는 Feature 맵 영역의 obeject Class 분류
    2. GT Box 위치를 예측할 수 있도록 수정된 좌표

drawing

  • 위의 사진에서 1 ~ 6 번까지 흐름 순서대로 따라가며 공부하기
  • 확실히 이해가 안되면 이번 Faster RCNN - Anchor box POST 확인해서 먼저 공부하고 다시 위의 사진 공부하기

4. SSD architecture 그리고 Default Box

drawing

  • SSD input은 512x512 or 300x300 이다.
  • Object Detion 단으로 넘어가는 Feature Map의 크기가 38x38 19x19 10x10 5x5 3x3 1x1 Feature map 결과들이다.
  • SSD 에서 논문에서는 Dafault Box라는 용어를 사용한다. Anchor Box와 똑같은 것이다.

  • 위 사진의 그림 중 윗 중간 부분에 Classifier : Conv, 3x3x(4x(classes+4)) 라고 적혀 있는 것을 확인할 수 있다. 이것에 대해서 자세하게 생각해보면 다음과 같다.
    • 3x3x(4x(classes+4)) : 3x3 필터를 사용한다. 위,위 사진을 확인하면 Locallization과 classification에서 3x3 필터를 사용하는 것처럼.
    • 3x3x(4x(classes+4)) : 4개의 Anchor 박스
    • 3x3x(4x(classes+4)) : 각 class에 대한 Softmax 값(= confidence 값)(20+1(classes And background)), Bounding Box 좌표값.

    • 3x3x(4x(classes+4)) 이것은 Kernel의 갯수이자, Detection 결과 Output의 Depth 값이다. (ouput 결과의 width hight는 38x38 19x19 10x10 5x5 3x3 1x1 일 것이다.) 당연히 3x3 Kernel의 Depth는 각 Feature Map(38x38 19x19 10x10 5x5 3x3 1x1)의 Depth값을 따르게 된다.
  • 그렇게 해서 만들어지는 Box의 갯수는 다음과 같고 이것을 NMS 처리를 한다.

drawing

5. Matching 전략과 Loss 함수

  • Matching 전략 : Ground True Bounding box와 겹치는 IOU가 0.5이상인 Anchor Box들을 찾는다. 이 Anchor Box를 Matching 된 Anchor Box라고 한다. 그런 Matching Anchor Box 만을 이용해서 Classification과 Bounding Box Regression에 대한 학습을 해나간다.

  • Loss 함수 :

    • Multi Tast Loss를 사용했다. Faster RCNN의 Loss와 동일하다.
    • N matching 된 Default Box의 갯수로써, 정규화에 사용한다.

drawing

6. Design Choice 별 Performance (아래 이미지 참조)

  • 1:2 rate의 box를 사용했을 때 vs 1:3 rate의 box를 사용했을 때

  • atrous : dilated convolution

  • Data Augmentation을 했을 때의 역할이 매우 컸다.

  • Yolo에서의 작은 Object Detection을 못하는 문제를 해결하기 위해 노력했다.
    이 문제를 SSD에서는 Data Augmentation을 이용해서 해결하려고 엄청 노력을 했다.

  • 논문에서 계속 나오는 용어

    • SSD300 : 300x300 이미지 Input
    • SSD512 : 512x512 이미지 Input

drawing

【Python-Module】 Faster RCNN 수행하기 - OpenCV DNN 모듈

이전 Post를 통해서 Faster RCNN이론에 대해서 공부했다. OpenCV DNN 모델을 사용해서 Faster RCNN을 수행해보자.

OpenCV DNN 모듈을 사용해서 Detection 수행하기
/DLCV/Detection/fast_rcnn/OpenCV_FasterRCNN_ObjectDetection.ipynb 참조

0. OpenCV 모듈 과정 요약

  1. cs_net = cv2.dnn.readNetFromFramwork(‘inference 가중치 파일’,’config파일’) 를 사용해서
  2. img_drwa = cv2.dnn.blobFromImage(cv2로 read한 이미지, 변환 형식1 , 변환 형식2)
  3. cv_out = cv_net.forward()
  4. for detection in cv_out[0,0,:,:] 으로 접근해서 output정보 가져오기.

1. OpenCV DNN 패키지를 이용하여 Faster R-CNN

  • Tensorflow 에서 Pretrained 된 모델 파일을 OpenCV에서 로드하여 이미지와 영상에 대한 Object Detection 수행.

1-0 입력 이미지로 사용될 이미지 보기

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

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

print('image shape:', img.shape)
plt.figure(figsize=(12, 12))
# plt.imshow(img_rgb)
image shape: (633, 806, 3)
<Figure size 864x864 with 0 Axes>
<Figure size 864x864 with 0 Axes>

1-1. Tensorflow에서 Pretrained 된 Inference모델(Frozen graph)와 환경파일을 다운로드 받은 후 이를 이용해 OpenCV에서 Inference 모델 생성

  • https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API 에 다운로드 URL 있음.
  • Faster-RCNN ResNet-50 2018_01_28 사용할 예정
  • pretrained 모델은 http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet50_coco_2018_01_28.tar.gz 에서 다운로드 후 압축 해제
  • pretrained 모델을 위한 환경 파일은 https://github.com/opencv/opencv_extra/blob/master/testdata/dnn/faster_rcnn_resnet50_coco_2018_01_28.pbtxt 에서 다운로드
  • download된 모델 파일과 config 파일을 인자로 하여 inference 모델을 DNN에서 로딩함.
%cd /home/sb020518/DLCV/Detection/fast_rcnn
!mkdir pretrained 
%cd ./pretrained
#!wget http://download.tensorflow.org/models/object_detection/faster_rcnn_resnet50_coco_2018_01_28.tar.gz
#!wget https://raw.githubusercontent.com/opencv/opencv_extra/master/testdata/dnn/faster_rcnn_resnet50_coco_2018_01_28.pbtxt
# !tar -xvf faster*.tar.gz
!mv faster_rcnn_resnet50_coco_2018_01_28.pbtxt graph.pbtxt
%cd /home/sb020518/DLCV/Detection/fast_rcnn

drawing

  • .pdtxt 파일 다운받기 : github raw 파일 전체 복붙을 해서 다운받기 여기에서 raw클릭해서 나오는 주소를 wget의 입력으로 넣어주어야 한다.

  • .pdtxt파일 : OpenCV를 위한 Config파일이다.
  • .pb 파일 : tensorflow inferecne를 위한 가중치 파일

1-2 dnn에서 readNetFromTensorflow()로 tensorflow inference 모델을 로딩

cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/faster_rcnn_resnet50_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/graph.pbtxt')
  • 유의 사항 : object ouput은 class index를 return한다. object 이름을 그대로 return해주지 않는다.
  • COCO는 91번까지의 ID 번호 object가 있는데, 11개가 COCO2017 에서는 사용하지 않아서 80개의 Object name이 존재한다.

  • coco 데이터 세트의 클래스id별 클래스명 지정해 주어야 한다.
  • Class ID가 0 ~ 90 , 0 ~ 91 , 0~79 로 다양하게 사용된다. 이것은 모델별로, 프레임워크별로 다 다르다… 아래와 같이. 아래의 표를 파악하는 방법은 실험적으로 알아내는 방법 밖에 없다.
  • 여기서는 OpenCV tensorflow FasterRCNN이므로 0 ~ 90을 사용한다

drawing

# OpenCV Yolo용 
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' }
labels_to_names_0 = {0:'person',1:'bicycle',2:'car',3:'motorcycle',4:'airplane',5:'bus',6:'train',7:'truck',8:'boat',9:'traffic light',
                    10:'fire hydrant',11:'street sign',12:'stop sign',13:'parking meter',14:'bench',15:'bird',16:'cat',17:'dog',18:'horse',19:'sheep',
                    20:'cow',21:'elephant',22:'bear',23:'zebra',24:'giraffe',25:'hat',26:'backpack',27:'umbrella',28:'shoe',29:'eye glasses',
                    30:'handbag',31:'tie',32:'suitcase',33:'frisbee',34:'skis',35:'snowboard',36:'sports ball',37:'kite',38:'baseball bat',39:'baseball glove',
                    40:'skateboard',41:'surfboard',42:'tennis racket',43:'bottle',44:'plate',45:'wine glass',46:'cup',47:'fork',48:'knife',49:'spoon',
                    50:'bowl',51:'banana',52:'apple',53:'sandwich',54:'orange',55:'broccoli',56:'carrot',57:'hot dog',58:'pizza',59:'donut',
                    60:'cake',61:'chair',62:'couch',63:'potted plant',64:'bed',65:'mirror',66:'dining table',67:'window',68:'desk',69:'toilet',
                    70:'door',71:'tv',72:'laptop',73:'mouse',74:'remote',75:'keyboard',76:'cell phone',77:'microwave',78:'oven',79:'toaster',
                    80:'sink',81:'refrigerator',82:'blender',83:'book',84:'clock',85:'vase',86:'scissors',87:'teddy bear',88:'hair drier',89:'toothbrush',
                    90:'hair brush'}
labels_to_names = {1:'person',2:'bicycle',3:'car',4:'motorcycle',5:'airplane',6:'bus',7:'train',8:'truck',9:'boat',10:'traffic light',
                    11:'fire hydrant',12:'street sign',13:'stop sign',14:'parking meter',15:'bench',16:'bird',17:'cat',18:'dog',19:'horse',20:'sheep',
                    21:'cow',22:'elephant',23:'bear',24:'zebra',25:'giraffe',26:'hat',27:'backpack',28:'umbrella',29:'shoe',30:'eye glasses',
                    31:'handbag',32:'tie',33:'suitcase',34:'frisbee',35:'skis',36:'snowboard',37:'sports ball',38:'kite',39:'baseball bat',40:'baseball glove',
                    41:'skateboard',42:'surfboard',43:'tennis racket',44:'bottle',45:'plate',46:'wine glass',47:'cup',48:'fork',49:'knife',50:'spoon',
                    51:'bowl',52:'banana',53:'apple',54:'sandwich',55:'orange',56:'broccoli',57:'carrot',58:'hot dog',59:'pizza',60:'donut',
                    61:'cake',62:'chair',63:'couch',64:'potted plant',65:'bed',66:'mirror',67:'dining table',68:'window',69:'desk',70:'toilet',
                    71:'door',72:'tv',73:'laptop',74:'mouse',75:'remote',76:'keyboard',77:'cell phone',78:'microwave',79:'oven',80:'toaster',
                    81:'sink',82:'refrigerator',83:'blender',84:'book',85:'clock',86:'vase',87:'scissors',88:'teddy bear',89:'hair drier',90:'toothbrush',
                    91:'hair brush'}

1-3 이미지를 preprocessing 수행하여 Network에 입력하고 Object Detection 수행 후 결과를 이미지에 시각화

# 원본 이미지가 Faster RCNN기반 네트웍으로 입력 시 resize됨. 
# resize된 이미지 기반으로 bounding box 위치가 예측 되므로 이를 다시 원복하기 위해 원본 이미지 shape정보 필요
img = cv2.imread('../../data/image/beatles01.jpg')
rows = img.shape[0]
cols = img.shape[1]
# cv2의 rectangle()은 인자로 들어온 이미지 배열에 직접 사각형을 업데이트 하므로 그림 표현을 위한 별도의 이미지 배열 생성. 
draw_img = img.copy()

# 원본 이미지 배열 BGR을 RGB로 변환하여 배열 입력. 
# Tensorflow Faster RCNN은 size를 고정할 필요가 없는 것으로 추정. 따라서 size = (300, 300)과 같이 파라메터 설정 안해줌
# hblobFromImage 매개변수 정보는 ttps://www.pyimagesearch.com/2017/11/06/deep-learning-opencvs-blobfromimage-works/
# 신경망에 이미지를 Inference 시킬 것이라는 것을 명시
cv_net.setInput(cv2.dnn.blobFromImage(img, swapRB=True, crop=False)) 

# Object Detection 수행하여 결과를 cvOut으로 반환 
# 이미지 Inference 수행                 
cv_out = cv_net.forward()
print('cvout type : ', type(cv_out))
print('cvout shape : ', cv_out.shape)
# cv_out에서 0,0,100,7 에서 100은 object의 수. 7은 하나의 object 정보
# 0,0은 Inference도 배치로 할 수 있다. 이미지를 여러개를 넣는다면, 한꺼번에 detection값이 나올때를 대비해서 4차원 cv_out이 나오게 한다. 

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

# detected 된 object들을 iteration 하면서 정보 추출
# cv_out[0,0,:,:]은 (100 x 7) 배열. detection에는  cv_out[0,0,:,:]의 하나의 행. 즉 7개의 원소가 들어간다. 
for detection in cv_out[0,0,:,:]:  
    score = float(detection[2])  # confidence 
    class_id = int(detection[1])
    # detected된 object들의 score가 0.5 이상만 추출
    if score > 0.7:
        # detected된 object들은 scale된 기준으로 예측되었으므로 다시 원본 이미지 비율로 계산
        # 아래의 값은 좌상단. 우하단. 의 좌표값이다. 
        left = detection[3] * cols  # detection[3],[4],[5],[6] -> 0~1 값이다. 
        top = detection[4] * rows
        right = detection[5] * cols
        bottom = detection[6] * rows
        # labels_to_names_seq 딕셔너리로 class_id값을 클래스명으로 변경.
        caption = "{}: {:.4f}".format(labels_to_names_0[class_id], score)
        print(caption)
        #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
        # cv2를 이용해서 상자를 그리면 무조건 정수값을 매개변수로 넣어줘야 한다. 실수를 사용하고 싶다면 matplot이용할것
        # putText : https://www.geeksforgeeks.org/python-opencv-cv2-puttext-method/
        cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), color=green_color, thickness=2) 
        cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, 1)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
cvout type :  <class 'numpy.ndarray'>
cvout shape :  (1, 1, 100, 7)
person: 0.9998
person: 0.9996
person: 0.9993
person: 0.9970
person: 0.8995
car: 0.8922
car: 0.7602
car: 0.7415

drawing

1-4 위에서 했던 작업을 def 함수로 만들어보자!

  • 추후에 비디오에서도 사용할 예정
import time

def get_detected_img(cv_net, img_array, score_threshold, use_copied_array=True, is_print=True):
    
    rows = img_array.shape[0]
    cols = img_array.shape[1]
    
    draw_img = None
    if use_copied_array:
        draw_img = img_array.copy()
    else:
        draw_img = img_array
    
    cv_net.setInput(cv2.dnn.blobFromImage(img_array, swapRB=True, crop=False))
    
    start = time.time()
    cv_out = cv_net.forward()
    
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)

    # detected 된 object들을 iteration 하면서 정보 추출
    for detection in cv_out[0,0,:,:]:
        score = float(detection[2])
        class_id = int(detection[1])
        # detected된 object들의 score가 함수 인자로 들어온 score_threshold 이상만 추출
        if score > score_threshold:
            # detected된 object들은 scale된 기준으로 예측되었으므로 다시 원본 이미지 비율로 계산
            left = detection[3] * cols
            top = detection[4] * rows
            right = detection[5] * cols
            bottom = detection[6] * rows
            # labels_to_names 딕셔너리로 class_id값을 클래스명으로 변경. opencv에서는 class_id + 1로 매핑해야함.
            caption = "{}: {:.4f}".format(labels_to_names_0[class_id], score)
            print(caption)
            #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
            cv2.rectangle(draw_img, (int(left), int(top)), (int(right), int(bottom)), color=green_color, thickness=2)
            cv2.putText(draw_img, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, 1)
    if is_print:
        print('Detection 수행시간:',round(time.time() - start, 2),"초")

    return draw_img
## 방금 위에서 만든 함수를 사용해서 다시 추론해보자. 
# image 로드 
img = cv2.imread('../../data/image/beatles01.jpg')
print('image shape:', img.shape)

# tensorflow inference 모델 로딩
cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/faster_rcnn_resnet50_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/graph.pbtxt')
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net, img, score_threshold=0.6, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

drawing

# image 로드 
img = cv2.imread('../../data/image/baseball01.jpg')
print('image shape:', img.shape)

# tensorflow inference 모델 로딩
cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/faster_rcnn_resnet50_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/graph.pbtxt')
# Object Detetion 수행 후 시각화 
draw_img = get_detected_img(cv_net, img, score_threshold=0.7, use_copied_array=True, is_print=True)

img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)

plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)

drawing

2. Video Object Detection 수행

2-1 원본 영상 보기

# Video API는 mp4에서만 사용가능함
from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/John_Wick_small.mp4')

2-2 VideoCapture와 VideoWriter 설정하기

  • VideoCapture를 이용하여 Video를 frame별로 capture 할 수 있도록 설정
  • VideoCapture의 속성을 이용하여 Video Frame의 크기 및 FPS 설정.
  • VideoWriter를 위한 인코딩 코덱 설정 및 영상 write를 위한 설정
video_input_path = '../../data/video/John_Wick_small.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/John_Wick_small_cv01.avi'

cap = cv2.VideoCapture(video_input_path)

codec = cv2.VideoWriter_fourcc(*'XVID')

vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) 
vid_fps = cap.get(cv2.CAP_PROP_FPS)  # 프레임 속도 값. 동영상의 1초 프레임 갯수
    
vid_writer = cv2.VideoWriter(video_output_path, codec, vid_fps, vid_size) 

frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print('총 Frame 갯수:', frame_cnt)
총 Frame 갯수: 58

2-3 총 Frame 별로 iteration 하면서 Object Detection 수행.

  • 개별 frame별로 위에서 한 단일 이미지 Object Detection을 수행해서 vid_writer에 프래임을 차곡차곡 쌓음
  • 여기서는 위에서 만든 함수 사용 하지 않음
# bounding box의 테두리와 caption 글자색 지정
green_color=(0, 255, 0)
red_color=(0, 0, 255)

while True:

    hasFrame, img_frame = cap.read()
    if not hasFrame:
        print('더 이상 처리할 frame이 없습니다.')
        break

    rows = img_frame.shape[0]
    cols = img_frame.shape[1]
    # 원본 이미지 배열 BGR을 RGB로 변환하여 배열 입력
    cv_net.setInput(cv2.dnn.blobFromImage(img_frame,  swapRB=True, crop=False))
    
    start= time.time()
    # Object Detection 수행하여 결과를 cv_out으로 반환 
    cv_out = cv_net.forward()
    frame_index = 0
    # detected 된 object들을 iteration 하면서 정보 추출
    for detection in cv_out[0,0,:,:]:
        score = float(detection[2])
        class_id = int(detection[1])
        # detected된 object들의 score가 0.5 이상만 추출
        if score > 0.5:
            # detected된 object들은 scale된 기준으로 예측되었으므로 다시 원본 이미지 비율로 계산
            left = detection[3] * cols
            top = detection[4] * rows
            right = detection[5] * cols
            bottom = detection[6] * rows
            # labels_to_names_0딕셔너리로 class_id값을 클래스명으로 변경.
            caption = "{}: {:.4f}".format(labels_to_names_0[class_id], score)
            #print(class_id, caption)
            #cv2.rectangle()은 인자로 들어온 draw_img에 사각형을 그림. 위치 인자는 반드시 정수형.
            cv2.rectangle(img_frame, (int(left), int(top)), (int(right), int(bottom)), color=green_color, thickness=2)
            cv2.putText(img_frame, caption, (int(left), int(top - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, red_color, 1)
    print('Detection 수행 시간:', round(time.time()-start, 2),'초')
    vid_writer.write(img_frame)
# end of while loop

vid_writer.release()
cap.release()   
Detection 수행 시간: 5.24 초
Detection 수행 시간: 5.04 초
Detection 수행 시간: 5.02 초
Detection 수행 시간: 5.01 초
Detection 수행 시간: 5.03 초
Detection 수행 시간: 5.0 초
더 이상 처리할 frame이 없습니다.

drawing

# Google Cloud Platform의 Object Storage에 동영상을 저장 후 Google Cloud 에 접속해서 다운로드
# 혹은 지금의 Jupyter 환경에서 다운로드 수행
!gsutil cp ../../data/output/John_Wick_small_cv01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_cv01.avi

2-4 위에서 만든 함수를 사용해서 ,video detection 전용 함수 생성.

def do_detected_video(cv_net, input_path, output_path, score_threshold, is_print):
    
    cap = cv2.VideoCapture(input_path)

    codec = cv2.VideoWriter_fourcc(*'XVID')

    vid_size = (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    vid_fps = cap.get(cv2.CAP_PROP_FPS)

    vid_writer = cv2.VideoWriter(output_path, codec, vid_fps, vid_size) 

    frame_cnt = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print('총 Frame 갯수:', frame_cnt)

    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    while True:
        hasFrame, img_frame = cap.read()
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break
        
        img_frame = get_detected_img(cv_net, img_frame, score_threshold=score_threshold, use_copied_array=False, is_print=is_print)
        
        vid_writer.write(img_frame)
    # end of while loop

    vid_writer.release()
    cap.release()
do_detected_video(cv_net, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_small_02.avi', 0.2, True)
!gsutil cp ../../data/output/John_Wick_small_02.avi gs://my_bucket_dlcv/data/output/John_Wick_small_02.avi

【Paper】 Faster RCNN 개념 정리 + OpenCV DNN 모듈 기본

Faster RCNN 개념을 다시 상기하고 정리해보고, 햇갈렸던 내용을 다시 공부해볼 예정이다.

Faster RCNN 개념, OpenCV Detection 구현

1. Faster RCNN

  • Faster RCNN = RPN(Region Proposal Network) + Fast RCNN
  • Region Proposal도 Network를 이용해서 찾는다.

drawing

  • ROI Pooing은 동일하다. (Mask RCNN : Align ROI Pooling)
  • RPN 에서도 Classification, Regression 모두가 들어가 있다. Classification는 객체의 유무만을 판단하고, Regression은 각 그리드 위치에 따른 객체가 있을 법한 위치를 뽑아준다. 이곳의 목표는 Object 유무와 위치를 대강 뽑아주는 것이다.
  • 위에서 대강 뽑은 Region Proposal에 대헤서 이후 끝단에 Classification, Regression을 해준다.

1-1 RPN과 Anchor Box

RPN에서 주어진 입력 Pixel에 대해서 Selective Search 정도의 혹은 그 이상의 Region Proposal을 해주어야 한다. 다시 말해서 RPN 입장에서 우리가 가진 것은 데이터(Ground True Bounding Box)와 Backbone Network를 통과해서 나온 Feature Map 뿐이다. 이 2가지를 어떻게 이용해서 Region Proposal을 수행할 수 있을까?

  • Feature Map에 Anchor Box를 놓아두고 그곳에 Object가 있는지 없는지, Object의 위치는 좀 더 어디에 위치한지 알아본다.

  • Anchor Box는 9개로 구성되어 있다. (128x128 256x256 512x512) X (1:1 1:2 2:1)

drawing

  • 위 이미지에 대해서 오른쪽 Feature Map의 하나의 1x1x512 pixel 값은 왼쪽 이미지의 16x16x3 pixel이 가지는 특징이 무엇인지에 대한 정보를 가지고 있다고 할 수 있다. (대충 그렇다는 것이다. 물론 Conv Layer를 통과하면서 Receptive Feild가 어땠는지는 다시 고려해봐야 할 것 이다.)

RPN의 구조

drawing

  • 참고로 RPN을 통과해서 나온는 Proposal Region x y w h 는 대강 추천해주는 Region Proposal 결과이다.

  • RPN은 간단하다. Featrue Map에 (1 padding + 3x3 conv) + (1x1 conv)를 하여 Classification, Box Regression 정보를 얻을 수 있다.

  • Softmax Classification에서 1 x 1 x 512 필터 하나가 하나의 엥커에 의해서 판단되는 결과(객체 유vs무)가 나와야 한다. 이 객체 유무의 값에서 ‘유’값(0~1값이) RPN에서의 Confidence값이라고 생각하면 된다.

  • Bounding Box Regression에서 1 x 1 x 512 필터 하나가 하나의 엥커에 의해서 판단되는 결과(대강 물체 Region Proposal)가 나와야 한다.

  • 참고 이미지 (알파프로젝트 하는 동안 Faster RCNN공부한 내용)
  • m x n 에서 m x n 은 Feature Extractor를 통과후 나오는 Feature Map의 Resolution
  • 1 padding + 3x3 conv -> 1x1 conv

1-2 RPN 상세 설명과 Loss 함수

  • Positive Anchor Box VS Negative Anchor Box
    • Ground True와 Bounding Box의 IOU값으로 분류 할 수 있다.
    • IOU 0.7 이상 : positive (Foreground를 학습)
    • IOU 0.3 이하 : Negative (Background를 학습)
    • IOU 0.3~0.7 사이 : 애매한 경우로 학습에 사용안함
    • Negative, Positive 만을 학습에 사용.
  • 학습순서
    1. 실제 이미지에 Anchor 박스를 올려 놓는다!(위에서 2번쨰 사진처럼)
    2. Ground True랑 방금 올려놓은 Anchor 박스와의 Positive, Negative 정도를 판단한다. positive일 때만 아래의 Box regression학습을 한다.
    3. Ground True랑 방금 올려놓은 Anchor 박스와의 Bounding Box Regression 정도를 파악하여 신경망을 학습시키고, RPN 신경망의 결과가 relative ofsset이 되도록 학습시킨다. (Anchor에 대해서, 어느정도로 이동하고 확장해야, Ground True Box로 근접하여 more positive가 될 수 있는지 신경망이 알도록 만든다. (Anchore이동으로 완벽히 Ground True가 될 필요는 없다. Anchor이동으로 Ground True에 대해서 more Positive가 되기만 하면 된다. ))
    4. 위의 2번과 3번에서 나온 결과값(A)이 RPN Layer를 통과하고 나온 결과(B), 즉 위에서 3번쨰 사진의 가장 오른쪽에 나오는 결과(B)가 되어야 한다.
    5. 따라서 RPN의 Loss함수는 이렇다고 할 수 있다.
      Loss Function = L1(A - B)
  • 위에서 Loss 함수의 감을 잡았다면, 정확한 Loss 함수 수식을 살펴 보자.
  • pi,ti가 B이고 pi*,ti* 값이 A이다. pi는 Softmax값이고 pi*는 1 or 0이다.

drawing

  • 워낙 Negative Anchor가 많기 때문에 Positive Anchore를 항상 적절하게 섞어주어 학습을 진행해 주었다. 그냥 이미지 그대로 전체 학습을 시키면 Negative가 너무 많아서 학습이 잘 안된다고 한다. (Nagative Mining)

  • 만약 모든 Positive Anchor가 Proposal된 Region이라고 하고, 이 모든 것들을 Head detector에 보내려면, 너무 많은 수의 Region을 보내야 한다. 따라서 다음과 같은 절차를 수행한다.

    • Objectness Score를 계산 한다.
      Objectness Score = pi값(Object일 확률 softmax값) * Ground True와 Bounding Box값의 IOU값
    • Objectness Score가 높은 순으로 오른차순 정렬을 한다.
    • 오름차순된 Region Proposal에 대해서 몇개까지 만을 다음 Layer에 가져갈지 숫자인 N(다음 Detection Layer로 보낼 Region Proposal의 갯수)을 정한다. Ex 2000. 300. 50
    • 그것들만 2 stage인 classification, Box regression 단으로 가져가 학습에 사용한다.

1-3 Alternation Training

drawing

  • 2개의 Network가 있다. 2개의 Network를 동시에 학습시키는 것이 쉽지 않다. 따라서 Training의 과정을 위의 사진과 같은 방법으로 해야한다. 여기서 1번과 2번은 Backbone과 같이 학습하고, 3번과 4번에서는 각각 1x1 conv, FC Layer만 좀만 더 Fine Turning 해준다.

  • 이런 방식으로 학습한 모델을 Inference 돌린 결과 실시간성 확보가 가능해지기 시작했다. 하지만 그래도 아직은 부족함이 존재했다.

    • 1 stage detector 개발 필요
    • End to End 학습 모델 구축 필요

2. OpenCV를 이용한 Object Detection 개념

2-1 OpenCV DNN 장단점

  • 장점
    1. 딥러닝 개발 프레임 워크 없이 쉽게 Inferecne 구현 가능
    2. OpenCV에서 지원하는 다양한 Computer vision 처리 API와 Deep Learning을 쉽게 결합
    3. 인텔은 모바일 분야에 집중하여 CPU기반 OpenCV
    4. NVIDIA - GPU 학습 혹은 추론 각각에서 좋은 성능을 가진 다양한 GPU개발되고 있음
    5. TPU - 구글에서 개발.
  • 단점
    1. GPU 지원이 약함
    2. Inference만 가능
    3. CPU 기반 Inference 속도가 개선되었으나, GPU를 사용하는 것 대비 아직도 속도가 느림.

2-2 Deep Learning Frame 사용방법

drawing

  • Yolo를 만든것이 DarkNet그룹이므로, OpenCV에서 Yolo를 사용하려면 DarkNet 회사에서 재공하는 가중치 모델 파일, 환경 파일을 넣어야 한다.
  • cvNet으로 Neural Network가 반환이 되어 그 변수를 사용할 수 있다.

2-3 참고 Document 사이트

drawing

2-4 OpenCV DNN을 이용하기 위한 Inference 수행 절차

  • 우리가 앞으로 코드에서 사용할 내용의 전체적인 큰 그림이다. 계속 사용할 것이므로, 잘 알아주자.

drawing

  1. 가중치와 환경설정파일의 형식(.pb, .pbtxt)임에 주의하자.
    1-1 그리고 이미지를 가져온다. 이미지를 가져오는 동안 bolbFromImage()라는 함수를 사용할 수 있다.
    bolbFromImage() 의 역할 : Network에 들어갈 이미지로 사이즈 조정, 이미지 값 스케일링, BGR을 RGB로 변경. 이미지 Crop할 수 있는, 모든 옵션 제공

     img_bgr = cv2.imread('img path')
     cvNet.setInput(cv2.dnn.blobFromImage(img_bgr, size = (300,300), SwapRB = True, crop = False)
     # swapRB : BGR을 RGB로 변경
     # size는 내가 사용하는 CvNet에 따라 다른다. 내가 사용하고 싶은 네트워크가 무슨 사이즈의 Input을 받는지는 알아두어야 한다. 
    

    1-2 Vidoe Stream Capture

    • OpenCV의 VideoCapture() API를 사용하면 Video Stream을 Frame별로 Capture한 Image에 대해서 Object Detection을 쉽게 수행할 수 있다.
    • 참고 이전 게시물
    • 모든 곳에서 아주 유용하게 활용되고 있으니 잘 알아두자.
  2. 이미지를 cvNet에 넣는다.

  3. forward를 통해서 Detect된 output을 변수에 저장한다.

  4. Detection된 결과에 for를 통해서 하나하나의 Object를 확인해본다.

2-5 코드 활용

다음 Blog Post 참조

【Paper】 RCNN(Regions with CNN), SPP(Spatial Pyramid Pooling), Fast RCNN

당연하다고 생각하지만, RCNN 개념을 다시 상기하고 정리해보면서 공부해볼 예정이다.

RCNN(Regions with CNN) 계열에 대한 고찰과 공부

1. RCNN

  • 초기의 방식 - Sliding Wnidow 방식과 Selective Search 기반의 Region Propsal 방식
  • RCNN은 Selective Search 을 사용한다.

drawing

  • 위의 사진에서 Bounding Box Regression에서 나오는 값은 사실 중앙좌표와 box크기 x,y,w,h 이다.
  • 위의 사진에서 Softmax를 적용하는게 아니라, SVM Classifier(Class를 구분하는 직선,평면을 찾는다)을 사용했다. Train 시킬때는 Softmax를 사용하고 Inference에서는 SVM을 사용함으로써 성능향상을 보았다고 한다.
  • Selective Search를 통해서 나온 이미지를 AlexNet에 넣기위해서 227*227을 만들어야 하기 때문에, Crop, Warp를 적용했다. (ROI Pooling과 같은 느낌)
  • Bounding Box Reggresion에서 좀더 정확한 Bounding Box를 찾아낸다.

1-1 RCNN 특징 및 의의

  • CNN을 통과하는 과정이 대략 2000개의 ROI에 모두 적용하므로 시간이 매우 오래걸린다.
  • 그 시기에 높은 Detection 정확도를 가졌지만, 너무 느리다.
  • 복잡한 프로세스 떄문에, 학습 및 모델 코드 구현이 매우 복잡하고 어렵다.
  • 각각의 Selective Search, Feature Extractor, FC layer, Box Regression 등이 서로 따로 놀아서 구현 및 다루기가 매우 힘들다.
  • 딥러닝 기반 Object Detection 성능을 입증
  • Region Proposal 기반 성능 입증
  • 위와 같은 복잡한 프로세스의 통합 연구의 필요성 제시

1-2 Bounding Box Regression

  • Selective Search를 하고 Bounding Box Regression을 왜 적용할까? - Selective Search에서 정확한 좌표가 나오지 않기 때문이다.

  • 여기서 p는 Selective Search에서 나오는 좌표들이고, g는 ground true bounding box이다. d=t함수를 찾는 것이 Bounding Box Regression의 목표이다.

drawing

  • [C:\Users\sb020\OneDrive\백업완료_2020.03\ML\알파프로젝트\Faster-Rcnn] 의 필기자료.

drawing

2. SPP(Spatial Pyramid Pooling) Net

  • Fast Rcnn 이전의 RCNN을 개선한 모델이라고 할 수 있다.
  • Pyramid Pooling 에서 ROI Pooling으로 바꾼게 Fast Rcnn이라고 할 수 있다.
  • Selective Search에서 나온 영역 모두를 DNN에 통과시키는게 아니라, 먼저 DNN에 통과시키고 Feature map에 대해서 Selective Search에서 제안한 영역을 바라 본다.

drawing

  • 여기서 문제가 있다. Feature Map에서 FC로 갈 때, FC에 들어가야하는 Input은 고정적이다. 하지만 Selective Search에서 Mapping된 Feature의 사이즈들이 서로서로 다르다. Ex[13x13x256, 9x9x256 등등..]
  • Spatial Pyramid Pooling이 Fature map과 FC 사이에서 동작한다. Spatial Pyramid Poolings는 위의 예시와 같은 다른 사이즈의 Feature Map을 Pooling을 통해서 동일한 크기의 Feature Map으로 바꾸고 그것을 FC에 넣음으로써 문제를 해결했다. (ROI Pooing과 거의 동일)
  • Spatial Pyramid Matching 기법이라는 컴퓨터 비전의 전통적인 방법을 가져온 것이 Spatial Pyramid Pooling이다. 또 Spatial Pyramid Matching 기법은 Bag of Visual words방법에 근간 한다. 따라서 흐름도는 다음과 같이 그릴 수 있다. SPP가 가장 간단하다.
    • Bag of Visual words —> Spatial Pyramid Matching —> Spatial Pyramid Pooling

      drawing

  • 위와 같은 방식으로 어떤 크기의 Feature라고 하더라도 21개의 백터값으로 표현할 수 있었다. 이것을 고정된 값이므로 FC로 넣는데 매우 용이했다. 아래의 사진 하단에서 Feature Map의 크기가 어떤 것이라고 할지라도, 21x256개의 값을 FC에 쉽게 넣을 수 있었다.

drawing

  • SPP Net을 사용함으로써 RCNN에 비교해서 성능은 비슷하지만, Inference 시간이 20배 정도 줄었다.

3. Fast RCNN

  • 위에서 공부한 Spatial Pyramid Pooling을 ROI Pooling으로 바꿨다. SPP처럼 굳이 Level0 Level1 Level2 로 만들지 않고, 7*7의 grid로만 쪼개고 Max Pooling과정을 거친다.

  • End-to-End Learning을 수행했다. SVM을 Softmax로 다시 변환했다. 이전까지는 Classification과 Regression을 따로따로 학습시켰는데, 이제는 Multi-task Loss함수를 적용해서 함께 학습시키는 방법을 고안했다.

drawing

drawing

  • u = 0 이면 Background라는 것을 의미하고 background에 대해서는 당연히 Bounding box regression을 수행하지 않는다. 감마는 multi-tast loss의 밸런스 상수이다.

  • Fast RCNN은 RCNN보다 조금 성능이 향상되고, 추론 시간이 RCNN보다 빠르다. 하지만 Selective Search를 추가하고 안하고의 시간이 각각 2.3초, 0.32초 인것을 감안했을 때, Region Proposal의 오랜시간이 걸리는 문제점을 해결해야함을 알 수 있었다.

【Vision】 Detection과 Segmentation 다시 정리 3 - Framework, Module, GPU

당연하다고 생각하지만, 아직은 공부할게 많은 Detection과 Segmentation에 대한 개념을 다시 상기하고 정리해보면서 공부해볼 계획이다.

Detection과 Segmentation 다시 정리 3

1. Object Detection을 위한 다양한 모듈

  1. Keras & Tensorflow, Pytorch
    • Customization 가능 .
    • 알고리즘 별로 구현을 다르게 해줘야함.
    • Keras와 Tensorflow는 동시에 사용되고 있다. 소스 코드에 서로가 서로를 사용하고 있으니 항상 같이 import하는 것을 추천.
  2. OpenCV의 DNN 모듈
    • 간편하게 Object Detection Inference 가능.
    • 학습이 불가능.
    • CPU 위주로 동작함. GPU 사용 불가.
  3. Tensorflow Object Detection API
    • 많은 Detection 알고리즘 적용가능.
    • 다루기가 어렵고 학습을 위한 절차가 너무 복잡.
    • 다른 오픈소스 패키지에 비해, Pretrain weight가 많이 지원된다. 다양한 모델, 다양한 Backbone에 대해서.
    • Yolo는 지원하지 않고, Retinanet에 대한 지원도 약함
    • MobileNet을 backbone으로 사용해서 실시간성, 저사양 환경에서 돌아갈 수 있도록 하는 목표를 가지고 있다.
    • Tensorflow Version2와 Version1의 충돌.
    • Document나 Tutorial이 부족하다.
    • Research 형태의 모듈로 안정화에 의문
  4. Detectron2
    • 많은 Detection 알고리즘 적용가능
    • 상대적으로 다루기가 쉽고 절차가 간단.

2. 사용하면 좋은 Keras와 Tensorflow 기반의 다양한 오픈소스 패키지들

  • 아래의 코드들은 대부분 Tensorflow & Keras 를 사용하고 있습니다.
  1. Yolo - https://github.com/qqwweee/keras-yolo3
    • 심플하면서 좋은 성능 보유
    • 조금 오래 됐고, 업그레이드가 잘 안됨.
  2. Retinanet - https://github.com/fizyr/keras-retinanet
    • 정교한 검출 성능, 작은 물체라도 잘 검출하는 능력이 있음
  3. Mask R-CNN - https://github.com/matterport/Mask_RCNN
    • Instance Segmentation의 중심.
    • 다양한 기능과 편리한 사용성으로 여러 프로젝트에 사용된다.
  4. Open CV - DNN Pakage Document
    • 컴퓨터 비전 처리를 널리 사용되고 있는 범용 라이브러리
    • 오픈소스 기반 실시간 이미지 프로세싱에 중점을 둔 라이브러리.
    • Deep Learning 기반의 Computer Vision 모듈 역시 포팅되어 있음.
    • OpenCV가 지원하는 딥러닝 프레임워크. 이것을 사용해 Inference.
      • Caffe, Tensorflow, Torch, Darknet
    • 모델만 Load해서 Inference하면 되는 장점이 있기 때문에 매우 간편
    • CPU 위주이며, GPU 사용이 어렵다.

3. GPU 활용

  1. CUDA : GPU에서 병렬처리 알고리즘을 C언어를 비롯한 산업 표준 언어를 사용하여 작성할 수 있도록 하는 기술. GPU Driver Layer 바로 위에서 동작한다고 생각하면 된다.
  2. cuDNN :CUDA는 그래픽 작업을 위해서 존재하는 것이고, cuDNN은 딥러닝 라이브러리를 위해서 추가된 기술.

drawing

  1. 위의 모든 것이 잘 설치되어 있다면, 당연히
    $ nvidia-smi
    $ watch -n 1 nvidia-smi
  2. 학습을 하는 동안 GPU 메모리 초과가 나오지 않게 조심해야 한다.
    따라서 GPU를 사용하는 다른 Processer를 끄는 것을 추천한다. (nvidia-smi에 나옴)

4. Object Detection 개요

drawing

  • Feature Extraction Network : Backbone Network, Classification을 위해 학습된 모델을 사용한다. 핵심적인 Feature를 뽑아내는 역할을 한다.
  • Object Detection Network
  • Region Progosal : Selective Search, RPN 등등..

  • Image Resolution, FPS, Detection 성능의 당연한 상관 관계 아래 그림 참조
    yoloV2 성능비교를 통해서 확인해 보자.

drawing

【Vision】 Detection과 Segmentation 다시 정리 2 - Datasets

당연하다고 생각하지만, 아직은 공부할게 많은 Detection과 Segmentation에 대한 개념을 다시 상기하고 정리해보면서 공부해볼 계획이다.

Detection과 Segmentation 다시 정리 2

Dataset에 대한 진지한 고찰

1. Pascal VOC 기본개념

  1. 기본 개념

    • XML Format (20개 Class)
    • 하나의 이미지에 대해서 Annotation이 하나의 XML 파일에 저장되어 있다.
    • 데이터셋 홈페이지 바로가기
    • 대회 종류
      • Classification/Detection
      • Segmentation
      • Action Classification
      • Person Layout
    • Annotation : Pascal VOC 같은 경우에 아래와 같은 형식의 Annotation 파일로써 Ground True 정보를 파악할 수 있다.

    • Data Set을 다운받으면 다음과 같은 구조를 확인할 수 있다. segmented : segmentation 정보를 가지고 있느냐?를 의미한다.
      drawing
      drawing

2. PASCAL VOC 2012 데이터 탐색해보기

  • 코드 안에 주석도 매우 중요하니 꼭 확인하고 공부하기
  • DLCV/Detection/preliminary/PASCAL_VOC_Dataset_탐색하기.ipynb 파일을 통해 공부한 내용
  !ls ~/DLCV/data/voc/VOCdevkit/VOC2012
  Annotations  ImageSets	JPEGImages  SegmentationClass  SegmentationObject
  !ls ~/DLCV/data/voc/VOCdevkit/VOC2012/JPEGImages | head -n 5
  2007_000027.jpg
  2007_000032.jpg
  2007_000033.jpg
  2007_000039.jpg
  2007_000042.jpg
  ls: write error: Broken pipe
  • JPEGImages 디렉토리에 있는 임의의 이미지 보기
  import cv2
  import matplotlib.pyplot as plt
  %matplotlib inline

  img = cv2.imread('../../data/voc/VOCdevkit/VOC2012/JPEGImages/2007_000032.jpg')
  img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  print('img shape:', img.shape)

  plt.figure(figsize=(8, 8))
  plt.imshow(img_rgb)
  plt.show()

  img shape: (281, 500, 3)
  • Annotations 디렉토리에 있는 임의의 annotation 파일 보기
  !cat ~/DLCV/data/voc/VOCdevkit/VOC2012/Annotations/2007_000032.xml
  <annotation>
    <folder>VOC2012</folder>
    <filename>2007_000032.jpg</filename>
    <source>
      <database>The VOC2007 Database</database>
      <annotation>PASCAL VOC2007</annotation>
      <image>flickr</image>
    </source>
    <size>
      <width>500</width>
      <height>281</height>
      <depth>3</depth>
    </size>
    <segmented>1</segmented>
    <object>
      <name>aeroplane</name>
      <pose>Frontal</pose>
      <truncated>0</truncated>
      <difficult>0</difficult>
      <bndbox>
        <xmin>104</xmin>
        <ymin>78</ymin>
        <xmax>375</xmax>
        <ymax>183</ymax>
      </bndbox>
    </object>
    <object>
      <name>aeroplane</name>
      <pose>Left</pose>
      <truncated>0</truncated>
      <difficult>0</difficult>
      <bndbox>
        <xmin>133</xmin>
        <ymin>88</ymin>
        <xmax>197</xmax>
        <ymax>123</ymax>
      </bndbox>
    </object>
    <object>
      <name>person</name>
      <pose>Rear</pose>
      <truncated>0</truncated>
      <difficult>0</difficult>
      <bndbox>
        <xmin>195</xmin>
        <ymin>180</ymin>
        <xmax>213</xmax>
        <ymax>229</ymax>
      </bndbox>
    </object>
    <object>
      <name>person</name>
      <pose>Rear</pose>
      <truncated>0</truncated>
      <difficult>0</difficult>
      <bndbox>
        <xmin>26</xmin>
        <ymin>189</ymin>
        <xmax>44</xmax>
        <ymax>238</ymax>
      </bndbox>
    </object>
  </annotation>
  • SegmentationObject 디렉토리에 있는 있는 임의의 maksing 이미지 보기
  img = cv2.imread('../../data/voc/VOCdevkit/VOC2012/SegmentationObject/2007_000032.png')
  img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  print('img shape:', img.shape)

  plt.figure(figsize=(8, 8))
  plt.imshow(img_rgb)
  plt.show()
  img shape: (281, 500, 3)
  • Annotation xml 파일에 있는 요소들을 파싱하여 접근하기
  • 파일 이름만 뽑아아서 xml_files 라는 변수에 저장해 두기
  # 파일 이름만 뽑아아서 xml_files 라는 변수에 저장해 두기
  import os
  import random

  VOC_ROOT_DIR ="../../data/voc/VOCdevkit/VOC2012/"
  ANNO_DIR = os.path.join(VOC_ROOT_DIR, "Annotations")
  IMAGE_DIR = os.path.join(VOC_ROOT_DIR, "JPEGImages")

  xml_files = os.listdir(ANNO_DIR)                       
  print(xml_files[:5]); print(len(xml_files))
  ['2012_001360.xml', '2008_003384.xml', '2008_007317.xml', '2009_000990.xml', '2009_003539.xml']
  17125
  • xml 파일을 다루기 위한 모듈은 다음과 같다.
  # !pip install lxml
  # 틔
  import os
  import xml.etree.ElementTree as ET

  xml_file = os.path.join(ANNO_DIR, '2007_000032.xml')

  # XML 파일을 Parsing 하여 Element 생성
  # 이렇게 2번의 과정을 거쳐서 Parsing을 완료하면 root라는 변수에 원하는 정보들이 저장되어 있다. 
  tree = ET.parse(xml_file)
  root = tree.getroot()

  # root가 dictionary 변수이면 root.keys()이렇게 출력하면 될덴데...
  print("root.keys = ", end='')
  for child in root:
      print(child.tag, end = ', ')
          

  # image 관련 정보는 root의 자식으로 존재
  # root를 이용해서 dictionary형식의 anotation 정보를 뽑아내는 방법은 다음과 같다.
  image_name = root.find('filename').text
  full_image_name = os.path.join(IMAGE_DIR, image_name)
  image_size = root.find('size')
  image_width = int(image_size.find('width').text)
  image_height = int(image_size.find('height').text)

  # 파일내에 있는 모든 object Element를 찾음.
  objects_list = []
  for obj in root.findall('object'):
      # object element의 자식 element에서 bndbox를 찾음. 
      xmlbox = obj.find('bndbox')
      # bndbox element의 자식 element에서 xmin,ymin,xmax,ymax를 찾고 이의 값(text)를 추출 
      x1 = int(xmlbox.find('xmin').text)
      y1 = int(xmlbox.find('ymin').text)
      x2 = int(xmlbox.find('xmax').text)
      y2 = int(xmlbox.find('ymax').text)
      
      bndbox_pos = (x1, y1, x2, y2)
      class_name=obj.find('name').text
      object_dict={'class_name': class_name, 'bndbox_pos':bndbox_pos}
      objects_list.append(object_dict)

  print('\nfull_image_name:', full_image_name,'\n', 'image_size:', (image_width, image_height))

  for object in objects_list:
      print(object)

      
  root.keys = folder, filename, source, size, segmented, object, object, object, object, 
  full_image_name: ../../data/voc/VOCdevkit/VOC2012/JPEGImages/2007_000032.jpg 
  image_size: (500, 281)
  {'class_name': 'aeroplane', 'bndbox_pos': (104, 78, 375, 183)}
  {'class_name': 'aeroplane', 'bndbox_pos': (133, 88, 197, 123)}
  {'class_name': 'person', 'bndbox_pos': (195, 180, 213, 229)}
  {'class_name': 'person', 'bndbox_pos': (26, 189, 44, 238)}
  • Annotation내의 Object들의 bounding box 정보를 이용하여 Bounding box 시각화
  import cv2
  import os
  import xml.etree.ElementTree as ET

  xml_file = os.path.join(ANNO_DIR, '2007_000032.xml')

  tree = ET.parse(xml_file)
  root = tree.getroot()

  image_name = root.find('filename').text
  full_image_name = os.path.join(IMAGE_DIR, image_name)

  img = cv2.imread(full_image_name)
  # opencv의 rectangle()는 인자로 들어온 이미지 배열에 그대로 사각형을 그려주므로 별도의 이미지 배열에 그림 작업 수행. 
  draw_img = img.copy()
  # OpenCV는 RGB가 아니라 BGR이므로 빨간색은 (0, 0, 255)
  green_color=(0, 255, 0)
  red_color=(0, 0, 255)

  # 파일내에 있는 모든 object Element를 찾음.
  objects_list = []
  for obj in root.findall('object'):
      xmlbox = obj.find('bndbox')
      
      left = int(xmlbox.find('xmin').text)
      top = int(xmlbox.find('ymin').text)
      right = int(xmlbox.find('xmax').text)
      bottom = int(xmlbox.find('ymax').text)
      
      class_name=obj.find('name').text
      
      # draw_img 배열의 좌상단 우하단 좌표에 녹색으로 box 표시 
      cv2.rectangle(draw_img, (left, top), (right, bottom), color=green_color, thickness=1)
      # draw_img 배열의 좌상단 좌표에 빨간색으로 클래스명 표시
      cv2.putText(draw_img, class_name, (left, top - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, red_color, thickness=1)

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

drawing

3. MS COCO

  • json Format (80개 Class : paper에서는 80개라고 했지만, 이미지에서는 굳이 분류 안한 class가 있으니 주의할 것.)
  • 300K 이미지들 1.5M개 Object들 (하나의 이미지당 5개의 객체)
  • 모든 이미지에 대해서 하나의 json 파일이 존재한다.
  • Pretrained Weight로 활용하기에 좋다.
  • 데이터셋 홈페이지 바로가기
    • 보통 2017데이터 셋 가장 최신의 데이터셋을 사용한다.
  • 데이터셋 Explore를 쉽게 할 수 있다.
  • 데이터셋 구성
    drawing
  • 하나의 이미지 안에 여러 Class들 여러 Object들이 존재하고, 타 데이터 세트에 비해 난이도가 높은 데이터이다.
  • 실제 우리의 환경에서 잘 동작할 수 있는 모델을 만들기 위해 만들어진 데이터셋이다.

4. Google Open Image

  • csv Format (600개 Class)
  • Size도 매우 크기 때문에 학습에도 오랜 시간이 걸릴 수 있다.

Pagination


© All rights reserved By Junha Song.