こちらの記事について、内容・翻訳・視点・長さなど、皆様のご意見をお送りください。今後の記事製作の参考にしたいと思います。
Thank you! Your feedback has been received.
There was a problem submitting your feedback, please try again later.
こちらの記事の感想をお聞かせください。
ディスプレイモジュール「Riverdi IoT」と音声認識システム「EasyVR 3 Plus」を用いた、話者照合とPINによる2要素認証を行うセキュリティーシステム
最近は、シンプルな音声コマンドでデバイスを操作する便利さがすっかり定着している。
これは、もともと音声制御システムを組み込んでいた家電メーカーにとって無視できないトレンドだ。製品にGoogleアシスタント、Siri、Alexaといった本格的なアシスタントを導入するには、デバイスの演算処理能力がフル機能のAndroidシステムやiOSシステムを動作できるレベルのものでなくてはならない。これを実現しようとすると、設計者はデバイスに十分な計算能力を与えることを強いられ、ハードウェア領域・プログラミングの両方で製品開発期間が大幅に増加する。
話者照合を用いた2要素認証
私たちは、デジタルセキュリティで同様の便利さを実現したいと考えている。そこで、ディスプレーモジュール「Riverdi」に2要素認証を導入する方法を紹介するデモを用意した。それはSensoryの革新的なテクノロジーを利用した話者認証だ。
この記事では、インテリジェント ディスプレーモジュール「Riverdi IoT」、専用のZerynthプログラミング環境(Pythonを使用した便利なアプリケーション制作が可能)、音声認識モジュール「Fortebit EasyVR 3 Plus」を使用して、PINコードの入力と個人パスワードの読み上げを使った2要素認証セキュリティシステムを作る。ユーザーシステムとのやり取りは、Riverdi IoTの5インチのディスプレイに表示されるグラフィカルなユーザーインターフェイスとEasyVR 3 Plusモジュールによって生成されるオーディオメッセージを通じて行う。
導入したプロジェクトのブロック図
コードを書き始める前に、アプリケーションのブロック図を分析する。
シンプルなグラフィカルインターフェイスについては、各画面の説明を明瞭で機能的なものにするために時間をかける価値がある(後出の図1を参照)。
- Riverdiロゴ – 最初の画面でRiverdiのロゴを表示し、ディスプレイの初期化の完了を確定する。
- Riverdiスクリーンセーバー – スクリーンセーバーはデバイスがアイドル状態になると表示される。シンプルなグラフィックが表示され、その位置は絶え間なく変化する。Fortebit EasyVRモジュールにより、「ハロー、デバイス」というトリガーワードを声に出すだけでディスプレイが起動する。トリガーコマンドでは、音声パスワードで利用されている話者照合(SV)テクノロジーとは対照的な不特定話者認識(SI)テクノロジーを採用している。
- PINパッド – 次にユーザーはPINコードを入力する(対応するメッセージはオーディオメッセージとして再生される)。PINコードが正しく入力されると、ユーザーは再度コードを入力するよう求められる。一定時間操作がない状態が続くと、アプリケーションはデフォルトのスクリーンセーバーディスプレイモードに切り替わる。正しいPINコードを入力すると、認証の次の段階に進む。
- 音声パスワードの入力 – ユーザーは個人パスワードを声に出して言う必要がある(特定話者(SD)オプションで音声認識)。アクセス許可画面が表示され、5秒間電磁ロックを制御するリレーがオンになって、与えられたPINコードとユーザーに割り当てられたパスワードの正しい端末が確認される。時間切れになると、ドアが再びロックされ、ユーザーインターフェイスがスクリーンセーバー表示ステージに戻る。
2要素認証:
図1. 導入したアクセスモジュールのブロック図
ハードウェアの選定
前のセクションで、私たちは導入したアクセスモジュールの操作モジュールを順調に定義することができた。導入する装置を選択した時点からの設計を見ると、以下の装置の層に関する要件をかなり正確に定義することができる。
- 音声認識アルゴリズムを実装し、音声メッセージを生成するためのEasyVRモジュール
- マイクロコントローラと画面径5インチの高品質ディスプレイで構成された一体型モジュール
- はんだ接続を行わずに装置を簡単に拡張できるモジュール(EasyVRモジュールを備えたmicroBUSコネクタの利用が可能)
- ハイレベルな言語(Pythonなど)で便利にコードを作成できる能力を提供する、フレームワークで選択した装置のオプションサポート
本プロジェクトに要する機能要素、すなわち:
- 静電容量方式タッチパネル対応の高品質なディスプレイ、
- microBUSコネクタ
- Python言語で組込ソフト開発できるZerynth対応
といった事を考えると、Riverdi IoTディスプレイモジュールが最適解として浮かびあがる(図2)。Riverdi IoTディスプレイモジュールは、ESP32マイコンにグラフィックコントローラ「Bridgetek BT81x」を搭載したLCDディスプレイ(5インチ径で800 x 480の解像度)だ。しかもRiverdiのすべてのインテリジェントIoTディスプレイには、Zerynth Studioライセンスがプリインストールされているため、入手後すぐにPythonでプログラミングを始めることができる。
図2. Riverdi IoTディスプレイモジュール
EasyVR 3 Plus – このモジュールは、音声の認識・分析と事前に録音されているオーディオサンプルの再生機能を担い、ディスプレイに表示されるグラフィカルユーザーインターフェイスを補完する役割を果たす。EasyVRモジュールの重要な機能は、「不特定話者認識」(SI)、「特定話者認識」(SD)、「話者認識」(SV)のコマンドの区別です。このモジュールには、デフォルトで、自動化システムで一般的なベーシックSIコマンドと、指示・数字・アクションコマンドを定義するジェネリックコマンドがプリロードされている。また、専用のQT2SI Liteソフトウェアを使用して、カスタマイズしたSI命令をアップロードする能力も備えている。
図3. EasyVR 3 (Plus)音声認識モジュール
Relay click – MikroElektronikaのMikroBUSボード (820-9858) 。2つのリレー出力と、モジュールのLEDを介したステータス信号を備えている。導入したプロジェクトでは、リレー出力が電磁ロックの操作制御を担う。図4. Relay clickリレーボード
システム全体の設置はこの上なく簡単だ。
基板上のマーキングに従い、Relay clickとEasyVRモジュールをRiverdi IoTディスプレイモジュールにあるmicroBUSソケットに接続、micro-USBケーブルを使用して準備したセットをコンピュータに接続して、完了だ。これでソフトウェアの準備を開始できる。
開発環境
Zerynthは 幅広いマイコンに対応したPythonプログラミング開発環境(C言語の関数を実装するオプションに対応)だ。対応ハードウェアプラットフォーム(Riverdi IoTディスプレイなど)と既製ソフトウェアのライブラリ(EasyVRモジュールの操作を容易にするライブラリを含む)の数が多く、対象のアクセスモジュールソフトウェアの準備が大幅に速くなり、容易になる。
図5. 統合コードエディタ – Zerynth Studio
EasyVR Commander – EasyVRモジュールの管理(自分の音声コマンドの作成、オーディオサンプルのアップロード、モジュールの設定など)を目的としており、開発者がシンプルで直感的なグラフィックインターフェイスを搭載した専用のEasyVR Commanderアプリケーションを用意している(図6参照)。
図6. EasyVR Commanderのメインウィンドウ
EasyVRモジュールの設定
Fortebit EasyVR 3モジュールを備えた装置の設定を開始する。これを行うため、付属のUSB-UART信号コンバータを使ってモジュールとコンピュータを接続する。EasyVR Commanderアプリケーションを起動したら、シリアルポートを選択し、[Connect (接続)]ボタンをクリックする。アプリケーションがモジュールでプログラムされた現在のコマンドグループを自動的に読み込み、リストに表示する(図7)。
図7. EasyVRモジュールから読み込んだコマンドグループのリスト
採用したコンセプトによると、ユーザーパスワードは特別な「Password」グループ(16の番号)下に配置され(最大5つのパスワードを保存可能)、話者照合(SV)テクノロジーに従って保存・認証される(特定話者認識テクノロジーとは対象的に、SVを利用して保存されたパスワードは、周辺のノイズの的確な除去とマイクとの距離の影響をより受けやすくなる)。スクリーンセーバー表示ステージからシステムをウェイクアップするため、「Grammar」グループのコマンドを使用する。ユーザーは「Grammar」グループに、QuickT2SIソフトウェア(このモジュールはEasyVR 3 Commanderソフトウェアで追加的にインストールすることが可能)を使って作成した自分のSIコマンドセットを置くことができる。モジュールのメーカーは、コマンドセットを「Grammar」セクションにアップロードする一般的な操作方法を紹介した短い説明ビデオを用意している。
自分のGrammarsセットを作成するプロセスの代替策として、既製のGrammarsセットを使用することもできる。既製のGrammarsセットは、EasyVRモジュールメーカーのWebサイトで入手できる。
自分のGrammarsセットを作成するプロセスの代替策として、既製のGrammarsセットを使用することもできる。既製のGrammarsセットは、EasyVRモジュールメーカーのWebサイトで入手できる。
図8. Group 1内で定義されるオーディオサンプルのセット
音声認識プロセスパラメータと、ユーザーに対するマイクの位置などに関連するパラメータは、[Recognition Settings (認識設定)]タブで厳密に設定できる(図9)。
図9. [Recognition Settings (認識設定)] ウィンドウ
Zerynth – 環境のインストールとデバイスの仮想化
選択したハードウェアソリューションの完全性により、ソフトウェアに直接関連するプロセスに迅速に移ることができる。私たちは、クロスプラットフォームである(Windows、Linux、macOSに対応)、Zerynth Studio開発環境をダウンロードするところからこのプロセスを開始する。
- インストーラを起動し、インストール方法の選択(ユーザーが事前にライブラリリポジトリをダウンロードしている場合、オンライン又はオフラインから選択)する。その後、ソフトウェアバージョンの選択を行う(記事作成時点の最新のバージョンはr2.3.0)。
- インストールプロセスが完了後、micro-USBケーブルを使用してRiverdi IoTディスプレイとコンピュータを接続する。
- 新しい仮想マシンを作る。仮想マシンを作るには、[Device info (デバイス情報)]ドロップダウンメニューからデバイスを選択し、[Create (作成)]ボタンを押して新しい仮想マシンを作成する。続いて、[Virtualize (仮想化)]ボタンをクリックして、デバイスを仮想化する(図10)。
図10. Zerynth環境における新しいRiverdi IoTディスプレイデバイスの登録と仮想化
デバイスの仮想化プロセスが終了したら、プログラミング段階に入る準備の完了だ。
Riverdi IoTディスプレイモジュールソフトウェア
作成したコードの可読性を高めるため、その構造を別個の2つのファイル(プログラムの主な関数を実行するコードを含むmain.pyとユーザーインターフェイスに関与する関数が置かれるgui.py)に分割する。ここでは、BT81xグラフィックコントローラとEasyVR音声認識モジュールをサポートするため、main.pyファイルライブラリでインポートすることから、コードの編集を開始する。
from riverdi.displays.bt81x import ctp50
from bridgetek.bt81x import bt81x
from fortebit.easyvr import easyvr
次のステップでは、シリアルポートの設定を進めます。SERIAL1は、EasyVRモジュールとの通信を担う。
streams.serial()
ser = streams.serial(SERIAL1, baud=9600, set_default=False)
evr = easyvr.EasyVR(ser)
スタート画面とスクリーンセーバーのニーズに対応するため、事前に準備したPNG形式のグラフィックを私たちのプロジェクトのグラフィックリソースに添付する。
new_resource('images/gui_riverdi_logo.png')
new_resource('images/screensaver.png')
EasyVR 3 Commanderを使用するというプロジェクトのニーズに対応するため、SoundTableセクションには、「access denied (アクセスが拒否されました)」、「please say your password (パスワードを教えてください)」など、ユーザー向けの音声メッセージを集めたオーディオサンプルのセットが含まれる。個々のオーディオサンプルの注文番号を使用しないようにするため、個々の音声記録を明確に定義された変数に割り当てる。
SND_Access_denied=1
SND_Access_granted=2
SND_Hello=3
SND_Please_repeat=4
SND_Please_say_your_password=5
SND_Hello_give_command=6
SND_Please_say_name=7
SND_Pwd_activated=8
次に、main.pyファイルのリンクが、SPIバスに接続されているBT81xコントローラを初期化する。
bt81x.init(SPI0, D4, D33, D34)
インストールの最終段階では、EasyVRモジュール設定をアプリケーションのニーズに合わせて調整する。初期化の段階で、精度などのパラメータと、その結果としてコマンド認識速度、話者の言語、又はタイムアウトの値を設定する。
evr.setLevel(2)
evr.setLanguage(evr.ENGLISH)
evr.setKnob(evr.STRICTER)
evr.setCommandLatency(evr.MODE_FAST)
evr.setTimeout(6)
このようにして、私たちは初期化プロセスを完了した。次の数行のコードで、EasyVRモジュールの検出プロセスを実行するシンプルなループを作成する。
while not evr.detect():
print("EasyVR not detected!")
print("EasyVR detected")
id = evr.getID()
print("EasyVR version id: %s" % id)
EasyVRモジュールの検出に成功したら、端末に以下を入力して、そのモジュールメモリの内容を確認してみる。
mask = evr.getGroupMask()
if mask != None:
for group in range(evr.PASSWORD + 1): #all groups: 0 to 16
if mask & 0x01 != 0:
count = evr.getCommandCount(group)
if group == evr.TRIGGER:
print("Trigger: "+ str(count))
elif group == evr.PASSWORD:
print("Password: " + str(count))
else:
print("Group " + str(group) + ": " + str(count))
for idx in range(count):
(name, train) = evr.dumpCommand(group, idx)
if not evr.isConflict():
print("%d %s Trained %d times, OK" % (idx,name,train))
else:
confl = evr.getWord()
if confl >= 0:
print("%d %s Trained %d times, Similar to Word %d" % (idx,name,train,confl))
else:
confl = evr.getCommand()
print("%d %s Trained %d times, Similar to Command %d" % (idx,name,train,confl))
mask >>= 1
図11. EasyVRモジュールのメモリ内容の表示
最初のユーザーインターフェイスウィンドウを表示する準備ができた。続行する前に、BT81xコントローラ用のZerynth APIドキュメンテーションをよく読んでほしい。
作業速度を上げるため、BT81xコントローラの内部RAMメモリに画像をロードする関数を作成する(RAM_Gメモリ領域のアドレス0から)。
def loadImage(image):
bt81x.load_image(0, 0, image)
続く引数が、それぞれ、RAMのアドレス、追加のオプション、ロードされた画像を表す変数を参照する。
メモリにある画像は、gui.pyファイルにあるshowLogo()関数を使って表示される。画面のクリーニングからこの関数を開始する。
def showLogo():
bt81x.dl_start()
bt81x.clear_color(rgb=(0xff, 0xff, 0xff))
bt81x.clear(1, 1, 1)
次に、Bitmapクラスのオブジェクトを作成し、画像の座標を引数として与えるdraw()メソッドを使って、ディスプレイで画像を描く。
image = bt81x.Bitmap(1, 0, (bt81x.ARGB4, 642 * 2), (bt81x.BILINEAR, bt81x.BORDER, bt81x.BORDER, 642, 144))
image.prepare_draw()
image.draw(((bt81x.display_conf.width - 642)//2, (bt81x.display_conf.height - 144)//2), vertex_fmt=0)
最後に、フレームを終了し、現行のディスプレイリストを置換する。
bt81x.display()
bt81x.swap_and_empty()
main.pyファイルに、gui.loadImage()関数とgui.showLogo()関数が加えられる。
gui.loadImage('gui_riverdi_logo.png')
gui.showLogo()
sleep(3000)
得られた効果を図12に表示する。
図12. loadImage()メソッドとshowLogo()メソッドの呼び出しの効果
インターフェイスのどの要素が現在表示されているかを示す変数screenLayoutは、ユーザーインターフェイスのその後の画面の推移を担う。画面選択はメインプログラムのループで実行される。
if screenLayout == 1:
gui.loadImage("screensaver.png")
#screensaver logo parameters
screensaver_logo_width = 300
screensaver_logo_height = 75
evr.recognizeWord(4)
cnt = 0
ここで、スクリーンセーバーの表示と「ハローデバイス」のフレーズ待ちに関連する機能を認識しておく(Grammar 4グループコマンドの認証の有効化による)。
while screenLayout == 1:
if evr.hasFinished():
if evr.getWord() == 0:
screenLayout = 2
break
evr.recognizeWord(4)
else:
cnt += 1
sleep(100)
if (cnt == 20):
x = random(1,bt81x.display_conf.width - screensaver_logo_width)
y = random(1,bt81x.display_conf.height - screensaver_logo_height)
gui.showScreensaver(x,y)
cnt = 0
私たちは、条件screenLayout = 1のmain whileループで、繰り返し「ハローデバイス」のフレーズが正しく認識されていることを確認し、スクリーンセーバーの内容を更新する。returnが認識されると、screenLayout変数が変更され、while()ループが中断される。showScreensaver(x,y)のボディーは、メソッドshowImage()の修正版だ。
def showScreensaver(x,y):
bt81x.dl_start()
bt81x.clear_color(rgb=(0x00, 0x00, 0x00))
bt81x.clear(1, 1, 1)
image = bt81x.Bitmap(1, 0, (bt81x.ARGB4, (300) * 2), (bt81x.BILINEAR, bt81x.BORDER, bt81x.BORDER, 300, 75))
image.prepare_draw()
image.draw((x, y), vertex_fmt=0)
bt81x.display()
bt81x.swap_and_empty()
図13. スクリーンセーバーのウィンドウ
ここで、オンスクリーンキーボードを使用してPINを入力するユーザー認証の最初のフォームに進もう。以下は、ユーザーインターフェイスのこの部分の表示を担うmain.pyの一部だ。
counter = 0
while (screenLayout == 2):
if (wait == True):
wait = False
sleep(2000)
gui.pinScreen(pin)
if counter == 500:
screenLayout = 1
sleep(10)
counter += 1
pin=""
gui.pinScreen()のタスクはテンキーの表示だ。この操作は、bt81x.add_keys()メソッドを使用して簡単に行うことができる。
bt81x.track(430, 50, 350, 70, 0)
bt81x.add_keys(430, 50, 350, 70, 30, 0, "123")
bt81x.add_keys(430, 130, 350, 70, 30, 0, "456")
bt81x.add_keys(430, 210, 350, 70, 30, 0, "789")
bt81x.add_keys(430, 290, 350, 70, 30, 0, ".0C")
テンキーが[Connect (接続)]ボタンで補完され、次に[TAG (タグ)]フィールドの値を割り当てる。これにより、プログラムの後の段階でボタンの操作が可能になる。
btn = bt81x.Button(430, 370, 350, 70, 30, 0, "Connect")
bt81x.tag(1)
bt81x.add_button(btn)
図14. テンキーが表示されたユーザーインターフェイス
ボタンのサポートには、main.pyファイルで定義したタッチパネルからコールバックイベントを処理する関数が必要となる。この場合、pressed()関数がすべてのイベントを担う。
bt81x.touch_loop(((-1, pressed), ))
Body function pressed ():
def pressed(tag, tracked, tp):
global screenLayout
global pin
global wait
global counter
global user2
#if we are in pinscreen
if (screenLayout == 2):
counter = 0
user2 = False
if ((tag != 67) and (tag !=1)):
if (len(pin) >= 4):
return
pin = pin + str(chr(tag))
elif ((tag == 67) and (len(pin) > 0)):
pin = pin[:-1]
elif ((tag == 1) and ((pin == valid_pin) or (pin == valid_pin2))):
if pin == valid_pin2:
user2 = True
screenLayout = 4
elif (len(pin) > 0):
wait = True
pin = ""
gui.showMessage("Access Denied")
#go to screensaver
evr.playSound(SND_Access_denied, evr.VOL_FULL)
画面にタッチしてpressed()関数を呼び出すたびに、デバイスのアイドル時間の検出を行うcounter変数がリセットされる。[Connect (接続)]ボタンを選択すると、入力した4桁のPINコードが確定される。事前に定義したPINコードの1つに対して違反があった場合、アクセスできない旨を知らせる短いメッセージがデバイスの画面に表示される(図15)。
図15. 不正なPINコード入力について知らせる画面
gui.pyで定義したshowMessage()関数を使って、アクセスの許可/拒否に関するメッセージを表示する。この関数では、画像のクリーニングとシンプルなテキストメッセージ(関数の引数によって渡された内容)の表示のみを行う。
def showMessage(text):
# start
bt81x.dl_start()
bt81x.clear(1, 1, 1)
#text
txt = bt81x.Text(400, 240, 31, bt81x.OPT_CENTERX | bt81x.OPT_CENTERY, text, )
bt81x.add_text(txt)
# display
bt81x.display()
bt81x.swap_and_empty()
PINを正しく入力すると、音声認証へとスムーズに進む。最初に、システムで音声メッセージを流し、ディスプレイにテキストメッセージを出して、ユーザーにパスワードの入力を促す(図15)。
if screenLayout == 4:
gui.showMessage("Enter Voice Password")
evr.playSound(SND_Please_say_your_password, evr.VOL_FULL)
図16. ユーザーにアクセスパスワードの入力を促すメッセージ
プログラムのメインループで、16番目のグループ(「Passwords」グループ)のコマンド認識を有効にして、returnフラグを待ちます。-1の値を取得した場合、音声フレーズが認識されなかったか、又はタイムアウトが発生したことを意味する。
while screenLayout == 4:
evr.recognizeCommand(16)
while not evr.hasFinished():
sleep(100)
while()ループのリリースでは、正しくない可能性のあるパスワードやタイムアウトについて戻された値をチェックする必要がある。この状況では、アクセスできない旨が通知され、パスワードの再入力を促される。
if ((evr.getCommand() == -1) and (not evr.isTimeout())):
gui.showMessage("Access Denied")
evr.playSound(SND_Access_denied, evr.VOL_FULL)
sleep(1000)
break
各ユーザーは、一貫性のあるデータのセット、つまりそのユーザーに割り当てられたPINコードと音声パスワードを提供することで認証される。そのため、音声パスワードに対するPINコードの割り当ての確認が必須だ。
if ((user2 and (evr.getCommand() != 2) and (evr.getCommand() != -1)) or ((not user2) and ((evr.getCommand() != 5) and (evr.getCommand() != -1)))):
gui.showMessage("Access Denied")
evr.playSound(SND_Access_denied, evr.VOL_FULL)
sleep(1000)
break
タイムアウトが発生した場合は、PIN入力画面を表示する。
if evr.isTimeout():
#if time out go to pin screen
screenLayout = 2
break
上記の条件のいずれにも適合せず、パスワードがユーザーのPIN番号と一致する場合、インターフェイスの次の段階へと進む。この段階では、認証が成功したユーザーにセキュリティで保護された領域へのアクセスを許可します(電磁ロックの開放時間は5秒間)。
if screenLayout == 5:
gui.showMessage("Access Granted")
evr.playSound(SND_Access_granted, evr.VOL_FULL)
sleep(1000)
relay_on()
for i in range(5):
time = str(5-i)
gui.showMessage("time left: " + time)
sleep(1000)
relay_off()
screenLayout = 1
図17. ユーザーに正しいアクセスについて知らせる画面
この段階で、少しの間Relay clickリレーモジュールに目を向ける。特殊なハードウェア構成を必要としない限り、プログラムコードで適切な出力制御ピンを選択し、その操作を制御するシンプルな関数を作成する必要がある。図18に、Relay clickモジュールのピン配列図を示す。
図18. Relay clickのピン配列
導入したプロジェクトでは、電磁ロックはRL1リレーを介して制御される。Relay clickモジュールは、Riverdi IoTディスプレイモジュール上のmicroBUS 1ソケットに接続されている。図19はソケットのピン配列図だ。
図19. microBUS1ソケットのピン配列
そのため、RL1リレーの制御はD23を得ることで実行される。
pinMode(D23, OUTPUT)
def relay_on():
digitalWrite(D23, HIGH)
def relay_off():
digitalWrite(D23, LOW)
完成したプロジェクトのフル機能を次のビデオで紹介している。
まとめ
今回のデモで、Riverdi IoTディスプレイとFortebit EasyVR 3 PlusをPythonで簡単にプログラミングできることをお分かりいただけただろうか?Riverdi IoTディスプレイの購入をご検討の方は、公式ページを確認してほしい。
----------------------------------------------------------
参考情報:
Riverdi official website: www.riverdi.com
Zerynth official website: www.zerynth.com
Fortebit official website: www.fortebit.tech
Relay click: (820-9858)
Riverdi IoT modules: https://riverdi.com/product-category/iot/
EasyVR 3: https://fortebit.tech/easyvr-3-plus/
Riverdi GitHub: https://github.com/riverdi/riverdi-speech-recognition-demo