Pythonで雪が降ってたらLINEに通知を出してみる その2 特徴点による比較
年が明け、だいぶ時間がたってしまいましたが、年明け早々に雪が降りましたので、前回の続きで特徴点マッチングを試していきます。当初の方針は、雪のない夜の写真と一時間ごとに更新される写真とで特徴点による比較をし、雪が積もれば差異が大きくなるので、それを検知してラインに通知を送るというものです。
基本的にはOpenCVを利用しました。
参考にさせていただいたサイトはこのあたりです。
ここで当初から方向転換がありまして、一度雪が降ると通知が止まらなくなってしまい、うるさいことになってしまいました。
ですので、一時間前に取得した写真との比較をメインにしていきたいと思います。一時時間前と差が大きい写真が取れた場合に、ラインに通知を送ります。
ここで、事前に画像処理をかけたほうが検出の頻度が上がるのではないかと考え、いくつかの処理を試し、それぞれの結果をまとめてみました。
値が小さいほど画像の類似度が高く、値が高いほど画像がの類似度はひくくなります。マッチングのアルゴリズムはAKAZEを使用しています。
一時間前との比較なので、17時は16時との差異を表しています。
この日は、2時から朝6時まで雪が降り続きました。表の赤字の部分です。その前提で考えると、画像処理をしない「無変換」でのマッチングがけっこういけてる気がします。まあ夜間はほぼ色がない写真となっているわけで、実質グレースケールとほぼ同じですが。
18時と朝7時で値が高くなっているのは、日が沈んで暗くなった時間と、日が昇って明るくなった時間です。こう考えると、やはり通知は夜だけに絞ったほうが精度が上がりそうです。
それから、上記のサイトを参考に明るさを正規化した画像を使って特徴点を計算した時に、検出できずにdivision zeroのエラーが良く出ました。この辺りはもとの画像に由来するのかもしれませんが(夜の写真なので正規化するとコントラストも弱くのっぺりした画像になってしまう)、他で使う時のためにちょっと覚えておこう。
無変換での写真の移り変わりと、特徴点の差を示すとこんな感じです。あ、今回の写真はあくまで例です。
時間 17:00
1時間前との差異 88
明るいせいで、細かいところまで見れるので、雪が降っていなくても一時間前と違いを検出しやすくなっているんじゃないでしょうか。
時間 18:00
1時間前との差異 130
まあ目で見ても、暗くなったため細部が見えなくなってますし、差が大きいのがわかります。
以降3時まであまり変化がありません。
時間 3:00
1時間前との差異 99
雪がうっすらつもり出しました。雪のせいで全体的に白っぽくなり、違いを検出できているようです。
こんな感じで、大体70くらいを閾値にすればいいんじゃなかろうか、ってことで実際に運用しているコードが次のものになります。画像の前処理をいろいろ試したときのコードはまた別の記事にしよう。ともかく、これで二つの画像の特徴点間の距離=差異の度合い、が出力される訳です。この理解であっているか、自信はないけど。
あとはもう少し雪が降れば、試行錯誤して精度を上げられそうなんですが、今年は雪が少ないですねー。
Pythonで雪が降ってたらLINEに通知を出してみる その1 Twitterからの画像の取得
冬ですね。突然ですが私が住んでいるのは長野県です。長野県の冬といえば、困るのが雪です。朝起きて、さて出勤しようかとドアを開けたとき、雪が積もっていればそこで遅刻確定です。なにしろいつもギリギリで生きていますので。
まあそんな雪国ですので、以前とある場所にウェブカメラを設置しました。職場から遠い人でも、職場の様子がわかるように、です。
雪が降ったら雪かきにこい、ということなのです。
これはなかなかいいものでした。布団の中にいながら、職場の様子がわかるので。
ですが、たまにうっかりしてしまうんですよね。天気予報が雪と言っていないのに降るときもあるからです。そして、能動的に確認をしなければいけないのです。今年も、12月の中頃に少しだけ雪が降ってひやりとしました。幸いなことに、遅刻はせずに済みましたけれども。
ただこういう本質的でないところに神経を使って生活するのは、好きなことではありません。ですので、最近かじっているPythonを使って、雪が振っていそうならLINEに通知が来るようにしてみました。
前提条件
既設のウェブカメラですが、RaspberrypiとUSBカメラでできています。1時間ごとに撮影しTwitterへ撮影時刻、撮影場所等のテキストをくっつけて投稿しています。いろんな場所におき、一つのアカウントからtweetしています。
処理の流れ
今回の流れですが、次のようになりました。
3番ですが、比較するのは夜のみです。昼間雪が降ってもそりゃさすがにわかりますし。出勤前日の夜に通知がくれば心に余裕ができますし、降り始めてからの通知でも同様です。あとは、昼間の写真だと情報量が多すぎてちょっと処理がめんどくさそうというのもあります。雲の違いとかも拾ってしまいそうで。
Twitterからの画像の取得
今回は、まず処理1のTwitterからの画像の取得についてです。例によって例のごとく、先人の方々の知恵もお借りします。
Raspberrypiから写真をtweetする際にもお世話になりました。まあ今回はその逆で、tweetから写真をダウンロードしたい訳なのです。まずはtweetから写真のリンクを抜き出してみます。
あんまりリファレンスとかと異なるところはないのですが、今回はTwitterのユーザー、tweetする時間、内容も以前自分で設定したものに合わせているので、汎用性の低い内かもしれません。
まずは、ユーザーIDを指定し、countで取得するtweetの数を指定します。複数の機器からtweetしてるのですが、時間がも若干異なる設定になっており、今回は3つも取得すればその中に1つ目的のtweetがはいっているのです。
tweetはjsonとして取得できるので、その中から必要なものを取り出します。が、いらないtweetもあるので、取得した3つのtweetの中から、お目当てのものを抜き出します。それには、tweetに含まれる撮影場所に欲しい場所が含まれているかで判断します。
これで写真のURLが取得できたので、ダウンロードするだけです。
今回は単純に晴れた日の写真と比較して、雪が降っているかを判別できればいいので、取得した写真を取っておく必要はありません。ただ、検知率が低ければ別の方法を考えなければいけないので、その時のために写真は個別に保存することにします。
名前が被らないように取得時の月日時間.jpgみたいにして保存させました。検出率が低かったり過剰に検出しちゃうようなら、こうやって集めたデータを使って今度こそ機械学習に進むべきなのかもしれません。
まとめると、こんなコードで運用しています。
現在仮動作中です。あいにくと今夜は雨でしたが、一応晴れとは違うということで通知はバンバンきています。本当に雪になった時にも、ちゃんと通知がきてくれるといいのですが。こればかりは天候なので、ゆっくり待つしか……あれ?雪が若干楽しみになってきたような??
Splatoon2 の戦績をPythonでGoogle Spread Sheetに保存する
だいぶ回り道をしたせいで、時間がけっこうかかりました。こんなことをやっている時間でゲームをしたほうがウデマエも上がるんじゃないかって気がします。
それはそれとして、Googleスプレッドシートにスプラトゥーン2の戦績を記録しはじめました。
長くて見づらくなってしまいましたので、改行しているときは\で明示的にしめしてあります。
コードの中の'GoogleAPI認証用json'は、GoogleのスプレッドシートAPIを有効化した時に作成した認証情報が含まれたjsonファイルです。Pythonのファイルと同じ場所に置いてあります。
'スプレッドシートID'は事前に作成したスプレッドシートのURLに表示されたd/の後から/までの文字列です。
iksm_sessionの値はプロキシなり、端末内に保存されたcookieなり、から読み取った値です。
まずは、事前に作成したスプレッドシートのbattle_Numberの列(今回はA列に記録してあります)の最大値を取得します。この最大値より大きいbattle_Numberの戦績は、シートに記載されていない新しい戦績ということになるのです。次の関数の部分ですね。
次に戦績の概要を取得します。
"https://app.splatoon2.nintendo.net/api/results"からは、戦績の概要がjsonとして取得できます。jsonの中は、一戦ごとにbattle_numberが振られたresultsに分かれています。まずはそのbattle_numberから新しい記録かどうかを判別したいので、取得したjsonを引数に、次の関数に移ります
この中で、maxBattleNumber()で取得した、スプレッドシートに記録されたbattle_numberの最大値と比較しています。その結果、まだスプレッドシートに記載されていない戦績であれば、
"https://app.splatoon2.nintendo.net/api/results/" + result["battle_number"]
からさらに詳細の戦績を取得します。
詳細の戦績を取得後は、記録したい要素をいったん変数にいれてから配列にまとめ、スプレッドシートに書き込みます。一件ずつ書き込まないのはGoogleのAPIの制限が100秒間に100件までとなっているからですね。
バトル一回分のデータを書き込んだら、次のバトルのデータを取得するまで5秒の待機をさせています。もともと公開されていないAPIを叩いているので、できるだけ負荷はかけないような形にしたかったのです。どうせ自動的にデータを集めるので、そこにきて多少の待ち時間が発生しても、運用上は問題ないですし。
反省点として、スプレッドシートへの書き込みは、それだけで一つの関数にまとめたほうがわかりやすかった気がします。まあでも動いているのでとりあえずはいいか。
これでしばらくはゲームに専念してデータをためることにします!
遊べるぞー!
イカリング2の情報収集準備2
iksm_sessionのほうはいったんおいておきまして、早々に情報収集へと切り替えていきます。
もう3か月も前になりますが、iksm_sessionがわかっていればPythonに戦績データをjson形式でダウンロードさせられるようになりました。ただこのままだと一戦ごとにバラバラのファイルになってしまいます。
本当はデータベース作ってその中に追記していくのがいいのかもしれません。ですが、まとめたデータを目で見てみたい気もします。そんな訳で、とりあえずはGoogleスプレッドシートに集めたデータを追記していくという方針にしました。
基本的なやり方はこちらを参照。
ここまでは、サンプルどおりに躓かずにやってこれました。取得した戦績の各要素を地道に書き込んでいけば結果は得られそうです。どうせ頑張るのは自分でなくてパソコンですし。
が、ここでGoogleのAPIの制限が問題になってしまいました。
Usage Limits | Sheets API | Google Developers
リンク先を確認すると、100秒間に100リクエストまでとのことです。
ガチバトルに関係する要素は、単純にユーザーごとのキル数、デス数、ブキ種類だけでも3要素×8プレーヤーで24リクエストにもなってしまいます。きちんと多くの要素を登録するには、1戦ごとのデータはまとめて書き込んでしまいたいところです。
参考にしたサンプルではgspreadというパッケージを利用していましたので、情報を求めてリファレンスを確認してみます。
gspread — gspread 3.1.0 documentation
このままCell.valueに値を入れると、rangeで指定した範囲にたしかにデータが入りますし、APIの使用状況をみても回数は1回とカウントされていそうです。なんですが、範囲内のセルが全部同じ値になってしまうんですよね。配列つくってそれを入力とかできればいいのですが。そもそもサンプルの'O_o'ってなんでしょう……hogeとかfugaみたいな?
いまいちわからなかったので、次はGoogleのスプレッドシートのAPIリファレンスを確認です。
Basic Writing | Sheets API | Google Developers
読んでみると異なる値を配列に入れて、一括で更新できそうです。
結局gspreadは使わなそうかな。
このvaluesの部分に、jsonから抜き出してきた結果をいれてあげればよさそうです。めどが立ちましたので、いったん入れ物となるスプレッドシートを作成しました。jsonに含まれているデータで、記録するものは次のとおりとします。
まずは全体的な項目として、ガチマッチのルール、ガチパワー、ステージ、勝敗、チームごとの進めたカウント、かかった時間。
これに各プレーヤーごとにウデマエ、ブキ、サブ、スペシャルの種類、スペシャル使用回数、キル数、アシスト数、デス数、塗ポイントを付け加えます。
プレーヤーごとのIDも面白そうなので追加し、ユニークなキーになってるっぽいbattle_numberで管理することにしました。
あとは頑張ってコードを書いていくだけです。
iksm_sessionを自動更新したい その4
スプラトゥーン2、ウデマエが1上がっては2下がり、2上がっては1下がる。結局平均は同じ、といった泥沼状態になっています。
この状況を打破するためにも、キャプチャボードを買って動画を撮影し、そこから学んでいく必要があるかもしれません。
なんてことを考えながら、ふらふらとネットをさ迷っていたところ、こんなブログが目に入りました。
自分でデータを取得しなくても、stat.inkから引っ張ってこれそうです。いや、その前に本文の中で、mitmproxyを使って普通にiksm_sessionを抜き取ってますね。記事も新しいし。
前回、root化した端末のプレインストールされた証明書に、mitmproxyの証明書を入れることで、Android7以降の端末でも不完全ながら通信内容の取得はできました。
と、いうことはです。Android7以降のセキュリティ強化が影響して、任天堂アプリの通信内容を取得できないという考えはそんなに的外れではないんじゃないでしょうか。それならより古いバージョンのAndroid端末を使うとすべて解決するのでは??
そんな訳で古いNexus5を引っ張り出してきて使ってみると、ちゃんと通信内容を取得できました。……今まで無駄に遠回りしてきていたんですね。ここでようやくトークンfの生成を考えるところまでたどり着けました。端末がなくてもエミュレータ使えばなんとかなりそうですね。
あと、root化した端末で任天堂アプリを使うためにMagiskをいれていましたが、証明書関連のツールもあったので覚書としてはっておきましょう。そのうち使うこともあるかもしれません。
iksm_sessionも楽に再取得できるようになったので、戦績のデータ解析も並行して進めていきましょう。このままトークンfにこだわってしまうと、本題になかなかたどりつけなくなってしまいそうですから。
iksm_sessionを自動更新したい その3
だいぶ時間がたってしまいました。記事のタイトルはその3となっていますが、そろそろギブアップしそうです。ざっくり今までの流れをまとめると、こうです。
- アプリの通信内容からiksm_sessionというcookieの値を取り出すと、戦績その他をデータで取得できるぞ。
- データ取得から整理までPythonで処理してみよう。
- iksm_sessionはどうやら定期的に更新されるみたいなので、その更新までPythonにいれこんでみよう。
- アプリ内で計算しているトークンfの値が、iksm_session更新に必要みたいだ。
- アプリを解析してみようか……ってところで更新があり、アプリの通信内容の確認ができなくなってしまった。
さて、まずは何をするにも通信内容の確認が必要です。
過去にたてた仮説をもとに、試行錯誤をしていきます。
アプリが更新されて以降、root化された端末では起動できなくなってしまいましたが、まずはそこを回避しなければなりません。
ここはアプリごとにroot化を隠す機能があるroot管理アプリを導入して回避しました。
次に通信内容を確認するためにたてたプロキシの証明書を、AndroidのプレインストールのCA証明書と同じ場所に入れました。
pem形式の証明書をこちらの記事を参考にリネームし、既定の場所に保存しました。
どうやらちゃんと動いているようで、アプリの通信が確認できるように……できるように……あれ?
確認できなくはないのですが、任天堂アプリの立ち上げ、ログインまではキャプチャできてるのですが、スプラトゥーン2の戦績の取得は途中で通信が止まってしまいできないみたいです。
このまま頑張るか、画像解析のほうに早々に切り替えるか、悩ましいところなのです。
気が付いたらGoPro HERO 2018が手元にあった話
気付いたら、GoPro HERO 2018が手元にありました。ただし、購入後すぐにGoProHERO7が出るという、Fitbit Versaを買ったときと同じ状態にはなってしまいましたが……。
GoProを買ってから登山に一回、ロングライドに二回、マラソン大会に一回、その他ちょっとしたお出かけに数回使ってみたのですが、初心者用アクションカメラとしてはすごくいいものでした。お値段も7の半分ほどですし、公式サイトから購入すると32GBのマイクロSDがついてくるのです。
すばらしくお買い得!
使い始めて一番実感したのは、持ち出すハードルの低さです。コンパクトデジカメよりはるかに気軽に使えるので、使用頻度がめちゃくちゃ上がりました。今のGoProはハウジングなしで防水になってるので、天候や使うシチュエーションを気にせずに気軽に持ち運べるのが大きいですね。あとはカメラだと「出して撮ってしまう」、という作業が発生しますが、アクションカメラは基本的にマウントに固定してるので、「録画ボタンを押す」というワンアクションで使えてしまいます。
その他にも、写真よりも見返す頻度があがりました。これは人によるのかもしれませんが、写真だと撮ってちょっと編集してSNSにあげて終わりでした。動画だと、撮影した時に会話も一緒に記録されるので、その時の空気がよりリアルに感じられます。
その分デメリットもあり、気軽に取れるためファイルが増えますし、動画は編集しないと面白みが少ないため写真に比べて作業時間が膨大になってきています。まだ一本目の動画すらできあがっていないんです。買ってから1か月以上たっているのに。
もう少し、本腰をいれて動画に取り組まねば。
スプラトゥーンの解析とかも手があまりついてないですし、時間が、時間が足りません。