pixi.js+netliftyで画像ジェネレーターを作った
今年の3月ぐらいに「pavicon maker」というwebアプリを作りました。
簡単にいうと画像加工アプリみたいな感じのものです。
ちょっとした、ファンアプリだと思ってもらえばいいです。
この記事ではその経緯とどう実装したのかを解説します。
何をするものなのか?
現在「けものフレンズ ぱびりおん」というゲームがリリースキャンペーンで、twitterのアイコンを期間限定で公開していました。
その後ファンアートでアイコン化されなかったフレンズのアイコンを作っている方がいて、それにインスパイアされてアイコンの中心のキャラ以外を作成するwebアプリを作ろうとしました。
キャラの作成はちょっと無理そうなので諦めました。
画力があればモンタージュ形式でキャラが作れるアプリでも作ろうかと考えていましたが、まあ無理ですよねw
ちなみに、ぱびりおんのアイコンということとfaviconという言葉があるので、それを文字って「pavicon」という名前にしました。
作成方法
アプリ部分
今回のアプリ作成で一番困難だったのは、どうやってアウトラインを作成するかでした。
画像処理ということでcanvasで加工して、UIは普通のhtmlなどの要素で構成しました。
モジュールハンドラー
今回はfuseboxを使いました。
ちょっと小耳の挟んでたので気になったので使ってみました。
個人的には中々使いやすくよかったです。
公式のドキュメントも十分ありますし、デフォルトでプラグインもいくつかあるので使う上ではあまり問題はありませんでした。
また、fuse.js
にタスクを書けるのが非常に便利でした。
webpackより取っ付きやすかったです。
時間がある時にもう少し使ってみたいと思います。
カラーピッカー
カラーピッカーは jscolor
というライブラリを使用しました。
jscolor - JavaScript Color Picker (Palette) with touch support
このライブラリの性質上、 head
で読み込む必要があるので、npm
で管理せずに head
で読み込ませています。
本当はひとまとめに出来ればいいのですが、自分の理想となる動きが出来るものがこれしか見つからなかったので…
また、コード内でカラーコードとrgbのパラメーターに変換するのに tinycolor2
というライブラリを使いました。
canvas
画像加工部分に canvas
を使いました。
ライブラリは pixi.js
を使いました。
pixi.js
を使たのはアウトラインを描画できるて、実装が楽なのが調べた中では pixi.js
しかなかったからです。
他のライブラリだと実現できてもかなり処理が重くあまり実用的ではありませんでした。
アウトラインはデフォルトのフィルターがあるのでそれを使いました。
楽ちんです。
アウトラインのフィルタ以外にも、色変えやドロップシャドウもフィルターを使っています。
フィルターを使って他にも色々なことが出来そうなので、時間があるときに触ってみようと思います。
CSS
cssフレームワークは bootstrap
を使っています。
cssのみ使う予定なので head
で bootstrap
のcssのみ読み込んでいます。
出来るだけ bootstrap
のものそのままを使ったのですが、input type="range"
はbootstrap性のものがなかったので下記記事を参考にして自作しました。
ちなみに参考にしたのはline風にやつです。
画像のアップロード
画像はアップロードした画像を Image
クラスとして読み込んで、それを pixi.js
の texter
として読み込んでいます。
コールバックが多くあまりすっきりした形ではないので、手直しして別の機会にそれに関して記事を別途書きたいと思います。
ちなみに読み込んだテクスチャはそのままだと、アウトラインを描画する面が足りなので、一度大きめの container
に addChild
で追加して RenderTexture
で余白が大き目のテクスチャとして書き出し、スプライトとして生成しなおしています。
// アップロードした画像をtextureとして読み込みspriteに貼り付ける const texture = new PIXI.Texture(new PIXI.BaseTexture(image)); const loadSprite = new PIXI.Sprite(texture); loadSprite.anchor.set(0.5); loadSprite.x = 325; loadSprite.y = 350; const r = loadSprite.width / loadSprite.height ; loadSprite.height = 600; loadSprite.width = 600 * r; // 読み込んだ画像のspriteをcontainerに入れる const container = new PIXI.Container(); container.addChild(loadSprite); // render textureを使ってcontainerをtextureにする const rt = PIXI.RenderTexture.create(650, 650); app.renderer.render(container, rt); // containerのtextureを貼り付けたspriteを生成 const largeSprite = PIXI.Sprite.from(rt);
画像のDL
下記の記事を参考に実装しました。
http:// https://st40.xyz/one-run/article/133/
一度キャンバスからbase64のデータ出力して、それをblobデータに変換、そのblobデータをダウンロード出来るURLを生成して、自分でイベント発火させダウンロードしています。
ちょっと回りくどいやり方ですが、他の方法もないみたいなのでこちらの方法で実装しました。
デプロイ部分
デプロイ先にはNetlifyを使用しました。
最近話題なのでご存知の方も多いと思います。
Netlifyは静的ファイルをホスティングするサービスです。
無料でもhttps対応、サブドメインをもらえます。
有料プランだと、カスタムドメインを設定できます。
また、デプロイも自動で行います。
github pagesだとパブリックにしないと使えませんでしたが、Netlifyだとプライベートリポジトリで使えます。
また、github pagesだと手動でビルドしてpushするか、何かしらのサービスを別に使ってビルドする必要があります。
しかし、NetlifyだとNetlify側でビルドしてもらえるので、色々と楽です。
自分は完全に気にって、最近はNetlifyばかり使っています。
まとめ
アウトラインをどうするかで色々なライブラリを探していましたがどれもいまいちで、トライアンドエラーを繰り返し1ヶ月くらいかかってしまったと思います。
次回からはもっと事前に調査して、素早く実装出来るようにして行きたいと思います。
また、pixi.js
の実装が重いらしく、スマホだとかなりカクカクとした動きになってしまいます。
時間があれば pixi.js
をもっと調べてここら辺より最適化ができないか調査したいと思います。
今年の目標
今かよ!感ありますが、今転職を考えていてちょうど自分について振り返るのにいい機会なので書いてみます。
これまで
元々プログラミングはどちらかというと苦手でした。
大学は数学科出身で、プログラミングの授業の課題は友達に聞いていました。
そんな状態からゲーム業界に入り、サーバーをやっている感じです。
MySQL?何それ?おいしいの?状態だったのに関わらず、いきなりローカル環境構築してねといわれ、wikiのURLだけ共有されました。
しかも、その手順書通りやっても正しく動かなくてw
質問しようにも全てがわからない(ターミナルをどう起動するかもわかってない)状態だったので、そもそも何を質問すればいいのか状態でした。
元々新卒受け入れている会社ではなかったのでいきなり出来る前提で来るのも仕方ないですが、ちょっと辛かったです。
それで業務についていく為に家帰ってからも勉強していました。
python、flask、MySQL、Redisなど業務に使うことを徐々に学習して行きました。
その時なんとなく新しいものを覚える際にどうすればいいのかとのコツを掴んだ感じです。
なので、今は自分の興味があることに関して幅広くアンテナを張って色々試している感じです。
興味・これからやっていきたいこと
これまでは仕事で使う物やただ単に興味があることを勉強していました。
それを踏まえて来年について考えていこうと思います。
GCPやkubernetesを使った開発
ここ最近のGCPは勢いがすごくGoogleにしか出来ないクラウドサービスをどんどん出して来ています。
例えばライブマイグレーションやSpannerなどがあげられます。
いかにすごいかは私の拙い説明より、他の方の記事を読んだ方がわかりやすいと思います。
またGCPはk8sを用いた開発に適しているのも魅力的なポイントです。
今後のサーバーアプリケーション開発ではコンテナを使う機会がどんどん増えていくと思われます。
なので、k8sを使ったコンテナ開発・運用については知っておく必要はあります。
AWSもECSやEKSなどコンテナサービスを出して来ていますが、今のところGCPが一歩進んでいるイメージです。
CPU、TCP/IP、linuxなど低いレイヤーの知識
何もわかっていないまま業界に飛び込んだので、とにかく目の前の業務をこなせるレベルに自分を無理やり持っていく必要がありました。
なので、割と低いレイヤーの領域に関しての知識はほぼないです。
新しい言語やフレームワークを使えるレベルまでスキルを伸ばすことは容易ですが、深いレベルなるとまだまだ勉強が足りないのが現状です。
より深い理解には低いレイヤーの知識は必須になって来るので、今後は時間を割いて勉強して行きたいと考えてます。
Golangの学習
低いレイヤーの学習において、実際に動くものを作る時にGoがいいのではと考えています。
もちろんCでやれるのであればやった方がいいのですが、自分の今までの言語歴的にLL言語に近いけど低いレイヤーに関する開発も行いやすいGoがいいのではと考えています。
また、最近サーバーの言語としても注目されていて業界的に需要が多いです。
いい機会なので、チャレンジしてもいいのではと考えています。
QIitaやブログでのアウトプット
これまでは「仕事=アウトプット」として成り立っていましたが、低いレイヤーのことに関して学習していくことを考えると、アウトプットするのに会社以外の場所が必要になります。
なので、今後はブログやQiitaでアウトプットを行なっていこうと考えています。
あと、文章を書くのクソ下手くそんなのでそれの勉強にもあればと考えています。
まとめ
まとめると
が今年のキーワードになって来ると思います。
特に低いレイヤーの学習は重点を置いてやっていきたいと思います。
これ以外にもElixirやUnityについても興味があるのですが…欲張っては全部失敗するので、これらはまた来年の課題にしたいと思います。
一年前に作ったWebアプリをアップデートする案
以前まではブログに書く内容はもう少しまとまってからにしようと思っていたのですが、こちらのnoteの記事を読んでもう少し頻繁に書いていこうかと思います。
今日は以前自分が作ったアプリをアップデートしたいと思ったので、そのプランについて書いていこうと思います。
作ったアプリについて
まず今のアプリについて話します。
ちょうど去年の今くらいに「けものフレンズ解析機」というアプリをリリースしました。
今まで自分でサーバーを立てたことないので、その練習用に作成しました。
また、興味があった機械学習と業務でpythonを使っていることもあり、機械学習にチャレンジもしてみました。
実装方法
webアプリはconohaでVPCを借りて、nginx + gunicorn + flaskで実装しています。
機械学習部分はtensorflow + dlibを使っています。
webフロントはjQueryで実装しています。
アップデート案
目的
このアプリをアップデートをしようと考えています。
理由としては以下の4つが上げられます。
- アプリの特性上一発ネタなので、今はアクセスはゼロに近い。でもサーバー費はわずかながらかかっている。勿体無い。
- 表示をjQueryで
display: block
とdisplay: none
を切り替えることで実現していますが、正直微妙 - 最近はwebフロントに触るようになり、jQuery以外の選択肢がある
- タスクキューを使うなど、より実践的な実装を試してみたい
特にサーバー費を浮かしたいのが今回の目的にメインです。
たかが500円、されど500円。
方法
それぞれに関して案を考えてみました。
サーバー費節約
案1:dcockerを使い他のアプリと同一サーバーに複数アプリを同居させる
サーバー一つをこのアプリに使っているから勿体無く感じるので、複数のアプリがデプロイ出来ればいいのではと考えました。
自分専用botのサーバーとかちょうど欲しかったので合い乗せできるならそこまでそんな感じはしないかなと思いました。
合い乗せにする際も、dockerを使ってアプリ同士の依存なく稼働させるように作りたいと考えています。
案2:herokuを使う
どうやらherokuでopencvやtensorflowを動かすことが可能みたいです。
正直ネタアプリなので、常に稼働している必要はなく、動くならherokuを利用してもいいかもしれません。
何よりタダと言うのがいいですね。
これに関しては案2を先に進めて、難しい場合は案1に戻る方法を取ろうと思います。
フロントの書き換え
jQueryでなんちゃってSPAを実装していましたがこれも書き換えたいと考えています。
自分が使える、VueかHyperappにしようと考えています。
こちらもとりあえずHyperappで作成して、無理そうならVueに置き換えるようにしたいと思います。
タスクキューの導入
あんまり必要ないので後回しになりますがこれもやってみようかと思います。
前に使ったことのあって、pythonで動くceleryを考えています。
ただherokuを選んだ時、celeryが動くようになるのか…
ちょっとわからないので、おまけで出来ればやる程度の認識です。
判定方法やフレンズ数を増やす
dlibで顔の位置を判定して、tensorflowでどのフレンズか判別しています。
dlibで学習したデータはどれも正面を向いているものばかりなので、ちょっとでも傾いていると判定が出来ません。
今後は画像を徐々に回転して判定出来るようにしたいと思っています。
そうすることで、より判定の制度をあげるとこができます。
また、今は4人のフレンズしか判定出来ないので、学習し直して判定できるフレンズを増やしたいです。
まとめ
つらつら書きましたが、まとめると以下のようになります。
- サーバーをherokuに移す
- 上手くいかなかったら、元サーバー上にdockerで稼働するようにする
- WebフロントをHyperappで書き換える
- 上手くいかなかったら、Vueでもやってみる
- 検証しながらceleryなどのタスクキューも入れていきたい
- 判定方法やフレンズ数を増やす
移行作業の過程も積極的に記事に書いていこうと思います。
勉強会参加:はてなブログの実例で学ぶ「はてな流」大規模Webサービス開発の勘所
社内Wikiで共有したところ好評だったので、ブログの方にも投稿しておきます。
こちらの勉強会に参加してきました。
https://supporterzcolab.com/event/246/
色々話されたのですが、自分が「お!」っと思ったところだけ2点抜粋して書いていきます。
新規機能実装はプルリクは細かく、リリースも細かく
一機能を同一のプルリクで作業すると。。。
- マージする際のコンフリクト地獄
- レビューの労力がかかってしまい安定性が失われてしまう
- 大量のレビューのフィードバック対応
など問題があったので、細かくプルリクを書いて、細かくリリースする方法を行なっているようです。
よくある方法として、リリースブランチを切ってそこにマージしていく方法があるが、それもコンフリクト地獄は発生するし、他の部分との兼ね合いで大量に新機能の一部をcherry pickすることになってします。
この方法でも依然問題が残ります。
なのでユーザーに見えないような工夫をして、実装を行い細かくリリースしていっています。
例えば、ロジック部分のみの実装や動線を消した状態の実装を行なっています。
また、プルリクも細かく行なっています。
これもコンフリクトをなるべく無くしたり、レビューの労力を軽減する為です。
意識するのはだいたい1日の作業分が1プルリクになるようにしているそうです。
例えば、モデルだけとか、コントローラだけとかそういった分割しやすい単位で分けているみたいです。
*ただし場合よってはモデルとコントローラーを一緒にやった方がいい場合もあるので、臨機応変に粒度は変えている。
その他具体的な方法は以下の記事により詳細が書いてあるので、そちらも確認していただければと思います。
http://developer.hatenastaff.com/entry/2017/08/07/170000
Mackerelのちょっと変わった利用方法
https://mackerel.io/ja/
Mackerelは、はてなが開発したサーバー監視ツールです。
一般的にはもちろん、サーバー監視に使っています。
ただ、それ以外の方法で使っており、いくつか例を教えてもらいました。
デプロイにかかる時間の計測
Makarelの機能を使って計測しているようです。
以前はてなではデプロイに時間がかかっていたので、そういったことを行なっているみたいです。
話は変わりますが、デプロイを短縮させたがたい的な方法は以下の記事により詳細に書いてあるので、そちらも確認していただければと思います。
http://this.aereal.org/entry/2016/12/16/170000
プルリク数の可視化
Mackerelを使ってプルリク数を可視化しています。
修正行数とかも計測しているようです。
そうすることで、実装の忙しさを見れるようにして、新規実装のタイミング等を図っています。
こういったことはmakarelのcliツールで簡単に行えます。
nodeのアップデートが必要なモジュールの数をトラッキング
以前はデプロイの度にモジュールのアップデートを行なっていたとのことです。 Mackerelでアップデートが必要なモジュール数を計測して可視化し、溜まって来たタイミングでアップデートをかけるそうです。
まとめ
プルリクを細かく分けて出す方法は今まで経験がないので一度試してみたいなと思いました。
Mackerelを使った方法ですが、特に「プルリク数の可視化」は非常にいいと思いました。
チケットベースで行なっていれば必要ないように思いますが、実際にのアウトプットもキチンと可視化することも重要に思いました。
またこのようなチーム運用を可視化する考えを発展させて、バグ・不具合の可視化も行なってみるのも面白いと思いました。
どういった場合のバグが多いのか、そして多いケースがわかれば、どのようにすればバグを減らせるのかの対策がより狙いやすくなる気がしています。
courseraの機械学習コースの受講終了しての感想
courseraの機械学習コースを受講し、終了しました!
courseraの機械学習コースとは、機械学習を勉強するならとりあえずこれをやれ!的な感じくらい有名なオンライン講座です。(私調べ)
基本無料ですが、認定書を発行してもらうには学費を払う必要があります。
私はお金に余裕はないので無料で受けさせていただきました。
受講したきっかけ
このコースを受講したのは以前「けものフレンズ判定機」なるものを作成したのがきっかけです。
一応動くものはできたものの、なんだかよくわからないけど出来たみたいな感じで全く機械学習については理解できませんでした。
自分の性格的に基礎からしっかりやらないとダメだなと感じたので、色々調べた結果一番求めているものに近かったので本コースを受講しました。
さて、実際に体験レポート等は他の人がよりすばらしい記事があるのでそちらに任せます。
今回は僕が特に感じたことを3点だけ書きます。
ニューラルネットワークがやっと理解できた
一番この講義でよかったと思う部分です。
昨今のニューラルネットワークブームに乗ろうかと色々な入門書を本屋で見ていたが、どれも初めの方にニューラルネットワークを説明する際に人間の脳細胞との類似性について記載されています。
それが個人的に府に落ちずそこから理解が進まないままでした。
ニューラルネットワークの式がただの記号の集まりにしか感じらず、理解が進まないままでした。
余談ですが、僕は数学は基礎からより、歴史に沿って勉強した方が理解しやすいと感じています。
いきなり定理を持ってこられると、こんな天才的な発想理解できねーよと感じてしまいます。
定理や法則の式を読んでもただの意味のない記号にしか見えません。
でも歴史に沿うとその定理なり法則なりが成立するまでの悩みや試行錯誤の過程をて意見することで、その定理の記号一つ一つに意味があることが理解でいます。
自分の中ではこれを「定理の心を理解する」と呼んでいますw
さらに、こういった理解の方が定理をきちんと理解しているので、応用が効きやすいです。
とにかく自分の中では「脳細胞のモデル」としてニューラルネットワークを理解するということがあまりに突飛すぎて全然理解できないものでした。
本講義ではニューラルネットワークより前の回帰分析から始まり、ニューラルネットワークまで解説してくれます。
これでやっと自分の中でニューラルネットワークの「心」を理解できました。
ニューラルネットワーク以外の機械学習についても学べた
講義をきちんと受ける前には僕の中では今時の機械学習=ニューラルネットワークに近い認識でした。(他の考え方はすでに古く廃れてしまっている、と思っていました)
講義を受けて、ニューラルネットワークは銀の弾丸ではなく、適材適所がありどういった場合に有効かを学べました。
例えば、不良品のジェットエンジンの検出のように、圧倒的に不良品のサンプルや複数の原因がある場合はニューラルネットワークよりアノマリー検出の方がより適しています。
さらに講義の後半は実践的な機械学習のシステムを構築する上での注意や引っ掛けについても講義がありました。
こういったことも実践前に知れてよかったです。
講義についてくのは大変
結構、他の人のレポート等を読んでいるとそこまで難しくないような感じを受けましたが、全然難しかったです。。。
そもそもビデオだけでも1,2時間、長い時は2,3時間かかります。ビデオを見るだけで結構苦労します。
しかも、英語。しかも、octave
という使いなれない言語(特に行列周りは難しいです。。。)
大学でいうと4単位分の授業料があるのではなかという量です。
しかも毎週課題のデッドラインがあるので本当についていくのに必死でした。
仕事が忙しい週は夜遅くまで勉強してなんとか課題をこなしました。
また、ビデオという形式も気楽にパソコンを開いてできないので、疲れている日はなかなか手をつけづらく、課題の期限直前に慌ててこなす感じでした。
ブログの更新が止まっていたのも課題に必死だった関係です。
特にネタがないというのもあるのですがw
この課題の提出期限があったから頑張れたところもあるので、なんともいえませんが。
ただ、しばらくはオンラインの授業は遠慮させていただきますw
まとめ
ちょっとスパルタでしたけど、本当に受けてよかったと思いました。
以前作ったけものフレンズ判定機も今回の勉強の成果を生かして改良していきたいと思います。
そして久しぶりに勉強がもっとしたくなりました。特に座学ですね。
実践で勉強するのはいいのですが、より基礎になる座学はやっぱりいいなと思いました。
また、時間ができたらcourseraは他にも授業があるので受けてみたいと思います!
(追記:私、顔写真付きの身分証持っておらず正式には登録できていないので、卒業できていないみたいです。。。まあ、今回は勉強が目的なので問題ないですね。)
顔画像周りのデータを扱うアプリをelectron-vueで作ってみた
この前けものフレンズ解析機を作った際にdlibやtensorflowを利用したのですが、その時顔の位置に関した情報を扱うことが多かったです。
dlibだと顔の位置判別の学習に、tensorflowは学習データを作成する際に顔画像を切り出すのに使いました。
なので、後々のデータセットを準備する作業のことを考えて、そういった顔の位置の情報を上手く扱えるようなアプリがあると便利だろうなーと思い今回作成しました。
概要
起動した画面はこんな感じです。
「Load」ボタンから画像が入ってるフォルダを選択して、フォルダ内部画像データを取得します。
再帰的にファイルを取得しないのでフォルダ直下の画像のみ読み込まれます。
実際にロードしたらこんな感じになります。
各画像の上に四角の描画、ドラッグ、リサイズ、削除が行うことができ、複数の四角を描画することも可能です。
描画が完了したら「SAVE」ボタンを押して保存します。
保存しないでページを変えると描画したデータが消えてしまいます。
「Publish」ボタンを押すと、上の画像のように画面に書かれた正方形の位置(x, y)と大きさ(rectLength)のデータをdata.json
として選択したフォルダ内部に出力します。
また、ロードした際にディレクトリにdata.json
があった場合はそのデータを元に画像に正方形を描画します。
なので、何回でも調整可能ということです。
また、dlibで検出した顔の位置の情報をdata.json
と同じ形式にすれば、そのデータも利用可能です。
(ただし画像に合わせて倍率を修正する必要があります)
mustacheというテンプレートエンジンを利用してtemplate.xml
からdata.xml
を出力できるようにしました。
出力される位置とうの情報は実際に画像に合わせて倍率をかけて四捨五入しています。
ただ、今回あくまで機械学習がメインであまり時間をかけるのは適切でないと判断して以下の部分で妥協しています。
- 画像の横幅は1000px固定(実際の大きさに戻す時に倍率の計算が楽)
- 縦長の画像は途切れて実質使えない(アニメの画像のみ使う想定なのでこれで横長画像しか使わない想定なので)
- リサイズするのに右下一箇所でしか出来ない
- vuexが上手く扱えず厳格モードではない
技術
今回アプリ作成に置いて、electron-vueを利用しました。
仕事で一度使ったことがあり、なんとなくですが知見があるからです。
vueではなくreactを使おうかと考えましたが、あくまで機械学習がメインでアプリ制作は手っ取り早く(それでも1ヶ月かかりましたが…)作ってしまいたかったので、経験のあるvueを採用しました。
四角の描画
顔を囲む四角はsvgを利用して記述しています。
svgを扱うのにライブラリは一切利用していません。
本当はsvg.jsを使いたかったのですが、vueと相性が悪いようで正しく動かなかったの(どうやらDOMを直接いじるのがvueだと上手く動かない)で諦めました。
正直正方形を記述するだけなので簡単だと思っていましたが、そんなことはなく苦労しました。
fabricを利用してのcanvasでの描画も考えたが、リサイズした時に大きさによっては四角にジャギが入るので、謎のこだわりでリサイズしても綺麗なsvgにしました。
正直canvasでよかったように思います…
またsvgはvueでhtmlのタグを扱うように扱えるので、vueの単一コンポーネントも使えます。
四角の描画部分はmouseup、mousedown、mousemove等と今は何を選択しているかで描画、ドラッグ、リサイズ等を行なっています。
正直コードは実験しながら行なっていたので、あまりよくありません。
ファイルの取得と画像の描画
nodeのfs(FileSystem)を利用しています。
画像はsvgのimageタグで表示させました。
file://<url>
を使えるようんするためにelectronのwebPreferences
のwebSecurity
をfalse
にしています。
canvasで読み込んで表示させる方法も考えましたが、個人利用の範囲ならこの設定でも問題ないかなと思い、楽しています。
画像の切り出し方法
こちらはpythonのコードを利用する想定です。
nodeで画像を切り取ることは可能ですが、pythonのコードがすでにあるのでそちらを使ったほうが早いと思い切り出す機能はつけていません。
時間に余裕があればつけたいと思っています。
なので「画像を切り出しソフト」はあまり正しくないのですが、いいネーミングがないので暫定でこの名前にしています。
問題
正しくビルドされない
現在なぜかビルドが正しく出来ないでいます。原因は不明です。
ビルドは正しく終わるのですが起動してみると真っ白のままです。
mainWindow.openDevTools()
を追記して、ビルドされたもののデバッグツールを確認すると、axios
というモジュールが見つかんないとエラーがでます。
これは素のelectron-vue(つまりvue init simulatedgreg/electron-vue application
のテンプレートのままのこと)でも上手く行かないようです。
webpack等のフロントのビルドに関してはテンプレートを信じて使っており、あまり詳しくないので一旦諦めました。
原因がわかれば追記します。
Vuexの厳格モードでのデータの扱い
Vuexを利用してgetterで受け取ったarrayを変更すると(今回の場合は正方形の情報)Vuexの厳格モードだとエラーが発生します。
jsは普段あまりつかわないのでうまい解決策がわからず、今回は厳格モードオフで作成しました。
後ほどVuexのベストプラクティスを調べてここら辺に理解を深めたいと思っています。
感想
だらだらっと箇条書きで感想を書いていきます。
svgは初めて利用しましたがvue扱うのに全く問題はありませんでした。
ずっと昔から簡易イラレ版みたいなの欲しいけど、インクスケープ操作感が悪く使いづらかたので、時間がある時にsvgを利用して作れないかな考えています。(時間ないけど)
electron-veuは昔使った時よりさらにドキュメントやツール周りがさらに強化されているなーという印象でした。
vue周りの発展の速度がすごい早いので驚きです。
Vuexを利用する想定がある場合は早めに利用したほうがいいです。
中盤くらいまでVuexを使わないで作っており、いざVuexを使うことを考えると書き直しが発生して、余計に作業が増えてしまいました…
mustacheはシンプルが故に簡単な演算は無理みたいなので、作業的に必要になったら他のテンプレートエンジンの利用も考えてみます。 でも今はこれで問題ないです。
これできっと機械学習のデータセットを作るのが多少楽になるはず…!
追記
python製のラベリングをサポートするツールがありました… これ使った方がいいですね… github.com
けものフレンズ解析機作ってみた。 - 後編 -
前回、機械学習でフレンズを判定できるようになったので、次はこれをwebアプリとして使えるように実装していきたいと思います。 前回の記事はこちら
前回の機械学習部分において参考にさせていただいたこちらの記事の例だと、アップロードした画像を1回保存して、顔を切り出したり、線で囲った画像を別ディレクトリで保存して結果画面に表示するというものです。
この方法だと公開することを考えると、容量的な問題(金銭的に容量は最小にせざる負えない)で同じ方法は難しいと判断しました。
そこで、サーバ側ではあくまで判定だけ行いその結果をブラウザのcanvasで表示する方法をとりました。
以下のような流れをイメージして実装しようと思いました。
- 画像アップロード
- canvasで読み込んで表示
- サーバへ送信
- 判定、結果を返す
- 結果を受けて顔の枠線と判定結果の描画
フロント
画面の切り替え
canvasでロードした画像を保持したまま通信や結果の表示させたいので、ブラウザの読みこみなしで表示の切り替えが行えるようにしました。
仕組みは簡単で各ページの要素にpage-xxxx
のように命名したclassをつけます。
そのclassの全要素を表示・非表示にして操作します。
切り替えるのは以下の関数で行います。
/* 定数 */ const PAGES = { // ページクラス: ボスのメッセージ 'page-top': 'ガゾウ ヲ エランデ ネ。', // ...(略) /* グローバル変数 */ var now = 'page-top'; // 次のページに遷移 function movePage(targetPage) { // 現在のページの要素を非表示 changeAllDisplayState(now, 'none'); // 現在のページを次のページに変更 now = targetPage; // 次のページの要素を表示 changeAllDisplayState(now, 'block'); // メッセージをセットする setMessage(PAGES[now]); } // 同一クラスの表示変更 function changeAllDisplayState(className, displayState) { $('.' + className).css('display', displayState); } // ボスのメッセージを書き換え function setMessage(msg) { $('#boss-msg').html(msg); }
表示を切り替える時はmovePage('page-xxx')
で行います。
また、ページを読み込んだ時一瞬要素が見えてしまうので、表示する必要ないpag-xxx
がついた要素はdisplay: none
にして表示できないようにしておきます。
ファイルアップロード
ファイル選択のボタンがブラウザのデフォルトだと、統一感がないので他のボタンと同じcssを適用させたいと思います。
labelで囲って実装する方法をとりました。
下の例はわかりやすいように説明に不要なclassは消してあります。
<label for="sendFile" class="button"> ファイル センタク <input type="file" name="file" accept="image/*" id="sendFile"> </label>
ファイルがアップロードされたら、そのファイルの画像をcanvasで読み込むようにします。
// ファイルアップロード時 $('#sendFile').on('change', function (evt) { handleFileSelect(evt); }); // アップロードした画像から情報取得 function handleFileSelect(evt) { var files = evt.target.files; if (!files.length) { failDetecting(); return; } loadImageFile(files[0]); movePage('page-ready'); } // アップロードした画像をcanvasで描画 function loadImageFile(uploadFile) { var canvas = $('#cnvs'); var ctx = canvas[0].getContext('2d'); var image = new Image(); var fr = new FileReader(); // ファイルをロードしたらコールバック fr.onload = function(evt) { // 画像をロードしたらコールバック image.onload = function() { var cnvsW = image.width; var cnvsH = cnvsW*image.naturalHeight/image.naturalWidth; canvas.attr('width', cnvsW); canvas.attr('height', cnvsH); ctx.drawImage(image, 0, 0, cnvsW, cnvsH); // 画像の表示 isUpload = true; displayCanvasImg(isUpload); } image.src = evt.target.result; } fr.readAsDataURL(uploadFile); }
file要素の変更を検知してcanvasに画像読み込みとページ遷移を行います。
また、canvasの幅を適切に設定して、画面外に画像が出ないよう、アスペクト比を保つようにしています。
ajaxでファイル送信・結果の描画
jQueryを使って行います。
解析に時間がかかることを考えてtimeoutを60000にしています。
// 画像投稿 $('#imgForm').on('submit', function(e) { e.preventDefault(); var formData = new FormData($(this).get(0)); $.ajax($(this).attr('action'), { type: 'post', timeout: 60000, processData: false, contentType: false, data: formData, success: movePage('page-detecting') }).done(function(response){ drawResult(response['result']); // 結果の描画 movePage('page-result') }).fail(function() { failDetecting(); }); return false; });
canvasで結果の描画
判定結果はcanvasの機能で描画しています。
特に難しいことはしていません。
リセット
初期状態に戻すために、以下のようにしています。
- 選択したファイルの値を空にする
- canvasの画像を全部書き換える
- 変数のリセット
- 表示ページを変更
function resetAll() { // ファイルをリセット $('#sendFile').val(''); // canvasの中の画像を削除 var canvas = $('#cnvs'); var ctx = canvas[0].getContext('2d'); ctx.clearRect(0, 0, canvas.width(), canvas.height()); // アップロード状態をリセット isUpload = false; displayCanvasImg(isUpload); // topへ遷移 movePage('page-top'); }
デザイン
cssライブラリとしてNormalize.cssとMilligramを使用しています。
フロントは詳しくないので、chromeで動くことしか考えないでcssを書いていきました。
ieとかedgeとかは気にしないフレンズ。
デザインはできるだけ「けものフレンズ」ぽくしたかたので、本家のサイト等を参考にそれっぽく作ってみました。
華やかさも欲しかったのでこちらのロゴジェネレータを使ったり、ボスの画像やfaviconをドット絵で作ってみました。
本当はここまでこだわる予定ではなかったのですが、時間をかけすぎてしましました。。。
バック
あんまり特殊なことはしていないので、箇条書きでざっくり書いていきます。 書いていないところは基本的に普通のflaskアプリと同じようなことをしてます。
- アップロードファイルの読み込みはこちらの記事にあるようにopencvで扱えるようにしました。
- 切り出した顔画像に関して、画像データ、元画像から抜き出した位置情報、解析結果を一緒に扱えるにclassにまとめました。
- evalとmainで別れていたファイルを一つに結合して、扱いやすくしました。
detector.svm
やmodel.ckpt
はlib
というディレクトリに置いてstatic
と分けています。 staticにおくと公開してしまい、誰でもダウンロード出来てしまうので、あまり良くないかなと思い分けています。
こちらのwebアプリ部分に関してはgithubに参考となるコードを上げておきます。
よかったらそちらも参照してください。
サーバにデプロイ
これも特別なことはしていません。
サーバは僕が書いた以下2記事と同じ方法を用いています。
またあらかじめvagrantで開発環境の構築を何回か練習しました。
サーバを立てること自体が、初だったので練習できてよかったと思います。
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "centos7.1" # 公式からcentos7.1最小構成のboxを取ってきています config.vm.network "private_network", ip: "192.168.33.10" config.vm.synced_folder "./www", "/var/www/app", create: true end
ただ今は更新したら、いちいちssh接続してgit pull
しているのでjenkins等で簡略化できないか考えています。
全体的な感想
長い道のりでしたが、フレンズ解析機を作成できました。
仕事でwebアプリを作成したことがあったが、このように一から自分で作ったことがなかったので、色々考えが不足しているところや、他の人が担当してどうなっているのかいまいちわからないことがいくつも出てきて、制作を通して知見が広がったように思います。
一度完成させるのは難しいかも(サーバのセットップあたり)と諦めかけました。
しかし、職場の同僚や上司、友人などにすでに話していたのでやめるわけに行かないと、モチベーションを上げてなんとか完成にこぎつきました。
これからはこのアプリを作成して見えてきた問題点を改良していって、ちょっとずつ成長させていこうと思います。
直近の目標としては、解析できるキャラを増やしたり、顔認識の精度をあげたり、アニメ以外のイラストもキャラの推測を行えるようにしたいと思います。