Pythonの出力をPHPでWeb上に表示

PHP
Pexels / Pixabay

こんにちは。

やらしみずです。

今回の趣旨

今回、みかんの品種を判断するAPIを作成したのですが、コマンドライン上でのみ動くようなものとなっていて、とてもじゃないけど他の人には見せられない、、、

ということで、Web上に表示してグラフィカルに操作できて、まぁギリギリアプリっていえなくはないかな!ってレベルにしました。
で、その時色々発見があったので、備忘録として書いておきます。
pythonはかけるけど、Webへの繋げ方がわからなーいと困っている皆さんの手助けになれば幸いですたい。
※そんなに技術力高い人間ではないので、わかりずらかったり間違っていた時はガンガン行ってください。頑張って修正します。

環境

PC:Mac
言語:python(3.6.6),php(7.1.7),html

その他:Apache(MAMP)
→試してないけど、Windowsでもそんなに変わらないと思う。

早速本題

今回私が作成したpythonコードからの出力は

$ python predict.py mr-carmack-lemons.jpg 
Using TensorFlow backend.
2018-03-25 03:47:53.385917: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
WARNING:tensorflow:Variable *= will be deprecated. Use variable.assign_mul if you want assignment to the variable value or 'x = x * y' if you want a new python Tensor object.
レモン(100%)

こんな感じで、みかんの品種を判断するコード(predict.py)に引数としてjpgの画像を渡すと、その画像がなんの画像かを出力してくれるのが、僕の作ったコードです。

で、これを最終的に

localhost

こんな感じにしました。
(見た目のツッコミはコメント欄にて受け付けます、、、)

ではでは早速備忘録を書いていきましょう。

HTML側のコード

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<h2><img src="site_image/orenge_hakase.jpg" alt="みかん博士とれもん" width="880" height="260"></h2>
</div>
<body>
<form action="update.php" method="post" enctype="multipart/form-data">

    <h1>みかんの画像をアップしてください。</h1>
    <br />
    <h5>*結果が出るまで数秒かかります</h5>
    <!--下記の「name」がupadate.phpにおいて画像の名前を所得するのに必要になる-->
    <input type="file" name="upfile" size="30" value=""/><br/>
    <input type="submit" value="決定!!"/><br/>
</form>
</body>
</html>

PHP側のコード

<?php
#変数の定義
$filedir = "update_image/";


if (is_uploaded_file($_FILES["upfile"]["tmp_name"])) {
    if (move_uploaded_file($_FILES["upfile"]["tmp_name"], $filedir.$_FILES["upfile"]["name"])) {
        echo "あなたがくれた".$_FILES["upfile"]["name"] . "は、、、、、、";

    #変数の宣言
    #画像の名前を定義する変数
    $image_name = $_FILES["upfile"]["name"];

    $cmdPath = 'pythonのフルパス 起動するコードのフルパス update_image/'.$image_name.' 2>&1';
    exec($cmdPath,$outputData,$result);
    echo "<br>";
    
    echo "状態:";
    if ($result==0){
        echo("正常終了");
    }
    else{
        echo("問題発生");
    }
    echo "<br>";
    echo("この画像はスバリ、");
 
    #解析結果ー種別
    echo(hex2bin(str_replace("'","",str_replace("\\x","",str_replace("b'\\x","",$outputData[3])))));
    echo("!!!!!");

    #画像の出力
    echo "<img src=\"";
    echo "update_image/".$image_name."\" width=\"250\" height=\"250\" align=\"left\">";

    }
    else {
        echo "ファイルをアップロードできません。";
    }
}
else {
    echo "ファイルが選択されていません。";
}

?>

画像の受け取り

画像の受け取りについては、index.html上で作成したフォームからPOSTで受け取るのですが、今回は書きません。
多分この類の記事は探したらいっぱいあるので笑

PHPのexec関数

結論からいうと、pythonのコードをコマンドライン上からではなく外部から呼び出す場合には、PHPの「exec()」関数を使います。
参照:exec()関数

使い方は、
exec(実行する命令,出力結果を入れる,実行後ステータス);
のように3つの引数をつけます。

※参照を見た人は飛ばして大丈夫です。
・実行する命令(ここ重要!)
Macだったらターミナル、Windowsだったらコマンドプロンプトのあの画面で入力する内容をそのままここにかけばOKです。
なお実行する命令は、上記の「update.php」を見てもらえばわかる通り、$cmd=’命令’;などで先に書いておいた方がいいです。

・出力結果をいれる(詳しくは解説しません)
命令を実行した後に出力されたものを全て配列として入れます。
引数の後ろ二つはなくても動きますが、のちにテストなどをすることを考えたら入れておいた方がべたー。

・実行後ステータス(詳しくは解説しません)
命令が正常に終了したかどうかを、0(成功)と1(失敗)で出力します。

つまづきポイント

これを使って今回はpythonを動かそうとしているので、「$cmd = python hogehoge.py hogehoge」のような形にして一つ目の引数を入れます。
ただ、ここで注意をしなくてはいけないのが、pythonのパッケージについてです。
僕の環境で「$cmd = python hogehoge.py hogehoge」のようにコードをかいたら、なんどもエラーが発生しました。

エラー1

File "/Users/shimizuryouta/Desktop/animalapi/predict_aug.py", line 5, in 
 from keras.models import Sequential,load_model
ImportError: No module named keras.models

このエラーを見てこのページに飛んできた人もいるかもしれませんね。

このエラー自体は、pythonがimportするパッケージを見つけられないというエラーなので、同じディレクトリにパッケージを持って来ればOKー

エラー2

と思いきや、Numpyなどを使おうとしていた人は、こんな感じなエラーが出てきたかもしません。

ImportError: Error importing numpy: you should not try to import numpy from its source directory; please exit the numpy source tree, and relaunch your python interpreter from there.

とか

removes all files not under version control). otherwise reinstall numpy.

ちなみに私はNumpyを使おうとしていたので両方出ました笑

最終的な回避方法

この方法が一番いい方法なのかはさておいて、私はこの問題をこのように回避しました。

どうやら、phpからpythonを読んだ場合と、コマンドラインからpythonを読んだ場合では参照されるpythonが異なるよう。(どっかで設定できるのか?)
そして、pythonのimport文でパッケージをimportをするためには、anacondaでインストールしたpythonならanaconda上にあるpythonのフルパスを、そのほかの方法でインストールしたのであれば、そのpythonをどうにかして見つけて、そのフルパスを入力する。
この方法なら、anacondaのパッケージ管理機能をちゃんとつかえる見たい。

今回はNumpyをimportしてたから、こんな感じなエラーが吐かれたけど、吐かれないんだったらpythonのコードと同じディレクトリにimportするパッケージを入れておいて、直接読み込むのもありかもしれない。というか、ほかのところに移植することとか考えたら、そっちの方がいいのかもしれない。
やり方わからんけど。

Python側のコード

# -*- coding: utf-8 -*-
#1枚の画像を機械学習によって判断する

from keras.models import Sequential,load_model
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import keras
from PIL import Image
import os, glob,sys
import numpy as np
from sklearn import model_selection


classes = ['でこぽん','いよかん','グレープフルーツ','レモン','甘夏','柚子']
num_classes = len(classes)
image_size = 50


def build_model():
    model = Sequential()
    model.add(Conv2D(32,(3,3), padding='same',input_shape=(50,50,3)))
    model.add(Activation('relu'))
    model.add(Conv2D(32,(3,3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.25))

    model.add(Conv2D(64,(3,3), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(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(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(len(classes)))
    model.add(Activation('softmax'))

    opt = keras.optimizers.rmsprop(lr=0.0001,decay=1e-6)


model.compile(loss='categorical_crossentropy',optimizer=opt,metrics=['accuracy'])


    # モデルの保存
    #モデルの保存って、、、なんや
    #モデルのロード

    model = load_model('./orenge_cnn.h5')

    return model


def main():
    image = Image.open(sys.argv[1])
    image = image.convert('RGB')
    image = image.resize((image_size,image_size))##エラー個所??1.かっこ削除
    data  = np.asarray(image)
    X = []
    X.append(data)
    X = np.array(X)
    model = build_model()


    result = model.predict([X])[0]
    predicted = result.argmax()#一番大きな数を選択する
    percentage = int(result[predicted] * 100)
    
    #print(classes[predicted].encode('utf-8'))#文字コードエラー
    #print(percentage)

    print("{0}({1}%)".format(classes[predicted],percentage))


if __name__ == "__main__":
    main()

このコードの76行目のコードで、みかんの種類とどのくらいの確率でその種類があっているかを出力しています。
ただ、このままだと問題が発生します。

エラー内容

print("{0}({1}%)".format(classes[predicted],percentage))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)

→文字コードエラー??
pythonの出力をphp側でこの出力を受け取った時、文字コードエラーで表示がされない!!

回避方法

文字コードエラーについては理解できたらまたアップするけど、とりあえず僕がやった回避方法は73,74行目のコードの通り、文字のエンコードを行います。

predict.py
出力をUTF-8にエンコードする

php
エンコードされた出力から不要なものを取り除く
hex2bin()を使って、16進数表記のUTF-8を日本語にデコードする

って感じです。

もっとスマートな方法があるのかもしれないですけど、とりあえず今回出力を見たら、python側でエンコードしたもの(レモン)が「b’\xe3\x83\xac\xe3\x83\xa2\xe3\x83\xb3’」で、php(html)側の標準のエンコード結果が「e383ace383a2e383b3」だったので、phpの方に形合わせて見たらええんちゃう??ってことで合わせて見たらしっかりと出力されました。やったね。
(何を抜いたかは見たらわかると思うので省略)

最後に

ちなみに作成したみかんAPIはもっとコードを綺麗にして、ビジュアルも綺麗になったらGithubとかにあげたり、ここに記事に載せようと思うので、コードを見たい人は少々お待ちをー^^
あ、もし今のクソみたいな状態のコードでも欲しかったら連絡してください笑

初めて開発日記っぽい日記をかけた気がする笑
ちゃんと定期的に更新できるように頑張らないとなー

あ、あともし文字コードエラー(Unicodeエラー)の解決方法を知っていて、説明できるぜってひとがいたらぜひコメント欄に書いて欲しいです^^
いろんなサイト見たけどわっかりずらくて笑

じゃ、今日はこれまで、バイなら。

PS.
今回書いた記事は、完全に自分自身への備忘録だったので、僕以外の人が見たらなんのコードかわからないものをしようしてしまいました。
また機会があれば、簡単なファイルを一緒につくって行って理解を深められるような記事を書きたいと思います。

コメント

タイトルとURLをコピーしました