大きく吸ってせーの! で・ぃ・ぷ・ら・ん・に・ん・ぐ! ~ひなこのーと×Deep Learning~

くいなちゃんのかわいさで僕は満足です。

どうも。ひなこのーと、よかったです。かわいかったです。でも、ひなこのーとを見ているとき、いろんな人からこんなことを言われました。

ひなこのーとって、エロいごちうさじゃね?

はぁああああああん!?

可愛いという共通点は認めるし、ごちうさは女神の生まれ変わりだと思っているが、ひなこのーとはごちうさとは違うすばらしさがある!ということで、Deep Learningを使って以下のことをやってみたいと思います。

 

  1. きんいろDeepLearningご注文はDeep Learningですか? など、可愛いアニメは必ずDeep Learningの餌食になると思うので、「ひなこのーと」もDeep Learningやってみる。
  2. アニメひなこのーとの可愛い動画を読み込ませて、主要キャラの分類モデルを作成し、イラストを読み込ませて判定する。
  3. できあがったモデルでごちうさの画像を読み込ませて、ひなこのーとのキャラに分類されないことを確認する。

 

前提条件

  • Python2.7(Anaconda3で仮想環境作成)
  • Windows 7
  • CPUオンリー

 

ちょっとした考察

本当にひなこのーとと、ごちうさは似ているのか、雰囲気だけではないか、よく考えて欲しい。

ひなこのーと

ごちうさ

 

 

1. 「ひなこのーと」もDeep Learningやってみる。

 

下準備

Deep Learningの難しいところは、とにかく大量のトレーニングデータが必要になるところであります。

途方に暮れていたところ、すばらしいスクリプトがあったので使わせて頂きます。

Python、OpenCVで顔の検出(アニメ)

こちらのスクリプトをそのまま使ってひなこのーとの1~12話から顔という顔を切り抜こう!

その際、OpenCVによるアニメ顔検出ならlbpcascade_animeface.xml のすばらしい設定ファイルを活用します。このXMLは後々使いますので、その際また。

そして、切り抜いた顔をそれぞれのキャラごとにフォルダに移します。(手作業)

こんな感じ。 50×50の可愛い画像がたくさん!

データセットはそれぞれ

  • ひなこ 1164枚
  • くいな 678枚
  • まゆき 563枚
  • ゆあ 541枚
  • ちあき 536枚
  • その他 5493枚

です。少ない。。。

 

 

 

学習させる

ご注文はDeep Learningですか?では、Caffeを使っていますが、自分はChainerの方がなじみあるので、Chainerを使っていきます。

と言ってももう既に化物語で同じことをやっていた人がいたので、モデル定義と学習、予測のコードをお借りしました。

python: chainerを使って化物語キャラを認識させるよ! 〜part5.5 主要キャラで多値分類(改良編)〜

 

今回は、ひなこ・くいな・まゆき・ゆあ・ちあき・その他の計6つの分類になりますので、モデル定義等のパラメータのみ変更しています。

モデル定義

#coding:utf-8

import os

import chainer
from chainer import optimizers
import chainer.functions as F
import chainer.links as L
import chainer.serializers as S

import numpy as np

class clf_bake(chainer.Chain):
    def __init__(self):

        super(clf_bake, self).__init__(
            conv1 =  F.Convolution2D(3, 16, 5, pad=2),
            conv2 =  F.Convolution2D(16, 32, 5, pad=2),
            l3    =  F.Linear(6272, 256),
            l4    =  F.Linear(256, 6) #ここを6にしました
        )

    def clear(self):
        self.loss = None
        self.accuracy = None

    def forward(self, X_data, y_data, train=True):
        self.clear()
        X_data = chainer.Variable(np.asarray(X_data), volatile=not train)
        y_data = chainer.Variable(np.asarray(y_data), volatile=not train)
        h = F.max_pooling_2d(F.relu(self.conv1(X_data)), ksize = 5, stride = 2, pad =2)
        h = F.max_pooling_2d(F.relu(self.conv2(h)), ksize = 5, stride = 2, pad =2)
        h = F.dropout(F.relu(self.l3(h)), train=train)
        y = self.l4(h)
        return F.softmax_cross_entropy(y, y_data), F.accuracy(y, y_data)

 

モデル定義に合わせて分類の数を変更し、画像の枚数に合わせて学習用、テスト用画像の枚数等を調整しました。

#coding: utf-8

import cv2
import os
import six
import datetime

import chainer
from chainer import optimizers
import chainer.functions as F
import chainer.links as L
import chainer.serializers as S
from clf_bake_model import clf_bake

import numpy as np

def getDataSet():

X_train = []
X_test = []
y_train = []
y_test = []

for i in range(0,6):
path = "dataset/"
if i == 0:

cutNum = 5493 # その他の画像数です。
cutNum2 = 5393 # 内100枚をテスト用にします。

elif i == 1:
cutNum = 1164 # ひなこの画像数です。
cutNum2 = 1139 # 内25枚をテスト用にします。

elif i == 2:
cutNum = 678 # くいなの画像数です。
cutNum2 = 653 # 内25枚をテスト用にします。

elif i == 3:
cutNum = 563 # まゆきの画像数です。
cutNum2 = 538 # 内25枚をテスト用にします。

elif i == 4:
cutNum = 541 # ゆあの画像数です。
cutNum2 = 516 # 内25枚をテスト用にします。

elif i == 5:
cutNum = 536 # ちあきの画像数です。
cutNum2 = 511 # 内25枚をテスト用にします。

imgList = os.listdir(path+str(i))
imgNum = len(imgList)
for j in range(cutNum):
imgSrc = cv2.imread(path+str(i)+"/"+imgList[j])

if imgSrc is None:continue
if j < cutNum2:
X_train.append(imgSrc)
y_train.append(i)
else:
X_test.append(imgSrc)
y_test.append(i)

return X_train,y_train,X_test,y_test

def train():

X_train,y_train,X_test,y_test = getDataSet()

X_train = np.array(X_train).astype(np.float32).reshape((len(X_train),3, 50, 50)) / 255
y_train = np.array(y_train).astype(np.int32)
X_test = np.array(X_test).astype(np.float32).reshape((len(X_test),3, 50, 50)) / 255
y_test = np.array(y_test).astype(np.int32)

model = clf_bake()
optimizer = optimizers.Adam()
optimizer.setup(model)

epochNum = 30
batchNum = 50
epoch = 1

while epoch <= epochNum:
print("epoch: {}".format(epoch))
print(datetime.datetime.now())

trainImgNum = len(y_train)
testImgNum = len(y_test)

sumAcr = 0
sumLoss = 0

perm = np.random.permutation(trainImgNum)

for i in six.moves.range(0, trainImgNum, batchNum):

X_batch = X_train[perm[i:i+batchNum]]
y_batch = y_train[perm[i:i+batchNum]]

optimizer.zero_grads()
loss, acc = model.forward(X_batch, y_batch)
loss.backward()
optimizer.update()

sumLoss += float(loss.data) * len(y_batch)
sumAcr += float(acc.data) * len(y_batch)
print('train mean loss={}, accuracy={}'.format(sumLoss / trainImgNum, sumAcr / trainImgNum))

sumAcr = 0
sumLoss = 0

for i in six.moves.range(0, testImgNum, batchNum):
X_batch = X_test[i:i+batchNum]
y_batch = y_test[i:i+batchNum]
loss, acc = model.forward(X_batch, y_batch, train=False)

sumLoss += float(loss.data) * len(y_batch)
sumAcr += float(acc.data) * len(y_batch)
print('test mean loss={}, accuracy={}'.format(
sumLoss / testImgNum, sumAcr / testImgNum))
epoch += 1

S.save_hdf5('model'+str(epoch+1), model)

 

学習結果

(py27con) C:\Users\tubone\PycharmProjects\anime-learn>python train.py
epoch: 1
2017-07-16 21:40:03.060000
train mean loss=1.16183573621, accuracy=0.619771432281
test mean loss=1.05560781558, accuracy=0.506666666104
epoch: 2
2017-07-16 21:42:13.192000
train mean loss=0.800489272901, accuracy=0.69348571573
test mean loss=0.871727473206, accuracy=0.577777779765
epoch: 3
2017-07-16 21:44:23.290000
train mean loss=0.698785139833, accuracy=0.743085714408
test mean loss=0.780110951927, accuracy=0.631111116873
epoch: 4
2017-07-16 21:46:33.502000
train mean loss=0.597107084649, accuracy=0.786971424307
test mean loss=0.548737568988, accuracy=0.83111111323
epoch: 5
2017-07-16 21:48:44.037000
train mean loss=0.500077148761, accuracy=0.82479999406
test mean loss=0.480820135938, accuracy=0.857777780957
epoch: 6
2017-07-16 21:50:57.706000
train mean loss=0.451180534618, accuracy=0.841257137571
test mean loss=0.448005066978, accuracy=0.87555554178
epoch: 7
2017-07-16 21:53:09.992000
train mean loss=0.405994535514, accuracy=0.861028568063
test mean loss=0.472903796368, accuracy=0.835555553436
epoch: 8
2017-07-16 21:55:22.262000
train mean loss=0.358310819779, accuracy=0.87851428066
test mean loss=0.306394663122, accuracy=0.911111103164
epoch: 9
2017-07-16 21:57:34.985000
train mean loss=0.337241342791, accuracy=0.880799995831
test mean loss=0.308397501707, accuracy=0.902222216129
epoch: 10
2017-07-16 21:59:47.595000
train mean loss=0.324274266022, accuracy=0.886742852415
test mean loss=0.323723706934, accuracy=0.871111101574
epoch: 11
2017-07-16 22:01:59.865000
train mean loss=0.296059874466, accuracy=0.897142853737
test mean loss=0.390861597326, accuracy=0.848888880677
epoch: 12
2017-07-16 22:04:12.384000
train mean loss=0.28229231613, accuracy=0.900799994469
test mean loss=0.381249505613, accuracy=0.888888888889
epoch: 13
2017-07-16 22:06:25.189000
train mean loss=0.242525527115, accuracy=0.915771426473
test mean loss=0.304150695602, accuracy=0.906666656335
epoch: 14
2017-07-16 22:08:37.995000
train mean loss=0.231497560718, accuracy=0.919314283303
test mean loss=0.275258473224, accuracy=0.906666662958
epoch: 15
2017-07-16 22:10:50.532000
train mean loss=0.219778511652, accuracy=0.923199997629
test mean loss=0.354618171851, accuracy=0.91555554337
epoch: 16
2017-07-16 22:13:02.623000
train mean loss=0.218345963359, accuracy=0.926285712378
test mean loss=0.36049440172, accuracy=0.897777775923
epoch: 17
2017-07-16 22:15:15.105000
train mean loss=0.199432469181, accuracy=0.933599996226
test mean loss=0.403067363633, accuracy=0.87555554178
epoch: 18
2017-07-16 22:17:27.614000
train mean loss=0.188562800608, accuracy=0.936114283289
test mean loss=0.316384883391, accuracy=0.911111109787
epoch: 19
2017-07-16 22:19:40.506000
train mean loss=0.187215176012, accuracy=0.933028570243
test mean loss=0.360161377324, accuracy=0.906666676203
epoch: 20
2017-07-16 22:21:53.107000
train mean loss=0.165474589265, accuracy=0.94388571058
test mean loss=0.282101011939, accuracy=0.919999996821
epoch: 21
2017-07-16 22:24:05.521000
train mean loss=0.153822022751, accuracy=0.947999996117
test mean loss=0.33798650321, accuracy=0.919999996821
epoch: 22
2017-07-16 22:26:18.048000
train mean loss=0.140677581344, accuracy=0.952228568281
test mean loss=0.309250995517, accuracy=0.924444450272
epoch: 23
2017-07-16 22:28:30.608000
train mean loss=0.138967069973, accuracy=0.951314284801
test mean loss=0.488151417838, accuracy=0.871111101574
epoch: 24
2017-07-16 22:30:43.540000
train mean loss=0.150780805051, accuracy=0.945828568935
test mean loss=0.305154048734, accuracy=0.924444450272
epoch: 25
2017-07-16 22:32:55.811000
train mean loss=0.133075305953, accuracy=0.952799998692
test mean loss=0.421937998798, accuracy=0.906666662958
epoch: 26
2017-07-16 22:35:08.076000
train mean loss=0.119467954348, accuracy=0.954514285156
test mean loss=0.384507967366, accuracy=0.902222222752
epoch: 27
2017-07-16 22:37:20.527000
train mean loss=0.138162662153, accuracy=0.949028568949
test mean loss=0.317712697718, accuracy=0.928888883856
epoch: 28
2017-07-16 22:39:32.964000
train mean loss=0.114523774907, accuracy=0.961485714912
test mean loss=0.39709764719, accuracy=0.919999996821
epoch: 29
2017-07-16 22:41:45.211000
train mean loss=0.120365411943, accuracy=0.958171426228
test mean loss=0.379737239745, accuracy=0.93333334393
epoch: 30
2017-07-16 22:43:57.336000
train mean loss=0.110197391031, accuracy=0.958742856298
test mean loss=0.401306927204, accuracy=0.920000010067

計32エポックで学習終了です。

 

2.主要キャラの分類モデルを作成し、イラストを読み込ませて判定する。

 

予測用のコードも以下からお借りしています。

python: chainerを使って化物語キャラを認識させるよ! 〜part5.5 主要キャラで多値分類(改良編)〜

 

# -*- coding: utf-8 -*-
#!/usr/bin/env python
import sys

import numpy as np
import six
import cv2
import os


import chainer
from chainer import computational_graph as c
import chainer.functions as F
import chainer.serializers as S

from chainer import optimizers

from clf_bake_model import clf_bake


model = clf_bake()
S.load_hdf5('./model32', model) # 32モデルだったので。
#model = pickle.load(open('model30','rb'))

chara_name = ['Unknown', "Hinako","Kuina","Mayuki","Yua","Chiaki"]

def forward(x_data):
    x = chainer.Variable(x_data, volatile=False)
    h = F.max_pooling_2d(F.relu(model.conv1(x)), ksize = 5, stride = 2, pad =2)
    h = F.max_pooling_2d(F.relu(model.conv2(h)), ksize = 5, stride = 2, pad =2)
    h = F.dropout(F.relu(model.l3(h)), train=False)
    y = model.l4(h)

    return y

def detect(image, cascade_file = "./lbpcascade_animeface.xml"): # アニメ顔抽出時使ったXMLを指定
    if not os.path.isfile(cascade_file):
        raise RuntimeError("%s: not found" % cascade_file)

    cascade = cv2.CascadeClassifier(cascade_file)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray = cv2.equalizeHist(gray)

    faces = cascade.detectMultiScale(gray,
                                     scaleFactor = 1.1,
                                     minNeighbors = 1,
                                     minSize = (20, 20))

    print(faces)

    return faces

def recognition(image, faces):
    face_images = []

    for (x, y, w, h) in faces:
        dst = image[y:y+h, x:x+w]
        dst = cv2.resize(dst, (50, 50))
        face_images.append(dst)

    face_images = np.array(face_images).astype(np.float32).reshape((len(face_images),3, 50, 50)) / 255

    #face_images = cuda.to_gpu(face_images)

    return forward(face_images) , image

def draw_result(image, faces, result):

    count = 0
    for (x, y, w, h) in faces:
        classNum = 0
        result_data = result.data[count]
        classNum = result_data.argmax()
        recognized_class = chara_name[result_data.argmax()]
        font = cv2.FONT_HERSHEY_TRIPLEX # 名前を入れたいので、OpenCVのFont機能を使います。
        font_size = 1.1
        if classNum == 0:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,255,3), 3)
                cv2.putText(image, chara_name[0], (x,y), font, font_size,
                            (255, 255, 3)) # 重ねたい画像を選択し、フォントサイズ等を指定
        elif classNum == 1:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,0,255), 3)
                cv2.putText(image, chara_name[1], (x,y), font, font_size,
                            (0, 0, 255))
        elif classNum == 2:
                cv2.rectangle(image, (x, y), (x+w, y+h),  (255,0,0), 3)
                cv2.putText(image, chara_name[2], (x,y), font, font_size,
                            (255, 0, 0))
        elif classNum == 3:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,255,255), 3)
                cv2.putText(image, chara_name[3], (x,y), font, font_size,
                            (255, 255, 255))
        elif classNum == 4:
                cv2.rectangle(image, (x, y), (x+w, y+h), (255,0,255), 3)
                cv2.putText(image, chara_name[4], (x,y), font, font_size,
                            (255, 0, 255))
        elif classNum == 5:
                cv2.rectangle(image, (x, y), (x+w, y+h), (0,255,255), 3)
                cv2.putText(image, chara_name[5], (x,y), font, font_size,
                            (255, 128, 255))

        count+=1

    return image

#ファイル読み込み
img = cv2.imread("test.jpg")

faces = detect(img)

result, image = recognition(img, faces)

image = draw_result(image, faces, result)
cv2.imwrite('out.png',image)

 

実際、読み込ませる

実際読み込ませてみたものがこちら。

  • 水色はUnknown
  • 赤がひなこ
  • 青がくいな
  • 白がまゆき
  • 紫がゆあ
  • オレンジがちあき

です。

大家さんだけがUnknownとなってしまいました。。。

データ量が少なかったためでしょうね。

 

3. できあがったモデルでごちうさの画像を読み込ませて、ひなこのーとのキャラに分類されないことを確認する。

いよいよオーラスです。これをやるためにつくったモデルです。

さっそく読み込ませて見ます。

 

 

むむっ。ココアさんがひなこに、シャロちゃんがまゆちゃんと認識されているではないか。

ココアさんとひなこ、なんか似ているように感じなくもなくもない?????

フルール・ド・ラパンの制服と、まゆちゃんの私服が似ているというのはあるが、髪色で判断しているわけではないよな??

 

金髪で判断しているという可能性がぬぐいきれないので。これでもくらえ!きんモザ&ごちうさのコラボじゃあああ。

 

 

金髪で判断しているというわけではなかった。

それにしてもココアさん=ひなこは覆らないな。

 

絵のタッチを変えてみる

こうなったら徹底討論じゃあ。絵のタッチを変えてどうなるか試してみましょう。

 

 

ココア、お前だったのか!! (結論)

 

おまけ1 ひなこのーともイラストで認識させてみる。

 

どうやら、くいなちゃんの認識率が一番いい。くいなちゃんかわいいからね。

 

おまけ2 くいなちゃん、某オタク少女に似ている説を確かめる。

 

 

大丈夫でした!!!

 

[ 大きく吸ってせーの! で・ぃ・ぷ・ら・ん・に・ん・ぐ! ~ひ... ]Anime,Tech, , , , 2017/07/17 03:29