アニメの顔写真をBag of Keywordsで分類してみた
皆さんこんにちは
お元気ですか。私は眠いです。書いている時間が深夜なんて言えない。
さて、今日はアニメの顔写真を分類してみます。
画像認識の分野において結構メジャーな手法であるBag of Keywordsを使います。
Bag of Keywordsの概要
Bag of keypoints, Bag of featureとも呼ばれる手法
元来は、文書分類の手法であるBag of wordである。画像の集合を局所特徴量の集合とみなし、利用する手法のことである。
局所特徴量
特徴量抽出法により、濃淡の変化が大きい部分を
特徴ベクトルとし、特徴点周りを微分したりしたもの。
要は最も特徴のある点を算出し、何らかの形でベクトルにしたものである。
今回は有名な手法であるSIFTを利用する。
SIFTの簡単な説明
特徴点と特徴量を記述するアルゴリズムだそうな。
詳しいアルゴリズムの内容はよくわかりませんが、エッジに対して不変なものを持っています。
Python Opencv2を今回は利用しました。
具体的なアルゴリズムについて
今回扱う手法についてのフローチャートを生成しました。
1.特徴量抽出
画像の特徴的なポイントを取得します。128次元のベクトルです。
今回はSIFTを利用して抽出しました。輝度変化が大きなところです。
2.クラスタリング(Bag of Keywords)
1で取得した特徴的なポイントをクラスタリングします。
ベクトルのままだと比較しずらいのでクラスタのインデックスをかわりに置こうということです。
これをすることにより不必要な些細な違いを無視することができます。
今回は時間がかかると面倒なのでK平均法を使ってみました。どんなことをやっているかイメージとしては以下の図を参照ください。
似ているものは同じ表現として置き換えるイメージの発想です。クラスタリングをどんなのを使うかとか議論はありますが、簡便化しました。
3.ヒストグラムの生成
クラスタリングのインデックスに基づいてヒストグラムを生成。ようはどんな特徴がどれだけあるかをヒストグラムにします。類似度を図るなら、ヒストグラムを比較するアルゴリズムを使えば良いと思いますが、今回はこれをクラスタリングにかけます。
各画像につき一つのヒストグラムを生成し、横軸はクラスタのインデックスとなります。
4.再度クラスタリング(画像の分類)
3で得たヒストグラムをクラスタリングし、画像にしています。
これも同じくK平均法を使っています。要はヒストグラムを元に勝手に分類ですね。
Dataset
animeface-charactor-dataset様を利用させて頂きました。
animeface-character-dataset
Result
画像貼るので目視で誰か評価してください。(画像が多いので、続きを読むから)
Cluster0
Cluster1
Cluster2
Cluster3
Cluster4
Cluster5
Cluster6
Cluster7
Cluster8
Cluster9
いや近い部分は本当に近いのが集まっていると思うのですよ。
評価の方法なくて困っているけどさ。
ソースコード
#coding:utf-8 import cv2 from sklearn.cluster import KMeans import numpy as np import os import random def random_choice_unique(lists, num=4): if not lists: return [] if len(lists) <= num: return lists copy_lists = lists[:] results = [] for i in range(num): t = random.choice(copy_lists) results.append(t) copy_lists.remove(t) return results class Image(object): def __init__(self): pass def fileread(self,filepath): #print filepath self.src = cv2.imread(filepath) self.srcGrey = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY) self.height,self.width = self.srcGrey.shape def readArray(self,array): self.srcGrey = array #self.height,self.width = self.srcGrey.shape def sift(self): sift = cv2.SIFT() kp, des = sift.detectAndCompute(self.srcGrey,None) return np.array(des) def addhist(self,histgram): self.hist = histgram class Images(object): def __init__(self): self.images = [] def addImage(self,image): self.images.append(image) def readAllFiles(self,folderpath,isResize=False,height=0,width=0): for path in self.readAllFilePath(folderpath): image = Image() image.fileread(path) if isResize: pi = ProcessImage() image = pi.resize(image, height, width) self.addImage(image) def readAllFilePath(self,folderpath): for root, dirs, files in os.walk(folderpath): for file in files: if not file.startswith(".") and file.endswith(".png"): yield os.path.join(root, file) def extractAllSift(self): sift_feature = self.images[0].sift() flag = False for image in self.images: if flag: sift_feature = np.vstack((sift_feature,image.sift())) flag = True return sift_feature def tile(self,height,width,filename): """ サイズを構成 """ board_height = 1120 board_width = 1120 board = np.zeros((board_height,board_width,3),dtype='uint8') pi = ProcessImage() height_index_num = board_height / height width_index_num = board_width / width image_list = random_choice_unique(self.images, width_index_num * height_index_num) for height_index in xrange(height_index_num): for width_index in xrange(width_index_num): try: index = height_index * (board_width / width) + width_index resizeImage = pi.resize(image_list[index], height, width) board[height_index * height:height_index * height + height,width_index * width:width_index * width + width] = resizeImage.srcGrey except: break cv2.imwrite(filename,board) class ProcessImage(object): def __init__(self): pass def resize(self,image,height,width): resizeImage = cv2.resize(image.src,(width,height)) resizeImgObject = Image() resizeImgObject.readArray(resizeImage) return resizeImgObject if __name__ == '__main__': images = Images() print "画像の読み込みを開始します" images.readAllFiles("/Users/Tereka/Downloads/animeface-character-dataset/thumb") print "画像の読みこみを終了しました" print "画像からSIFT特徴量を抽出します。" sift_feature = images.extractAllSift() print "画像からSIFT特徴量の抽出を完了しました。" print "Bag of keywordsの構築" cluster_number = 200 kmeans = KMeans(n_clusters=cluster_number) kmeans.fit(sift_feature) print "Bag of keywordsの学習終了" print "ヒストグラムの構築を開始" hist_list = [] for image in images.images: image_sift = image.sift() predict_index = kmeans.predict(image_sift) histgram= np.zeros((cluster_number)) for index in predict_index: histgram[index] += 1 histgram = histgram / sum(histgram) image.addhist(histgram) hist_list.append(histgram) print "ヒストグラムの構築を完了" print "画像分類を開始" cluster_number = 10 kmeans = KMeans(n_clusters=cluster_number) kmeans.fit(np.array(hist_list)) print "画像分類を完了" print "画像分類したものを画像に" TileList = [] for i in xrange(cluster_number): tile_images = Images() TileList.append(tile_images) for image in images.images: index = kmeans.predict(np.array([image.hist])) TileList[index[0]].addImage(image) num = 0 for tile in TileList: tile.tile(80,80,"./cluster_%d.png" % num) num += 1 print "全てのプログラムを完了しました"