stMind

about Tech, Computer vision and Machine learning

pythonを使って簡単な画像分類を実現する

yhatでpythonを使って簡単な画像分類をするエントリがあったので紹介します。

画像分類のステップ

画像分類を実現するステップは以下の通りです。

  1. 学習画像を集める
  2. 画像を特徴量に変換する
  3. 識別器を学習, モデルを評価する

1. 学習画像を集める

分類器を学習するための画像を取得します。Bingの画像検索結果のHTMLを解析して、学習画像を保存します。上記のエントリでは、check(小切手)か運転免許証かを分類する例になってましたが、ここではarchitecture(建物)かfood(食べ物)を分類するようにしてみます。(SNS向けに撮った写真は建物か食べ物が多いので)

スクリプトは大体同じですが、queryをハードコードせずに引数として与えるようにしました。

#-*- encoding: utf-8 -*-

"""
Usage:
    query_bing_images.py <query>
    query_bing_images.py -h | --help
    query_bing_images.py --version

Options:
    -h --help  show this screen
    --version  show version
"""

from bs4 import BeautifulSoup
import requests
import re
import urllib2
import os
from docopt import docopt


def get_soup(url):
    return BeautifulSoup(requests.get(url).text)


def main():
    options = docopt(__doc__, version='1.0')
    query = options['<query>']
    image_type = query

    url = "http://www.bing.com/images/search?q=" + query + \
        "&gft=+filterui:color2-bw+filterui:imagesize-large&FORM=R5IR3"

    soup = get_soup(url)
    images = [a['src2'] for a in soup.find_all("img", {"src2": re.compile("mm.bing.net")})]

    for img in images:
        raw_img = urllib2.urlopen(img).read()
        cntr = len([i for i in os.listdir("images") if image_type in i]) + 1
        f = open("images/" + image_type + "_" + str(cntr) + '.jpg', 'wb')
        f.write(raw_img)
        f.close()

if __name__ == '__main__':
    main()

集めた画像はこんな感じです。

f:id:satojkovic:20140115001637j:plain

f:id:satojkovic:20140115001648j:plain

2. 画像を特徴量に変換する

ここでは、シンプルな方法で画素のRGB値をそのまま使っています。つまり、300 x 150pxの画像であれば、45,000個の[r,g,b]のベクトルになります。実際には、各画像を300 x 167にリサイズした後、img_to_matrixとflatten_imageでベクトルの形に変換しています。 処理イメージの画像を引用します。

ただし、45,000 x 3次元のベクトルは過学習や学習時間増の可能性があるため、Randomized PCAにより次元削減を行います。学習と識別は5次元の特徴ベクトルで行いますが、プロットして確認しやすいように2次元に圧縮してみます。

f:id:satojkovic:20140115010403p:plain

うまく分類できそうな感じです。

3. 識別器の学習とモデルの評価

1で集めた画像を学習用の画像とテスト用の画像に分け、学習画像だけを使ってSVM識別器を学習します。最後に、学習した識別器でテスト画像を分類して見ると、

Predicted   0   1
Actual           
0          12   0
1           0  14

ということで、誤識別はありませんでした。(0がfood, 1がarchitecture)

別のテスト画像でも、

Predicted  0  1
Actual         
0          8  0
1          0  6

となって、建物画像と食べ物画像の分類をすることが出来ました。

学習と識別を行うスクリプトはこのような内容です。(若干の修正あり)

#-*- encoding: utf-8 -*-

from PIL import Image
import numpy as np
import os
import pandas as pd
import pylab as pl
from sklearn.decomposition import RandomizedPCA
from sklearn.externals import joblib
from sklearn.svm import LinearSVC


STANDARD_SIZE = (300, 167)


def img_to_matrix(filename, verbose=False):
    img = Image.open(filename)
    if verbose:
        print 'changing size from %s to %s' % (str(img.size), str(STANDARD_SIZE))
    img = img.resize(STANDARD_SIZE)
    imgArray = np.asarray(img)
    return imgArray  # imgArray.shape = (167 x 300 x 3)


def flatten_image(img):
    s = img.shape[0] * img.shape[1] * img.shape[2]
    img_wide = img.reshape(1, s)
    return img_wide[0]


def main():
    img_dir = 'images/'
    images = [img_dir + f for f in os.listdir(img_dir)]
    labels = ['architecture' if 'architecture' in f.split('/')[-1] else 'food' for f in images]

    data = []
    for image in images:
        img = img_to_matrix(image)
        img = flatten_image(img)
        data.append(img)

    data = np.array(data)

    is_train = np.random.uniform(0, 1, len(data)) <= 0.7
    y = np.where(np.array(labels) == 'architecture', 1, 0)

    train_x, train_y = data[is_train], y[is_train]

    # plot in 2 dimensions
    pca = RandomizedPCA(n_components=2)
    X = pca.fit_transform(data)
    df = pd.DataFrame({"x": X[:, 0], "y": X[:, 1],
                       "label": np.where(y == 1, 'architecture', 'food')})
    colors = ['red', 'yellow']
    for label, color in zip(df['label'].unique(), colors):
        mask = df['label'] == label
        pl.scatter(df[mask]['x'], df[mask]['y'], c=color, label=label)

    pl.legend()
    pl.savefig('pca_feature.png')

    # training a classifier
    pca = RandomizedPCA(n_components=5)
    train_x = pca.fit_transform(train_x)

    svm = LinearSVC(C=1.0)
    svm.fit(train_x, train_y)
    joblib.dump(svm, 'model.pkl')

    # evaluating the model
    test_x, test_y = data[is_train == False], y[is_train == False]
    test_x = pca.transform(test_x)
    print pd.crosstab(test_y, svm.predict(test_x),
                      rownames=['Actual'], colnames=['Predicted'])

if __name__ == '__main__':
    main()

まとめ

pythonを使って、とても少ないステップで簡単な画像分類を実現することが出来ました。

今回は、Bing画像検索で保存した画像を学習とテスト用に分けてモデルを評価しましたが、 Instagramなどに投稿した画像を分類してみるのも面白いなと思います。 それから、今回は2クラスの分類だったけれど、多クラス分類へ拡張してみるというのもいいかなと思います。