はじめに
人気シリーズ、AIで「ねぎ」と「たまねぎ」を見極めよう!の次の企画を始めます。題して、AIでライオンとネコを検出する!です。
これまではTensorflow, Salesforce Einstein Visionといった、AIのフレームワークを用いて、「ねぎ」と「玉ねぎ」を見極めてきました。これらはImage Classificationと呼ばれる技術でした。今回はもう一段レベルを挙げて画像から特定の物体を検出するObject Detectionをやってみようと思います。
前提
CentOS7
環境構築
python3とライブラリの導入
1 2 |
sudo yum install -y https://centos7.iuscommunity.org/ius-release.rpm sudo yum install -y python36 python36-libs python36-devel python36-pip |
1 2 3 4 5 6 |
pip3 install --upgrade pip pip3 install tensorflow pip3 install pandas pip3 install pillow pip3 install Cython pip3 install matplotlib |
Protocol Bufferコンパイラの導入
1 2 3 4 5 |
sudo yum install protobuf-compiler ↑バグってる!! wget https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip unzip protoc-3.3.0-linux-x86_64.zip cp protoc /usr/bin/protoc |
学習データの準備
学習用のデータを準備します。画像を集めるのは大変なので、今回は対象を動画で撮影したものから画像を抽出して学習データとしたいと思います。
1.動画の撮影
2.画像の抽出
3.ラベリング
1についてはスマホなどで動画を撮影してPCに取り込んでください。このiphoneやandroidでさくっっと撮影できると思いますので、説明は割愛します。以下、2と3の説明をします。
画像の抽出
動画から画像を抽出するのはffmpegを用いるのが簡単かと思います。学習用データに適した画像のサイズは使用するモデルによります。今回は’ssd_mobilenet_v2_coco’を用いる予定です。300×300のサイズが適しているようですので、300×300で画像を抽出したいと思います。
ffmpegで下記のコマンドを実行します。
1 |
ffmpeg -i xxx.mp4 -vf "scale=-1:300" -f image2 -r 4 xxx_%05d.jpg |
-r で1秒あたりの画像抽出数を指定します。4だと0.25秒に1枚抽出します。また、scaleでアスペクト比を維持したまま高さを300pxにするように指定します。出力ファイル名はprintfフォーマットに似た感じで連番を付けれます。
ラベリング
画像に対してラベル付けをしていきます。いろいろと調べてみるとlabelImgが評判良さそうなので、これを使ってラベリングしていきます。
こんな感じで、さささっとできます。ショートカットは「w」で矩形の作成開始、「ctrl+s」で保存です。次のイメージに進むためのショートカットはなさそうですが、Next Imageボタンで同じディレクトリの次の画像に進むことができます。
モデル生成
1 2 3 |
git clone https://github.com/tensorflow/models.git git clone https://github.com/cocodataset/cocoapi.git git clone https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10.git |
ProtoclBufferのビルド
cloneしたmodelの下に使用するプロトコルバッファの定義があるのでこれをコンパイルしておきます。
1 2 |
cd models/research/ protoc --python_out=. ./object_detection/protos/*.proto |
cocoapiのビルド
1 2 3 4 |
cd cocoapi/PythonAPI emacs Makefile make cp -r pycocotools xxx/models/research/ |
データ変換ツールの準備
上記のObject Detectionのチュートリアルページのツールが便利なので、cloneしたmodelsのresearch/object_detectionフォルダにコピーしておきます。
1 2 |
cp TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10/generate_tfrecord.py models/research/object_detection/ cp TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10/xml_to_csv.py models/research/object_detection/ |
モデルの取得
1 2 3 |
cd models/research/object_detection/ wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz tar xvfz ssd_mobilenet_v2_coco_2018_03_29.tar.gz |
学習データの振り分け
1 2 3 |
mkdir images mkdir images/test mkdir images/train |
tfrecordの作成
チュートリアルのツールを使ってtfrecordを作成します。まずはcsvを作成してから、tfrecordを作成するという流れになります。ただし、tfrecord生成ツール(generate_tfrecord.py)はチュートリアル用に作成されているので、一部改変が必要です。具体的にはclass_text_to_int:の部分を検出したいものに変更する必要があります。ライオンと猫の場合は次のような感じ。
1 2 3 4 5 6 7 |
def class_text_to_int(row_label): if row_label == 'lion': return 1 elif row_label == 'cat': return 2 else: None |
コマンドは次のように実行します。
1 2 3 4 5 |
export PYTHONPATH=/home/xxx/models:/home/xxx/models/research:/home/xxx/models/research/slim python36 xml_to_csv.py python36 generate_tfrecord.py --csv_input=images/train_labels.csv --image_dir=images/train --output_path=train.record python36 generate_tfrecord.py --csv_input=images/test_labels.csv --image_dir=images/test --output_path=test.record |
トレーニングの設定
1 2 3 4 |
mkdir training cp samples/configs/ssd_mobilenet_v2_coco.config training/ emacs training/labelmap.pbtxt emacs training/ssd_mobilenet_v2_coco.config |
labelmap.pbtxtは次のようになります。
1 2 3 4 5 6 7 8 9 |
item { id: 1 name: 'lion' } item { id: 2 name: 'cat' } |
ssd_mobilenet_v2_coco.configの設定
- num_classes: 識別するクラス数。今回は2
- fine_tune_checkpoint: 今回使うモデルのチェックポイント。今回は/home/xxx/models/research/object_detection/ssd_mobilenet_v2_coco_2018_03_29/model.ckpt
- train_input_reader->input_path: トレーニング用のtfrecord。今回は/home/xxx/models/research/object_detection/train.record
- train_input_reader->label_map_path: ラベルへのパス。今回は/home/xxx/models/research/object_detection/training/labelmap.pbtxt
- eval_input_reader->input_path: エバリュエーション用のtfrecord. 今回は/home/xxx/models/research/object_detection/test.record
- eval_input_reader->label_map_path: ラベルへのパス。今回は/home/xxx/models/research/object_detection/training/labelmap.pbtxt
トレーニング実施
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
python36 ./legacy/train.py --logtostderr --train_dir=log --pipeline_config_path=training/ssd_mobilenet_v2_coco.config <snip> INFO:tensorflow:Recording summary at step 0. INFO:tensorflow:Recording summary at step 0. INFO:tensorflow:global step 1: loss = 14.8737 (37.709 sec/step) INFO:tensorflow:global step 1: loss = 14.8737 (37.709 sec/step) INFO:tensorflow:global step 2: loss = 14.1087 (12.528 sec/step) INFO:tensorflow:global step 2: loss = 14.1087 (12.528 sec/step) INFO:tensorflow:global step 3: loss = 13.5204 (12.200 sec/step) INFO:tensorflow:global step 3: loss = 13.5204 (12.200 sec/step) INFO:tensorflow:global step 4: loss = 12.9567 (12.427 sec/step) INFO:tensorflow:global step 4: loss = 12.9567 (12.427 sec/step) INFO:tensorflow:global step 5: loss = 12.8636 (10.828 sec/step) INFO:tensorflow:global step 5: loss = 12.8636 (10.828 sec/step) INFO:tensorflow:global step 6: loss = 12.3016 (11.096 sec/step) INFO:tensorflow:global step 6: loss = 12.3016 (11.096 sec/step) INFO:tensorflow:global step 7: loss = 11.8504 (11.058 sec/step) INFO:tensorflow:global step 7: loss = 11.8504 (11.058 sec/step) |
検出
1 |
python3.6 export_inference_graph.py --input_type=image_tensor --pipeline_config_path=log/pipeline.config --trained_checkpoint_prefix=log/model.ckpt-xxx --output_directory=out |
まとめ
今回は、Tensorflowを使って「ライオン」と「ネコ」の検出を行いました。結構簡単にできてびっくりですね。次はもう少し精度を上げられるようにGPUを使って学習をしてみたいなぁ。(希望)
付録
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
import numpy as np import os import six.moves.urllib as urllib import sys import tarfile import tensorflow as tf import zipfile import matplotlib matplotlib.use('Agg') from distutils.version import StrictVersion from collections import defaultdict from io import StringIO from matplotlib import pyplot as plt from PIL import Image # This is needed since the notebook is stored in the object_detection folder. sys.path.append("..") from object_detection.utils import ops as utils_ops from utils import label_map_util from utils import visualization_utils as vis_util PATH_TO_FROZEN_GRAPH="out/frozen_inference_graph.pb" PATH_TO_LABELS="training/labelmap.pbtxt" detection_graph = tf.Graph() with detection_graph.as_default(): od_graph_def = tf.GraphDef() with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid: serialized_graph = fid.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='') category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True) def load_image_into_numpy_array(image): (im_width, im_height) = image.size return np.array(image.getdata()).reshape( (im_height, im_width, 3)).astype(np.uint8) PATH_TO_TEST_IMAGES_DIR = 'images' TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'train/IMG_20190222_224827.jpg')] # Size, in inches, of the output images. IMAGE_SIZE = (12, 8) def run_inference_for_single_image(image, graph): with graph.as_default(): with tf.Session() as sess: # Get handles to input and output tensors ops = tf.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.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.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['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] image_np_expanded = np.expand_dims(image_np, axis=0) # 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) plt.savefig('figure.png') |
コメント