takuroooのブログ

勉強したこととか

Pythonのurllibを使ってImageNetから画像をダウンロードする

*この記事は以前Qiitaで書いたものです。

qiita.com

概要

PythonでImageNetから画像をダウンロードする方法を解説する記事。

ImageNetの画像をダウンロードする方法は2つある。 一つはImageNet経由で一括ダウンロードする方法と、もう一つはImageNetが管理している画像元のURL一覧取得して、そのURLを使って自分でダウンロードする方法である。

前者は、非営利目的の研究/教育目的のみ利用可能。 なのでそれ以外の人は後者の方法をとらなければならない。 今回は後者の方法をPythonを使ってダウンロードする。

ダウンロードに必要な知識と実際のコードをそれぞれ解説していく。

ImageNetとは

f:id:takuroooooo:20201030083153p:plain

  • 研究目的で作成された画像のデータベース
  • 機械学習の学習データによく使用されている。
  • 画像の種類はWordNetで管理 WordNet(Wikipedia)
  • ImageNetが画像の著作権を保持しているわけではない。ImageNetは画像元のURLと画像のサムネイルを提供しているだけ。
  • 画像数:14,197,122
  • バウンディングボックス:1,034,908

WordnetIDとSynsetとは

ImageNetはWordNetという辞書で画像が管理されている。 具体的にはWordNetIDとSynsetという組み合わせで画像の種別を表現している。

例えば、WordNetID 「n02113335」 は、 Synset 「Poodle, poodle dog」 を表現している。

例から分かるように、Synsetは人間が分かる単語で表現され、WordNetIDはその単語と紐付いている番号('n' + 8桁の番号)である。

ImageNetのサイトで画像を探すときはSynsetで検索できる。またSynsetは親(上位概念)と子(下位概念)で構成されている。(「Poodle, poodle dog」の下位概念には「Toy poodle」がいる。)

f:id:takuroooooo:20201030083214p:plain

左のツリーがSynsetの階層構造。 WordNetIDは、右上の黄色いマークを押すと取得できる。

人間に分かりやすいSynsetだけ分かればいいのでは?と思うが、プログラムで画像をダウンロードする際にはWordNetIDでダウンロードしたい種別を選択する必要がある。

ダウンロード方法

プログラムでダウンロードするためにはImageNetが提供しているAPIを使う。APIとはあるURLのことで、このURLにWordNetIDをくっつけてアクセスすると、そのWordNetIDに関する情報が取得できる。 APIにはいろいろな種類がある。

ImageNet API

No. PageName URL
1 Synset検索画面のページ http://www.image-net.org/synset?wnid=***
2 指定したwnidの下位概念のwnid表示 http://www.image-net.org/api/text/wordnet.structure.hyponym?wnid=***
3 指定したwnidの下位概念のwnid表示(最下層まで探索) http://www.image-net.org/api/text/wordnet.structure.hyponym?wnid=***&full=1
4 指定したwnidに対応するSynset表示 http://www.image-net.org/api/text/wordnet.synset.getwords?wnid=***
5 指定したwnidに対応するファイル名と画像のURL表示 http://www.image-net.org/api/text/imagenet.synset.geturls.getmapping?wnid=***
6 指定したwnidに対応する画像のURL表示 http://www.image-net.org/api/text/imagenet.synset.geturls?wnid=***
  • 米印にはWordNetIDが入ります。
  • wnid = WordNetIDです。

順番に見ていくと、 1は「WordnetIDとSynsetとは」で見た検索画面にジャンプするURL。 2と3は、指定したWordNetIDの下位概念を表示する。2は直下のものしか表示しないが、3は最下層まで表示する。


f:id:takuroooooo:20201030083319p:plain

4は、指定したWordNetIDに対応するSynsetを表示するURL。


f:id:takuroooooo:20201030083352p:plain


5,6は、指定したWordNetIDに属する画像のURL一覧が表示される。5はファイル名も合わせて表示される。


f:id:takuroooooo:20201030083419p:plain


n20113335_17679というのがファイル名になっている。 バウンディングボックスなどはファイル名と紐付いているので、5番のAPIでアクセスするのがいいと思う。

ということで画像ダウンロードに使うのは5番です。 方法としては、 1.Pythonurllibを使って5番のページに情報取得の要求を出す。 2.ファイル名とURLの一覧が手に入る。 3.取得したURLに対してさらに情報取得の要求を出す。 4.URL先の画像データが手に入る。 5.画像データをファイルに書く。 以下3-5を繰り返す。

コード

以下サンプルコード

1.ファイル名と画像元URLの取得

from urllib import request
IMG_LIST_URL="http://www.image-net.org/api/text/imagenet.synset.geturls.getmapping?wnid={}"

url = IMG_LIST_URL.format("n02113335")
with request.urlopen(url) as response:
    html = response.read() 

URLをurlopen()で開いて、read()するだけでページの情報が取得できます。この時点でhtmlには、バイナリ型で以下のような文字列が入っている。

n02113335_17679 http://farm1.static.flickr.com/194/467227983_ce131cca2a.jpg
n02113335_4957 http://static.flickr.com/164/388222083_d98ab2ec7e.jpg
n02113335_4907 http://www.dkimages.com/discover/previews/919/65004609.JPG
n02113335_4943 http://farm1.static.flickr.com/82/225053708_e1b941261a.jpg
n02113335_4942 http://farm1.static.flickr.com/17/19821754_6cb866105a.jpg
n02113335_4935 http://farm3.static.flickr.com/2397/2132261952_28dd898274.jpg
・
・
・

文字列として扱いたいのでdecodeする。(バイナリ型->文字列型)

data = html.decode()

あとはこれを1列目と2列目に分割すればファイル名と画像元のURLが取得できる。 例えば以下のようにする。

data = data.split()
# data = [fname_0, url_0, fname_1, url_1, .....]
fnames = data[::2]
urls = data[1::2]

2.画像の取得

先ほど取得した1枚目のURLを使って、1と同様にアクセスする。

url="http://farm1.static.flickr.com/194/467227983_ce131cca2a.jpg"
with request.urlopen(url) as response:
    img = response.read() 

このときimgにバイナリ形式の画像データが入っている。 画像なので、以下のようにそのままバイナリ形式でファイルに書けばダウンロード完了。

with open('n02113335_17679.jpg', 'wb') as f:
    f.write(img)

ImageNet_Downloader

f:id:takuroooooo:20201030083556p:plain

github.com

ここまで紹介したImageNetAPIのラッパークラスを書いてみた。中身の処理は先ほど説明したことがメインなので割愛。以下、クラスの簡単な使い方。

import downloader
import os
root_dir = os.getcwd()
wnid = "n02113335"
api = downloader.ImageNet(root_dir)
api.download(wnid, verbose=True)

downloadでwnidを設定すると、そのwnidの画像をダウンロードし始める。以下のフォルダを作成して順次ダウンロードしていく。n02113335.txtは、ファイル名と画像のURLが書いてあるテキストファイル。

root/  
 ├ img/  
 │ └ n02113335/
 │       └ n02113335_xxx.jpg
 │       └ n02113335_xxx.jpg
 │       └ ....
 ├ list/  
 │ └ n02113335.txt

example.pyを使うと簡単にダウンロードできる。

python example.py <WordnetID> -v

下位層も含めてダウンロードしたい場合は-rをつける。

python example.py <WordnetID> -v -r

ダウンロードする枚数を指定した場合は、-limit <num>を指定する。

python example.py <WordnetID> -v -r -limit 100

まとめ

Pythonのurllibを使うと簡単にネット上の画像をダウンロードできる。

参考