今回はVue.jsとFlaskでアヤメの分類を行って、分類結果の出力に合わせてCubismWeb(Live2D Cubism3SDK for Web)のモデルを動かしてみます。
アプリケーションの動作
1. Vueにアヤメのパラメータ(がく片の長さ、がく片の幅、花弁の長さ、花弁の幅)を入力する
2. Vueに入力されたパラメータをFlaskの分類器に渡す
3. 分類器の判定結果をVueに返す
4. 分類結果を表示してCubismWebと連動させる
使用するイラスト
CubismWebで表示するモデルです。
© Unity Technologies Japan/UCL
上記モデルには以前の記事で Live2D のテンプレート「FaceRig」を適用しています。
テンプレートを適用する手順についてはこちらで紹介しています。
動作確認サンプル
ChromeとEdgeで動作を確認しています。(下の画面はChrome)
アヤメの品種名を答えてくれるLive2Dモデルの作成
Vue.jsとFlaskでアヤメの分類を行って、分類結果をLive2Dモデル(CubismWeb)に渡すことで、アニメーションと連動させます。
1. scikit-learnでアヤメの学習モデルを作成する
機械学習のライブラリ「scikit-learn」にはサンプルとしてデータセットが用意されているので、今回はその中からアヤメの品種データを学習させます。
scikit-learnの使い方については、解説しているサイトが多数あるので省略します。
アヤメの学習モデルについてはpickleで保存します。
# save the model as pickle joblib.dump(forest, './trained-model/rfcParam.pkl', compress=True)
2. Vue.jsで入力フォームを作成する
Vue.jsでアヤメのパラメータの入力フォームと決定ボタンを作成します。
<div class="param"> <div id="paramsL"> <select v-model="selectedA"> <option v-for="option in options" v-bind:value="option.value"> [[ option.text ]] </option> </select> <span>がく片の長さ SepalLength: [[ selectedA ]] cm</span> </div> <div id="paramsL"> <select v-model="selectedB"> <option v-for="option in options" v-bind:value="option.value"> [[ option.text ]] </option> </select> <span>がく片の幅 SepalWidth: [[ selectedB ]] cm</span> </div> ・ ・ ・ </div> <div id="answer"> <h2>Answer</h2> <button @click="getAnswer" id="btn">try</button> <p>[[ irisName ]]</p> </div>
3. 入力された値を分類器に渡す
入力フォーム関連の記述を追加します。
<script> var ans = new Vue({ el: "#exampleA", delimiters: ["[[", "]]"], data: { irisName: '// 菖蒲のパラメータが入力されたら品種名を出力します //', selectedA: '0', selectedB: '0', selectedC: '0', selectedD: '0', options: [ {text:'0',value:'0'}, //0~10までの数値でアヤメのパラメータを選択 {text:'1',value:'1'}, {text:'2',value:'2'}, {text:'3',value:'3'}, {text:'4',value:'4'}, {text:'5',value:'5'}, {text:'6',value:'6'}, {text:'7',value:'7'}, {text:'8',value:'8'}, {text:'9',value:'9'}, {text:'10',value:'10'} ] }, methods: { update_name: function(str) { this.irisName = str; }, getAnswer:function() { var src = []; var SepalLength = parseFloat(this.selectedA); var SepalWidth = parseFloat(this.selectedB); var PetalLength = parseFloat(this.selectedC); var PetalWidth = parseFloat(this.selectedD); src = [SepalLength, SepalWidth, PetalLength, PetalWidth]; callback = this.update_name; fetch('http://localhost:5000/irisCubism', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(src), //入力された値を分類器に渡す }).then(function(res){ return res.json(); }).then(function(src) { callback('「品種名は ' + src.irisName + ' です」'); //分類器からirisNameが返ってくる }).catch(function(error) { console.log(error) }) }} }) </script>
4. 分類器の処理
Vueから渡された値を基にして、分類器でアヤメの品種を判定します。
@app.route('/irisCubism', methods = ['GET', 'POST']) def irisCubism(): src = request.json //Vueから送られてきた値 params = np.array(src) if request.method == 'POST': pred = predictIris(params) //1.で作成した学習モデルで判定を行う、内容については「機械学習」で調べて irisName = getIrisName(pred) //0,1,2で出力される判定結果を品種名に変換する処理を行う return make_response(jsonify({ //分類器の判定結果をVueに返す 'irisName':irisName })) elif request.method == 'GET': return render_template('irisCubism.html')
5. CubismWebの画面を表示する
Vueの入力フォームの上にCubismWebの画面を配置します。
<div class="sizer" id="example"> <canvas id="SAMPLE" width="1280" height="640"> このブラウザは canvas 要素をサポートしていません。 </canvas> </div>
live2dcubismcoreなどはページの最後尾で読み込みます。
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script> <script src="static/Core/live2dcubismcore.min.js"></script> <script src="static/dist/index.js"></script>
lappdefine.tsの相対パス ResourcesPath を作業環境に合わせて変更します。
今回は static というフォルダに必要なファイルを格納しているので、下記のように変更しました。
// 相対パス export const ResourcesPath: string = "static/Sample/JavaScript/Demo/Resources/";
6. 分類結果の表示に合わせてモデルのモーションを変更する
getAnswerのボタン(id=”btn”)がクリックされた時にモデルのモーションを更新することにしました。
モデルの更新処理及び描画処理を行うonUpdateを使って強引に更新しています。
(できればモーション更新用の処理で切り替えたい)
window.onload = () => { let isPlay = false; let bb = document.getElementById("btn"); bb.onclick = () => { isPlay = true; this.onUpdate = () => { //onUpdateの中身と同じ// if (isPlay === false) { return; } for (let i: number = 0; i < this._models.getSize(); i++) { //更新用のモーション this._models.at(i).startMotion(LAppDefine.MotionGroupAdd, 0, LAppDefine.PriorityNormal); } isPlay = false; } } }