Help us understand the problem. What is going on with this article?

AzureのFace APIを使って顔の類似度を判定する②

この記事はAzureのFace APIを使って顔の類似度を判定する①の続きです。

前回はFace APIのFace Detectを使用して、まずはFaceIDなどの必要な情報を取得してきました。
今回はそれを使用して、Face FindSimilarによる類似度判定や、OpenCVSharpでの顔検出箇所の描画を行っていきます。

ちなみに前回Face Detectから取得した顔情報は、以下のようなList<FaceInfo>型に整形してMainメソッドで受け取りました。
FaceInfoは検出時のFaceIDと検出箇所の座標、それが比較元と比較先画像のどちらから検出したものなのかという情報を持つ独自クラス型です。

faceDivision: base , sourceUrl: 比較元画像のファイルパス
faceId: 206b61c0-6539-4ad8-bf01-2fd8c18537c1
Rectangle: {874, 241, 210, 210}

faceDivision: target , sourceUrl: 比較先画像のファイルパス
faceId: 68b07497-0502-41e9-9af1-290348848b1e
Rectangle: {765, 260, 148, 148}

faceDivision: target , sourceUrl: 比較先画像のファイルパス
faceId: 2df5c831-f10c-475e-a6c8-734a3cc8c04e
Rectangle: {545, 347, 124, 124}

4. OpenCVSharpで検出箇所を画像に描画する

今のままでは1枚の画像から複数人の顔が検出された際に誰が誰だかわからないので、最低限検出した箇所と、その際に割り当てられたFaceIDを描画したいと思います。
画像への描画には、画像処理・画像解析のライブラリであるOpenCVSharp(OpenCVのラッパー)を使用します。

OpenCVSharp3の追加

NuGetからインストールします。
VisualStudioのメニューから「プロジェクト」→「NuGetパッケージの管理」を選択してください。
管理画面が表示されたら、「参照」の検索窓から「opencvsharp」と検索し、「OpenCvSharp3-AnyCPU」をインストールしましょう。現時点での最新版は「4.0.0.20181129」でした。
キャプチャ7.PNG
インストールが成功すると、ソリューションエクスプローラー上で参照に色々追加されているのが確認できると思います。
キャプチャ8.PNG
ついでにusingにもOpenCvSharpを追加しておきましょう。

FaceFindSimilar.cs
using OpenCvSharp;

描画用メソッドの実装

それでは、実際にOpenCVSharpを使用して描画する為のメソッドを追加します。
以下の2つのメソッドをFaceFindSimilarクラス内に追加してください。

FaceFindSimilar.cs
        static void dispImageByOpenCV(string urlBase, string urlTarget, List<FaceInfo> faceInfoList)
        {
            // FaceInfoクラスのリストを仕分け
            List<FaceInfo> faceInfoBaseList = faceInfoList.Where(x => x.faceDivision == DIVISION_BASE).ToList();
            List<FaceInfo> faceInfoTargetList = faceInfoList.Where(x => x.faceDivision == DIVISION_TARGET).ToList();

            // 比較元画像の表示
            ShowImage(urlBase, faceInfoBaseList);

            // 比較先画像の表示
            ShowImage(urlTarget, faceInfoTargetList);
        }

        static void ShowImage(string url, List<FaceInfo> faceInfoList)
        {
            // 画像の読み込み
            using (Mat image = new Mat(url))
            {
                foreach (FaceInfo faceInfo in faceInfoList)
                {
                    // 顔の検出箇所に赤枠を表示
                    Rect rect = new Rect(faceInfo.recLeft, faceInfo.recTop, faceInfo.recWidth, faceInfo.recHeight);
                    Cv2.Rectangle(image, rect, new Scalar(0, 0, 255), 2);

                    // FaceIDを描画
                    Cv2.PutText(image, faceInfo.faceId, new Point(faceInfo.recLeft, faceInfo.recTop)
                        , HersheyFonts.HersheyComplexSmall, 1, new Scalar(255, 0, 255), 1, LineTypes.AntiAlias);
                }

                Cv2.ImShow(url, image);
                Cv2.WaitKey(0);
            }
        }

dispImageByOpenCVメソッドではLinqで比較元画像の顔情報と比較先画像の顔情報を分けてあげて、ShowImageメソッドでそれぞれの画像に対してCv2.RectangleCv2.PutTextで顔周りの四角い枠とFaceIDを描画しています。

Mainメソッドに呼び出し処理を追加

ではdispImageByOpenCVをMainメソッドから呼び出してみましょう。
dispImageByOpenCVメソッドはMainメソッドから引数として以下を受け取ります。

  • 比較元画像のファイルパス
  • 比較先画像のファイルパス
  • Face Detectで取得した顔情報のリスト
FaceFindSimilar.cs
        static void Main(string[] args)
        {
            Dictionary<string, string> imageUrls = new Dictionary<string, string>();
            imageUrls.Add(DIVISION_BASE, URL_BASE);
            imageUrls.Add(DIVISION_TARGET, URL_TARGET);

            // FindSimilarのパラメータとなるfaceId、その他を取得
            List<FaceInfo> faceInfoList = FaceDetect.Detect(imageUrls);

            if (faceInfoList.Exists(x => x.faceDivision == DIVISION_BASE)
                && faceInfoList.Exists(x => x.faceDivision == DIVISION_TARGET))
            {
                Console.WriteLine("顔が検出できました。:");

                foreach (FaceInfo faceInfo in faceInfoList)
                {
                    Console.WriteLine("faceDivision: " + faceInfo.faceDivision + " , sourceUrl: " + faceInfo.sourceUrl
                        + Environment.NewLine + "faceId: " + faceInfo.faceId
                        + Environment.NewLine + "Rectangle: {" + faceInfo.recLeft + ", " + faceInfo.recTop + ", " + faceInfo.recWidth + ", " + faceInfo.recHeight + "}"
                        + Environment.NewLine);
                }

                // 追加箇所 From----------------------------------------
                dispImageByOpenCV(URL_BASE, URL_TARGET, faceInfoList);
                // 追加箇所 To------------------------------------------
            }
            else
            {
                Console.WriteLine("顔が検出できませんでした。");
            }

            Console.ReadLine();
            // 追加箇所 From----------------------------------------
            Cv2.DestroyAllWindows();
            // 追加箇所 To------------------------------------------

        }

// 追加箇所 From(~To)が呼び出し箇所です。
特に戻り値もないので、それぞれのファイルパスと顔情報のリストをそのまま渡しただけですね。
描画した画像ウィンドウを閉じるCv2.DestroyAllWindows();も忘れずに記述しましょう。

ここまででF5実行すると、以下のように顔周りに赤い四角い枠とFaceIDが描画されると思います。
キャプチャ9.PNG
キャプチャ10.PNG
以上で画像の描画処理は終了となります。

5. Face FindSimilarで類似度の判定

やっとという感じですが、Face FindSimilarを使用して類似度の判定を行いましょう。
まずは以下をコピペして、using4つとメソッド1つとクラス2つを追加してください。
書いといてなんですが、詰め込みすぎですね。すみません。
追加してください。(曲げない)

using

FaceFindSimilar
using System.Configuration;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;

FindSimilarRequestメソッド

FaceFindSimilar
        static async void FindSimilarRequest(List<FaceInfo> faceInfoList)
        {
            var client = new HttpClient();

            // リクエストヘッダー
            client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", ConfigurationManager.AppSettings["subscriptionKey"]);

            string uri = ConfigurationManager.AppSettings["requestUrl"] + "findsimilars";

            HttpResponseMessage response;

            List<string> targetFaceIdList = new List<string>();

            // FindSimilarの第二引数(faceIds)作成
            foreach (FaceInfo faceInfoTarget in faceInfoList.FindAll(x => x.faceDivision == DIVISION_TARGET))
            {
                targetFaceIdList.Add(faceInfoTarget.faceId);
            }

            foreach (FaceInfo faceInfoBase in faceInfoList.FindAll(x => x.faceDivision == DIVISION_BASE))
            {
                Console.WriteLine("■FaceID:[" + faceInfoBase.faceId + "] との類似度判定");

                // リクエストボディをJSON形式にする
                MatchFaceSimilar matchFaceSimilar = new MatchFaceSimilar(faceInfoBase.faceId, targetFaceIdList.ToArray());
                var json = JsonConvert.SerializeObject(matchFaceSimilar);

                byte[] byteData = Encoding.UTF8.GetBytes(json);

                using (var content = new ByteArrayContent(byteData))
                {
                    // リクエストヘッダーの作成
                    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                    response = await client.PostAsync(uri, content);

                    // 実行結果からJSONの取得
                    var contentString = await response.Content.ReadAsStringAsync();
                    //Console.WriteLine(contentString);

                    // JSONを整形して出力
                    List<FaceConfidence> faceConfidence = JsonConvert.DeserializeObject<List<FaceConfidence>>(contentString);
                    faceConfidence.ForEach((face) =>
                    {
                        decimal confidence = Math.Round(decimal.Parse(face.confidence) * 100, 2, MidpointRounding.AwayFromZero);
                        Console.WriteLine(" " + face.faceId + " : " + confidence.ToString() + "%");
                    });
                }
            }

            Console.WriteLine("処理終了");
        }

FaceConfidenceクラス、MatchFaceSimilarクラス

FaceFindSimilar
    public class FaceConfidence
    {
        public string faceId { get; set; }
        public string confidence { get; set; }
    }

    [JsonObject]
    public class MatchFaceSimilar
    {
        const int MAX_NUM = 20;
        const string MODE_MATCH_PERSON = "matchPerson";
        const string MODE_MATCH_FACE = "matchFace";

        [JsonProperty("faceId")]
        public string FaceId { get; private set; }

        [JsonProperty("faceIds")]
        public string[] FaceIds { get; private set; }

        [JsonProperty("maxNumOfCandidatesReturned")]
        public int MaxNumOfCandidatesReturned { get; private set; }

        [JsonProperty("mode")]
        public string Mode { get; private set; }

        public MatchFaceSimilar(
            string faceId
            , string[] faceIds
            , int maxNumOfCandidatesReturned = MAX_NUM
            , string mode = MODE_MATCH_FACE
            )
        {
            this.FaceId = faceId;
            this.FaceIds = faceIds;
            this.MaxNumOfCandidatesReturned = maxNumOfCandidatesReturned;
            this.Mode = mode;
        }
    }

流れとしてはMainメソッドからFindSimilarRequestメソッドを呼び出し、その中でFace FindSimilarへ投げる際のリクエストヘッダ、リクエストボディを整形して、各類似度を取得しています。

Face FindSimilarに投げる際のリクエストボディはJSONにして渡さないといけないので、MatchFaceSimilar独自クラスのインスタンスを作成後、Json.NETのJsonConvert.SerializeObject()を使用してシリアライズしています。

ちなみに公式ドキュメントによると、以下のような形式のJSONにする必要があります。(一部加工しています)

{
    "faceId": "比較元画像のFaceID",
    "faceIds": ["比較先画像のFaceID_1","比較先画像のFaceID_2","比較先画像のFaceID_3",...],
    "maxNumOfCandidatesReturned": 20,
    "mode": "matchFace"
}

MatchFaceSimilarクラス内のJsonPropertyには上記のキー項目名を設定しています。

また、FaceConfidenceクラスはFace FindSimilarからの結果JSONを受け取るための独自クラスです。FindSimilarRequestメソッド内で、Face FindSimilar実行後にcontentStringに入った結果JSONをJsonConvert.DeserializeObject()でデシリアライズし、FaceConfidenceクラスのインスタンスを作成しています。

FaceFindSimilarの実行

最後に、MainメソッドでdispImageByOpenCV(URL_BASE, URL_TARGET, faceInfoList);としていた部分を以下のコードで上書きして、実行してみましょう。

FaceFindSimilar.cs
                try
                {
                    dispImageByOpenCV(URL_BASE, URL_TARGET, faceInfoList);
                    FindSimilarRequest(faceInfoList);
                }
                catch (Exception e)
                {
                    Console.WriteLine("例外:" + e.Message);
                }

ここでは以下の画像を使用して試してみます。

比較元:ちょいワルになりきれない男たちのフリー画像(写真)
OYtantei0I9A9515_TP_V.jpg

比較先:社員の不正を暴く監査役のフリー画像(写真)
AL206_kinshi220140810213504_TP_V.jpg

実行結果

顔が検出できました。:
faceDivision: base , sourceUrl: {"比較元画像ファイルパス"}
faceId: dea8b4c8-550a-46a9-b289-327fd8b9eaeb
Rectangle: {885, 216, 150, 150}

faceDivision: base , sourceUrl: {"比較元画像ファイルパス"}
faceId: 97812fab-a7b8-4fc6-a337-022b74ca3f3f
Rectangle: {472, 197, 139, 139}

faceDivision: target , sourceUrl: {"比較先画像ファイルパス"}
faceId: 63b697e5-3043-419b-9c10-95e59a1641c0
Rectangle: {786, 558, 105, 105}

faceDivision: target , sourceUrl: {"比較先画像ファイルパス"}
faceId: 28cb05e9-68bf-4a43-92e4-1d4c31ad5b2a
Rectangle: {1054, 242, 103, 103}

■FaceID:[dea8b4c8-550a-46a9-b289-327fd8b9eaeb] との類似度判定
 28cb05e9-68bf-4a43-92e4-1d4c31ad5b2a : 27.62%
 63b697e5-3043-419b-9c10-95e59a1641c0 : 9.75%
■FaceID:[97812fab-a7b8-4fc6-a337-022b74ca3f3f] との類似度判定
 28cb05e9-68bf-4a43-92e4-1d4c31ad5b2a : 61.75%
 63b697e5-3043-419b-9c10-95e59a1641c0 : 13.41%
処理終了

キャプチャ11.PNG
キャプチャ12.PNG

比較元画像から2人分の顔が検出され、比較先画像で検出された2人分の顔とそれぞれ比較、類似度が判定されていますね。

以上で終了になります。

おわり

.NET Framework(C#, VB)を用いてFace FindSimilarの類似判定をしている記事が意外に見つけられなかったので、自分でやってみました。

SDKでNuGetに「Microsoft.Azure.CognitiveServices.Vision.Face」とかいうすごく便利そうなものがあると知ったのは作った後のこと。泣きそう…

次はちゃんとこの辺活用したコード書きます!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした