stMind

about Tech, Computer vision and Machine learning

OneVsRestClassifierで多クラスの画像分類

pythonを使って簡単な画像分類を実現する - s.t.Mindで2クラスの画像分類を試しましたが、今回は多クラスの画像分類です。

多クラスの画像分類

多クラスになっても画像分類のステップ自体は2クラスの時と同じです。

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

今回の学習画像もBingから集めてきました。下のスクリプトを使って、architecture、car、dog、food、peopleの5種類で、それぞれ35枚の画像セットです。

#-*- coding: utf-8 -*-
from __future__ import with_statement
from bs4 import BeautifulSoup
import requests
import re
import urllib2
import os
import argparse


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


def get_images_from_bing(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 query in i]) + 1
        with open('images/' + query + '_' + str(cntr) + '.jpg',
                  'w') as f:
            f.write(raw_img)

    return cntr


def main():
    parser = argparse.ArgumentParser(
        description='collect training images from Bing'
    )

    parser.add_argument(
        dest='query',
        help='search query',
        nargs='+',
    )

    args = parser.parse_args()

    for q in args.query:
        num_images = get_images_from_bing(q)
        print '[%s] got %d images' % (q, num_images)

if __name__ == '__main__':
    main()
$ python collect_images.py architecture food car dog people
[architecture] got 35 images
[food] got 35 images
[car] got 35 images
[dog] got 35 images
[people] got 35 images
特徴量の取得と識別器学習

特徴量は前回と同様で、画素のRGBをそのまま使う方式です。識別器の学習も基本的には同じですが、前回はarchitecture(0)とfood(1)の2種類のラベルが、今回はarchitecture(0), car(1), dog(2), food(3), people(4)の5種類になります。

多クラスの識別器の学習には、OneVsRestClassifierを使います。ただし、scikit-learnで提供されるSVCやLinearSVCはそもそも多クラスに対応しているので、OneVsRestClassifierを使わなくても多クラス識別器の学習が出来ます。参照: 1.10. Multiclass and multilabel algorithms — scikit-learn 0.14 documentation

#-*- coding: utf-8 -*-
import os
import numpy as np
from PIL import Image
from sklearn.multiclass import OneVsRestClassifier
from sklearn.decomposition import RandomizedPCA
from sklearn.svm import LinearSVC
import pandas as pd

STANDARD_SIZE = (300, 167)


def img_to_matrix(filename, verbose=False):
    """
    Load image as array

    Returns
    -------
    imgArray : numpy array
        Image is resized to STANDARD_SIZE
    """
    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


def flatten_image(img):
    """
    Flatten image array

    Parameters
    ----------
    img : numpy array
        Image array

    Returns
    -------
        img_wide : numpy array
    """
    img_wide = img.reshape(1, img.size)
    return img_wide[0]


def main():
    img_dir = 'images/'
    images = [img_dir + f for f in os.listdir(img_dir)]
    labels = [f.split('/')[-1].split('_')[0] for f in images]
    label2ids = {v: i for i, v in enumerate(sorted(set(labels),
                                                   key=labels.index))}
    y = np.array([label2ids[l] for l in labels])

    data = []
    for image_file in images:
        img = img_to_matrix(image_file)
        img = flatten_image(img)
        data.append(img)
    data = np.array(data)

    # training samples
    is_train = np.random.uniform(0, 1, len(data)) <= 0.7
    train_X, train_y = data[is_train], y[is_train]

    # training a classifier
    pca = RandomizedPCA(n_components=5)
    train_X = pca.fit_transform(train_X)
    multi_svm = OneVsRestClassifier(LinearSVC())
    multi_svm.fit(train_X, train_y)

    # 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, multi_svm.predict(test_X),
                      rownames=['Actual'], colnames=['Predicted'])

if __name__ == '__main__':
    main()

結果

今回の結果はいまいちでした。

$ python train_model.py
Predicted  0  1  2  3  4
Actual                  
0          6  0  2  0  1
1          2  1  0  3  2
2          0  0  0  3  1
3          0  2  2  7  1
4          1  0  0  4  9

高々数十枚の学習画像で、しかも単に画像全体を特徴として使うという単純な方法では、クラス数が増えるとやはり難しいですね。 今回の結果はさておき、scikit-learnを使うと簡単に多クラス分類の実装ができるので、色々と試して見ると面白いかなと思います。