관리 메뉴

NineTwo meet you

SSD Mobilenet 환경 세팅 및 데이터 학습 진행 튜토리얼 본문

기타

SSD Mobilenet 환경 세팅 및 데이터 학습 진행 튜토리얼

NineTwo 2021. 8. 9. 15:49
반응형

0. 기본 환경

  • Ubuntu 18.04
  • Nvidia-driver 450
  • cuda 10.2
  • cudnn 8.2

 

1. 가상환경 세팅

$ conda create -n tSSD
$ conda activate

의존성 패키지 설치

$ pip install tensorflow

// Tensorflow와 함께 사용할 수 있는 GPU가 있는 경우
$ pip install tensorflow-gpu

$ pip install pillow Cython lxml jupyter matplotlib

 

2. Tensorflow model repository clone

 ~/anaconda/envs/<your_env_name>/lib/python3.6/site-packages/tensorflow로 이동한다.

해당 위치는 설치한 가상환경에 따라 조금씩 다를것이라 예상된다.

$ cd ~/anaconda/envs/tSSD/lib/python3.6/site-packages/tensorflow
$ git clone https://github.com/tensorflow/models.git

3. 환경 세팅

매번 새 터미널을 실행할때마다 다음 명령어를 수행해야 한다.

아니면 다음 ModuleNotFoundError: No module named 'object_detection'라는 에러가 발생한다.

$ cd <경로_to_your_tensorflow_installation>/models/research/
$ protoc object_detection/protos/*.proto --python_out=.
$ export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim

다음 명령어를 실행했을때 별다른 오류없이 잘 됐다는 메세지가 뜨면 잘 설치된 것을 확인할 수 있다.

$ python object_detection/builders/model_builder_test.py

4. 폴더 구조화

다음은 권장하는 models 폴더의 structure 입니다. 

annotations, images등 폴더를 생성합니다.

annotations/xmls에 데이터 라벨링한 xml 파일을 저장하고, images 아래 train 과 test에는 jpg 파일을 적당한 비율(7:3)로 나누어 저장합니다.

models
    - annotations
          -- xmls
    - images
          -- train
          -- test
    - checkpoints
    - tf_record
    - research
    - ...

5. label_map.pbtxt 생성 & trainval.txt 생성

5-1)

label_map.pbtxt 파일을 annotations디렉토리 안에 생성합니다.

예를 들어, 사과과 바나나라는 클래스가 있다면 아래와 같이 작성합니다.

이때 id 0값은 예약되어 있기 때문에 주의해야 할 점은 id값은 1부터 시작해야 합니다.

item { 
    id:
    name: 'apple'
}
item { 
    id: 2 
    name: 'banana'
}

5-2)

trainval.txt 파일을  annotations디렉토리 안에 생성합니다.

아래와 같이 이미지 이름을 확장자없이 나열합니다.

apple1
apple2
banana1
apple3
banana2
:

6. XML 파일->  CSV 파일

XML 파일을 CSV 파일로 전환해야 합니다.

첨부된 링크의 코드를 참고해 XML파일을 CSV파일로 변환할 수 있습니다.

다만 가지고 있는 이미지 파일명이 순차적이지 않았기 때문에 아래 예시처럼 train과 test별로 나눠 python코드를 실행했습니다.  수행하고 나면 data 폴더 안에 train_labels.csv와 test_labels.csv가 저장됩니다.

더보기

test_xml_csv.py

import os
import glob
import pandas as pd
import xml.etree.ElementTree as ET

test_dir = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/images/test'
test_list = os.listdir(test_dir)

def test_xml_to_csv():
    xml_list = []
    for f in test_list:
        xml_file = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/annotations/xmls/'+f[:-4]+'.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():
    xml_df = test_xml_to_csv()
    xml_df.to_csv('data/test_labels.csv', index=None)

main()

train_xml_csv.py

import os
import glob
import pandas as pd
import xml.etree.ElementTree as ET

train_dir = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/images/train'
train_list = os.listdir(train_dir)

def train_xml_to_csv():
    xml_list = []
    for f in train_list:
        xml_file = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/annotations/xmls/'+f[:-4]+'.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():
    xml_df = train_xml_to_csv()
    xml_df.to_csv('data/train_labels.csv', index=None)

main()

 

7. TFRecord 생성 및 config 파일 수정

generate_tfrecord.py를 자기 클래스에 맞춰서 수정해 models/research/object_detection/dataset_tools/ 에 저장합니다. 

코드의 TO-DO부분을 수정합니다.

앞서 생성한 label_map.pbtxt의 id가 여기서 return 값이 되고 row_label은 name값이 됩니다.

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

/models에서 다음과 같이 코드를 train과 test 두 번 수행하고 나면 train.record와 test.record 파일이 생성됩니다.

# train

$ python research/object_detection/dataset_tools/generate_tfrecord.py --csv_input=data/train_labels.csv --output_path=train.record --image_dir=images/train
# test

$ python research/object_detection/dataset_tools/generate_tfrecord.py --csv_input=data/test_labels.csv --output_path=test.record --image_dir=images/test

AttributeError: module 'tensorflow' has no attribute 'app' 에러 발생시 generate_tfrecord.py 에서 
import tensorflow as tf -> import tensorflow.compat.v1 as tf 로 바꾸면 해결됩니다.

8. pre-trained model 다운로드

pre-trainde된 model을 다운받고 /models/checkpoints경로 아래 다음 3가지의 파일을 저장합니다.

/tensorflow 폴더에서 다음 명령어를 실행해 ssd_mobilenet_v2_ coco.config파일을  models/ 로 복사합니다.

$ cp models/research/object_detection/samples/configs/ssd_mobilenet_v2_ coco.config models/
ssd_mobilenet_v2_ coco 파일에서 3가지 사항을 수정합니다. 

1. num_classes 변경

ssd {
    num_classes: 2 # 자기 클래스 개수로 변경
    box_coder {
        faster_rcnn_box_coder {
            y_scale: 10.0 
            x_scale: 10.0 
            height_scale: 5.0 
            width_scale: 5.0 
    } 
 }

2. fine_tune_checkpoint 변경

fine_tune_checkpoint: "checkpoints/model.ckpt" # model.ckpt 경로 입력

3. train, eval input reader 변경

train_input_reader: { 
    tf_record_input_reader { 
        input_path: "train.record" # train.record 경로 입력
    } 
    label_map_path: "annotations/label_map.pbtxt"  #  label_map.pbtxt 경로 입력
  }

eval_input_reader: { 
    tf_record_input_reader { 
        input_path: "test.record" # test.record 경로 입력
    } 
    label_map_path: "annotations/label_map.pbtxt"  # label_map.pbtxt 경로 입력
    shuffle : false
    num_readers : 1
  }

해당 파일을 수정하다 google.protobuf.text_format.ParseError 오류가 발생하면 해당 config 파일에 오타가 발생했다는 의미이므로 다시 잘 살펴봐야 한다. 

9. Train

models디렉토리에서 train, eval 디렉토리를 만들고 train.py를 실행합니다. 

train.py가 research/object_detection에 없으면 찾아봐야 합니다. 찾은 경로의 파일을 실행합니다.

교육 시간은 컴퓨터의 컴퓨팅 성능에 따라 다릅니다. 보통 200000번이 돌아가게 되는데 train.py가 돌아가는 도중 마감해도 됩니다.

# 모델 디렉토리로 변경
 $ cd tensorflow/models

# 학습 진행 상황을 저장할 디렉토리를 만듭니다.
 $ mkdir train

# 유효성 검사 결과를 저장할 디렉토리를 만듭니다.
 $ mkdir eval

# 훈련 시작
 $ python research/object_detection/legacy/train.py \ 
    --logtostderr \ 
    --train_dir=train \ 
    --pipeline_config_path=ssd_mobilenet_v2_coco.config

ValueError: ssd_mobilenet_v2 is not supported. See `model_builder.py` for features extractors compatible with different versions of Tensorflow 에러가 발생하는 경우 tensorflow 1.15.0버전을 설치해줍니다.

$ !pip install tensorflow==1.15.0

10. Evaluation

models디렉토리에서 eval.py를 실행합니다. train.py와 병행하여 실행할 수 있습니다.

train.py와 마찬가지로 research/object_detection에 없으면 찾아봐야 합니다. 찾은 경로의 파일을 실행합니다.

$ python research/object_detection/lecacy/eval.py  
\--logtostderr  
\--pipeline_config_path=ssd_mobilenet_v2_coco.config 
\--checkpoint_dir=train 
\--eval_dir=eval

"tensorflow.python.framework.errors_impl.FailedPreconditionError: ." 간혹 이런 에러가 발생하는 경우 명령어에 잘못된 tab이나 공백을 포함했다는 의미로 명령어를 하나씩 쳐서 다시 실행해야 합니다.

11. Model Export

train을 마치고 /train을 살펴보면 많은 파일이 생긴것을 확인할 수 있다.

/models 디렉토리에서 fine_tuned_model디렉토리를 생성하고 다음 명령어를 입력한다.

이때 <the_highest_checkpoint_number>에는 가장 높은 숫자를 작성하면 된다.

또한 파일명 뒤에 .meta .index .data 등은 입력하지 않습니다.

$ mkdir fine_tuned_model
$ python research/object_detection/export_inference_graph.py \     
--input_type image_tensor \     
--pipeline_config_path ssd_mobilenet_v2_coco.config \     
--trained_checkpoint_prefix train/model.ckpt-<the_highest_checkpoint_number> \     
--output_directory fine_tuned_model

12. Object Detection

models/research/colab_tutorials/object_detection_tutorial.ipynb 파일이 존재합니다. jupyter notebook을 열어서 다음코드를 순서대로 노트북으로 실행하면 됩니다. 다음 블로그의 .py와 .ipynb 파일을 섞어서 사용했습니다. Here 경로를 환경에 맞춰 설정해 사용해야 합니다. 가지고 있던 데이터가 (XXX, XXX) 형태와 (XXX, XXX, 3) 두가지 형태로 되어있어 tensor를 load_image_into_numpy_array에서 tensor를 맞춰주는 return 값을 2개로 수정했습니다.

!pip3 install --upgrade pip

!pip3 install -U --pre tensorflow=="2.*"
!pip3 install tf_slim
!pip install pathlib
import os
import pathlib


if "models" in pathlib.Path.cwd().parts:
  while "models" in pathlib.Path.cwd().parts:
    os.chdir('..')
elif not pathlib.Path('models').exists():
  !git clone --depth 1 https://github.com/tensorflow/models
%%bash
cd /home/piai/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/research/
protoc object_detection/protos/*.proto --python_out=.
export PYTHONPATH=$PYTHONPATH:`pwd`:`pwd`/slim

%cd ./models/research
import numpy as np
import os
import six.moves.urllib as urllib
import sys
import tarfile
import tensorflow as tf
import zipfile

from collections import defaultdict
from io import StringIO
from matplotlib import pyplot as plt
from PIL import Image
from object_detection.utils import ops as utils_ops

if tf.__version__ < '1.4.0':
  raise ImportError('Please upgrade your tensorflow installation to v1.4.* or later!')

from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as vis_util

# What model to download.
MODEL_NAME = 'ssd_mobilenet_v2_coco_2018_03_29'
MODEL_FILE = MODEL_NAME + '.tar.gz'
DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'

# Path to frozen detection graph. This is the actual model that is used for the object detection.
PATH_TO_CKPT = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/fine_tuned_model/frozen_inference_graph.pb' # Here

# List of the strings that is used to add correct label for each box.
PATH_TO_LABELS = '/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/annotations/label_map.pbtxt' # Here
NUM_CLASSES = 2 # Here

opener = urllib.request.URLopener()
opener.retrieve(DOWNLOAD_BASE + MODEL_FILE, MODEL_FILE)
tar_file = tarfile.open(MODEL_FILE)

for file in tar_file.getmembers():
  file_name = os.path.basename(file.name)
  if 'frozen_inference_graph.pb' in file_name:
    tar_file.extract(file, os.getcwd())

detection_graph = tf.Graph()

with detection_graph.as_default():
  od_graph_def = tf.compat.v1.GraphDef()
  with tf.compat.v2.io.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:
    serialized_graph = fid.read()
    od_graph_def.ParseFromString(serialized_graph)
    tf.import_graph_def(od_graph_def, name='')


label_map = label_map_util.load_labelmap(PATH_TO_LABELS)
categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True)
category_index = label_map_util.create_category_index(categories)

def load_image_into_numpy_array(image):
#  return tf.image.grayscale_to_rgb(tf.convert_to_tensor(np.array(image))[...,tf.newaxis]).numpy() # if image shape is (XXX, XXX)
  return np.array(image) # if image shape is (XXX, XXX, 3)

# TEST_IMAGES PATH
PATH_TO_TEST_IMAGES_DIR = pathlib.Path('/home/XXX/anaconda3/envs/tSSD/lib/python3.6/site-packages/tensorflow/models/images/test') # Here
TEST_IMAGE_PATHS = sorted(list(PATH_TO_TEST_IMAGES_DIR.glob("*.jpg")))

# Size, in inches, of the output images.
IMAGE_SIZE = (120, 80)

def run_inference_for_single_image(image, graph):
  with graph.as_default():
    with tf.compat.v1.Session() as sess:
      # Get handles to input and output tensors
      ops = tf.compat.v1.get_default_graph().get_operations()
      all_tensor_names = {output.name for op in ops for output in op.outputs}
      tensor_dict = {}
      for key in [
          'num_detections', 'detection_boxes', 'detection_scores',
          'detection_classes', 'detection_masks'
      ]:
        tensor_name = key + ':0'
        if tensor_name in all_tensor_names:
          tensor_dict[key] = tf.compat.v1.get_default_graph().get_tensor_by_name(tensor_name)

      if 'detection_masks' in tensor_dict:
        # The following processing is only for single image
        detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0])
        detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])
        # Reframe is required to translate mask from box coordinates to image coordinates and fit the image size.
        real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)
        detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])
        detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])
        detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(
            detection_masks, detection_boxes, image.shape[0], image.shape[1])
        detection_masks_reframed = tf.cast(
            tf.greater(detection_masks_reframed, 0.5), tf.uint8)
        # Follow the convention by adding back the batch dimension
        tensor_dict['detection_masks'] = tf.expand_dims(
            detection_masks_reframed, 0)
      image_tensor = tf.compat.v1.get_default_graph().get_tensor_by_name('image_tensor:0')

       # Run inference
      output_dict = sess.run(tensor_dict,
                             feed_dict={image_tensor: np.expand_dims(image, 0)})

      # all outputs are float32 numpy arrays, so convert types as appropriate
      #output_dict['num_detections'] = int(output_dict['num_detections'][0])
      output_dict['num_detections'] = int(tf.get_static_value(output_dict.pop('num_detections'))[0])  
      output_dict['detection_classes'] = output_dict[
          'detection_classes'][0].astype(np.uint8)
      output_dict['detection_boxes'] = output_dict['detection_boxes'][0]
      output_dict['detection_scores'] = output_dict['detection_scores'][0]

      if 'detection_masks' in output_dict:
        output_dict['detection_masks'] = output_dict['detection_masks'][0]
  return output_dict

for image_path in TEST_IMAGE_PATHS:
  image = Image.open(image_path)
  # the array based representation of the image will be used later in order to prepare the
  # result image with boxes and labels on it.
  image_np = load_image_into_numpy_array(image)
  # Expand dimensions since the model expects images to have shape: [1, None, None, 3]

  # Actual detection.
  output_dict = run_inference_for_single_image(image_np, detection_graph)

  # Visualization of the results of a detection.
  vis_util.visualize_boxes_and_labels_on_image_array(
      image_np,
      output_dict['detection_boxes'],
      output_dict['detection_classes'],
      output_dict['detection_scores'],
      category_index,
      instance_masks=output_dict.get('detection_masks'),
      use_normalized_coordinates=True,
      line_thickness=8)

  plt.figure(figsize=IMAGE_SIZE)
  plt.imshow(image_np)
  print ('img',image_path)
  plt.savefig(str(image_path)[:-3]+'png')

참고자료

https://seoftware.tistory.com/108

https://towardsdatascience.com/custom-object-detection-using-tensorflow-from-scratch-e61da2e10087

https://github.com/tensorflow/models/issues?q= 

 

반응형
Comments