MR and Azure 303 : Luis

シーン内にオブジェクトを配置していきたいと思います。

1. 土台の配置

[Hierarchy]->[Create]->[3D Object]->[Plane]で土台を作り、[Inspector]->[Transform]を編集していきます。

—————————————–

Position | X : 0 | Y : -1 | Z : 0

—————————————–

2. 球の配置

[Hierarchy]->[Create]->[3D Object]->[Sphere]で球を作り、[Inspector]->[Transform]を編集していきます。

—————————————–

Position | X : 2 | Y : 1 | Z : 2

—————————————–

3. シリンダーの配置

[Hierarchy]->[Create]->[3D Object]->[Cylinder]でシリンダーを作り、[Inspector]->[Transform]を編集していきます。

—————————————–

Position | X : -2 | Y : 1 | Z : 2

—————————————–

4. 正方形の配置

[Hierarchy]->[Create]->[3D Object]->[Cube]で正方形を作り、[Inspector]->[Transform]を編集していきます。

Position |  X : 0  |  Y : 1  |  Z : 4

Rotation | X : 45 | Y : 45 | Z : 45

![](http://azurerecipe.blob.core.windows.net/2019-01-07-03/303-39.png)

5. テキストの配置

a. [Hierarchy]->[Create]->[3D Object]->[Text]でテキストを作り、名前をDictationTextとします。[Inspector]->[Transform]を編集していきます。

Position |  X : -2  |  Y : 6  |  Z : 9

Scale     | X : 0.1 | Y : 0.1 | Z : 0.1

b. [Inspector]内のその他の編集を行っていきます。

5. カメラまわりの編集

デフォルトで備わっているMain Cameraの代わりにHoloToolkitを使います。使うものを以下のリンクからダウンロードしてください。

HoloToolkit-Unity-2017.4.2.0.unitypackage

HoloToolkit-Unity-Example-2017.4.2.0.unitypackage

a. [Assets]->[Import Package]->[Custom Package..]からダウンロードしたpackageを二つともインポートします。

b. [Hierarchy]->[Main camera]をdeleteします。

c. [Project]から以下の三つを検索し、[Hierarchy]パネルへD&Dします。

・MixedRealityCameraParent

・DefaultCursor

・MixedRealityCameraParent

・DefaultCursor

・InputManger

d. [Hierarchy]->[MixedRealityCameraParent]->[MixedRealityCamera]の[Inspector]を編集していきます。

[Camera]

Clear Flags | Solid Color

[Mixed Reality Camera Manager]

Clear Flags | color

Near clip     | 0.2

Clear Flags | color

e. [Hierarchy]->[Input Manager]の[Inspector]を編集していきます。[Simple Single Pointer Selector]のCursorへ[Hierarchy]->[Default Cursor]をアタッチします。

f. [Hierarchy]->[MixedRealityCameraParent]->[MixedRealityCamera]の[Inspector]->[Add Component]からAudioSourceを検索しこれを追加します。

この章で設定した[Inspector]->[Transform]は目安ですので、より見やすいように編集してください。

この章で設定した[Inspector]->[Transform]は目安ですので、より見やすいように編集してください。

5.スクリプトの作成

ここでは以下の4つのスクリプトを作っていきたいと思います。

・MicrophoneManagerLuis

・LuisManager

・Behaviours

・Gaze

スクリプトの作り方はまず、すべてのスクリプトをまとめておくフォルダーを作成します。[Project]->[Create]をクリックし、[Folder]を選択して新しいフォルダーを作ります。名前をScriptsとします。続いて[Project]->[Create]->[C# Script]をクリックし、クラス名をつけていきます。

・MicrophoneManagerLuis

このクラスはMicrophoneで音声をキャプチャし、そのキャプチャした音声をテキストにするクラスです。聞いた音声を順に文字におこしていきます。今回は簡単のため「change」と聞き取ったらテキストを「The color of the cylinder must be red」として使用することにします。また、しゃべった言葉を直接使う方法については後述します。喋り始めてから一度止まるまでの音声がテキストになります。コードは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;

public class MicrophoneManagerLuis : MonoBehaviour
{

public static MicrophoneManagerLuis instance; //help to access instance of this object
private int frequency = 44100; //recording frequency of mic
private AudioSource audioSource; //AudioSource component, provides access to mic
private DictationRecognizer dictationRecognizer; //Component converting speech to text
public TextMesh dictationText; //a UI object used to debug dictation result

private void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}

// Use this for initialization
void Start()
{

if (Microphone.devices.Length > 0)
{
// Once the scene starts, begin to capture the audio
audioSource = GetComponent<AudioSource>();
Debug.Log("Mic Detected");
StartCapturingAudio();
}
}

/// <summary>
/// Start microphone capture, by providing the microphone as a continual audio source (looping),
/// then initialise the DictationRecognizer, which will capture spoken words
/// </summary>
public void StartCapturingAudio()
{
dictationRecognizer = new DictationRecognizer();
dictationRecognizer.DictationHypothesis += DictationRecognizer_DictationHypothesis;
dictationRecognizer.Start();
Debug.Log("Capturing Audio...");
}

private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
Debug.Log("text:" + dictationCaptured);
if(dictationCaptured == "change")
{
dictationCaptured = "The color of the cylinder must be red";
Debug.Log("text translation:" + dictationCaptured);
StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}
}
}

喋った言葉を直接使いたい場合は上のコードの以下の部分を変更します。

private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
Debug.Log("text:" + dictationCaptured);
if(dictationCaptured == "change")
{
dictationCaptured = "The color of the cylinder must be red";
Debug.Log("text translation:" + dictationCaptured);
StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}
}

の部分を

private void DictationRecognizer_DictationHypothesis(string dictationCaptured)
{
    Debug.Log("text:" + dictationCaptured);
    StartCoroutine(LuisManager.instance.SubmitRequestToLuis(dictationCaptured));
dictationText.text = dictationCaptured;
}

とします。

・LuisManager

このクラスのやることはMicrophoneManagerLuisクラスからテキストを受け取り、Azure Language Understanding APIにテキストを送ることです。ここで2.準備(2)で取得したEndpointを使用します。–Insert your Endpoint–にEndpointを挿入してください。コードは以下の通りです。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

public class LuisManager : MonoBehaviour
{

[System.Serializable] //this class represents the LUIS response
public class AnalysedQuery
{
public TopScoringIntentData topScoringIntent;
public EntityData[] entities;
public string query;
}

// This class contains the Intent LUIS determines
// to be the most likely
[System.Serializable]
public class TopScoringIntentData
{
public string intent;
public float score;
}

// This class contains data for an Entity
[System.Serializable]
public class EntityData
{
public string entity;
public string type;
public int startIndex;
public int endIndex;
public float score;
}

public static LuisManager instance;

//Substitute the value of luis Endpoint with your own End Point
string luisEndpoint = " --Insert your Endpoint --";

private void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}

/// <summary>
/// Call LUIS to submit a dictation result.
/// </summary>
public IEnumerator SubmitRequestToLuis(string dictationResult)
{
WWWForm webForm = new WWWForm();

string queryString;

queryString = string.Concat(Uri.EscapeDataString(dictationResult));

using (UnityWebRequest unityWebRequest = UnityWebRequest.Get(luisEndpoint + queryString))
{
unityWebRequest.downloadHandler = new DownloadHandlerBuffer();

yield return unityWebRequest.SendWebRequest();

long responseCode = unityWebRequest.responseCode;

try
{
using (Stream stream = GenerateStreamFromString(unityWebRequest.downloadHandler.text))
{
StreamReader reader = new StreamReader(stream);

AnalysedQuery analysedQuery = new AnalysedQuery();

analysedQuery = JsonUtility.FromJson<AnalysedQuery>(unityWebRequest.downloadHandler.text);

//analyse the elements of the response
AnalyseResponseElements(analysedQuery);
}
}
catch (Exception exception)
{
Debug.Log("Luis Request Exception Message: " + exception.Message);
}

yield return null;
}
}

public static Stream GenerateStreamFromString(string receivedString)
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(receivedString);
writer.Flush();
stream.Position = 0;
return stream;
}

private void AnalyseResponseElements(AnalysedQuery aQuery)
{
string topIntent = aQuery.topScoringIntent.intent;

// Create a dictionary of entities associated with their type
Dictionary<string, string> entityDic = new Dictionary<string, string>();

foreach (EntityData ed in aQuery.entities)
{
entityDic.Add(ed.type, ed.entity);
}

// Depending on the topmost recognised intent, read the entities name
switch (aQuery.topScoringIntent.intent)
{
case "ChangeObjectColor":
string targetForColor = null;
string color = null;

foreach (var pair in entityDic)
{
if (pair.Key == "target")
{
targetForColor = pair.Value;

}
else if (pair.Key == "color")
{
color = pair.Value;

}
}

Behaviours.instance.ChangeTargetColor(targetForColor, color);
break;

case "ChangeObjectSize":
string targetForSize = null;
foreach (var pair in entityDic)
{
if (pair.Key == "target")
{
targetForSize = pair.Value;
}
}

if (entityDic.ContainsKey("upsize") == true)
{
Behaviours.instance.UpSizeTarget(targetForSize);
}
else if (entityDic.ContainsKey("downsize") == true)
{
Behaviours.instance.DownSizeTarget(targetForSize);
}
break;
}
}
}

・Behaviours

このクラスは適切なアクションを起こすためのクラスです。変更するオブジェクト、色を指定します。コードは以下の通りです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Behaviours : MonoBehaviour
{

public static Behaviours instance;

// the following variables are references to possible targets
public GameObject sphere;
public GameObject cylinder;
public GameObject cube;
internal GameObject gazedTarget;

void Awake()
{
// allows this class instance to behave like a singleton
instance = this;
}

/// <summary>
/// Changes the color of the target GameObject by providing the name of the object
/// and the name of the color
/// </summary>
public void ChangeTargetColor(string targetName, string colorName)
{
GameObject foundTarget = FindTarget(targetName);
if (foundTarget != null)
{
Debug.Log("color = " + colorName + ", target = " + foundTarget.name);
switch (colorName)
{
case "blue":
Debug.Log("change blue");
foundTarget.GetComponent<Renderer>().material.color = Color.blue;
break;

case "red":
Debug.Log("change red");
foundTarget.GetComponent<Renderer>().material.color = Color.red;
break;

case "yellow":
Debug.Log("change yellow");
foundTarget.GetComponent<Renderer>().material.color = Color.yellow;
break;

case "green":
Debug.Log("change green");
foundTarget.GetComponent<Renderer>().material.color = Color.green;
break;

case "white":
Debug.Log("change white");
foundTarget.GetComponent<Renderer>().material.color = Color.white;
break;

case "black":
Debug.Log("change black");
foundTarget.GetComponent<Renderer>().material.color = Color.black;
break;
}
}
}

/// <summary>
/// Reduces the size of the target GameObject by providing its name
/// </summary>
public void DownSizeTarget(string targetName)
{
GameObject foundTarget = FindTarget(targetName);
foundTarget.transform.localScale -= new Vector3(0.5F, 0.5F, 0.5F);
}

/// <summary>
/// Increases the size of the target GameObject by providing its name
/// </summary>
public void UpSizeTarget(string targetName)
{
GameObject foundTarget = FindTarget(targetName);
foundTarget.transform.localScale += new Vector3(0.5F, 0.5F, 0.5F);
}

/// <summary>
/// Determines which obejct reference is the target GameObject by providing its name
/// </summary>
private GameObject FindTarget(string name)
{
GameObject targetAsGO = null;
switch (name)
{
case "sphere":
targetAsGO = sphere;
break;

case "cylinder":
targetAsGO = cylinder;
break;

case "cube":
Debug.Log("change cube");
targetAsGO = cube;
break;

case "this": // as an example of target words that the user may use when looking at an object
case "it": // as this is the default, these are not actually needed in this example
case "that":
default: // if the target name is none of those above, check if the user is looking at something
if (gazedTarget != null)
{
targetAsGO = gazedTarget;
}
break;
}
return targetAsGO;
}
}

・Gaze

このクラスはユーザーの視点を更新するクラスです。コードは以下の通りです。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Experimental.XR;
using UnityEngine.UI;
using UnityEngine.XR;
using UnityEngine.XR.WSA.Input;

public class Gaze : MonoBehaviour
{
internal GameObject gazedObject;
public float gazeMaxDistance = 300;

void Update()
{
// Uses a raycast from the Main Camera to determine which object is gazed upon.
Vector3 fwd = gameObject.transform.TransformDirection(Vector3.forward);
Ray ray = new Ray(Camera.main.transform.position, fwd);
RaycastHit hit;
Debug.DrawRay(Camera.main.transform.position, fwd);

if (Physics.Raycast(ray, out hit, gazeMaxDistance) && hit.collider != null)
{
if (gazedObject == null)
{
gazedObject = hit.transform.gameObject;

// Set the gazedTarget in the Behaviours class
Behaviours.instance.gazedTarget = gazedObject;
}
}
else
{
ResetGaze();
}
}

// Turn the gaze off, reset the gazeObject in the Behaviours class.
public void ResetGaze()
{
if (gazedObject != null)
{
Behaviours.instance.gazedTarget = null;
gazedObject = null;
}
}
}

全てのスクリプトが完成したら、動作するようにアタッチしていきたいと思います。記述した全てのスクリプトを[Hierarchy]パネル内の[MixedRealityCamera]にD&Dします。

[MixedRealityCamera]の[Inspector]内に先ほどD&Dしたスクリプトが追加されていると思います。[Behaviours(script)]->[Sphere]、[Cylinder]、[Cube]に[Hierarchy]パネル内の[Sphere]、[Cylinder]、[Cube]を、[Microphone Manager Luis(script)]->[Dictation Text]へ[Hierarchy]パネル内の[Dictation Text]をそれぞれアタッチします。

6.ビルド

[File]->[Build Settings..]->[Build]の順に選択します。新しいフォルダー(BUILDS)を作成し、その中にさらにフォルダー(Luis_V0.1)を作ります。このフォルダーを選択し、保存します。

この後の手順についてはこちらを参照してください。

Unity上で実行した様子です。

HoloLensで実行した時の様子です。

以上となります。 お疲れ様でした。

この記事を書いた人

azure-recipe-user