AIをARに応用するぞ会 → (第7回) ~基本設計2~
こんにちは。
開発メンバーのYです。
ようやく夏になりましたね。
けど外出できなくてとてもつらいです。
さて今回は、Androidアプリ開発におけるライフサイクルについてお勉強しました。
いざカメラを動かそうと公式ドキュメントを確認したところ、
「OnResume」等の見慣れない言葉が多々出現したため、今一度Androidアプリ開発のプロセスについて見直そうといった感じです。
以下のドキュメントに従ってカメラ映像を取得しようとしました。
developer.android.com
調べたところ、Androidアプリはライフサイクルをもっていて、
OnCreate() Activityが最初に起動するときに呼び出される。画面インターフェースの作成や、1度しか行わない最初の初期化処理を記述する。
↓
onStart() Activityが画面に表示されるときに呼び出される
↓
onResume() Activityが前面にきて、ユーザーとのやりとりを始められるようになる直前に呼び出される。
みたいな感じで動いているらしいです。
具体的には、ホーム画面に戻ったりすると、onCreateで生成した画面は維持されたままで、
onResume内のプロセスは破棄されるみたいな動作になるっぽいですね。
Android開発においては常識らしいですが、今まで気にせずonCreate内で全て終わらせてました。すみません。
以下、参考にしたブログです。
qiita.com
以前作成した、推論アプリをライフサイクルに従って修正していきます。
イメージとしては、
onCreate() ボタン生成まで
onResume() 実際の処理
そんなこんなで、できあがったのが下記です。
package com.example.myapplication_test; import androidx.appcompat.app.AppCompatActivity; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.media.MediaPlayer; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.ImageView; import android.graphics.Bitmap; import org.tensorflow.lite.Interpreter; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; public class MainActivity extends AppCompatActivity { private TextView textView; private boolean buttonTap = false; final int CLASS_NUM = 2; final int IMG_WIDTH = 224; final int IMG_HEIGHT = 224; MediaPlayer mp = null; Button button1; private MappedByteBuffer loadModel(Context context, String modelPath) { try { AssetFileDescriptor fd = context.getAssets().openFd(modelPath); FileInputStream in = new FileInputStream(fd.getFileDescriptor()); FileChannel fileChannel = in.getChannel(); return fileChannel.map(FileChannel.MapMode.READ_ONLY, fd.getStartOffset(), fd.getDeclaredLength()); } catch (Exception e) { e.printStackTrace(); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ボタンを設定 button1 = findViewById(R.id.button); } @Override protected void onResume() { super.onResume(); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { /* ここがメイン関数 */ /*モデル取得*/ // String ModelPath = "mobilenet_v1_1.0_224_quant.tflite"; String ModelPath = "model_img_recog_ramen_pan_bn.tflite"; Context context = MainActivity.this; MappedByteBuffer myModel = loadModel(context, ModelPath); try (Interpreter interpreter = new Interpreter(myModel)){ /*****************************************/ /*********** 画像の取得 *******************/ /*****************************************/ /*画像を配列に格納(128*128*3)*/ byte[][][][] byteTestImg = new byte[1][IMG_HEIGHT][IMG_WIDTH][3]; InputStream inputStream; Bitmap bitmap = null; try{ inputStream = getAssets().open("test.jpg"); bitmap = BitmapFactory.decodeStream(inputStream); } catch (IOException openErr){ openErr.printStackTrace(); } /* 画像サイズ取得 */ int w = bitmap.getWidth(); int h = bitmap.getHeight(); /* 画像を配列に格納 */ for(int i=0;i < w;i++) { for (int j = 0; j < h; j++) { /* 今見てるピクセルが128ピクセルとしては何ピクセル目かを調査 */ /* 計算量無駄になってるけど許してほしい */ int x = (int)(i*((float)IMG_WIDTH/(float)w)); int y = (int)(j*((float)IMG_HEIGHT/(float)h)); int color = bitmap.getPixel(i,j); int r = Color.red(color); int b = Color.blue(color); int g = Color.green(color); byteTestImg[0][y][x][0] = (byte) r; // R byteTestImg[0][y][x][1] = (byte) g; // G byteTestImg[0][y][x][2] = (byte) b; // B } } /*****************************************/ /*********** 画像の表示 *******************/ /*****************************************/ ImageView inferenceImageView = (ImageView) findViewById (R.id.image_view_01); /* pixelごとのRGBに値を格納 */ int[] pixels = new int [IMG_WIDTH * IMG_HEIGHT]; for (int i = 0; i < IMG_WIDTH; i++) { for (int j = 0; j < IMG_HEIGHT; j++) { int c = j + i * IMG_WIDTH; int red = (int)byteTestImg[0][i][j][0]; int green = (int)byteTestImg[0][i][j][1]; int blue = (int)byteTestImg[0][i][j][2];; pixels [c] = Color.argb (255, red, green, blue); } } /* bitmap配列にrgb値を格納 */ Bitmap bitmapImage = Bitmap.createBitmap (IMG_WIDTH, IMG_HEIGHT, Bitmap.Config.ARGB_8888); bitmapImage.setPixels (pixels, 0, IMG_WIDTH, 0, 0, IMG_WIDTH, IMG_HEIGHT); /* 画像の表示 */ inferenceImageView.setImageBitmap (bitmapImage); /*****************************************/ /*********** 推論の実施 *******************/ /*****************************************/ /* [0,255]の画像を[0.0, 1.0]に正規化 */ float[][][][] testImg = new float[1][IMG_HEIGHT][IMG_WIDTH][3]; for (int i = 0; i < IMG_WIDTH; i++) { for (int j = 0; j < IMG_HEIGHT; j++) { for (int ch = 0; ch < 3; ch++) { // testImg[0][i][j][ch] = (float) 0.1; testImg[0][i][j][ch] = (float)byteTestImg[0][i][j][ch] / 255.0f; } } } /*推論実施*/ float[][] output = new float[1][CLASS_NUM]; interpreter.run(testImg, output); /*****************************************/ /*********** 結果の出力 *******************/ /*****************************************/ /* 結果の描画 */ String cate1 = "PanCake: " + String.valueOf((int)(output[0][0]*100)) + "%"; String cate2 = "Ramen: " + String.valueOf((int)(output[0][1]*100)) + "%"; ((TextView) findViewById(R.id.category1)).setText(cate1); ((TextView) findViewById(R.id.category2)).setText(cate2); } catch (IllegalArgumentException e){ System.out.println("IllegalArgumentException!!!!!!!!!!!!!!!!!!!!!!!"); System.out.println(e); } } }); } }
以前より圧倒的に見やすくなりました。
動作は何も変わりませんが、メモリ使用効率がよかったりアプリの強制終了とかにも強くなってるんですかね。
元の状態でリリースとかしたら大恥かきそう。
次回から、カメラ動かしてみたいと思います。
それではまた。