きっかけ
AR_FukuokaでLTした。やったことは星を利用したAR(*1)。この年になって星を使う事になるとは思わなかったが、やってよかったと思ってる。
星座を利用するって壮大
*1: 星は利用してない
やること
Androidのカメラで星とかを映すと何の星座か教えてくれるクリスマスにぴったりなアプリ。
それっぽい画像も透過的に表示してすごいクリスマス感が出るはず。
動きはこんな感じ。
- カメラを有効化
- ボタンを押して「何の星座か教えて」の音声を認識する
- カメラ画像に近似した星座を特定する
- 星座の方向や位置に合わせて星座のイメージを透過的に表示
- 音声で「これは○○座です」って発話
時間もないので下記点は妥協する。
- 「何の星座か教えて」以外の音声も認識する
- 星座の位置や方向に合わせない
- 何が映っても全部ペルセウス
- 音声は「これはペルセウスです」固定
何でもペルセウス。ペルセウス最高。
準備
OpenCVを使う。
Androidで OpenCV 4を使う方法とカメラライブビューの表示という素晴らしすぎる記事があるので同じところまでやる。この記事すごい。すごく助かる。
で 、 色 々 や り ま し た ! !
が、2019.12.06時点のAndroidStudioだと動かないことが判明したのでOpenCVのカメラライブビューをあきらめることにした。参照は残したまま別の方法を探る。
尚、できなかった理由は下記の通り。
- OnUnhandledKeyEventListenerがないとか言われてCameraViewが生成できない
- compileSdkVersionを27以下にすればOK☆
- AAPTでリソースのマージエラー。compileSdkVersionが28以上じゃないと使えないものが混ざる
- compileSdkVersionを28以上にすればOK☆
- OnUnhandle(ry
- compileSdkVersionを28以上にすればOK☆
- AAPTでリソースのマージエラー。compileSdkVersionが28以上じゃないと使えないものが混ざる
- compileSdkVersionを27以下にすればOK☆
多分すごい人が何とかすんじゃなかろうか。
改めて準備
Android SDKのCamera2を使う。
Android, Kotlin + Camera API v2 でカメラ機能を実装するという素晴らしすぎる記事があるので同じところまでやる。この記事すごい。すごく助かる。
ここまでできれば問題ないはず
SurfaceView + Camera2で実装した。コードは後でアップするのでそっちを見てほしい。
一つだけ注意点を上げるとすれば、AndroidのCamera2はややこしい
の一言に尽きる。
星座を教えてほしい的な音声を認識させる
コード書くのが面倒なので、怒りを爆発させて地球人を少しだけ超えるを流用する。
画面タップしたらインテント投げる感じにする。
SurfaceViewでイベント取って
surfaceView.setOnClickListener(object: View.OnClickListener{
override fun onClick(view: View?) {
DetectHoshiMitai()
}
})
星座を認識したら(してないけど)message投げて
fun DetectHoshiMitai(){
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
intent.putExtra(RecognizerIntent.EXTRA_PREFER_OFFLINE, true)
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, packageName)
val recognizer = SpeechRecognizer.createSpeechRecognizer(applicationContext)
recognizer.setRecognitionListener(object: RecognitionListener {
override fun onReadyForSpeech(p0: Bundle?) { }
override fun onRmsChanged(p0: Float) { }
override fun onBufferReceived(p0: ByteArray?) { }
override fun onPartialResults(p0: Bundle?) { }
override fun onEvent(p0: Int, p1: Bundle?) { }
override fun onBeginningOfSpeech() { }
override fun onEndOfSpeech() { }
override fun onError(p0: Int) {
}
override fun onResults(results: Bundle?) {
if (results == null) {
return
}
val texts = results!!.getStringArrayList(android.speech.SpeechRecognizer.RESULTS_RECOGNITION)!!
var detectKeyword = false
for (text in texts) {
if (text.indexOf("星座") >= 0 ||
text.indexOf("精度") >= 0 ||
text.indexOf("制度") >= 0 ||
text.indexOf("正座") >= 0 ) {
detectKeyword = true
}
}
if (!detectKeyword){
val message = Message.obtain()
message.what = DETECTED_NEED_SEIZA
handler.sendMessage(message)
}
}
})
recognizer.startListening(intent)
}
handlerで受ける
handler = object:Handler(){
override fun handleMessage(msg: Message) {
when(msg.what){
WAIT_CREATE_CAMERA ->{
if(state == StateInit){
state.enter()
}
}
DETECTED_NEED_SEIZA->{
}
}
super.handleMessage(msg)
}
}
星座を認識して星座を出しつつ教える(星座の認識はしない、出すのも教えるのもペルセウスのみ)
「この星座はペルセウスです」って音声出す。
Handlerで受け付ける
handler = object:Handler(){
override fun handleMessage(msg: Message) {
when(msg.what){
WAIT_CREATE_CAMERA ->{
if(state == StateInit){
state.enter()
}
}
DETECTED_NEED_SEIZA->{
TeachPeruseusu()
WritePeruseusu()
}
PERUSEUS_HIDING->{
peruseusuImageView.alpha = peruseusuImageView.alpha * 0.9f
if(peruseusuImageView.alpha <= 0.1){
peruseusuImageView.visibility = View.INVISIBLE
} else{
handler.sendEmptyMessageDelayed(PERUSEUS_HIDING, 100)
}
}
}
super.handleMessage(msg)
}
}
ペルセウス言う
fun TeachPeruseusu(){
if(tts.isSpeaking){
tts.stop()
}
tts.setPitch(0.1f)
var map = HashMap<String,String>()
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "PERUSEUS")
tts.speak("この星座はペルセウスです", TextToSpeech.QUEUE_FLUSH, map)
}
ペルセウス出す。
ペルセウスのImageViewを用意して
<?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">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp"/>
<ImageView
android:id="@+id/peruseusuImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
android:src="@drawable/perseus"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
透過的に出す
fun WritePeruseusu(){
peruseusuImageView.alpha = 0.5f
peruseusuImageView.visibility = View.VISIBLE
handler.sendEmptyMessageDelayed(PERUSEUS_HIDING, 100)
}
ふりかえり
猫とかティッシュとかでやるととてつもなくくだらなくて楽しい
この星座もペルセウス