end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

HOG特徴量 + KNN(k近傍) による ジェスチャー(手話)画像認識

先程のエントリの続きとして、ジェスチャー画像のHOG特徴量を算出し、 その結果を、k近傍することで、手話の画像認識を行います。

画像処理(画像認識) - 「密なSIFT(HOG : Histogram of Oriented Gradients )」算出 - end0tknr's kipple - 新web写経開発

pythonでのk近傍法( k-nearest neighbor algorithm, k-NN ) - end0tknr's kipple - 新web写経開発

前準備 - 訓練データと、確認用データ

実践コンピュータビジョン サンプルプログラム

に記載されている通り、

http://www.idiap.ch/resource/gestures/data/shp_marcel_test.tar.gz

の uniform/ 以下にある {A,B,C,Five,Point,V} の 画像を train と test dirにcopy 。

次にのように一気に行うこともできます

$ cd ~/tmp/VISION/chap8
$ mkdir train
$ mkdir test
$ wget http://www.idiap.ch/resource/gestures/data/shp_marcel_test.tar.gz
$ tar -xvf shp_marcel_test.tar.gz
$ mv Marcel-Test/*/uniform/*uniform[0-2]?.ppm train
$ mv Marcel-Test/*/uniform/*.ppm test

STEP.1 各画像の HOG特徴量を算出

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
from PIL import Image
import os
import dsift
import numpy

## http://www.vlfeat.org/download/vlfeat-0.9.20.tar.gz
SIFT_CMD = '/home/endo/local/vlfeat/sift'

def main():
    d = 'test'  # test用data
    imlist = [os.path.join(d,f) for f in os.listdir(d) if f.endswith('.ppm')]
    d = 'train' # 訓練用data
    imlist += [os.path.join(d,f) for f in os.listdir(d) if f.endswith('.ppm')]

    # 50x50sizeで、「密なSIFT記述子(HOG)」算出
    for filename in imlist:
        featfile = filename[:-3]+'dsift'
        process_image_dsift(filename,featfile,10,5,resize=(50,50))


# 画像から「密なSIFT記述子(HOG)」を抽出し、fileに保存.
#   args size=特徴量size, steps=grid間隔, resize=画像resize用のタプル
#   force_orientation= True →最大の局所勾配の方向で記述子が正規化
#                      False→全て上向き
def process_image_dsift(imagename,
                        resultname,
                        size=20,
                        steps=10,
                        force_orientation=False,
                        resize=None):
    im = Image.open(imagename).convert('L')  # file open後、グレースケール化
    if resize != None:
        im = im.resize(resize)
    m, n = im.size

    if imagename[-3:] != 'pgm':
        im.save('tmp.pgm')  # pgmファイルを作成
        imagename = 'tmp.pgm'

    # frameを作成し一時fileに保存
    scale = size / 3.0
    x, y = numpy.meshgrid(range(steps, m, steps), range(steps, n, steps))
    xx, yy = x.flatten(), y.flatten()
    frame = numpy.array([xx, yy,
                         scale * numpy.ones(xx.shape[0]),
                         numpy.zeros(xx.shape[0])])

    numpy.savetxt('tmp.frame', frame.T, fmt='%03.3f')

    if force_orientation:
        cmmd = str(SIFT_CMD +" "+ imagename + " --output=" + resultname +
                   " --read-frames=tmp.frame --orientations")
    else:
        cmmd = str(SIFT_CMD +" "+ imagename + " --output=" + resultname +
                   " --read-frames=tmp.frame")
    os.system(cmmd)

    print 'processed', imagename, 'to', resultname


if __name__ == '__main__':
    main()

↑こう書くと、test , train dir 以下にある画像のHOGを算出し、 結果を *.dsift ファイルに保存します

STEP.2 k-NNによる画像認識

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import numpy
import os


def main():
    features, labels = read_gesture_features_labels('train/')
    test_features, test_labels = read_gesture_features_labels('test/')
    classnames = numpy.unique(labels)

    # k近傍法を試す
    k = 1
    knn_classifier = KnnClassifier(labels, features)
    res = numpy.array([
        knn_classifier.classify(test_features[i], k)
        for i in range(len(test_labels))
    ])
    # 精度
    acc = sum(1.0 * (res == test_labels)) / len(test_labels)
    print 'Accuracy:', acc
    print_confusion(res, test_labels, classnames)


# HOGが登録された *.dsift の filename list 作成
def read_gesture_features_labels(path):
    featlist = [
        os.path.join(path, f) for f in os.listdir(path) if f.endswith('.dsift')
    ]

    # 特徴量を読込み
    features = []
    for featfile in featlist:
        l, d = read_features_from_file(featfile)
        features.append(d.flatten())
    features = numpy.array(features)
    
    # file名からラベルを作成
    labels = [featfile.split('/')[-1][0] for featfile in featlist]
#    print labels
    
    return features, numpy.array(labels)


# 特徴量を読込み行列形式で返す
def read_features_from_file(filename):
    f = numpy.loadtxt(filename)
    return f[:, :4], f[:, 4:]  # 特徴点の配置と記述子


def print_confusion(res, test_labels, classnames):
    n = len(classnames)
    # 混同行列
    class_ind = dict([(classnames[i], i) for i in range(n)])
    confuse = numpy.zeros((n, n))
    for i in range(len(test_labels)):
        confuse[class_ind[res[i]], class_ind[test_labels[i]]] += 1
    print 'Confusion matrix for'
    print classnames
    print confuse


class KnnClassifier(object):
  def __init__(self,labels,samples):
    """ 教師データを使って分類器を初期化する """
    self.labels = labels
    self.samples = samples

  def classify(self,point,k=3):
    """ pointを教師データ中のk個の最近傍を使って分類し、
        ラベルを返す """

    # 全教師データへの距離を計算する
    dist = numpy.array([L2dist(point,s) for s in self.samples])
    # ソートする
    ndx = dist.argsort()
    # k個の最近傍を保持するのに辞書を用いる
    votes = {}
    for i in range(k):
      label = self.labels[ndx[i]]
      votes.setdefault(label,0)
      votes[label] += 1

    return max(votes, key=lambda x: votes.get(x))


def L2dist(p1,p2):
    return numpy.sqrt( sum( (p1-p2)**2) )


if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

$ python 0_8.1.3_2.knn_g.py 
Accuracy: 0.617021276596
Confusion matrix for
  [   'A'  'B'  'C'  'F'  'P'  'V']
[['A' 23.   2.   2.   1.   0.   2.]
 ['B'  2.  20.   2.   5.   4.   1.]
 ['C'  0.   1.  23.   1.   0.   0.]
 ['F'  0.   1.   0.  20.   0.   0.]
 ['P'  0.   5.   3.   1.  12.   8.]
 ['V'  4.   0.   6.   1.  20.  18.]]

特に右から2列目の結果(12と30)から、P が Vと混同されやすいことが分かります。