TensorFlow 2 による物体検出」で、アノテーション作成ツール「VoTT」を使用して、動画から教師データや検証用データとして使用するアノテーションデータを作成しました。今回は出力されたアノテーションデータについて、詳しく調べます。

【実行環境】

  • tensorflow 2.6.0
  • tensorflow-addons 0.14.0
  • tensorflow-datasets 4.4.0
  • tensorflow-estimator 2.6.0
  • tensorflow-gpu 2.6.0
  • tensorflow-hub 0.12.0
  • tensorflow-metadata 1.2.0
  • tensorflow-model-optimization 0.6.0
  • tensorflow-text 2.6.0

TFRecordファイルの可視化

アノテーション作成ツール「VoTT」で、フォルダ「TensorTest-TFRecords-export」にエクスポートしたTFRecordファイルの構造について、次のスクリプト「TFRecordToImg.py」により可視化します。ファイル「signalmp4.tfrecord」は、アノテーション作成ツール「VoTT」でエクスポートしたTFRecordファイルです。詳細については「TFRecords と tf.Example の使用法」の「TFRecord ファイルの読み込み」を参照してください。

TFRecordToImg.py

import tensorflow as tf
import numpy as np
import IPython.display as display

loaded_ds_train = tf.data.TFRecordDataset("signalmp4.tfrecord")
print(loaded_ds_train)

for raw_record in loaded_ds_train.take(1):
#    print(repr(raw_record))
    example = tf.train.Example()
    example.ParseFromString(raw_record.numpy())
    print(example)

スクリプト「TFRecordToImg.py」を実行した結果を次に示します。

  • 9行目の「image/encoded」で、 画像のバイナリデータが定義されます。
  • 49行目の「image/object/bbox/xmax」,58行目の「image/object/bbox/xmin」,67行目の「image/object/bbox/ymax」,76行目の「image/object/bbox/ymin」で、 アノテーションした座標位置が定義されます。アノテーションした数だけ値が含みます。
  • 85行目の「class/label で、タグ名に付与された番号「0」「1」が定義されます。
  • 94行目の「class/text」で、タグの名前「signalA_red」「signalB_green」が定義されます。
2021-10-08 08:18:33.422875: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-10-08 08:18:34.861523: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 1335 MB memory:  -> device: 0, name: NVIDIA GeForce GT 1030, pci bus id: 0000:01:00.0, compute capability: 6.1
<TFRecordDatasetV2 shapes: (), types: tf.string>
2021-10-08 08:18:34.966740: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
features {
  feature {
    key: "image/encoded"
    value {
      bytes_list {
        value: "\377\330\37 ・・・・・(シリアライズされた画像データ)
      }
    }
  }
  feature {
    key: "image/filename"
    value {
      bytes_list {
        value: "signal.mp4#t=0.2.jpg"
      }
    }
  }
  feature {
    key: "image/format"
    value {
      bytes_list {
        value: "jpg"
      }
    }
  }
  feature {
    key: "image/height"
    value {
      int64_list {
        value: 480
      }
    }
  }
  feature {
    key: "image/key/sha256"
    value {
      bytes_list {
        value: "TqXFCKZWbnYkBUP4/rBv1Fd3e+OVScQBZDav2mXSMw4="
      }
    }
  }
  feature {
    key: "image/object/bbox/xmax"
    value {
      float_list {
        value: 0.6279257535934448
        value: 0.4789402186870575
      }
    }
  }
  feature {
    key: "image/object/bbox/xmin"
    value {
      float_list {
        value: 0.48531702160835266
        value: 0.37633150815963745
      }
    }
  }
  feature {
    key: "image/object/bbox/ymax"
    value {
      float_list {
        value: 0.3261205554008484
        value: 0.645285964012146
      }
    }
  }
  feature {
    key: "image/object/bbox/ymin"
    value {
      float_list {
        value: 0.2047913372516632
        value: 0.515455961227417
      }
    }
  }
  feature {
    key: "image/object/class/label"
    value {
      int64_list {
        value: 0
        value: 1
      }
    }
  }
  feature {
    key: "image/object/class/text"
    value {
      bytes_list {
        value: "signalA_red"
        value: "signalB_green"
      }
    }
  }
  feature {
    key: "image/object/difficult"
    value {
      int64_list {
        value: 0
        value: 0
      }
    }
  }
  feature {
    key: "image/object/truncated"
    value {
      int64_list {
        value: 0
        value: 0
      }
    }
  }
  feature {
    key: "image/object/view"
    value {
      bytes_list {
        value: "Unspecified"
        value: "Unspecified"
      }
    }
  }
  feature {
    key: "image/source_id"
    value {
      bytes_list {
        value: "signal.mp4#t=0.2.jpg"
      }
    }
  }
  feature {
    key: "image/width"
    value {
      int64_list {
        value: 640
      }
    }
  }
}

Process finished with exit code 0

アノテーション画像の取得

アノテーション作成ツール「VoTT」で、フォルダ「Target」に出力されたjsonファイルを用いて、入力した動画ファイルから作成したアノテーション画像を、次のスクリプト「cutimg.py」により取得します。

cutimg.py

import json
import os
import fnmatch
import cv2

JSON_DIR = 'vott-json/'
IMG_DIR = 'vott-json/'
CUT_IMAGE = 'cut_images/'
IMAGE_FORMAT = '.jpg'


class Check():

    def filepath_checker(self, dir):

        if not (os.path.exists(dir)):
            print('No such directory > ' + dir)
            exit()

    def directory_init(self, dir):

        if not (os.path.exists(dir)):
            os.makedirs(dir, exist_ok=True)


def save_frame_sec(video_path, sec, result_path):
    cap = cv2.VideoCapture(video_path)

    if not cap.isOpened():
        return

    os.makedirs(os.path.dirname(result_path), exist_ok=True)

    fps = cap.get(cv2.CAP_PROP_FPS)

    cap.set(cv2.CAP_PROP_POS_FRAMES, round(fps * sec))

    ret, frame = cap.read()

    if ret:
        cv2.imwrite(result_path, frame)
        return frame


def main():
    check = Check()

    # jsonファイルを格納したディレクトリが存在するかチェック
    check.filepath_checker(JSON_DIR)

    # 'CUTした画像の格納場所を準備'
    check.directory_init(CUT_IMAGE)

    # jsonを解析し、画像とアノテーション座標から切り出しをしていく
    count = 0
    for jsonName in fnmatch.filter(os.listdir(JSON_DIR), '*.json'):

        # jsonを開く
        with open(JSON_DIR + jsonName) as f:
            result = json.load(f)

            # 画像のファイル名の取得
            imgName = result['asset']['name']
            print('jsonName = {}, imgName = {} '.format(jsonName, imgName))

            # img = cv2.imread(IMG_DIR + imgName)

            if  'timestamp' not in result['asset'] :
                print('<timestamp is NULL>  imgName = ', imgName)
                continue

            img = save_frame_sec(IMG_DIR + 'signal.mp4', result['asset']['timestamp'], 'temp/result_10sec.jpg')

            if img is None:
                print('cv.imread Error')
                exit()

            # アノテーションした数だけループ
            for region in result['regions']:

                height = int(region['boundingBox']['height'])
                width = int(region['boundingBox']['width'])
                left = int(region['boundingBox']['left'])
                top = int(region['boundingBox']['top'])

                cutImage = img[top: top + height, left: left + width]
                # アノテーション中に誤って1点クリックしてしまった情報は避ける
                if height == 0 or width == 0:
                    print('<height or width is 0>  imgName = ', imgName)
                    continue

                # 書き出す前にリサイズする場合はコメントアウトを外しましょう
                # cutImage = cv.resize(cutImage, (300,300))

                # 「cut_images/cat0000.jpg」と連番でファイルを書き出す
                filename =region['tags'][0]
                cv2.imwrite(CUT_IMAGE + filename + "{0:04d}".format(count + 1) + IMAGE_FORMAT, cutImage)
                print("{0:04d}".format(count + 1))
                count += 1


if __name__ == "__main__":
    main()

スクリプト「cutimg.py」を実行した結果を次に示します。

jsonName = 001475cdcbe31e6b07da7f7b59f53cdc-asset.json, imgName = signal.mp4#t=8.933333 
0001
0002
jsonName = 01c6357f4521d5ce878d8c95d02b3474-asset.json, imgName = signal.mp4#t=4.133333 
0003
0004
jsonName = 01e94c050e6819a2962903bbedd2d5e8-asset.json, imgName = signal.mp4#t=27.8 

・・・

0229
jsonName = fce141e62a7ccfbdcab982e3b24fe07a-asset.json, imgName = signal.mp4#t=32.866667 
0230
0231
jsonName = fd4633d1b9b6441bce9d327cf3f1a034-asset.json, imgName = signal.mp4#t=14.133333 
0232

Process finished with exit code 0

取得したアノテーション画像を次に示します。アノテーション作成ツール「VoTT」でアノテーション範囲を指定した画像が表示されます。