HoloLens2 × Azure Cognitive Services(画像分析APIで画像説明文生成)

はじめに

HoloLensアドベントカレンダー2020 の4日目の記事です。

前回 に引き続き、Cognitive Services系をまとめていきまーす。

今日は、Azure Computer Vision APIの画像分析APIを用いて画像から視覚的特徴を抽出します。

例えば、下の サンプル画像 を見て、「人通りの多い道」と説明してくれます。

Figure_4.png
これで韓ドラ「スタートアップ」に出てきたアプリ「ヌンギル」が作れそうですね。

開発環境

  • Azure
  • Computer Vision API(画像分析 API)
  • HoloLens2
  • Unity 2019.4.1f1
  • MRTK 2.5.1
  • OpenCV for Unity
  • Windows PC

導入

前回 の顔検出をほぼほぼ使って、異なるところだけ説明していきます。

image.png

1.AzureポータルからComputer Vision APIを作成し、エンドポイントとサブスクリプションキーをメモしておいてください。

image.png

2.Unityのプロジェクトはこんな感じ。前回の「TapToCapture」を無効にして「TapToCaptureAnalyzeAPI」を作成しました。

image.png

3.「TapToCaptureAnalyzeAPI.cs」は、エアタップしたら画像をキャプチャし、Analyze APIに投げるスクリプトになります。

1TapToCaptureAnalyzeAPI.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 TapToCaptureAnalyzeAPI : MonoBehaviour
14{
15    public GameObject quad;
16
17    [System.Serializable]
18    public class Analyze
19    {
20        public Categories[] categories;
21        public Color color;
22        public Description description;
23        public string requestId;
24        public Metadata metadata;
25    }
26
27    [System.Serializable]
28    public class Categories
29    {
30        public string name;
31        public float score;
32    }
33
34    [System.Serializable]
35    public class Color
36    {
37        public string dominantColorForeground;
38        public string dominantColorBackground;
39        public string[] dominantColors;
40        public string accentColor;
41        public bool isBwImg;
42        public bool isBWImg;
43    }
44
45    [System.Serializable]
46    public class Description
47    {
48        public string[] tags;
49        public Captions[] captions;
50    }
51
52    [System.Serializable]
53    public class Captions
54    {
55        public string text;
56        public float confidence;
57    }
58
59    [System.Serializable]
60    public class Metadata
61    {
62        public int height;
63        public int width;
64        public string format;
65    }
66
67    UnityEngine.Windows.WebCam.PhotoCapture photoCaptureObject = null;
68    Texture2D targetTexture = null;
69
70    private string endpoint = "https:///vision/v3.1/analyze";
71    private string subscription_key = "";
72    private bool waitingForCapture;
73
74    void Start(){
75        waitingForCapture = false;
76    }
77
78    public void AirTap()
79    {
80        if (waitingForCapture) return;
81        waitingForCapture = true;
82
83        Resolution cameraResolution = UnityEngine.Windows.WebCam.PhotoCapture.SupportedResolutions.OrderByDescending((res) => res.width * res.height).First();
84        targetTexture = new Texture2D(cameraResolution.width, cameraResolution.height);
85
86        // PhotoCapture オブジェクトを作成します
87        UnityEngine.Windows.WebCam.PhotoCapture.CreateAsync(false, delegate (UnityEngine.Windows.WebCam.PhotoCapture captureObject) {
88            photoCaptureObject = captureObject;
89            UnityEngine.Windows.WebCam.CameraParameters cameraParameters = new UnityEngine.Windows.WebCam.CameraParameters();
90            cameraParameters.hologramOpacity = 0.0f;
91            cameraParameters.cameraResolutionWidth = cameraResolution.width;
92            cameraParameters.cameraResolutionHeight = cameraResolution.height;
93            cameraParameters.pixelFormat = UnityEngine.Windows.WebCam.CapturePixelFormat.BGRA32;
94
95            // カメラをアクティベートします
96            photoCaptureObject.StartPhotoModeAsync(cameraParameters, delegate (UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result) {
97                // 写真を撮ります
98                photoCaptureObject.TakePhotoAsync(OnCapturedPhotoToMemoryAsync);
99            });
100        });
101    }
102
103    async void OnCapturedPhotoToMemoryAsync(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result, UnityEngine.Windows.WebCam.PhotoCaptureFrame photoCaptureFrame)
104    {
105        // ターゲットテクスチャに RAW 画像データをコピーします
106        photoCaptureFrame.UploadImageDataToTexture(targetTexture);
107        byte[] bodyData = targetTexture.EncodeToJPG();
108
109        Response response = new Response();
110
111        try
112        {
113            string query = endpoint + "?visualFeatures=Categories,Description,Color";
114            var headers = new Dictionary<string, string>();
115            headers.Add("Ocp-Apim-Subscription-Key", subscription_key);
116            response = await Rest.PostAsync(query, bodyData, headers, -1, true);
117        }
118        catch (Exception e)
119        {
120            photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
121            return;
122        }
123
124        if (!response.Successful)
125        {
126            photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
127            return;
128        }
129
130        Debug.Log(response.ResponseCode);
131        Debug.Log(response.ResponseBody);
132        Analyze analyze = JsonUtility.FromJson(response.ResponseBody);
133        Debug.Log(analyze.description.captions[0].text);
134
135        // OpenCVを用いて結果をて画像に書き込み
136        Mat imgMat = new Mat(targetTexture.height, targetTexture.width, CvType.CV_8UC4);
137        Utils.texture2DToMat(targetTexture, imgMat);
138        Debug.Log("imgMat.ToString() " + imgMat.ToString());
139        Imgproc.putText(imgMat, analyze.description.captions[0].text, new Point(10, 100), Imgproc.FONT_HERSHEY_SIMPLEX, 4.0, new Scalar(255, 255, 0, 255), 4, Imgproc.LINE_AA, false);
140        Texture2D texture = new Texture2D(imgMat.cols(), imgMat.rows(), TextureFormat.RGBA32, false);
141        Utils.matToTexture2D(imgMat, texture);
142        Renderer quadRenderer = quad.GetComponent() as Renderer;
143        quadRenderer.material.SetTexture("_MainTex", texture);
144
145        // カメラを非アクティブにします
146        photoCaptureObject.StopPhotoModeAsync(OnStoppedPhotoMode);
147    }
148
149    void OnStoppedPhotoMode(UnityEngine.Windows.WebCam.PhotoCapture.PhotoCaptureResult result)
150    {
151        // photo capture のリソースをシャットダウンします
152        photoCaptureObject.Dispose();
153        photoCaptureObject = null;
154        waitingForCapture = false;
155    }
156}

4.endpointとsubscription_keyにメモしておいたものを貼り付けてください。

5.クエリパラメータはCategories,Description,Colorを指定しています。

1endpoint + "?visualFeatures=Categories,Description,Color";

6.MRTKのRestを用いて、分析する画像をPOSTしたら、response.ResponseBodyが次のように返ってきます。

1{'categories': [{'name': 'outdoor_', 'score': 0.00390625, 'detail': {'landmarks': []}}, {'name': 'outdoor_street', 'score': 0.33984375, 'detail': {'landmarks': []}}], 'color': {'dominantColorForeground': 'Brown', 'dominantColorBackground': 'Brown', 'dominantColors': ['Brown'], 'accentColor': 'B54316', 'isBwImg': False, 'isBWImg': False}, 'description': {'tags': ['building', 'outdoor', 'street', 'city', 'people', 'busy', 'night', 'ride', 'crowd'], 'captions': [{'text': 'a busy street with many people', 'confidence': 0.3540118634700775}]}, 'requestId': '88d8a3c6-8972-4315-8a15-b20bf14c33ce', 'metadata': {'height': 600, 'width': 450, 'format': 'Jpeg'}}

7.今回はリストのJsonにはなっていないので、JsonHelperを使わずにJsonUtilityで読むことができます。

8.仕様に合わせてAnalyzeクラスとCategoriesクラス、Colorクラス、Descriptionクラス、Captionsクラス、Metadataクラスを作成しました。

9.生成された画像説明文をOpenCVを用いて、画像に張り付けてquadのテクスチャに表示します。

実行

HoloLens2にデプロイして、実行した結果がこちらになります。Editor上でもできるので試してみてください。

HoloLens2にデプロイして、実行した結果がこちらになります。Editor上でもできるので試してみてください。

“a laptop on a desk”

20201205_184245_HoloLens.jpg
“a person using a laptop”

“a person using a laptop”

20201205_185710_HoloLens.jpg

お疲れ様でした。

明日(というか遅れたので今日)は、弟子( @Horomoto-Asahi )による「Holo関連のなにか書きます」です。

参考

この記事を書いた人

azure-recipe-user