初めに
pythonのexe化は非常に難しいところがあります。ライブラリが増えるとpyinstallerのエラー地獄でだいたい前に進めなくなります。また、GUIにいいものがありません。GUIはビジュアルに強いhtml+cssで作りたいところです。javascriptのelectronは、デスクトップアプリ用ライブラリでhtml+cssでGUIを作れてexe化も容易です。一方、javascriptの機械学習系はライブラリが多くありません。ライブラリの豊富なpythonを使いたくなります。pythonでelectronに似てるライブラリがないか探したところeelというパッケージに出会いました。
色々調べて、インターフェイスはhtml、css、javascriptで作り、そのインターフェイスから画像を選択して、python(tensorflow)で画像の分類をするというデスクトップアプリを作ることができました。その方法をメモっておきます。
一番困ったのは、eelでhtmlからpython側にどうやって画像の受け渡しを行うのかというところです。情報があまりありませんでした。
流れ
- tensorflowで画像分類CNNモデルを作成する。そしてそのモデルを用いて、写真ファイルを読みこませ推論可能なスクリプトを作成しておく
- eelをインストール
- pyファイル内で、wxpythonによるファイルの選択ダイアログが出せる関数を作成し、javascriptに共有する
- htmlでファイル選択ボタンを作りjavasciptでボタンが押されたときに、上記の関数を使ってファイル選択ダイアログを表示させる
- pyファイル内で、画像クラス分けの関数を作成して、javascriptに共有する。関数は、画像のファイルパスを変数として受け取り、推測されるクラスを返す。最初に作ったスクリプトをほぼコピペ。
- htmlで推論実行ボタンを作りjavascriptでボタンが押されたときに、上記の関数を使って推論する。推論の結果を受け取り、htmlのどこかに結果を反映させる
- pyinstallerを使ってexe化する。
tensorflowで画像分類CNNモデルを作成する。そしてそのモデルを用いて、写真ファイルを読みこませ推論可能なスクリプトを作成しておく
以下の記事に詳しく書いています。これにだいぶ時間がかかります。
eelをインストール
後にpyinstallerを使うことを考えて、まっさらな仮想環境を作ります。仮想環境をactivateするところまで
py -m venv myenvironment
cd myenvironment
cd Scripts
activate
そこにeelをインストールしました。また、後ほど必要なパッケージもインストールしておきます。wxpythonは、ファイル選択ダイアログを出すためのライブラリです。tkinterでもいいのかもしれませんが、以前pyinstallerエラー地獄にあったことがあるのでtkinterを使うのが怖いです。pillowは画像を読み込むために使い、tensorflow-cpuはファイルサイズを小さくするために-cpuを付けています。
pip install eel
pip install wxpython
pip install pillow
pip install tensorflow-cpu
eelの基本的な使い方
フォルダの構成にルールがあります。フォルダ(ルート)に1つのpyファイルと、1つの子フォルダを作ります。pyファイルは、eelを起動させるpythonファイルです。子フォルダ内には、GUIを構成するhtmlとcssとjavascriptファイルを入れます。
pyファイルの一例
import eel
eel.init('web') # webはhtml等が入ったフォルダ名
@eel.expose
def pythonFunction():
return "result"
eel.start('main.html', size=(1024,768)) # htmlファイル名
eel.init(”子フォルダ名”) で子フォルダを把握します
@eel.expose で、その下の関数をjavascriptに共有します。
eel.start(‘main.html’, size=(1024,768))で、eelアプリケーションを起動します。main.htmlは子フォルダ内のhtmlファイル名を指定します。
javascript側の一例 ボタンクリックによって、関数をinvoke
<script type="text/javascript">
$("#button").click(function () {
eel.pythonFunction()((returned_val) => {
$('#filepath').text(returned_val)
});
};
</script>
pyファイル内で、wxpythonによるファイルの選択ダイアログが出せる関数を作成し、javascriptに共有する
pyファイル ― ファイル選択ダイアログ
import wx
@eel.expose
def pythonFunction(wildcard="*"):
app = wx.App(None)
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
dialog = wx.FileDialog(None, 'Open', wildcard="JPG files (*.jpg)|*.jpg", style=style)
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = None
dialog.Destroy()
return path
htmlでファイル選択ボタンを作りjavasciptでボタンが押されたときに、上記の関数を使ってファイル選択ダイアログを表示させる
select fileボタンを押すと、pythonのファイルダイアログが開く。filepathというidのタグのところにファイル名が表示される。ファイル名だけ表示させるためにフルパスから、最後のファイル名のところだけ取り出す操作が入っています。
<button type="button" onclick="getPathToFile()">Select File</button>
<script type="text/javascript">
picpath = ""
target_file = ""
function getPathToFile() {
eel.pythonFunction()((r) => {
picpath = r
target_file = picpath.replace(/^.*[\\\/]/, '')
$('#filepath').text(target_file)
});
};
</script>
pyファイル内で、画像クラス分けの関数を作成して、javascriptに共有する。関数は、画像のファイルパスを変数として受け取り、推測されるクラスを返す。最初に作ったスクリプトをほぼコピペ。
model_path=”converted_akmodel.tflite” というところで、ルートフォルダ内モデルを選択しています。
from tensorflow.keras.models import load_model
from PIL import Image, ImageOps
import numpy as np
from tensorflow.keras.preprocessing.image import img_to_array
import tensorflow as tf
@eel.expose
def predict(file):
classlist = ["Happy","Angry","Neutral","No energy"]
X_trial = []
im = Image.open(file)
img = im.resize((224,224))
img = img_to_array(img)
img = img.reshape(224, 224, 3)
img = img.astype('float32')/255
X_trial.append(img)
X_trial = np.array(X_trial, dtype='float')
# Load the TFLite model and predict
interpreter = tf.lite.Interpreter(model_path="converted_akmodel.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], X_trial)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
result = classlist[np.argmax(output_data)]
return result
htmlで推論実行ボタンを作りjavascriptでボタンが押されたときに、上記の関数を使って推論する。推論の結果を受け取り、htmlのどこかに結果を反映させる
<button id="but_upload">Predict</button>
<div id="prediction"> </div>
<script>
$("#but_upload").click(function () {
eel.predict(picpath)((result) => {
console.log(result)
$('#prediction').text(result)
})
});
</script>
pyinstallerを使ってexe化する。
恐る恐るexe化するも、意外にもエラーなく完成!distフォルダが出来上がるので、そのフォルダ内に機械学習モデル(tflite)を入れて、exeをダブルクリックで起動させれば完成。意外にもexe化できた!!起動は遅いです。
pip install pyinstaller
py -m eel script.py web --onefile