本日はAzure学習枠です。
Microsoftによって提供されているAzureではAIを含む様々なサービスをクラウド上のコンピュータによって行うことができます。
今回はその中でAzure Face APIをUnityで動かしていきます。
〇Azure Face APIとは?
Azure Face APIは顔の解析、検出に特化したAzureのサービスです。
サーバー上に画像を投げることでその画像の中から人物の顔の位置、表情、ジェンダー、年齢を推測するものです。
利用するためにはAzure Portalへとサインインします。
[+リソースの作成]から検索欄でFaceを検索します。

リソースを作成します。
リソースが作成されたらサブスクリプションキーとエンドポイントを控えます。

サブスクリプションキーは[リソース管理]から[キーとエンドポイント]から確認できます。

ポータルでの作業は以上です。
〇UnityでAzure Face APIを使用する
今回Azure Face APIを利用するにあたりほかの方の情報を見てみても、情報が古いかUnityではないネイティブなC#やPythonの情報が多くUnity単体での利用に関しての記事があまりなかったです。
筆者が見つけたものが、筆者の師に当たるがち本さんによるHoloLensでの利用記事ですが、これはMRTKの機能やOpenCV for Unityなどほかのライブラリを組み合わせて利用していたため今回Unityで基本的に動かすことをやってみました。
記事後半で紹介しているソースコードをAzureから提供されているネイティブなC#サンプルをもとに作成しました。
作成したAzureFaceAPI.csを適当なゲームオブジェクトにアタッチします。

次に解析する画像を用意します。今回はフリーの画像から次の画像を使用しました。
AzureFaceAPI.csのimagePathに画像を保存したPCのパスを入力します。

この状態で実行することでconsoleウィンドウにJsonが返されます。

[
{
"faceId": "ddbd38e8-f685-4263-9752-ecbeeb540a3c",
"faceRectangle": {
"top": 197,
"left": 332,
"width": 331,
"height": 331
},
"faceAttributes": {
"smile": 1.0,
"gender": "female",
"age": 29.0,
"emotion": {
"anger": 0.0,
"contempt": 0.0,
"disgust": 0.0,
"fear": 0.0,
"happiness": 1.0,
"neutral": 0.0,
"sadness": 0.0,
"surprise": 0.0
}
}
}
]
Jsonには顔の位置(座標)のほか笑顔率、ジェンダー、年齢、そして感情のパラメータが表示されています。
この情報によると
29歳の女性が幸せそうに笑っているというような情報を読み解くことができます。
以上でUnityでAzue Face APIを動かすことができました。
〇ソースコード
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
//ベーシックなAzureFaceAPIを使用するためのクラス
//実行することで画像が送信され、感情、年齢、ジェンダー、顔の位置の情報が返される。
public class AzureFaceAPI : MonoBehaviour
{
static string SUBSCRIPTION_KEY = "自分自身のサブスクリプションキー";
static string ENDPOINT = "自分自身のエンドポイント";
public string imagePath ;
private void Start()
{
Main();
}
void Main()
{
// Get the path and filename to process from the user.
Debug.Log("Detect faces:");
Debug.Log("Enter the path to an image with faces that you wish to analyze: ");
string imageFilePath = imagePath ;
Debug.Log(imageFilePath);
if (File.Exists(imageFilePath))
{
try
{
Debug.Log("Wait a moment for the results to appear.");
MakeAnalysisRequest(imageFilePath);
}
catch (Exception e)
{
Debug.Log(e.Message );
}
}
else
{
Debug.Log("Invalid file path.");
}
}
async Task MakeAnalysisRequest(string imageFilePath)
{
HttpClient client = new HttpClient();
// Request headers.
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", SUBSCRIPTION_KEY);
// Request parameters. A third optional parameter is "details".
string requestParameters = "detectionModel=detection_01&returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=age,gender,smile,emotion";
// Assemble the URI for the REST API Call.
string uri = ENDPOINT + "/face/v1.0/detect?" + requestParameters;
Debug.Log(uri);
HttpResponseMessage response;
// Request body. Posts a locally stored JPEG image.
byte[] byteData = GetImageAsByteArray(imageFilePath);
using (ByteArrayContent content = new ByteArrayContent(byteData))
{
// This example uses content type "application/octet-stream".
// The other content types you can use are "application/json"
// and "multipart/form-data".
content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
// Execute the REST API call.
response = await client.PostAsync(uri, content);
// Get the JSON response.
string contentString = await response.Content.ReadAsStringAsync();
// Display the JSON response.
Debug.Log("Response:");
Debug.Log(JsonPrettyPrint(contentString));
}
}
static byte[] GetImageAsByteArray(string imageFilePath)
{
using (FileStream fileStream =
new FileStream(imageFilePath, FileMode.Open, FileAccess.Read))
{
BinaryReader binaryReader = new BinaryReader(fileStream);
return binaryReader.ReadBytes((int)fileStream.Length);
}
}
static string JsonPrettyPrint(string json)
{
if (string.IsNullOrEmpty(json))
return string.Empty;
json = json.Replace(Environment.NewLine, "").Replace("\t", "");
StringBuilder sb = new StringBuilder();
bool quote = false;
bool ignore = false;
int offset = 0;
int indentLength = 3;
foreach (char ch in json)
{
switch (ch)
{
case '"':
if (!ignore) quote = !quote;
break;
case '\'':
if (quote) ignore = !ignore;
break;
}
if (quote)
sb.Append(ch);
else
{
switch (ch)
{
case '{':
case '[':
sb.Append(ch);
sb.Append(Environment.NewLine);
sb.Append(new string(' ', ++offset * indentLength));
break;
case '}':
case ']':
sb.Append(Environment.NewLine);
sb.Append(new string(' ', --offset * indentLength));
sb.Append(ch);
break;
case ',':
sb.Append(ch);
sb.Append(Environment.NewLine);
sb.Append(new string(' ', offset * indentLength));
break;
case ':':
sb.Append(ch);
sb.Append(' ');
break;
default:
if (ch != ' ') sb.Append(ch);
break;
}
}
}
return sb.ToString().Trim();
}
}
