はじめに
HoloLensアドベントカレンダー1日目の記事です!
APIよくわからないと弟子から相談があったので、Cognitive Services系をまとめていきたいと思いまーす。
今日は、Cognitive ServicesのFace APIをHoloLens2でやってみました。
実機なしでもできるのでやってみてください。
開発環境
- Unity 2019.4.1f1
- MRTK 2.5.1
- OpenCV for Unity
- Windows PC
プライムデーで安かったので、予備PC
— 藤本賢志(ガチ本)@pixivFANBOXはじめました (@sotongshi) October 18, 2020
【基本スペック】
OS:Windows 10 Home 64bit、CPU:インテル Core i7-9750H、GPU:NVIDIA GeForce GTX 1650 Ti Max-Q デザイン 4GB GDDR5、メモリ:16GB(8GB ×2)、SSD:512GB、液晶パネル:15.6インチ、フルHD(1,920×1,080)、リフレッシュレート120Hz pic.twitter.com/S8RIsayFPY
導入
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に投げるスクリプトになります。
usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem;usingUnityEngine;usingMicrosoft.MixedReality.Toolkit.Utilities;usingSystem.Threading.Tasks;usingOpenCVForUnity.CoreModule;usingOpenCVForUnity.UnityUtils;usingOpenCVForUnity.ImgprocModule;publicclassTapToCapture:MonoBehaviour{publicGameObjectquad;[System.Serializable]publicclassFace{publicstringfaceId;publicFaceRectanglefaceRectangle;publicFaceAttributefaceAttributes;}[System.Serializable]publicclassFaceRectangle{publicinttop;publicintleft;publicintwidth;publicintheight;}[System.Serializable]publicclassFaceAttribute{publicfloatage;publicstringgender;}UnityEngine.Windows.WebCam.PhotoCapturephotoCaptureObject=null;Texture2DtargetTexture=null;privatestringendpoint="https://<Your Endpoint>.cognitiveservices.azure.com/face/v1.0/detect";privatestringsubscription_key="<Insert Your API Key>";publicvoidAirTap(){ResolutioncameraResolution=UnityEngine.Windows.WebCam.PhotoCapture.SupportedResolutions.OrderByDescending((res)=>res.width*res.height).First();targetTexture=newTexture2D(cameraResolution.width,cameraResolution.height);// PhotoCapture オブジェクトを作成しますUnityEngine.Windows.WebCam.PhotoCapture.CreateAsync(false,delegate(UnityEngine.Windows.WebCam.PhotoCapturecaptureObject){photoCaptureObject=captureObject;UnityEngine.Windows.WebCam.CameraParameterscameraParameters=newUnityEngine.Windows.WebCam.CameraParameters();cameraParameters.hologramOpacity=0.0f;cameraParameters.cameraResolutionWidth=cameraResolution.width;cameraParameters.cameraResolutionHeight=cameraResolution.height;cameraParameters.pixelFormat=UnityEngine.Windows.WebCam.CapturePixelFormat.BGRA32;// カメラをアクティベートしますphotoCaptureObject.StartPhotoModeAsync(cameraParameters,delegate(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResultresult){// 写真を撮りますphotoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemoryAsync);});});}asyncvoidOnCapturedPhotoToMemoryAsync(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResultresult,UnityEngine.Windows.WebCam.PhotoCaptureFramephotoCaptureFrame){// ターゲットテクスチャに RAW 画像データをコピーしますphotoCaptureFrame.UploadImageDataToTexture(targetTexture);byte[]bodyData=targetTexture.EncodeToJPG();Responseresponse=newResponse();try{// string query = endpoint + "?detectionModel=detection_02&returnFaceId=true";// 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";stringquery=endpoint+"?detectionModel=detection_01&returnFaceId=true&returnFaceAttributes=age,gender";varheaders=newDictionary<string,string>();headers.Add("Ocp-Apim-Subscription-Key",subscription_key);// headers.Add("Content-Type", "application/octet-stream");response=awaitRest.PostAsync(query,bodyData,headers,-1,true);}catch(Exceptione){photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);return;}if(!response.Successful){photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);return;}Debug.Log(response.ResponseCode);Debug.Log(response.ResponseBody);stringnewResponseBody="{ \"results\": "+response.ResponseBody+"}";Face[]faces=JsonHelper.FromJson<Face>(newResponseBody);MatimgMat=newMat(targetTexture.height,targetTexture.width,CvType.CV_8UC4);Utils.texture2DToMat(targetTexture,imgMat);// Debug.Log("imgMat.ToString() " + imgMat.ToString());foreach(varfaceinfaces){//Debug.Log(face.faceId);//Debug.Log(face.faceRectangle.left);//Debug.Log(face.faceRectangle.top);//Debug.Log(face.faceRectangle.width);//Debug.Log(face.faceRectangle.height);Imgproc.putText(imgMat,face.faceAttributes.age.ToString()+","+face.faceAttributes.gender,newPoint(face.faceRectangle.left,face.faceRectangle.top-10),Imgproc.FONT_HERSHEY_SIMPLEX,1.5,newScalar(0,0,255,255),2,Imgproc.LINE_AA,false);Imgproc.rectangle(imgMat,newPoint(face.faceRectangle.left,face.faceRectangle.top),newPoint(face.faceRectangle.left+face.faceRectangle.width,face.faceRectangle.top+face.faceRectangle.height),newScalar(0,0,255,255),2);}Texture2Dtexture=newTexture2D(imgMat.cols(),imgMat.rows(),TextureFormat.RGBA32,false);Utils.matToTexture2D(imgMat,texture);// テクスチャが適用されるゲームオブジェクトを作成// GameObject quad = GameObject.CreatePrimitive(PrimitiveType.Quad); RendererquadRenderer=quad.GetComponent<Renderer>()asRenderer;// quadRenderer.material = new Material(Shader.Find("Unlit/UnlitTexture"));// quad.transform.parent = this.transform;// quad.transform.localPosition = new Vector3(0.0f, 0.0f, 3.0f);quadRenderer.material.SetTexture("_MainTex",texture);// カメラを非アクティブにしますphotoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);}voidOnStoppedPhotoMode(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResultresult){// photo capture のリソースをシャットダウンしますphotoCaptureObject.Dispose();photoCaptureObject=null;}}
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、年齢と性別を返すように設定しています。
<your endpoint>?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です。
[{"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スクリプトを作成します。
usingUnityEngine;usingSystem;publicclassJsonHelper{publicstaticT[]FromJson<T>(stringjson){Wrapper<T>wrapper=JsonUtility.FromJson<Wrapper<T>>(json);returnwrapper.results;}[Serializable]privateclassWrapper<T>{publicT[]results;}}
JsonHelperについて
- yuiyoichi/JsonHelper.cs
- How to load an array with JsonUtility?
- UnityのJsonUtilityでJSON配列を処理する
17.返ってきたResponseBodyを次のようにすることで、パースすることが可能になります。
{"results":[{...}]}
18.あとは仕様に合わせてFaceクラスとFaceRectangleクラス、FaceAttributeクラスを作成しました。
19.顔検出結果をOpenCVを使って画像に描画し、Quadのマテリアルに割り当てます。3D Object->Quadを作成しましょう。
OpenCV for Unity サンプルはこちら
- Texture2DからMatに変換
- 矩形を描画(Imgproc.rectangle)
- テキストを描画(Imgproc.putText)
20.OrbitalをAdd Componentし、Quadがカメラに追従するようにしています。
21.TapToCaptureにQuadをD&Dしてアタッチしたら完成です。
実行
HoloLens2にデプロイして、実行した結果がこちらになります。Editor上でもできるので試してみてください。
お疲れ様でした。
明日は弟子(@Horomoto-Asahi)による「HoloLens 2のSpatialAwarenessの調査」です。