Chainer で Deep Learning: model zoo で R-CNN やりたい
ニューラルネットワークを使ったオブジェクト検出の手法に R-CNN (Regions with CNN) というものがある。簡単にいうと、R-CNN は以下のような処理を行う。
- 入力画像中からオブジェクトらしい領域を検出し切り出す。
- 各領域を CNN (畳み込みニューラルネットワーク) にかける。
- 2での特徴量を用いて オブジェクトかどうかをSVMで判別する。
R-CNN については 論文著者の方が Caffe (Matlab) での実装 (やその改良版) を公開している。
が、自分は Matlab のライセンスを持っていないので Python でやりたい。Python でやるなら 今 流行りの Chainer
を使ってみたい。その試行の記録。
準備
とりあえず論文の再現は目指さず、R-CNN っぽい処理 (オブジェクト検出 -> CNN) を Python から回せるようにしたい。
まずは オブジェクト検出について調べてみると、上述の論文 & 実装で利用されている Selective Search という手法は ( Python の wrapper はあるが) Matlab なしでは利用できないようだ。Python から使える代替手法として GOP (Geodesic Object Proposals) という方法を見つけた。著者がパッケージも作っているのだが、どうもオブジェクト検出の方法がよくわからない、、、が矩形を切り取ることはできそうだ。
ということで今回は以下の処理が流せるようにしたい。モデルの修正やファインチューニングはせず、 caffenet をそのまま使う(済まぬ、、)。環境は EC2 の GPU インスタンス上に作成する。
- オブジェクトっぽい矩形をクロップ (Geodesic K-means)
- クロップした領域を CNN で判別 (
Chainer
model zoo で caffenet を利用) - 判別した結果を描画
都合上、 2 -> 1 -> 3 の順で記載する。
Chainer model zoo の利用
本日時点で PyPI にリリースされている v1.0.1 標準にはない機能のため、GitHub からダウンロードする。自分が利用したのは 本日時点 5222fe572b のリビジョン。
model zoo のダウンロード
chainer/examples/modelzoo
から以下を実行してモデル と mean ファイルをダウンロードする。
$ python download_model.py caffenet $ python download_mean_file.py
また、データセットの ID / ラベルを含む synset_words.txt
を別途ダウンロードしておく。これは以下のファイルに含まれている。
$ wget http://dl.caffe.berkeleyvision.org/caffe_ilsvrc12.tar.gz $ mkdir ilsvrc $ tar zxvf caffe_ilsvrc12.tar.gz -C ilsvrc
model zoo の読み込み
以降は IPython Notebook で。最初に必要なパッケージをロードする。
%matplotlib inline import os import sys import numpy as np import chainer from chainer import cuda import chainer.functions as F from chainer.functions import caffe import matplotlib.pyplot as plt
次に、先ほどダウンロードした caffenet のパスを指定し、Chainer
のモデルとして読み込む。
また、synset_words.txt
を np.array
として読み込む。
base = os.path.join('chainer', 'examples', 'modelzoo') model = os.path.join(base, 'bvlc_reference_caffenet.caffemodel') func = caffe.CaffeFunction(model) cuda.init(0) func.to_gpu() synset_words = np.array([line[:-1] for line in open(os.path.join(base, 'ilsvrc', 'synset_words.txt'), 'r')]) synset_words[:10] # array(['n01440764 tench, Tinca tinca', # 'n01443537 goldfish, Carassius auratus', # 'n01484850 great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias', # 'n01491361 tiger shark, Galeocerdo cuvieri', # 'n01494475 hammerhead, hammerhead shark', # 'n01496331 electric ray, crampfish, numbfish, torpedo', # 'n01498041 stingray', 'n01514668 cock', 'n01514859 hen', # 'n01518878 ostrich, Struthio camelus'], # dtype='|S131')
続けて、画像に対する caffenet の判別結果を返す関数を準備する。画像のリストを入力とし、一度に入力された画像群に対してはバッチで処理するようにした。
in_size = 224 cropwidth = 256 - in_size start = cropwidth // 2 stop = start + in_size # caffenet meannpy = os.path.join(base, 'ilsvrc_2012_mean.npy') mean_image = np.load(meannpy) mean_image = mean_image[:, start:stop, start:stop].copy() def predict(images): """画像のリストに対する判別を行う""" global mean_image, in_size, cropwidth, start, stop def swap(x): x = np.array(x)[:, :, ::-1] x = np.swapaxes(x, 0, 2) x = np.swapaxes(x, 1, 2) return x if not isinstance(images, list): images = [images] batch_size = len(images) x_data = np.ndarray((batch_size, 3, in_size, in_size), dtype=np.float32) for i, image in enumerate(images): image = swap(image) image = image[:, start:stop, start:stop].copy().astype(np.float32) image -= mean_image x_data[i] = image x_data = cuda.to_gpu(x_data) x = chainer.Variable(x_data, volatile=True) y, = func(inputs={'data': x}, outputs=['fc8'], train=False) y.data = cuda.to_cpu(y.data) indexer = y.data.argmax(axis=1) return synset_words[indexer], y.data.max(axis=1)
動作確認
自分は ImageNet へのアクセス権を持っていないため、wikimedia で適当な画像をみつくろって試す。そのため、URL を指定して画像をダウンロードする関数、画像を caffenet へ入力するためにリサイズする関数を作成する。
def get_image(url): """URLから画像をダウンロード""" import urllib import StringIO import PIL.Image as Image return Image.open(StringIO.StringIO(urllib.urlopen(url).read())).convert("RGB") def resize(image): """画像をリサイズ""" import PIL.Image as Image return image.resize((256, 256), Image.ANTIALIAS)
いくつか結果を添付する。
url1 = "https://upload.wikimedia.org/wikipedia/commons/0/0e/BIRD_PARK_8_0189.jpg"
img = get_image(url1)
plt.imshow(img)
img = resize(img) plt.imshow(img)
predict(img) # (array(['n01806143 peacock'], # dtype='|S131'), array([ 26.1235218], dtype=float32))
別の画像。
url2 = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/Ostrich_Ngorongoro_05.jpg/640px-Ostrich_Ngorongoro_05.jpg"
img = get_image(url2)
plt.imshow(img)
img = resize(img) plt.imshow(img)
predict(img) # (array(['n01518878 ostrich, Struthio camelus'], # dtype='|S131'), array([ 22.44941521], dtype=float32))
定量的に測った訳ではないが、魚類は正解しにくい気がする。
url3 = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Peixe010eue.jpg/640px-Peixe010eue.jpg"
img = get_image(url3)
plt.imshow(img)
img = resize(img) plt.imshow(img)
predict(img) # (array(['n01443537 goldfish, Carassius auratus'], # dtype='|S131'), array([ 24.29219818], dtype=float32))
複数画像を処理する場合は、画像のリストを渡す。
images = [resize(get_image(url1)), resize(get_image(url2)), resize(get_image(url3))] labels, scores = predict(images) labels # array(['n01806143 peacock', 'n01518878 ostrich, Struthio camelus', # 'n01443537 goldfish, Carassius auratus'], # dtype='|S131') scores # array([ 26.1235218 , 22.44941521, 24.29219818], dtype=float32)
矩形のクロップ (Geodesic K-means)
Geodesic Object Proposals 著者のサイトから Code, Data をダウンロードする。README が少しわかりにくいが、以下の方法でインストールできた。
# eigen のダウンロード $ wget http://bitbucket.org/eigen/eigen/get/3.2.5.zip $ unzip 3.2.5.zip # GOP のダウンロード $ wget http://googledrive.com/host/0B6qziMs8hVGieFg0UzE0WmZaOW8/code/gop_1.3.zip $ unzip gop_1.3.zip # GOP の解凍ディレクトリ/external/eigen に Eigen ディレクトリをコピー $ mkdir gop_1.3/external/eigen $ cp -r eigen-eigen-bdd17ee3b1b3/Eigen/ gop_1.3/external/eigen/ $ ls gop_1.3/external/eigen # Eigen $ cd gop_1.3/build # -DUSE_PYTHON で Python 2.x とのリンクを指定 $ cmake .. -DCMAKE_BUILD_TYPE=Release -DUSE_PYTHON=2 $ sudo make install
また、ダウンロードした Data は gop_1.3/data
ディレクトリの中に入れておく。
$ unzip gop_data.zip
GOP を IPython Notebook から使ってみる。GOP は site-packages にインストールされないため、ロードするには sys に対して path を追加してやる必要がある。矩形を選択する方法のうち、今のところ動かせたのは Geodesic K-means のみなのでそれを使う。GOP では Geodesic K-means の後にオブジェクト検出を行うようなのだが、その結果を利用して矩形選択する方法はまだわかってない (そのため、オブジェクト検出はまだできていないという理解だが Proposal
の処理を追っていないので自信はない)。
sys.path.append('./gop_1.3/src/') from gop import * from util import * prop = proposals.Proposal(setupLearned(200, 4, 0.8)) detector = contour.MultiScaleStructuredForest() detector.load("./gop_1.3/data/sf.dat") def get_boundaries(image): # 画像データを直接入力とする方法が不明のため、一時ファイルに保存 img.save('tmp.png') s = segmentation.geodesicKMeans(imgproc.imread('tmp.png'), detector, 100) b = prop.propose(s) boxes = s.maskToBox(b) return boxes
以下の画像を使って確認してみる。
url = "https://upload.wikimedia.org/wikipedia/commons/8/8f/Dogs_playing_on_the_beach_in_the_sand.jpg"
img = get_image(url)
plt.imshow(img)
fig, ax = plt.subplots(1, 1) ax.imshow(img) boxes = get_boundaries(img) def plot_boxes(ax, boxes, labels=None): """ax への矩形とラベルの追加""" if labels is None: labels = [None] * len(boxes) history = [] from matplotlib.patches import FancyBboxPatch for box, label in zip(boxes, labels): coords = (box[0], box[1]) b = FancyBboxPatch(coords, box[2]-box[0], box[3]-box[1], boxstyle="square,pad=0.", ec="b", fc="none", lw=0.5) mindist = 100000 if len(history) > 0: mindist = min([sum((box - h) ** 2) for h in history]) # ほぼ重なる矩形は描画しない if mindist > 30000: if label is not None: ax.text(coords[0], coords[1], label, color='b') ax.add_patch(b) history.append(box) plot_boxes(ax, boxes)
クロップされた画像はこんな感じ。
cropped = img.crop(boxes[13])
plt.imshow(cropped)
リサイズして caffenet にかける。犬という意味では近いが、品種は当たってない。
predict(resize(cropped)) # (array(['n02105412 kelpie'], # dtype='|S131'), array([ 10.38668251], dtype=float32))
各領域への CNN の適用
Geodesic K-means で抽出された各領域をクロップした画像のリストを作り、caffenet に渡す。
images = [resize(img.crop(box)) for box in boxes] labels = [] scores = [] nunit = len(images) / 5 unit = len(images) / nunit for i in range(nunit+1): l, s = predict(images[i*unit:min(i*unit+unit, len(images))]) labels.extend(l.tolist()) scores.extend(s.tolist()) import pandas as pd df = pd.DataFrame({'Labels': labels, 'Scores': scores}) df = df.sort('Scores', ascending=False) df.head()
判別結果のうちスコアがよい部分をラベル付きで描画する。やはりうまくオブジェクトが切り出せていない感じがする。
fig, ax = plt.subplots(1, 1) ax.imshow(img) heads = df.head(n=50) labels = [l.split(' ', 1)[1] for l in heads['Labels'].tolist()] plot_boxes(ax, boxes[heads.index], labels=labels)
まとめ
今回、Python から以下の処理をまわすことはできた。
- オブジェクトっぽい矩形をクロップ
- クロップした領域を CNN で判別
- 判別した結果を描画
が、判別結果自体は微妙な感じなので、以下2つについてはもう少し調べたい。
同日追記
パラメータ調整したら矩形抽出は多少ましになったような、、(画像末端まで選択されてしまうことが減っている)。元論文では候補を 2000 個抽出しているため、同程度以上にしておけばよさそう。
- 作者: 岡谷貴之
- 出版社/メーカー: 講談社
- 発売日: 2015/04/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る