スマートスピーカーを写すと「アレクサ」とかしゃべるAIカメラ


いま流行りのAIカメラ M5StickVを使って、
スマートスピーカーを画像認識して、ウェイクワードをしゃべらせてみました。
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Niwatori wake-word speaker <a href="https://t.co/MZg5V2WXpa">pic.twitter.com/MZg5V2WXpa</a></p>&mdash; Ryota Ikeuchi (@IkeuchiRyota) <a href="https://twitter.com/IkeuchiRyota/status/1213480718711410688?ref_src=twsrc%5Etfw">January 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
（デモ動画の3番目のはFire HD（amazonタブレット）です。Alexa用に使用しているのでウェイクさせようとしました。出来ていませんが）




Niwatori  wake-word speaker
![gray__bird_niwatori_chabo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239549/cee5907c-273e-41b3-1615-ee797da781c9.jpeg)

# 概要
カメラ画像に対して画像判別を行い、スマートスピーカーと認識されれば、ウェイクワードをスピーカーから再生します。（[Open JTalk] (http://open-jtalk.sp.nitech.ac.jp/index.php)を使用して音声ファイルを予め用意しておく）
Google Homeを写すと「OK Google」としゃべり、
Clovaなら「ねえ Clova」、
Fire HD（amazonタブレット）なら「アレクサ」と言います。
M5StickV単体で動作します。

何に役立つかは…とくに考えていませんが、
メガネに組み込んで、「しばらく見つめると、ウェイクワードを代わりに言ってくれる」というのは便利かも？（「アイズフリー（見る必要がない）」という音声UIのメリットを台無しにする使い方ですが）

画像認識や音声読み上げを試したくて作ったものです。


# AIカメラ　M5StickV
2019年夏ごろに発売された、"AIカメラ"です。いろいろすごくて話題になっています。

- CNNを高速に実行できる"AIチップ"を搭載
- カメラ、ディスプレイ、バッテリー、スピーカーなどが一体
- 基板むき出しじゃなくケース入り
- たったの3,000円

既にスマホという高性能な"AIカメラ"があるので、スマホには出来ない使い方を考えてみたいですね。

- 大量に買って、いろんな場所に取り付ける（安いので）
- 子供（ちびっこ）のおもちゃにする
- 機器に組み込んだり、建物に固定で設置する

とかでしょうか。


# 画像認識のモデル
認識対象が一般的なものなら、既にあるモデルをそのまま使えばOKです。
例えば、MobileNet v1の1,000クラスのモデルが公開されています。（https://www.maixhub.com/index.php/index/index/detail/id/2.html たぶんM5StickVでそのまま使える）
しかし、今回の認識対象は、「Google Home」とか、「Clova Friends Sally」とかです。そのようなものは用意されていませんので、自分でモデルをつくる必要があります。
ここが腕の見せ所であり、私のようなAIビギナーのがんばりどころなのですが・・・
**今回は[V-Training] (https://docs.m5stack.com/#/en/related_documents/v-training)というツールを使いました。**今回使用するAIカメラのメーカーが提供しているものです。
認識させたいものそれぞれ35枚ずつ写真を撮ってアップロードすれば、学習済みモデルをつくってくれるというものです。お手軽。
写真はM5StickVを使用して撮影します。撮影時に使うプログラムも用意されていて、とっても簡単です。
AIの開発の敷居がどんどん低くなっていることを感じますね。


# コード
V-Trainingを使うと、学習済みモデルとともにそれをM5StickVで動作させるためのコードも送られておきます。
これは、単に認識されたラベルを画面上に表示させる程度のものです。
今回は、そのラベルに応じて読み上げをさせたい。
読み上げの部分は[Brownie] (https://github.com/ksasao/brownie)のコードを参考にさせていただきました。
（そもそも、読み上げさせるというアイデア自体がBrownieの真似のようなものですが）

```python:boot.py
import image
import lcd
import sensor
import sys
import time
import KPU as kpu
from fpioa_manager import *

import KPU as kpu

import audio
import gc
import uos
from machine import I2C
from Maix import I2S, GPIO

lcd.init()
lcd.rotation(2)
i2c = I2C(I2C.I2C0, freq=400000, scl=28, sda=29)

fm.register(board_info.SPK_SD, fm.fpioa.GPIO0)
spk_sd=GPIO(GPIO.GPIO0, GPIO.OUT)
spk_sd.value(1) #Enable the SPK output

fm.register(board_info.SPK_DIN,fm.fpioa.I2S0_OUT_D1)
fm.register(board_info.SPK_BCLK,fm.fpioa.I2S0_SCLK)
fm.register(board_info.SPK_LRCLK,fm.fpioa.I2S0_WS)

wav_dev = I2S(I2S.DEVICE_0)

fm.register(board_info.BUTTON_A, fm.fpioa.GPIO1)
but_a=GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

fm.register(board_info.BUTTON_B, fm.fpioa.GPIO2)
but_b = GPIO(GPIO.GPIO2, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

fm.register(board_info.LED_W, fm.fpioa.GPIO3)
led_w = GPIO(GPIO.GPIO3, GPIO.OUT)
led_w.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_R, fm.fpioa.GPIO4)
led_r = GPIO(GPIO.GPIO4, GPIO.OUT)
led_r.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_G, fm.fpioa.GPIO5)
led_g = GPIO(GPIO.GPIO5, GPIO.OUT)
led_g.value(1) #RGBW LEDs are Active Low

fm.register(board_info.LED_B, fm.fpioa.GPIO6)
led_b = GPIO(GPIO.GPIO6, GPIO.OUT)
led_b.value(1) #RGBW LEDs are Active Low


def play_sound(filename):
    try:
        player = audio.Audio(path = filename)
        player.volume(100)
        wav_info = player.play_process(wav_dev)
        wav_dev.channel_config(wav_dev.CHANNEL_1, I2S.TRANSMITTER,resolution = I2S.RESOLUTION_16_BIT, align_mode = I2S.STANDARD_MODE)
        wav_dev.set_sample_rate(wav_info[1])
        while True:
            ret = player.play()
            if ret == None:
                break
            elif ret==0:
                break
        player.finish()
    except:
        pass


try:
    from pmu import axp192
    pmu = axp192()
    pmu.enablePMICSleepMode(True)
except:
    pass

try:
    img = image.Image("/sd/startup.jpg")
    lcd.display(img)
except:
    lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED)


task = kpu.load("/sd/model/vtraining_smartspeakers_1.kmodel")

labels=["G_Home","clove_sally","fire_hd","G_Home_mini"] #You can check the numbers here to real names.

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
sensor.run(1)

lcd.clear()


# isButtonPressedA = 0

while(True):
    img = sensor.snapshot()
    fmap = kpu.forward(task, img)
    plist=fmap[:]
    pmax=max(plist)
    max_index=plist.index(pmax)
    a = lcd.display(img)
    if pmax > 0.95:
        lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
        # if but_a.value() == 0  and isButtonPressedA == 0:
        if but_a.value() == 1:
            play_sound("/sd/voice/ja/"+str(max_index)+".wav")
            # isButtonPressedA = 1
    # if but_a.value() == 1:
    #     isButtonPressedA = 0
a = kpu.deinit(task)
```
Brownieでは（物体が認識されている状態で）ボタンを押すと読み上げですが、
今回は見つけた瞬間に読み上げとしてみました。
黙らせたいときにはボタンを押します。


# 課題と今後
スマートスピーカー以外のものを写したときの誤認識が多いです。
ざっくり言うと「とりあえず白ければGoogle Home」、「黄色のはClova Friends Sally」という感じです。
「どのスマートスピーカーでもない」という学習用データを与えていないので当然でしょうか。

やはり学習用データやモデルの工夫が重要そうです。
V-Trainingにおまかせでなく、自分で色々と試してみようと思います。
こちらのcolab notebookが詳しそうです：
[Creating an Image Classification Model for M5StickV by Transfer Learning](https://colab.research.google.com/drive/1mirG8BSoB3k87mh-qyY3-8-ZXj0XB6h6#scrollTo=sHpfWFHgSccx)


# 参考
セットアップなどは、[M5StickVで超お手軽エッジAI画像認識（からあげ様）] (https://karaage.hatenadiary.jp/entry/2019/08/14/073000)が参考になりました。