以前、顔認証アプリを作成しました(C#で顔検出アプリを作成してみるを参照)。このときはOpenCvSharpを使いましたが、今回はAzure.CognitiveServicesを使います。
キーとエンドポイントを取得する
Face API に接続するには、キーとエンドポイントが必要です。まず、ここからAzure サブスクリプション – 無料アカウントを作成します。そしてAzure サブスクリプションを入手したら、Azure portal で Face リソースを作成し、デプロイされたらキーとエンドポイントを取得します。
これはクイック スタート:Face クライアント ライブラリを使用するを参考に作成したコードです。
まず上記の手順で作成されたSUBSCRIPTION_KEYとENDPOINTを指定します。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); } const string SUBSCRIPTION_KEY = "XXXXXXXXXXXXXXXXXXXXXXX"; const string ENDPOINT = "https://XXXX.cognitiveservices.azure.com/"; } |
画像から顔情報を取得する
ボタンがクリックされたら画像ファイルであることを確認してから自作メソッド DetectFaceExtractを実行します。これは非同期処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public partial class Form1 : Form { private async void button1_Click(object sender, EventArgs e) { string filePath = ""; OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) filePath = dialog.FileName; dialog.Dispose(); // filePathが空ならファイルは選択されていない if (filePath == "") return; // Image.FromFileメソッドを実行して例外が発生するなら、これは画像ファイルではない try { Image image = Image.FromFile(filePath); image.Dispose(); } catch { MessageBox.Show("これは画像ファイルではありません!", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } IFaceClient client = new FaceClient(new ApiKeyServiceClientCredentials(SUBSCRIPTION_KEY)) { Endpoint = ENDPOINT }; await DetectFaceExtract(client, filePath, RecognitionModel.Recognition04); } } |
取得する属性を決める
DetectFaceExtractメソッドでは最初に顔に関する何を取得するのかを決めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { void SetReturnFaceAttributes(List<FaceAttributeType> faceAttributeTypes) { faceAttributeTypes.Add(FaceAttributeType.Accessories); faceAttributeTypes.Add(FaceAttributeType.Age); faceAttributeTypes.Add(FaceAttributeType.Blur); faceAttributeTypes.Add(FaceAttributeType.Emotion); faceAttributeTypes.Add(FaceAttributeType.Exposure); faceAttributeTypes.Add(FaceAttributeType.FacialHair); faceAttributeTypes.Add(FaceAttributeType.Gender); faceAttributeTypes.Add(FaceAttributeType.Glasses); faceAttributeTypes.Add(FaceAttributeType.Hair); faceAttributeTypes.Add(FaceAttributeType.HeadPose); faceAttributeTypes.Add(FaceAttributeType.Makeup); faceAttributeTypes.Add(FaceAttributeType.Noise); faceAttributeTypes.Add(FaceAttributeType.Occlusion); faceAttributeTypes.Add(FaceAttributeType.Smile); } } |
FaceAttributeType.Accessories
特定の顔にアクセサリ(帽子、眼鏡、マスクなど)があるかどうか。その信頼度スコア(0~1で評価)。
FaceAttributeType.Age
顔の推定年齢
FaceAttributeType.Blur
画像内の顔のぼかしの程度(0~1で評価)。
FaceAttributeType.Emotion
感情。喜び、悲しみ、中立、怒り、軽蔑、嫌悪感、驚き、恐怖(それぞれを0~1で評価)
FaceAttributeType.Exposure
顔の露出の程度(0~1で評価)
FaceAttributeType.FacialHair
顔ひげの有無と長さ
FaceAttributeType.Gender
性別
FaceAttributeType.Glasses
眼鏡があるかどうか。NoGlasses、ReadingGlasses、Sunglasses、Swimming Goggles
FaceAttributeType.Hair
髪の毛が見えるかどうか、はげが検出されたかどうか、どのような髪の色か。
FaceAttributeType.HeadPose
3 次元空間での顔の向き。上向きか下向きか、左向きか右向きか、回転しているか。
FaceAttributeType.Makeup
化粧しているか。
FaceAttributeType.Noise
視覚ノイズ(0~1で評価)
FaceAttributeType.Occlusion
顔のパーツをブロックするオブジェクトがあるかどうか
FaceAttributeType.Smile
笑顔表現(0~1で評価)
属性を取得する処理
これらをFaceAttributeTypeのリストに格納してIFaceClient.Face.DetectWithStreamAsyncメソッドに渡します。クイック スタート:Face クライアント ライブラリを使用するでは全部ひとつのメソッドに詰め込んでいますが長くなりすぎているので、この記事では各メソッドにわけました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
public partial class Form1 : Form { public async Task DetectFaceExtract(IFaceClient client, string filePath, string recognitionModel) { IList<DetectedFace> detectedFaces; List<FaceAttributeType> faceAttributeTypes = new List<FaceAttributeType>(); SetReturnFaceAttributes(faceAttributeTypes); System.IO.FileStream fileStream = new System.IO.FileStream(filePath, System.IO.FileMode.Open); detectedFaces = await client.Face.DetectWithStreamAsync( fileStream, returnFaceAttributes: faceAttributeTypes, detectionModel: DetectionModel.Detection01, recognitionModel: recognitionModel); fileStream.Close(); List<FaceRectangle> rectangles = new List<FaceRectangle>(); StringBuilder sb = new StringBuilder(); foreach (DetectedFace face in detectedFaces) { // 顔を囲む矩形を取得する rectangles.Add(face.FaceRectangle); sb.Append($"Rectangle(Left/Top/Width/Height) : {face.FaceRectangle.Left} {face.FaceRectangle.Top} {face.FaceRectangle.Width} {face.FaceRectangle.Height}\n"); // 顔の属性を文字列で取得していく if(face.FaceAttributes.Accessories != null) sb.Append($"アクセサリ : \n{GetAccessoriesOfFace(face)}\n"); if(face.FaceAttributes.Age != null) sb.Append($"年齢 : {face.FaceAttributes.Age}\n"); if(face.FaceAttributes.Blur != null) sb.Append($"ぼかし : {face.FaceAttributes.Blur.Value*100} %\n"); if(face.FaceAttributes.Emotion != null) sb.Append($"感情 : {GetEmotionOnFace(face)}\n"); if (face.FaceAttributes.Exposure != null) sb.Append($"露出 : {face.FaceAttributes.Exposure.Value * 100} %\n"); if (face.FaceAttributes.FacialHair != null) sb.Append($"ひげ : {GetFacialHairOnFace(face)}\n"); if (face.FaceAttributes.Gender != null) { if (face.FaceAttributes.Gender == Gender.Male) sb.Append($"性別 : 男性\n"); else sb.Append($"性別 : 女性\n"); } if (face.FaceAttributes.Glasses != null) sb.Append($"メガネ : {face.FaceAttributes.Glasses}\n"); if (face.FaceAttributes.Hair != null) sb.Append($"髪の毛 : {GetHairColor(face)}\n"); if (face.FaceAttributes.HeadPose != null) sb.Append($"頭部姿勢 : {HeadPose(face)}\n"); if (face.FaceAttributes.Makeup != null) sb.Append($"化粧 : {string.Format("{0}", (face.FaceAttributes.Makeup.EyeMakeup || face.FaceAttributes.Makeup.LipMakeup) ? "Yes" : "No")}\n"); if (face.FaceAttributes.Noise != null) sb.Append($"視覚ノイズ : {face.FaceAttributes.Noise.Value * 100} %\n"); if (face.FaceAttributes.Occlusion != null) sb.Append($"オクルージョン: \n{string.Format("EyeOccluded: {0}", face.FaceAttributes.Occlusion.EyeOccluded ? "Yes" : "No")} " + $" {string.Format("ForeheadOccluded: {0}", face.FaceAttributes.Occlusion.ForeheadOccluded ? "Yes" : "No")} {string.Format("MouthOccluded: {0}", face.FaceAttributes.Occlusion.MouthOccluded ? "Yes" : "No")}\n"); if (face.FaceAttributes.Smile != null) sb.Append($"笑顔 : {face.FaceAttributes.Smile * 100} %\n"); sb.Append("\n"); } // 取得した属性にかんする文字列を表示 richTextBox1.Text = sb.ToString(); // 顔を矩形で囲んだBitmapを表示する Bitmap bitmap = new Bitmap(filePath); Graphics graphics = Graphics.FromImage(bitmap); foreach (var rectangle in rectangles) { Point point = new Point(rectangle.Left, rectangle.Top); Size size = new Size(rectangle.Width, rectangle.Height); graphics.DrawRectangle(Pens.White, new Rectangle(point, size)); } pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; pictureBox1.Image = new Bitmap(bitmap); bitmap.Dispose(); } } |
GetAccessoriesOfFaceメソッドはアクセサリがあるかどうかを文字列で返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { string GetAccessoriesOfFace(DetectedFace face) { // Get accessories of the faces List<Accessory> accessoriesList = (List<Accessory>)face.FaceAttributes.Accessories; int count = face.FaceAttributes.Accessories.Count; if (count == 0) return "無し"; else { string accessory = ""; for (int i = 0; i < count; ++i) accessory += String.Format("{0} (信用度:{1}), ", accessoriesList[i].Type.ToString(), accessoriesList[i].Confidence); return accessory; } } } |
GetEmotionOnFaceメソッドは喜び、悲しみ、中立、怒り、軽蔑、嫌悪感、驚き、恐怖の各感情のなかでもっともスコアの高いものと、そのスコア(0~100)を返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public partial class Form1 : Form { string GetEmotionOnFace(DetectedFace face) { string emotionType = string.Empty; double emotionValue = 0.0; Emotion emotion = face.FaceAttributes.Emotion; if (emotion.Anger > emotionValue) { emotionValue = emotion.Anger; emotionType = "怒り"; } if (emotion.Contempt > emotionValue) { emotionValue = emotion.Contempt; emotionType = "軽蔑"; } if (emotion.Disgust > emotionValue) { emotionValue = emotion.Disgust; emotionType = "嫌悪"; } if (emotion.Fear > emotionValue) { emotionValue = emotion.Fear; emotionType = "恐怖"; } if (emotion.Happiness > emotionValue) { emotionValue = emotion.Happiness; emotionType = "喜び"; } if (emotion.Neutral > emotionValue) { emotionValue = emotion.Neutral; emotionType = "中立"; } if (emotion.Sadness > emotionValue) { emotionValue = emotion.Sadness; emotionType = "悲しみ"; } if (emotion.Surprise > emotionValue) { emotionValue = emotion.Surprise; emotionType = "驚き"; } return String.Format("{0} (感情スコア:{1})\n", emotionType, emotionValue * 100); } } |
GetFacialHairOnFaceメソッドは推定される顔ひげの有無と長さを文字列で返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { string GetFacialHairOnFace(DetectedFace face) { double moustache = face.FaceAttributes.FacialHair.Moustache; double beard = face.FaceAttributes.FacialHair.Beard; double sideburns = face.FaceAttributes.FacialHair.Sideburns; if (moustache + beard + sideburns == 0) return "無し"; string ret = ""; if (moustache > 0) ret += "口ひげ、"; if (beard > 0) ret += "ひげ、"; if (sideburns > 0) ret += "もみあげ、"; return ret; } } |
GetHairColorメソッドは髪の毛が見えるかどうか、はげが検出されたかどうか、どのような髪の色が検出されたかを文字列で返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public partial class Form1 : Form { string GetHairColor(DetectedFace face) { Hair hair = face.FaceAttributes.Hair; string color = null; if (hair.HairColor.Count == 0) { if (hair.Invisible) return "見えない"; else return "はげ"; } double maxConfidence = 0.0f; foreach (HairColor hairColor in hair.HairColor) { if (hairColor.Confidence <= maxConfidence) continue; maxConfidence = hairColor.Confidence; color = hairColor.Color.ToString(); } return String.Format("{0} (スコア:{1} %)\n", color, maxConfidence * 100); } } |
HeadPoseメソッドは頭部姿勢(3次元空間での顔の向き)を文字列で返します。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { string HeadPose(DetectedFace face) { double pitch = Math.Round(face.FaceAttributes.HeadPose.Pitch, 2); double roll = Math.Round(face.FaceAttributes.HeadPose.Roll, 2); double yaw = Math.Round(face.FaceAttributes.HeadPose.Yaw, 2); return string.Format("Pitch: {0}, Roll: {1}, Yaw: {2}", pitch, roll, yaw); } } |