.NET做人脸识别并分类的实现示例
在游乐场、玻璃天桥、滑雪场等娱乐场所,经常能看到有摄影师在拍照片,令这些经营者发愁的一件事就是照片太多了,客户在成千上万张照片中找到自己可不是件容易的事。在一次游玩等活动或家庭聚会也同理,太多了照片导致挑选十分困难。
还好有.NET,只需少量代码,即可轻松找到人脸并完成分类。
本文将使用MicrosoftAzure云提供的认知服务(CognitiveServices)API来识别并进行人脸分类,可以免费使用,注册地址是:https://portal.azure.com。注册完成后,会得到两个密钥,通过这个密钥即可完成本文中的所有代码,这个密钥长这个样子(非真实密钥):
fa3a7bfd807ccd6b17cf559ad584cbaa
使用方法
首先安装NuGet包Microsoft.Azure.CognitiveServices.Vision.Face,目前最新版是2.5.0-preview.1,然后创建一个FaceClient:
stringkey="fa3a7bfd807ccd6b17cf559ad584cbaa";//替换为你的key
usingvarfc=newFaceClient(newApiKeyServiceClientCredentials(key))
{
Endpoint="https://southeastasia.api.cognitive.microsoft.com",
};
然后识别一张照片:
usingvarfile=File.OpenRead(@"C:\Photos\DSC_996ICU.JPG"); IListfaces=awaitfc.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保存了其人脸的位置信息,可供对其做进一步操作。RecognitionModel、FaceLandmarks、FaceAttributes是一些额外属性,包括识别性别、年龄、表情等信息,默认不识别,如下图API所示,可以通过各种参数配置,非常好玩,有兴趣的可以试试:
最后,通过.GroupAsync来将之前识别出的多个faceId进行分类:
varfaceIds=faces.Select(x=>x.FaceId.Value).ToList(); GroupResultreslut=awaitfc.Face.GroupAsync(faceIds);
返回了一个GroupResult,其对象定义如下:
publicclassGroupResult
{
publicIList>Groups
{
get;
set;
}
publicIListMessyGroup
{
get;
set;
}
//...
}
包含了一个Groups对象和一个MessyGroup对象,其中Groups是一个数据的数据,用于存放人脸的分组,MessyGroup用于保存未能找到分组的FaceId。
有了这个,就可以通过一小段简短的代码,将不同的人脸组,分别复制对应的文件夹中:
voidCopyGroup(stringoutputPath,GroupResultresult,Dictionaryfaces) { foreach(variteminresult.Groups .SelectMany((group,index)=>group.Select(v=>(faceId:v,index))) .Select(x=>(info:faces[x.faceId],i:x.index+1)).Dump()) { stringdir=Path.Combine(outputPath,item.i.ToString()); Directory.CreateDirectory(dir); File.Copy(item.info.file,Path.Combine(dir,Path.GetFileName(item.info.file)),overwrite:true); } stringmessyFolder=Path.Combine(outputPath,"messy"); Directory.CreateDirectory(messyFolder); foreach(varfileinresult.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(thefirstframe),andBMPformataresupported.Theallowedimagefilesizeisfrom1KBto6MB.
- Theminimumdetectablefacesizeis36x36pixelsinanimagenolargerthan1920x1080pixels.Imageswithdimensionshigherthan1920x1080pixelswillneedaproportionallylargerminimumfacesize.
因此,如果图片太大,必须进行一定的压缩(当然如果图片太小,显然也没必要进行压缩了),使用.NET的Bitmap,并结合C#8.0的switchexpression,这个判断逻辑以及压缩代码可以一气呵成:
byte[]CompressImage(stringimage,intedgeLimit=1920)
{
usingvarbmp=Bitmap.FromFile(image);
usingvarresized=(1.0*Math.Max(bmp.Width,bmp.Height)/edgeLimit)switch
{
varxwhenx>1=>newBitmap(bmp,newSize((int)(bmp.Size.Width/x),(int)(bmp.Size.Height/x))),
_=>bmp,
};
usingvarms=newMemoryStream();
resized.Save(ms,ImageFormat.Jpeg);
returnms.ToArray();
}
竖立的照片
相机一般都是3:2的传感器,拍出来的照片一般都是横向的。但偶尔寻求一些构图的时候,我们也会选择纵向构图。虽然现在许多API都支持正负30度的侧脸,但竖着的脸API基本都是不支持的,如下图(实在找不到可以授权使用照片的模特了