概要
Open Image Dataset v4から600クラスのデータを落としてきて中身を確認したり軽く使ってみたり、Pascal VOCの形式に変換したりする。
v5が出たこの時期にいまさらな記事。
ダウンロード
下記からダウンロードする
https://storage.googleapis.com/openimages/web/download.html
Imagesは容量が大きいので後回しにして、それ以外のBoxes, Image Labels, Image IDs, MetadataなどをWebからダウンロードする。
一番容量が大きいImagesについてはWebダウンロードでは何度かエラーで止まりresumeも失敗する事象に悩まされたので、Download from CVDFの方に切り替える。
ダウンロード方法は下記に乗っている。
https://github.com/cvdfoundation/open-images-dataset#download-images-with-bounding-boxes-annotations
Google Cloud SDKがいるのでそれをまずインストールする。
https://cloud.google.com/sdk/install
自分のアカウントで認証する。
$ gcloud init
同じアカウントでCVDFのサイトでも認証しておく。
http://www.cvdfoundation.org/datasets/open-images-dataset/signup.html
コマンドからダウンロード実行。
$ mkdir test $ gsutil -m rsync -r gs://open-images-dataset/test test
上記で画像データ(125,436枚、36GB)を落とせる。
画像の確認
$ ls test | head -5 000026e7ee790996.jpg 000062a39995e348.jpg 0000c64e1253d68f.jpg 000132c20b84269b.jpg 0002ab0af02e4a77.jpg
上記のような16進数の名前の画像がたくさん入っている。
ファイル数の確認
$ ls test | wc -l 125436
これらの画像がどういった分類をされているのか。まずは下記ファイルに分類クラスの名前が載っている。
class-descriptions-boxable.csv
全601クラス。左がID、右がラベル名。動物とか車とか食べ物とかいろいろ載ってる。今回は600クラス版を落としたけどその下にはさらにデカイ19,995クラス版もいる。
犬の画像だけ集めてみる
一般家庭の環境ではこれらすべてを使って機械学習に使うのは厳しいので、一旦犬画像だけ取り出して犬判定モデルでも作ってみる。
Dogでclass-descriptions-boxable.csvを検索すると下記の行がある。
$ grep Dog class-descriptions-boxable.csv
/m/0bt9lr,Dog
/m/0h8n6f9,Dog bed
Dog bedなんてあるんだ、というのは置いておいてDogのIDは /m/0bt9lr になっていることがわかった。
続いて test-annotations-bbox.csv の中を確認。ImageIDに対するアノテーションの情報が入っている。
$ head -5 test-annotations-bbox.csv ImageID,Source,LabelName,Confidence,XMin,XMax,YMin,YMax,IsOccluded,IsTruncated,IsGroupOf,IsDepiction,IsInside 000026e7ee790996,freeform,/m/07j7r,1,0.071905,0.145346,0.206591,0.391306,0,1,1,0,0 000026e7ee790996,freeform,/m/07j7r,1,0.439756,0.572466,0.264153,0.435122,0,1,1,0,0 000026e7ee790996,freeform,/m/07j7r,1,0.668455,1.000000,0.000000,0.552825,0,1,1,0,0 000062a39995e348,freeform,/m/015p6,1,0.205719,0.849912,0.154144,1.000000,0,0,0,0,0
これをDogのID、 /m/0bt9lr でgrepすれば犬画像が取れる。
# ヘッダだけ事前に出力 $ head -1 test-annotations-bbox.csv > dog-test-annotations-bbox.csv # Dogカテゴリのboxを出力 $ grep "/m/0bt9lr" test-annotations-bbox.csv >> dog-test-annotations-bbox.csv
これで犬のAnnotationファイルができた。
$ head -5 dog-test-annotations-bbox.csv ImageID,Source,LabelName,Confidence,XMin,XMax,YMin,YMax,IsOccluded,IsTruncated,IsGroupOf,IsDepiction,IsInside 000ccf1d00f7f1cd,freeform,/m/0bt9lr,1,0.000000,0.823583,0.134748,0.999894,0,1,0,0,0 004130acea29204f,freeform,/m/0bt9lr,1,0.221060,0.937226,0.000726,0.926652,0,1,0,0,0 004421e64e1be85b,freeform,/m/0bt9lr,1,0.282111,0.783627,0.123731,0.999788,0,1,0,0,0 0050fb46c84869b1,freeform,/m/0bt9lr,1,0.082796,0.799341,0.174982,0.865755,0,0,0,0,0
件数は4810枚で5818頭。かなりの数だけど犬は個体差が大きいし種類も豊富だから数はたくさん欲しい。
続いてこの4811画像だけをコピーして別のフォルダに入れる。画像の名称は画像IDに.jpgを付けるだけ。
import os. shutil os.mkdir('dog') dog_ids = set() with open('dog-test-annotations-bbox.csv', 'rt') as f: next(f) # 1行目のheaderはスキップ for line in f: dog_ids.add(line.split(',')[0]) for dog_id in dog_ids: src = 'test/{}.jpg'.format(dog_id) dest = 'dog/{}.jpg'.format(dog_id) shutil.copyfile(src, dest)
これで犬ファイルとそれに対するAnnotationのデータが揃った。
このデータでSSDのモデルを作ってみる。ライブラリとして下記のTensorflowを使ったものを利用する。
https://github.com/ljanyst/ssd-tensorflow
このライブラリはPascal-VOCのデータを読む用になっているので(annotationはXML形式)形式を変換しないといけない。変換用のコードはそのへんに落ちてそうだったけど検証するより書いた方が早そうなので車輪の再発明をする。
最低限、これだけあれば処理は動く。
<annotation> <filename>xxxxx.jpg</filename> <object> <name>dog</name> <bndbox> <xmin>167</xmin> <ymin>1</ymin> <xmax>375</xmax> <ymax>128</ymax> </bndbox> </object> </annotation>
見ての通り、objectのxmin, ymin, xmax, ymax等がピクセルサイズになっている。
これに対してOpen Image Dataset v4では割合で記録している。
000026e7ee790996,freeform,/m/07j7r,1,0.071905,0.145346,0.206591,0.391306,0,1,1,0,0
まずは画像のサイズを見てここを変換してあげないといけない。OpenCVで画像のサイズを取ってピクセルに変換してみる。ついでに可視化もしてちゃんと変換できてるか確認する。
まずはpandasでannotationファイルを開く。
import cv2, pandas as pd df = pd.read_csv('dog-test-annotations-bbox.csv') df.head(3)
index | ImageID | Source | LabelName | Confidence | XMin | XMax | YMin | YMax |
---|---|---|---|---|---|---|---|---|
0 | 000ccf1d00f7f1cd | freeform | /m/0bt9lr | 1 | 0 | 0.823583 | 0.134748 | 0.999894 |
1 | 004130acea29204f | freeform | /m/0bt9lr | 1 | 0.22106 | 0.937226 | 0.000726 | 0.926652 |
2 | 004421e64e1be85b | freeform | /m/0bt9lr | 1 | 0.282111 | 0.783627 | 0.123731 | 0.999788 |
(スペースの都合で一部カラムを省略)
動作確認用の画像として複数ワンコが写っているファイルが良いので、ID 00924896424a6180を選ぶ。
annotations = df[df.ImageID == '00924896424a6180']
annotations
ImageID | Source | LabelName | Confidence | XMin | XMax | YMin | YMax | |
---|---|---|---|---|---|---|---|---|
9 | 00924896424a6180 | freeform | /m/0bt9lr | 1 | 0 | 0.733391 | 0.183994 | 0.866967 |
10 | 00924896424a6180 | freeform | /m/0bt9lr | 1 | 0.253301 | 0.854086 | 0 | 0.263123 |
指定画像を開く。
img = cv2.imread('data/dog/dog/00924896424a6180.jpg') y, x = img.shape[0:2] x, y
shapeによると画像サイズは1024×683らしい。
枠の幅を計算してrectangle書いてみる。
ri = lambda d: int(round(d)) for idx, annot in annotations.iterrows(): pt1 = (ri(x * annot.XMin), ri(y * annot.YMin)) pt2 = (ri(x * annot.XMax), ri(y * annot.YMax)) print(pt1, pt2) cv2.rectangle(img, pt1, pt2, color=(255, 128, 0), thickness=7) %matplotlib inline from matplotlib import pylab as plt plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
大丈夫そう。この処理でXMLを生成する。
from lxml import etree multi_ids = df.groupby('ImageID').count() multi_ids = multi_ids[multi_ids.Source > 1] df2 = df[df.ImageID.isin(multi_ids.index)].head(8) ris = lambda d: str(int(round(d))) for image_id, rows in df2.groupby('ImageID'): img = cv2.imread('data/dog/dog/{}.jpg'.format(image_id)) y, x = img.shape[0:2] doc = etree.Element('annotation') fname_elem = etree.SubElement(doc, 'filename') fname_elem.text = '{}.jpg'.format(image_id) for idx, row in rows.iterrows(): box = {'xmin': ris(x * row.XMin), 'xmax': ris(x * row.XMax), 'ymin': ris(y * row.YMin), 'ymax': ris(y * row.YMax)} obj_elem = etree.SubElement(doc, 'object') obj_name = etree.SubElement(obj_elem, 'name') obj_name.text = 'dog' bndbox_elem = etree.SubElement(obj_elem, 'bndbox') for p in ['xmin', 'xmax', 'ymin', 'ymax']: e = etree.SubElement(bndbox_elem, p) e.text = box[p] with open('doc.xml', 'wb') as w: w.write(etree.tostring(doc, pretty_print=True))
出力されたXML。
<annotation> <filename>017c5a5507ec70b8.jpg</filename> <object> <name>dog</name> <bndbox> <xmin>190</xmin> <xmax>551</xmax> <ymin>214</ymin> <ymax>521</ymax> </bndbox> </object> <object> <name>dog</name> <bndbox> <xmin>537</xmin> <xmax>772</xmax> <ymin>374</ymin> <ymax>627</ymax> </bndbox> </object> </annotation>
合ってそう。
間違いがあるといけないのでXMLから枠を出力するコードを書いて、一応確認する。
from lxml import etree xml_file = 'doc.xml' with open(xml_file) as f: tree = etree.parse(f) img_name = tree.xpath('/annotation/filename')[0].text objects = tree.xpath('/annotation/object') img = cv2.imread('data/dog/dog/{}'.format(img_name)) print( 'data/dog/dog/{}'.format(img_name) ) for obj in objects: bndbox = obj.find('bndbox') xmin = int(bndbox.find('xmin').text) ymin = int(bndbox.find('ymin').text) xmax = int(bndbox.find('xmax').text) ymax = int(bndbox.find('ymax').text) print(xmin, ymin, xmax, ymax) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color=(255, 128, 0), thickness=7) from matplotlib import pylab as plt plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
問題なし。
最後にここで書いたコードをまとめて、下記のような動作をするスクリプトを書く。
指定したラベル(複数指定可)の画像を、指定フォルダ配下にコピーする コピーした画像に対してPascal-VOCと同じ形式のXMLアノテーションファイルを生成する その他、条件
引数にはラベル名、データセットがいるフォルダ、出力先フォルダを取る データセットの直下に class-descriptions-boxable.csvがいて、[train|test|validation]-annotations-bbox.csv のいずれかと、それに対応する[train|test|validation]フォルダがいるものとする train, test, validationの3種類のデータを読み込み(なければスキップ)、出力先にもtrain, test, validationそれぞれのフォルダを作ってその配下に出力する ということで書いたコード。
import os, shutil, argparse, cv2, pandas as pd from lxml import etree def main(labels, input_dir, output_dir): if not os.path.exists(output_dir): os.mkdir(output_dir) # ラベルのIDを取得 label_info = pd.read_csv(os.path.join(input_dir, 'class-descriptions-boxable.csv'), names=['label_id', 'label_name']) label_info = label_info[label_info.label_name.isin(labels)] label_ids = dict([(l.label_id, l.label_name.lower()) for idx, l in label_info.iterrows()]) # train, test, validationに対してそれぞれ処理を実行 for target in ['train', 'test', 'validation']: execute(label_ids, input_dir, output_dir, target) def execute(label_ids, input_dir, output_dir, target): # ファイルチェック annot_path = os.path.join(input_dir, '%s-annotations-bbox.csv' % target) if not os.path.exists(annot_path): print('ファイルがおらんね : ' + annot_path) return # 出力ディレクトリ生成 target_output_dir = os.path.join(output_dir, target) if not os.path.exists(target_output_dir): os.mkdir(target_output_dir) # 対象ラベルのannotationを抽出 annot = pd.read_csv(annot_path) annot = annot[annot.LabelName.isin(label_ids.keys())] ris = lambda d: str(int(round(d))) for image_id, rows in annot.groupby('ImageID'): # 画像ファイルの読み込み src = os.path.join(input_dir, target, '%s.jpg' % image_id) img = cv2.imread(src) y, x = img.shape[0:2] # XMLの生成 doc = etree.Element('annotation') fname_elem = etree.SubElement(doc, 'filename') fname_elem.text = '{}.jpg'.format(image_id) for idx, row in rows.iterrows(): box = {'xmin': ris(x * row.XMin), 'xmax': ris(x * row.XMax), 'ymin': ris(y * row.YMin), 'ymax': ris(y * row.YMax)} obj_elem = etree.SubElement(doc, 'object') obj_name = etree.SubElement(obj_elem, 'name') obj_name.text = label_ids[row.LabelName] bndbox_elem = etree.SubElement(obj_elem, 'bndbox') for p in ['xmin', 'xmax', 'ymin', 'ymax']: e = etree.SubElement(bndbox_elem, p) e.text = box[p] with open(os.path.join(output_dir, target, '%s.xml' % image_id), 'wb') as w: w.write(etree.tostring(doc, pretty_print=True)) # 画像のコピー dest = os.path.join(output_dir, target, '%s.jpg' % image_id) shutil.copy(src, dest) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--labels', required=True, help='extract labels') parser.add_argument('--input-dir', required=True, help='input folder') parser.add_argument('--output-dir', required=True, help='output folder') args = parser.parse_args() labels = args.labels.split(',') main(labels, args.input_dir, args.output_dir)
改定履歴
Author: Masato Watanabe, Date: 2019-05-10, 記事投稿