【Android】adbを使ってAlarmManagerの実行予定時間を確認する

開発中にAlarmが発火するまで待機するのも馬鹿らしいので、adbのコマンドを利用して指定した時間にAlarmが設定できているのか確認を行う。

$ adb shell dumpsys alarm | grep 【hoge】
RTC_WAKEUP #4: Alarm{7700c50 type 0 when 1498210200398 com.hoge.exampleapp}
      tag=*walarm*:com.hoge.exampleapp/.ExampleReceiver
      operation=PendingIntent{455ce49: PendingIntentRecord{45bb25c com.hoge.exampleapp broadcastIntent}}

hoge】には自分のプロジェクトで使っているPackage nameの一部を入れる。
whenの後に表示されているunix timeを適当な変換器で日本時間に直してあげればOK。

参考

Androidアプリ開発者なら知っておくべきdumpsysコマンド | Money Forward Engineers' Blog

HoI4の実績を全て解除した

4月の頭辺りにHoI4が発売されていることを知り(遅い)、Steamで即購入後遊びまくった。

一通り主要国で遊んだあと、今作のゲームシステムへの理解を深めるために全実績解除を目指すことにした。

Achievements - Hearts of Iron 4 Wiki

英語版HoI4wikiに各実績の補足みたいなことが書いてあるので参考になった。

現時点で発売されている唯一のDLCであるTogether for Victoryで追加された実績を含め、全実績を無事解除できたので、軽く感想を記録しておく。
バージョンは全て1.3.3で行った。

f:id:taka_say:20170530221540j:plain

個人的に面倒だったのは、Freegyptだと思う。
これはただただレンドリース設定後の待機が面倒なのと、枢軸北アフリカ軍に本土占領されることがあったのでストレスが溜まった。

実績解除に手こずったのは、恐らく定番のBearer of ArtilleryとCrusader Kings 2だった。

Bearer of Artilleryはダンツィヒをドイツに譲ったあとソ連に勝てば実質終了なので、試行1回で解除できた。

f:id:taka_say:20170530221704j:plain

ただCrusader Kings 2の方は、解除にTake10以上の試行が必要で、丸3日間くらいかかった(下手糞!)。
プレイ方針は、NFと政治顧問で共産化目指しつつ、軍事工場2建設の後造船所4建設と潜水艦の研究を進める。
政治系NFが落ち着いた段階で海軍系NFを進め、次世代潜水艦のNF研究ボーナスで早めに1940型潜水艦の研究を終え、生産を急ぐ。
あとは幅16ぐらいの海兵隊(陸軍経験値で可能な限り海兵隊大隊を増やす)を8部隊用意し、NFで得た宣戦事由でポルトガルに宣戦。
初期配備の歩兵でアフリカ本土を守りつつ、海兵隊ポルトガル島嶼と飛び地を占領する。
40年式の潜水艦が10程度いれば、上陸部隊の援護はぎりぎり可能なはずだが、運が悪いと敵駆逐艦隊に殲滅される(リセット案件)。
島嶼を制圧後、本土への上陸作戦を計画する。 ここで、リスボンではなく、ポルトに上陸し、河川にそって防衛線を敷きつつ、一部隊だけリスボンへの上陸作戦を命令すると、敵本土部隊とほぼ交戦せず降伏まで追い込める。

ポルトガル占領後はベルギー占領を行う方法も試行したが、あまり得にならない上に、ベルギーが連合に入るまでの時間が結構シビアなので、今回はやらなかった。

あとは普通に軍備を増強し、枢軸が連合に喧嘩売るのを待ち、英本土に上陸作戦をしかける。
この頃には潜水艦隊が40〜50隻ぐらいはいるはずなので、どうにか英本土に上陸することは可能なはず。
上陸後はほとんど英軍の抵抗は無いので、ロンドンの占領を目指す。
ただし、イギリスが降伏してしまうと和平会議が開催されてしまうため、いくつかビクポの高いプロビを残しつつ、アフリカ戦線に移る。
占領を進めていくと各アフリカ諸国を独立させるイベントが発生するが、これが完全にランダムイベントなため、かなりイライラが募る。
連合のアフリカ領侵攻を進めるときは、南アの直轄地になるように、軍は自国から進軍させるよう心がけたほうが良い気がする。
先に独立した国の領土になっていると、ランダム独立イベントが発生しない気がする(1948辺りまで待機しても独立しなかったので……)。

一応アメリカの介入を防ぐには、共産化してすぐに政治工作をかければ間に合うはず。
アメリカが参戦してくると、アフリカ諸国の独立が遅い場合英本土が守りきれない可能性があるので、可能ならば工作で共産化させたい。

Hearts Of Iron 4 - Crusader Kings 2 Achievement Guide - YouTube

基本的にはこの人のプレイを参考にやったが、微妙にバージョンが違うので、同じやり方ではベルギー戦あたりが厳しい。

実績解除自体は途中から作業感が出てきて気持ちが萎える部分もあったが、AIの挙動が分かったり、プレイングの幅が広がる点では面白かった。

f:id:taka_say:20170530221603j:plain

一先ずニート以外では不可能なプレイ時間(1ヵ月で300時間!)分は遊んだので、次のDLCまで寝かせようと思う。

NBA LEAGUE PASSを解約する方法

シーズンも終わったし(ファーストラウンドスウィープ)NBA league passを解約しようとアカウントの設定関係を見て回ったんですが、Subscription停止がどこにもない。

解約に一手間必要だったので一応メモ。

これは2017年5月時点での方法なので、変更されている可能性があります。

規約

NBA SupportのFAQによると、

How can I cancel my NBA LEAGUE PASS subscription?
To cancel your NBA LEAGUE PASS subscription, please email nbasupport@neulion.com with your cancellation request.

とのこと。

簡単に訳すと、Subscriptionの解約するにはnbasupport@neulion.comにメールしてね!ということらしい。
解約するためだけに今時メールさせるの!?という驚きはあったが、仕様がないので指示に従う。

メール送付

簡易な英語で問題ないようなので、Youtubeにて解約方法を紹介していた人の例に倣う。
以下文例。

件名:Please cancel my subscription

Hello

Please cancel my NBA league pass subscription.

My username is [自分のアカウントのユーザ名].

Thanks, [自分の名前].

結果

数日後にSubscription停止の返信が来ていた。

解約させたくないのは分かるけど、現代のサービスでこの仕様はあんまりだと思う。
調べてみたところ、アカウントの設定から解約できたという人がいたり、分割払いだから途中では解約できないよという人がいたり、若干情報が錯綜している感じがあった。 確かにNBA league passの契約内容はシーズン毎に多少なり差があるようなので、その時々で微妙に差異があるのかもしれない。

参考

www.youtube.com

【逃げ恥】恋ダンスでただガッキーだけを見ていたかったのでディープラーニングする(後編)

taka-say.hateblo.jp

上記記事の続きです。

復習は面倒だが役に立つ

前回の記事では、ガッキーの顔とそうでないものの二値分類を行うモデルを作成し、実際に動画への適用を試みました。
残念ながら上手くいきませんでした。

詳細は前編をどうぞ。

忍耐は辛いが役に立つ②

前回の取り組みで顕在化した問題に対処していきます。
対処方法として、分類するクラスを増やすことで解決を図ります。

  • ガッキー
  • その他出演者4名
  • その他顔
  • その他顔として検出されてしまう物体

上記の7クラスの分類を行う顔判別機を作る方針でいきます。

本来であれば、各々の顔を前編でやったような手法で収集しなければいけませんが、面倒だったので、恋ダンス動画自体から顔データの収集を行います。
顔画像取得のコードは下記の通りです。

import cv2
import datetime
import numpy as np
from keras.models import load_model

# 動画関係
target = "input.mp4"
movie = cv2.VideoCapture(target)

# 顔認識準備
model = load_model('./Models/gakky_face_model.hdf5')
cascade_path = '/path/to/your/opencv3/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
gakky_n = 0

if movie.isOpened() is True:
    ret, frame = movie.read()
else:
    ret = False
count = 0
while ret:
    # 顔検出
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    frontfaces = cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)
    # 顔認識
    for (x, y, w, h) in frontfaces:
        dst = frame[y:y + h, x:x + w]
        image = cv2.resize(dst, (64, 64))
        image = image.transpose(2, 0, 1)
        image = image / 255.
        image = image.reshape(1, 3, 64, 64)
        if model.predict_classes(np.array(image)) == 0:
            cv2.imwrite('./FaceImagesFromVideo/Gakky/' + str(count) + '.jpg', dst)
        else:
            cv2.imwrite('./FaceImagesFromVideo/Others/' + str(count) + '.jpg', dst)
        count += 1
    ret, frame = movie.read()
    # 50フレームごとに経過を出力
    if movie.get(cv2.CAP_PROP_POS_FRAMES) % 50 == 0:
        print(datetime.datetime.now().strftime('%H:%M:%S'),
              '現在フレーム数:' + str(int(movie.get(cv2.CAP_PROP_POS_FRAMES))))

前回作成したモデルは、ガッキーかそうでないかを判別する能力はありませんでしたが、顔検出した箇所が、顔っぽいかそうでないかを判別する能力はありそうだったので、その予測値を利用して保存するディレクトリを選択しています。
この事前分類をしておくことで、後の分類負担を多少緩和できます。
処理が終わると、恋ダンスの動画中で顔だと思われる箇所が全て保存されるはずです。

あとは、冒頭で示した7つのクラスに合致するように、手動で画像を各々のディレクトリに分類していきます。
ディレクトリ名は下の画像のように、数字の接頭辞を付けておくとよいです。

f:id:taka_say:20161218212929p:plain

最終的に動画から切り出した顔画像の枚数は、

  • ガッキー:1516
  • 出演者A:349
  • 出演者B:398
  • 出演者C:295
  • 出演者D:452
  • その他顔:85
  • その他物:2591

となりました(アンバランス!)。
前編で用意したデータと統合すると、顔画像データは合計で7,525枚になりました。

繰り返しは気怠いが役に立つ

新しい顔画像データで再度学習を行っていきます。
修正箇所は下記の2ヵ所です。

  • 顔データのNumPy配列出力を行う処理の箇所で、data_dir_pathを新しい顔画像データのあるディレクトリに修正
  • モデルオプションの指定をnb_classes = 7に修正

枚数が多くなったせいかそれなりに学習に時間がかかります。

最終的な学習経過はこのようになりました。

f:id:taka_say:20161219142240p:plain f:id:taka_say:20161219142248p:plain

若干学習途中のような気がしないでもないですが、とりあえずこれで良しとします。

結果は怖いが役に立つ

では新しいモデルでの処理結果をいくつかご紹介します。

f:id:taka_say:20161220004034g:plain
f:id:taka_say:20161220004038g:plain

動画中の顔データを使っているので当たり前といえば当たり前ですが、ある程度求めていた処理が行われているように思います。

では、全く異なる動画中の顔を正しく判別することはできるでしょうか。
検証のため、予告動画に対して同様の処理を試みます。

  • 多少うまくいっているパターン f:id:taka_say:20161220010442g:plain

  • ダメダメなパターン f:id:taka_say:20161220010457g:plain

上手く判別が行えている箇所も散見されましたが、上出来というには程遠い状態でした。
やはり顔データの学習画像が少なすぎるかつ似たパターンばかりという問題は大きいようで、きちんとした予測モデルを構築するためには、個々人の顔データを別途集め直す必要がありそうです。

DataAugmentationの訳は分からないが役に立つ

おまけとして、Kerasでお手軽にできるData Augmentation処理をしたモデルも作成してみます。
正直なところ、各パラメータの真偽でどのように扱いが変わるのかが分かりかねるので(勉強不足!)、設定に関してはいい加減です。

元々のソースコードmodel.fit()部分を下記のように書き換えて試みます。

from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.1,
    height_shift_range=0.1,
    horizontal_flip=True)
datagen.fit(X_train)
hist = model.fit_generator(datagen.flow(X_train, Y_train,
                           batch_size=batch_size),
                           samples_per_epoch=X_train.shape[0],
                           nb_epoch=nb_epoch,
                           validation_data=(X_test, Y_test),
                           verbose=1,
                           callbacks=[checkpointer])

先程上手く判別できていなかった箇所はこのようになりました。

f:id:taka_say:20161220011333g:plain

おおよそ正しく判別できています。
このシーンだけに着目するとかなり改善されたように思われますが、実際には誤判別も増えていたので、今回の場合はrecall増えてprecision減ったという印象でした。

逃げるは恥だが役に立つ

前編から引き続き、判別モデルの問題点解消に取り組みました。

当初の目的として掲げた、「恋ダンスでただガッキーだけを見ていたい」という目標はいくらか達成されたように思います。
記事中の検証用gifでは顔検出の枠線が多すぎてかなり気が散りますが、顔の上書きだけを行った動画を見ると、それなりに快適度がアップしており、個人的には満足しております。

f:id:taka_say:20161220012825g:plain

とはいえ、画像収集に実施した手法(動画からの切り出し!)がイマイチだったため、モデルの汎用性にはかなりの問題が見られました。
学習に使用するデータの重要性が再認識できたように思います。


ガッキーかわいい!

参考は別サイトだが役に立つ

モチベーション

新垣結衣 | アーティスト | レプロエンタテインメント
ご注文はDeep Learningですか? - kivantium活動日記

Keras

Keras Documentation
Are there any codes for AlexNet, ZF Net, GoogLeNet, VGGNet in Keras · Issue #1568 · fchollet/keras · GitHub
Error when checking model target: expected activation_2 to have shape (None, 10) but got array with shape (3, 1) · Issue #3109 · fchollet/keras · GitHub
Kerasでアニメキャラの顔認識 - Qiita
続・深層学習でアニメ顔を分類する with Keras - Qiita

動画

Python + OpenCV で雑コラ動画を作成する③ 雑コラ動画作成 - Qiita
overlay a smaller image on a larger image python OpenCv - Stack Overflow

Gif

PicGIF Lite を Mac App Store で

【逃げ恥】恋ダンスでただガッキーだけを見ていたかったのでディープラーニングする(前編)

ガッキー視聴を毎週の楽しみにしている方も多いのではないでしょうか。
かくいう私もその一人です。

現代社会の荒波に揉まれては心が荒む日々。
灼熱地獄の砂漠に与えられた一縷の望み、オアシス。
私にとって、恋ダンスを踊るガッキーとはそのような存在といえるでしょう。

そういうわけで、踊っているガッキーを毎日のように眺めているわけですが、ここで一つ不都合があります。
そう、ギャラリーが多すぎるということ。

私はただガッキーだけを見ていたいのですが、残念ながらそうは問屋が卸しません。 他の方々が踊っていたり、最悪の場合、ガッキーが全く画面に出ていないタイミングもあります(何を考えて編集しているんだ……)。

というわけで、オアシスの水を濾過するが如く、ガッキー以外の登場人物には退場願うこととします。

作業の流れとしては、

  1. ガッキーの顔かそうでないかを二値分類する判別機をつくる
  2. ガッキー以外の人の顔をガッキーで上書きする

という感じになります。
随分と前口上が長くなりましたが、早速取り掛かります。


書くのは恥だが役に立つ

……はず。

注意は頭にくるが役に立つ

  • 記載しているソースコードは、公開用に一部修正して載せているので、もしかすると動かない場合があるかもしれません。
  • ディープラーニングおよび画像処理に関しては全くのド素人なので、誤った記述が含まれている恐れがあります。

環境は人それぞれ違うが役に立つ

OpenCV3の導入は面倒だが役に立つ

下記記事を参考にOpenCV3をPythonから使用できるように準備します。

Mac OS X で OpenCV 3 + Python 2/3 の開発環境を整備する方法 – ymyzk’s blog

自分の環境下では、2016年12月の段階で

brew install opencv3 --with-python3

の実行時にエラーが発生していました。
もし同様の症状であれば、下記記事の解決策で解消できるはずです。

【macOS Sierra】OpenCV 3をbrewでインストールできない - プログラムは、用いる言葉の選択で決まる

ともかく、最終的にOpenCV3をインポートしてバージョンが確認できればOKです。

$ python
>>> import cv2
>>> cv2.__version__
'3.1.0-dev'

容量は食うが役に立つ

何はともあれ、学習に使用するガッキーのお顔が無ければ始まりません。
画像収集の方法はいくつか考えられるかと思いますが、今回はTumblr の投稿の中から、新垣結衣のタグがついた画像付きの投稿を収集することにしました。

ちなみに、Tumblrにどんな画像が上がっているのか、というのは実際に見に行けばよく分かりますので、リンクを置いておきます。

https://www.tumblr.com/search/新垣結衣

見始めると一日が終わってしまうので気をつけてください。

実際の画像収集ですが、下記の2ステップで行いました。

  1. 投稿されている画像のURLを保存
  2. URLのリストから画像の保存

画像ダウンロードは並行して行っても問題無いとは思いますが、念のため処理を分けました。

画像URL取得のコードは下記の通りです。
TumblrAPI_KEYが必要なので、各自でご用意ください。
また、requestsを使用するので、pip install requestsも事前に行ってください。

import requests
import time

num = 50
url = 'https://api.tumblr.com/v2/tagged'
payload = {
    'api_key': 'YOUR_API_KEY',
    'tag': '新垣結衣',
    'before': ''
}

photo_urls = []
for i in range(num):
    r = requests.get(url, params=payload)
    r_json = r.json()

    for data in r_json['response']:
        if data['type'] != 'photo':
            continue
        for photo in data['photos']:
            photo_urls.append(photo['original_size']['url'])

    payload['before'] = r_json['response'][len(r_json['response']) - 1]['timestamp']
    time.sleep(1)

with open('photo_urls.txt', 'w') as file:
    for url in photo_urls:
        file.write('%s\n' % url)

一度のリクエストにつき20件の投稿が取得できるので、num = 50でおよそ1000件分の投稿から画像URLを取得したことになります。
次に、生成したURLリストから、実際に画像を取得します。

import requests
import time

photo_urls = []
with open('photo_urls.txt', 'r') as f:
    line = f.readline()
    while line:
        photo_urls.append(line.strip())
        line = f.readline()

for i, url in enumerate(photo_urls):
    extension = url[-3:]
    if extension != 'jpg' and extension != 'png':
        continue
    with open('./TumblrImages/' + str(i) + '.jpg', 'wb') as handler:
        response = requests.get(url)
        if not response.ok:
            continue
        handler.write(response.content)
    time.sleep(1)

URLから順次画像を取得し、TumblrImagesディレクトリに格納します。

ひとまずこれで画像の収集作業は終了です。
自分の場合は、最終的に1931枚の画像が集まりました。

忍耐は辛いが役に立つ

一生懸命収集してきた画像ですが、このままでは顔の特徴データとしては使えません。
なので、各々の画像に対してOpenCVを用いて顔を検出し、顔の範囲を切り出す処理を行います。

import os
import cv2

data_dir_path = './TumblrImages/'
image_list = os.listdir(data_dir_path)  # 恐らくglobで取得した方が良い

front_cascade = cv2.CascadeClassifier('/path/to/your/opencv3/haarcascade_frontalface_default.xml')
count = 0
for i, image_name in enumerate(image_list):
    image = cv2.imread(data_dir_path + image_name)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    frontfaces = front_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5)
    for (x, y, w, h) in frontfaces:
        dst = image[y:y + h, x:x + w]
        cv2.imwrite('./FaceImages/' + str(count) + '.jpg', dst)
        count += 1

detectMultiScaleで顔検出の許容度合を大きくしてしまうと、かなりゴミが紛れ込んでくるので適当なパラメータを設定する必要があります。
余談ですが、個人的にはOpenCVの顔検出の精度は余り高くないように思いました。
前処理等の工夫が必要なのかなあという印象です。

参考:dlib vs OpenCV face detection - YouTube

最終的な切り抜き後の画像は、1839枚でした。

さて、ここまでの作業が終わると、FaceImagesディレクトリはガッキーの顔で満たされているはずです。
しかし残念なことに、OpenCVでの誤検出画像や、別人の顔が一部紛れ込んでいるため、取り除く作業が必要になります。

何か効率的な手段があれば良かったのですが、残念ながら妙案が思い浮かばなかったので、目視で分類します。

import glob
import shutil
import cv2

data_dir_path = './FaceImages/'
image_list = glob.glob(data_dir_path + '*.jpg')

gakkies = []
others = []
for i, path in enumerate(image_list):
    image = cv2.imread(path)
    cv2.imshow('image', image)
    # Left arrow is not gakky: 63234
    # Right arrow is gakky: 63235
    key = 0
    while key != 63234 and key != 63235:
        key = cv2.waitKey(0)
    if key == 63234:
        others.append(path)
    else:
        gakkies.append(path)
    cv2.destroyAllWindows()

for gakky in gakkies:
    shutil.move(gakky, data_dir_path + 'Gakky/.')
for other in others:
    shutil.move(other, data_dir_path + 'Other/.')

一枚ずつ画像を表示しつつ、矢印キーの入力でガッキーとその他を分類します。
最終的に、それぞれGakkyディレクトリとOtherディレクトリに画像を移動します。
分類後の枚数は、ガッキーが887枚、その他が952枚でした。

f:id:taka_say:20161217014050p:plain

分類後は、無事ガッキーの顔でディレクトリが満たされていることを確認できます。
これでようやく学習データの準備が完了です。

NumPyは苦しいが役に立つ

学習を行っていくにあたり、画像データをNumPy配列として保存しておきます。 実際の学習はこのデータを基に行われます。

import os
import glob
import numpy as np
import cv2

data_dir_path = './FaceImages/'
tmp = os.listdir(data_dir_path)
dir_list = sorted([x for x in tmp if os.path.isdir(data_dir_path + x)])

X_data = []
Y_data = []
for i, dir_name in enumerate(dir_list):
    images = glob.glob(data_dir_path + dir_name + '/*.jpg')
    for path in images:
        image = cv2.imread(path)
        image = cv2.resize(image, (64, 64))
        image = image.transpose(2, 0, 1)
        image = image / 255.
        X_data.append(image)
        Y_data.append(i)

X_ary = np.array(X_data)
np.save('X_data.npy', X_ary)
Y_ary = np.array(Y_data)
np.save('Y_data.npy', Y_ary)

X_data.npyが画像データの配列で、Y_data.npyが正解クラスの配列ですね。
画像の適切なサイズが分からなかったので、とりあえず64*64にしています。

Kerasは角だが役に立つ

ここから、本題のKerasを使った深層学習に入っていきます。
応用するだけのスキルも知識も無いので、Kerasドキュメントの公式に掲載されているVGG風CNNのモデル構造を微修正しながら利用させて貰います。

Kerasでは、TensorFlowをバックエンドで使用します。
今回はGPUを使わないので、インストールはシンプルです。
ついでにいくつかの必要なパッケージも入れてしまいましょう。

$ pip install scikit-learn scipy pandas matplotlib
$ pip install tensorflow keras h5py
$ python
>>> import keras
Using TensorFlow backend.

このようにバックエンドがTensorFlowになっていることが確認できれば準備完了です。

では実際に学習を行います。

from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.callbacks import ModelCheckpoint
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

# 学習データの用意
X_data = np.load('X_data.npy')
Y_data = np.load('Y_data.npy')
X_train, X_test, Y_train, Y_test = train_test_split(
    X_data, Y_data, test_size=0.15, random_state=42)

# オプション指定
batch_size = 32
nb_classes = 2
nb_epoch = 50

# モデル定義
model = Sequential()

model.add(Convolution2D(32, 3, 3, border_mode='same', input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Convolution2D(32, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Convolution2D(64, 3, 3, border_mode='same'))
model.add(Activation('relu'))
model.add(Convolution2D(64, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256))
model.add(Activation('relu'))
model.add(Dropout(0.5))

model.add(Dense(nb_classes))
model.add(Activation('softmax'))

sgd = SGD(lr=2e-2, momentum=0.9, decay=0.0, nesterov=True)
model.compile(loss='sparse_categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
checkpointer = ModelCheckpoint(filepath="./Models/gakky_face_model.hdf5", verbose=1, save_best_only=True)
# モデル学習
hist = model.fit(X_train, Y_train,
                 batch_size=batch_size,
                 nb_epoch=nb_epoch,
                 validation_data=(X_test, Y_test),
                 shuffle=True,
                 verbose=1,
                 callbacks=[checkpointer])

# 学習経過のプロット
plt.style.use("ggplot")
df = pd.DataFrame(hist.history)
df.index += 1
df.index.name = "epoch"
df[["acc", "val_acc"]].plot(linewidth=2)
plt.savefig("acc_history.pdf")
df[["loss", "val_loss"]].plot(linewidth=2)
plt.savefig("loss_history.pdf")

もし、

Exception: Error when checking model target: expected activation_ to have shape

というようなエラーが出ている場合、~/.keras/keras.jsonを開き、"image_dim_ordering": "th"と修正すると上手くいくかもしれません。 バックエンドがTensorFlowなのかTheanoなのか云々で発生する問題らしいですが、詳細は分かりません。

コードの方では、モデルを定義し、学習させ、最後にmodel.fitの返り値のhistory情報を使って、学習経過をPDFで保管しています。
学習時のcallbackModelCheckpointを指定することで、全エポック中で最も良い指標になったモデルを自動的に保存するように設定しました。
保存先のディレクトリは事前に用意していないとエラーになるのでご注意ください。

ちなみに、学習経過はこのようになりました。

f:id:taka_say:20161218214831p:plain f:id:taka_say:20161218214840p:plain

今回は第20epochが最も優秀だったようです。

人工知能は人類最悪にして最後の発明だが役に立つ

モデルが完成したので、試しに対話環境でロードして遊んでみます。

$ python
>>> from keras.models import load_model
Using TensorFlow backend.
>>> import numpy as np
>>> import cv2
>>> model = load_model('./Models/gakky_face_model.hdf5')
>>> def predict(path):
...     image = cv2.imread(path)
...     image = cv2.resize(image, (64, 64))
...     image = image.transpose(2, 0, 1)
...     image = image / 255.
...     image = image.reshape(1, 3, 64, 64)
...     print(model.predict(np.array(image)))
>>> predict('gakky.png')
[[ 0.96610594  0.03389405]]
>>> predict('yama.png')
[[ 0.21037847  0.78962159]]

f:id:taka_say:20161218152423p:plain:w64:h64 f:id:taka_say:20161218152427p:plain:w64:h64

上記のような適当に用意したガッキーの顔画像とSierraの山並み(一部!)に対して予測すると、確かに結果は正しそうです。

動画処理は重いが役に立つ

ここからは、用意したモデルを使用しながら、実際に恋ダンス動画の加工処理を行います。
とりあえず恋ダンスの動画が必要なので、TBSの公式Youtubeから頂戴します。

TBS公式 YouTuboo - YouTube

冒頭から述べている通り、判別機がガッキーの顔だと判断すればそのまま、その他の顔だと判断すれば下記のガッキーフェイスで上書きします。

f:id:taka_say:20161218190110p:plain

動画加工のためのコードは下記の通りです。

import cv2
import datetime
import numpy as np
from keras.models import load_model

# 動画関係準備
target = 'input.mp4'  # 恋ダンス動画
result = 'output.m4v'
movie = cv2.VideoCapture(target)
fps = movie.get(cv2.CAP_PROP_FPS)
height = movie.get(cv2.CAP_PROP_FRAME_HEIGHT)
width = movie.get(cv2.CAP_PROP_FRAME_WIDTH)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
out = cv2.VideoWriter(result, int(fourcc), fps, (int(width), int(height)))

# 顔認識準備
model = load_model('./Models/gakky_face_model.hdf5')
cascade_path = '/path/to/your/opencv3/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
gakky_n = 0  # モデル予測時のクラス番号

# 書き込み画像準備
ol_imgae_path = "gakky.png"
ol_image = cv2.imread(ol_imgae_path)

# 各フレームへの処理
if movie.isOpened() is True:
    ret, frame = movie.read()
    f_h, f_w = frame.shape[:2]
else:
    ret = False
while ret:
    # 顔検出
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    frontfaces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=2)

    # 顔認識
    for (x, y, w, h) in frontfaces:
        dst = frame[y:y + h, x:x + w]
        image = cv2.resize(dst, (64, 64))
        image = image.transpose(2, 0, 1)
        image = image / 255.
        image = image.reshape(1, 3, 64, 64)
        face_class = model.predict_classes(np.array(image), verbose=0)
        # ガッキーじゃなければ上書き
        if face_class != gakky_n:
            resized = cv2.resize(ol_image, (h, w))
            frame[y:y + h, x:x + w] = resized
        cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)

    # フレーム書き込み
    out.write(frame)
    ret, frame = movie.read()
    # 50フレームごとに経過を出力
    if movie.get(cv2.CAP_PROP_POS_FRAMES) % 50 == 0:
        print(datetime.datetime.now().strftime('%H:%M:%S'),
              '現在フレーム数:' + str(int(movie.get(cv2.CAP_PROP_POS_FRAMES))))

    # 途中で終了する場合コメントイン
    # if movie.get(cv2.CAP_PROP_POS_FRAMES) > 500:
    #     break

検証段階では、分かりやすいように顔検出したエリアに枠を描写しています。
つまり、枠のみは判別機がガッキーだと判断したエリアで、枠+ガッキーは判別機がガッキーでないと判断したエリアです。

実際の処理結果はこのようになります。

f:id:taka_say:20161218185808g:plain

なんということでしょう。
顔の誤検出が多すぎて酷い仕上がりです。
更にいうと、他の人の顔をガッキーに変換するという当初の目的すら達成できていません。

これは、ガッキーの顔かそうでないかという二値分類ではなく、顔っぽいかそうでないかという形で学習をしてしまったからだと考えられます。
冷静に考えれば当たり前ですね。

まとめは雑だが役に立つ

この記事では、ガッキーの顔画像を収集し、分類し、学習し、動画を加工するまでの過程を記載しました。 残念ながら芳しい結果は伴いませんでしたが、問題の原因は明らかなように思われます。
というわけで、その辺りの問題を解消していきたいのですが、だいぶ長くなってしまったので、続きは後編で。

taka-say.hateblo.jp

参考は別サイトだが役に立つ

モチベーション

新垣結衣 | アーティスト | レプロエンタテインメント
ご注文はDeep Learningですか? - kivantium活動日記

Keras

Keras Documentation
Are there any codes for AlexNet, ZF Net, GoogLeNet, VGGNet in Keras · Issue #1568 · fchollet/keras · GitHub
Error when checking model target: expected activation_2 to have shape (None, 10) but got array with shape (3, 1) · Issue #3109 · fchollet/keras · GitHub
Kerasでアニメキャラの顔認識 - Qiita
続・深層学習でアニメ顔を分類する with Keras - Qiita

動画

Python + OpenCV で雑コラ動画を作成する③ 雑コラ動画作成 - Qiita
overlay a smaller image on a larger image python OpenCv - Stack Overflow

Gif

PicGIF Lite を Mac App Store で

brewでインストールしたOpenCV 3をpyenvのvirtualenv環境で使う

brewOpenCV 3を入れた場合、pyenvで作成したvirtualenv環境では、デフォルトでimport cv2ができない。
numpyのインストールと、OpenCV 3へのシンボリックリンク作成が必要なようなのでメモ。

環境

手順

まずはOpenCV 3を使いたいpythonのvirtualenv環境を有効化する。
便宜的に、環境作成からの手順を載せる。

$ pyenv virtualenv 3.5.2 opencv-test
$ pyenv local opencv-test

環境を有効化したら、pipでnumpyを導入する。

$ pip install numpy

次にbrewでインストールされたOpenCV 3の場所を確認する。
恐らくは下記辺りに落ちているはず。
インストール時のバージョン等によって細部は異なる。

/usr/local/Cellar/opencv3/3.0.0/lib/python3.4/site-packages/cv2.so

自分の場合はHEADからインストールを行ったので、下記のようになった。

/usr/local/Cellar/opencv3/HEAD-300f923_4/lib/python3.5/site-packages/cv2.cpython-35m-darwin.so

ここで、おもむろにPythonの対話型シェルを起動し、virtualenv環境のsite-packagesの場所を調べる。

$ python
>>> import site; site.getsitepackages()
['/Users/hoge/.pyenv/versions/opencv-test/lib/python3.5/site-packages']

恐らく上記のような感じで表示されるはず。
上記フォルダに移動し、OpenCV 3へのシンボリックリンクを生成する。

$ cd /Users/hoge/.pyenv/versions/opencv-test/lib/python3.5/site-packages
$ ln -s /usr/local/Cellar/opencv3/HEAD-300f923_4/lib/python3.5/site-packages/cv2.cpython-35m-darwin.so ./

最後に、きちんとインポートできるかおよびOpenCVのバージョンを確認する。
virtualenvの環境が有効な箇所に移動し、Pythonの対話型シェルを起動。

$ python
>>> import cv2
>>> cv2.__version__
'3.1.0-dev'

これでOpenCV 3が、pyenvのvirtualenv環境で使用できるようになった。

参照

Mac OS X で OpenCV 3 + Python 2/3 の開発環境を整備する方法 – ymyzk’s blog
site-packagesの場所を確認する - プログラムは、用いる言葉の選択で決まる

【macOS Sierra】OpenCV 3をbrewでインストールできない

よくある手順通りに進めていたところインストールでだいぶ躓いたのでメモ。

環境

  • macOS Sierra 10.12.1
  • Homebrew 1.1.2
  • OpenCV stable 3.1.0(結果的に入ったやつ)

問題

Python3で使用したかったので、追加のオプションを入れつつインストール命令実行。

$ brew install opencv3 --with-python3

実行の結果出てきたエラーっぽいやつら。

/tmp/opencv3-20161207-28854-41wt3t/opencv-3.1.0/modules/videoio/src/cap_qtkit.mm:46:9: fatal error: 'QTKit/QTKit.h' file not found
#import <QTKit/QTKit.h>
        ^
1 error generated.
make[2]: *** [modules/videoio/CMakeFiles/opencv_videoio.dir/src/cap_qtkit.mm.o] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [modules/videoio/CMakeFiles/opencv_videoio.dir/all] Error 2
make: *** [all] Error 2

READ THIS: https://git.io/brew-troubleshooting

These open issues may also help:
opencv3 missing xz dependency in linuxbrew https://github.com/Homebrew/homebrew-science/issues/2642
opencv3: fix build with vtk https://github.com/Homebrew/homebrew-science/pull/3749
opencv3 python3 wrappers can not be generated https://github.com/Homebrew/homebrew-science/issues/3302
opencv3 misconfigures cmake because brew incorrectly sets $PYTHONPATH https://github.com/Homebrew/homebrew-science/issues/3329
OpenCV and OpenCV3 fail to build with ximea camera support https://github.com/Homebrew/homebrew-science/issues/3395
import cv2 fails and cannot find module once OpenCV3 has been installed via homebrew on Mac OS Sierra with xcode 8.1 https://github.com/Homebrew/homebrew-science/issues/4571
opencv3 PYTHON_PATH and other variables set incorrectly in some cases https://github.com/Homebrew/homebrew-science/issues/2846
opencv3 build fails without clear error message on OSX 10.10.5 https://github.com/Homebrew/homebrew-science/issues/3119

エラーメッセージで検索した結果出てきたissueを参考に、オプション追加。

$ brew install --HEAD opencv3 --with-python3

無事成功。
インストール後のbrew infoの結果。

$ brew info opencv3
homebrew/science/opencv3: stable 3.1.0, HEAD [keg-only]
Open source computer vision library, version 3
http://opencv.org/
/usr/local/Cellar/opencv3/HEAD-300f923_4 (286 files, 48.8M)
  Built from source on 2016-12-07 at 18:09:15 with: --with-python3

恐らく最新版では修正済みの期間限定エラーだったのだと思われる。
やめて頂きたい。

参照

Updated PR #7159 (OSX AVFoundation support) by alalek · Pull Request #7266 · opencv/opencv · GitHub

【Django REST framework】viewの中で使用するserializerを変更する

ModelViewSetなんかを使用していると、それぞれのアクション毎にserializerを変更したいことが多々あると思います。
日本語情報が少なくてベストプラクティスなのかどうかは疑わしいですが、クラスベースviewの中でserializerを変更する方法をとりあえずメモしておきます。

環境

Python 3.5.2
Django 1.9.7
djangorestframework 3.4.0

方法

get_serializer_classメソッドをオーバーライドし、アクション毎に異なるserializerを返却するようにします。
以下にlistretrieveで返却するフィールドを変えたserializers.pyviews.pyの簡単なコード例を示します。

serializers.py

class HogeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Hoge
        fields = ('id', 'name')


class HogeRetrieveSerializer(serializers.ModelSerializer):
    class Meta:
        model = Hoge
        fields = '__all__'

views.py

from .serializers import HogeSerializer, HogeRetrieveSerializer

class HogeView(viewsets.ModelViewSet):
    queryset = Hoge.objects.all()

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return HogeRetrieveSerializer
        return HogeSerializer

上記の例ですと、listの時はidとnameカラムのみが返却され、retrieveの時には、モデルの全てのフィールドが返却されます。
このように、get_serializer_classメソッドをオーバーライドすることで、アクションに応じたserializerを設定することができます。
今回はlistretrieveの例でしたが、他のアクションも同様にして、serializerを分けることが可能です。

参考

Generic views - Django REST framework
Django rest framework, use different serializers in the same ModelViewSet-open source projects tomchristie/django-rest-framework
Google グループ

【MySQL】外部キー制約を削除する

制約の設定をしたものの、テストデータの投入段階でinsertを阻止してくる邪魔なforeign keyに悩まされたので、一時的に参照整合性の制約を削除します。

環境

MySQL 5.6

手順

まずは、テーブルに設定されている参照制約を確認しましょう。

mysql> SHOW CREATE TABLE something\G
*************************** 1. row ***************************
       Table: something
Create Table: CREATE TABLE `something` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `another_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `somedb_something_a08cee2d` (`another_id`),
  CONSTRAINT `somedb_another_id_3a4999a1_fk_somedb_another_id` FOREIGN KEY (`another_id`) REFERENCES `another_table` (`id`)
) ENGINE=InnoDB CHARSET=utf8mb4
1 row in set (0.00 sec)

上記の結果を見ると、somethingテーブルの外部キーanother_idが、another_tableidに外部キーとして制約が設定されています。
これを削除するには、CONSTRAINTの次に書いてある文字列を指定して下記のコマンドを実行します。

mysql> ALTER TABLE something DROP FOREIGN KEY `somedb_another_id_3a4999a1_fk_somedb_another_id`;

もう一度SHOW CREATE TABLEを実行すると、外部キー制約の項がなくなっていることが確認できます。

参照

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.1.17.2 外部キー制約の使用