1. 程式人生 > >.NET做人臉識別並分類

.NET做人臉識別並分類

.NET做人臉識別並分類

在遊樂場、玻璃天橋、滑雪場等娛樂場所,經常能看到有攝影師在拍照片,令這些經營者發愁的一件事就是照片太多了,客戶在成千上萬張照片中找到自己可不是件容易的事。在一次遊玩等活動或家庭聚會也同理,太多了照片導致挑選十分困難。

還好有.NET,只需少量程式碼,即可輕鬆找到人臉並完成分類。

本文將使用Microsoft Azure雲提供的認知服務Cognitive ServicesAPI來識別並進行人臉分類,可以免費使用,註冊地址是:https://portal.azure.com。註冊完成後,會得到兩個金鑰,通過這個金鑰即可完成本文中的所有程式碼,這個金鑰長這個樣子(非真實金鑰):

fa3a7bfd807ccd6b17cf559ad584cbaa

使用方法

首先安裝NuGetMicrosoft.Azure.CognitiveServices.Vision.Face,目前最新版是2.5.0-preview.1,然後建立一個FaceClient

string key = "fa3a7bfd807ccd6b17cf559ad584cbaa"; // 替換為你的key
using var fc = new FaceClient(new ApiKeyServiceClientCredentials(key))
{
    Endpoint = "https://southeastasia.api.cognitive.microsoft.com",
};

然後識別一張照片:

using var file = File.OpenRead(@"C:\Photos\DSC_996ICU.JPG");
IList<DetectedFace> faces = await fc.Face.DetectWithStreamAsync(file);

其中返回的faces是一個IList結構,很顯然一次可以識別出多個人臉,其中一個示例返回結果如下(已轉換為JSON):

[
    {
      "FaceId": "9997b64e-6e62-4424-88b5-f4780d3767c6",
      "RecognitionModel": null,
      "FaceRectangle": {
        "Width": 174,
        "Height": 174,
        "Left": 62,
        "Top": 559
      },
      "FaceLandmarks": null,
      "FaceAttributes": null
    },
    {
      "FaceId": "8793b251-8cc8-45c5-ab68-e7c9064c4cfd",
      "RecognitionModel": null,
      "FaceRectangle": {
        "Width": 152,
        "Height": 152,
        "Left": 775,
        "Top": 580
      },
      "FaceLandmarks": null,
      "FaceAttributes": null
    }
  ]

可見,該照片返回了兩個DetectedFace物件,它用FaceId儲存了其Id,用於後續的識別,用FaceRectangle儲存了其人臉的位置資訊,可供對其做進一步操作。RecognitionModelFaceLandmarksFaceAttributes是一些額外屬性,包括識別性別年齡表情等資訊,預設不識別,如下圖API所示,可以通過各種引數配置,非常好玩,有興趣的可以試試:

最後,通過.GroupAsync來將之前識別出的多個faceId進行分類:

var faceIds = faces.Select(x => x.FaceId.Value).ToList();
GroupResult reslut = await fc.Face.GroupAsync(faceIds);

返回了一個GroupResult,其物件定義如下:

public class GroupResult
{
    public IList<IList<Guid>> Groups
    {
        get;
        set;
    }

    public IList<Guid> MessyGroup
    {
        get;
        set;
    }

    // ...
}

包含了一個Groups物件和一個MessyGroup物件,其中Groups是一個數據的資料,用於存放人臉的分組,MessyGroup用於儲存未能找到分組的FaceId

有了這個,就可以通過一小段簡短的程式碼,將不同的人臉組,分別複製對應的資料夾中:

void CopyGroup(string outputPath, GroupResult result, Dictionary<Guid, (string file, DetectedFace face)> faces)
{
    foreach (var item in result.Groups
        .SelectMany((group, index) => group.Select(v => (faceId: v, index)))
        .Select(x => (info: faces[x.faceId], i: x.index + 1)).Dump())
    {
        string dir = Path.Combine(outputPath, item.i.ToString());
        Directory.CreateDirectory(dir);
        File.Copy(item.info.file, Path.Combine(dir, Path.GetFileName(item.info.file)), overwrite: true);
    }
    
    string messyFolder = Path.Combine(outputPath, "messy");
    Directory.CreateDirectory(messyFolder);
    foreach (var file in result.MessyGroup.Select(x => faces[x].file).Distinct())
    {
        File.Copy(file, Path.Combine(messyFolder, Path.GetFileName(file)), overwrite: true);
    }
}

然後就能得到執行結果,如圖,我傳入了102張照片,輸出了15個分組和一個“未找到隊友”的分組:

還能有什麼問題?

就兩個API呼叫而已,程式碼一把梭,感覺太簡單了?其實不然,還會有很多問題。

圖片太大,需要壓縮

畢竟要把圖片上傳到雲服務中,如果上傳網速不佳,流量會挺大,而且現在的手機、單反、微單都能輕鬆達到好幾千萬畫素,jpg大小輕鬆上10MB,如果不壓縮就上傳,一來流量和速度遭不住。

二來……其實Azure也不支援,文件(https://docs.microsoft.com/en-us/rest/api/cognitiveservices/face/face/detectwithstream)顯示,最大僅支援6MB的圖片,且圖片大小應不大於1920x1080的解析度:

  • JPEG, PNG, GIF (the first frame), and BMP format are supported. The allowed image file size is from 1KB to 6MB.
  • The minimum detectable face size is 36x36 pixels in an image no larger than 1920x1080 pixels. Images with dimensions higher than 1920x1080 pixels will need a proportionally larger minimum face size.

因此,如果圖片太大,必須進行一定的壓縮(當然如果圖片太小,顯然也沒必要進行壓縮了),使用.NETBitmap,並結合C# 8.0switch expression,這個判斷邏輯以及壓縮程式碼可以一氣呵成:

byte[] CompressImage(string image, int edgeLimit = 1920)
{
    using var bmp = Bitmap.FromFile(image);
    
    using var resized = (1.0 * Math.Max(bmp.Width, bmp.Height) / edgeLimit) switch
    {
        var x when x > 1 => new Bitmap(bmp, new Size((int)(bmp.Size.Width / x), (int)(bmp.Size.Height / x))), 
        _ => bmp, 
    };
    
    using var ms = new MemoryStream();
    resized.Save(ms, ImageFormat.Jpeg);
    return ms.ToArray();
}

豎立的照片

相機一般都是3:2的感測器,拍出來的照片一般都是橫向的。但偶爾尋求一些構圖的時候,我們也會選擇縱向構圖。雖然現在許多API都支援正負30度的側臉,但豎著的臉API基本都是不支援的,如下圖(實在找不到可以授權使用照片的模特了