【Keras】Keras기반 Mask-RCNN - Balloon 데이터셋, Python 추가 공부

Keras 기반 Mask-RCNN를 이용해 Balloon 데이터셋을 학습시키고 추론해보자.

Keras기반 Mask-RCNN - Balloon 데이터셋

1. 앞으로의 과정 핵심 요약

drawing

  1. /Mask_RCNN/tree/master/samples/balloon 에 있는 내용들을 활용하자.

  2. Matterport Mask RCNN Train 프로세스 및 앞으로의 과정 핵심 정리

    drawing

    drawing

2. 스스로 공부해보기

  • 강의를 보니, 큰 그림은 잡아주신다. 하지만 진짜 공부는 내가 해야한다. 코드를 하나하나 파는 시간을 가져보자. 데이터 전처리 작업으로써 언제 어디서 나중에 다시 사용할 능력일지 모르니, 직접 공부하는게 낫겠다.
    • 코드 보는 순서 기록
      1. /mask_rcnn/Balloon_데이터세트_학습및_Segmentation.ipynb
      2. /sample/balloon/balloon.py
      3. /mrcnn/utils.py

3. Python 새로운 핵심 정리

python 새롭게 안 사실 및 핵심 내용들

  1. super() : 참고 사이트
    • __init__ 나 다른 맴버 함수를 포함해서, 자식 클래스에서 아무런 def을 하지 않으면 고대로~ 부모 클래스의 내용이 상속된다. 자식 클래스에서도 함수든 변수든 모두 사용 가능하다.
    • 하지만 문제가 언제 발생하냐면, def 하는 순간 발생한다. 만약 def __init__(self, ..): 하는 순간, 오버라이딩이 되어 원래 부모 클래스의 내용은 전부 사라지게 된다. 이럴 떄, 사용하는게 super이다.
    • 대신 클래스 변수를 사용하는 공간에는 super를 사용하지 않아도 상속한 부모 클래스의 내용이 전부 알아서 들어간다.
    • super자리에 코드들이 쫘르르륵 들어간다고 생각하라.(마치 해더파일에 있는 함수의 내용이 링크에 의해서 쫘르르 코드가 옮겨 들어가듯)
    • 아래와 같이, super(MethodFunctionName, self).init(객체 생성자에 들어가야 할 input parameter) 를 사용하면, 이렇게 생성된 부모클래스로 만들어진 객체의 맴버변수, 맴버함수를 그대로 사용할 수 있다.
            super(MaskRCNNPredictor, self).__init__(OrderedDict([
                ("conv5_mask", misc_nn_ops.ConvTranspose2d(in_channels, dim_reduced, 2, 2, 0)),
                ("relu", nn.ReLU(inplace=True)),
                ("mask_fcn_logits", misc_nn_ops.Conv2d(dim_reduced, num_classes, 1, 1, 0)),
            ]))
      
  2. VS code - 새롭게 파일을 열 떄 강제로 새로운 탭으로 나오게하는 방법 : setting -> workbencheditor.enablePreview” -> false 체크
  3. jupyter Notebook font size 바꾸기 : setting -> Editor:Font Size Controls the font size in pixels. -> 원하는size대입
  4. 함수안에 함수를 정의하는 행동은 왜 하는 것일까?
    그것은 아래와 같은 상황에 사용한다. 함수안의 함수(fun2)는 fun1의 변수를 전역변수처럼 이용할 수 있다. 다시 말해 fun2는 a와 b를 매개변수로 받지 않았지만, 함수 안에서 a와 b를 사용하는 것을 확인할 수 있다.
     def fun1(self, a,b):
         a = 1
         b = 2
         def fun2(c):
             return a + b + c
         k = fun2(3)
         return k
    
  5. numpy array에 대한 고찰
     import numpy as np
     a = np.ones((10, 10, 3))
     print(np.sum(a, axis=-1).shape)             
     # (10,10) 그냥 2차원 배열이다.
     print(np.sum(a, -1, keepdims=True).shape)   
     np.sum(a, -1, keepdims=True)[:,:,0] 
     # (10,10,1) 3차원 배열의 가장 첫번쨰 원소에 2차원 배열이 들어가 있고, 그 2차원 배열 shape가 10*10 이다.
    

    여기서 주요 요점!

    • n차원의 k번째 원소에는(하나의 원소는) n-1차원의배열이다.
    • 3차원의 1번쨰 원소에는 2차원 배열이다.
    • 2차원의 1번째 원소에는 1차원 배열이다.
    • 1차원의 1번째 원소에는 0차원 배열(스칼라)이다.
  6. numpy indexing (인덱싱) 언젠간 공부해야한다.
  7. cv2.circle : 이미지에 점 찍기
  8. skimage.draw.polygon : polygon 정보로 내부 채우기
  9. pretrained weights를 load 할 때, class의 갯수가 다르면head 부분의 layer는 exclude하여 가져오기.

4. 전체 코드 공부해보기

1. Balloon data data 준비

  • /DLCV/Segmentation/mask_rcnn/Balloon_데이터세트_학습및_Segmentation.ipynb 파일 참조
  • Matterport 패키지를 이용하여 Balloon 데이터 세트를 학습하고 이를 기반으로 Segmentation 적용
    1. realse에 자료 다운 받기. balloon_dataset.zip 파일

drawing

  1. ~/DLCV/data 에 wget해서 바로 unzip
  2. train, val 폴더가 있고, 그 안에 많은 이미지와 json파일 하나 있다.
import os
import sys
import itertools
import math
import logging
import json
import re
import random
from collections import OrderedDict

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.lines as lines
from matplotlib.patches import Polygon
import cv2

%matplotlib inline
from mrcnn import utils
from mrcnn import visualize
from mrcnn.visualize import display_images
import mrcnn.model as modellib
from mrcnn.model import log
  • 아래과정 설명
    • /Mask_RCNN/tree/master/samples/balloon를 그냥 패키지로 append해버리자.
    • 그리고 그 안에 ballon.py를 쉽게 사용할 수 있다.
    • /Mask_RCNN/blob/master/samples/balloon/balloon.py파일을 하나하나 뜯어가며 공부해도 좋다. 그대로 사용하는 것보다 공부를 하자!!
    • 코드를 보면, 아래처럼 구현되어 있는 것을 확인할 수 있다. 우리도 이렇게 해야한다.
        # utils.Dataset을 상속해서 오버로딩을 해야한다. 
        class BalloonDataset(utils.Dataset):
            def load_mask(self, image_id):
      
#Mask_RCNN 패키지의 samples/balloon 디렉토리의 balloon.py 를 import 한다. 
ROOT_DIR = os.path.abspath(".")
sys.path.append(os.path.join(ROOT_DIR, "Mask_RCNN/samples/balloon/"))

import balloon
  • balloon 데이터 세트가 제대로 되어 있는지 확인. train과 val 서브 디렉토리가 ~/DLCV/data/balloonn 에 존재해야 함.
import subprocess
from pathlib import Path

HOME_DIR = str(Path.home())
BALLOON_DATA_DIR = os.path.join(HOME_DIR, "DLCV/data/balloon")
  • balloon 모듈에 설정된 Config 셋업. GPU 갯수, Batch시 image갯수가 사전 설정 되어 있음.
  • BalloonConfig도 mrcnn.config를 상속해서 사용하는 것을 알 수 있다.
config = balloon.BalloonConfig()
config.IMAGES_PER_GPU = 1
config.display()
Configurations:
BACKBONE                       resnet101
BACKBONE_STRIDES               [4, 8, 16, 32, 64]
BATCH_SIZE                     2
BBOX_STD_DEV                   [0.1 0.1 0.2 0.2]
COMPUTE_BACKBONE_SHAPE         None
...
USE_MINI_MASK                  True
USE_RPN_ROIS                   True
VALIDATION_STEPS               50
WEIGHT_DECAY                   0.0001
  • balloon 모듈에서 balloon 데이터 세트 로딩.
# Dataset 로딩한다. . 

dataset = balloon.BalloonDataset()
dataset.load_balloon(BALLOON_DATA_DIR, "train")

# Must call before using the dataset
dataset.prepare()

print("Image Count: {}".format(len(dataset.image_ids)))
print("Class Count: {}".format(dataset.num_classes))
for i, info in enumerate(dataset.class_info):
    print("{:3}. {:50}".format(i, info['name']))
Image Count: 61
Class Count: 2
  0. BG                                                
  1. balloon                                           
  • balloon 모듈에서 로딩한 balloon 데이터 세트의 세부 정보 확인.
# dataset의 image_info는 리스트 객체이며 내부 원소로 이미지별 세부 정보를 딕셔너리로 가지고 있음. 
# dataset의 image_ids 는 이미지의 고유 id나 이름이 아니라 dataset에서 이미지의 상세 정보를 관리하기 위한 리스트 인덱스에 불과 

print('#### balloon 데이터 세트 이미지의 인덱스 ID들 ####')
print(dataset.image_ids)
# print('\n ##### balloon 데이터 세트의 이미지 정보들 ####')
# print(dataset.image_info)
#### balloon 데이터 세트 이미지의 인덱스 ID들 ####
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 48 49 50 51 52 53 54 55 56 57 58 59 60]

2. 가져온 데이터 Load & Read 해보기

  • balloon.py 파일의 BalloonDataset.Load_mask 함수를 보면, polygon정보를 True-False mask 정보로 바꾸는 것을 확인할 수 있다.
image_28 = dataset.image_info[28]  # 28 index 이미지의 정보를 가져온다
print(image_28,'\n')
polygons = image_28['polygons']
polygon_x = polygons[0]['all_points_x']
polygon_y = polygons[0]['all_points_y']
print(len(polygon_x))
# print('polygon_x:', polygon_x, 'polygon_y:',polygon_y)

polygon_xy = [(x, y) for (x, y) in zip(polygon_x, polygon_y)]
print('polygon_xy:', polygon_xy)
{'id': '5178670692_63a4365c9c_b.jpg', 'source': 'balloon', 'path': '/home/sb020518/DLCV/data/balloon/train/5178670692_63a4365c9c_b.jpg', 'width': 683, 'height': 1024, 'polygons': [{'name': 'polygon', 'all_points_x': [371, 389, 399, 409, 416, 415, 407, 395, 381, 364, 346, 331, 327, 324, 322, 318, 316, 319, 304, 290, 276, 280, 289, 304, 326, 351, 371], 'all_points_y': [424, 432, 443, 459, 482, 505, 526, 544, 558, 567, 573, 576, 576, 580, 580, 577, 574, 572, 562, 543, 509, 477, 451, 436, 423, 420, 424]}]} 

27
polygon_xy: [(371, 424), (389, 432), (399, 443), (409, 459), (416, 482), (415, 505), (407, 526), (395, 544), (381, 558), (364, 567), (346, 573), (331, 576), (327, 576), (324, 580), (322, 580), (318, 577), (316, 574), (319, 572), (304, 562), (290, 543), (276, 509), (280, 477), (289, 451), (304, 436), (326, 423), (351, 420), (371, 424)]
image_28_array = cv2.imread(os.path.join(BALLOON_DATA_DIR,'train/'+image_28['id']))
for position in polygon_xy:
    cv2.circle(image_28_array, position, 3, (255, 0, 0), -1) # 이미지에 점을 찍는 함수

# plt.figure(figsize=(8, 8))
# plt.axis('off')    
# plt.imshow(image_28_array)

drawing

np.random.seed(99)
# Load and display random samples
image_ids = np.random.choice(dataset.image_ids, 4)
print('image_ids:', image_ids)
for image_id in image_ids:
    image = dataset.load_image(image_id)
    # 지정된 image_id에 있는 mask 를 로딩하고 시각화를 위한 mask정보들과 대상 클래스 ID들을 추출
    mask, class_ids = dataset.load_mask(image_id)
    #원본 데이터와 여러개의 클래스들에 대해 Mask를 시각화 하되, 가장 top 클래스에 대해서는 클래스명까지 추출. 나머지는 배경
    visualize.display_top_masks(image, mask, class_ids, dataset.class_names)
image_ids: [ 1 35 57 40]

drawing

image = dataset.load_image(28)
print(image.shape)
print(image_28['polygons'])

3. polygon 형태의 데이터를 boolean mask 형태로 변환

  • balloon.py의 dataset.load_mask 함수 내용 참조
  • skimage.draw.polygon이라는 함수 적극적으로 사용
import skimage

img = np.zeros((10, 10), dtype=np.uint8)
r = np.array([1, 2, 8])
c = np.array([1, 7, 4])
plt.imshow(img, cmap='gray')
plt.show()
# r과 c로 지정된 인덱스에 있는 img 값만 1로 설정함. 
rr, cc = skimage.draw.polygon(r, c)
img[rr, cc] = 1
print('row positions:',rr, 'column positions:',cc)
print('Boolean형태로 masking된 img:\n',img.astype(np.bool))
plt.imshow(img, cmap='gray')

drawing

mask, class_ids = dataset.load_mask(59)
print("mask shape:", mask.shape, "class_ids:", class_ids)
# print(mask)
mask shape: (679, 1024, 28) class_ids: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
image = dataset.load_image(28)
mask, class_ids = dataset.load_mask(28)
visualize.display_top_masks(image, mask, class_ids, dataset.class_names)

4. ballon 데이터 세트를 다루기 위한 BallonDataset Class 정의

  • 아래의 코드는 ballon.py의 class BalloonDataset(utils.Dataset): 내용과 동일하다. 여기서 보면 load_balloon를 사용해서 우리가 가진 이미지에 대해서 정의한다. 단, json파일은 이미 만들어져 있어야 한다.
  • 그리고 위의 코드에서 dataset.load_balloon(BALLOON_DATA_DIR, “train”); dataset.prepare(); 을 사용했던 것을 확인할 수 있다.
class BalloonDataset(utils.Dataset):

    def load_balloon(self, dataset_dir, subset):
        """Load a subset of the Balloon dataset.
        dataset_dir: Root directory of the dataset.
        subset: Subset to load: train or val
        """
        # 클래스 id와 클래스명 등록은 Dataset의 add_class()를 이용. 
        self.add_class("balloon", 1, "balloon")

        # train또는 val 용도의 Dataset 생성만 가능. 
        assert subset in ["train", "val"]
        dataset_dir = os.path.join(dataset_dir, subset)
        
        # json 형태의 annotation을 로드하고 파싱. 
        annotations = json.load(open(os.path.join(dataset_dir, "via_region_data.json")))
        annotations = list(annotations.values())  # don't need the dict keys
        
        annotations = [a for a in annotations if a['regions']]

        # Add images
        for a in annotations:
            # Get the x, y coordinaets of points of the polygons that make up
            # the outline of each object instance. These are stores in the
            # shape_attributes (see json format above)
            # The if condition is needed to support VIA versions 1.x and 2.x.
            if type(a['regions']) is dict:
                polygons = [r['shape_attributes'] for r in a['regions'].values()]
            else:
                polygons = [r['shape_attributes'] for r in a['regions']] 

            # load_mask() needs the image size to convert polygons to masks.
            # Unfortunately, VIA doesn't include it in JSON, so we must read
            # the image. This is only managable since the dataset is tiny.
            image_path = os.path.join(dataset_dir, a['filename'])
            image = skimage.io.imread(image_path)
            height, width = image.shape[:2]

            self.add_image(
                "balloon",
                image_id=a['filename'],  # use file name as a unique image id
                path=image_path,
                width=width, height=height,
                polygons=polygons)

    def load_mask(self, image_id):
        """Generate instance masks for an image.
       Returns:
        masks: A bool array of shape [height, width, instance count] with
            one mask per instance.
        class_ids: a 1D array of class IDs of the instance masks.
        """
        # If not a balloon dataset image, delegate to parent class.
        image_info = self.image_info[image_id]
        if image_info["source"] != "balloon":
            return super(self.__class__, self).load_mask(image_id)

        # Convert polygons to a bitmap mask of shape
        # [height, width, instance_count]
        info = self.image_info[image_id]
        mask = np.zeros([info["height"], info["width"], len(info["polygons"])],
                        dtype=np.uint8)
        for i, p in enumerate(info["polygons"]):
            # Get indexes of pixels inside the polygon and set them to 1
            rr, cc = skimage.draw.polygon(p['all_points_y'], p['all_points_x'])
            mask[rr, cc, i] = 1

        # Return mask, and array of class IDs of each instance. Since we have
        # one class ID only, we return an array of 1s
        return mask.astype(np.bool), np.ones([mask.shape[-1]], dtype=np.int32)
    
    '''def image_reference(self, image_id):
        """Return the path of the image."""
        info = self.image_info[image_id]
        if info["source"] == "balloon":
            return info["path"]
        else:
            super(self.__class__, self).image_reference(image_id)
    '''

5. balloon 데이터 세트의 학습 수행.

  • 학습과 Validation용 Dataset 설정.
import skimage

# Training dataset.
dataset_train = BalloonDataset()
dataset_train.load_balloon(BALLOON_DATA_DIR, "train")
dataset_train.prepare()

# Validation dataset
dataset_val = BalloonDataset()
dataset_val.load_balloon(BALLOON_DATA_DIR, "val")
dataset_val.prepare()
  • Config 설정 : 이것도 balloon.py에 있는 내용을 거의 그대로!
from mrcnn.config import Config

TRAIN_IMAGE_CNT = len(dataset_train.image_info)
VALID_IMAGE_CNT = len(dataset_val.image_info)

class BalloonConfig(Config):
    """Configuration for training on the toy  dataset.
    Derives from the base Config class and overrides some values.
    """
    # Give the configuration a recognizable name
    NAME = "balloon"

    # Number of classes (including background)
    NUM_CLASSES = 1 + 1  # Background + balloon

    # Skip detections with < 90% confidence
    DETECTION_MIN_CONFIDENCE = 0.9
    
    # We use a GPU with 12GB memory, which can fit two images.
    # Adjust down if you use a smaller GPU.
    IMAGES_PER_GPU = 1
    
    # 추가.
    GPU_COUNT = 1

    # 원본에서 수정.
    #STEPS_PER_EPOCH = TRAIN_IMAGE_CNT  // IMAGES_PER_GPU
    #VALIDATION_STEPS = VALID_IMAGE_CNT  // IMAGES_PER_GPU
    
    # 원본 STEPS_PER_EPOCH
    STEPS_PER_EPOCH = TRAIN_IMAGE_CNT  // IMAGES_PER_GPU
    VALIDATION_STEPS = VALID_IMAGE_CNT  // IMAGES_PER_GPU

    #BACKBONE = 'resnet101'
    
# config 설정. 
train_config = BalloonConfig()
train_config.display()

6. Mask RCNN Training 초기 모델 생성 및 pretrained weight값 로딩

  • 여기서 부터는 (/mrcnn/model.py 내부에)karas의 내용들이 다수 들어가기 때문에 어려울 수 있으니 참고.
import mrcnn.model as modellib
from mrcnn.model import log

balloon_model = modellib.MaskRCNN(mode="training", config=train_config, model_dir='./snapshots')

# COCO 데이터 세트로 pretrained 된 모델을 이용하여 초기 weight값 로딩. 
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "./pretrained/mask_rcnn_coco.h5")
balloon_model.load_weights(COCO_MODEL_PATH, by_name=True, exclude=["mrcnn_class_logits", "mrcnn_bbox_fc","mrcnn_bbox", "mrcnn_mask"])

7. 위에서 가져온 pretrained model을 가지고 학습 수행

'''
데이터 세트가 작고,단 하나의 클래스임. 
pretrained 된 Coco 데이터 세트로 초기 weight 설정되었기에 RPN과 classifier만 학습해도 모델 성능은 큰 영향이 없을 거라 예상
all: All the layers
3+: Train Resnet stage 3 and up
4+: Train Resnet stage 4 and up
5+: Train Resnet stage 5 and up
'''
print("Training network heads")
balloon_model.train(dataset_train, dataset_val,
            learning_rate=train_config.LEARNING_RATE,
            epochs=30,
            layers='heads')
Training network heads

Starting at epoch 0. LR=0.001

Checkpoint Path: ./snapshots/balloon20200923T1430/mask_rcnn_balloon_{epoch:04d}.h5
Selecting layers to train
fpn_c5p5               (Conv2D)
fpn_c4p4               (Conv2D)
fpn_c3p3               (Conv2D)
fpn_c2p2               (Conv2D)
fpn_p5                 (Conv2D)
fpn_p2                 (Conv2D)
fpn_p3                 (Conv2D)
fpn_p4                 (Conv2D)
In model:  rpn_model
    rpn_conv_shared        (Conv2D)
    rpn_class_raw          (Conv2D)
    rpn_bbox_pred          (Conv2D)
mrcnn_mask_conv1       (TimeDistributed)
mrcnn_mask_bn1         (TimeDistributed)
mrcnn_mask_conv2       (TimeDistributed)
mrcnn_mask_bn2         (TimeDistributed)
mrcnn_class_conv1      (TimeDistributed)
mrcnn_class_bn1        (TimeDistributed)
mrcnn_mask_conv3       (TimeDistributed)
mrcnn_mask_bn3         (TimeDistributed)
mrcnn_class_conv2      (TimeDistributed)
mrcnn_class_bn2        (TimeDistributed)
mrcnn_mask_conv4       (TimeDistributed)
mrcnn_mask_bn4         (TimeDistributed)
mrcnn_bbox_fc          (TimeDistributed)
mrcnn_mask_deconv      (TimeDistributed)
mrcnn_class_logits     (TimeDistributed)
mrcnn_mask             (TimeDistributed)

Use tf.cast instead.
Epoch 1/30
Process Process-2:
Process Process-3:
Process Process-5:
Process Process-6:

8. 학습이 완료된 모델을 이용하여 inference 수행.

  • config를 inference용으로 변경
class InferenceConfig(BalloonConfig):
    # NAME은 학습모델과 동일한 명을 부여
    NAME='balloon'
    GPU_COUNT = 1
    IMAGES_PER_GPU = 1
        
infer_config = InferenceConfig()
infer_config.display()
  • 학습된 모델의 weight 파일을 MaskRCNN의 inference 모델로 로딩.
model = modellib.MaskRCNN(mode="inference", model_dir='./snapshots', config=infer_config)
# callback에 의해 model weights 가 파일로 생성되며, 가장 마지막에 생성된 weights 가 가장 적은 loss를 가지는 것으로 가정. 
weights_path = model.find_last()
print('최저 loss를 가지는 model weight path:', weights_path)
# 지정된 weight 파일명으로 모델에 로딩. 
model.load_weights(weights_path, by_name=True)
  • Instance Segmentation을 수행할 파일들을 dataset로 로딩. val 디렉토리에 있는 파일들을 로딩.
# Inference를 위해 val Dataset 재로딩. 
dataset_val = BalloonDataset()
dataset_val.load_balloon(BALLOON_DATA_DIR, "val")
dataset_val.prepare()

print("Images: {}\nClasses: {}".format(len(dataset_val.image_ids), dataset_val.class_names))
- 아래처럼 model.detect을 사용해서 아주 쉽게, Detect 결과 추출!
from mrcnn import model as modellib

# dataset중에 임의의 파일을 한개 선택. 
#image_id = np.random.choice(dataset.image_ids)
image_id = 5
image, image_meta, gt_class_id, gt_bbox, gt_mask=modellib.load_image_gt(dataset_val, infer_config, image_id, use_mini_mask=False)
info = dataset_val.image_info[image_id]
print("image ID: {}.{} ({}) {}".format(info["source"], info["id"], image_id, 
                                       dataset_val.image_reference(image_id)))

# Run object detection
results = model.detect([image], verbose=1)

image

r = results[0]
visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'], 
                            dataset_val.class_names, r['scores'], 
                            title="Predictions")

drawing

9. Detect 결과에서 풍선만 칼라로. 나머지는 흑백으로 바꾸자.

#Mask_RCNN 패키지의 samples/balloon 디렉토리의 balloon.py 를 import 한다. 
ROOT_DIR = os.path.abspath(".")
sys.path.append(os.path.join(ROOT_DIR, "Mask_RCNN/samples/balloon/"))

import balloon
from mrcnn.visualize import display_images

splash = balloon.color_splash(image, r['masks'])
display_images([splash], cols=1)

drawing

  • balloon.py 내부의 color_splash 함수는 다음과 같다.
def color_splash(image, mask):
    """Apply color splash effect.
    image: RGB image [height, width, 3]
    mask: instance segmentation mask [height, width, instance count]
    Returns result image. (풍선을 제외하고 Gray로 변환하기)
    """
    # '칼라->흑백->칼라' 과정을 거쳐서, gray 값을 3 depth로 가지는 3채널 이미지 만듬
    gray = skimage.color.gray2rgb(skimage.color.rgb2gray(image)) * 255
    # Copy color pixels from the original color image where mask is set
    if mask.shape[-1] > 0:
        # We're treating all instances as one, so collapse the mask into one layer
        mask = (np.sum(mask, -1, keepdims=True) >= 1) # 전체 풍선에 대해서, 풍선이 위치했던 곳에 대한 Mask 정보(True&False) 추출
        # An array with elements from x where condition is True, and elements from y elsewhere.
        # https://numpy.org/doc/stable/reference/generated/numpy.where.html
        splash = np.where(mask, image, gray).astype(np.uint8)
    else:
        splash = gray.astype(np.uint8)
    return splash
  • 어떻게 위와 같은 함수가 동작할까?
print('image shape:',image.shape, 'r mask shape:',r['masks'].shape)
mask = (np.sum(r['masks'], -1, keepdims=True) >= 1)
print('sum mask shape:',mask.shape)
  • np.sum() 테스트
import numpy as np
a = np.ones((10, 10, 3))
#print(a)
#print(np.sum(a))
print(np.sum(a, axis=-1).shape)             # 그냥 2차원 배열이다.
print(np.sum(a, -1, keepdims=True).shape)   # 3차원 배열의 가장 첫번쨰 원소에 2차원 배열이 들어가 있고, 그 2차원 배열 shape가 10*10 이다.
print(np.sum(a, -1, keepdims=True) >=1 )
print(np.sum(a, -1, keepdims=True)[:,:,0])

(10, 10)
(10, 10, 1)
  • np.where() 테스트
test_mask = (np.sum(a, -1, keepdims=True) >=1)
print(test_mask.shape)
for i in range(5):
    for j in range(5):
        test_mask[i, j, 0] = False
        
test_image = np.ones((10, 10, 3))
test_gray = np.zeros((10, 10, 3))
np.where(test_mask, test_image, test_gray)[:,:,0]
(10, 10, 1)





array([[0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [0., 0., 0., 0., 0., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])

10. Video에 Inferece 적용 후 color splash를 적용.

from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/balloon_dog02.mp4')
  • Video color splash를 적용한 함수를 생성해보자.
  • 이전부터 사용했던, cv2.VideoCapture 그대로를 사용할 것이다!
import cv2
import time

def detect_video_color_splash(model, video_input_path=None, video_output_path=None):

    cap = cv2.VideoCapture(video_input_path)
    codec = cv2.VideoWriter_fourcc(*'XVID')
    fps = round(cap.get(cv2.CAP_PROP_FPS))
    vid_writer = cv2.VideoWriter(video_output_path, codec, fps, (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),
                                                                 round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))))

    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print("총 Frame 개수: {0:}".format(total))

    frame_index = 0
    success = True
    while True:
        
        hasFrame, image_frame = cap.read()
        if not hasFrame:
            print('End of frame')
            break
        frame_index += 1
        print("frame index:{0:}".format(frame_index), end=" ")
        
        # OpenCV returns images as BGR, convert to RGB
        image_frame = image_frame[..., ::-1] 
        # ::-1은 slice step. 시작은 무조건 0 끝은 len
        print('End of frame')
            break
        frame_index += 1
        print("frame index:{0:}".format(frame_index), end=" ")
        
        # OpenCV returns images as BGR, convert to RGB
        image_frame = image_frame[..., ::-1] 
        # ::-1은 slice step. 시작은 무조건 0 끝은 len
        # https://numpy.org/doc/stable/user/basics.indexing.html#combining-index-arrays-with-slices 참조
        start=time.time()
        # Detect objects
        r = model.detect([image_frame], verbose=0)[0]
        print('detected time:', time.time()-start)
        # Color splash
        splash = color_splash(image_frame, r['masks'])
        # RGB -> BGR to save image to video
        splash = splash[..., ::-1] 
        # Add image to video writer
        vid_writer.write(splash)
    
    vid_writer.release()
    cap.release()       
    
    print("Saved to ", video_output_path)
    
detect_video_color_splash(model, video_input_path='../../data/video/balloon_dog02.mp4', 
                          video_output_path='../../data/output/balloon_dog02_output.avi')

  File "<ipython-input-20-f12d9e351b0b>", line 30
    break
    ^
IndentationError: unexpected indent
  • numpy index 머리가 너무 아프지만, 알아두면 분명히 좋을 것이다. 일단 필수 사이트는 아래와 같다
  • 참조사이트 : https://numpy.org/doc/stable/user/basics.indexing.html

      >>> y
      array([[ 0,  1,  2,  3,  4,  5,  6],
             [ 7,  8,  9, 10, 11, 12, 13],
             [14, 15, 16, 17, 18, 19, 20],
             [21, 22, 23, 24, 25, 26, 27],
             [28, 29, 30, 31, 32, 33, 34]])
      >>> y[0][1]
      1
      >>> y[0,1]
      1
      >>> y[1]
      array([ 7,  8,  9, 10, 11, 12, 13])
      >>> y[3]
      array([21, 22, 23, 24, 25, 26, 27])
      >>> y[[1,3]]
      array([[ 7,  8,  9, 10, 11, 12, 13],
             [21, 22, 23, 24, 25, 26, 27]])
      >>> y[1:5:2]
      array([[ 7,  8,  9, 10, 11, 12, 13],
             [21, 22, 23, 24, 25, 26, 27]])
      >>> y[1:5:2,[0,3,6]]
      array([[ 7, 10, 13],
             [21, 24, 27]])
      >>> y[1:5:2,::3]
      array([[ 7, 10, 13],
             [21, 24, 27]])
    
      >>> z[1,...,2]
      array([[29, 32, 35],
             [38, 41, 44],
             [47, 50, 53]])
      >>> z[1,:,:,2]
      array([[29, 32, 35],
             [38, 41, 44],
             [47, 50, 53]])
      >>> z[1] == z[1,:,:,:]
      True
      >>> z[1,...,2] == z[1,:,:,2]
      True # 즉( :,:,:,: 을 줄여쓰고 싶면 ... 을 사용하면 된다.)
    
  • 생성된 Output 파일을 Object Storage에 저장한 뒤 확인
!gsutil cp ../../data/output/balloon_dog02_output.avi gs://my_bucket_dlcv/data/output/balloon_dog02_output.avi

5. 데이터 학습을 위한 전체 흐름도

drawing

【Keras】Keras기반 Mask-RCNN - 오픈 소스 코드로 Inference

Keras 기반 Mask-RCNN를 이용해 Inference 를 수행해보자

  • /DLCV/Segmentation/mask_rcnn/Matterport패키지를_이용한_Segmentation.ipynb 파일 참조
  • Keras 기반, Mask RNN위한 최고의 오픈소스 코드 사용
  • matterport는 3D비전 회사

1. matterport/Mask_RCNN의 특징

  • 장점
    • 최적화 된 Mask RCNN 알고리즘으로 보다 뛰어난 성능
    • 전 세계에서 다양한 분야에서 사용 중
    • 다양한 편의 기능을 제공 및 이해를 위한 여러 샘플 코드 및 Document 제공
    • Mask Rcnn, Faster Rcnn을 이해하는데도 큰 도움을 주는 코드
    • 쉬운 Config 설정
  • 단점
    • Coco Dataset 포맷을 기반의 Generator를 사용해서, 원하는 데이터를 COCO 형태로 변환 필요
    • voc(xml)는 coco(json)로 바꾸면 된다.
    • (너무 친절해서) 자체 시각화 기능이 Matplotlib으로 되어 있어서 영상 Segmentation시 costomization 필요
    • COCO 데이터세트를 기반으로 하는 Evaluation을 한다. 따라서 자체적은 Evalution은 없고, COCO data의 PycocoTools Evaluation API을 사용한다.
  • 여러가지 프로젝트에서 이 페키지를 사용한 다양한 응용 에플리케이션 등이 있다.
  • 꾸준히 업그레이드가 되고 있다.
  • 시간을 충분히 투자해서 공부해도 좋은 페키지이다.
  • 제공되는 많은 sample 코드들을 공부하면 많은 도움이 된다.

2. matterport/Mask_RCNN으로 Inference 수행하기

  • Keras 2.2 version, Tensorflow 1.13 을 사용한다.
  • 코랩 사용한다면, 코랩 전용 압축 파일 참고해서 matterport/Mask_RCNN패키지의 main.py파일 바꾸기

  • 설치하기
      $ git clone https://github.com/matterport/Mask_RCNN.git
      $ cd Mask-RCNN
      $ pip install -r requirements.txt
      $ python setup.py install
    

    Setup을 이렇게 하고 나면, ./mrcnn/model.py ./mrcnn/utils.py 를 import해서 사용할 수 있게 된다.

  • ./mrcnn 내부의 파일 알아보기
    • config.py : 환경설정의 Defalut 값을 가진다(모두 대문자)
    • parallel_model.py : GPU 2개 이상 사용할 때
    • model.py : 모델에 관련된 핵심적인 함수, 클래스들이 들어 있다.
    • utils.py : coco dataset generater 를 사용한다.
    • visualization.py : 시각화를 위한 편리한 기능.(너무 편리하게 해놔서 문제), matplotlib, skimage를 사용하기 때문에, 영상 처리 시각화를 위해서 customization이 필요하다.

3. sample을 이용해서 코드를 이해해보자.

  • /DLCV/Segmentation/mask_rcnn/Matterport패키지를_이용한_Segmentation.ipynb 파일 참조

1. Matterport 패키지 모듈 및 클래스 활용

  • pretrained coco 모델을 로딩 후 단일 이미지와 영상 Segmentation 수행.
import os
import sys
import random
import math
import numpy as np
import cv2

drawing

  • python setup.py install 함으로써 mrcnn이 우리 python의 모듈이 되었다. import numpy하는 것처럼, import mncnn을 해서 사용할 수 있게 되었다.
  • class.__dict__, object.__dict__, module.__dict__ 를 사용하면 내부의 맴버 함수나 클래스, 객체, 모듈에 대한 정보를 알 수 있다. 참고 stackoverflow
from mrcnn import utils
import mrcnn.model as modellib
from mrcnn import visualize
  • Matterport로 pretrained된 coco weight 모델을 다운로드함(최초시)
  • utils.download_trained_weights에 있음
# URL from which to download the latest COCO trained weights
COCO_MODEL_URL = "https://github.com/matterport/Mask_RCNN/releases/download/v2.0/mask_rcnn_coco.h5"
with urllib.request.urlopen(COCO_MODEL_URL) as resp, open(coco_model_path, 'wb') as out:
        shutil.copyfileobj(resp, out)
from mrcnn import utils

ROOT_DIR = os.path.abspath('.')

# 최초에는 coco pretrained 모델을 다운로드함. 
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "./pretrained/mask_rcnn_coco.h5")

if not os.path.exists(COCO_MODEL_PATH):
    utils.download_trained_weights(COCO_MODEL_PATH)
!ls ~/DLCV/Segmentation/mask_rcnn/pretrained
mask_rcnn_coco.h5
mask_rcnn_inception_v2_coco_2018_01_28
mask_rcnn_inception_v2_coco_2018_01_28.tar.gz
  • MASK RCNN 모델을 위한 Config 클래스의 객체 생성 설정
  • mrcnn/utile/config.py

  • config 객체 생성 1 방법
from mrcnn.config import Config

infer_config = Config()
infer_config.BATCH_SIZE=1
infer_config.display()   # config 정보들이 쭉 print된다.
Configurations:
BACKBONE                       resnet101
BACKBONE_STRIDES               [4, 8, 16, 32, 64]
BATCH_SIZE                     1
BBOX_STD_DEV                   [0.1 0.1 0.2 0.2]
COMPUTE_BACKBONE_SHAPE         None
DETECTION_MAX_INSTANCES        100
DETECTION_MIN_CONFIDENCE       0.7
DETECTION_NMS_THRESHOLD        0.3
FPN_CLASSIF_FC_LAYERS_SIZE     1024
GPU_COUNT                      1
GRADIENT_CLIP_NORM             5.0
IMAGES_PER_GPU                 2
IMAGE_CHANNEL_COUNT            3
IMAGE_MAX_DIM                  1024
IMAGE_META_SIZE                13
IMAGE_MIN_DIM                  800
IMAGE_MIN_SCALE                0
IMAGE_RESIZE_MODE              square
IMAGE_SHAPE                    [1024 1024    3]
LEARNING_MOMENTUM              0.9
LEARNING_RATE                  0.001
LOSS_WEIGHTS                   {'rpn_class_loss': 1.0, 'rpn_bbox_loss': 1.0, 'mrcnn_class_loss': 1.0, 'mrcnn_bbox_loss': 1.0, 'mrcnn_mask_loss': 1.0}
MASK_POOL_SIZE                 14
MASK_SHAPE                     [28, 28]
MAX_GT_INSTANCES               100
MEAN_PIXEL                     [123.7 116.8 103.9]
MINI_MASK_SHAPE                (56, 56)
NAME                           None
NUM_CLASSES                    1
POOL_SIZE                      7
POST_NMS_ROIS_INFERENCE        1000
POST_NMS_ROIS_TRAINING         2000
PRE_NMS_LIMIT                  6000
ROI_POSITIVE_RATIO             0.33
RPN_ANCHOR_RATIOS              [0.5, 1, 2]
RPN_ANCHOR_SCALES              (32, 64, 128, 256, 512)
RPN_ANCHOR_STRIDE              1
RPN_BBOX_STD_DEV               [0.1 0.1 0.2 0.2]
RPN_NMS_THRESHOLD              0.7
RPN_TRAIN_ANCHORS_PER_IMAGE    256
STEPS_PER_EPOCH                1000
TOP_DOWN_PYRAMID_SIZE          256
TRAIN_BN                       False
TRAIN_ROIS_PER_IMAGE           200
USE_MINI_MASK                  True
USE_RPN_ROIS                   True
VALIDATION_STEPS               50
WEIGHT_DECAY                   0.0001
  • config 객체 생성 2 방법
# Config 클래스를 상속받아서 사용
from mrcnn.config import Config

#환경 변수는 모두 대문자 
class InferenceConfig(Config):
    # inference시에는 batch size를 1로 설정. 그리고 IMAGES_PER_GPU도 1로 설정. 
    GPU_COUNT = 1
    IMAGES_PER_GPU = 1 # BATCH_SIZE=1 과 같은 효과
    # NAME은 반드시 주어야 한다. 
    NAME='coco_infer'  # NAME을 꼭 주어야 한다.
    NUM_CLASSES=81     # Background 0번 + coco 80개 
    

infer_config = InferenceConfig()
# infer_config.display()
  • COCO ID와 클래스명 매핑
# matterport는 0을 Background로, 1부터 80까지 coco dataset 클래스 id/클래스 명 매핑. 
labels_to_names = {0:'BG',1: 'person',2: 'bicycle',3: 'car',4: 'motorbike',5: 'aeroplane',6: 'bus',7: 'train',8: 'truck',9: 'boat',10: 'traffic light',
                   11: 'fire hydrant',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: 'backpack',26: 'umbrella',27: 'handbag',28: 'tie',29: 'suitcase',30: 'frisbee',
                   31: 'skis',32: 'snowboard',33: 'sports ball',34: 'kite',35: 'baseball bat',36: 'baseball glove',37: 'skateboard',38: 'surfboard',39: 'tennis racket',40: 'bottle',
                   41: 'wine glass',42: 'cup',43: 'fork',44: 'knife',45: 'spoon',46: 'bowl',47: 'banana',48: 'apple',49: 'sandwich',50: 'orange',
                   51: 'broccoli',52: 'carrot',53: 'hot dog',54: 'pizza',55: 'donut',56: 'cake',57: 'chair',58: 'sofa',59: 'pottedplant',60: 'bed',
                   61: 'diningtable',62: 'toilet',63: 'tvmonitor',64: 'laptop',65: 'mouse',66: 'remote', 67: 'keyboard',68: 'cell phone',69: 'microwave',70: 'oven',
                   71: 'toaster',72: 'sink',73: 'refrigerator',74: 'book',75: 'clock',76: 'vase',77: 'scissors',78: 'teddy bear',79: 'hair drier',80: 'toothbrush' }

2. Inferecne 모델 구축 및 pretrained 사용

  • 아래 과정 정리
    1. mrcnn.model.MaskRCNN Class를 사용해서 객체를 생성한다.
    2. 기본적인 신경망 모델 객체가 생성됐다. weigts import 안함
      (3. InferenceConfig의 Name 설정안하면 에러 발생.)
    3. model object 생성!
    4. 이제 이 model을 가지고
      model.detect([cv2 read img])하면 inference 수행 끝!
# MS-COCO 기반으로 Pretrained 된 모델을 로딩
# snapshots 폴더 만들어 놓아야 함.
# model_dir는 보통 epoch마다 파일 저장하는 폴더 인데... inference랑 상관없지만..
import mrcnn.model as modellib

MODEL_DIR = os.path.join(ROOT_DIR,'snapshots') 
print(MODEL_DIR)
model = modellib.MaskRCNN(mode="inference",  model_dir=MODEL_DIR, config=infer_config) # snapshots에는 아무 지솓 안하니 걱정 ㄴ

model.load_weights(COCO_MODEL_PATH, by_name=True)
/home/sb020518/DLCV/Segmentation/mask_rcnn/snapshots

3. Inference 수행

import matplotlib.pyplot as plt
%matplotlib inline 

beatles_img = cv2.imread('../../data/image/beatles01.jpg')
# matterport는 내부적으로 image처리를 위해 skimage를 이용하므로 BGR2RGB처리함. 
beatles_img_rgb = cv2.cvtColor(beatles_img, cv2.COLOR_BGR2RGB)
 
results = model.detect([beatles_img_rgb], verbose=1)
# verbose를 하면 정보를 출력해준다.
Processing 1 images
image                    shape: (633, 806, 3)         min:    0.00000  max:  255.00000  uint8
molded_images            shape: (1, 1024, 1024, 3)    min: -123.70000  max:  151.10000  float64
image_metas              shape: (1, 93)               min:    0.00000  max: 1024.00000  float64
anchors                  shape: (1, 261888, 4)        min:   -0.35390  max:    1.29134  float32
print(type(results))
print(len(results))
print(type(results[0]))
print(results[0].keys())
print(type(results[0]['class_ids']))
print(results[0]['class_ids'].shape) # 18개의 객체 발견
# 각각 Region of interest, class ID, Confidence, Mask 정보
<class 'list'>
1
<class 'dict'>
dict_keys(['rois', 'class_ids', 'scores', 'masks'])
<class 'numpy.ndarray'>
(18,)
# results[0]['masks']는 object 별로 mask가 전체 이미지에 대해서 layered된 image 배열을 가지고 있음.  
results[0]['rois'].shape, results[0]['scores'].shape, results[0]['class_ids'].shape, results[0]['masks'].shape
((18, 4), (18,), (18,), (633, 806, 18))
  • result 정보 분석하기
    • (18, 4) : 18개의 bounding box 좌상단 우하단
    • (18,) : 18개의 클래스 ID
    • (18,) : 18개의 confidence
    • (633, 806, 18) : input 이미지가 (633, 806, 3)였다. (633, 806)에 대한 false true 정보가 담겨 있다
  • 이 정보를 visualize 함수로 쉽게 시각화 할 수 있다.

지금까지의 모델 중 가장 안정성과 성능이 뛰어난 것을 확인할 수 있다. 아주 최적화가 잘 된 패키지이다. 미친 성능이다.

from mrcnn import visualize

r = results[0]
class_names = [value for value in labels_to_names.values()]
visualize.display_instances(beatles_img_rgb, r['rois'], r['masks'], r['class_ids'], 
                            class_names, r['scores'])

drawing

4. Inference 수행 작업만 함수화

import time

wick_img = cv2.imread('../../data/image/john_wick01.jpg')
wick_img_rgb = cv2.cvtColor(wick_img, cv2.COLOR_BGR2RGB)

def get_segment_result(img_array_list, verbose):
    
    start_time = time.time()
    results = model.detect(img_array_list, verbose=1)
    
    if verbose==1:
        print('## inference time:{0:}'.format(time.time()-start_time))
    
    return results

r = get_segment_result([wick_img_rgb], verbose=1)[0]
visualize.display_instances(wick_img_rgb, r['rois'], r['masks'], r['class_ids'], 
                            class_names, r['scores'])

cpu로 24초 소요. P100으로 0.1ch thdy

drawing

5. 위의 함수로 Video에 Segmentation 활용

  • MaskRCNN 패키지는 visualize.display_instances() 함수내부에서 matplotlib를 이용하여 자체 시각화를 수행.
  • 따라서 우리는 이미지를 결과로 얻고 싶은데 matplotlib를 그냥 해버리니 우리가 직접 이미지 결과 추출 함수 구현
  • visualize.display_instances() 코드 보기 - 처음부터 _, ax = plt.subplots(1, figsize=figsize) 객체를 선언해서 쭈욱 그려나가는 모습을 볼 수 있다.
  • 이 코드의 대부분을 차용해서 아래의 get_segmented_image 함수를 정의한다.
  • visualize.display_instances()에서는 matplot.show으로 함수를 마무리 하지만, get_segmented_image는 최종으로 그려진 이미지를 return한다.
  • visualize.display_instances()에서는 from matplotlib.patches import Polygon 으로 함수를 사용 하지만, get_segmented_image는 cv2.polylines를 사용한다.
from mrcnn.visualize import *
import cv2

def get_segmented_image(img_array, boxes, masks, class_ids, class_names,
                      scores=None, show_mask=True, show_bbox=True, colors=None, captions=None):
   
    # Number of instances
    N = boxes.shape[0]
    if not N:
        print("\n*** No instances to display *** \n")
    else:
        assert boxes.shape[0] == masks.shape[-1] == class_ids.shape[0]
    

    # Generate random colors
    colors = colors or random_colors(N)

    # Show area outside image boundaries.
    height, width = img_array.shape[:2]

    masked_image = img_array.astype(np.uint32).copy()

    for i in range(N):
        color = np.array(colors[i])*255
        color = color.tolist()

        # Bounding box 그리기
        if not np.any(boxes[i]):
            # Skip this instance. Has no bbox. Likely lost in image cropping.
            continue
        y1, x1, y2, x2 = boxes[i]
        
        if show_bbox:
            cv2.rectangle(img_array, (x1, y1), (x2, y2), color, thickness=1 )

        # Label 표시하기
        if not captions:
            class_id = class_ids[i]
            score = scores[i] if scores is not None else None
            label = class_names[class_id]
            caption = "{} {:.3f}".format(label, score) if score else label
        else:
            caption = captions[i]
            
        cv2.putText(img_array, caption, (x1, y1+8), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), thickness=1)
        
        # Mask 정보 그려 넣기
        # 클래스별 mask 정보를 추출 
        mask = masks[:, :, i]
        if show_mask:
            # visualize 모듈의 apply_mask()를 적용하여 masking 수행. 
            # from mrcnn.visualize import * 모든 함수 import
            img_array = apply_mask(img_array, mask, color)
            
            # mask에 contour 적용. 
            padded_mask = np.zeros(
                            (mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8)
            padded_mask[1:-1, 1:-1] = mask
            contours = find_contours(padded_mask, 0.5)
            for verts in contours:
                # padding 제거. 아래에서 verts를 32bit integer로 변경해야 polylines()에서 오류 발생하지 않음. 
                verts = verts.astype(np.int32)
                #x, y 좌표 교체
                verts = np.fliplr(verts) - 1
                cv2.polylines(img_array, [verts], True, color, thickness=1)
    
    return img_array

단일 IMAGE에 적용

import time

wick_img = cv2.imread('../../data/image/john_wick01.jpg')
wick_img_rgb = cv2.cvtColor(wick_img, cv2.COLOR_BGR2RGB)

r = get_segment_result([wick_img_rgb], verbose=1)[0]
segmented_img = get_segmented_image(wick_img_rgb, r['rois'], r['masks'], r['class_ids'], 
                                    class_names, r['scores'])

plt.figure(figsize=(16, 16))
plt.imshow(segmented_img)

Video Segmentation 적용

import time

video_input_path = '../../data/video/John_Wick_small.mp4'
# video output 의 포맷은 avi 로 반드시 설정 필요. 
video_output_path = '../../data/output/John_Wick_small_matterport01.avi'

cap = cv2.VideoCapture(video_input_path)
codec = cv2.VideoWriter_fourcc(*'XVID')
fps = round(cap.get(cv2.CAP_PROP_FPS))

vid_writer = cv2.VideoWriter(video_output_path, codec, fps, (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))))

total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print("총 Frame 개수: {0:}".format(total))

frame_index = 0
while True:
    
    hasFrame, image_frame = cap.read()
    if not hasFrame:
        print('End of frame')
        break
    
    frame_index += 1
    print("frame index:{0:}".format(frame_index), end=" ")
    r = get_segment_result([image_frame], verbose=1)[0]
    segmented_img = get_segmented_image(image_frame, r['rois'], r['masks'], r['class_ids'], 
                                    class_names, r['scores'])
    vid_writer.write(segmented_img)
    
vid_writer.release()
cap.release()       
!gsutil cp ../../data/output/John_Wick_small_matterport01.avi gs://my_bucket_dlcv/data/output/John_Wick_small_matterport01.avi

drawing

다른 동영상에 적용.

video_input_path = '../../data/video/London_Street.mp4'
# video output 의 포맷은 avi 로 반드시 설정 필요. 
video_output_path = '../../data/output/London_Street_matterport01.avi'

cap = cv2.VideoCapture(video_input_path)
codec = cv2.VideoWriter_fourcc(*'XVID')
fps = round(cap.get(cv2.CAP_PROP_FPS))
vid_writer = cv2.VideoWriter(video_output_path, codec, fps, (round(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),round(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))))

total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print("총 Frame 개수: {0:}".format(total))

import time

frame_index = 0
while True:
    
    hasFrame, image_frame = cap.read()
    if not hasFrame:
        print('End of frame')
        break
    
    frame_index += 1
    print("frame index:{0:}".format(frame_index), end=" ")
    r = get_segment_result([image_frame], verbose=1)[0]
    segmented_img = get_segmented_image(image_frame, r['rois'], r['masks'], r['class_ids'], 
                                    class_names, r['scores'])
    vid_writer.write(segmented_img)
    
vid_writer.release()
cap.release()       

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

drawing

【Tensorflow】Mask-RCNN inference 수행하기

Tensorflow v1.3 API로, Mask-RCNN inference 수행하기
/DLCV/Segmentation/mask_rcnn/Tensorflow를_이용한_Mask_RCNN_Segmentation.ipynb 참고하기
이번 post를 참고해서, Tensorflofw inference 흐름 파악하기

1. 이미지 Read

  • Tensorflow에서 Pretrained 된 Inference모델(Frozen graph)을 다운로드 받은 후 이를 이용해 OpenCV에서 Inference 모델 생성
  • 이전 post에서 다운 받은 .pb 파일 그대로 사용할 예정
    • https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API 에 다운로드 URL 있음.
import cv2
import matplotlib.pyplot as plt
import numpy  as np
%matplotlib inline

beatles_img = cv2.imread('../../data/image/driving2.jpg')
beatles_img_rgb = cv2.cvtColor(beatles_img, cv2.COLOR_BGR2RGB)

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

drawing

# coco dataset의 클래스 ID별 클래스명 매핑
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'}


#masking 시 클래스별 컬러 적용
colors = list(
    [[0, 255, 0],
     [0, 0, 255],
     [255, 0, 0],
     [0, 255, 255],
     [255, 255, 0],
     [255, 0, 255],
     [80, 70, 180],
     [250, 80, 190],
     [245, 145, 50],
     [70, 150, 250],
     [50, 190, 190]] )

2. import graph & 한 이미지 Inference

  • tensorflow inference 수행하기
  • 아래의 내용이 이해가 알될 수도 있다. 그러면 꼭 이전 post(OpenCV-Maskrcnn) 참고하기
  • 주의! sess.run 할 때, 이전과 다르게 graph.get_tensor_by_name(‘detection_masks:0’)도 가져와야한다.
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/mask_rcnn_inception_v2_coco_2018_01_28/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/driving2.jpg')
    draw_img = img.copy()
    
    img_height = img.shape[0]
    img_width = img.shape[1]
    #inp = cv2.resize(img, (300, 300))
    # OpenCV로 입력받은 BGR 이미지를 RGB 이미지로 변환 
    inp = img[:, :, [2, 1, 0]] 

    start = time.time()
    # Object Detection 수행 및 mask 정보 추출. 'detection_masks:0' 에서 mask결과 추출 
    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'),
                    sess.graph.get_tensor_by_name('detection_masks: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)
    score_threshold = 0.5
    mask_threshold = 0.4
    
    #### out 결과, 타입 Debugging #### 
    print("### out 크기와 타입:", len(out), type(out))
    print(out[0].shape, out[1].shape, out[2].shape, out[3].shape, out[4].shape)
    print('num_detection:',out[0], 'score by objects:', out[1][0], 'bounding box')
    # Bounding Box 시각화 
    # Detect된 Object 별로 bounding box 시각화 
    num_detections = int(out[0][0])
    for i in range(num_detections):
        # Object별 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]]
        # Object별 mask 정보 추출
        classMask = out[4][0][i]
        
        if score > score_threshold:
            left = int(bbox[1] * img_width)
            top = int(bbox[0] * img_height)
            right = int(bbox[3] * img_width)
            bottom = int(bbox[2] * img_height)
            # cv2의 rectangle(), putText()로 bounding box의 클래스명 시각화 
            cv2.rectangle(draw_img, (left, top), (right,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)
            
            # 원본 이미지의 object 크기에 맞춰 mask 크기 scale out 
            scaled_classMask = cv2.resize(classMask, (right - left + 1, bottom - top + 1))
            print('원본 이미지 비율로 scale out된 classMask shape:', scaled_classMask.shape)
            # 지정된 mask Threshold 값 이상인지 True, False boolean형태의 mask 정보 생성. 
            s_mask_b = (scaled_classMask > mask_threshold)
            print('scaled mask shape:', s_mask_b.shape, 'scaled mask pixel count:', s_mask_b.shape[0]*s_mask_b.shape[1],
                  'scaled mask true shape:',s_mask_b[s_mask_b==True].shape, 
                  'scaled mask False shape:', s_mask_b[s_mask_b==False].shape)
            
            # mask를 적용할 bounding box 영역의 image 추출
            before_mask_roi = draw_img[top:bottom+1, left:right+1]
            print('before_mask_roi:', before_mask_roi.shape)
            
            
            # Detect된 Object에 mask를 특정 투명 컬러로 적용. 
            colorIndex = np.random.randint(0, len(colors)-1)
            color = colors[colorIndex]
            after_mask_roi = draw_img[top:bottom+1, left:right+1][s_mask_b]
            draw_img[top:bottom+1, left:right+1][s_mask_b] = ([0.3*color[0], 0.3*color[1], 0.3*color[2]] + 0.6 * after_mask_roi).astype(np.uint8)
            # Detect된 Object에 윤곽선(contour) 적용. 
            s_mask_i = s_mask_b.astype(np.uint8)
            contours, hierarchy = cv2.findContours(s_mask_i,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
            cv2.drawContours(draw_img[top:bottom+1, left:right+1], contours, -1, color, 1, cv2.LINE_8, hierarchy, 100)

plt.figure(figsize=(14, 14))
img_rgb = cv2.cvtColor(draw_img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)
plt.show()
car: 0.9624
원본 이미지 비율로 scale out된 classMask shape: (414, 510)
scaled mask shape: (414, 510) scaled mask pixel count: 211140 scaled mask true shape: (155967,) scaled mask False shape: (55173,)
before_mask_roi: (414, 510, 3)
car: 0.9441
원본 이미지 비율로 scale out된 classMask shape: (225, 251)
scaled mask shape: (225, 251) scaled mask pixel count: 56475 scaled mask true shape: (40707,) scaled mask False shape: (15768,)
before_mask_roi: (225, 251, 3)
traffic light: 0.9239
원본 이미지 비율로 scale out된 classMask shape: (91, 50)
scaled mask shape: (91, 50) scaled mask pixel count: 4550 scaled mask true shape: (4003,) scaled mask False shape: (547,)
before_mask_roi: (91, 50, 3)
car: 0.9119
원본 이미지 비율로 scale out된 classMask shape: (153, 183)
scaled mask shape: (153, 183) scaled mask pixel count: 27999 scaled mask true shape: (21834,) scaled mask False shape: (6165,)
before_mask_roi: (153, 183, 3)
car: 0.8999
원본 이미지 비율로 scale out된 classMask shape: (67, 94)
scaled mask shape: (67, 94) scaled mask pixel count: 6298 scaled mask true shape: (5041,) scaled mask False shape: (1257,)
before_mask_roi: (67, 94, 3)

drawing

3. 위의 작업 함수화 및 함수 사용

## 이전 opencv에서 선언한 get_box_info()함수에 left, top, right, bottom을 가지오는 bbox 위치 인덱스 변경
def get_box_info(bbox, img_width, img_height):
    
    left = int(bbox[1] * img_width)
    top = int(bbox[0] * img_height)
    right = int(bbox[3] * img_width)
    bottom = int(bbox[2] * img_height)
    
    left = max(0, min(left, img_width - 1))
    top = max(0, min(top, img_height - 1))
    right = max(0, min(right, img_width - 1))
    bottom = max(0, min(bottom, img_height - 1))
    
    return left, top, right, bottom

# 이전 opencv에서 선언한 draw_box()함수에 classId, score 인자가 추가됨.     
def draw_box(img_array, classId, score, box, img_width, img_height, is_print=False):
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    left, top, right, bottom = get_box_info(box, img_width, img_height)
    text = "{}: {:.4f}".format(labels_to_names[classId], score)
    
    if is_print:
        pass
        #print("box:", box, "score:", score, "classId:", classId)
    
    cv2.rectangle(img_array, (left, top), (right, bottom), green_color, thickness=2 )
    cv2.putText(img_array, text, (left, top-3), cv2.FONT_HERSHEY_SIMPLEX, 0.5, red_color, thickness=1)
    
    return img_array
    
def draw_mask(img_array, bbox, classMask, img_width, img_height, mask_threshold, is_print=False):
        
        left, top, right, bottom = get_box_info(bbox, img_width, img_height)
        # 원본 이미지의 object 크기에 맞춰 mask 크기 scale out 
        scaled_classMask = cv2.resize(classMask, (right - left + 1, bottom - top + 1))
        s_mask_b = (scaled_classMask > mask_threshold)
        before_mask_roi = img_array[top:bottom+1, left:right+1]
        
        # mask를 적용할 bounding box 영역의 image 추출하고 투명 color 적용. 
        colorIndex = np.random.randint(0, len(colors)-1)
        #color = colors[colorIndex]
        color=(224, 32, 180)
        after_mask_roi = img_array[top:bottom+1, left:right+1][s_mask_b]
        img_array[top:bottom+1, left:right+1][s_mask_b] = ([0.3*color[0], 0.3*color[1], 0.3*color[2]] + 0.6 * after_mask_roi).astype(np.uint8)
        # Detect된 Object에 윤곽선(contour) 적용. 
        s_mask_i = s_mask_b.astype(np.uint8)
        contours, hierarchy = cv2.findContours(s_mask_i,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
        cv2.drawContours(img_array[top:bottom+1, left:right+1], contours, -1, color, 1, cv2.LINE_8, hierarchy, 100)
        
        return img_array
import time

def detect_image_mask_rcnn_tensor(sess, img_array, conf_threshold, mask_threshold, use_copied_array, is_print=False):
    
    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
    
    img_height = img_array.shape[0]
    img_width = img_array.shape[1]
    
    # BGR을 RGB로 변환하여 INPUT IMAGE 입력 준비
    inp = img_array[:, :, [2, 1, 0]]  

    start = time.time()
    # Object Detection 수행 및 mask 정보 추출. 'detection_masks:0' 에서 mask결과 추출 
    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'),
                    sess.graph.get_tensor_by_name('detection_masks:0')],
                   feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
    if is_print:
        print('Segmentation Inference time {0:}'.format(round(time.time() - start, 4)))
        
    num_detections = int(out[0][0])

    for i in range(num_detections):
        # Object별 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]]
        # Object별 mask 정보 추출
        classMask = out[4][0][i]

        if score > conf_threshold:
            draw_box(img_array , classId, score, bbox, img_width, img_height, is_print=is_print)
            draw_mask(img_array, bbox, classMask, img_width, img_height, mask_threshold, is_print=is_print)
    
    return img_array
#inference graph를 읽음. .
with tf.gfile.FastGFile('./pretrained/mask_rcnn_inception_v2_coco_2018_01_28/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 = detect_image_mask_rcnn_tensor(sess, img, conf_threshold=0.5, mask_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)

drawing

4. 위에서 만든 함수 사용해서, Video Segmentation 적용

  • 10fps 정도의 좋은 속도가 나오는 것을 확인할 수 있다.
  • 아래에 sess.close() 가 있다. 이것은 사실이 코드가 먼저 실행 되어야 한다. 이 코드는 sess을 with로 실행 하지 않음.
    drawing
from IPython.display import clear_output, Image, display, Video, HTML
Video('../../data/video/London_Street.mp4')
video_input_path = '../../data/video/London_Street.mp4'
# linux에서 video output의 확장자는 반드시 avi 로 설정 필요. 
video_output_path = '../../data/output/London_Street_mask_rcnn_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 )

frame_index = 0
while True:
    hasFrame, img_frame = cap.read()
    if not hasFrame:
        print('더 이상 처리할 frame이 없습니다.')
        break
    frame_index += 1
    print("frame index:{0:}".format(frame_index), end=" ")
    draw_img_frame = detect_image_mask_rcnn_tensor(sess, img_frame, conf_threshold=0.5, mask_threshold=0.4, use_copied_array=False, is_print=True)
    vid_writer.write(draw_img_frame)
# end of while loop

vid_writer.release()
cap.release()  

!gsutil cp ../../data/output/London_Street_mask_rcnn_01.avi gs://my_bucket_dlcv/data/output/London_Street_mask_rcnn_01.avi
sess.close()  # 

drawing

【26살】 존경하는 선생님으로 부터의 배움 정리

가끔, 이충권 선생님과 전한길 선생님의 유투브 쓴소리를 들으면서, 나의 마음을 다잡곤 한다… 그 내용을 정리해 보았다.

나는 가끔, 내가 가장 존경하는 선생님이신 이충권 선생님과 전한길 선생님의 유투브 쓴소리를 들으면서, 나의 마음을 다잡곤 한다.
‘그래. 나를 위한 인생이 아닌, 나의 소중한 사람을 위한 인생을 살아가자’
‘다른 사람이랑 비교하지말고 경쟁하지 말고, 나 스스로와 싸우자. 어제의 나와, 현재 하기 싫어하는 나와 싸워 이기자’
라는 생각을 하면서 마음을 다잡고, 다시 공부하고 다시 운동하며 다시 노력하고 다시 최선을 다 한다.

최근 면접(인성면접)을 준비하면서 나의 생각과 관점에 대해 정리할 필요가 있었다. 나의 생각과 관점 그리고 가치관이 즉! 존경하는 선생님께 배운 내용을 다시 정리해 보았다.
이러한 배움을 할 수 있다는 것에 감사하고, 이러한 가르침을 주신 이충권 선생님, 전한길 선생님 정말 정말 감사합니다…

이충권 선생님 배움 정리

  1. 자기한테 독한 사람이 자기한테 덜 독한 사람 지배하고 사는게 세상이야. 내 자신이 독하지 못하고 게으르면 절대 용서하지 않아. 몸뚱아리가 시키는 대로 한다는 자체가 자존심 상해.
  2. 깡다구로 해. 참아. 그래 봤자 니 몸이잖아. 세상을 너 혼자 산다고 생각하냐. 다른 사람의 지배를 받는다. 자기한테 독한 세끼가 자기한테 덜 독한 세끼 지배하고 사는게 세상이다. 자기 몸이 하라는데로 하면 나중에 지랄 같은 상황이 생겨. 니 몸을 니가 지배하라고. 공부하라면 공부하고. 운동하라면 운동하고. 깡다구로 사는 거다.
  3. 몰입 상태. 사람이 가장 행복한 순간을 느낄 때. 그때가 바로 몰입 상태 일 때라고 한다. 일이든 공부든 운동이든.
  4. 뜨거운거 잡고 손이 다 타기 전까지 참을 수 있나? 고통과 자극 속에 끈기와 깡다구로 버텨야해요.
  5. 귀신? 그런거 안 무서워. 극복해서 이길 생각을 하라. 도망가지 말고. 무언가가 내 앞을 와서 가로 막잖아 그럼 부딪혀서 해결하려고 노력해야지! 도망가지 말고 그 상황을 끝을 봐! 끝을 보라고. 도망가지 말고.
  6. 공부하는 주제에 왜 우울해 해. 앞으로 더 잘살라고 공부하는데 웃고 행복해야지. 지금 상황이 안 좋다고? 항상 안 좋을까? 과연 계속 안 좋을까? 도대체 어디까지 올려놓으려고 나를 이 밑에 까지 내려놓을까? 라고 생각해. 못알아 듣겠다. 운을 믿어라. 운이 굉장히 좋다고 믿어라. 이것이 운의 기운을 트게 하는 방법이다. 어차피 다 잘되어 있는 상황에서 타이머신 타고 놀러왔다고 생각해라.
  7. 눈빛이 얼마나 중요한지 아나? 눈빛은 정말 중요하다. 눈빛에 그 사람 인생이 담겨있다.
  8. 모르는 것은 절대로 이상한게 아니고 창피한게 아니다. 지금 약간 밀렸다고 쭈그러들 필요없다. 모르는 것은 물어보고 알아가면 되는 것이다.
  9. 껄딱 고개 처럼. 날이 밝기 전에 가장 어두운 것 처럼.
  10. 스스로에게 보상하지 마라. 보상은 사회가 해주는 거다. 어떤 절대자가 있으면 절대 용서하지 않을거야. 절대 스스로에게 보상하지 마라.
  11. 아니다 싶을 때, 옳지 않다 싶을 때는 하지마세요. 그게 얻는 것 같죠 훨씬 잃는게 많아요. 하지마라면 하지마.
  12. 갑자기 너무 힘들잖아 어떻게 해야 돼. 그냥해라. 둔하지 근데 둔한 사람이 이긴다. 성실히 꾸준히 하면 반드시 이긴다. 생각을 하지말고 따라해요. 그냥 하세요. 성공하고 싶니? 그럼 그냥 해라. 한결같이 하려고 노력하라. 노력을 습관적으로 고통스럽게 하세요. 힘들면 반복하래요 반복하면 힘들지 않대요. 몸이 시키는대로 하지 말고, 내가 몸에게 휴식을 부여해주어라. 내가 일어나라면 몸이 일어나야하고, 뛰어라하면 죽기 전까지 뛰어야 돼.

전한길 선생님 배움 정리

  1. 현재 태어난 것에 정말로 감사한거고. 대한민국에 태어난 것 만으로도 행복하고 감사한 것이다.
  2. 본인이 행복하기 위해서는 항상 감사해야한다. 감사하다. 훌륭하다. 신기하다. 위대하다.
  3. 돈보다 더 가치있는 건강과 청춘이 있음에 감사하고 긍정적으로 생각하라.
  4. 삶이라는 것은 항상 가시밭길 오르막길 내리막길이 있더라. 인생은 살아 볼만한 가치가 있다. 인생은 생방송이다. 고통도 실패도 성공도 즐긴다. 도화지에 나의 인생을 그려가는 중이다. 어떤 것이라도 다 소중한 것이고 즐겨야 한다. 재방송은 재미없다. 생방송은 재미있잖아?
  5. 대기만성. 큰 사람이 되기 위해서는 많은 노력과 시간이 필요함.
  6. 새옹지마. 미래는 절대 알 수 없고, 항상 긍정적으로 생각해야한다.

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

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

Mask-RCNN 수행하기 - OpenCV DNN 모듈
OpenCV 사용과정 요약은 이전 Post 참조

주의 사항

  1. 지금까지와의 과정은 다르다. Segmentation 정보도 가져와야 한다.
    따라서 그냥 forward()하면 안되고, foward([‘detection_out_final’, ‘detection_masks’])를 해줘야 한다.
  2. Network에서 예측된 Mask정보를 원본 이미지 비율로 확장해야한다.
  3. 시각화를 해야하는데, 그냥 원본 이미지에 mask정보를 덮어버리면 안되고, 약간 투명하게 mask정보를 시각화해야한다.
  4. conda activate tf113 환경 사용하지
  5. /home/sb020518/DLCV/Segmentation/mask_rcnn 파일 참고할 것
  6. 여기에서는 cv2사용하는 것 말고, 전처리 및 데이터 사용에 대해서 깊게 살펴보기

0. OpenCV 파일 다운로드

  • Tensorflow에서 Pretrained 된 Inference모델(Frozen graph)와 환경파일을 다운로드 받은 후 이를 이용해 OpenCV에서 Inference 모델 생성
  • https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API 에 다운로드 URL 있음.
  • pretrained 모델은 http://download.tensorflow.org/models/object_detection/mask_rcnn_inception_v2_coco_2018_01_28.tar.gz 에서 다운로드 후 압축 해제
  • pretrained 모델을 위한 환경 파일은 https://raw.githubusercontent.com/opencv/opencv_extra/master/testdata/dnn/mask_rcnn_inception_v2_coco_2018_01_28.pbtxt 에서 다운로드. 하고 이름은 graph.pbtxt 로 바꿔주기
  • download된 모델 파일과 config 파일을 인자로 하여 inference 모델을 DNN에서 로딩함.
import cv2
import matplotlib.pyplot as plt
import numpy  as np
%matplotlib inline

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

plt.figure(figsize=(12, 12))
plt.axis('off')
plt.imshow(img_rgb)

drawing

  • Tensorflow의 Object Detection 모델 Weight인 frozen_inference_graph와 graph.pbtxt를
  • 이용하여 Opencv 의 dnn Network 모델로 로딩
  • 이전에는 forward()만을 사용했지만 이제는 boxes, masks = cv_net.forward([‘detection_out_final’, ‘detection_masks’])
  • 과거 yolo를 사용했을때, cv_outs = cv_net_yolo.forward(outlayer_names) 를 사용했다.
  • forward([,])에 들어가야하는 인자는 layer name이 되어야 한다!!

1. 신경망 cv_net 구상하기

cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/mask_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/mask_rcnn_inception_v2_coco_2018_01_28/graph.pbtxt')

blob = cv2.dnn.blobFromImage(img , swapRB=True, crop=False)
cv_net.setInput(blob)

# Bounding box 정보는 detection_out_final layer에서 mask 정보는 detection_masks layer에서 추출. 
boxes, masks = cv_net.forward(['detection_out_final', 'detection_masks'])
layer_names = cv_net.getLayerNames()
outlayer_names = [layer_names[i[0] - 1] for i in cv_net.getUnconnectedOutLayers()]
print(type(layer_names),len(layer_names))
print(layer_names.index('detection_out_final'), layer_names.index('detection_masks'))
<class 'list'> 332
260 331
# coco dataset의 클래스 ID별 클래스명 매핑
labels_to_names_seq= {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'}



#masking 시 클래스별 컬러 적용
colors = list(
    [[0, 255, 0],
     [0, 0, 255],
     [255, 0, 0],
     [0, 255, 255],
     [255, 255, 0],
     [255, 0, 255],
     [80, 70, 180],
     [250, 80, 190],
     [245, 145, 50],
     [70, 150, 250],
     [50, 190, 190]] )
print('boxes shape:', boxes.shape, 'masks shape:', masks.shape)
# 각각이 무슨 역할인지는 바로 다음 코드 참조
# 지금 100개의 객체를 찾았다. confidence를 이용해서 몇개 거르긴 해야 함
boxes shape: (1, 1, 100, 7) masks shape: (100, 90, 15, 15)

2. 하나의 객체 Detect 정보 시각화 하기(꼭 알아두기)

  • 발견된 첫번째 객체에 대해서만, mask정보 추출 및 전처리

    drawing

numClasses = masks.shape[1]
numDetections = boxes.shape[2]

# opencv의 rectangle(), putText() API는 인자로 들어온 IMAGE array에 그대로 수정작업을 수행하므로 bounding box 적용을 위한 
# 별도의 image array 생성. 
draw_img = img.copy()

img_height = draw_img.shape[0]
img_width = draw_img.shape[1]

conf_threshold = 0.5
mask_threshold = 0.3

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

# 이미지를 mask 설명을 위해서 iteration을 한번만 수행. 
#for i in range(numDetections):
for i in range(1):
    box = boxes[0, 0, i] # box변수의 7개 원소를 가져온다.
    mask = masks[i]      # 90, 15, 15 shaep의 list. 90 : 각 클래스에 대해서 분류 이미지 depth, 15 x 15를 해당 객체 box size로 확장해야함
    score = box[2]       # 아하 box변수의 3번째 원소가 score값
    if score > conf_threshold:
        classId = int(box[1])             
        left = int(img_width * box[3])  # 정규화 되어 있으니까, w,h랑 곱해주는거 잊지말기
        top = int(img_height * box[4])
        right = int(img_width * box[5])
        bottom = int(img_height * box[6])
        
        # 이미지에 bounding box 그림 그리기
        text = "{}: {:.4f}".format(labels_to_names_seq[classId], score)
        cv2.rectangle(draw_img, (left, top), (right, bottom), green_color, thickness=2 )
        cv2.putText(draw_img, text, (left, top-3), cv2.FONT_HERSHEY_SIMPLEX, 0.3, red_color, 1)

        # mask 정보 처리하기
        classMask = mask[classId] # (15, 15)
        # 15 x 15 를 해당 객체 box size로 확장해야함
        scaled_classMask = cv2.resize(classMask, (right - left + 1, bottom - top + 1)) # (123, 224)
        # 지정된 mask Threshold 값 이상인지 True, False boolean형태의 mask 정보 생성. 
        s_mask_b = (scaled_classMask > mask_threshold) # bool형태의 (123, 224) 리스트 생성
        print('scaled mask shape:', s_mask_b.shape, '\nscaled mask pixel count:', s_mask_b.shape[0]*s_mask_b.shape[1],
              '\nscaled mask true shape:',s_mask_b[s_mask_b==True].shape, 
              '\nscaled mask False shape:', s_mask_b[s_mask_b==False].shape)
        # 원본 이미지의 bounding box 영역만 image 추출
        # 참고로 copy()해서 가져온게 아니기 때문에, before_mask_roi를 바꾸면 draw_img도 바뀐다
        before_mask_roi = draw_img[top:bottom+1, left:right+1]
        print('before_mask_roi:', before_mask_roi.shape)
scaled mask shape: (123, 224) 
scaled mask pixel count: 27552 
scaled mask true shape: (22390,) 
scaled mask False shape: (5162,)
before_mask_roi: (123, 224, 3)
  • 위 코드에서 얻은 첫번째 객체에 대한 마스크 정보를 이용해 시각화 준비.
vis_mask = (s_mask_b * 255).astype("uint8")  # true자리는 255로 만들어 준다

# mask정보를 덮어주는 작업을, cv2.bitwise_and 모듈을 사용해서 쉽게 한다.
# 객체가 있는 true자리만 그대로 놔두고, 아니면 검은색으로 바꿔버린다. vis_mask의 255가 검은색이다.
instance = cv2.bitwise_and(before_mask_roi, before_mask_roi, mask=vis_mask)

fig, (ax1, ax2, ax3, ax4) = plt.subplots(figsize=(8, 8), ncols=4, nrows=1)

ax1.set_title('network detected mask')
ax1.axis('off')
ax1.imshow(classMask)

ax2.set_title('resized mask')
ax2.axis('off')
ax2.imshow(scaled_classMask)


ax3.set_title('Before Mask ROI')
ax3.axis('off')
ax3.imshow(before_mask_roi)

ax4.set_title('after Mask ROI')
ax4.axis('off')
ax4.imshow(instance)

drawing

  • Detected된 object에 mask를 특정 투명 컬러로 적용후 시각화
  • 엄청 신기하게 하니까 알아두기
  • 이 코드를 보고 이해 안되면 최대한 이해해 보기

drawing

# 색갈을 하나 랜덤하게 골라서
colorIndex = np.random.randint(0, len(colors)-1)
color = colors[colorIndex]
after_mask_roi = draw_img[top:bottom+1, left:right+1][s_mask_b] # 필요한 부분만 때온다. 주의!! 2차원이 1차원으로 바뀐다! (depth정보는 그대로)
print(after_mask_roi.shape) 
# 이 코드를 계속 실행하면, 색을 덮고덮고덮고덮고를 반복한다. 랜덤으로 색이 바뀌는게 아니라...
draw_img[top:bottom+1, left:right+1][s_mask_b] = ([0.1*color[0], 0.1*color[1], 0.1*color[2]] + (0.5 * after_mask_roi)).astype(np.uint8)
# 2차원이든 1차원이든 쨋든 원하는 부분만 색을 바꿔준다. 투명 윈리는 위와 같다.

plt.figure(figsize=(6,6))
plt.axis('off')
plt.title('After Mask color')
plt.imshow(draw_img[top:bottom+1, left:right+1])
# 시각화 되는 건 draw_img의 일부분이지만, python variable immutable 때문에 draw_img의 전체도 바뀌어 있다

drawing

  • 굳이 할 필요 없지만, Detect된 Object에 contour 윤곽선 적용.
  • cv2.findContours, cv2.drawContours모듈을 사용하면 쉽다.
s_mask_i = s_mask_b.astype(np.uint8)
# https://datascienceschool.net/view-notebook/f9f8983941254a34bf0fee42c66c5539/ 에 이미지 컨투어 설명 있음 
contours, hierarchy = cv2.findContours(s_mask_i,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(draw_img[top:bottom+1, left:right+1], contours, -1, color, 1, cv2.LINE_8, hierarchy, 100)

plt.figure(figsize=(6,6))
plt.axis('off')
plt.title('After Mask color')
plt.imshow(draw_img[top:bottom+1, left:right+1])

drawing

3. 모든 객체에 대해서 mask정보 그려주기

  • 위에서 했던 작업 동일하게 반복 수행
numClasses = masks.shape[1]
numDetections = boxes.shape[2]

# opencv의 rectangle(), putText() API는 인자로 들어온 IMAGE array에 그대로 수정작업을 수행하므로 bounding box 적용을 위한 
# 별도의 image array 생성. 
draw_img = img.copy()

img_height = draw_img.shape[0]
img_width = draw_img.shape[1]
conf_threshold = 0.5
mask_threshold = 0.3

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

for i in range(numDetections):
    box = boxes[0, 0, i]
    mask = masks[i]
    score = box[2]
    if score > conf_threshold:
        classId = int(box[1])
        left = int(img_width * box[3])
        top = int(img_height * box[4])
        right = int(img_width * box[5])
        bottom = int(img_height * box[6])

        text = "{}: {:.4f}".format(labels_to_names_seq[classId], score)
        cv2.rectangle(draw_img, (left, top), (right, bottom), green_color, thickness=2 )
        cv2.putText(draw_img, text, (left, top-3), cv2.FONT_HERSHEY_SIMPLEX, 2, red_color, 1)

        classMask = mask[classId]
        scaled_classMask = cv2.resize(classMask, (right - left + 1, bottom - top + 1))
        s_mask_b = (scaled_classMask > mask_threshold)
        
        # 마스크 정보 투명하게 색 덮어 주기
        before_mask_roi = draw_img[top:bottom+1, left:right+1]
        colorIndex = np.random.randint(0, len(colors)-1)
        color = colors[colorIndex]
        after_mask_roi = draw_img[top:bottom+1, left:right+1][s_mask_b]
        draw_img[top:bottom+1, left:right+1][s_mask_b] = ([0.3*color[0], 0.3*color[1], 0.3*color[2]] + 0.6 * after_mask_roi).astype(np.uint8)
        
        # Detect된 Object에 윤곽선(contour) 적용. 
        s_mask_i = s_mask_b.astype(np.uint8)
        contours, hierarchy = cv2.findContours(s_mask_i,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
        cv2.drawContours(draw_img[top:bottom+1, left:right+1], contours, -1, color, 1, cv2.LINE_8, hierarchy, 100)

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

drawing

4. 바로 위에서 한 작업. 함수화 및 이미지에 함수 사용

def get_box_info(box, img_width, img_height):
    
    classId = int(box[1])
    left = int(img_width * box[3])
    top = int(img_height * box[4])
    right = int(img_width * box[5])
    bottom = int(img_height * box[6])
    
    left = max(0, min(left, img_width - 1))
    top = max(0, min(top, img_height - 1))
    right = max(0, min(right, img_width - 1))
    bottom = max(0, min(bottom, img_height - 1))
    
    return classId, left, top, right, bottom

    
def draw_box(img_array, box, img_width, img_height, is_print=False):
    green_color=(0, 255, 0)
    red_color=(0, 0, 255)
    
    score = box[2]
    classId, left, top, right, bottom = get_box_info(box, img_width, img_height)
    text = "{}: {:.4f}".format(labels_to_names_seq[classId], score)
    
    if is_print:
        print("box:", box, "score:", score, "classId:", classId)
    
    cv2.rectangle(img_array, (left, top), (right, bottom), green_color, thickness=2 )
    cv2.putText(img_array, text, (left, top-3), cv2.FONT_HERSHEY_SIMPLEX, 0.5, red_color, thickness=1)
    
    return img_array
    
def draw_mask(img_array, box, mask, img_width, img_height, mask_threshold, is_print=False):
        
        classId, left, top, right, bottom = get_box_info(box, img_width, img_height)
        classMask = mask[classId]
        
        # 원본 이미지의 object 크기에 맞춰 mask 크기 scale out 
        scaled_classMask = cv2.resize(classMask, (right - left + 1, bottom - top + 1))
        s_mask_b = (scaled_classMask > mask_threshold)
        before_mask_roi = img_array[top:bottom+1, left:right+1]
        
        # mask를 적용할 bounding box 영역의 image 추출하고 투명 color 적용. 
        colorIndex = np.random.randint(0, len(colors)-1)
        color = colors[colorIndex]
        after_mask_roi = img_array[top:bottom+1, left:right+1][s_mask_b]
        img_array[top:bottom+1, left:right+1][s_mask_b] = ([0.3*color[0], 0.3*color[1], 0.3*color[2]] + 0.6 * after_mask_roi).astype(np.uint8)
        
        # Detect된 Object에 윤곽선(contour) 적용. 
        s_mask_i = s_mask_b.astype(np.uint8)
        contours, hierarchy = cv2.findContours(s_mask_i,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
        cv2.drawContours(img_array[top:bottom+1, left:right+1], contours, -1, color, 1, cv2.LINE_8, hierarchy, 100)
        
        return img_array
  • 바로 위에서 만든 함수 사용해서, detect_image_mask_rcnn 함수 만들기
import time

def detect_image_mask_rcnn(cv_net, img_array, conf_threshold, mask_threshold, use_copied_array, is_print=False):
    
    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
        
    start_time = time.time()
    
    blob = cv2.dnn.blobFromImage(img_array, swapRB=True, crop=False)
    cv_net.setInput(blob)
    boxes, masks = cv_net.forward(['detection_out_final', 'detection_masks'])
    
    inference_time = time.time() - start_time
    if is_print:
        print('Segmentation Inference time {0:}'.format(inference_time))

    numClasses = masks.shape[1]
    numDetections = boxes.shape[2]

    img_height = img_array.shape[0]
    img_width = img_array.shape[1]
    
    for i in range(numDetections):
        box = boxes[0, 0, i]
        mask = masks[i]
        score = box[2]
        #print("score:", score)
        if score > conf_threshold:
            draw_box(img_array , box, img_width, img_height, is_print=is_print)
            draw_mask(img_array, box, mask, img_width, img_height, mask_threshold, is_print=is_print)
    
    return img_array
labels_to_names_seq = {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'}
  • 위에서 만든 함수를 사용해서 instance segmentation 수행하기
img = cv2.imread('../../data/image/baseball01.jpg')

cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/mask_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/mask_rcnn_inception_v2_coco_2018_01_28/graph.pbtxt')

img_detected = detect_image_mask_rcnn(cv_net, img, conf_threshold=0.5, mask_threshold=0.3, use_copied_array=True, is_print=True)

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

  • 위에서 만든 함수를 사용해서 instance segmentation 수행하기
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

wick_img = cv2.imread('../../data/image/john_wick01.jpg')

wick_img_detected = detect_image_mask_rcnn(cv_net, wick_img, conf_threshold=0.5, mask_threshold=0.3, use_copied_array=True, is_print=True)

wick_img_rgb = cv2.cvtColor(wick_img_detected, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(12, 12))
plt.imshow(wick_img_rgb)

drawing

5. 동영상에 Segmentation 적용. 위에서 만든 함수 사용

def detect_video_mask_rcnn(cv_net, input_path, output_path, conf_threshold, mask_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, 24, vid_size) 

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

    frame_index=0
    while True:
        hasFrame, img_frame = cap.read()
        frame_index += 1
        if not hasFrame:
            print('더 이상 처리할 frame이 없습니다.')
            break
        print("frame index:{0:}".format(frame_index), end=" ")
        returned_frame = detect_image_mask_rcnn(cv_net, img_frame, conf_threshold=conf_threshold,
                                                mask_threshold=mask_threshold,use_copied_array=False, is_print=is_print)
        vid_writer.write(returned_frame)
    # end of while loop

    vid_writer.release()
    cap.release()
cv_net = cv2.dnn.readNetFromTensorflow('./pretrained/mask_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb', 
                                     './pretrained/mask_rcnn_inception_v2_coco_2018_01_28/graph.pbtxt')

detect_video_mask_rcnn(cv_net, '../../data/video/John_Wick_small.mp4', '../../data/output/John_Wick_mask_01.avi',
                      conf_threshold=0.5, mask_threshold=0.3, is_print=True)
!gsutil cp ../../data/output/John_Wick_mask_01.avi gs://my_bucket_dlcv/data/output/John_Wick_mask_01.avi

【Paper】 Mask R-CNN 논문 핵심 정리

Mask R-CNN 논문의 핵심만 직접 정리해 적어놓은 내용입니다.

Mask R-CNN 논문 핵심 정리
이전 게시물도 참고하시는 것을 추천드립니다.

1. FCN (Fully Convolutional Network)

  • Pixel wise Classification : 이미지 각 pixel에 대한 Classification 수행
    = Pixel Grid by Grid로 Softmax값이 추출된다.
  • Segmentation Annotation 기법 2가지 방법
    • 1 depth를 이용하는 첫번째 방법
    • Class 갯수 depth를 이용하는 두번째 방법
    • coco - mask 데이터의 다각형 꼭지점 값 이용.

drawing

  • Encoder & Decoder 기법
    • Dimension Reduction 하여 응축된 정보를 가지고 (위치 정보 소실) 다시 복원(Upsampling) 하는 과정을 거친다.

drawing

  • FCN architecture : 사진 속 필기 확인하기
    • De-Convolution : convolution 기법을 반대로 수행하는 것이 아니다. 보간법(bilinear interpolation)을 사용한다. 이것을 통해서 복구를 하는 것이다. 최대한 비슷하게…

drawing

  • Feature Map을 혼합한 Sementic Segmentation
    • FCN 32 :
      (width, hight) -> (width/32, hight/32) -> (width, hight)
    • FCN 16 :
      (width, hight) -> (width/32, hight/32)x2 + (width/16, hight/16) -> (width, hight)
    • FCN 8 :
      (width, hight) -> (width/32, hight/32)x4 + (width/16, hight/16)x2 + (width/8, hight/8)-> (width, hight)

drawing

2. Mask RCNN

  • Mask R-CNN 핵심
    • Faster Rcnn + FCN
    • ROI Align
    • Binary Mask Rediction
    • 비교적 빠른 수행 시간
    • 비교적 높은 정확도
    • 직관적이고 상대적으로 쉬운 구현
  • 이전 게시물 핵심 이미지 - Mask-RCNN을 한눈에 표현함

drawing

  • ROI Pooling의 문제점
    • 아래 이미지 참조. quantization을 2번 거친다. (= 실수의 반올림을 2번 한다.)
    • 아래의 이미지의 2번쨰 quantization(B1)은 아래와 같은 과정을 거칠 수도 있지만,
      Grid의 나눔을 불균등하게 하는 방법(B2)도 있다. (B1 - 5 = 2 + 2 + (1 버림) OR B2- 5 = 3 + 2)
    • 때문에 정확한 Pooling이 이뤄지지 못한다.

    drawing

2-1 ROI Align - ROI Pooling 문제점 해결

  • ROI Align : 이전 Post 55 슬라이드 부터 참조. 가장 중요한 부분 첨부
    • 순서 정리(꼭 이 사이트 그림 참조)
      1. Input-EachROI. output-7x7(nxn) Pooled Feature.
      2. nxn등분(첫번째 quantization)
      3. 각4등분(두번째 quantization)->Bilinear interpolation
      4. 각4등분에 대해 Max Pooling->(nxn) Pooled Feature Map 획득.
      5. 아래 이미지 참조 (아래 빨간 메모 꼭 읽기)

drawing

  • ROI Pooling 대신에 ROI Align을 적용해서 AP 성능 향상을 3~4% 할 수 있었다.

2-2 Backbone과 Loss function

  • Fearture Extractor (BackBone) : Resnet + Feature Pyramid Network

  • Totoal Loss = L_cls + L_bbox + L_mask
    • L_cls = Multiclass cross-entropy loss (Softmax)
    • L_bbox = Smooth L1 loss
    • L_mask = Binary cross-entropy loss(해당 픽셀이 Classification에서 찾 Object class인지 아닌지(Sigmoid 사용))
  • Mask Prediction
    • 7 x 7 x 2048이 14 x 14 x 256으로 Upsampling된다.
    • 14 x 14 x 256은 14 x 14 x 80이 되어서, COCO 80개 Class에 대한 이진 Binary mask prediction값이 나온다. (아래 오른쪽 이미지의 Prediced Mask)
    • Prediced Mask(14 x 14)를 Upsampling하여 Resized Mask가 되도록 만든다.

drawing

2-3 성능

  • Instance Segmentation에서 좋은 성능이 나오는 것을 확인할 수 있다.

drawing

【Keras】Keras기반 Yolo3 - Google Open Image 학습시키기

이전 게시물에서 공부했던 Keras 기반 Yolo3를 이용해 Google Open Image 데이터 학습을 시켜보자.

Google Open Image 학습시키기 - Keras기반 Yolo3 이전의 Pascal VOC, MS coco 데이터 셋에 대해서는 이전 게시물 참조

1. Google Open Image 고찰 정리

  • 600개의 object 카테고리,
  • Object Detection Dataset 170만개 train 4만 validation 12만개 test
  • 하지만 데이터셋 용량이 너무 크다보니, COCO로 pretrained된 모델만 전체적으로 공유되는 분위기이다.
  • Version 4 : 2018 Kaggle Challenge Detection Bounding Box
  • Version 5 : 2019 Kaggle Challenge Detection Bounding Box + Instace Segmentation
  • Version 6 : 2020.02 https://storage.googleapis.com/openimages/web/index.html
  • Google Open Image Dataset 특징 : COCO와 비슷하지만, 용량은 훨씬 많다. (아래 이미지 확인)

drawing

  • 아래의 사진처럼, Download를 받아보면 Box의 정보가 CSV 파일로 되어있는 것을 확인할 수 있다. (특징 : Xmin Xmax Ymin Ymax가 0~1사이값으로 정규화가 되어있기 때문에 Image w,h를 곱해줘야 정확한 box위치라고 할 수 있다.)

drawing

  • 아래의 사진처럼, Metadate의 Class Names를 확인해보면 다음과 같이 ClassID와 Class 이름이 mapping되어 있는 것을 확인할 수 있다. (나중에 사용할 예정)

  • Class 구조(Hierarchy_bounding box label). (Download 사이트의 맨 아래로 가면 볼 수 있다.)

drawing

2. OIDv4 Toolkit

  • OIDv4 Toolkit Git 사이트
  • 방대한 Open Image를 전체 다운로드 받는 것은 힘드므로, 원하는 양의 이미지, 원하는 클래스의 이미지만 다운로드 할 수 있게 해주는 툴이다.
  • Xmin Xmax Ymin Ymax가 0~1사이값을 좌표로 자동 변환을 해준다.
  • 설치
      $ cd ~/DLCV/data
      $ git clone https://github.com/EscVM/OIDv4_ToolKit.git
      $ pip3 install -r requirements.txt
    
  • 다운로드 - 아래의 1,2,3,4 순서과정 주의하기
      $ python3 main.py downloader --classes Apple Orange --type_csv validation
    
      아래와 같은 예시로 다운로드 된다. 
       main_folder
      │   main.py
      │
      └───OID
          │   file011.txt
          │   file012.txt
          │
          └───csv_folder
          |    │   class-descriptions-boxable.csv         1. open image Metadata에 있는 class 이름
          |    │   validation-annotations-bbox.csv        2. open image의 annotation csv파일 그대로 다운로드
          |
          └───Dataset
              |
              └─── test
              |
              └─── train
              |
              └─── validation                         3. 위의 anotation csv 파일을 참조해서 apple과 orange에 관련된 이미지만 아래와 같이 다운로드 
                  |
                  └───Apple
                  |     |
                  |     |0fdea8a716155a8e.jpg
                  |     |2fe4f21e409f0a56.jpg
                  |     |...
                  |     └───Labels
                  |            |
                  |            |0fdea8a716155a8e.txt  4. 사과에 대한 내용만 annotation으로 csv 파일형식으로 txt파일로 만들어 놓는다. 
                  |            |2fe4f21e409f0a56.txt
                  |            |...
                  |
                  └───Orange
                      |
                      |0b6f22bf3b586889.jpg
                      |0baea327f06f8afb.jpg
                      |...
                      └───Labels
                              |
                              |0b6f22bf3b586889.txt
                              |0baea327f06f8afb.txt
                              |...
    
  • 위의 명령어에서 validation은 Google Open Image에서 validation에 있는 데이터 중에 사과와 오렌지에 관한 것만 다운로드를 해준다.
  • 직접 쳐서 다운로드 하고 어떻게 구성되어 있는지 직접 확인하기.

drawing

  • Keras-Yolo3에서 데이터를 활용하기 위한 변환 과정 (이미지 있는 코드 적극 활용 위해)
    1. OIDv4 Toolkit으로 데이터 다운로드
    2. VOC XML 타입으로 변경
    3. Keras yolo용 CSV로 변환

3. 데이터 다운로드 및 전처리 해보기(Keras-yolo3 용)

  • 아래 전체 과정 요약
    1. 내가 가진 데이터 전처리(패키지가 원하는 형식으로)
    2. Data Genereator에 넣기(torch같은 경우 TensorDataset, DataLoader를 이용해서)
    3. create_model 하기. (torch.nn 모듈을 사용해서 정의한 신경망 모델 클래스로, 객체 생성하기)
    4. Check point, log 생성을 위한 작업하기.(Keras 같은 경우 keras.callbacks 모듈의 함수 이용)
    5. model을 위한 optimizer, Loss function(criterion) 설정하기.
    6. model.fit_generator를 이용해서 학습 및 가중치 갱신 시키기. (from keras.models import Model에 있는 맴버함수이다.)
    7. .h5 형식의 Inference를 위한 가중치 파일을 얻을 수 있다.
    8. .h5 형식 파일을 이용해서, yolo.py함수의 YOLO 클래스를 이용해 객체 생성
    9. YOLO.detect_image를 이용해서 객체 탐지 수행
  1. 실습을 위한 데이터 다운로드
     $ python3 main.py downloader --classes Football Football_helmet Fish Shark Shellfish  --type_csv train --multiclasses 1 --limit 300
     # multiclasses : 다양한 class의 이미지를 위에처럼 다른 폴더에 넣는게 아니라 하나의 폴더에 묶어서 다운로드
     # limit : 카테고리당, 최대 이미지 수
     # Football_helmet라고 쳐도 되고, 'Football helmet'라고 해도 된다.
    
  2. Label 폴더의 annotation.txt 파일을 모두 VOC XML 파일로 바꾼다.
    • Label 폴더의 annotation.txt 파일들이 각각 이미지 하나에 txt파일 이미지 하나로 맴핑된다.
    • 이것은 VOC XML 파일 형식과 매우 유사한 것이다. (이미지 하나당, XML 파일 하나)
    • 따라서 annotation.txt 파일을 XML 파일로 바꾼다.
    • Git에 돌아니는, oid_to_pascal_voc_xml.py 파일을 이용할 것이다. 해당 파일을 보면 알겠듯이, OIDv4_ToolKit repository가 있는 그 위치에 파일이 존재해야한다.
     $ cd ~/DLCV/data/util
     $ cp oid_to_pascal_voc_xml.py ~/DLCV/data/OIDv4_ToolKit
     $ cd ~/DLCV/data/OIDv4_ToolKit
     $ python oid_to_pascal_voc_xml.py 
    
    • 아래처럼 xml파일이 /train/ballnfish 파일 바로 아래에 jpg파일과 같은 위치에 생성된 것을 알 수 있다.

    drawing

  3. 이전 Post 내용(라쿤데이터셋 전처리)을 활용해서 위의 XML파일을 CSV로 전환
    • 새로운 위치에 이미지와 xml파일 옮기기
     $ cd ~/DLCV/data
     $ mkdir ballnfish
     $ cd ./ballnfish
     $ mkdir annotation
     $ mkdir images
     $ cd ~/DLCV/data/OIDv4_ToolKit/OID/Dataset/train/ballnfish 
     $ cp *.jpg  ~/DLCV/data/ballnfish/images
     $ cp *.xml  ~/DLCV/data/ballnfish/annotation
    
    • images에 들어가면 이미지가 1499개 있다. (하나의 카테코리당 300개 이미지)
    • 이전에 했던 라쿤데이터셋 전처리 함수 이용하기 xml_to_csv 함수 찾아 이용.
    • 여기서부터 참조 : /DLCV/Detection/yolo/KerasYolo_OpenImage_학습및_Detection.ipynb
  • Open Image Dataset의 Object Detection 학습 및 Inference
    • Open Image Dataset에서 Football 관련 Object, Fish관련 Object를 추출 후 학습 데이터 세트 생성.
    • 이를 이용하여 Object Detection 수행.
# annotation과 image 디렉토리 설정. annotation디렉토리에 있는 파일 확인. 
import os
from pathlib import Path

HOME_DIR = str(Path.home())

ANNO_DIR = os.path.join(HOME_DIR, 'DLCV/data/ballnfish/annotations')
IMAGE_DIR = os.path.join(HOME_DIR, 'DLCV/data/ballnfish/images')
print(ANNO_DIR)

files = os.listdir(ANNO_DIR)
print('파일 개수는:',len(files))
print(files)
!cat /home/sb020518/DLCV/data/ballnfish/annotations/e5e4aba50208e92f.xml
<annotation>
  <folder>vallnfish</folder>
  <filename>e5e4aba50208e92f.jpg</filename>
  <path>/home/sb020518/DLCV/data/OIDv4_ToolKit/OID/Dataset/train/vallnfish/e5e4aba50208e92f.jpg</path>
  ...
  <object>
    <name>Football_helmet</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>685</xmin>
      <ymin>86</ymin>
      <xmax>778</xmax>
      <ymax>197</ymax>
    </bndbox>
  </object>
</annotation>
import glob
import xml.etree.ElementTree as ET

classes_map = {'Football':0, 'Football_helmet':1, 'Fish':2, 'Shark':3, 'Shellfish':4 }

def xml_to_csv(path, output_filename):
    xml_list = []
    # xml 확장자를 가진 모든 파일의 절대 경로로 xml_file할당. 
    with open(output_filename, "w") as train_csv_file:
        for xml_file in glob.glob(path + '/*.xml'):
            # xml 파일을 parsing하여 XML Element형태의 Element Tree를 생성하여 object 정보를 추출. 
            tree = ET.parse(xml_file)
            root = tree.getroot()
            # 파일내에 있는 모든 object Element를 찾음. 
            full_image_name = os.path.join(IMAGE_DIR, root.find('filename').text)
            value_str_list = ' '
            for obj in root.findall('object'):
                xmlbox = obj.find('bndbox')
                class_name = obj.find('name').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)
                # 
                class_id = classes_map[class_name]
                value_str = ('{0},{1},{2},{3},{4}').format(x1, y1, x2, y2, class_id)
                # object별 정보를 tuple형태로 object_list에 저장. 
                value_str_list = value_str_list+value_str+' '
        
            train_csv_file.write(full_image_name+' '+ value_str_list+'\n')
        # xml file 찾는 for loop 종료 
xml_to_csv(ANNO_DIR, os.path.join(ANNO_DIR,'ballnfish_anno.csv'))
print(os.path.join(ANNO_DIR,'ballnfish_anno.csv'))
!cat /home/sb020518/DLCV/data/ballnfish/annotations/ballnfish_anno.csv
/home/sb020518/DLCV/data/ballnfish/images/6d3552b1e28f05c0.jpg  339,304,631,472,2 
/home/sb020518/DLCV/data/ballnfish/images/379a8df86070df59.jpg  361,312,498,445,1 76,39,108,77,1 120,42,171,92,1 
/home/sb020518/DLCV/data/ballnfish/images/ba993dcfb21071c7.jpg  177,345,753,678,2 
/home/sb020518/DLCV/data/ballnfish/images/1a51f73f96d2e88b.jpg  50,294,778,598,3 
/home/sb020518/DLCV/data/ballnfish/images/823873ff2c000a52.jpg  126,127,912,484,2 
/home/sb020518/DLCV/data/ballnfish/images/022ab76a12ee9808.jpg  0,0,1023,767,4 445,408,771,692,4 
/home/sb020518/DLCV/data/ballnfish/images/222e79f02c455fc5.jpg  64,5,942,613,3 
/home/sb020518/DLCV/data/ballnfish/images/0eade5d90ecc799e.jpg  111,35,139,53,1 135,85,175,116,1 170,25,200,42,1 225,42,255,71,1 264,100,309,149,1 280,46,304,67,1 479,37,510,71,1 552,55,588,81,1 604,81,637,113,1 709,69,746,109,1 
/home/sb020518/DLCV/data/ballnfish/images/de93f11430812481.jpg  445,256,566,346,2 264,443,307,509,2 553,320,658,376,2 796,251,865,289,2 
/home/sb020518/DLCV/data/ballnfish/images/94798692e8a62998.jpg  131,162,997,741,2 
/home/sb020518/DLCV/data/ballnfish/images/aff435118770ffce.jpg  355,208,753,530,3 

4. 모델 구성하고 학습시키기

  • 라쿤 데이터 학습과는 조금 많이 다르게 학습시킨다.
  • 좀 더 하나하나를 함수화 해서 체계적으로 코드를 구성해 나갈 계획이다.
import cv2
import matplotlib.pyplot as plt
%matplotlib inline

plt.imshow(cv2.cvtColor(cv2.imread('/home/sb020518/DLCV/data/ballnfish/images/a9b21c816203e357.jpg'), cv2.COLOR_BGR2RGB))
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import sys, os

LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)

from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data
from train import get_classes, get_anchors
from train import create_model, data_generator, data_generator_wrapper

BASE_DIR = os.path.join(HOME_DIR, 'DLCV/Detection/yolo/keras-yolo3')

## 학습을 위한 기반 환경 설정. annotation 파일 위치, epochs시 저장된 모델 파일, Object클래스 파일, anchor 파일.
annotation_path = os.path.join(ANNO_DIR, 'ballnfish_anno.csv')
log_dir = os.path.join(BASE_DIR, 'snapshots/ballnfish/')
classes_path = os.path.join(BASE_DIR, 'model_data/ballnfish_classes.txt')
anchors_path = os.path.join(BASE_DIR,'model_data/yolo_anchors.txt')

class_names = get_classes(classes_path)
num_classes = len(class_names)
anchors = get_anchors(anchors_path)
print(class_names, num_classes)
print(anchors)
['Football', 'Football_helmet', 'Fish', 'Shark', 'Shellfish'] 5
[[ 10.  13.]
 [ 16.  30.]
 [ 33.  23.]
 [ 30.  61.]
 [ 62.  45.]
 [ 59. 119.]
 [116.  90.]
 [156. 198.]
 [373. 326.]]
  • yolo 모델 학습을 위한 전반적인 파라미터를 config 클래스로 설정하고 필요시 이를 수정하여 학습.
# csv annotation 파일을 읽어서 lines 리스트로 만듬. 
with open(annotation_path) as f:
    lines = f.readlines()

# 추후에 사용할 config 정보를 json, xml 등으로 적을 수 있겠지만, 여기서는 class변수 하나에 config에 대한 정보를 모아 놓았다. 
# 해당 cell의 맨 아래와 같이 맴버변수를 사용해서 환경변수 데이터를 불러올 예정이다.
class config:
    #tiny yolo로 모델로 초기 weight 학습 원할 시 아래를 tiny-yolo.h5로 수정. 
    initial_weights_path=os.path.join(BASE_DIR, 'model_data/yolo.h5' )
    # input_shape는 고정. 
    input_shape=(416, 416) 
    # epochs는 freeze, unfreeze 2 step에 따라 설정. 
    first_epochs=50
    first_initial_epochs=0
    second_epochs=100
    second_initial_epochs=50
    # 학습시 batch size, train,valid건수, epoch steps 횟수  
    batch_size = 2
    val_split = 0.1   
    num_val = int(len(lines)*val_split)
    num_train = len(lines) - num_val
    train_epoch_steps = num_train//batch_size 
    val_epoch_steps =  num_val//batch_size
    
    anchors = get_anchors(anchors_path)
    class_names = get_classes(classes_path)
    num_classes = len(class_names)
    # epoch시 저장된 weight 파일 디렉토리 
    log_dir = os.path.join(BASE_DIR, 'snapshots/ballnfish/')  
    
print('Class name:', config.class_names,'\nNum classes:', config.num_classes)
Class name: ['Football', 'Football_helmet', 'Fish', 'Shark', 'Shellfish'] 
Num classes: 5
  • create_generator 함수 만들기
    • csv 파일을 입력 받아서 train 데이터와 valid 데이터 처리를 위한 data_generator_wrapper객체를 각각 생성.
    • train용, valid 용 data_generator_wrapper는 Yolo 모델의 fit_generator()학습시 인자로 입력됨.
def create_generator(lines):
    
    train_data_generator = data_generator_wrapper(lines[:config.num_train], config.batch_size, 
                                                  config.input_shape, config.anchors, config.num_classes)
    
    valid_data_generator = data_generator_wrapper(lines[config.num_train:], config.batch_size, 
                                                  config.input_shape, config.anchors, config.num_classes)
    
    return train_data_generator, valid_data_generator
  • create_yolo_model 함수 만들기
    • YOLO 모델 또는 tiny yolo 모델 반환. 초기 weight값은 pretrained된 yolo weight값으로 할당.
# anchor 개수에 따라 tiny yolo 모델 또는 yolo 모델 반환. 
# tiny yolo 를 위한 config파일은 나중에 만들어 사용할 에정이다.
def create_yolo_model():
    is_tiny_version = len(config.anchors)==6 
    if is_tiny_version:
        model = create_tiny_model(config.input_shape, config.anchors, config.num_classes, 
            freeze_body=2, weights_path=config.initial_weights_path)
    else:
        model = create_model(config.input_shape, config.anchors, config.num_classes, 
            freeze_body=2, weights_path=config.initial_weights_path)
        
    return model 
  • callback 객체들을 생성.
    • Keras에서 많이 사용하는 checkpoint 저장 방식인듯 하다. 우선 이것도 모르지만 넘어가자.
# Tensorboard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping callback 반환
def create_callbacks():
    logging = TensorBoard(log_dir=config.log_dir)
    checkpoint = ModelCheckpoint(config.log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
        monitor='val_loss', save_weights_only=True, save_best_only=True, period=3)  # epoch 3마다 weight 저장
    reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
    early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)
    # early_stopping에 의해 loss가 항상 비슷하면 학습을 자동 중단한다. patience를 늘리면 더 학습이 진행된다.
    
    #개별 callback들을 한꺼번에 list로 묶어서 반환
    return [logging, checkpoint, reduce_lr, early_stopping]
  • 위의 작업으로 모델 구성을 마쳤다. 학습 수행을 시작한다 {:.lead}
    • GPU : $ watch -d -n 1 nvidia-smi
    • CPU : $ vmstat 3
      us : 사용자 사용량 wa : wait cpu가 놀고 있는 량

drawing

   
# create_generator(), create_model(), create_callbacks() 수행. 
train_data_generator, valid_data_generator = create_generator(lines)
ballnfish_model = create_yolo_model()
callbacks_list = create_callbacks()

# 최초 모델은 주요 layer가 freeze되어 있음. 안정적인 loss를 확보하기 위해 주요 layer를 freeze한 상태로 먼저 학습. 
print('First train 시작' )
ballnfish_model.compile(optimizer=Adam(lr=1e-3), loss={'yolo_loss': lambda y_true, y_pred: y_pred})

#1단계 학습 완료 모델 저장. 
# 아래의 과정을 통해서 foward -> backpropagation -> weight 갱신 -> weight 저장
ballnfish_model.fit_generator(
                train_data_generator, 
                steps_per_epoch=config.train_epoch_steps,
                validation_data=valid_data_generator, 
                validation_steps=config.val_epoch_steps,
                epochs=config.first_epochs, 
                initial_epoch=config.first_initial_epochs, 
                callbacks=callbacks_list)
ballnfish_model.save_weights(log_dir + 'trained_weights_stage_1.h5')

# 2단계 학습 시작. 
# create_model() 로 반환된 yolo모델에서 trainable=False로 되어 있는 layer들 없이, 모두 True로 만들고 다시 학습 (뭔지 정확히 모르겠음)
# 모든 layer를 trainable=True로 설정하고 학습 수행. 
for i in range(len(ballnfish_model.layers)):
    ballnfish_model.layers[i].trainable = True
    
print('Second train 시작' )
ballnfish_model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) 
ballnfish_model.fit_generator(train_data_generator, steps_per_epoch=config.train_epoch_steps,
    validation_data=valid_data_generator, validation_steps=config.val_epoch_steps,
    epochs=config.second_epochs, initial_epoch=config.second_initial_epochs,
    callbacks=callbacks_list)

# 최종 학습 완료 모델 저장. 
ballnfish_model.save_weights(log_dir + 'trained_weights_final.h5')
  • 최종 학습된 모델을 로딩하여 Object Detection 수행.
    • 이제 위에서 만든 구성 및 모듈 다 필요없다.
    • 가장 마지막에 만들어진 Weight 파일인 /keras-yolo3/snapshots/ballnfish/trained_weights_final.h5 를 가지고 학습을 시작하자
from yolo import YOLO
#keras-yolo에서 image처리를 주요 PIL로 수행. 
from PIL import Image

LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)0

ballnfish_yolo = YOLO(model_path='/home/younggi.kim999/DLCV/Detection/yolo/keras-yolo3/snapshots/ballnfish/trained_weights_final.h5',
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/ballnfish_classes.txt')
  • 이미지 Object Detection
    • 위에서 정의한 모델을 가지고 ballnfish_yolo.detect_image(img) 를 사용해서 detection 수행해 나가자!
football_list = ['f1b492a9bce3ac9a.jpg', '1e6ff631bb0c198b.jpg', '97ac013310bda756.jpg',
                'e5b1646c395aecfd.jpg', '53ef241dad498f6c.jpg', '02ccbf5ddaaecedb.jpg' ]
for image_name in football_list:
    img = Image.open(os.path.join(IMAGE_DIR, image_name))
    detected_img = ballnfish_yolo.detect_image(img)
    plt.figure(figsize=(8, 8))
    plt.imshow(detected_img)
    
helmet_list = ['1fed5c930211c6e0.jpg', '011a59a160d7a091.jpg', 'd39b46aa4bc0c165.jpg', '7e9eb7eba80e34e7.jpg', '9c27811a78b74a48.jpg']
for image_name in helmet_list:
    img = Image.open(os.path.join(IMAGE_DIR, image_name))
    detected_img = ballnfish_yolo.detect_image(img)
    plt.figure(figsize=(8, 8))
    plt.imshow(detected_img)
fish_list = ['25e42c55bfcbaa88.jpg', 'a571e4cdcfbcb79e.jpg', '872c435491f2b4d3.jpg', 
             'bebac23c45451d93.jpg', 'eba7caf07a26829b.jpg', 'dc607a2989bdc9dc.jpg' ]
for image_name in fish_list:
    img = Image.open(os.path.join(IMAGE_DIR, image_name))
    detected_img = ballnfish_yolo.detect_image(img)
    plt.figure(figsize=(8, 8))
    plt.imshow(detected_img)
shark_list = ['d92290f6c04dd83b.jpg', '3a37a09ec201cdeb.jpg', '32717894b5ce0052.jpg', 'a848df5dbed78a0f.jpg', '3283eafe11a847c3.jpg']
for image_name in shark_list:
    img = Image.open(os.path.join(IMAGE_DIR, image_name))
    detected_img = ballnfish_yolo.detect_image(img)
    plt.figure(figsize=(8, 8))
    plt.imshow(detected_img)
shell_list=['5cc89bc28084e8e8.jpg',  '055e756883766e1f.jpg', '089354fc39f5d82d.jpg', '80eddfdcb3384458.jpg']
for image_name in shell_list:
    img = Image.open(os.path.join(IMAGE_DIR, image_name))
    detected_img = ballnfish_yolo.detect_image(img)
    plt.figure(figsize=(8, 8))
    plt.imshow(detected_img)

drawing

5. 영상 Object Detection

  • 어렵지 않게 cv2.VideoCapture 함수와 ballnfish_yolo.detect_image(img) 를 사용해서 Detection 수행하면 끝!
import cv2
import time

def detect_video_yolo(model, input_path, output_path=""):
    
    start = time.time()
    cap = cv2.VideoCapture(input_path)
    
    #codec = cv2.VideoWriter_fourcc(*'DIVX')
    codec = cv2.VideoWriter_fourcc(*'XVID')
    vid_fps = cap.get(cv2.CAP_PROP_FPS)
    vid_size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    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, '원본 영상 FPS:',vid_fps, '원본 Frame 크기:', vid_size)
    index = 0
    while True:
        hasFrame, image_frame = cap.read()
        if not hasFrame:
            print('프레임이 없거나 종료 되었습니다.')
            break
        start = time.time()
        # PIL Package를 내부에서 사용하므로 cv2에서 읽은 image_frame array를 다시 PIL의 Image형태로 변환해야 함.  
        image = Image.fromarray(image_frame)
        # 아래는 인자로 입력된 yolo객체의 detect_image()로 변환한다.
        detected_image = model.detect_image(image)
        # cv2의 video writer로 출력하기 위해 다시 PIL의 Image형태를 array형태로 변환 
        result = np.asarray(detected_image)
        index +=1
        print('#### frame:{0} 이미지 처리시간:{1}'.format(index, round(time.time()-start,3)))
        
        vid_writer.write(result)
    
    vid_writer.release()
    cap.release()
    print('### Video Detect 총 수행시간:', round(time.time()-start, 5))
detect_video_yolo(ballnfish_yolo, '../../data/video/NFL01.mp4', '../../data/output/NFL_yolo_01.avi')
!gsutil cp ../../data/output/NFL_yolo_01.avi gs://my_bucket_dlcv/data/output/NFL_yolo_01.avi
detect_video_yolo(ballnfish_yolo, '../../data/video/FishnShark01.mp4', '../../data/output/FishnShark_yolo_01.avi')
!gsutil cp ../../data/output/FishnShark_yolo_01.avi gs://my_bucket_dlcv/data/output/FishnShark_yolo_01.avi
  • 분석결과 : 영상의 FPS가 작다. 그래서 빠르게 움직이는 물체들을 잡는 능력이 부족하다.
  • 영상의 FPS가 커지면, Model의 Inference 속도도 굉장히 빨라져야한다….
  • 쉽지 않은 문제이다.

drawing

drawing

【Keras】Keras기반 Yolo3 -라쿤 데이터 학습시키기 + GPU 학습 유의사항

GPU Object Detection시 유의 사항을 간단히 알아보고, 이전 게시물에서 공부했던 Keras 기반 Yolo3를 이용해 데이터 학습을 시켜보자.

GPU 학습 유의사항/ 라쿤 데이터 학습시키기 - Keras기반 Yolo3

1. GPU 학습 시킬 떄 주의 사항

  1. 대량의 이미지 학습시 메모리 문제
    • 알다시피 개별 이미지가 Tensor, narray로 변환해서 NeuralNet에 집어넣을떄 이미지를 Batch로 묶어 처리한다.
    • 이 Batch가 그래픽 카드 메모리 사용량의 많은 부분을 좌우한다.
      (1000장 - 10장 배치 * 100번 = 1 에폭)
    • GPU 사용 % 자주 확인하기. Image를 loading하는 것은 CPU에서 한다. Batch가 크면 GPU는 바쁘고 CPU는 논다. Batch가 작으면 GPU는 놀고 CPU는 바쁘다. 근데 Object Detection은 GPU에서 할게 너무 많아서 Batch를 작게하는게 대부분이다. (Batch = 1~16)
  2. Genrator와 Batch
    • 특히 fit_generator를 확인해보면, Python gererator를 사용한다. gerator, next, yield에 대해서 알아보자. 코딩도장 generator 설명
    • 배치 사이즈를 크게 늘린다고 학습이나 추론이 빨라지지 않는다. 100장의 이미지를 한방에 끌어오는것도 힘들지만, 그 이미지를 네트워크에 넣는다 하더라도 어딘가에서 병목이 걸릴건 당연하다. 따라서 배치 사이즈를 어떻게 설정하지는 CPU core, GPU 성능 등을 파악해서 균형있게 맞춰야 한다.(Heuristic 하게 Turning)
    • 한쪽은 열심히 일하고, 한쪽은 기다리는 현상이 일어나지 않게끔. 이렇게 양쪽이 일을 쉬지 않고 균형있게 배치하는 것이 필요하다.
    • 이러한 Batch를 Keras에서는 (fit_generator 함수가 사용하는) DataGenerator, flow_from_drectory 가 해준다. 알다시피 Torch는 TensorDataset, DataLoader가 해준다.
    • 이런 generator를 사용하기 위해서 Keras와 같은 경우에 다음과 같은 코드를 사용한다.

            from keras.preprocessing.image import ImageDataGenerator
      
            train_datagen = ImageDataGerator(rescale=1/255) # 이미지 정규화
            train_gernator = train_datagen.flow_from_director('학습용 이미지 Directory', targer_size = (240,240), batch_size-10, class_mode='categoricl') 
            valid_datagen = ImageDataGerator(rescale=1/255) 
            valid_gernator = valid_datagen.flow_from_director('검증용 이미지 Directory', targer_size = (240,240), batch_size-10, class_mode='categoricl')
      
            # 모델을 구성하고
            model = Sequential()
            model.add(Conv2D(32, kernel_size=(3,3), activation='relu', input_shape=(240,240,3)))
            model.add ...
            ...
            ...
            model.add(Dense(3, activation='softmax'))
            model.compile(loss='categorial_crossentropy', optimizer='adam', metrixs=['accuracy'])
      
            # 학습을 시킨다.
            model.fit_generator(train_generator, steps_per_epoch=100, epochs=10, validation_data=valid_generator, validation_steps=2)
      
    • 이렇게 fit_generator을 돌리면 data pipeline이 만들어 진다. train_generator가 디렉토리에 가서 배치만큼의 파일을 읽어와서 yeild를 하여 모델에 데이터를 넣는다.

    drawing

2. qqwweee/Keras-Yolo3로 Training 하기 위한 분석

주의사항{:.lead}

  • 아래의 일렬의 과정들은 패키지의 있는 함수들을 전적으로 사용합니다.
  • 따라서 그냥 보면 흐름을 이해하지 못할 수도 있습니다.
  • 꼭 패키지의 코드들과 같이 보는 것을 추천합니다.
  • 만약 케라스를 잘 모른다면, 그냥 다른 Data를 학습시킬때는 이런 일렬의 과정들이 있구나
    정도의 감을 잡는 것도 아주 좋은 공부 일 수 있습니다.
  • 아래 전체 과정 요약
    1. 내가 가진 데이터 전처리(패키지가 원하는 형식으로)
    2. Data Genereator에 넣기(torch같은 경우 TensorDataset, DataLoader를 이용해서)
    3. create_model 하기. (torch.nn 모듈을 사용해서 정의한 신경망 모델 클래스로, 객체 생성하기)
    4. Check point, log 생성을 위한 작업하기.(Keras 같은 경우 keras.callbacks 모듈의 함수 이용)
    5. model을 위한 optimizer, Loss function(criterion) 설정하기.
    6. model.fit_generator를 이용해서 학습 및 가중치 갱신 시키기. (from keras.models import Model에 있는 맴버함수이다.)
    7. .h5 형식의 Inference를 위한 가중치 파일을 얻을 수 있다.
    8. .h5 형식 파일을 이용해서, yolo.py함수의 YOLO 클래스를 이용해 객체 생성
    9. YOLO.detect_image를 이용해서 객체 탐지 수행
  • 지금까지는 1. Pretrained된 Weight값을 가져와서 Inference를 수행하거나(Tensorflow(SSD, Yolo), Keras-yolo3) 2. OpenCV DNN 모듈을 사용하거나 해왔다.
  • 참고 : 1개의 객체만 검출한다면 100장이어도 충분하다. 근데 Class가 여러개면 수 많은 이미지 데이터가 필요하다.
  • <weight 가중치 파일 형식 정리>
    • keras weight(케라스 가중치) 파일은 파일 형식이 .h5파일이다.
    • Tensorflow에서 Inference를 위한 파일 형식은 .pb 파일이다. .ckpt파일에는 학습에 대한check point에 대한 정보를 담고 있다.
    • PyTorch에서는 .pt 파일이 torch.save(model.state_dict()를 이용해서 저장한 weight파일이다.
  • qqwweee/keras-yolo3에 적힌 Training 방법 정리
    1. voc(xml 파일사용)을 annotation파일로 바꾸는 convert_annotation.py 파일을 살펴보면 최종적으로 ‘%s_%s.txt’%(year, image_set) 파일을 만드는 것을 확인할 수 있다.
    2. Readme의 내용을 따르면,
      각 한줄 : image_file_path box1 box2 … boxN
      boxK의 format : x_min,y_min,x_max,y_max,class_id (no space) 이다.
       path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3
       path/to/img2.jpg 120,300,250,600,2
       ...
      
    3. 일단 pretrained된 weight를 가져와서 학습시키는 것이 좋으니, conver.py를 실행해서 darknet weight를 keras weight로 바꿔준다.
    4. train.py를 사용해서 학습을 한다. 내부 코드 적극 활용할 것.
    5. train.py에 들어가야하는 Option을 설정하는 방법은 원래의 default option이 무엇인지 확인하면 된다.

drawing

  • train.py을 적극적으로 이용할 계획이다. 아래와 같은 방법으로. 함수는 아래와 같이 import를 해서 사용할 것이고, main함수의 내용은 소스 코드 그대로 복사해 가져와서 사용할 예정이다.
          from train import get_classes, get_anchors
          from train import create_model, data_generator 
    

3. 라쿤 데이터셋으로 학습시키고 Inference하기

3-1 데이터 전처리 하기

  • Raccoon 데이터 세트 download 후
    • 이미지와 annoation 디렉토리를 제외하고 모두 삭제합니다.
    • images에 이미지가 있고 annoation에는 각이미지에 해당하는 xml파일 203개가 존재한다.
  • 아래의 내용들은 전처리를 하기에 아주 좋은 함수들과 모듈들이 잘 사용되고 있으므로 잘 알아두고 나중에도 꼭 잘 활용하자
# annotation과 image 디렉토리 설정. annotation디렉토리에 있는 파일 확인. 
import os
from pathlib import Path

HOME_DIR = str(Path.home())

ANNO_DIR = os.path.join(HOME_DIR, 'DLCV/data/raccoon/annotations')
IMAGE_DIR = os.path.join(HOME_DIR, 'DLCV/data/raccoon/images')
print(ANNO_DIR)

files = os.listdir(ANNO_DIR)
print('파일 개수는:',len(files))  # 200개
print(files)  # Dicrectory에 있는 파일 이름 전부가 list 변수에 저장된다.
import glob
import xml.etree.ElementTree as ET

def xml_to_csv(path, output_filename):
    """
    path : annotation Detectory
    filename : ouptut file name
    """
    xml_list = []
    # xml 확장자를 가진 모든 파일의 절대 경로로 xml_file할당. 
    with open(output_filename, "w") as train_csv_file:
        for xml_file in glob.glob(path + '/*.xml'):
            # path에 있는 xml파일중 하나 하나를 가져온다. 
            tree = ET.parse(xml_file) 
            root = tree.getroot()
            # 파일내에 있는 모든 object Element를 찾음. 
            full_image_name = os.path.join(IMAGE_DIR, root.find('filename').text)
            value_str_list = ' '
            # find all <object>인것 다 찾는다
            for obj in root.findall('object'): 
                xmlbox = obj.find('bndbox')
                x1 = int(xmlbox.find('xmin').text)
                y1 = int(xmlbox.find('ymin').text)
                x2 = int(xmlbox.find('xmax').text)
                y2 = int(xmlbox.find('ymax').text)
                # 단 하나의 class_id raccoon
                class_id = 0
                value_str = ('{0},{1},{2},{3},{4}').format(x1, y1, x2, y2, class_id)
                value_str_list = value_str_list+value_str+' ' 
                # box1 box2 ......
                # object별 정보를 tuple형태로 object_list에 저장. 
            train_csv_file.write(full_image_name+' '+ value_str_list+'\n') # image_file_path box1 box2 ... boxN \n ... image_file_path
        # xml file 찾는 for loop 종료 
xml_to_csv(ANNO_DIR, os.path.join(ANNO_DIR,'raccoon_anno.csv'))
print(os.path.join(ANNO_DIR,'raccoon_anno.csv'))
/home/sb020518/DLCV/data/raccoon/annotations/raccoon_anno.csv

3-2 전처리한 데이터로 학습시키기 - pretrain 모델가져오기

drawing

  • 위의 파일 내부의 함수들을 import할 예정이다. import방법은 아래와 같다. 참조하자
  • 사진의 model.py, utils.py, keras-yolo3의 train.py파일을 아래와 같이 import할 것이다.
import sys

LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)

from yolo3.model import preprocess_true_boxes, yolo_body, tiny_yolo_body, yolo_loss
from yolo3.utils import get_random_data 
# get_random_data - Image Augmentation 하기 위한 모듈 - Augmentation을 할 때 Bounding Box 처리도 해준다.
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
from train import get_classes, get_anchors
from train import create_model, data_generator, data_generator_wrapper

BASE_DIR = os.path.join(HOME_DIR, 'DLCV/Detection/yolo/keras-yolo3')

## 학습을 위한 기반 환경 설정. annotation 파일 위치, epochs시 저장된 모델 파일, Object클래스 파일, anchor 파일.
annotation_path = os.path.join(ANNO_DIR, 'raccoon_anno.csv')
log_dir = os.path.join(BASE_DIR, 'snapshots/000/')  # tensorboard, epoch 당 weight 저장 장소로 사용할 될 예정이다.
classes_path = os.path.join(BASE_DIR, 'model_data/raccoon_class.txt')    # 내가 만듬 한줄 - raccoon
# tiny yolo로 모델을 학습 원할 시 아래를 yolo_anchors.txt' -> tiny_yolo_anchors.txt'로 수정. 
anchors_path = os.path.join(BASE_DIR,'model_data/yolo_anchors.txt')      # 그대로 사용

class_names = get_classes(classes_path)
num_classes = len(class_names)   # 1개
anchors = get_anchors(anchors_path)

# 아래는 원본 train.py에서 weights_path 변경을 위해 임의 수정. 최초 weight 모델 로딩은 coco로 pretrained된 모델 로딩. 
# tiny yolo로 모델을 학습 원할 시 아래를 model_data/yolo.h5' -> model_data/tiny-yolo.h5'로 수정. 
model_weights_path = os.path.join(BASE_DIR, 'model_data/yolo.h5' )

input_shape = (416,416) # yolo-3 416 을 사용하므로 이렇게 정의. 라쿤 이미지들 wh는 모두 다르다.

is_tiny_version = len(anchors)==6 # default setting
# create_tiny_model(), create_model() 함수의 인자 설정을 원본 train.py에서 수정. 
if is_tiny_version:
    model = create_tiny_model(input_shape, anchors, num_classes,
        freeze_body=2, weights_path=model_weights_path)
else:
    # create_model 은 해당 패키지의 tarin.py 내부에 있는 클래스를 사용했다. 이 함수는 keras 모듈이 많이 사용한다. 우선 모르는 건 pass하고 넘어가자.
    model = create_model(input_shape, anchors, num_classes,
        freeze_body=2, weights_path=model_weights_path) # make sure you know what you freeze

# epoch 마다 call back 하여 모델 파일 저장.
# 이것 또한 Keras에서 많이 사용하는 checkpoint 저장 방식인듯 하다. 우선 이것도 모르지만 넘어가자.
logging = TensorBoard(log_dir=log_dir)
checkpoint = ModelCheckpoint(log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
    monitor='val_loss', save_weights_only=True, save_best_only=True, period=3)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)

Create YOLOv3 model with 9 anchors and 1 classes.
Load weights /home/sb020518/DLCV/Detection/yolo/keras-yolo3/model_data/yolo.h5.
Freeze the first 249 layers of total 252 layers.

3-3 전처리한 데이터로 학습시키기 - 학습과 검증 데이터로 나누고 학습!

  • 학습 전에 GPU정리를 하자.
  • P100에서 40분정도 걸린다.
  • 1 epoch당 weight가 저장되는 것을 확인할 수 있다.

drawing

  • 데이터가 적기 때문에, 최종 Loss값이 큰 것을 확인할 수 있다. 하지만 그래도 Inference 성능이 나쁘지 않다. Class 갯수가 1개 이므로..
val_split = 0.1  # train data : val_data = 9 : 1

with open(annotation_path) as f:
    # 이러니 annotation 파일이 txt이든 csv이든 상관없었다.
    lines = f.readlines()

# 랜덤 시드 생성 및 lines 셔플하기
np.random.seed(10101)
np.random.shuffle(lines)
np.random.seed(None)

# 데이터셋 나누기
num_val = int(len(lines)*val_split)
num_train = len(lines) - num_val

# 여기서 부터 진짜 학습 시작! 
# create_model() 로 반환된 yolo모델에서 trainable=False로 되어 있는 layer들 제외하고 학습
if True:
    # optimizer와 loss 함수 정의
    # 위에서 사용한 create_model 클래스의 맴버함수를 사용한다. 
    model.compile(optimizer=Adam(lr=1e-3), loss={
        # use custom yolo_loss Lambda layer.
        'yolo_loss': lambda y_true, y_pred: y_pred})

    batch_size = 2
    print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
    
    # foward -> backpropagation -> weight 갱신 -> weight 저장
    # checkpoint 만드는 것은 뭔지 모르겠으니 pass...
    model.fit_generator(
            data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
            steps_per_epoch=max(1, num_train//batch_size),
            validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
            validation_steps=max(1, num_val//batch_size),
            epochs=50,
            initial_epoch=0,
            callbacks=[logging, checkpoint])
    model.save_weights(log_dir + 'trained_weights_stage_1.h5')

# create_model() 로 반환된 yolo모델에서 trainable=False로 되어 있는 layer들 없이, 모두 True로 만들고 다시 학습
if True:
    for i in range(len(model.layers)):
        model.layers[i].trainable = True
    model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change
    print('Unfreeze all of the layers.')

    batch_size = 4 # note that more GPU memory is required after unfreezing the body
    print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
    model.fit_generator(
        data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
        steps_per_epoch=max(1, num_train//batch_size),
        validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
        validation_steps=max(1, num_val//batch_size),
        epochs=100,
        initial_epoch=50,
        callbacks=[logging, checkpoint, reduce_lr, early_stopping])
    model.save_weights(log_dir + 'trained_weights_final.h5')
Train on 180 samples, val on 20 samples, with batch size 2.
Epoch 1/50
90/90 [==============================] - 501s 6s/step - loss: 933.7634 - val_loss: 111.7494
Epoch 2/50
66/90 [=====================>........] - ETA: 1:58 - loss: 89.7261


KeyboardInterrupt: 
# YOLO 객체 생성. 
import sys
import argparse
#keras-yolo에서 image처리를 주요 PIL로 수행. 
from PIL import Image

LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)

from yolo import YOLO, detect_video
raccoon_yolo = YOLO(
            model_path=os.path.join(HOME_DIR,'DLCV/Detection/yolo/keras-yolo3/snapshots/000/trained_weights_final.h5'),
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/raccoon_class.txt')
import matplotlib
import matplotlib.pyplot as plt

img = Image.open(os.path.join(IMAGE_DIR, 'raccoon-171.jpg'))

plt.figure(figsize=(12, 12))
plt.imshow(img)
detected_img = raccoon_yolo.detect_image(img)

plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
  • 이제 모델구성까지 모두 끝났다. 이제 raccoon_yolo.detect_image(img) 만 잘사용하면 밑의 내용은 끝이다.

drawing

3-4 임의의 16개의 원본 이미지를 추출하여 Object Detected된 결과 시각화

import numpy as np
np.random.seed(0)

# 랜덤하게 16개의 이미지 파일만 선택.
# 랜덤 숫자 만을 이용하지 말고 아래와 같은 방법을 이용하면, 알아서 random하게 파일을 선택해준다. 
all_image_files = glob.glob(IMAGE_DIR + '/*.jpg')
all_image_files = np.array(all_image_files)
file_cnt = all_image_files.shape[0]
show_cnt = 16
show_indexes = np.random.choice(file_cnt, show_cnt)
show_files = all_image_files[show_indexes]
print(show_files)

# 16개의 
fig, axs = plt.subplots(figsize=(24,24) , ncols=4 , nrows=4)
for i , filename in enumerate(show_files):
    print(filename)
    row = int(i/4)
    col = i%4
    img = Image.open(os.path.join(IMAGE_DIR, filename))
    detected_image = raccoon_yolo.detect_image(img)
    axs[row][col].imshow(detected_image)
    

drawing

4. 위의 내용들을 다시 활용 - Video Object Detection 수행.

  • video frame 가져오는 것은 cv2 사용.
  • 해당 프래임 이미지를 narray에서 PIL image로 변환해서, model.detect_image(image) 수행
import cv2
import time

def detect_video_yolo(model, input_path, output_path=""):
    
    start = time.time()
    cap = cv2.VideoCapture(input_path)
    
    #codec = cv2.VideoWriter_fourcc(*'DIVX')
    codec = cv2.VideoWriter_fourcc(*'XVID')
    vid_fps = cap.get(cv2.CAP_PROP_FPS)
    vid_size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    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, '원본 영상 FPS:',vid_fps, '원본 Frame 크기:', vid_size)
    index = 0
    while True:
        hasFrame, image_frame = cap.read()
        if not hasFrame:
            print('프레임이 없거나 종료 되었습니다.')
            break
        start = time.time()
        # PIL Package를 내부에서 사용하므로 cv2에서 읽은 image_frame array를 다시 PIL의 Image형태로 변환해야 함.  
        image = Image.fromarray(image_frame)
        # 아래는 인자로 입력된 yolo객체의 detect_image()로 변환한다.
        detected_image = model.detect_image(image)
        # cv2의 video writer로 출력하기 위해 다시 PIL의 Image형태를 array형태로 변환 
        result = np.asarray(detected_image)
        index +=1
        print('#### frame:{0} 이미지 처리시간:{1}'.format(index, round(time.time()-start,3)))
        
        vid_writer.write(result)
    
    vid_writer.release()
    cap.release()
    print('### Video Detect 총 수행시간:', round(time.time()-start, 5))
detect_video_yolo(raccoon_yolo, '../../data/video/jack_and_raccoon.mp4', '../../data/output/jack_and_raccoon_yolo_01.avi')

drawing

5. 위의 내용들을 반복 - tiny yolo로 학습.

  • 위에서 했던 내용을 통채로 함수로 만들자.
import numpy as np
import keras.backend as K
from keras.layers import Input, Lambda
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
from train import get_classes, get_anchors
from train import create_model, create_tiny_model, data_generator, data_generator_wrapper

def train_yolo(pretrained_path, annotation_path,classes_path, anchors_path,log_dir,trained_model_name, b_size, epochs_cnt):      
        
        print('pretrained_path:', pretrained_path)
        class_names = get_classes(classes_path)
        num_classes = len(class_names)
        anchors = get_anchors(anchors_path)

        input_shape = (416,416) # multiple of 32, hw
        # tiny yolo여부를 anchor 설정 파일에서 자동으로 알 수 있음. anchor갯수가 6개이면 tiny yolo
        is_tiny_version = len(anchors)==6 # default setting
        
        # create_tiny_model(), create_model() 함수의 인자 설정을 원본 train.py에서 수정.
        if is_tiny_version:
            model = create_tiny_model(input_shape, anchors, num_classes,
                freeze_body=2, weights_path=pretrained_path)
        else:
            model = create_model(input_shape, anchors, num_classes,
                freeze_body=2, weights_path=pretrained_path) # make sure you know what you freeze

        logging = TensorBoard(log_dir=log_dir)
        checkpoint = ModelCheckpoint(log_dir + 'ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
            monitor='val_loss', save_weights_only=True, save_best_only=True, period=3)
        reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=3, verbose=1)
        early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=10, verbose=1)

        val_split = 0.1
        with open(annotation_path) as f:
            lines = f.readlines()
        np.random.seed(10101)
        np.random.shuffle(lines)
        np.random.seed(None)
        num_val = int(len(lines)*val_split)
        num_train = len(lines) - num_val

        # Train with frozen layers first, to get a stable loss.
        # Adjust num epochs to your dataset. This step is enough to obtain a not bad model.
        if True:
            model.compile(optimizer=Adam(lr=1e-3), loss={
                # use custom yolo_loss Lambda layer.
                'yolo_loss': lambda y_true, y_pred: y_pred})

            batch_size = b_size
            print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
            model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
                    steps_per_epoch=max(1, num_train//batch_size),
                    validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
                    validation_steps=max(1, num_val//batch_size),
                    epochs=int(epochs_cnt/2),
                    initial_epoch=0,
                    callbacks=[logging, checkpoint])
            model.save_weights(log_dir + trained_model_name+'_stage_1.h5')

        # Unfreeze and continue training, to fine-tune.
        # Train longer if the result is not good.
        if True:
            for i in range(len(model.layers)):
                model.layers[i].trainable = True
            model.compile(optimizer=Adam(lr=1e-4), loss={'yolo_loss': lambda y_true, y_pred: y_pred}) # recompile to apply the change
            print('Unfreeze all of the layers.')

            batch_size = b_size # note that more GPU memory is required after unfreezing the body
            print('Train on {} samples, val on {} samples, with batch size {}.'.format(num_train, num_val, batch_size))
            model.fit_generator(data_generator_wrapper(lines[:num_train], batch_size, input_shape, anchors, num_classes),
                steps_per_epoch=max(1, num_train//batch_size),
                validation_data=data_generator_wrapper(lines[num_train:], batch_size, input_shape, anchors, num_classes),
                validation_steps=max(1, num_val//batch_size),
                epochs=epochs_cnt,
                initial_epoch=int(epochs_cnt/2),
                callbacks=[logging, checkpoint, reduce_lr, early_stopping])
            model.save_weights(log_dir + trained_model_name+'_final.h5')
BASE_DIR = os.path.join(HOME_DIR, 'DLCV/Detection/yolo/keras-yolo3')
# keras-yolo3에서 convert 된 yolo-tiny pretrained 모델을 사용해야 함. 
pretrained_path = os.path.join(BASE_DIR, 'model_data/yolo-tiny.h5')
annotation_path = os.path.join(ANNO_DIR,'raccoon_anno.csv')
classes_path = os.path.join(BASE_DIR, 'model_data/raccoon_class.txt')
anchors_path = os.path.join(BASE_DIR, 'model_data/tiny_yolo_anchors.txt')
log_dir = os.path.join(BASE_DIR,'snapshots/000/')
trained_model_name = 'raccoon'
b_size=4
epochs_cnt = 100

train_yolo(pretrained_path, annotation_path,classes_path, anchors_path, log_dir,trained_model_name, b_size, epochs_cnt)
raccoon_tiny_yolo = YOLO(model_path=os.path.join(HOME_DIR,'DLCV/Detection/yolo/keras-yolo3/snapshots/000/raccoon_final.h5'),
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/tiny_yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/raccoon_class.txt')
# 16개의 이미지를 Detect해보자. 
import numpy as np
np.random.seed(0)

# 모든 이미지 파일중에서 임의의 16개 파일만 설정. 
all_image_files = glob.glob(IMAGE_DIR + '/*.jpg')
all_image_files = np.array(all_image_files)
file_cnt = all_image_files.shape[0]
show_cnt = 16

show_indexes = np.random.choice(file_cnt, show_cnt)
show_files = all_image_files[show_indexes]
print(show_files)
fig, axs = plt.subplots(figsize=(24,24) , ncols=4 , nrows=4)

for i , filename in enumerate(show_files):
    print(filename)
    row = int(i/4)
    col = i%4
    img = Image.open(os.path.join(IMAGE_DIR, filename))
    detected_image = raccoon_tiny_yolo.detect_image(img)
    axs[row][col].imshow(detected_image)
    
detect_video_yolo(raccoon_tiny_yolo, '../../data/video/jack_and_raccoon.mp4', '../../data/output/jack_and_raccoon_tiny_yolo_01.avi')

drawing

음… tiny yolo… 성능이 심각하게 안 좋다.

【Keras】Keras기반 YOLO3 - 오픈 소스 코드로 Inference

심플하면서 좋은 성능을 보유하는 YOLO - Keras open source 코드를 공부해보자

Keras기반 YOLO3 오픈 소스 코드 공부 및 활용

1. Keras 기반 YOLO

  • DarkNet은 c언어 기반의 하나의 딥러닝 프레임워크라고 할 수 있다.
  • 따라서 Yolo를 python에서 사용하기 위해서는 Tensorflow, keras, torch에서 Yolo모델을 지원해주어야 한다.
  • Tenworflow Object Detection API에서는 YOLO를 지원하지 않고, keras에서 위의 코드를 이용해서 구현할 수 있다.
  • keras를 사용함에 있어서 source 코드가 Tensorflow보다 훨씬 이해하기가 쉽다. 그래서 Customization도 상대적으로 쉽다.

  • keras 기반 유명한 Yolo 오픈소스 코드 비교

drawing

2. qqwweee/keras-yolo3 분석하기

drawing

  1. Yolo.py : train, inference 대부분이 이 파일에 있는 Yolo Class의 메소드 함수를 사용한다.
  2. yolo3 Directory : model.py와 utils.py에 필요한 함수가 정의되어 있고, Yolo.py 내부 함수들이 이것을 이용한다.
  3. train.py : 학습에 사용
  4. convert.py : darkNet weight를 받아서 Keras에서 사용할 수 있는 Weight 파일로 바꿔준다.
  5. coco_annotation.py : coco data format을 csv로 바꿔준다.
  6. voc_annotation.py : voc xml파일 포멧을 csv로 바꿔준다.
  7. yolo_video.py : Inferene 할 수 있따. 하기 전에 cumstomization이 필요하다.
  8. 이 코드는 Setup이 안돼 있어서 손수 몇가지 작업을 해주어야 한다.
  9. keras : 2.2 version. Tensorflow : 1.3 version
  10. model_data : 환경구성 파일 class id 와 mapping, yolo_anchors - Default anchors가 들어가 있다.
    csv 파일 format 예시
    path/to/img1.jpg 50,100,150,200,0 30,50,200,120,3
    path/to/img2.jpg 120,300,250,600,2

3. keras-yolo3 패키지를 이용하여 Yolo 수행

  • 앞으로 우리는 오픈소스 패키지를 이용할 것이다. 이떄 패키지의 .py파일을 그대로 이용하는게 아니라, 파일 내부의 클래스와 맴버 함수를 하나하나 적극적으로 사용해 나갈 것이다.
  • 물론 맴버함수 하나하나 전부 알고 사용한다면 좋겠지만, 정말 필요한 핵심적인 맴버 함수(YOLO.detect_image)를 사용하면 이 함수가 알아서 주변의 맴버 함수 이것저것을 사용해서 결국 Detection을 수행한다.
  • 차근차근 코드를 처보고 익히면서 공부해보자.
  • 참고 자료 : /DLCV/Detection/yolo/KerasYolo_이미지와_영상_Detection.ipynb

  • Yolo를 사용하는 딥러닝 모든 딥러닝 프레임워크 들은, 다크넷에서 Pretrained된 yolo/tiny-yolo weights 모델을 다운로드해서 사용한다.
  • pytorch_YoloV3 package만 봐도 다크넷사이트에서 pretrained 모델을 다운로드해서 처리하는 것을 확인할 수 있다.
  • 다운로드한 다크넷 weight 파일을 기반으로 keras-yolo3에서 사용할 수 있는 weight 파일로 변환 후 이를 이용하여 Object Detection 수행할 것이다. 아래에 어떻게 그렇게 하는지 나와 있다.
import os
import sys
import random
import math
import time
import numpy as np
import tensorflow as tf
import matplotlib
import matplotlib.pyplot as plt
%cd ./keras-yolo3  
from yolo import YOLO  # yolo.py 파일의 YOLO class를 가져온다.
/home/sb020518/DLCV/Detection/yolo/keras-yolo3


Using TensorFlow backend.

3-1 Local Directory 상에서 yolo package를 import 함.

  • 위와 같이 파일이 있는 위치에 가서 import를 해도 되지만 아래와 같은 방법도 있다. 즉
  • keras-yolo3는 setup을 제공하지 않으므로 local directory상에서 바로 package를 import함.
  • 이를 위해 keras-yolo3를 system path에 추가. 그래서 아래 2줄이 필요한 것이다. 없다며 import 불가능
  • keras-yolo3 디렉토리에 있는 yolo.py에서 YOLO class를 import하여 사용.

  • 점프 투 파이썬 내용 참조하기(python package directory path add 하기)
    • import sys; sys.path’ [내부의 위치들이 파이썬 라이브러리가 설치되어 있는 디렉터리들의 목록을 보여준다.]
    • sys.path.append(“모듈이 있는 절대 위치 ex) C:/doit/mymod”) 그 후 import mod3를 하면 정상적으로 import되는 것을 확인할 수 있다.!
print(sys.path)  # 파이썬 라이브러리가 설치되어 있는 디렉터리 확인 가능
LOCAL_PACKAGE_DIR = os.path.abspath("./keras-yolo3")
sys.path.append(LOCAL_PACKAGE_DIR)

from yolo import YOLO  # yolo.py 파일의 YOLO class를 가져온다.
# YOLO 클래스는 model_path, achors_path, classes_path를 model_data 밑에 파일로 가짐. 변경을 위해서는 yolo.py 파일에서 YOLO 클래스코드를  직접 변경 필요. 

print(LOCAL_PACKAGE_DIR)
!ls /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3
!cat /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3/yolo.py
/home/sb020518/DLCV/Detection/yolo/keras-yolo3/keras-yolo3
ls: cannot access '/home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3': No such file or directory
cat: /home/chulmin.kwon45/DLCV/Detection/yolo/keras-yolo3/yolo.py: No such file or directory

YOLO 객체를 사용하기 위한 모델 파일 설정 및 소스 코드 변경

  • 다크넷에서 Yolo V3 Weight 모델 파일을 다운로드 받은 뒤 이를 keras-yolo3용으로 모델 파일 변경
  • convert.py 파일을 이용해서 아래와 같은 작업을 거치면 된다. GPU를 사용하기도 하지만 나는 CPU밖에 없으므로 CPU로도 처리가 가능하다. 대략 5분정도 걸린다.
  • model_data 디렉토리 밑에 yolo_anchors.txt, coco_classes.txt 가 있는지 확인.

  • keras-yolo3 디렉토리 밑에서 아래 명령어로 다크넷에서 Yolov3 weight를 다운로드 받고 이를 keras-yolo3용으로 모델 파일 변경
  • cfg 파일은 이미 git 레포지토리에 있었다. pjreddie(YOLO 저자)사이트에서 받은 파일과 거의 똑같다. batch등 값만 아주 조금 다르다.
$ wget https://pjreddie.com/media/files/yolov3.weights
$ python convert.py yolov3.cfg yolov3.weights model_data/yolo.h5
  • convert.py를 수행하면 yolo anchor값이 yolo_anchors.txt 파일이 자동으로 생성됨.
  • coco label과 클래스 매핑은 0부터 매핑함.(0 => person)
  • yolo_anchors : Default anchor box정보가 들어가 있다.(9개) - 너비 x 높이, 너비 x 높이…

아래 코드 설명

  • YOLO 객체 생성. config는 default로 keras-yolo3 디렉토리에 있는 yolov3.cfg를 적용
  • 중요한 인자를 YOLO 생성자 인자로 넣어준다. 그래서 yolo class가 생성된다.
  • 인자를 아무것도 넣지 않아도, _default에 의해서 맴버변수가 정의가 자동으로 정의되지만, 우리에게 맞게 아래와 같이 설정

drawing

  • self.__dict__.update에 의해서 yolo 생성자의 값이 자동으로 분배 및 저장된다.
    예를 들어서 self.model_path, self.anchors_path, self.classes_path, self.score 가 위에 정의되어 있는 dictionary 변수를 읽어서 자동으로 맴버변수를 정의해준다.
  • 새로 넣어주는 생성자 인자에서 path값을 절대경로로 넣어줘야 한다. 상대경로 안된다!! 아래의 코드들을 보면 왜 안되는지 알 수 있다. (os.path.expanduser 라는 API를 이용하기 때문이다. 이 모듈은 home directory 부터 시작하는 ~부터 시작하는 path를 적어줘야 한다.)
  • yolo class 생성자에 의해서 yolo.py의 _get_class, _get_anchors, generate 맴버함수를 사용한다.
  • 이미지를 PIL을 사용한다. 지금 PIL은 거의 죽은 패키지이다. 근데 이 패키지는 PIL을 사용하기 때문에 우리도 어쩔 수 없이 이것을 사용해야 한다. 보통은 openCV등으로 img read를 해서 array형태로 받아와야하는데…
import sys
import argparse
from yolo import YOLO, detect_video
#keras-yolo에서 image처리를 주요 PIL로 수행. 
from PIL import Image


config_dict = {}
yolo = YOLO(model_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo.h5',
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/coco_classes.txt',
           score=0.55)
# score=0.55를 지우면 원래 _default대로 0.3으로 지정된다.
/home/sb020518/DLCV/Detection/yolo/keras-yolo3/model_data/yolo.h5 model, anchors, and classes loaded.

단일 이미지 Object Detection

# 원본 이미지 보기 # Path 설정 조심하기 ~/DLCV로 하면 안되더라.
from PIL import Image
img = Image.open(os.path.join('/home/sb020518/DLCV/data/image/beatles01.jpg'))

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

drawing

  • yolo.detect_image() 메소드는 PIL package를 이용하여 image 작업 수행. keras-yolo3/font 디렉토리를 상위 디렉토리로 복사 해야함. (나는 아무 문제 없었음)
  • 만약 font문제가 있다면, 해결방법은 다음과 같다.
// detect_image 메소드의 ImageFont.truetype에러. 
~~yolo/keras-yolo3 $ cp -rf font ..
// keras-yolo3 패키지 내부의 font 폴더를 아래의 디렉토리(지금 나의 주피터 노트북파일(KerasYolo_이미지와_영상_Detection)이 있는 위치로 내려준다.)

img = Image.open(os.path.join('/home/sb020518/DLCV/data/image/beatles01.jpg'))
detected_img = yolo.detect_image(img)

plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
(416, 416, 3)
Found 11 boxes for img
car 0.56 (314, 233) (351, 267)
car 0.70 (502, 230) (572, 291)
car 0.79 (471, 235) (503, 261)
car 0.93 (383, 222) (400, 238)
car 0.94 (432, 225) (452, 242)
car 0.96 (138, 246) (256, 334)
person 0.71 (606, 228) (627, 293)
person 0.98 (395, 274) (551, 554)
person 0.98 (588, 272) (735, 557)
person 1.00 (48, 254) (189, 556)
person 1.00 (260, 263) (377, 537)
9.930654634023085

drawing

4. Video Object Detection 수행

  • yolo_video.py를 사용하면 inference를 이미지든 비디오든 쉽게 수행할 수 있지만, 이것은 cv2.imshow를 사용한다. 이것은 jupyter에서 보이지 않는다. (OpenCV Windows Frame 인터페이스 - 이전 게시물)
  • 따라서 우리는 새롭게 우리가 다시 정의해서 사용해보자.
  • 나는 CPU만을 사용하기 때문에 하나의 이미지당 2초가 걸린다. GPU를 쓰는 사람들은 0.05초 정도 걸린다.

drawing

import cv2
import time

def detect_video_yolo(class_yoloModel, input_path, output_path=""):
    
    start = time.time()
    cap = cv2.VideoCapture(input_path)
    
    #codec = cv2.VideoWriter_fourcc(*'DIVX')
    codec = cv2.VideoWriter_fourcc(*'XVID')
    vid_fps = cap.get(cv2.CAP_PROP_FPS)
    vid_size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    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, '원본 영상 FPS:',vid_fps)
    index = 0
    while True:
        hasFrame, image_frame = cap.read()
        if not hasFrame:
            print('프레임이 없거나 종료 되었습니다.')
            break
        start = time.time()
        # PIL Package를 내부에서 사용하므로 cv2에서 읽은 image_frame array를 다시 PIL의 Image형태로 변환해야 함.  
        image = Image.fromarray(image_frame)
        # 아래는 인자로 입력된 yolo객체의 detect_image()로 변환한다.
        detected_image = class_yoloModel.detect_image(image)
        # cv2의 video writer로 출력하기 위해 다시 PIL의 Image형태를 array형태로 변환 
        result = np.asarray(detected_image)
        index +=1
        if index%5 == 0:
            print('#### frame:{0} 1장 이미지 처리시간:{1}, 진행률:{2:4.2f}%'.format(index, round(time.time()-start,3), index/frame_cnt*100 ))
        
        vid_writer.write(result)
    
    vid_writer.release()
    cap.release()
    print('### Video Detect 총 수행시간:', round(time.time()-start, 5))
detect_video_yolo(yolo, '/home/sb020518/DLCV/data/video/Night_Day_Chase.mp4', '/home/sb020518/DLCV/data/output/Night_Day_Chase_yoloKeras_01.avi')
총 Frame 갯수: 1383 원본 영상 FPS: 28.282227867912656
(416, 416, 3)
Found 2 boxes for img
motorbike 0.99 (559, 177) (862, 459)
person 0.87 (549, 113) (870, 451)
2.561595087056048
2.6086239949800074
#### frame:5 5장 이미지 처리시간:13.065, 진행률:0.36%
(416, 416, 3)

drawing

5. tiny Yolo를 이용하여 이미지와 영상 object detection 수행.

  • tiny yolo weights파일은 https://pjreddie.com/media/files/yolov3-tiny.weights 에서 다운로드 받을 수 있음.
  • 다운로드 받은 tiny-yolo weight파일을 keras-yolo3에서 사용할 수 있게 Convert 수행 후 YOLO객체에서 로딩하여 사용
  • 확실히 tiny Yolo는 이미지에 정말 확실한!! 객체만 찾는다.
  • 그럼에도 불구하고 PIL을 사용해서 그런지 (?) 수행속도가 0.03정도 나온다. (논문 공식 속도인 220 FPS 까지는 안된다.)
$ wget https://pjreddie.com/media/files/yolov3-tiny.weights
$ !python convert.py yolov3-tiny.cfg ./model_data/yolov3-tiny.weights model_data/yolo-tiny.h5
  • tiny yolo weight 파일과 tiny와 맞는 anchor 파일, coco 클래스 파일을 YOLO 객체 생성 시 인자로 입력
tiny_yolo = YOLO(model_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/yolo-tiny.h5',
            anchors_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/tiny_yolo_anchors.txt',
            classes_path='~/DLCV/Detection/yolo/keras-yolo3/model_data/coco_classes.txt')
  • 단일 이미지 Object Detection
img = Image.open(os.path.join('../../data/image/beatles01.jpg'))
detected_img = tiny_yolo.detect_image(img)

plt.figure(figsize=(12, 12))
plt.imshow(detected_img)
  • Video Object Detection
detect_video_yolo(tiny_yolo, '../../data/video/secretlife_pet.mp4', '../../data/output/secretlife_pet_yolo01.avi')

Pagination


© All rights reserved By Junha Song.