Object Detection with Tensorflow API

Object Detection 이란?

Object Detection이란 사진이나 동영상에 존재하는 사물에 대해 인식하는 것을 의미합니다. 다음은 위키 피디아(Wikipedia)에서 정의한 Object detection의 정의입니다

Object detection is a computer technology related to computer vision and image processing that deals with detecting instances of semantic objects of a certain class (such as humans, buildings, or cars) in digital images and videos. Well-researched domains of object detection include face detection and pedestrian detection. Object detection has applications in many areas of computer vision, including image retrieval and video surveillance.

사물인식은 디지털 이미지와 비디오에 존재하는 특정 사물을 인식하는 컴퓨터 비전과 이미지 처리와 관련된 컴퓨터 기술이다. 사물인식에 관한 연구로는 얼굴 인식과 보행자 인식 등이 있으며, 이미지 검색과 비디오 감시 등 다양한 컴퓨터 비전 분야에 사용되고 있다.

사물인식의 예는 아래의 그림과 같으며, 이미지 내에서 사람과 연에 대한 인식 결과를 살펴볼 수 있습니다.

Object Detection은 어떻게 구현할까?

상당한 수준의 난이도를 요구하는 기술처럼 보이지만, 다행스럽게도 Tensorflow에서 Object Detection에 대한 API를 제공하고 있으며, YOUTUBE의 Sentdex 채널(https://goo.gl/ReG6Z9)에서도 초보자를 위해 Tensorflow API를 활용하여 Object Detection을 구현하도록 영상을 업로드 했습니다.

먼저 Object Detection을 구현하기 위해 다음과 같은 환경이 갖추어져야 합니다.

  1. Linux OS(Windows 기반에서는 Tensorflow API를 활용하여 Object Detection을 구현할 수 없습니다.)
  2. Python 3
  3. Tensorflow library

또한, Object Detection을 구현하기 위해 다음과 같은 과정이 수행됩니다.

  1. Tensorflow API 설치
  2. 이미지 수집
  3. Object Labeling
  4. tf-record 생성
  5. 기기 학습
  6. 결과 확인

1. Tensorflow API 설치

Object Detection를 구현하기 위해서는 Tensorflow API를 설치해야 합니다. 우선 아래의 압축 파일을 다운받습니다.

models.zip (148 downloads)

바탕화면에 ‘models.zip’의 압축을 해제합니다.

바탕화면에서 터미널을 실행한 후(Ctrl+Alt+T) 아래의 명령문을 차례대로 실행합니다.

sudo pip install pillow
sudo pip install lxml
sudo pip install jupyter
sudo pip install matplotlib
cd Desktop/models
protoc object_detection/protos/*.proto --python_out=.
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
sudo python3 setup.py install

———————————명령문 설명———————————
1. sudo pip3 install pillow
2. sudo pip3 install lxml
3. sudo pip3 install jupyter
4. sudo pip3 install matplotlib => (1~4)pillow, lxml, jupyter, matplotlib를 설치합니다.
5. sudo apt-get install pyqt5-dev-tools -> pyqt5-dev-tools를 설치합니다.
6. cd Desktop/models -> models 경로로 이동합니다.
7. protoc object_detection/protos/*.proto –python_out=.
8. export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
9. sudo python3 setup.py install => (7~9) object detection API를 설치합니다.
—————————————————————————–

명령문 실행 결과는 아래의 그림과 같습니다.

2. 이미지 수집

이미지 수집 절차는 간단합니다. Google및 여러 검색 포털 사이트에서 연구 목적에 맞는 이미지를 검색하여 다운로드 할 수 있으며, 본 게시물에서는 ‘마카로니 치즈’에 대한 이미지를 검색하여 수집했습니다.

빠른 이미지 수집을 위해 아래의 링크에서 ‘마카로니 치즈’에 대한 이미지를 다운로드 할 수 있습니다.

images-1.zip (124 downloads)
출처: Python Programming Tutorials(https://goo.gl/ekkARi)

3. Object Labeling

이미지 수집이 완료된 후에는 Object labeling을 수행해야 합니다. Object labeling을 위해 바탕화면에서 터미널 창(Linux에서 Ctrl+Alt+T 입력)을 열어 아래의 명령어를 차례대로 실행합니다.

git clone https://github.com/tzutalin/labelImg
sudo apt-get install pyqt5-dev-tools
cd labelImg
make qt5py3
python3 labelImg.py

———————————명령문 설명———————————
sudo apt-get install git -> git을 설치하는 단계입니다.
git clone https://github.com/tzutalin/labelImg -> labelImg에 대한 code를 git을 사용하여 다운로드합니다.
sudo apt-get install pyqt5-dev-tools -> pyqt5-dev-tools를 설치합니다.
cd labelImg -> labelImg 경로로 이동합니다.
make qt5py3 -> qt5py3를 생성합니다.
python3 labelImg.py -> labelImg.py를 실행합니다.
—————————————————————————–

위의 코드 실행 결과는 다음과 같습니다.

Object labeling을 수행하기 위해 이전에 다운로드 했던 이미지 파일의 경로를 아래와 같이 설정합니다.

아래와 같이 ‘Create RectBox’를 클릭한 다음 마카로니 치즈에 대한 영역을 드래그하여 설정합니다. 새로운 창이 생성되면 ‘macncheese’를 기입한 다음 ‘OK’ 버튼을 클릭합니다. 모든 사진에 대하여 동일한 절차를 반복합니다.

해당 절차는 시간이 많이 소요되는 작업이므로, 시간이 없거나 빠른 진행을 원하시는 분들은 아래의 파일을 다운로드 하시면 됩니다.

object-detection-1.zip (148 downloads)
출처: Python Programming Tutorials(https://goo.gl/ekkARi)

4. Tf-record 생성

Tensorflow API를 활용하여 기기를 학습시키려면 Object label 결과를 tf-record로 생성하는 절착라 필요합니다. labelImg를 활용하여 Object labeling 결과는 xml형태로 저장되어 있습니다. xml형태로 저장되어 있는 Object labeling 결과를 CSV 형태로 변환한 다음, 이를 tf-record 형태로 변환하겠습니다.

1. xml을 csv로 변환하기

이전에 다운로드 받았던 ‘object-detection.zip’을 바탕화면에서 압축 해제합니다. ‘object-detection’폴더에서 아래 그림과 같이 오른쪽 클릭을 한 다음 ‘New Document’ -> ‘Empty Document’를 클릭합니다. 새로운 문서의 이름을 ‘xml_to_csv.py’로 입력한 다음 저장한 다음 해당 파일을 실행합니다.

xml_to_csv.py를 실행한 다음 아래의 코드를 복사하여 붙여넣은 다음 저장합니다.

import os
import glob
import pandas as pd
import xml.etree.ElementTree as ET
def xml_to_csv(path):
    xml_list = []
    for xml_file in glob.glob(path + '/*.xml'):
        tree = ET.parse(xml_file)
        root = tree.getroot()
        for member in root.findall('object'):
            value = (root.find('filename').text,
                     int(root.find('size')[0].text),
                     int(root.find('size')[1].text),
                     member[0].text,
                     int(member[4][0].text),
                     int(member[4][1].text),
                     int(member[4][2].text),
                     int(member[4][3].text)
                     )
            xml_list.append(value)
    column_name = ['filename', 'width', 'height', 'class', 'xmin', 'ymin', 'xmax', 'ymax']
    xml_df = pd.DataFrame(xml_list, columns=column_name)
    return xml_df
def main():
    for directory in ['train','test']:
        image_path = os.path.join(os.getcwd(), 'images/{}'.format(directory))
        xml_df = xml_to_csv(image_path)
        xml_df.to_csv('data/{}_labels.csv'.format(directory), index=None)
        print('Successfully converted xml to csv.')
main()

 

출처: https://github.com/datitran/raccoon_dataset

‘object-detection’폴더에서 ‘data’라는 새로운 폴더를 생성합니다. ‘object-detection’ 폴더 내에서 터미널을 열어 아래의 명령을 차례대로 실행합니다.

sudo pip3 install pandas
python3 xml_to_csv.py

———————————명령문 설명———————————
sudo pip3 install pandas -> python library중 하나인 pandas를 설치합니다.
python3 xml_to_csv.py -> xml_to_csv.py를 실행합니다.
—————————————————————————–

아래의 그림과 같은 화면이 나타나면 명령문이 성공적으로 실행된 것입니다.


‘object-detection’->’data’로 이동하면 ‘test_labels.csv’와 ‘train_labels.csv’가 생성된 것을 확인할 수 있습니다.

2. csv를 tf-record로 변환하기

‘object-detection’폴더에서 아래 그림과 같이 오른쪽 클릭을 한 다음 ‘New Document’ -> ‘Empty Document’를 클릭합니다. 새로운 문서의 이름을 ‘generate_tfrecord.py’로 입력한 다음 저장한 다음 해당 파일을 실행합니다.

generate_tfrecord.py를 실행한 다음 아래의 코드를 복사하여 붙여넣은 다음 저장합니다.

"""
Usage:
  # From tensorflow/models/
  # Create train data:
  python3 generate_tfrecord.py --csv_input=data/train_labels.csv  --output_path=data/train.record

  # Create test data:
  python3 generate_tfrecord.py --csv_input=data/test_labels.csv  --output_path=data/test.record
"""
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import os
import io
import pandas as pd
import tensorflow as tf

from PIL import Image
from object_detection.utils import dataset_util
from collections import namedtuple, OrderedDict

flags = tf.app.flags
flags.DEFINE_string('csv_input', '', 'Path to the CSV input')
flags.DEFINE_string('output_path', '', 'Path to output TFRecord')
FLAGS = flags.FLAGS

# TO-DO replace this with label map
def class_text_to_int(row_label):
    if row_label == 'macncheese':
        return 1
    else:
        None

def split(df, group):
    data = namedtuple('data', ['filename', 'object'])
    gb = df.groupby(group)
    return [data(filename, gb.get_group(x)) for filename, x in zip(gb.groups.keys(), gb.groups)]

def create_tf_example(group, path):
    with tf.gfile.GFile(os.path.join(path, '{}'.format(group.filename)), 'rb') as fid:
        encoded_jpg = fid.read()
    encoded_jpg_io = io.BytesIO(encoded_jpg)
    image = Image.open(encoded_jpg_io)
    width, height = image.size

    filename = group.filename.encode('utf8')
    image_format = b'jpg'
    xmins = []
    xmaxs = []
    ymins = []
    ymaxs = []
    classes_text = []
    classes = []

    for index, row in group.object.iterrows():
        xmins.append(row['xmin'] / width)
        xmaxs.append(row['xmax'] / width)
        ymins.append(row['ymin'] / height)
        ymaxs.append(row['ymax'] / height)
        classes_text.append(row['class'].encode('utf8'))
        classes.append(class_text_to_int(row['class']))

    tf_example = tf.train.Example(features=tf.train.Features(feature={
        'image/height': dataset_util.int64_feature(height),
        'image/width': dataset_util.int64_feature(width),
        'image/filename': dataset_util.bytes_feature(filename),
        'image/source_id': dataset_util.bytes_feature(filename),
        'image/encoded': dataset_util.bytes_feature(encoded_jpg),
        'image/format': dataset_util.bytes_feature(image_format),
        'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),
        'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),
        'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),
        'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),
        'image/object/class/text': dataset_util.bytes_list_feature(classes_text),
        'image/object/class/label': dataset_util.int64_list_feature(classes),
    }))
    return tf_example
def main(_):
    writer = tf.python_io.TFRecordWriter(FLAGS.output_path)
    path = os.path.join(os.getcwd(), 'images')
    examples = pd.read_csv(FLAGS.csv_input)
    grouped = split(examples, 'filename')
    for group in grouped:
        tf_example = create_tf_example(group, path)
        writer.write(tf_example.SerializeToString())

    writer.close()
    output_path = os.path.join(os.getcwd(), FLAGS.output_path)
    print('Successfully created the TFRecords: {}'.format(output_path))


if __name__ == '__main__':
    tf.app.run()

출처: https://github.com/datitran/raccoon_dataset

‘generate_tfrecord.py’를 실행하기 이전에 ‘Desktop/models’에서 터미널을 열어 다음과 같은 명령어를 실행합니다.

export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
cd ..
cd object-detection
python3 generate_tfrecord.py --csv_input=data/train_labels.csv --output_path=data/train.record
python3 generate_tfrecord.py --csv_input=data/test_labels.csv --output_path=data/test.record

———————————명령문 설명———————————
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim -> object detection API를 불러옵니다.
cd .. -> 상위 폴더로 이동합니다
cd object-detection -> Desktop/object-detection 폴더로 이동합니다.
python3 generate_tfrecord.py –csv_input=data/train_labels.csv –output_path=data/train.record -> train_lables.csv를 tfrecord로 변환합니다.
python3 generate_tfrecord.py –csv_input=data/test_labels.csv –output_path=data/test.record -> test_lables.csv를 tfrecord로 변환합니다.
—————————————————————————–

아래 그림은 명령문 실행 결과입니다.

object-detecion 폴더 내의 data 폴더로 이동하면 아래 그림과 같이 ‘test.record’, ‘train.record’가 생성된 것을 알 수 있습니다.

5. 기기 학습

기기 학습을 위해 아래의 파일을 다운로드한 다음 ‘object-detection’ 폴더에 압축 해제합니다.

ssd_mobilenet_v1_coco_11_06_2017.tar.gz (138 downloads)
출처: Tensorflow API(https://goo.gl/BKT3y2)

‘object-dection’ 폴더 내에 ‘training’ 폴더를 새롭게 생성합니다.

새롭게 생성된 ‘training’ 폴더 내에서 ‘ssd_mobilenet_v1_pets.config’ 라는 제목의 새로운 문서를 생성합니다.

‘ssd_mobilenet_v1_pets.config’를 실행하여 아래의 코드를 복사해서 붙여넣은 후 저장합니다.

# SSD with Mobilenet v1, configured for the mac-n-cheese dataset.
# Users should configure the fine_tune_checkpoint field in the train config as
# well as the label_map_path and input_path fields in the train_input_reader and
# eval_input_reader. Search for "${YOUR_GCS_BUCKET}" to find the fields that
# should be configured.

model {
  ssd {
    num_classes: 1
    box_coder {
      faster_rcnn_box_coder {
        y_scale: 10.0
        x_scale: 10.0
        height_scale: 5.0
        width_scale: 5.0
      }
    }
    matcher {
      argmax_matcher {
        matched_threshold: 0.5
        unmatched_threshold: 0.5
        ignore_thresholds: false
        negatives_lower_than_unmatched: true
        force_match_for_each_row: true
      }
    }
    similarity_calculator {
      iou_similarity {
      }
    }
    anchor_generator {
      ssd_anchor_generator {
        num_layers: 6
        min_scale: 0.2
        max_scale: 0.95
        aspect_ratios: 1.0
        aspect_ratios: 2.0
        aspect_ratios: 0.5
        aspect_ratios: 3.0
        aspect_ratios: 0.3333
      }
    }
    image_resizer {
      fixed_shape_resizer {
        height: 300
        width: 300
      }
    }
    box_predictor {
      convolutional_box_predictor {
        min_depth: 0
        max_depth: 0
        num_layers_before_predictor: 0
        use_dropout: false
        dropout_keep_probability: 0.8
        kernel_size: 1
        box_code_size: 4
        apply_sigmoid_to_scores: false
        conv_hyperparams {
          activation: RELU_6,
          regularizer {
            l2_regularizer {
              weight: 0.00004
            }
          }
          initializer {
            truncated_normal_initializer {
              stddev: 0.03
              mean: 0.0
            }
          }
          batch_norm {
            train: true,
            scale: true,
            center: true,
            decay: 0.9997,
            epsilon: 0.001,
          }
        }
      }
    }
    feature_extractor {
      type: 'ssd_mobilenet_v1'
      min_depth: 16
      depth_multiplier: 1.0
      conv_hyperparams {
        activation: RELU_6,
        regularizer {
          l2_regularizer {
            weight: 0.00004
          }
        }
        initializer {
          truncated_normal_initializer {
            stddev: 0.03
            mean: 0.0
          }
        }
        batch_norm {
          train: true,
          scale: true,
          center: true,
          decay: 0.9997,
          epsilon: 0.001,
        }
      }
    }
    loss {
      classification_loss {
        weighted_sigmoid {
          anchorwise_output: true
        }
      }
      localization_loss {
        weighted_smooth_l1 {
          anchorwise_output: true
        }
      }
      hard_example_miner {
        num_hard_examples: 3000
        iou_threshold: 0.99
        loss_type: CLASSIFICATION
        max_negatives_per_positive: 3
        min_negatives_per_image: 0
      }
      classification_weight: 1.0
      localization_weight: 1.0
    }
    normalize_loss_by_num_matches: true
    post_processing {
      batch_non_max_suppression {
        score_threshold: 1e-8
        iou_threshold: 0.6
        max_detections_per_class: 100
        max_total_detections: 100
      }
      score_converter: SIGMOID
    }
  }
}

train_config: {
  batch_size: 10
  optimizer {
    rms_prop_optimizer: {
      learning_rate: {
        exponential_decay_learning_rate {
          initial_learning_rate: 0.004
          decay_steps: 800720
          decay_factor: 0.95
        }
      }
      momentum_optimizer_value: 0.9
      decay: 0.9
      epsilon: 1.0
    }
  }
  fine_tune_checkpoint: "ssd_mobilenet_v1_coco_11_06_2017/model.ckpt"
  from_detection_checkpoint: true
  data_augmentation_options {
    random_horizontal_flip {
    }
  }
  data_augmentation_options {
    ssd_random_crop {
    }
  }
}

train_input_reader: {
  tf_record_input_reader {
    input_path: "data/train.record"
  }
  label_map_path: "data/object-detection.pbtxt"
}

eval_config: {
  num_examples: 40
}

eval_input_reader: {
  tf_record_input_reader {
    input_path: "data/test.record"
  }
  label_map_path: "training/object-detection.pbtxt"
  shuffle: false
  num_readers: 1
}

‘training’ 폴더에서 ‘object-detection.pbtxt’ 라는 이름의 새로운 문서를 생성합니다.

‘object-detection.pbtxt’를 실행한 후 아래의 코드를 복사하여 붙여넣은 다음 저장합니다.

item{
  id: 1
  name: 'macncheese'
}

‘object-detection.pbtxt’를 복사하여 ‘object-detection/data’에 붙여넣습니다.

object-detection 내의 ‘data’, ‘images’, ‘ssd_mobilenet_v1_coco_11_06_2017’, ‘training’ 폴더를 ‘models/obejct_detection’ 폴더로 복사하여 붙여넣습니다.

‘Desktop/models’에서 터미널을 실행한 다음 아래의 명령문을 실행합니다.

export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
cd object_detection 
python3 train.py --logtostderr --train_dir=training/ --pipeline_config_path=training/ssd_mobilenet_v1_pets.config

아래 그림은 명령문 실행 결과입니다.

step이 증가함에 따라 loss가 감소하는 것을 알 수 있습니다. 충분한 학습이 이루어 진 후 학습을 중지 시키려면 ‘Ctrl+C’를 입력하면 됩니다.

충분한 학습이 이루어진 다음 ‘desktop/models/object_detection’에서 터미널을 실행한 후 아래의 명령문을 실행하면 Tensorboard를 확인할 수 있는 주소값을 출력합니다.

tensorboard --logdir='training'

아래 그림은 명령문 실행 결과입니다.

http://LEE:6006에 접속하면 다음과 같은 화면이 출력됩니다.

‘TotalLoss’를 클릭하면 다음과 같은 그래프가 출력됩니다. 해당 그래프를 살펴보면 학습이 진행됨에 따라 Loss 값이 줄어드는 것을 알 수 있습니다.

6. 결과 확인

‘Desktop/models’에서 터미널을 실행한 다음 아래의 명령문을 실행합니다.

export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim
cd object_detection
python3 export_inference_graph.py \
 --input_type image_tensor \
 --pipeline_config_path training/ssd_mobilenet_v1_pets.config \
 --trained_checkpoint_prefix training/model.ckpt-9540 \
 --output_directory mac_n_cheese_graph

———————————주의———————————
–trained_checkpoint_prefix training/model.ckpt-9540 \ model.ckpt-9540은 아래의 그림과 같이 ckpt 파일 중 가장 높은 수의 파일을 기준으로 작성된 코드이며 연구 상황에 따라 변경될 수 있습니다. 

아래의 그림은 명령문 실행 결과입니다.

‘Desktop/models/object_detection/mac_n_cheese_graph’로 이동하면 아래와 같이 새로운 ckpt 파일이 생성된 것을 알 수 있습니다.

‘Desktop/models/object_detection’에서 터미널 실행 후 아래의 명령어를 실행합니다.

jupyter notebook

아래의 그림은 명령문 실행 결과입니다.

‘object_detection_tutoral.ipynb’를 클릭합니다.

Model preparation의 ‘Variables’ 코드를 아래와 같이 변경합니다.

Model preparation – Variables

# What model to download.
MODEL_NAME = 'mac_n_cheese_graph'

# Path to frozen detection graph. This is the actual model that is used for the object detection.
PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'

# List of the strings that is used to add correct label for each box.
PATH_TO_LABELS = os.path.join('training', 'object-detection.pbtxt')

NUM_CLASSES = 1

Model preparation의 ‘Download Model’ 코드를 삭제합니다.

코드 변경 결과는 아래의 그림과 같습니다.

‘Desktop/object-detection/images’ 폴더로 이동하여 테스트 하고자 하는 몇 개의 이미지를 복사한 다음 ‘Desktop/models/object_detection/test_images’에 붙여넣습니다. 붙여넣은 이미지 파일의 이름을 ‘image1.jpg’, ‘image2.jpg’ 와 같이 image + 숫자 + .jpg 로 변경합니다.

Detection에 관한 코드를 이미지 개수에 맞게 변경합니다. 제 경우에는 image3.jpg ~ image5.jpg 가 test image로 활용되므로 for i in range(1,3) for i in range(3, 6)으로 변경했습니다.

Detection

# For the sake of simplicity we will use only 2 images:
# image1.jpg
# image2.jpg
# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.
PATH_TO_TEST_IMAGES_DIR = 'test_images'
TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(3, 6) ]
# Size, in inches, of the output images.
IMAGE_SIZE = (12, 8)

메뉴바에서 ‘Cell – Run All’을 차례대로 클릭합니다.

아래와 같은 출력 결과를 확인할 수 있습니다.

댓글 남기기