はじめに
HoloLensアドベントカレンダー1日目の記事です!
APIよくわからないと弟子から相談があったので、Cognitive Services系をまとめていきたいと思いまーす。
今日は、Cognitive ServicesのFace APIをHoloLens2でやってみました。
実機なしでもできるのでやってみてください。
開発環境
- Azure
- Face API
- HoloLens2
- Unity 2019.4.1f1
- MRTK 2.5.1
- OpenCV for Unity
- Windows PC
導入
1.AzureポータルからFace APIを作成し、エンドポイントとサブスクリプションキーをメモしておいてください。
2.Unityでプロジェクトを作成、MRTK2.5.1をインポートします。なんかウィンドウでたらApplyします。
3.メニューのMixed Reality Toolkit->Add to Scene and Configureしてください。
4.Build Settingsから、Universal Windows PlatformにSwitch Platformして、以下のように設定してください。あとAdd Open ScenesでScenes/SampleSceneにチェックが入っていることを確認します。
5.MixedRealityToolkitのDefaultHoloLens2ConfigureProfileをcloneし、Diagnostics->Enable Diagnostics Systemのチェックを外します。これでCPU使用率とかのデバッグ情報を非表示にできます。
6.Project SettingsのXR Settings、Publishing Settings->Capabilitiesを以下のように設定してください。
7.空のGameObjectを作成し、名前を「TapToCapture」にします。
8.Add Componentから「TapToCapture.cs」スクリプトを作成します。エアタップしたら、画像をキャプチャし、Face APIに投げるスクリプトになります。
1TapToCapture.cs
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System;
6using UnityEngine;
7using Microsoft.MixedReality.Toolkit.Utilities;
8using System.Threading.Tasks;
9using OpenCVForUnity.CoreModule;
10using OpenCVForUnity.UnityUtils;
11using OpenCVForUnity.ImgprocModule;
12
13public class TapToCapture : MonoBehaviour
14{
15 public GameObject quad;
16
17 [System.Serializable]
18 public class Face
19 {
20 public string faceId;
21 public FaceRectangle faceRectangle;
22 public FaceAttribute faceAttributes;
23 }
24
25 [System.Serializable]
26 public class FaceRectangle
27 {
28 public int top;
29 public int left;
30 public int width;
31 public int height;
32 }
33
34 [System.Serializable]
35 public class FaceAttribute
36 {
37 public float age;
38 public string gender;
39 }
40
41 UnityEngine.Windows.WebCam.PhotoCapture photoCaptureObject = null;
42 Texture2D targetTexture = null;
43
44 private string endpoint = "https:///face/v1.0/detect";
45 private string subscription_key = "";
46 private bool waitingForCapture;
47
48 void Start(){
49 waitingForCapture = false;
50 }
51
52 public void AirTap()
53 {
54 if (waitingForCapture) return;
55 waitingForCapture = true;
56
57 Resolution cameraResolution = UnityEngine.Windows.WebCam.PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
58 targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
59
60 // PhotoCapture オブジェクトを作成します
61 UnityEngine.Windows.WebCam.PhotoCapture.CreateAsync(false, delegate (UnityEngine.Windows.WebCam.PhotoCapture captureObject) {
62 photoCaptureObject = captureObject;
63 UnityEngine.Windows.WebCam.CameraParameters cameraParameters = new UnityEngine.Windows.WebCam.CameraParameters();
64 cameraParameters.hologramOpacity = 0.0f;
65 cameraParameters.cameraResolutionWidth = cameraResolution.width;
66 cameraParameters.cameraResolutionHeight = cameraResolution.height;
67 cameraParameters.pixelFormat = UnityEngine.Windows.WebCam.CapturePixelFormat.BGRA32;
68
69 // カメラをアクティベートします
70 photoCaptureObject.StartPhotoModeAsync(cameraParameters, delegate (UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result) {
71 // 写真を撮ります
72 photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemoryAsync);
73 });
74 });
75 }
76
77 async void OnCapturedPhotoToMemoryAsync(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result, UnityEngine.Windows.WebCam.PhotoCaptureFrame photoCaptureFrame)
78 {
79 // ターゲットテクスチャに RAW 画像データをコピーします
80 photoCaptureFrame.UploadImageDataToTexture(targetTexture);
81 byte[] bodyData = targetTexture.EncodeToJPG();
82
83 Response response = new Response();
84
85 try
86 {
87 // string query = endpoint + "?detectionModel=detection_02&returnFaceId=true";
88 // string query = endpoint + "?detectionModel=detection_01&returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise";
89 string query = endpoint + "?detectionModel=detection_01&returnFaceId=true&returnFaceAttributes=age,gender";
90 var headers = new Dictionary<string, string>();
91 headers.Add("Ocp-Apim-Subscription-Key", subscription_key);
92 // headers.Add("Content-Type", "application/octet-stream");
93
94 response = await Rest.PostAsync(query, bodyData, headers, -1, true);
95 }
96 catch (Exception e)
97 {
98 photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
99 return;
100 }
101
102 if (!response.Successful)
103 {
104 photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
105 return;
106 }
107
108 Debug.Log(response.ResponseCode);
109 Debug.Log(response.ResponseBody);
110 string newResponseBody = "{ \"results\": " + response.ResponseBody + "}";
111 Face[] faces = JsonHelper.FromJson(newResponseBody);
112
113 Mat imgMat = new Mat(targetTexture.height, targetTexture.width, CvType.CV_8UC4);
114
115 Utils.texture2DToMat(targetTexture, imgMat);
116 // Debug.Log("imgMat.ToString() " + imgMat.ToString());
117
118 foreach (var face in faces){
119 //Debug.Log(face.faceId);
120 //Debug.Log(face.faceRectangle.left);
121 //Debug.Log(face.faceRectangle.top);
122 //Debug.Log(face.faceRectangle.width);
123 //Debug.Log(face.faceRectangle.height);
124 Imgproc.putText(imgMat, face.faceAttributes.age.ToString()+","+face.faceAttributes.gender, new Point(face.faceRectangle.left, face.faceRectangle.top-10), Imgproc.FONT_HERSHEY_SIMPLEX, 1.5, new Scalar(0, 0, 255, 255), 2, Imgproc.LINE_AA, false);
125 Imgproc.rectangle(imgMat, new Point(face.faceRectangle.left, face.faceRectangle.top), new Point(face.faceRectangle.left + face.faceRectangle.width, face.faceRectangle.top + face.faceRectangle.height), new Scalar(0, 0, 255, 255), 2);
126 }
127
128 Texture2D texture = new Texture2D(imgMat.cols(), imgMat.rows(), TextureFormat.RGBA32, false);
129 Utils.matToTexture2D(imgMat, texture);
130
131 // テクスチャが適用されるゲームオブジェクトを作成
132 // GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
133
134 Renderer quadRenderer = quad.GetComponent() as Renderer;
135 // quadRenderer.material = new Material(Shader.Find("Unlit/UnlitTexture"));
136
137 // quad.transform.parent = this.transform;
138 // quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);
139
140 quadRenderer.material.SetTexture("_MainTex", texture);
141
142 // カメラを非アクティブにします
143 photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
144 }
145
146 void OnStoppedPhotoMode(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result)
147 {
148 // photo capture のリソースをシャットダウンします
149 photoCaptureObject.Dispose();
150 photoCaptureObject = null;
151 waitingForCapture = false;
152 }
153}9.PhotoCaptureのサンプルは こちら です。エアタップしたら、画像キャプチャするようにInputActionHandlerをAdd Componentし、AirTap関数を作成します。エアタップ(On Input Action Started)したらAirTap関数が発火するように設定します。
10.撮影できたら、targetTextureに画像データが入っているので、JPGにエンコードして、Face APIに投げます。FaceAPIのサンプルはこちら C# 、 Python です。
11.endpointとsubscription_keyにメモしておいたものを貼り付けてください。
12.クエリパラメータは、detection_01モデルを使用、FaceId、年齢と性別を返すように設定しています。
1https:///face/v1.0/detect?detectionModel=detection_01&returnFaceId=true&returnFaceAttributes=age,gender"ちなみにfaceAttributesはsmile, headPose, gender, age, facialHair, glasses, emotion, blur, exposure, noise, makeup, accessories, occlusion, hairといった情報が取れます。
13.MRTKのRestを用いてHTTPリクエストします。
ヘッダーは、”Ocp-Apim-Subscription-Key”: subscription_keyを指定、”Content-Type”: “application/octet-stream”はRestの中でやってくれるのでコメントアウトします。
14.クエリと画像データ、ヘッダーをPOSTします。
response = await Rest.PostAsync(query, bodyData, headers, -1, true);
15.response.ResponseBodyが下記のように返ってくればOKです。
1[{"faceId":"f1b97cf1-58d0-4dc9-9169-e19cb0655e48","faceRectangle":{"top":347,"left":451,"width":285,"height":285},"faceAttributes":{"gender":"male","age":23.0}}]16.Face APIのResponseBodyがリストのjsonになっているので、パースできるようにJsonHelper.csスクリプトを作成します。
1JsonHelper.cs
2using UnityEngine;
3using System;
4
5public class JsonHelper
6{
7 public static T[] FromJson(string json)
8 {
9 Wrapper wrapper = JsonUtility.FromJson<Wrapper>(json);
10 return wrapper.results;
11 }
12
13 [Serializable]
14 private class Wrapper
15 {
16 public T[] results;
17 }
18}JsonHelperについて
– How to load an array with JsonUtility?
– UnityのJsonUtilityでJSON配列を処理する
17.返ってきたResponseBodyを次のようにすることで、パースすることが可能になります。
1{
2 "results" : [ {...} ]
3}18.あとは仕様に合わせてFaceクラスとFaceRectangleクラス、FaceAttributeクラスを作成しました。
19.顔検出結果をOpenCVを使って画像に描画し、Quadのマテリアルに割り当てます。3D Object->Quadを作成しましょう。
20.OrbitalをAdd Componentし、Quadがカメラに追従するようにしています。
21.TapToCaptureにQuadをD&Dしてアタッチしたら完成です。
実行
HoloLens2にデプロイして、実行した結果がこちらになります。Editor上でもできるので試してみてください。
お疲れ様でした。
明日は弟子( @Horomoto-Asahi )による「HoloLens 2のSpatialAwarenessの調査」です。