- 投稿日:2020-05-29T21:56:22+09:00
AndroidStudio 3.6.3 インストール
はじめに
AndroidStudio 3.6.3をインストールするところまで確認したいと思います。
環境と前提条件
- windows10(64bit)
- メモリ:8GB
当初はメモリ4GBでAndroidStudioをいじっていましたが、エミュレーター起動時に厳しさを感じたのでメモリを増設して8GBにしました。故に、メモリは8GB以上がよろしいかと思います。
AndroidStudioをダウンロードする
こちらのページを参考に進めていきます。
赤枠に対象のバージョンと対象OSが記載されています。今回はこちらで問題ありません。
そのまま、DOWNLOAD ANDROID STUDIOを押下します。
AndroidStudioをインストールする
ダウンロードが完了したらandroid-studio-ide...を起動します。
SetUpウィンドウが開かれました。Nextを押下して進めていきます。
AVDをインストールするか否かを決めます。エミュレータを利用する際に必要になるのでチェックしておきます。
スタートメニューにショートカットを作るか否かと、その名前を決めます。デフォルトのまま進めます。
Installを押下してインストールを進めます。
インストールが完了したらFinishを押下します。
また、Start Android Studioにはチェックを入れておきます。
初期設定
続けて初期設定を行います。
AndroidStudioを起動すると、下記ウィンドウが開かれます。
Nextを押下して設定を進めます。
インストールタイプを選びます。今回はStandardを選びます。
設定情報を確認します。熟読する必要はないと思いますが、念のため情報はメモしておきます。
Finishを押下すると必要なファイルのダウンロードが始まります。
改めて、AndroidStudioを起動します。
このようなウィンドウが開けばインストール成功です。
さいごに
AndroidStudio 3.6.3をインストールできたことを確認しました。
このインストール内容で一先ずHelloWorldまでは確認できました。→こちら参考にしたサイト
- 投稿日:2020-05-29T17:18:37+09:00
Layout Validation を使いこなせ
Layout Validation とは
Android Studio 4.0 から使えるようになった、様々な画面サイズやデバイスの構成におけるレイアウトを同時にプレビューするためのツールです。
画面サイズだけでなく、Dark Theme や色覚多様性、多言語、テキストサイズの変更までも確認することができます。
使い方
Android Studio 4.0 以降でレイアウトのファイルを開くと IDE に
Layout Validation
のボタンが表示されます。
様々な画面サイズの確認
左上のドロップダウンより
Pixel Devices
を選択することで様々な Pixel 端末の画面サイズでのレイアウトを確認することができます。色覚多様性の見え方の確認
左上のドロップダウンより
Color Blind
を選択することで色覚多様性の見え方を確認することができます。フォントサイズの変更の確認
左上のドロップダウンより
Font Sizes
を選択することでテキストサイズが変更された場合の見え方を確認できます。Dark Theme の確認
左上のドロップダウンより
Custom
を選択するとドロップダウンの右に端末のアイコンが表示されます。
その端末のアイコンから表示されるポップアップで設定した端末の状態を追加することで様々なレイアウトの確認をすることができます。Dark Theme の場合は
- Theme に
.DayNight
- Night Mode に
Night time
を設定することで Dark Theme のレイアウトの確認ができます。
そのほかにも横向きや多言語での確認も設定することができます。
これまで端末で設定を変更して確認していた部分が改善されて、レイアウトの確認が捗りますね?
- 投稿日:2020-05-29T17:08:25+09:00
KotlinでFragmentを使う(失敗)
KotlinでFragmentを使う
KotlinでFragmentを使ってみます。失敗っていうのは結果だけで途中の段階は重要な部分もあります。
前準備
Gsonを使いますのでbuild.gradle(app)に
build.gradle(app)dependencies { 省略 implementation 'com.google.code.gson:gson:2.8.6' }gsonを追加します。 2.8.6の部分はバージョン番号です。
strings.xml<?xml version="1.0" encoding="utf-8" ?> <resources> <string name="app_name">FragmentSample</string> <string name="btSave">保存</string> <string name="btLoad">読込</string> <string name="tvStr">String型</string> <string name="tvInt">Int型</string> <string name="tvDouble">Double型</string> </resources>空のFragmmentを作ります。
プロジェクトツリーから右クリック→新規→フラグメント→フラグメント(空白)と選んでいきます。
フラグメントの名称を決める画面ではデフォルトのBlankFragmentとしました。
作ったフラグメントのデザインはfragment_blank.xml<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/frame" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".BlankFragment"> <!-- TODO: Update blank fragment layout --> <LinearLayout android:id="@+id/llv" android:layout_width="204dp" android:layout_height="301dp" android:orientation="vertical"> <LinearLayout android:id="@+id/llh" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal"> <TextView android:id="@+id/tvStr" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvStr" /> <EditText android:id="@+id/etStr" android:layout_width="144dp" android:layout_height="wrap_content" android:ems="10" android:inputType="text" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal"> <TextView android:id="@+id/tvInt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvInt" /> <EditText android:id="@+id/etInt" android:layout_width="144dp" android:layout_height="wrap_content" android:ems="10" android:inputType="number" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="right" android:orientation="horizontal"> <TextView android:id="@+id/tvDouble" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tvDouble" /> <EditText android:id="@+id/etDouble" android:layout_width="144dp" android:layout_height="wrap_content" android:ems="10" android:inputType="numberDecimal" /> </LinearLayout> </LinearLayout> </FrameLayout>としました。
今度はMainActivityの方を作っていきます。activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/clMain" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/clTool" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="buttonClick" android:text="Button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout android:id="@+id/llView" android:layout_width="wrap_content" android:layout_height="378dp" android:orientation="horizontal" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.231" app:layout_constraintStart_toStartOf="@+id/clTool" app:layout_constraintTop_toBottomOf="@+id/clTool"> <fragment android:id="@+id/fragmentView" android:name="com.example.qiitafragmentsample.BlankFragment" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" /> <LinearLayout android:id="@+id/llEdit" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> </LinearLayout> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>先ほど作ったFragmentをすでに設計段階で置くことが出来ます。
設計時に置いたFragmentに初期値を
そしてボタンを押したらFragmentを作るようにしてみます。MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val dStr = findViewById<EditText>(R.id.etStr) val dInt = findViewById<EditText>(R.id.etInt) val dDouble = findViewById<EditText>(R.id.etDouble) dStr.setText("Test") dInt.setText("1234") dDouble.setText("98.76") } fun buttonClick(view : View){ val dStr = findViewById<EditText>(R.id.etStr) val dInt = findViewById<EditText>(R.id.etInt) val dDouble = findViewById<EditText>(R.id.etDouble) val bundle = Bundle() val data = gsonFile() data.dStr = dStr.text.toString() data.dInt = dInt.text.toString().toInt() data.dDouble = dDouble.text.toString().toDouble() data.backColor = Color.rgb(0xff, 0, 55) bundle.putString("gson", data.gsonString()) val editFragment = BlankFragment() editFragment.setArguments(bundle) val fragmentManager = supportFragmentManager val fragmentTransaction = fragmentManager.beginTransaction() fragmentTransaction.replace(R.id.llEdit, editFragment) fragmentTransaction.commit() } }Fragmentに値を渡す
bundle.ktval bundle = Bundle() val data = gsonFile() data.dStr = dStr.text.toString() data.dInt = dInt.text.toString().toInt() data.dDouble = dDouble.text.toString().toDouble() data.backColor = Color.rgb(0xff, 0, 55) bundle.putString("gson", data.gsonString())Fragmentには直接値を渡せないのでBundle()という仕組みを使います。
BundleにputString(キー,値) などして値を入れておくと受け取った側のFragmentでgetString(キー)とすれば値を受け取ることが出来ます。
色々な値があるFragmentにいちいち get putしてられないので独自にGsonを使って処理を簡単にしています。
今回作ったFragmentには 文字列型のdStr、数値型の dInt、浮動小数点型の dDouble あと確認のためbackColor という数値(色を管理させている)も用意しています。replace.ktfragmentTransaction.replace(R.id.llEdit, editFragment)LinearLayoutに付けたID llEdit を今回作ったFragmentに置き換えています。他にaddとかありますが使わないと思います。
今度はFragment側で受け取ってみます。
BlankFragment.ktclass BlankFragment : Fragment() { private val data = gsonFile() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val str : String = arguments?.getString("gson") ?:return Log.d("Sample","Fragment str=$str") data.fromData(str) val dStr : EditText? = activity?.findViewById<EditText>(R.id.etStr) val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt) val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble) Log.d("Sample","dStr = ${data.dStr} dInt = ${data.dInt} dDouble = ${data.dDouble}") dStr?.setText(data.dStr) dInt?.setText(data.dInt.toString()) dDouble?.setText(data.dDouble.toString()) dStr?.setTextColor(data.backColor) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_blank, container, false) } }Fragmentを作ったときに出来た宣言にいくつか追加します。
Gsonを管理するgsonFileクラスは後で説明します。
Fragment側で値を受け取る
gson.ktval str : String = arguments?.getString("gson") ?:returnFragmentには直接値を渡すことが出来ず arguments経由で渡されるのでそこからgson形式の文字列を取り出します。設計時には取り出すことが出来ずstrがnullになるのでその場合は処理をしないようにしています。
null許容型にしていても if(str == null)とかで判断出来ないので注意です。クラスにデータを管理させる
独自のgsonDataクラスを宣言してデータを管理しています。
管理したいデータが他にもあれば宣言を追加して、初期化して、代入命令 assignに代入文を追加します。gsonData.ktopen class gsonData(){ var dStr : String = "" var dInt : Int = 0 var dDouble : Double = 0.0 var backColor : Int = android.R.color.background_light fun assign(a : gsonData){ dStr = a.dStr dInt = a.dInt dDouble = a.dDouble backColor = a.backColor } fun gsonString() : String{ return Gson().toJson(this) } }先ほど宣言したデータを管理するクラスにデータ→Gson、Gson→データなど命令を追加します。
自分自身にデータを渡せないのでデータ部分の宣言と処理部分で分けています。
gsonFileクラス内にgsonDataを定義しても良いのですがそれだと gsonFild.data.dIntと長くなるので継承にしました。他に良い方法あればご教授願います。gsonFile.ktclass gsonFile() : gsonData(){ fun fromData(str : String){ if (str == "") return val data = Gson().fromJson<gsonData>(str, gsonData::class.java) as gsonData super.assign(data) } fun fileSave(filename : String,str : String){ File(SingletonContext.applicationContext().filesDir, filename).writer().use { it.write(str) } } fun fileLoad(filename : String) : String?{ val readFile = File(SingletonContext.applicationContext().filesDir, filename) if(!readFile.exists()){ Log.d("debug","No file exists") return null } else{ return readFile.bufferedReader().use(BufferedReader::readText) } } }Contextを使い回す。
ファイルの読み書きにContextがいるのですが単なるデータクラスにあるわけもなく、このクラスから参照しています。
private class SingletonContext : Application() { init { instance = this } companion object { private var instance: SingletonContext? = null fun applicationContext() : Context {return instance!!.applicationContext} } }結果
最初に表示されている設計時Fragmentに入力した値はボタンを押すと新しいFragmentが作られそちらに渡されます・・・が、そうはなりません。
値は渡るもののval dStr : EditText? = activity?.findViewById(R.id.etStr)
で得られる R.id.etStr は設計時のとボタンクリックで作ったものが同じなのでなんと設計時の方が参照されます。Fragmentの目的は
1.表示させる項目を動的に変化させる
2.画面サイズにより表示、非表示を切り替える
であり、同じFragmentを同時に画面に表示させる物では無いと思われます。最後に
練習でFragmentを使って見ましたが、こうなりました。
あと重要なことですが、作ったFragmentへ値は渡せましたが値を返すことは出来ません。
- 投稿日:2020-05-29T16:36:39+09:00
AndroidStudio 3.6.3を日本語化する
はじめに
Android Studioを日本語化するところまで確認したいと思います。
環境と前提条件
- windows10
- Android Studio 3.6.3(64bit)
Android Studioがインストールされていることが前提です。
インストール方法はこちらを参考にしてみてください。プラグインをダウンロードする
こちらからプラグインをダウンロードします。
リンクを開いたら、 Pleiades プラグイン・ダウンロードのwindowsボタンを押下します。
あと、7-zipで解凍云々と書かれていますね。公式に従い、インストールしていない場合は7-zipもインストールします。後ほど使います。
続いて、リンクを押下します。ダウンロードされるまでしばし待ちましょう。。。
ダウンロードが完了したら7-zipで解凍します。
(展開されたファイルはひとつのフォルダにまとめました。)
日本語化する
日本語化するアプリケーションを選択します。
デフォルトであればC:\Program Files\Android\Android Studio\bin\studio64.exeです。
次にAndroid Studioが日本語化されているか、確認します。
できました!さいごに
Android Studio 3.6.3を日本語化できることを確認しました。
参考にしたサイト
- 投稿日:2020-05-29T13:06:23+09:00
AndroidStudioで様々なボタンの動作を確認
AndroidStudioでボタンの動作を確認します
ボタンのクリックイベント、ボタンのチェック状態を確認するサンプルを作ってみます。
strings.xml<?xml version="1.0" encoding="utf-8" ?> <resources> <string name="app_name">ButtonSample</string> <string name="radio_Udon">うどん</string> <string name="radio_Soba">そば</string> <string name="check_Age">油揚げ</string> <string name="check_Tempura">天ぷら</string> <string name="btn_Pay">精算</string> <string name="tv_Pay">お支払い</string> </resources>うどん、そば屋さんで精算するアプリです。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <RadioGroup android:id="@+id/radioGroup" android:layout_width="wrap_content" android:layout_height="wrap_content"> <RadioButton android:id="@+id/radioUdon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/radio_Udon" /> <RadioButton android:id="@+id/radioSoba" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/radio_Soba" /> <CheckBox android:id="@+id/checkAge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="false" android:text="@string/check_Age" /> <CheckBox android:id="@+id/checkTempura" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/check_Tempura" /> <Button android:id="@+id/btPay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:onClick="onClick" android:text="@string/btn_Pay" /> <TextView android:id="@+id/tvPay" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/tv_Pay" /> </RadioGroup> </androidx.constraintlayout.widget.ConstraintLayout>RadioGroupにボタンを入れると勝手に整列するので本来の使い方とは違うと思いますが使いました。
MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } fun onClick(view : View){ val radioUdon = findViewById<RadioButton>(R.id.radioUdon) val radioSoba = findViewById<RadioButton>(R.id.radioSoba) val checkAge = findViewById<CheckBox>(R.id.checkAge) val checkTempura = findViewById<CheckBox>(R.id.checkTempura) val tvPay = findViewById<TextView>(R.id.tvPay) var sum : Int=0 if (radioUdon.isChecked){sum += 350} if (radioSoba.isChecked){sum += 370} if (checkAge.isChecked){sum += 150} if (checkTempura.isChecked){sum +=200} tvPay.setText("合計:$sum 円です") } }値段は適当です。正常に動くか確認してみましょう。
radioButton、checkButtonともにisCheckedを調べるとボタンのチェック状態がわかります。このプロパティに代入すると表示も変化します。1つのイベントを複数のボタンから利用する
毎回精算ボタンを押すのは面倒なのでradioButton、checkButtonがクリックされても計算するようにしましょう。
radioButton、checkButtonを全て選択した状態でonClickの値を設定すれば全てに反映されます。
もう精算ボタンは不要なので消してしまいましょう。イベント発生元を調べる
複数のボタンから同じイベントを呼ぶ場合、呼ばれた側がどのボタンから呼ばれたのか知りたい場合があります。
その場合はイベントの引数として渡されるView型の viewを利用するようです。
view.ktfun onClick(view : View){ val radioUdon = findViewById<RadioButton>(R.id.radioUdon) val radioSoba = findViewById<RadioButton>(R.id.radioSoba) val checkAge = findViewById<CheckBox>(R.id.checkAge) val checkTempura = findViewById<CheckBox>(R.id.checkTempura) val tvPay = findViewById<TextView>(R.id.tvPay) var sum : Int=0 if (radioUdon.isChecked){sum += 350} if (radioSoba.isChecked){sum += 370} if (checkAge.isChecked){sum += 150} if (checkTempura.isChecked){sum +=200} tvPay.setText("合計:$sum 円です") when(view.id) { radioUdon.id -> { Log.d("Sample","うどんが押された") } radioSoba.id -> { Log.d("Sample","そばが押された") } checkAge.id -> { Log.d("Sample","油揚げが押された") } checkTempura.id -> { Log.d("Sample","天ぷらが押された") } } }このように viewのidとレイアウト上のidをfindViewByIdで変換した idを比較することで、どこから来たのか判明します。
最後に
今回使用したonClick(view : View)のイベント受け取り方法は引数が view : View のものに限定されるようです。
それ以外はリスナーを使うのですが全てのリスナーを説明するための情報がまだ不十分なので全部揃ったら説明というかメモします。
- 投稿日:2020-05-29T12:01:14+09:00
Android Studioへの画像リソースの追加が劇的に楽になってたので紹介します
これまでのやり方
これ僕だけなのかもしれないのですが、今までは下記のようなサイズ別の画像を
下記のリソースフォルダへ一つ一つコピペしておりました。
これが地味に面倒で、コピー対象にはldpi
がなかったり、v24
などがあったり、ちゃんと間違いのないコピペができたかを毎度チェックしていました。今回のように一つのリソースならまだしも、大きめの改修でリソースの数が多いと、この作業だけで結構気力を奪われます。これからのやり方
Android Studio 3.4からは
Resource Manager
という機能が追加されました。下記画面の項目から開くことができます。
Resource Manager
はDrawable
、Color
、Layout
、Mip Map
、String
などのリソースをGUIで表示することができます。画像以外のDrawableリソースもプレビューされるのでわざわざファイルを開いて確認する手間が省けてこれだけでも便利です。さて、それでは本題の画像の追加方法についてですが、まず下記のようにDrawableタブを選択し、
+
をクリックします。
Import Drawables
を選びます。別ウィンドウが開きファイルの選択を求められるので、フォルダごと選択して
Open
を押すと、なんということでしょう!インポートする画像がそれぞれのフォルダへ自動で選別されプレビューされました!
あとは
Next
->Import
->Add
と選択するだけで簡単画像リソースを追加することができます!あとがき
2012年からAndroid 開発に携わってきましたが、最初は
Eclipse
でした。それがAndroid Studio
に変わり今でも進化を続けてどんどん便利になっています。開発もどんどん楽になるので嬉しいですね!参考
- 投稿日:2020-05-29T11:07:02+09:00
THETAプラグインでTensorFlow Liteのセグメンテーションをかける
はじめに
リコーのYuuki_Sです。
弊社ではRICOH THETAという全周囲360度撮れるカメラを出しています。
RICOH THETA VやTHETA Z1は、OSにAndroidを採用しており、Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます。(詳細は本記事の末尾を参照)。
上述の通りTHETAは、カメラでありながらAndroid端末でもあるため、単体で撮影し機械学習の処理をかけて出力することが可能です。
以前、@KA-2さんがTHETAプラグインで連続フレームにTensorFlow Liteの物体認識をかける記事を掲載しましたが、今回はセマンティックセグメンテーションを実施する方法を記載しようと思います。
本記事を参考にすることで、セグメンテーション結果をライブプレビューに反映したり、
下図の様に人物と背景で異なる画像処理をおこなうことが可能になります。
セマンティックセグメンテーションとは
画像中のそれぞれのピクセルがどの様な属性(クラス)の物体に属しているか分類することを指します。
例えば、下図のように様々な物体が写った画像から、この領域のピクセルは人間、この領域は車といった具合に領域分割することです。
画像引用元:"Panoptic Segmentation" Alexander Kirillov et al.,CVPR, 2019この様な分類処理は、昔から画像処理の分野でおこなわれていましたが、Deep Learningを用いることで飛躍的に性能が向上しました。
今では、iPhoneのポートレートモードや自動車の自動運転などでも利用されています。TensorFlow Liteのセグメンテーション
TensorFlow Liteのページで紹介されているモデルの1つに、このセグメンテーション処理があります。
詳しくはTensorFlow Lite Segmentationのページを参照してください。
TensorFlow Liteでは、下記の21種類のクラス分けが可能です。
(background/aeroplane/bicycle/bird/boat/bottle/bus/car/cat/chair/cow/diningtable/dog/horse/motorbike/person/pottedplant/sheep/sofa/train/tv)
今回試した環境では、人間とTVのクラス分けが出来ることを確認しました。THETA Plug-in SDKで動作環境構築
それでは、本題であるTHETA上でのセグメンテーション動作環境構築に入っていきます。
作業のベースとなるプロジェクトファイル一式は、@KA-2さんの以下記事で解説したものです。下準備
今回使用するセグメンテーションのサンプルコードは、AndroidXライブラリを用いてKotlinで書かれています。これらに対応させつつ、サンプルコードを導入しMainActivity.javaからセグメンテーション処理を利用できるようにします。
※KotlinもAndroidXも使用経験がなく、試行錯誤で対応した結果を記載しているため、冗長な部分があるかもしれません。
build.gradle(Module:app)の設定
*「apply」に2行追加。
*「testInstrumentationRunner」を変更。
*「aaptOptions」で、モデルファイルを圧縮しないように指定。
*「implementation」の定義を書き換え。
*「targetSdkVersion」等を変更。build.gradleapply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 buildToolsVersion "29.0.2" ~省略~ defaultConfig { ~省略~ targetSdkVersion 29 ~省略~ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } ~省略~ aaptOptions { noCompress "tflite" } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'androidx.appcompat:appcompat:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' implementation 'com.theta360:pluginlibrary:2.1.0' implementation 'org.nanohttpd:nanohttpd-webserver:2.3.1' implementation('org.tensorflow:tensorflow-lite:0.0.0-nightly') { changing = true } implementation('org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly') { changing = true } implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.71' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' implementation 'androidx.core:core-ktx:1.2.0' implementation "androidx.exifinterface:exifinterface:1.1.0" }build.gradle(Project:~)の設定
TensorFlow Lite側のコードがkotlinのため、以下を追加しました。
build.gradlebuildscript { ~省略~ dependencies { ~省略~ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.0" ~省略~ } }学習済みモデルの配置
TensorFlow LiteのセグメンテーションのページにあるDownload stater modelのボタンを押して、学習済みモデルをダウンロードします。
記事執筆時点のファイル名は「deeplabv3_257_mv_gpu.tflite」です。セグメンテーション処理クラスの追加
Android用のサンプルコードから、以下の3ファイルを取得します。
ファイル名 説明 ImageUtils.kt 画像の読み込みと操作をおこなうユーティリティ ModelExecutionResult.kt セグメンテーション結果の構造を記載したクラス ImageSegmentationModelExecutor.kt セグメンテーションの実処理が記述されているファイル 取得したファイルは、配置の仕方にあわせpackageの定義を書き換えてください。
以下は、ベースとしたプロジェクトの「~\app\src\main\java\com\theta360\extendedpreview」に3つのファイルを配置した場合の例です。この場合、packageの定義は3ファイル共に以下となります。また、元のpackage定義はコメントアウトしてください。
package com.theta360.extendedpreview //package org.tensorflow.lite.examples.imagesegmentationAndroidXへのリファクタリング
ここまで終えたら、プロジェクト全体をAndroidXへリファクタリングします。
ツールバーのRefactorからMingrate to AndroidXを選択します。
選択後、現状のプロジェクトの状態をZip形式でバックアップするか尋ねられますので、必要な場合はバックアップします。
その後、リファクタリングが必要な箇所の検索が始まり、終わるとリストが表示されますので、Do Refactorボタンをクリックします。
最後に、レイアウトファイルを編集します。
activity_main.xmlを開き、<androidx.constraintlayout.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"を
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"に書き換えます。
以上で、MainActivity.javaからセグメンテーション処理を利用できる様になります。
MainActivityからセグメンテーション処理を利用する
まずは、プレビュー画面にセグメンテーション結果を重ねて描画してみます。
処理の流れは下記になります。
プレビュー画像の取得→中央部分を正方形で切り抜き→セグメンテーション処理に流し込む→結果を受け取り重ねて描画これをおこなうコードは次の通りです。
MainActivity.javaprivate byte[] latestFrame_Result; private ImageSegmentationModelExecutor imageSegmentationModel; public void drawTFThread() {; new Thread(new Runnable() { @Override public void run() { Bitmap beforeBmp = null; android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); imageSegmentationModel = new ImageSegmentationModelExecutor((Context)MainActivity.this, true); while (mFinished == false) { byte[] jpegFrame = latestLvFrame; if ( jpegFrame != null ) { //JPEG -> Bitmap BitmapFactory.Options options = new BitmapFactory.Options(); options.inMutable = true; Bitmap bitmap = BitmapFactory.decodeByteArray(jpegFrame, 0, jpegFrame.length); Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888); //crop Bitmap cropBitmap = Bitmap.createBitmap(bitmap, (bitmap.getWidth() - bitmap.getHeight())/2, 0, bitmap.getHeight(), bitmap.getHeight(), null, true); //TF処理 ModelExecutionResult result = imageSegmentationModel.execute(cropBitmap); //結果の描画 Bitmap result_bmp = result.getBitmapResult(); Bitmap afterResizeBitmap = Bitmap.createScaledBitmap(result_bmp,cropBitmap.getWidth(),cropBitmap.getHeight(),true); Canvas resultCanvas = new Canvas(output); resultCanvas.drawBitmap(bitmap,0,0,null); resultCanvas.drawBitmap(afterResizeBitmap,(bitmap.getWidth() - bitmap.getHeight())/2, 0,null); //ライブビューへの反映 ByteArrayOutputStream baos = new ByteArrayOutputStream(); output.compress(Bitmap.CompressFormat.JPEG, 100, baos); latestFrame_Result = baos.toByteArray(); } else { try { Thread.sleep(33); } catch (InterruptedException e) {e.printStackTrace();} }}}}).start(); }事前処理、事後処理で少し長くなっていますが、セグメンテーション処理をおこなうのは下記の1行です。
cropBitmapという名前の切り抜いたBitmapを渡しています。ModelExecutionResult result = imageSegmentationModel.execute(cropBitmap);そして、セグメンテーション結果は以下でBitmapとして受け取ります。
Bitmap result_bmp = result.getBitmapResult();この例では、セグメンテーション結果と元画像が重なった画像を取得していますが、セグメンテーション結果のマスクのみの取得も可能です。
結果の種類は、ModelExecutionResult.ktで定義されており、以下があります。
変数名 説明 bitmapResult 元画像とセグメンテーション結果が重なった画像 bitmapOriginal 元画像 bitmapMaskOnly セグメンテーション結果のマスクのみ画像 executionLog ログ情報 itemsFound セグメンテーションした物体の属性番号リスト さて、処理するコードを書いたので、これを呼び出す記述とプレビューに反映する記述をおこないます。
MainActivity.java//処理を呼び出す記述 protected void onResume() { ~省略~ drawTFThread(); } //プレビューに反映する処理(returnするBitmapを変更) private WebServer.Callback mWebServerCallback = new WebServer.Callback() { ~省略~ public byte[] getLatestFrame() { //return latestLvFrame; return latestFrame_Result; }};以上を変更し実行すると下図のようにプレビューにセグメンテーション結果が描画されます。
この画像中では人間しか分類していない為、1種類しか色分けされていませんが、複数検出されれば、複数の色で描画されます。
なお、現状ではフレームレートが1fps程度しか出ません。認識状態の確認には使えますが、コマ送りです。
(スマホでプラグイン動作のプレビューが見えるのは、やはり便利)撮影画像に対するセグメンテーション&画像処理
せっかく人領域と背景領域がセグメンテーションにより分離できるようになったので、
これを撮影画像に対して反映し、簡単な処理をした画像を保存する仕組みを作ってみます。具体的には撮影ボタンが押されたことをトリガとして、撮影画像のファイルパスを取得し、その画像に前述と同じセグメンテーション処理をおこないます。
そして、セグメンテーション結果から人物と背景領域に異なる画像処理をかけて保存します。これらの処理はTakePictureTask.Callback内のonTakePictureに記述します。
(THETAの撮影画像利用に関しては右記の記事が参考になります。→画像処理を含むTHETAプラグインの実装方法【THETAプラグイン開発】コードは以下の通りです。
MainActivity.javapublic static final String DCIM = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath(); public void onTakePicture(String fileUrl) { //撮影済み画像のファイルパス取得 Matcher matcher = Pattern.compile("/\\d{3}RICOH.*").matcher(fileUrl); if (matcher.find()) { fileUrl = DCIM + matcher.group(); Log.d(TAG,matcher.group()); } //JPEG読み込み、Bitmap化 BitmapFactory.Options options = new BitmapFactory.Options(); options.inMutable = true; File file = new File(fileUrl); Bitmap bitmap_input = null; try(InputStream inputStream = new FileInputStream(file)) { bitmap_input = BitmapFactory.decodeStream(inputStream); } catch (IOException e) { e.printStackTrace(); } //crop Bitmap cropBitmap = Bitmap.createBitmap(bitmap_input, (bitmap_input.getWidth() - bitmap_input.getHeight())/2, 0, bitmap_input.getHeight(), bitmap_input.getHeight(), null, true); //TF処理 ModelExecutionResult result = imageSegmentationModel.execute(cropBitmap); //結果の処理(マスク画像を抽出し、それをリサイズ) Bitmap result_bmp_mask = result.getBitmapMaskOnly(); Bitmap afterResizeBitmap = Bitmap.createScaledBitmap(result_bmp_mask,cropBitmap.getWidth(),cropBitmap.getHeight(),true); //人物領域をマスク画像に基づいて抽出 Bitmap output_Front = Bitmap.createBitmap(bitmap_input.getWidth(),bitmap_input.getHeight(), Bitmap.Config.ARGB_8888); Canvas resultCanvas_Front = new Canvas(output_Front); Paint paint = new Paint(); resultCanvas_Front.drawBitmap(cropBitmap,(bitmap_input.getWidth() - bitmap_input.getHeight())/2, 0,paint); paint.setFilterBitmap(false); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); resultCanvas_Front.drawBitmap(afterResizeBitmap,(bitmap_input.getWidth() - bitmap_input.getHeight())/2, 0,paint); paint.setXfermode(null); if (cropBitmap != null) { cropBitmap.recycle(); cropBitmap = null; } if (afterResizeBitmap != null) { afterResizeBitmap.recycle(); afterResizeBitmap = null; } if (result_bmp_mask != null) { result_bmp_mask.recycle(); result_bmp_mask = null; } //背景領域を彩度0にする Bitmap output_back = Bitmap.createBitmap(bitmap_input.getWidth(),bitmap_input.getHeight(), Bitmap.Config.RGB_565); Canvas resultCanvas_Back = new Canvas(output_back); Paint paint_ColorFilter = new Paint(); ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); paint_ColorFilter.setColorFilter(f); resultCanvas_Back.drawBitmap(bitmap_input,0,0,paint_ColorFilter); //人物領域と背景領域を重ね合わせ Bitmap output = Bitmap.createBitmap(bitmap_input.getWidth(),bitmap_input.getHeight(), Bitmap.Config.RGB_565); Canvas resultCanvas = new Canvas(output); resultCanvas.drawBitmap(output_back,0,0,null); resultCanvas.drawBitmap(output_Front,0,0,null); //画像保存処理開始 try { FileOutputStream fos = null; String targetFileName = fileUrl.substring(fileUrl.length() - 11); targetFileName = targetFileName.substring(0,7); int addNum = Integer.parseInt(targetFileName) + 1; String outputFileName = fileUrl.substring(0,fileUrl.length() - 11) + String.format("%07d", addNum)+".JPG"; fos = new FileOutputStream(outputFileName); output.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.close(); String[] FolderUrls = new String[]{"DCIM/100RICOH"}; notificationDatabaseUpdate(FolderUrls); } catch (Exception e) { Log.e("Error", "" + e.toString()); } startPreview(mGetLiveViewTaskCallback, previewFormatNo); }また、画像を保存するためにマニフェストファイルに外部ストレージアクセスを追記します。
AndroidManifest.xml<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>ポイントとしてはマスク画像を利用した合成にPorterDuffの機能を利用しています。
PorterDuffは、キャンバス上でBitmapの合成を可能にする機能です。
これによって、マスク画像と撮影画像に比較演算をおこない、撮影画像から人物領域を抜き出しています。結果
撮影ボタンを押すと、下図のような画像が保存されます。
人物の領域だけ色が残っているのが分かるかと思います。
まとめ
今回はTensorFlow Liteのセグメンテーション処理をTHETA上でおこなう方法を紹介しました。
機械学習で撮影対象を認識し、対象ごとに異なる処理をかけることは最近のスマホアプリでもトレンドだと思いますが、カメラ単体で完結するのは珍しいと思いますので、ぜひ興味があればTHETAプラグイン、触ってみてください。RICOH THETAプラグインパートナープログラムについて
THETAプラグインをご存じない方はこちらをご覧ください。
パートナープログラムへの登録方法はこちらにもまとめてあります。
QiitaのRICOH THETAプラグイン開発者コミュニティ TOPページ「About」に便利な記事リンク集もあります。
興味を持たれた方はTwitterのフォローとTHETAプラグイン開発コミュニティ(Slack)への参加もよろしくおねがいします。
- 投稿日:2020-05-29T01:59:07+09:00
楽天モバイルで自社回線かパートナー(KDDI)回線のどちらに接続しているか判定する
前置き
筆者はデジモノガジェットが好きなので、楽天モバイルのRakuten UN-LIMITを契約しています。
ご存知の方も多いと思いますが、楽天モバイルは後発のキャリアですので自社の回線のエリアが狭く、パートナー(KDDI)回線でエリアを補強しています。
自社回線は容量無制限でパートナー(KDDI)回線は5GBという制限があるので、リアルタイムでどちらの回線に接続しているか把握できれば便利だと思い調査しました。構成
名前 バージョン macOS Catalina 10.15.4 AndroidStudio 3.6.3 Kotlin 1.3.72 AVD(API) 使用しません 実機 SH-RM11 判定ロジック
世間では電波の周波数(バンド)を取得して判断していることが多いと思います。
バンド 事業者 3 楽天モバイル自社回線 18 パートナー(KDDI)回線 この記事では接続中の基地局の情報を取得して確認してみます。
テスト方法
楽天モバイル公式対応の端末(SH-RM11)で自社回線エリアとパートナー(KDDI)回線エリアの境界付近で検証します。
自宅が境界付近で自社回線に接続されたりパートナー(KDDI)回線に接続されたりしていまして、実験に適しています。ソースコードの説明
MainActivity.ktpackage com.devnokiyo.bandscope import android.Manifest import android.content.pm.PackageManager import android.os.Bundle import android.telephony.* import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat class MainActivity : AppCompatActivity() { private lateinit var telephonyManager: TelephonyManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Manifest.permission.ACCESS_COARSE_LOCATIONのパーミッションを取得して良いか確認します。 // 〜〜 省略 〜〜 // TelephonyManagerを取得します。 telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager } public override fun onPause() { super.onPause() // リスナーを解除します。 telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) } public override fun onResume() { super.onResume() // セルの情報が変わったり、電波状態が変更になったときのリスナーを登録します。 telephonyManager.listen( phoneStateListener, PhoneStateListener.LISTEN_CELL_INFO or PhoneStateListener.LISTEN_SIGNAL_STRENGTHS ) } public override fun onDestroy() { super.onDestroy() } /** * PhoneStateListenerを定義します。 */ private val phoneStateListener = object : PhoneStateListener() { /** * 接続中の基地局の情報が変更された時のイベントです。 */ override fun onCellInfoChanged(cellInfo: MutableList<CellInfo>?) { super.onCellInfoChanged(cellInfo) // SIMカードを抜いたときはnullになっていました。 cellInfo ?: return showCellInfo(cellInfo) } /** * 電波強度が変化した時のイベントです。 */ override fun onSignalStrengthsChanged(signalStrength: SignalStrength?) { super.onSignalStrengthsChanged(signalStrength) // Manifest.permission.ACCESS_COARSE_LOCATIONのパーミッションを取得しているか判定します。 // 〜〜 省略 〜〜 showCellInfo(telephonyManager.allCellInfo) } private fun showCellInfo(cellInfos: List<CellInfo>) { // LTE(4G)の基地局のうち信号があるものを抽出します。 val cellInfoLtes = cellInfos.filter { q -> q is CellInfoLte && q.isRegistered } .map { q -> q as CellInfoLte } // LTE(4G)の基地局情報をログに出力します。 cellInfoLtes.forEach { q -> Log.d("TestApp", "Cell ID is ${q.cellIdentity.ci}") Log.d( "TestApp", "Mobile Network Operator is ${q.cellIdentity.mobileNetworkOperator}" ) Log.d("TestApp", "Earfcn is ${q.cellIdentity.earfcn}") Log.d("TestApp", "Operator Alpha Short is ${q.cellIdentity.operatorAlphaShort}") } } } }ログの出力内容
実機をUSBデバックしてサンプルアプリを実行してみます。
Cell IDは位置が割り出せると聞きますので、一部マスクさせて頂きますが、自社回線に接続されたりパートナー(KDDI)回線に接続されたりしていることが確認できました。2020-05-09 00:30:20.823 10205-10205/com.devnokiyo.bandscope D/TestApp: Cell ID is 37*****1 2020-05-09 00:30:20.823 10205-10205/com.devnokiyo.bandscope D/TestApp: Mobile Network Operator is 44050 2020-05-09 00:30:20.823 10205-10205/com.devnokiyo.bandscope D/TestApp: Earfcn is 5900 2020-05-09 00:30:20.823 10205-10205/com.devnokiyo.bandscope D/TestApp: Operator Alpha Short is KDDI 2020-05-09 00:32:17.033 10205-10205/com.devnokiyo.bandscope D/TestApp: Cell ID is 36*****3 2020-05-09 00:32:17.033 10205-10205/com.devnokiyo.bandscope D/TestApp: Mobile Network Operator is 44050 2020-05-09 00:32:17.033 10205-10205/com.devnokiyo.bandscope D/TestApp: Earfcn is 5900 2020-05-09 00:32:17.034 10205-10205/com.devnokiyo.bandscope D/TestApp: Operator Alpha Short is KDDI 2020-05-09 00:32:44.043 10205-10205/com.devnokiyo.bandscope D/TestApp: Cell ID is 34*****6 2020-05-09 00:32:44.043 10205-10205/com.devnokiyo.bandscope D/TestApp: Mobile Network Operator is 44050 2020-05-09 00:32:44.043 10205-10205/com.devnokiyo.bandscope D/TestApp: Earfcn is 5900 2020-05-09 00:32:44.043 10205-10205/com.devnokiyo.bandscope D/TestApp: Operator Alpha Short is KDDI 2020-05-09 00:35:43.779 12740-12740/com.devnokiyo.bandscope D/TestApp: Cell ID is 88*****4 2020-05-09 00:35:43.779 12740-12740/com.devnokiyo.bandscope D/TestApp: Mobile Network Operator is 44011 2020-05-09 00:35:43.779 12740-12740/com.devnokiyo.bandscope D/TestApp: Earfcn is 1500 2020-05-09 00:35:43.779 12740-12740/com.devnokiyo.bandscope D/TestApp: Operator Alpha Short is Rakuten 2020-05-09 00:40:27.566 14476-14476/com.devnokiyo.bandscope D/TestApp: Cell ID is 88*****6 2020-05-09 00:40:27.567 14476-14476/com.devnokiyo.bandscope D/TestApp: Mobile Network Operator is 44011 2020-05-09 00:40:27.567 14476-14476/com.devnokiyo.bandscope D/TestApp: Earfcn is 1500 2020-05-09 00:40:27.567 14476-14476/com.devnokiyo.bandscope D/TestApp: Operator Alpha Short is Rakuten終わりに
本当はServiceで通知バーにリアルタイム表示するアプリを作成する予定でしたが、新しく買ったOPPOのスマホは通知バーにうまく表示できないようです。
先日ツイートをしてしまったくらい、すっかり開発意欲が萎えてしまいました