kivantium活動日記

プログラムを使っていろいろやります

友利奈緒判定botを作った

TVアニメCharlotteのヒロイン友利奈緒がTwitter上で異常に増殖する怪現象が起こっています。


と煽られたので実装しました。(3秒ではできませんでした)

f:id:kivantium:20150904135505j:plain

遊び方

@mitra_sun22に画像つきのリプライを飛ばせば顔と判定した部分に白枠をつけた画像と判定結果を返信します。簡単ですね!
ちなみに友利奈緒と判定された画像は筆者(@kivantium)Twitterアイコンに設定されるようになっています。

要素技術

Twitterで煽られたので、Twitterで送られた画像から顔を検出してその顔が友利奈緒かどうか判断してリプライするという仕様にしました。
ほとんど過去の記事からコピペしただけでできました。

ソースコード

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

from tweepy import *
import urllib
import sys
import datetime
import re
from PIL import Image
import cv2
import sys
import os.path
import caffe
from caffe.proto import caffe_pb2
from caffe.io import array_to_datum
import numpy as np
import skimage
import copy
import dlib
import scipy

# mitra_sun22のログイン情報
f = open('config.txt')
data = f.read()
f.close()
lines = data.split('\n')

# kivantiumのログイン情報
f = open('config2.txt')
data = f.read()
f.close()
lines2 = data.split('\n')

# Caffeの準備
mean_blob = caffe_pb2.BlobProto()
with open('mean.binaryproto') as f:
    mean_blob.ParseFromString(f.read())
mean_array = np.asarray(
mean_blob.data,
dtype=np.float32).reshape(
    (mean_blob.channels,
    mean_blob.height,
    mean_blob.width))
classifier = caffe.Classifier(
    'cifar10_quick.prototxt',
    'cifar10_quick_iter_4000.caffemodel',
    mean=mean_array,
    raw_scale=255)

# 顔検出器
detector = dlib.simple_object_detector("detector.svm")

# エンコード設定
reload(sys)
sys.setdefaultencoding('utf-8')

def get_oauth():
	consumer_key = lines[0]
	consumer_secret = lines[1]
	access_key = lines[2]
	access_secret = lines[3]
	auth = OAuthHandler(consumer_key, consumer_secret)
	auth.set_access_token(access_key, access_secret)
	return auth

def get_oauth2():
	consumer_key = lines2[0]
	consumer_secret = lines2[1]
	access_key = lines2[2]
	access_secret = lines2[3]
	auth = OAuthHandler(consumer_key, consumer_secret)
	auth.set_access_token(access_key, access_secret)
	return auth

class StreamListener(StreamListener):
    # ツイートされるたびにここが実行される
    def on_status(self, status):
        if status.in_reply_to_screen_name=='mitra_sun22':
            if status.entities.has_key('media') :
                text = re.sub(r'@mitra_sun22 ', '', status.text)
                text = re.sub(r'(https?|ftp)(://[\w:;/.?%#&=+-]+)', '', text)
                medias = status.entities['media']
                m =  medias[0]
                media_url = m['media_url']
                print media_url
                now = datetime.datetime.now()
                time = now.strftime("%H%M%S")
                filename = '{}.jpg'.format(time)
                try:
                    urllib.urlretrieve(media_url, filename)
                except IOError:
                    print "保存に失敗しました"

                frame = cv2.imread(filename)
                img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                #顔の検出
                dets = detector(img)
                height, width = img.shape[:2]
                flag = True
                #顔が見つかった場合は顔領域だけについて判定
                if len(dets) > 0:
                    flag = False
                    d = dets[0] # 一番大きいものだけを調べる仕様にした
                    # 顔の領域がおかしい場合のチェック
                    if d.top()<0 or d.bottom()>height or  d.left()<0 or d.right()>width:
                        flag = True
                    else:
                        image = frame[d.top():d.bottom(), d.left():d.right()]
                        margin = min((d.bottom()-d.top())/4, d.top(), height-d.bottom(), d.left(), width-d.right())
                        icon = frame[d.top()-margin:d.bottom()+margin, d.left()-margin:d.right()+margin]
                        cv2.imwrite("original.jpg", icon) # アイコン画像は顔よりすこし広い範囲にする
                        #顔部分を白枠で囲む
                        cv2.rectangle(frame, (d.left(), d.top()), (d.right(), d.bottom()), (255, 255, 255), 2)
                        cv2.imwrite(filename, frame)

                if flag: #顔が見つからない場合には全体について判定する
                    image = frame
                    cv2.imwrite("original.jpg", image)
                # Caffeで読める形式に変換
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                sample = skimage.img_as_float(image).astype(np.float32)
                predictions = classifier.predict([sample], oversample=False)
                pred = np.argmax(predictions)

                if pred==0: #友利奈緒の場合
                    print "友利奈緒です"
                    message = '.@'+status.author.screen_name+' 友利奈緒です({}%)'.format(int(predictions[0][pred]*100))
                    try: #アイコンの更新
                        api2.update_profile_image("original.jpg")
                    except TweepError, e:
                        print "error response code: " + str(e.response.status)
                        print "error message: " + str(e.response.reason)
                else:
                    print "友利奈緒ではありません"
                    message = '.@'+status.author.screen_name+' 友利奈緒ではありません({}%)'.format(int(predictions[0][pred]*100))
                message = message.decode("utf-8")
                try:
                    #画像をつけてリプライ
                    api.update_with_media(filename, status=message, in_reply_to_status_id=status.id)
                except TweepError, e:
                    print "error response code: " + str(e.response.status)
                    print "error message: " + str(e.response.reason)


# streamingを始めるための準備
auth = get_oauth()
auth2 = get_oauth2()
api = API(auth)
api2 = API(auth2)
stream = Stream(auth, StreamListener(), secure=True)
print "Start Streaming!"
stream.userstream()

結果

第1世代

初日には適当に集めた150枚くらいの画像で学習したデータで動かしていました。
それなりの精度を出していたのですが、次第に誤認識されるパターンがフォロワーによって暴かれていきました。


他にも
f:id:kivantium:20150904121611p:plain:w300f:id:kivantium:20150904121622j:plain:w300f:id:kivantium:20150904121633j:plain:w300
のような銀髪+青い目という特徴を狙い撃ちにする単純なパターンが友利奈緒と認識されるようになっていました。

第2世代

そこでデータを増やして、精度の向上を図りました。


のように最初はよくなったように見えたのですが……



のように次々と失敗例が挙げられていきました。

そしてついに









という過程で2色による誤認識パターンが発見されました。
これを発見した@sampi_さんには頭が上がりません。

第3世代

第2世代の反省を元にdropoutなどの技術を追加した第3世代のネットワークで運用を行いました。
ちなみに第3世代に投稿された画像は集計を行った21時半の段階で448枚でした。この判定対象部位のうち、真の友利奈緒が98枚、友利奈緒ではないものが404枚、友利奈緒ではあるが加工されている/顔ではない画像が44枚でした。いかに誤判定が狙われているかが伺えます。

面白かった判定例



目の位置に青成分を入れる重要性が感じられます








この撃破パターン、第2世代で見ました。





銀髪キャラによる攻撃パターンが多かったのですが、この発見によりピンクキャラによる攻撃が続きました。
学習データにピンク髪のキャラがほとんどいなかったのがピンクに弱い原因かもしれません。






BPOもビックリ!




ふざけずにちゃんと認識してくれ!





(ピンクは)ヤバいです ヤバいです もう本当にヤバいんです。






50%というなんとも微妙な判定が2回も出ました。




khws4v1.myhome.cx
あんまりまほろさんをいじめないであげてください……




番外編


これはさすがに厳しすぎます……

第4世代

第3世代のネットワークのまま、学習回数と画像数を増やしたネットワークを現在準備中です。今夜のCharlotteが放送されるまでに投入したいです。Caffeの活躍に期待しています。


強化を続けるネットワークとそれに挑むオタクたち。

最後に勝利を収めるのはDeep Learningか、オタクたちか。

目の離せない戦いが飽きられるまで続きます。

広告コーナー