第6回:スマホでPython・機械学習 ! (4) 機械学習予測モデルの実行

“AndroidスマホのTermux上でPythonを動かして、Tensorflow liteのコードを実行するまで”

( はてなブログから引っ越し '24/8/16 )

しょくぶつ(^^)です。
では、こんな感じでコードを動かしていきます。

しかし、前回から中断したことがほとんどでしょう。
再開方法を示します。

途中でやめて再開するときは~Python仮想環境まで

スマホとパソコンとの接続は、パソコンをスリープさせたりすると接続が切れてしまいます。

そんなときは、次のコマンドを実行してください。
(1)スマホ側のTermuxを閉じているときは下記を実行。

  • Termuxを開く
  • Termux上で次のコマンドを打ってsshを起動。なお、"↑"を押すと1回前に実行したコマンドが表示されます。もう何回か"↑"を押すと、下記のコマンドが出てくるはずです。
    ※ sshではなく sshd であることに注意。
    ※実行しても特にメッセージは出ません
~$ sshd
  • Termux上で次のコマンドでipアドレスを確認。
    ただし、前回と変わっていないことがほとんどです。
~$ ip -4 a
・・・
inet (スマホのipアドレス)/24
・・・

(2) Powershell上からsshでTermuxと接続。

例: PS C:\Users\(ご自身の環境に依存)> ssh u0_a435@192.168.11.20 -p 8022

(3) ubuntu環境に入り、更にpython仮想環境に入る。

~$ proot-distro login ubuntu
root@localhost:~# py3102

無事に下記のように再開できたでしょうか?

(py3102) root@localhost:~# 

1. 必要な機器

必要な機材は次の通りです。
そのほか、windows PC と SDカード は使うと便利。

  • android スマホ ※ネットワークは必要ありません

  • HDMIキャプチャー機器
    動作確認済み エレコムのAD-HDMICAPBK

  • HDMIケーブル, 3本

  • Nintendo Switch本体

  • ドック

  • ★HDMI分配器(HDMIスプリッターとも言います。)
     注意! 「HDMI切替機」ではありません。
     私はグリーンハウスのGH-HSPA2-BKを使っています。
    (現在はGH-HSPG2-BKという後継品あり。'24/9月現在だとヨドバシで1,900円くらい。)

図にするとこんな感じです。

必要な機器、その接続

コードをスマホにコピーする方法

コードをスマホにコピーする方法はいろいろあるのですが、次の「スマホをストレージとしてwindowsと接続」する方法が一番お手軽です。

(1)あらかじめwindowsにコードをダウンロード
(2)スマホをストレージとして接続
(3)Windows PCから、スマホのSDカード側の、たとえば次の場所へ、コードなどを移動。
SDカード > Pictures > Screenshots
(4) Termux上で作業ディレクトリを作成 ※ 初回だけ 

(py3102) root@localhost:~# cd ~
(py3102) root@localhost:~# mkdir workspace

(5)Termux上で、cpコマンドを使って作業ディレクトリにコードをコピー
例: "testcode.py"というファイルの場合

(py3102) root@localhost:~# cp
/sdcard/Pictures/Screenshots/testcode.py ~/workspace

コード内の画像取り込みパラメーター修正

画像取り込みはpython側では実現できなかったので「USBカメラ」アプリで行います。(インフィニテグラ(株)という信頼できるカメラ技術の会社の無料アプリです。)

282x300

「USBカメラ」のPlayストア紹介画面

このとき、スマホとキャプチャー機器の組み合わせによって画像サイズが変わってしまいます。
そこで画像取り込みパラメーターを手動で修正します。
本コードには ダイアログボックスに入力 とか、 自動 とか、 おしゃれな 機能はありません。
コード内の数値を書き換えていきます。

まず、キャプチャー機器をUSB端子に接続し、次に「USBカメラ」を起動しましょう。
キャプチャー画像は見られるでしょうか?
なんか、スマホにSwitchの画面が映って、おお~って感じですよね。

スマホにSwitchの画面が映りました!!

次はお好み次第ですが、私は 全画面表示 にしました。
 
そうしたら、電源ボタン + 音量ダウンボタン などでスクリーンショットを撮ってください。  

スクリーンショットは /sdcard/Pictures/Screenshots/ に保存されるので、上記の「コードをスマホにコピーする方法」を参考にして、スクリーンショットをパソコン側にコピーしてください。

そして、スクリーンショット自体の画像サイズと、その画像内のキャプチャー画面のサイズを、パソコンなどで調べて下さい。
たとえば「ペイント」で開いて、左下に表示されるカーソル位置を使います。

そしてコード内の

# # # # #
#cap_dev_no = 1
cap_W = 1920    # キャプチャー画像の横幅
cap_H = 1080    # キャプチャー画像の縦幅
cap_Left = 215 # キャプチャー画像の横の原点。
cap_Right = cap_Left + 1920
cap_Top = 0 #キャプチャー画像の縦の原点
cap_Bottom = 1080
# # # # #

6個の変数、cap_W, cap_H, cap_Left, cap_Right, cap_Top,cap_Bottomを修正してください。
 

画像取り込みパラメーターの設定

コードを動かす方法

いよいよコードを動かしていきます。

コード等のファイルをスマホ上にコピー

次のURLに私が作成したファイルをアップロードしました。
※ コードは本ブログの一番最後に記載します。

github.com

このうち、次のファイルを任意の場所 (たとえば作業ディレクトリ ~/workspace ) にコピーしてください。

  • buki-classification.tflite
    学習データです。

  • buki-spe-list.csvブキとスペシャルの対応表です。

  • TFlite-predictV0-in-phone.pyコードです。


Pythonコードの実行 方法(1)

このあと方法(2)も紹介しますが、この方法(1)が一番簡単です。
この方法(1)は、次のコマンドを実行するだけです。

(py3102) root@localhost:~# python TFlite-predictV0-in-phone.py

スマホ上のコード実行の様子

Pythonコードの実行 方法(2)

この方法(2)は、Jupyter labを用意して実行するものです。 
※次のURLを参考にしました。
https://www.silhouette-designer.com/the-real-pocket-note-pc-cosmo-communicator-termux-jupyterlab/  

i) Jupyterlab のインストール

(py3102) root@localhost:~# python -m pip install jupyterlab

ii) jupyterlabのパスワードを設定

(py3102) root@localhost:~# jupyter lab password

iii) jupyterlabを起動

(py3102) root@localhost:~#  cd ~/workspace
(py3102) root@localhost:~#jupyter lab --allow-root --ip=* --no-browser

iv) Androidのブラウザを起動
そして、Jupyter lab の起動画面にも表示されている
http://localhost:8888/lab
もしくは
http://127.0.0.1/lab
を入力して開きます。
するとjupyterlabの画面となります。

v) コード( TFlite-predictV0-in-phone.py ) を開きます。
具体的には、 画面左のフォルダのアイコンをクリック、次に、 ファイル名をダブルクリック です。

スマホ上でJupyter labが起動

vi) notebookをクリックします。

notebookをクリック

vii) %run TFlite-predictV0-in-phone.py と入力して、実行します。
なお、この画面のように縦画面で使うときは、viewのShow Left Sidebarをオフにすると開いたファイルが大きく表示にできます。

%run (コード名) と入力、そして実行!!

キャプチャー機器からの画像取り込み

Pythonコード単体ではキャプチャー機器から画像を取り込めないので、上述の「USBカメラ」アプリと連携します。

・ 「USBカメラ」起動
Termuxやブラウザは終了しないでそのままにして、「USBカメラ」を起動してください。

・ 画像取り込み
上記の「コード内の画像取り込みパラメーター修正」と同じ状態で、画像を取り込みます。(私は 全画面表示 にしました。)
画面上に敵・味方のブキ一覧が表示されたら、電源ボタン + 音量ダウンボタン などでスクリーンショットを撮ってください。

5~10秒くらいで「スプラの敵のスペシャルを声でお知らせ」

スクリーンショットを撮ると、/sdcard/Pictures/Screenshots/の中のファイルが増えます。
それをPython側が検知して、自働で機械学習モデルを使った予測(画像を見てブキを判別)が始めます。
5~10秒くらいで「スプラの敵のスペシャルを声でお知らせ」してくれるはずです !!

いかがでしょうか?

「コード内の画像取り込みパラメーター修正」とか、うっかり全画面表示を解除したり、ミスしなければ、Windows上で実行したときと同じような正確さで、お知らせしてくれたはずです。

感想

おつかれさまでした!!

初心者の方は仕組みは分からないにしても、機械学習を使った画像処理のPythonプログラムをスマホで実行できたわけです。

しかも、スマホでのLinux, その下でのubuntu, さらにその下でのPython, またまたその下でのTensorflow, OpenCVを使える環境もゲットできました !!
なお、今回までに紹介した方法ではTensorflowはlite版しか使えなかったので予測だけで、学習はできませんでした。
しかし、無料のTermux (そう、今回までは全て無料でできるんです) ではなく、2000円弱のPydroid3を使えば、スマホでもちゃんと学習も実行できます。
これもいつか紹介しますね。(リクエストがあれば優先度を上げます)

さらにいうと、Windows上で動いていたコードをスマホで動かせたのは大感激です !!
たしかに、前回のような何カ所もの修正が必要でした。
それでも、算数がわりとめんどうで私としては一番いじりたくない、画像のカットや変形のコードについて、修正がゼロだったのは大きかったです。

ほかの活用法もどんどん考えたいと思います。

今回使ったコード

# -*- coding: utf-8 -*-
"""
Created on Tue Nov 31 13:01:48 2023

@author: plant-smile
"""

try:
    # linux
    import tflite_runtime.interpreter as tflite
except ImportError:
    # win
    from tensorflow import lite as tflite

import numpy as np
import pandas as pd
#import pyttsx3
import cv2
import matplotlib.pyplot as plt
from matplotlib.image import imread, imsave
import subprocess
import glob
import os
import time
from PIL import Image
Image.LOAD_TRUNCATED_IMAGES = True
import datetime

# # # # #
#cap_dev_no = 1
cap_W = 1920
cap_H = 1080
cap_Left = 215 # 2280*1080時左右非対称  120# 2160*1080時
cap_Right = cap_Left + 1920
cap_Top = 0
cap_Bottom = 1080
# # # # #

def original_resize(img_loc, photo_size_loc): 
    # 画像img_locを、縦横photo_size_locの正方形に、縮小
    # Pillow(PIL)のImageモジュール, resize を使うとアスペクト比が変わってしまうので、
    # アスペクト比を変えないでサイズ変更する関数を作りました。
    # 予測性能がアップ? した気がします。  x: width方向, y: height方向
    #
    # 真っ黒な、縦横photo_size_locの正方形の画像を用意。
    base_img=np.zeros((photo_size_loc,photo_size_loc,3),np.uint8)
    #
    img_height, img_width, img_c = img_loc.shape
    x_ratio = photo_size_loc / img_width
    y_ratio = photo_size_loc / img_height
    if x_ratio < y_ratio: # widthの方が大きい、つまり横長の場合
        resize_heght = round(img_height * x_ratio)
        resize_sizes = (photo_size_loc, resize_heght)
        img_loc=cv2.resize(img_loc, resize_sizes)
        # 上下に空いた隙間は真っ黒ではなく、元画像の端部をコピーして埋めます。
        for i in range(int(photo_size_loc/2)):
            base_img[i]=img_loc[0]
        for i in range(int(photo_size_loc/2),photo_size_loc):
            base_img[i]=img_loc[ resize_heght - 1]
        # base_img と img_loc を合成
        height0 = int( ( photo_size_loc-resize_heght )/2 )
        base_img[height0:resize_heght + height0,:,:]=img_loc
    else: # 縦長の場合 今回は縦長にはならないはずですが、念のため。
        resize_size = (round(img_width * y_ratio), photo_size_loc)
        # base_img と img_loc を合成
        img_loc=cv2.resize(img_loc, resize_size)
        base_img[0:resize_size[1],0:resize_size[0],:]=img_loc
    return(base_img)
    '''
    スマホ版では削除した部分(OpenCVによるカメラからの画像取り込み)

    '''
def cut_img_to_XPdata(img_loc):
    # 画面右上に表示される相手側のブキ画像4個(幅:高さ=80:64=5:4)を切り出してXPdataに格納
    # すこし大きめに切り出している理由は、ガチマッチ進行によるブキ画像の拡大表示に対応するため。
    XPdata =[]
    Img_cut_org = []
    Htop, Hbottom = int(16/720*cap_H), int((16+64)/720*cap_H)
    Wtop = int(692/1280*cap_W)
    for i in range(4):
        Wbottom = Wtop + int(80/1280*cap_W)
        img_cut = img_loc[Htop: Hbottom, Wtop: Wbottom]
        # オリジナルのカット画像の保管(ファイル保存したいとき用)
        Img_cut_org.append (img_cut)
        # サイズを64x64に統一
        img_cut = original_resize(img_cut, 64)
        XPdata.append (img_cut)
        Wtop = Wtop + int(59/1280*cap_W)
    # np.arrayが必要な理由。
    # 1. tensorflowで作成したモデルへの入力はnp.arrayである必要があるため。
    # 2. [NParray,NParray,NParray,NParray,]というリストを
    #   (4, 64,64,3)のnp.array of unit8に、、さらに/255することでfloat64 に変換するため。
    XPdata=np.array(XPdata)/255
    return(XPdata, Img_cut_org)

def show_predict_buki(predicted_no, Img_cut_org):
    print("取り込んだ画像と予測categoriesの表示")
    # Pillow(PIL)を使って4個のブキ画像を並べて表示します。
    plt.figure(figsize=(15,15))
    for i in range(4):
        plt.subplot(2,2,i+1)
        plt.xticks([])
        plt.yticks([])
        img_cut = cv2.cvtColor(Img_cut_org[i], cv2.COLOR_BGR2RGB)
        plt.imshow( img_cut )
        plt.xlabel( categories[ predicted_no[i] ] )
    plt.show(block=False)

def speak_buki(predicted_no):
    # ブキ名を発音します。
    for i in range(4):
        #engine.say( name_buki[ predicted_no[i] ] )
        #engine.runAndWait()
        cmd = "termux-tts-speak " + str(name_buki[ predicted_no[i] ])
        tmp=subprocess.getoutput(cmd)
        print( name_buki[ predicted_no[i] ] )

def speak_special(predicted_no):
    # スペシャル名を発音します。
    #engine.say('スペシャルは')
    #engine.runAndWait()
    tmp=subprocess.getoutput("termux-tts-speak スペシャルは")
    print('スペシャルは')
    for i in range(4):
        #engine.say( name_special[ predicted_no[i] ] )
        #engine.runAndWait()
        cmd = "termux-tts-speak " + str(name_special[ predicted_no[i] ])
        tmp=subprocess.getoutput(cmd)
        print( name_special[ predicted_no[i] ] )

def save_cut_img(predicted_no, Img_cut_org):
    # 画像の保存        
    now = datetime.datetime.now()
    for i in range(4):
        f_name = '/sdcard/Pictures/Screenshots/cap_'  \
            + categories[ predicted_no[i] ] +str(i)  \
            + now.strftime('%Y%m%d_%H%M%S')
        cv2.imwrite(f_name + ".png", Img_cut_org[i])

if __name__ == '__main__':
    # モデル読み込み (Tensorflow使用, 畳み込みニューラルネットワーク(CNN)を駆使して訓練したものです)
    #loaded_model =load_model('buki-classification.h5')
    #TFlite: モデルの読み込み
    interpreter = tflite.Interpreter(model_path="buki-classification.tflite")
    #TFlite: モデルのテンソルへの割り当て
    interpreter.allocate_tensors()
    #TFlite: 入力の詳細の取得
    input_details = interpreter.get_input_details()
    #TFlite: 出力の詳細の取得
    output_details = interpreter.get_output_details()

    # データベースをdataframeとして読み込み (Pandas使用)
    f_name_in = "buki-spe-list.csv"
    df = pd.read_csv(f_name_in, index_col=0, encoding="Shift-JIS")
    name_buki = df["name_J"].tolist()
    name_special = df["func"].tolist()
    filename_special = df["fname_func"].tolist()
    categories = df.index.values.tolist()
    # 音声合成ライブラリpyttsx3の初期化
    #engine = pyttsx3.init()
    # pkill com.termux.api
    # 音声のスピード調整
    #rate = engine.getProperty('rate')
    #print('デフォルトの音声スピード: {}'.format(rate))
    #engine.setProperty('rate', 200)

    while True:
        #input("Enterキーを押したら実行")
        print("ファイルが更新されたら実行されます")
        list_of_files =glob.glob('/sdcard/Pictures/Screenshots/*')
        latest_file = max(list_of_files, key=os.path.getctime)
        old_l_file=""+latest_file
        while (latest_file == old_l_file):
            old_l_file=""+latest_file
            list_of_files =glob.glob('/sdcard/Pictures/Screenshots/*')
            latest_file = max(list_of_files, key=os.path.getctime)
        plt.close()
        time.sleep(0.9)
        # プレイ中画像読み込み
        #img = cap_camera_to_img()
        img_pil = Image.open(latest_file)
        if img_pil.mode == 'RGBA' or "transparency":
            img_pil = img_pil.convert('RGB')
        img_pil_array = np.array(img_pil)
        img = img_pil_array[:,:,[2,1,0]]
        # スクリーンショットの大きさに応じて不要部分をカット
        img_cut = img[cap_Top:cap_Bottom, cap_Left:cap_Right]
        # 画面右上に表示される相手側のブキ画像4個を切り出してXPdataに格納
        XPdata, Img_cut_org = cut_img_to_XPdata(img_cut)
        #プレイ中画像の予測 (Tensorflow使用)
        #predicted_labels = loaded_model.predict_on_batch(XPdata)
        predicted_labels=[]
        for i in range(4):
            #TFlite バッチ次元の追加と型変換 (エラー文の仰せに従い変換)
            #XPdata_tmp=XPdata[i]
            XPdata_tmp= np.expand_dims(XPdata[i], axis=0).astype("float32")
            #TFlite  入力画像のセット
            interpreter.set_tensor(input_details[0]['index'], XPdata_tmp)
            # TFlite 処理実行
            interpreter.invoke()
            # TFlite 出力の取り出し
            predicted_labels.append( interpreter.get_tensor(output_details[0]['index']) )
        # 予測結果はベクトルなので、それを数値に変換 たとえば
        # predicted_labels[?] = [1,0,0,0,0,0,0,0....,0,0]
        # → predicted_no[?]= 0
        # predicted_labels[?] = [0,0,0,0,1,0,0,0....,0,0]
        # → predicted_no[?]= 5
        predicted_no = []
        for i in range(4):
            predicted_no.append(np.argmax( predicted_labels[i] ))
        # 取り込んだ画像と予測categoriesの表示
        show_predict_buki(predicted_no, Img_cut_org)
        # 画像の保存        
        save_cut_img(predicted_no, Img_cut_org)
        # ブキの名前発音
        speak_buki(predicted_no)
        # スペシャルの名前発音
        speak_special(predicted_no)
タイトルとURLをコピーしました