Azure FaceAPIを使って顔認証をしてみよう
本チュートリアルの内容
■ 開発環境の確認
前提とする開発環境
このチュートリアルでは、以下の環境を想定します。
- 開発PC:Windows 8.1 Professional(64bit)
- 開発環境:Visual Studio2013 C#
■ Azure Congnitive Service
Azure Cognitive Serviceは、マイクロソフト社が提供する各種の自動認識サービスを提供するクラウドサービスです。視覚、音声、知識などのサービスが提供されますが、本チュートリアルでは、視覚認識の中の、FaceAPIを使用します。このAPIを使用することで、顔の検出や、顔を使った認証を行うことができます。
利用にあたっては、Azureサービスの購読が必要になります。私が使用したときには、1か月の無料お試し期間があり、さらに、登録後も、一定の料金までは無料で使用することができました。実際の使用においては、料金の発生の有無を確認し、発生する場合には、どの程度発生するかを確認し利用するとよいでしょう。
FaceAPIを使用する際には、リソースを追加する必要がありますが、FaceAPIは、「AI+Cognitive Services」の中にあります。私が設定をした際には、FaceAPIを提供しているリージョンを選んで追加しました。おそらく、サービスが提供されていないリージョンでは、利用できないと思われます(試した方はどのようになるか、教えてください)。追加後のAzureダッシュボードでは以下のようにリソースが利用可能になっていることが分かります。ここでは、faceapi01という名前でリソースを追加しています。
図 リソース追加
追加後は、利用に必要なサブスクリプションキーと、Endpointを取得しておく必要があります。キーは、メニューの「Keys」から、Endpointは、「Overview」の「基本」の項目に記載されています。私が使用した米国中西部のEndpointは、以下のようになっています。
https://westcentralus.api.cognitive.microsoft.com/face/v1.0
では、キーとEndpointが取得できたものとして、以降はプログラミングに入りたいと思います。
■ FaceAPIを利用した顔認証の仕方
このチュートリアルのゴールは、FaceAPIを利用した顔認証なのですが、FaceAPIを利用して顔認証をするためには、1)PersonGroupの作成、2)PersonGroupに属するPersonの作成(画像登録)、3)PersonGroupのトレーニングを行う必要があります。それぞれに使用するAPIは、PersonGroupの作成とトレーニングは、PersonGroupのAPIに、Personの作成は、PersonのAPIに分類されています。これらを含め、FaceAPIには、4グループのAPIが定義されています。
- Face
- Face List
- Person
- Person Group
■ MSが提供するライブラリ(FaceServiceClient.cs)
それでは、実装の説明に入りたいと思いますが、このチュートリアルでは、マイクロソフト社が提供する顔認識用のライブラリを利用することにします。
このライブラリには、FaceAPIで提供されているAPIがメソッドとして提供されており、FaceAPIが指定する引数などをコーディングしなくても、APIを利用することができます。Visual Studio2013で利用する場合には、NuGetから必要なパッケージをインストールします。では、実際にライブラリをインストールしてみます。
まず、C#のフォームアプリケーションを作成します。ここでは、AzureFace01という名前のプロジェクトを作成しています。
図 プロジェクト作成
次に、プロジェクトタブのNuGetパッケージ管理を選択します。
図 NuGet管理
開いた画面で、必要なライブラリを選択するのですが、ここでは、検索画面に、ProjectOxfordと入力して、Microsoft Cognitive Services Face API Client Libraryを選択し、インストールします。依存関係のある他のライブラリも同時にインストールされます。また、コードの方には、usingディレクティブを二種類追加しておきます。
using Microsoft.ProjectOxford.Face; using Microsoft.ProjectOxford.Face.Contract;
では、以降でプログラムの作成をしたいと思います。
■ 実装
画面の作成
まず、画面を作成します。画面に配置する必要があるのは、PersonGroupの作成、Personの作成、PersonGroupのトレーニングに必要な要素と、顔認証を行うのに必要な要素となります。また、ここでは、PersonGroup、および、Personが正しく作成されたかを確認するためのクエリ処理も作成しておきます。必要に応じて各処理のグループを示すLabelを追加してもよいでしょう。
- 共通:実行結果表示用TextBox、表示画面消去Button
- PersonGroup作成:PersonGroup名用のTextBox(説明用のLabel)、Button
- Person作成:Person名用のTextBox(説明用のLabel)、Person作成用のButton、画像フォルダPath用のTextBox(説明用のLabel)、画像フォルダ選択用のButton、および、フォルダ選択用のダイアログ(FolderBrowserDialog)
- PersonGroupトレーニング:PersonGroup名用のTextBox(共用)、Button
- 顔認証:PersonGroup名用のTextBox(説明用のLabel)、認証対象画像のPath用のTextBox(説明用のLabel)、Button、画像ファイルPath用のTextBox(説明用のLabel)、ファイル選択用のダイアログ(OpenFileDialog))
- PersonGroup確認:Button
- Person確認:Button
実際に作成したものは以下のようになります。
図 画面作成
では、以降ではコードを追加していきます。
PersonGroupの作成
まず、共通の変数として、FaceAPIを使用するためのキーと、URLを定義しておきます。
namespace AzureFace01 { public partial class Form1 : Form { static string skey = "ここにキーを入れる"; static string url = "https://westcentralus.api.cognitive.microsoft.com/face/v1.0/"; public Form1() { InitializeComponent(); } } }
また、実際の処理の前に、表示画面を消去するボタンの処理を書いておきます。
private void button7_Click(object sender, EventArgs e) { // 表示画面の消去 textBox6.Text = ""; }
次に、PersonGroup作成の箇所ですが、クラウドとの通信を行うため、非同期処理を行います。ここでは、非同期処理を行うメソッドをCreatePGとして、引数として、TextBoxから取得するPersonGroup名を与えることとします。また、簡単のため、PersonGroupIdとPersonGroupに与えることができる名前を同一のものとしています。
private async void button1_Click(object sender, EventArgs e) { // PersonGroupを作成する。TextBoxが空欄の場合にはエラーメッセージを表示する。 if (textBox1.Text.Equals("")) { MessageBox.Show("PersonGroup名を入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // PersonGroupの作成 var task = await CreatePG(textBox1.Text); textBox6.Text = textBox6.Text + "\r\n" + "PersonGroup作成-> " + task.ToString(); } } async Task<string> CreatePG(string personGroupId) { string msg = ""; // 結果の戻り値 // 作成処理 FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); try { await faceServiceClient.CreatePersonGroupAsync(personGroupId, personGroupId); msg = "作成しました。"; } catch (Exception ex) { msg = ex.Message; } return msg; }
同様にして、確認用の処理も記述します。非同期処理としては、GetPGとして、引数として、TextBoxから取得するPersonGroup名を与えることとします。
private async void button8_Click(object sender, EventArgs e) { // PersonGroupを確認する。TextBoxが空欄の場合にはエラーメッセージを表示する。 if (textBox1.Text.Equals("")) { MessageBox.Show("PersonGroup名を入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // PersonGroupの作成 var task = await GetPG(textBox1.Text); textBox6.Text = textBox6.Text + "\r\n" + "PersonGroup確認-> " + task.ToString(); } } async Task<string> GetPG(string personGroupId) { string msg = ""; // 結果の戻り値 // 作成処理 FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); var pg = await faceServiceClient.GetPersonGroupAsync(personGroupId); if (personGroupId.Equals(pg.Name)) msg = personGroupId + "の作成を確認しました"; else msg = personGroupId + "は確認できませんでした"; return msg; }
では、test_groupという名前のPersonGroup名でPersonGroupを作成してみます。
図 PersonGroup作成
作成は確認できましたか。
Personの作成
次にPersonを作成します。Personの作成では、登録したい人の顔写真が入ったフォルダを指定し、それらの画像をクラウドに送ることで、作成をします。ということで、登録用の画像を用意する必要がありますが、ここでは、著作権フリー、肖像権の許諾がとれている顔写真があるウェブサイトの画像を利用します。簡単のため、男性二人分の画像を5枚取得し、3枚でトレーニングを行い、残りの2枚を認証に用いることにします。
それではまず、登録用の画像ファイルが入ったフォルダを選択するボタンの処理を実装します。ここでは以下のようにします。
private void button4_Click(object sender, EventArgs e) { // Person登録用の画像フォルダ選択 // 初期フォルダ folderBrowserDialog1.SelectedPath = @"C:\Users\tinaba\Desktop"; // FolderBrowserDialogを表示 if (folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { // ユーザーが選択したフォルダをテキストボックスに表示 textBox3.Text = folderBrowserDialog1.SelectedPath; } }
次に、Person作成処理を記述します。ここでは先ほどと同様に、Person名が入っていない場合とPathが指定されていない場合にエラーを表示するようにします。非同期処理のメソッドは、CreatePersonとし、引数としては、PersonGroup名、Person名、フォルダPathをとることとします。また、usingディレクティブに、System.IOを追加しておきましょう。
private async void button3_Click(object sender, EventArgs e) { // Person作成 if (textBox3.Text.Equals("")) { MessageBox.Show("画像フォルダを選択してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else if (textBox2.Text.Equals("")) { MessageBox.Show("Person名を入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { var task = await CreatePerson(textBox1.Text, textBox2.Text, textBox3.Text); textBox6.Text = textBox6.Text + "\r\n" + "Person作成-> " + task.ToString(); } } async Task<string> CreatePerson(String personGroupId, String personName, string folderPath) { string msg = ""; // 戻り値のメッセージ FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); CreatePersonResult person = await faceServiceClient.CreatePersonAsync(personGroupId, personName); foreach (string imagePath in Directory.GetFiles(folderPath, "*.jpg")) { using (Stream s = File.OpenRead(imagePath)) { try { await faceServiceClient.AddPersonFaceAsync(personGroupId, person.PersonId, s); msg = "作成に成功しました。"; } catch (Exception ex) { msg = ex.Message; } } } return msg; }
続いて、先ほどと同様に、Personが作成できたかを確認するための処理を追加します。非同期処理名をGetPersonListとして、引数としては、PersonGroupをとることとします。ここでは、ここのPersonの作成状況ではなく、PersonListの取得という形で、作成状況を確認します。
private async void button9_Click(object sender, EventArgs e) { // Personを確認する。TextBoxが空欄の場合にはエラーメッセージを表示する。 if (textBox1.Text.Equals("")) { MessageBox.Show("PersonGroup名を入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // PersonGroupの作成 var task = await GetPersonList(textBox1.Text); textBox6.Text = textBox6.Text + "\r\n" + "Person確認-> " + task.ToString(); } } async Task<string> GetPersonList(string personGroupId) { string msg = ""; // 結果の戻り値 // 作成処理 FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); try { var persons = await faceServiceClient.ListPersonsAsync(personGroupId); foreach (Person p in persons) { msg += p.Name + ": "; } } catch (Exception ex) { msg = ex.Message; } return msg; }
では、二人をそれぞれ、man_a、man_bという名前のPerson名で作成してみます。
図 Person作成
作成は確認できましたか。
トレーニング
次に、登録した画像を認証に使用するためのトレーニングの処理を追加します。基本的には同じ処理となります。非同期処理としては、TrainPGを、引数としては、PersonGroup名を渡すこととします。
private async void button2_Click(object sender, EventArgs e) { // PersonGroupのトレーニング if (textBox1.Text.Equals("")) { MessageBox.Show("PersonGroup名を入力してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { // PersonGroupの作成 var task = await TrainPG(textBox1.Text); textBox6.Text = textBox6.Text + "\r\n" + "PersonGroupトレーニング-> " + task.ToString(); } } async Task<string> TrainPG(string personGroupId) { string msg = ""; // 結果の戻り値 // トレーニング処理 FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); try { await faceServiceClient.TrainPersonGroupAsync(personGroupId); msg = "トレーニングしました。"; } catch (Exception ex) { msg = ex.Message; } return msg; }
実際にトレーニングをしておきます。
図 PersonGroupトレーニング
認証
最後に認証を行ってみます。まず、認証に使用する画像を選択するボタンを作成します。
private void button5_Click(object sender, EventArgs e) { //オープンファイルダイアログを生成する OpenFileDialog op = new OpenFileDialog(); op.InitialDirectory = @"C:\Users\tinaba\Desktop\"; op.Filter = "画像(*.jpg)|*.jpg|すべてのファイル(*.*)|*.*"; //オープンファイルダイアログを表示する DialogResult result = op.ShowDialog(); if (result == DialogResult.OK) { //「開く」ボタンが選択された時の処理 string fileName = op.FileName; //こんな感じで選択されたファイルのパスが取得できる textBox5.Text = fileName; } }
次に認証処理です。認証処理の非同期処理は、IdentifyPersonとして、引数としては、PersonGroupと、画像ファイルのPathを与えることとします。手順はこれまでのものより少し複雑で、まず、FaceServiceClientのDetectAsyncを使って、顔の検出を行い、結果を配列に格納(faces)します。次に、その顔の配列から、faceIdだけを取り出して、faceIdの配列(faceIds)に格納します。
ここまでが下準備で、ここからは、検出された顔が、事前にトレーニングしたPersonGroupにあるかどうかを確認する作業に入るのですが、ここでは、FaceServiceClientのIdentifyAsyncメソッドを使用します。判定結果も配列(results)になっているのですが、そこから候補者(identifyResult)をえて、さらに処理をしていきます。このidentifyResultも複数の候補者を持つことができる配列です。ここでは、最初の候補者だけを取り出して、その候補者のfaceIdから、その人の名前を取得する(FaceServiceClientのGetPersonAsyncメソッド)とともに、候補者の確信度(Confidence)を表示するようにしています。
private async void button6_Click(object sender, EventArgs e) { // 画像の認証 if (textBox5.Text.Equals("")) { MessageBox.Show("ファイルを選択してください。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { var task = await IdentifyPerson(textBox4.Text, textBox5.Text); textBox6.Text = textBox6.Text + "\r\n" + "顔認証-> " + task.ToString(); } } async Task<string> IdentifyPerson(string personGroupId, string filePath) { string msg = ""; // 戻り値のメッセージ FaceServiceClient faceServiceClient = new FaceServiceClient(skey, url); using (Stream s = File.OpenRead(filePath)) { var faces = await faceServiceClient.DetectAsync(s); var faceIds = faces.Select(face => face.FaceId).ToArray(); // 判定 var results = await faceServiceClient.IdentifyAsync(personGroupId, faceIds); foreach (var identifyResult in results) { if (identifyResult.Candidates.Length == 0) { msg = "該当者がいません"; } else { // 候補者あり var candidateId = identifyResult.Candidates[0].PersonId; var person = await faceServiceClient.GetPersonAsync(personGroupId, candidateId); Console.WriteLine("Identified as {0}", person.Name); msg = msg + person.Name + "(" + identifyResult.Candidates[0].Confidence + "), "; } } } return msg; }
実際に認証を行った結果を以下に示します。認証は、man_aの第4、第5の画像、man_bの第4、第5の画像、最後に、私の画像を試してみました。
図 認証結果
確かに顔の認証ができていることが確認できました。
■ 機能拡張
ここまでで、PersonGroupの登録、Personの登録、認証を行うことが出ましたが、Personの削除、変更などは実装しませんでした。実際の運用においては、Personを変更したり削除したりすることもあると思います。是非とも、機能拡張に挑戦してみてください。
(ページの先頭へ)
(2018年1月5日作成)