星を使った(使ってない)AR

# きっかけ

[レジャー好きな人の支援](https://speakerdeck.com/miyatanam/ar-support-for-leisure-lovers)

<script async class="speakerdeck-embed" data-slide="1" data-id="a9a3a4e049484b47b986b2c5ba2dd849" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

AR_FukuokaでLTした。やったことは星を利用したAR(\*1)。この年になって星を使う事になるとは思わなかったが、やってよかったと思ってる。

![ペルセウス.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/3e4efa81-0307-0e10-f4be-88509ccad091.png)

<font color="dark_pink">星座を利用するって壮大</font>

*1: 星は利用してない

# やること

Androidのカメラで星とかを映すと何の星座か教えてくれるクリスマスにぴったりなアプリ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/731c20da-f096-ab0c-de87-9f827ca9ab2d.png)

それっぽい画像も透過的に表示してすごいクリスマス感が出るはず。
動きはこんな感じ。

 + カメラを有効化
 + ボタンを押して「何の星座か教えて」の音声を認識する
 + カメラ画像に近似した星座を特定する
 + 星座の方向や位置に合わせて星座のイメージを透過的に表示
 + 音声で「これは○○座です」って発話

時間もないので下記点は妥協する。

 + 「何の星座か教えて」以外の音声も認識する
 + 星座の位置や方向に合わせない
 + 何が映っても全部ペルセウス
 + 音声は「これはペルセウスです」固定

何でもペルセウス。ペルセウス最高。

# 準備
OpenCVを使う。

[Androidで OpenCV 4を使う方法とカメラライブビューの表示](https://qiita.com/iwatake2222/items/2642669419fdaa20a8a6)という素晴らしすぎる記事があるので同じところまでやる。この記事すごい。すごく助かる。

``で　、　 色 　々 　や 　り 　ま 　し 　た 　！ 　！``

が、2019.12.06時点のAndroidStudioだと動かないことが判明したのでOpenCVのカメラライブビューをあきらめることにした。参照は残したまま別の方法を探る。

尚、できなかった理由は下記の通り。

 + OnUnhandledKeyEventListenerがないとか言われてCameraViewが生成できない
   + compileSdkVersionを27以下にすればOK☆
     + AAPTでリソースのマージエラー。compileSdkVersionが28以上じゃないと使えないものが混ざる
       + compileSdkVersionを28以上にすればOK☆
         + OnUnhandle(ry 

多分すごい人が何とかすんじゃなかろうか。

# 改めて準備

Android SDKのCamera2を使う。

[Android, Kotlin + Camera API v2 でカメラ機能を実装する](https://qiita.com/nomunomu0504/items/05bb7aefcb6a04a8f6fc)という素晴らしすぎる記事があるので同じところまでやる。この記事すごい。すごく助かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/18846b59-0510-fe53-97c5-0162429c499a.png)

<font color="dark_pink">ここまでできれば問題ないはず</font>

SurfaceView + Camera2で実装した。コードは後でアップするのでそっちを見てほしい。
一つだけ注意点を上げるとすれば、`AndroidのCamera2はややこしい`の一言に尽きる。

# 星座を教えてほしい的な音声を認識させる

コード書くのが面倒なので、[怒りを爆発させて地球人を少しだけ超える](https://qiita.com/miyatama/items/d746fadf512e53dc6d42)を流用する。
画面タップしたらインテント投げる感じにする。

SurfaceViewでイベント取って

```kotlin
surfaceView.setOnClickListener(object: View.OnClickListener{
    override fun onClick(view: View?) {
	DetectHoshiMitai()
    }
})
```

星座を認識したら(してないけど)message投げて

```kotlin
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で受ける

```kotlin
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で受け付ける

```kotlin
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)
    }
}
```

ペルセウス言う

```kotlin
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
<?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>
```

透過的に出す

```kotin
fun WritePeruseusu(){
    peruseusuImageView.alpha = 0.5f
    peruseusuImageView.visibility = View.VISIBLE
    handler.sendEmptyMessageDelayed(PERUSEUS_HIDING, 100)
}
```


# ふりかえり

猫とかティッシュとかでやるととてつもなくくだらなくて楽しい

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42445/7ad49cba-a67a-18f4-7e6f-423f72d29603.png)

<font color="dark_pink">この星座もペルセウス</font>