Java
PHP
音声認識
sota
合成音声
ozvisionDay 20

Sotaにデータベースの値を喋り返してもらう

こんにちは!
株式会社オズビジョンでエンジニアインターンシップをしている@saike1119です。
普段は学生をしていて、AI(人工知能)や自然言語処理に強い研究室でぬくぬくしています。
そこでは卒業研究としてSotaを利用したアプリケーションを開発しています。
それに加えて、最近AIやロボット、スマートスピーカーが流行っているということで、Sotaを利用した簡単な処理の実装を記述していきたいと思います。
今回実装する処理はSotaに喋った内容をデータベースから探してきてもらいそれに関連する内容を返してもらうといった処理です。
イメージはこちらです。
スクリーンショット 2017-12-20 4.44.48.png

※Sotaの環境構築は以下の公式ページで行えますので、済ませて置いてください!

http://www.vstone.co.jp/sotamanual/index.php?Java%E3%81%A7%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B

Sota側を実装する

SotaはVStoneMagicとJavaのどちらかで開発できますが、今回はJavaで実装していきます。

Sotaに実装させるJavaファイルは以下の通りです。

SearchDbSota.java
//packageのsearchdbsotaは任意のpackage名に変更してください
package jp.vstone.searchdbsota;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

import jp.vstone.RobotLib.CPlayWave;
import jp.vstone.RobotLib.CRobotMem;
import jp.vstone.RobotLib.CRobotPose;
import jp.vstone.RobotLib.CRobotUtil;
import jp.vstone.RobotLib.CSotaMotion;
import jp.vstone.sotatalk.SpeechRecog;
import jp.vstone.sotatalk.TextToSpeechSota;

public class SearchDbSota {

    static final String TAG = "SearchDbSota";
    // 実行ファイルを指定
    static final String getText_url = "実行ファイルを指定"; // 例:http://192.168.11.10/SearchFruitsSotaSample/SearchFruits.php

    // VSMDと通信ソケット・メモリアクセス用クラス
    private static CRobotMem mem = new CRobotMem();
    private static CSotaMotion motion = new CSotaMotion(mem);
    // Sota用モーション制御クラス
    private static SpeechRecog recog = new SpeechRecog(motion);
    private static CRobotPose pose;

    public static void main(String[] args) {
        CRobotUtil.Log(TAG, "Start " + TAG);

        // VSMDと通信ソケット・メモリアクセス用クラス
        CRobotMem mem = new CRobotMem();
        // Sota用モーション制御クラス
        CSotaMotion motion = new CSotaMotion(mem);

        if (mem.Connect()) {
            // Sota仕様にVSMDを初期化
            motion.InitRobot_Sota();

            CRobotUtil.Log(TAG, "Rev. " + mem.FirmwareRev.get());

            // サーボモータを現在位置でトルクOnにする
            CRobotUtil.Log(TAG, "Servo On");
            motion.ServoOn();

            // 全ての軸を初期化
            pose = new CRobotPose();
            pose.SetPose(new Byte[] { 1, 2, 3, 4, 5, 6, 7, 8 } // id
                    , new Short[] { 0, -900, 0, 900, 0, 0, 0, 0 } // target pos
            );
            // LEDを点灯(左目:赤、右目:赤、口:Max、電源ボタン:赤)
            pose.setLED_Sota(Color.ORANGE, Color.ORANGE, 255, Color.ORANGE);

            motion.play(pose, 100);
            CRobotUtil.wait(100);
        }

        while (true) {
            pose();
            CPlayWave.PlayWave_wait(TextToSpeechSota.getTTSFile("何の果物について知りたいですか?やめる時は終了と言ってください。"));
            CRobotUtil.Log(TAG, "Mic Recording...");
            String fruitName = recog.getResponse(15000, 3);
            CRobotUtil.Log(TAG, fruitName);

            if (fruitName.contains("終了")) {
                CRobotUtil.Log(TAG, "finish...");
                break;
            }
            String speech_text = searchFruit(fruitName);
            CPlayWave.PlayWave_wait(TextToSpeechSota.getTTSFile(speech_text));
        }
    }

    /**
     * WebサーバにアクセスしてテキストをGETで取得する処理
     *
     * @param strGetUrl
     * @return
     */
    public static String getStringByCallGET(String strGetUrl) {

        HttpURLConnection con = null;
        StringBuffer result = new StringBuffer();

        try {

            URL url = new URL(strGetUrl);

            con = (HttpURLConnection) url.openConnection();

            con.setRequestMethod("GET");
            con.connect(); // URLにGETでリクエストを送信

            // HTTPレスポンスコード
            final int status = con.getResponseCode();
            if (status == HttpURLConnection.HTTP_OK) {
                // 通信に成功した
                // テキストを取得する
                final InputStream in = con.getInputStream();
                String encoding = con.getContentEncoding();
                if (null == encoding) {
                    encoding = "UTF-8";
                }
                final InputStreamReader inReader = new InputStreamReader(in, encoding);
                final BufferedReader bufReader = new BufferedReader(inReader);
                String line = null;
                // 1行ずつテキストを読み込む
                while ((line = bufReader.readLine()) != null) {
                    result.append(line);
                }
                bufReader.close();
                inReader.close();
                in.close();
            } else {
                // System.out.println(status);
            }

        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            if (con != null) {
                // コネクションを切断
                con.disconnect();
            }
        }
        // System.out.println("result=" + result.toString());

        return result.toString();
    }

    /**
     * DBから値を検索する
     *
     * @param fruit
     * @return
     */
    public static String searchFruit(String fruit) {
        String param = "?";
        String speechSota = "";
        String error = "error";

        if (fruit != null && fruit.length() > 0) {
            param += "fruitsName=" + encodeWord(fruit);
        }
        speechSota = getStringByCallGET(getText_url + param);
        if (speechSota != null && speechSota.length() > 0) {
            return speechSota;
        }
        return error;
    }

    /**
     * 受け取ったStringをエンコードする
     *
     * @param word
     * @return
     */
    public static String encodeWord(String word) {
        if (word != null) {
            try {
                word = URLEncoder.encode(word, "utf-8");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return word;
    }

    /**
     * あざといポーズ
     */
    public static void pose() {
        pose.SetPose(new Byte[] { 1, 2, 3, 4, 5 }, new Short[] { 0, 180, -850, -180, 850 });
        motion.play(pose, 1000);
        CRobotUtil.wait(100);
    }
}

上記のJavaファイルを実行したい階層に置いてください。
例:SearchFruitsSotaSample/bin/jp/vstone/searchdbsota/SearchDbSota.java

サーバー側を用意する

サーバー側はSotaからアクセス可能な簡単なLAMP環境を用意してください。

サーバー側に実装するPHPファイルは以下の通りです。

db_def.php
<?php
//以下の$dbname以外の値は任意のものを設定してください
$dbhost = '127.0.0.1';
$dbuser = 'root';
$dbpasswd = 'root';
$dbname = 'fruits_data';
?>
SearchFruit.php
<?php

include_once('db_def.php');

$speech_txt = "";
$fruitsName = "";

$mysqli = new mysqli($dbhost, $dbuser, $dbpasswd, $dbname);

if ($mysqli->connect_error) {
    echo $mysqli->connect_error;
    exit();
}
if (isset($_REQUEST['fruitsName'])) {
    $fruitsName = $_REQUEST['fruitsName'];
    $fruitsName = mysqli_escape_string($mysqli, $fruitsName);
    $speech_txt = $fruitsName;
}

$query = "select * from fruits WHERE fruit = '{$fruitsName}';";
$result = $mysqli->query($query);
if ($result->num_rows >= 1) {
    while ($row = $result->fetch_assoc()) {
        $speech_txt .= 'は' . $row["description"];
    }
    $result->free();
} else {
    $speech_txt .= 'は見つかりませんでした。';
}

$mysqli->close();

echo $speech_txt;

上記のPHPファイルをサーバー内の実行可能な階層に置いてください。
例:/var/www/html/SearchFruitsSotaSample/db_def.php
例:/var/www/html/SearchFruitsSotaSample/SearchFruit.php

DBを用意する

今回はテスト用のDBテーブルから値を探します。
なお、ここではmysqlとしてします。
mysqlにログインし、以下のsql文を実行してください。

1.fruits_dataデータベースを作る

create database fruits_data;

2.fruits_dataデータベースを使用

use fruits_data;

3.fruitsのテーブルを作成する

create table fruits (id int not null primary key auto_increment, fruit varchar(20), description varchar(255));

4.fruitsのテーブルに値を追加する

insert into fruits (fruit, description) values ('りんご', 'ばら科の落葉高木。春、白または薄紅の花が咲く。果実は球形で甘ずっぱい。品種が多く、早いのは六月下旬、遅いのは十二月上旬に熟す。色も赤・黄・薄緑等がある。'), ('みかん', 'みかん科の常緑低木または高木。六月ごろ白い花が咲き、冬に黄色で丸い果実を結ぶ。食用。品種が多い。'), ('ぶどう', 'ぶどう科の落葉植物。茎がつるになって他の物にからみついて伸び、夏、球状の実が房のような形につく。実は食用、またぶどう酒の原料。');

Sotaを動かしてみる

以上の事柄ができたら、実際にSotaを動かしてみましょう!
Sota側で実装した内容をsend.xmlで送り、成功したらsshでSotaにログインします。(send.xmlやsshのホスト名はSotaが喋ってくれます)
そうしたら、./java_run.shで先ほど作ったSearchDbSota.javaファイルを実行してください!

※実行時、java_run.shのパーミッションに注意してください。

結果

 Sotaデモ動画

できました!これで喋った内容をDBから探してSotaが喋り返してくれます。

おわりに

いかがだったでしょうか?
Sotaは様々なシーンで利用され実際に導入している企業も多い優秀なヒューマノイドロボットです。
ここでは、音声合成・音声認識・ポーズの利用しかしていませんが、他にも画像認識を搭載していたり、様々なAPIとの連携で無限の可能性があります!
それでありながら、Sotaに関してはあまり記事がないので、簡単な処理ですが今回の内容が閲覧してくださった人の参考になれば幸いです!
また、質問や分からないことがあればコメントをお願いします。ノシ

※一応GitHubリポジトリにて上記のソースコードを公開しています。
https://github.com/saike1119/SearchFruitsSotaSample