2017年6月20日火曜日

第13回 Cognitive Servicesの活用

~機械学習(Azure Machine Learning)の入門からビジネス活用へ~

1.     Cognitive Servicesとは


Microsoft Cognitive Servicesは、Microsoft社から提供されている機械学習APIサービスです。一部のサービスを除いて、Azure Machine Learningとは異なりアルゴリズムやデータセットの内容を変更することはできませんが、VisionSpeechLanguageKnowledgeSearchといった人間の認知機能とリンクしたテーマについて、より高度な分析を行わせることができます。

20174月現在で公開されている24種類のAPIの一覧を表にまとめてみました。


 

分野

API

内容

日本語

対応

1

Vision

Computer Vision

タグやキャプションの付与、カテゴリ分類、顔検出、サムネイル画像作成、文字情報や色情報の抽出を行う

1

2

Content Moderator

ユーザー生成コンテンツ(UGC)に含まれる文章・画像・動画に対して、不適切な内容がないか監視し、通知したりフィルタリングしたりする


3

Emotion

画像または動画に映っている被写体の感情を8項目に分けて分析し、それぞれのスコアを算出する

-

4

Face

画像中の顔を検知したり、照合、グループ化、個人識別をしたりする

-

5

Video

動画中の顔を検知・追跡したり、動作検出やブレの軽減をしたり、サムネイル動画を生成したりする

-

 
 

分野

API

内容

日本語

対応 

6

Speech

Bing Speech

音声認識によって会話を文字に起こしたり、文章を合成音声で読み上げたりする


7

Custom Speech

話し方や背景雑音、語彙に合わせて2種類のモデルをカスタマイズすることで音声認識と発話の精度を上げる

-

8

Speaker Recognition

話者認識によって、話し手の検証や識別を行う

×

 

 

分野

API

内容

日本語
対応

9

Language

Bing Spell Check

単語区切りを修正したり、スラング、よくある人名、同音異義語、ブランド名のスペルを修正したりする

×

10

Language Understanding

ユーザーが入力した文章を解析して、コマンドを実行する(Cortanaに命令するイメージ)


11

Linguistic Analysis

自然言語処理のツールによって、文章構造を確認する

×

12

Text Analytics

重要語句や話題を抽出したり、感情を判定したり、言語を判定したりする

2

13

Translator

真相学習を用いた翻訳を行う


14

Web Language Model

文章の分かち書きを行ったり、単語同士の関係性を示す確率値を求めたり、入力された文字列に続くと考えられる単語の候補を示したりする

×

 

 

分野

API

内容

日本語
対応

15

Knowledge

Academic Knowledge

検索クエリの入力補完や評価を行って、Microsoft Academic GraphMAG)から情報を取得する


16

Entity Linking

字面通りでない意味を持ちうる単語が、ある文脈でどのような意味を持つか解析する(「タイムズ」が「ニューヨークタイムズ」を指すか「タイムズスクエア」を指すか)

×

17

Knowledge Exploration

自然言語を入力し、構造化データを対話的に検索する


18

QnA Maker

PDFWordファイルといったデータをもとに会話型で質問を受け付けるBotを作成し、案内しやすくする


19

Recommendations

ECサイトにおいて、顧客の過去の行動データをもとに商品を推薦する

 

 

 

分野

API

内容

日本語
対応

19

Search

Bing Autosuggest

検索キーワードの候補を提示する


20

Bing Image Search

画像を検索する


21

Bing News Search

ニュースを検索する


22

Bing Video Search

動画を検索する


23

Bing Web Search

Webページを検索する


1 印刷文字の読み取り(OCR)のみ対応(手書き文字のOCRおよびキャプションの生成は英語のみ)

2 言語判定および重要語句抽出のみ対応

次章以降では、これらのAPIのうちいくつかについてアプリケーションを作成し、ソースコードや実行結果をもとに特徴や評価方法についてご紹介していきます。

2.     Vision API

 

2.1.     Computer Vision

2.1.1.    方針

Computer Vision APIの応用例として、はじめにOCR(文字認識)を活用するアプリケーションを試してみます。予めPC上に保存されている画像を入力として解析するサンプルアプリケーションについてはMicrosoft社のWebサイトで既に公開されているので、今回は、Webカメラで撮影した画像を入力として読み取りを行うアプリケーションを作成することにします。また、読み取り結果の評価を簡単に行うことができるように、出力方法も改良してみます。
 

2.1.2.    手順

Cognitive ServicesWebサイトを開きます。

Microsoftアカウントを使用してサインインすると、「Request new trials」というボタンが表示されるので、クリックしてサブスクリプションキーを取得する画面を開きます。使用したいAPIキーの左側にあるチェックボックスを有効化して、利用許諾に同意し、Subscribeボタンをクリックします。


サブスクリプションキーの一覧が表示されるので、キーの下にあるCopyリンクをクリックすると、クリップボードにサブスクリプションキーがコピーされるので、ローカルPC上の任意の場所に書き留めておきます。


Visual Studioを起動します。


ファイル>新規作成>「プロジェクト」メニューを開き、Visual C#のコンソールアプリケーションのプロジェクトを作成します。今回は「CogOcr」という名称のプロジェクトを作成しました。


ソリューションエクスプローラーにあるプロジェクトのアイテムを右クリックし、「NuGetパッケージの管理」を開きます。


 
「参照」をクリックしてタブを切り替えた後、検索ボックスに「OpenCvSharp」と入力します。そして、検索結果に表示された「OpenCvSharp-AnyCPU」を選択した状態で右側パネルの「インストール」ボタンをクリックします。


 
変更の確認ウィンドウが表示された場合は「OK」ボタンをクリックします。

 
ウィンドウ下側に出力パネルが表示され、「'OpenCvSharp-AnyCPU <バージョン>' <プロジェクト名> に正常にインストールされました」と表示されればインストール成功です。今回は、バージョン2.4.10.20170306をインストールしました。


続いて検索ボックスに「Vision」と入力します。そして、検索結果に表示された「Microsoft.ProjectOxford.Vision」を選択した状態で右側パネルの「インストール」ボタンをクリックします。


 
変更の確認ウィンドウが表示された場合は「OK」ボタンをクリックします。

 
ライセンスへの同意ウィンドウが表示された場合は「同意する」ボタンをクリックします。


ウィンドウ下側に出力パネルが表示され、「' Microsoft.ProjectOxford.Vision <バージョン>' <プロジェクト名> に正常にインストールされました」と表示されればインストール成功です。今回は、バージョン1.0.393をインストールしました。


以下のクラスを新規作成します。

クラス名

内容

ClassFileIo

ローカルでのファイル操作に関する処理を

まとめたクラス

ClassOpencv

OpenCVを利用した処理をまとめたクラス

ClassComputerVisionApi

Computer Vision APIを利用した処理をまとめたクラス
 


Program.csを開き、以下のソースコードを入力します。

using System;

 

namespace CogOcr

{

    class Program

    {

        static void Main(string[] args)

        {

            // 画像保存先フォルダが存在するか検査します。

            if (!ClassFileIo.FolderCheck())

                return; // 終了

 

            // 画像保存先ファイルパスを取得します。

            var imagePath = ClassFileIo.GetImagePath();

 

            // 画像をWebカメラから取得します。

            ClassOpencv.getBitmapFromCamera(imagePath);

            Console.WriteLine("image: " + imagePath); // 画像ファイルのパスを出力します。

 

            // 画像ファイルが存在するか検査します。

            if (!ClassFileIo.FileCheck(imagePath))

                return; // 終了

 

            // Cognitive ServicesAPIを呼び出します。

            CallApi(imagePath);

 

            // API呼び出し結果を表示した後、キーを2回押すまでコンソールを開いたままにしておきます。

            Console.ReadKey();

            Console.ReadKey();

        }

 

        static async void CallApi(string imagePath)

        {

            var results = await ClassComputerVisionApi.Main(imagePath);

 

            int i = 0;

            foreach (var result in results)

            {

                // 結果をコンソールウィンドウに出力します。

                ClassComputerVisionApi.printOcrResultsToStdout(

                    ClassComputerVisionApi.OcrResultsToWordList(result)

                    );

                // 結果を画像ファイルに出力します。

                ClassComputerVisionApi.PrintOcrResultsToPng(

                    imagePath,

                    ClassComputerVisionApi.OcrResultsToWordList(result),

                    i

                    );

                Console.WriteLine("----------");

 

                i++;

            }

        }

    }

}

ClassFileIo.csを開き、以下のソースコードを入力します。

using System;

using System.IO;

 

namespace CogOcr

{

    class ClassFileIo

    {

        /// <summary>

        /// 画像ファイルの保存先フォルダパスを指定します。

        /// </summary>

        public static string IMG_DIR = @"C:\work\";

 

        /// <summary>

        /// 画像ファイルの保存先ファイル名称を指定します。

        /// </summary>

        private static string IMG_NAME = @"'frame'yyyyMMddHHmmssfff'.png'";

 

        /// <summary>

        /// ファイルが存在するか検査します。

        /// </summary>

        /// <param name="filePath">検査対象のファイルパス</param>

        /// <returns>ファイルが存在する場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FileCheck(string filePath)

        {

            try

            {

                return File.Exists(filePath);

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 保存先フォルダが存在するか検査し、存在しない場合は作成します。

        /// </summary>

        /// <returns>フォルダが存在するか作成できた場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FolderCheck()

        {

            try

            {

                if (!Directory.Exists(Path.GetDirectoryName(IMG_DIR)))

                    Directory.CreateDirectory(Path.GetDirectoryName(IMG_DIR));

 

                return Directory.Exists(Path.GetDirectoryName(IMG_DIR));

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 現在の日付を基に、画像の保存先ファイルパスを生成します。

        /// </summary>

        /// <returns>ファイルパス文字列</returns>

        public static string GetImagePath()

        {

            DateTime dt = DateTime.Now;

            return IMG_DIR + dt.ToString(IMG_NAME);

        }

    }

}

ClassOpencv.csを開き、以下のソースコードを入力します。


using OpenCvSharp;

 

namespace CogOcr

{

    class ClassOpencv

    {

        /// <summary>

        /// Webカメラから画像を取得します。

        /// </summary>

        /// <returns>画像ファイルパス</returns>

        public static string getBitmapFromCamera(string imagePath)

        {

            // CreateCameraCaptureの引数はカメラのインデックス番号です。

            using (var capture = Cv.CreateCameraCapture(0))

            {

                IplImage frame = new IplImage();

 

                // 640px, 高さ480pxのウィンドウ「Capture」を作ります。

                double w = 640, h = 480;

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameWidth, w);

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameHeight, h);

 

                // 何かキーを押すまでは、Webカメラの画像を表示し続けます。

                while (Cv.WaitKey(1) == -1)

                {

                    // カメラからフレームを取得します。

                    frame = Cv.QueryFrame(capture);

 

                    // ウィンドウ「Capture」に、Webカメラのプレビューを表示します。

                    Cv.ShowImage("Capture", frame);

                }

 

                // 一度PNGファイルへ保存します。

                frame.SaveImage(imagePath);

 

                //  使い終わったWindowCapture」を破棄します。

                Cv.DestroyWindow("Capture");

 

                // 画像ファイルを保存したファイルパスを返します。

                return imagePath;

            }

        }

    }

}

ClassComputerVisionApi.csを開き、以下のソースコードを入力します。

using Microsoft.ProjectOxford.Vision;

using Microsoft.ProjectOxford.Vision.Contract;

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Imaging;

using System.IO;

using System.Threading.Tasks;

 

namespace CogOcr

{

    class ClassComputerVisionApi

    {

        /// <summary>

        /// Cognitive ServicesComputer Vision API用のサブスクリプションキー

        /// </summary>

        public static string API_KEY = "********************************";

 

        /// <summary>

        /// 結果を画像に書き出すときの描画色を配列で指定します。

        /// </summary>

        public static System.Drawing.Color[] colors = new System.Drawing.Color[] {

            System.Drawing.Color.Red,

            System.Drawing.Color.OrangeRed,

            System.Drawing.Color.Orange,

            System.Drawing.Color.Yellow,

            System.Drawing.Color.YellowGreen,

            System.Drawing.Color.Green

        };

 

        /// <summary>

        /// ComputerVisionAPIに画像を渡し、読み取り結果を取得します。

        /// </summary>

        /// <param name="imageFilePath">画像ファイルが保存されているパス</param>

        /// <param name="language">言語</param>

        /// <returns>読み取り結果</returns>

        static async Task<OcrResults> UploadAndRecognizeImage(string imageFilePath, string language)

        {

            VisionServiceClient VisionServiceClient = new VisionServiceClient(ClassComputerVisionApi.API_KEY);

 

            using (Stream imageFileStream = File.OpenRead(imageFilePath))

            {

                // Upload an image and perform OCR

                OcrResults ocrResult = await VisionServiceClient.RecognizeTextAsync(imageFileStream, language);

                return ocrResult;

            }

        }

 

        /// <summary>

        /// このクラスの主処理を行います。

        /// </summary>

        /// <param name="imagePath">画像ファイルが保存されているパス</param>

        /// <returns></returns>

        public static async Task<List<OcrResults>> Main(string imagePath)

        {

            Console.WriteLine("Executing OCR...");

 

            Uri imageUri = new Uri(imagePath);

 

            // 検出対象とする言語を切り替えて、複数回API呼び出しを行います。

            List<OcrResults> results = new List<OcrResults>();

            string[] languageCodes = { "unk", "ja", "en" }; // unk: unknown

            foreach (var languageCode in languageCodes)

            {

                // API呼び出しを行い、結果をリストに格納します。

                var ocrResult = new OcrResults();

                ocrResult = await UploadAndRecognizeImage(imageUri.LocalPath, languageCode);

                results.Add(ocrResult);

            }

 

            Console.WriteLine("OCR complete");

            return results;

        }

 

        /// <summary>

        /// OcrResultsWordのリストへ変換します。

        /// </summary>

        /// <param name="results"></param>

        public static List<Word> OcrResultsToWordList(OcrResults results)

        {

            List<Word> result = new List<Word>();

 

            // OcrResultsの中からwordを抽出して、リストに格納します。

            if (results != null && results.Regions != null)

            {

                foreach (var item in results.Regions)

                {

                    foreach (var line in item.Lines)

                    {

                        foreach (var word in line.Words)

                        {

                            result.Add(word);

                        }

                    }

                }

            }

 

            return result;

        }

 

        /// <summary>

        /// 読み取り結果を画像ファイルに重ね描きします。

        /// </summary>

        /// <param name="imagePath">画像ファイルのパス</param>

        /// <param name="results">OCR処理結果</param>

        /// <param name="i">複数言語を読み取り対象に指定してそれぞれAPIを呼び出しているため、それらを区別するためのインデックス番号</param>

        public static void PrintOcrResultsToPng(string imagePath, List<Word> results, int i)

        {

            // 枠と文字列を描画するときのペン・ブラシの描画色を、(読み取り対象言語の)インデックス番号を基に設定します。

            System.Drawing.Color color = colors[i % colors.Length];

 

            try

            {

                // PNGファイルを読み込み、Bitmapオブジェクトを生成します。

                using (Bitmap bitmap = (Bitmap)Image.FromFile(imagePath))

                {

                    // 描画対象となるGraphicsオブジェクトを作成します。

                    using (Graphics graphic = Graphics.FromImage(bitmap))

                    {

                        // 読み取り結果ごとに

                        // 結果の位置を示す四角形の枠と、読み取られた文字列を半透明で描画します。

                        foreach (var word in results)

                        {

                            // 半透明のペンを生成します。

                            using (Pen pen = new Pen(System.Drawing.Color.FromArgb(128, color), 1))

                            {

                                // 枠を描画します。

                                graphic.DrawRectangle(

                                    pen,

                                    new System.Drawing.Rectangle(

                                        word.Rectangle.Left, word.Rectangle.Top,    // x座標, y座標

                                        word.Rectangle.Width, word.Rectangle.Height // , 高さ

                                        )

                                    );

 

                                // 文字列を描画します。

                                Brush brush = new System.Drawing.SolidBrush(System.Drawing.Color.FromArgb(128, color));

                                Font font = new Font("MSゴシック", 8);

                                graphic.DrawString(word.Text,               // 読み取り結果文字列

                                    font, brush,                            // 上で設定したフォントとブラシ

                                    word.Rectangle.Left, word.Rectangle.Top // x座標, y座標

                                    );

                            }

                        }

                    }

 

                    // 重ね描きしたBitmapオブジェクトをPNGファイルへ保存します。

                    bitmap.Save(imagePath.Replace(".png", "." + i.ToString() + ".png"), ImageFormat.Png);

                }

            }

            catch (Exception)

            {

                return;

            }

        }

 

        /// <summary>

        /// 読み取り結果を標準出力へ出力します。

        /// </summary>

        /// <param name="results">OCR処理結果</param>

        public static void printOcrResultsToStdout(List<Word> results)

        {

            // 読み取られた文字列ごとに処理を行います。

            // 日本語の場合は1文字ごとに検出されるようです。

            foreach (var word in results)

            {

                // 標準出力に以下の5項目を書き出します。

                //  ・読み取られた文字列

                //  ・読み取られた文字列のx座標

                //  ・読み取られた文字列のy座標

                //  ・読み取られた文字列の幅

                //  ・読み取られた文字列の高さ

                Console.WriteLine("{0}\t\t{1},\t{2},\t{3},\t{4}",

                    word.Text,

                    word.Rectangle.Left, word.Rectangle.Top, word.Rectangle.Width, word.Rectangle.Height);

            }

        }

    }

}
 

2.1.3.    読み取り処理


精度を評価するために、今回は「吾輩は猫である」の冒頭部分を対象とした文字認識を試してみます。この文章は著作権が消滅した作品をインターネット上で公開している青空文庫から取得し、ふりがなを除去したものを使用しています。

例文

 

吾輩は猫である。名前はまだ無い。

 どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。この書生というのは時々我々を捕えて煮て食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう。この時妙なものだと思った感じが今でも残っている。第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶だ。その後猫にもだいぶ逢ったがこんな片輪には一度も出会わした事がない。のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙を吹く。どうも咽せぽくて実に弱った。これが人間の飲む煙草というものである事はようやくこの頃知った。

 この書生の掌の裏でしばらくはよい心持に坐っておったが、しばらくすると非常な速力で運転し始めた。書生が動くのか自分だけが動くのか分らないが無暗に眼が廻る。胸が悪くなる。到底助からないと思っていると、どさりと音がして眼から火が出た。それまでは記憶しているがあとは何の事やらいくら考え出そうとしても分らない。

2.1.4.    結果

先ほど示した文章を撮影した画像、読み取り結果を重ねて描画した画像、読み取り結果と元の文章を比較した表を以下にまとめます。


撮影された画像




読み取り結果を重ね描きした画像




2.1.5.    評価


2.1.5.1. 正答率による評価

上の表で示した2つの文章(正答と読み取り結果)1文字ずつ比較したとき、同じ位置に同じ文字が存在したのは595件のうち104件だったため、正答率は約17.4%でした。
 

2.1.5.2. 類似度による評価

次に、正答と読み取り結果の2つの文章について、句点で区切った上で文字列同士類似度を測る指標の1つであるレーベンシュタイン距離を求めた結果を以下に示します。レーベンシュタイン距離は、2つの文章が完全に一致しているときは0となり、類似度が低くなるにつれて大きい値をとる指標値です。1行に含まれる文字数がそれぞれ異なっているため、標準化のためにレーベンシュタイン距離を文字数で割った値を比較します。ちなみに、句点で区切らずに文章全体のレーベンシュタイン距離を求めたところ、80になりました。


文番号

距離

距離/
文字数

読み取り結果

1

1

0.14

吾斐は猫である

2

1

0.14

名前はまた無い

3

3

0.19

どこで生れたかとんと見当かっかめ

4

4

0.11

何でも消哘いしめしめした所でニャーニャー泣いていた事だけは記憶している

5

1

0.05

吾はここで始めて人間というものを見た

6

2

0.06

しかもあとで間くとそれは書生という人間中で一番獰悪な種旗であったそうだ

7

2

0.07

この書生というのは時々我々を捕えて有て食うという話てある

8

1

0.03

しかしその当時は何という考もなかったから別段諟しいとも思わなかった

9

8

0.19

ただ彼の掌に載せられてス-ーと持ち上けられた時何たかフリフリした感しかあったはかりである

10

6

0.16

学の上で少し落らついて書生のを見たのかいわゆる人間というものの三であろう

11

1

0.04

この時妙なものだと思った感しが今でも残っている

12

7

0.23

第一毛をもって諟飾され公当.はすの朝かつるつるしてまるで薬缶だ

13

9

0.28

その径猶にもたいふ進ったかこんな片には一度も出会わしたかない

14

4

0.20

のみならすの耳中があまりに究起している

15

3

0.14

そうしてその穴の中から時々ぶうぶうと厦を吹く

16

5

0.38

どうも咽すぼま第に弱った

17

3

0.10

これが人間のむ阜というものである事はようやくこの頃知った

18

6

0.13

この書生の掌の要でしはらくはよい心持に坐っておったが、しはらくすると非常な達力で道続し始めた

19

7

0.25

書生か動くのか自分たけか動くのか分らないか無喧に眼かる

20

1

0.17

職が悪くなる

21

2

0.07

到底助からないと思っていると、どさりと音莎して眼から大が出た

22

3

0.08

それまでは記愴しているかあとは何の事やらい(ら考え出そうとしても分らない
 

2.1.5.3. Cognitive Servicesを使用しない手順との比較

続いて、Cognitive Servicesを使用せずに文字認識を行った上で、先ほどのCognitive Servicesを使用した読み取り結果と比較することで、認識精度を評価してみようと思います。
比較対象としてCognitive Servicesを使用せずに文字認識を行うために、今回は「Tesseract.NET」を使ってみます。Tesseract.NETは、オープンソースで開発されている「Tesseract」というOCRエンジンを.NETアプリケーションから呼び出して使用するための、「ラッパー」と呼ばれる種類のライブラリです。開発にVisual Studioを使用している場合、NuGetパッケージマネージャーを使用して簡単に導入することができます。

同様の機能を持つライブラリには、他にMicrosoft社から提供されている「OCR Library for Windows Runtime」もあります。このライブラリは、UWPアプリケーション開発にしか使用できないというデメリットはありますが、商用アプリケーションに組み込んで使用しても利用料がかかりません。
2.1.5.3.1.  手順
Tesseract.NETを使用して文字読み取りアプリケーションを作成する手順を以下に示します。


まずは、プロジェクトを作成します。Cognitive Servicesを使用したプロジェクトと揃える意味で、Visual C#コンソールアプリケーションを選択します。今回は「LocalOcr」という名称のプロジェクトを作成しました。

プロジェクトが作成されたらNuGetパッケージマネージャーを開いて、「OpenCvSharp-AnyCPU」をインストールします。手順はCogOcrプロジェクトの時と同様です。

続いて、「Tesseract」をインストールします。今回は、バージョン3.0.2をインストールしました。類似した名前のパッケージがあるため、注意が必要です。

Tesseractで文字読み取りを行わせたい言語に対応した「言語ファイル」を、本家TesseractのプロジェクトWebサイトからダウンロードして、ローカルに保存しておきます。

今回は日本語と英語でそれぞれ読み取らせるため、「jpn.traineddata」と「eng.traineddata」が必要になります。
これらの名称のリンクをクリックして開いたページの「Download」ボタンをクリックします。
これらのファイルの保存先は任意ですが、後ほどソースコード中で場所を指定するため、どこに保存したか控えておきます。今回は、プロジェクトフォルダの中の、LocalOcr.csprojと同じ階層に「tessdata」フォルダを作成し、その中に2つの言語ファイルを配置しました。


次に、「System.Drawing」への参照を追加します。ソリューションエクスプローラーにある「参照」の右クリックメニューを開いて、「参照の追加」をクリックします。
参照マネージャー左上の「アセンブリ」をクリックした後、右上の「アセンブリの検索」欄に「System.Drawing」と入力します。その後、中央部に表示される「System.Drawing」のチェックボックスをクリックして有効化した後、「OK」をクリックします。
次に、Class3つ新規作成し、以下のソースコードを記述します。作成するクラスと処理内容は以下の表の通りです。また、プロジェクト直下にもしもApp.configファイルが存在しない場合は、新規作成しておきます。
 

クラス名

内容

ClassFileIo.cs

ファイル操作に関する関数をまとめたクラス

ClassOpenCv.cs

Open CVを使用する関数をまとめたクラス

ClassTesseract.cs

Tesseractを使用する関数をまとめたクラス


Program.csを開いて、以下のコードを記述します。


using System;

 

namespace LocalOcr

{

    class Program

    {

        static void Main(string[] args)

        {

            // 画像保存先フォルダが存在するか検査します。

            if (!ClassFileIo.FolderCheck())

                return; // 終了

 

            // 画像保存先ファイルパスを取得します。

            var imagePath = ClassFileIo.GetImagePath();

 

            // 画像をWebカメラから取得します。

            ClassOpencv.GetBitmapFromCamera(imagePath);

            Console.WriteLine("image: " + imagePath); // 画像ファイルのパスを出力します。

 

            // 画像ファイルが存在するか検査します。

            if (!ClassFileIo.FileCheck(imagePath))

                return; // 終了

 

            // 文字読み取り処理を行います。

            CallApi(imagePath);

 

            // API呼び出し結果を表示した後、キーを2回押すまでコンソールを開いたままにしておきます。

            Console.ReadKey();

            Console.ReadKey();

        }

 

        private static void CallApi(string imagePath)

        {

            var results = ClassTesseract.Main(imagePath);

 

            int i = 0;

            foreach (var ocrResult in results)

            {

                // 結果をコンソールウィンドウに出力します。

                ClassTesseract.PrintOcrResultsToStdout(

                    ocrResult

                    );

                // 結果を画像ファイルに出力します。

                ClassTesseract.PrintOcrResultsToPng(

                    imagePath,

                    ocrResult,

                    i

                    );

                Console.WriteLine("----------");

 

                i++;

            }

        }

    }

}

 

ClassFileIo.csを開いて、以下のコードを記述します。


using System;

using System.IO;

 

namespace LocalOcr

{

    class ClassFileIo

    {

        /// <summary>

        /// 画像ファイルの保存先フォルダパスを指定します。

        /// </summary>

        public static string IMG_DIR = @"C:\work\";

 

        /// <summary>

        /// 画像ファイルの保存先ファイル名称を指定します。

        /// </summary>

        private static string IMG_NAME = @"'frame'yyyyMMddHHmmssfff'.png'";

 

        /// <summary>

        /// ファイルが存在するか検査します。

        /// </summary>

        /// <param name="filePath">検査対象のファイルパス</param>

        /// <returns>ファイルが存在する場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FileCheck(string filePath)

        {

            try

            {

                return File.Exists(filePath);

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 保存先フォルダが存在するか検査し、存在しない場合は作成します。

        /// </summary>

        /// <returns>フォルダが存在するか作成できた場合はTrueを、それ以外の場合はFalseを返します</returns>

        public static bool FolderCheck()

        {

            try

            {

                if (!Directory.Exists(Path.GetDirectoryName(IMG_DIR)))

                    Directory.CreateDirectory(Path.GetDirectoryName(IMG_DIR));

 

                return Directory.Exists(Path.GetDirectoryName(IMG_DIR));

            }

            catch (Exception)

            {

                return false;

            }

        }

 

        /// <summary>

        /// 現在の日付を基に、画像の保存先ファイルパスを生成します。

        /// </summary>

        /// <returns>ファイルパス文字列</returns>

        public static string GetImagePath()

        {

            DateTime dt = DateTime.Now;

            return IMG_DIR + dt.ToString(IMG_NAME);

        }

    }

}

 

ClassOpenCv.csを開いて、以下のコードを記述します。


using OpenCvSharp;

 

namespace LocalOcr

{

    class ClassOpencv

    {

        /// <summary>

        /// Webカメラから画像を取得します。

        /// </summary>

        /// <returns>画像ファイルパス</returns>

        public static string GetBitmapFromCamera(string imagePath)

        {

            // CreateCameraCaptureの引数はカメラのインデックス番号です。

            using (var capture = Cv.CreateCameraCapture(0))

            {

                IplImage frame = new IplImage();

 

                // 640px, 高さ480pxのウィンドウ「Capture」を作ります。

                double w = 640, h = 480;

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameWidth, w);

                Cv.SetCaptureProperty(capture, CaptureProperty.FrameHeight, h);

 

                // 何かキーを押すまでは、Webカメラの画像を表示し続けます。

                while (Cv.WaitKey(1) == -1)

                {

                    // カメラからフレームを取得します。

                    frame = Cv.QueryFrame(capture);

 

                    // ウィンドウ「Capture」に、Webカメラのプレビューを表示します。

                    Cv.ShowImage("Capture", frame);

                }

 

                // 一度PNGファイルへ保存します。

                frame.SaveImage(imagePath);

 

                // 使い終わったWindowCapture」を破棄します。

                Cv.DestroyWindow("Capture");

 

                // 画像ファイルを保存したファイルパスを返します。

                return imagePath;

            }

        }

    }

}

 

ClassTesseract.csを開いて、以下のコードを記述します。


// NuGetパッケージマネージャーで「Tesseract(作者:Charles Weld)をインストールしておく

// 認識させたい言語に対応する言語ファイルをダウンロード( https://github.com/tesseract-ocr/tessdata )し、exe出力先のフォルダに入れておく

 

// System.Drawing への参照が追加されていることを確認する

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Imaging;

 

namespace LocalOcr

{

    class ClassTesseract

    {

        public static string languageFilePath = @"C:\Users\USERNAME\Documents\Visual Studio 2015\Projects\LocalOcr\LocalOcr\tessdata"; // tessdataフォルダへのパスを設定します。

 

        /// <summary>

        /// 結果を画像に書き出すときの描画色を配列で指定します。

        /// </summary>

        public static Color[] colors = new Color[] {

            Color.Red,

            Color.OrangeRed,

            Color.Orange,

            Color.Yellow,

            Color.YellowGreen,

            Color.Green

        };

 

        /// <summary>

        /// このクラスの主処理を行います。

        /// </summary>

        /// <param name="imagePath">画像ファイルが保存されているパス</param>

        /// <returns>文字<文書<読み取り対象言語の順に結果をまとめたリスト</returns>

        public static List<List<Dictionary<string, Tesseract.Rect>>> Main(string imagePath)

        {

            Console.WriteLine("Executing OCR...");

 

            Uri imageUri = new Uri(imagePath);

 

            // 検出対象とする言語を切り替えて、複数回API呼び出しを行います。

            string[] languages = { "jpn", "eng" };

 

            var results = new List<List<Dictionary<string, Tesseract.Rect>>>();

            foreach (var language in languages)

            {

                // 対象言語ごとにOCR処理を呼び出し、結果をリストに格納します。

 

                List<Dictionary<string, Tesseract.Rect>> ocrResult = RecognizeImage(imageUri.LocalPath, language);

                results.Add(ocrResult);

            }

 

            Console.WriteLine("OCR complete");

            return results;

        }

 

        /// <summary>

        /// 読み取り結果を標準出力へ出力します。

        /// </summary>

        /// <param name="results">OCR処理結果</param>

        public static void PrintOcrResultsToStdout(List<Dictionary<string, Tesseract.Rect>> results)

        {

            // 読み取り結果一文字ごとに処理を行います。

            foreach (var dict in results)

            {

                foreach (KeyValuePair<string, Tesseract.Rect> kvpair in dict)

                {

                    // 標準出力に以下の5項目を書き出します。

                    //  ・読み取られた文字

                    //  ・読み取られた文字のx座標

                    //  ・読み取られた文字のy座標

                    //  ・読み取られた文字の幅

                    //  ・読み取られた文字の高さ

                    Console.WriteLine(

                        string.Format("{0}\t\t{1},\t{2},\t{3},\t{4}",

                        kvpair.Key, kvpair.Value.X1, kvpair.Value.Y1, kvpair.Value.Width, kvpair.Value.Height)

                    );

                }

            }

        }

 

        /// <summary>

        /// 読み取り結果を画像ファイルに重ね描きします。

        /// </summary>

        /// <param name="imagePath">画像ファイルのパス</param>

        /// <param name="results">OCR処理結果</param>

        /// <param name="i">複数言語を読み取り対象に指定してそれぞれAPIを呼び出しているため、それらを区別するためのインデックス番号</param>

        public static void PrintOcrResultsToPng(string imagePath, List<Dictionary<string, Tesseract.Rect>> results, int i)

        {

            // 枠と文字列を描画するときのペン・ブラシの描画色を、(読み取り対象言語の)インデックス番号を基に設定します。

            Color color = colors[i % colors.Length];

 

            try

            {

                // PNGファイルを読み込み、Bitmapオブジェクトを生成します。

                using (Bitmap bitmap = (Bitmap)Image.FromFile(imagePath))

                {

                    // 描画対象となるGraphicsオブジェクトを作成します。

                    using (Graphics graphic = Graphics.FromImage(bitmap))

                    {

                        // 半透明のペンを生成します。

                        using (Pen pen = new Pen(Color.FromArgb(128, color), 1))

                        {

                            // 読み取り結果一文字ごとに処理を行います。

                            foreach (var dict in results)

                            {

                                foreach (KeyValuePair<string, Tesseract.Rect> kvpair in dict)

                                {                                   

                                    // 枠を描画します。

                                    graphic.DrawRectangle(

                                        pen,

                                        new Rectangle(

                                            kvpair.Value.X1, kvpair.Value.Y1,       // x座標, y座標

                                            kvpair.Value.Width, kvpair.Value.Height // , 高さ

                                            )

                                        );

 

                                    // 文字列を描画します。

                                    Brush brush = new SolidBrush(Color.FromArgb(128, color));

                                    Font font = new Font("MSゴシック", 8);

                                    graphic.DrawString(kvpair.Key,       // 読み取り結果文字列

                                        font, brush,                     // 上で設定したフォントとブラシ

                                        kvpair.Value.X1, kvpair.Value.Y1 // x座標, y座標

                                        );

                                }

                            }

                        }

                    }

 

                    // 重ね描きしたBitmapオブジェクトをPNGファイルへ保存します。

                    bitmap.Save(imagePath.Replace(".png", "." + i.ToString() + ".png"), ImageFormat.Png);

                }

            }

            catch (Exception)

            {

                return;

            }

        }

 

        /// <summary>

        /// Tesseractに画像を渡し、読み取り結果を取得します。

        /// </summary>

        /// <param name="imageFilePath">画像ファイルが保存されているパス</param>

        /// <param name="language">言語</param>

        /// <returns>読み取り結果</returns>

        static List<Dictionary<string, Tesseract.Rect>> RecognizeImage(string imageFilePath, string language)

        {

            // 戻り値を格納するリストを用意します

            List<Dictionary<string, Tesseract.Rect>> results = new List<Dictionary<string, Tesseract.Rect>>();

            // オブジェクトを作成します

            var tesseract = new Tesseract.TesseractEngine

                (

                    languageFilePath,

                    language

                );

 

            // 読み取り精度を向上させるために制約条件を追加します。

            if (language == "eng")

            {

                // 半角英数のみを対象文字種とします。

                tesseract.SetVariable("tesseract_char_whitelist", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");

            }

 

            // 画像ファイルを開き、読み取り処理を実行します。

            using (var img = new Bitmap(imageFilePath))

            {

                // 読み取り処理

                var proc = tesseract.Process(img);

 

                using (var iterator = proc.GetIterator())

                {

                    do

                    {

                        Tesseract.Rect rectangle;

                        if (iterator.TryGetBoundingBox(Tesseract.PageIteratorLevel.Symbol, out rectangle))

                        {

                            string key = iterator.GetText(Tesseract.PageIteratorLevel.Symbol);

                            var item = new Dictionary<string, Tesseract.Rect>()

                                        {

                                            { key, rectangle }

                                        };

                            results.Add(item);

                        }

                    } while (iterator.Next(Tesseract.PageIteratorLevel.Symbol));

                }

                return results;

            }

        }

    }

}

 

2.1.5.3.2.  結果

Cognitive Servicesを使用せずにTesseract.NETを使用して文字読み取りを行った結果を以下の表に示します。また、この結果とCognitive Servicesを使用して文字読み取りを行った結果を比較した図を以下に示します。


読み取り結果 (Cognitive Servicesなし)

)疆闔眞であろ。名前はまた慧い〟“どニで菫れたかどんど見当がつヵ燐〟何でも甫囁旨いじめじめした所いた三けは記蛯していろ〟善輩愴二二で始めて人閘といラものを見~しヵ`もぁどで蘭〈どそれ鬱鱈生どいラ人闇中で-謡狩悪な嚥でぁつたそラた〟二の薯隻といラの輝疇々教々ーを傭え補て實ぅどいラぁろ"5"しかしその当ー寺は何といラ考もなかっ汗かち別佼恐しい~ども愚わなかった~岐の攣に繍瀋【…hてスーと持ち上げられた躊何だかフヮフヮした應じ炉ぁつたばかりぁる・掌の上て少し落ちついて書隼の顧を見だの炉いわゅろ人蘭といラ〟ものの見婁ぁろラ.こ例奪妙なものだど惠~が今ても競っていろ〟房一曼をもって〝湖さーーぺ睾はずの讀がつるつろしてまろ棗その玩幻寅にもだい構ったがこんな佇萱輌には一慶も出会ゎした享がない〟のみならず潅貴の貪中があまりに魏しれ`ろ〟そラして_萱その穴の申かち蒔々ぷラぷラと優を吹〈.どぅも咽せ旛〈マ冥弱った〟二ーーヵ~^鳳の瞰膿といラものでぁろ蔓ばよぅゃくこ媚知った〟】こ媚生の奮の嚢でし躊ら〈繪よいr俯に坐ゥておっだヵ\し癒ら<ず石ど非撃殲勘で『魎し姓めた〟薯隼が動くのか實分だけヵ瑯〈のヵ`分ちないヵに鴎カ齏る.寶諷豪

 

Cognitive Servicesを使用した文字読み取りでは、余分な文字が紛れ込んでしまったものの文章としてそれなりに読むことができるような精度でしたが、Cognitive Servicesを使用しない文字読み取りでは全く意味が通らない文章になってしまいました。

Cognitive Servicesを使用した場合と使用しない場合のそれぞれで、読み取り結果を重ねて描画した画像を以下にまとめます。


読み取り結果を重ね描きした画像(Cognitive Servicesあり)




読み取り結果を重ね描きした画像(Cognitive Servicesなし)


 

2.1.5.3.3.  評価

正答とCognitive Servicesを使用しない読み取り結果を1文字ずつ比較したとき、同じ位置に同じ文字が存在したのは595件のうち14件だったため、正答率は約2.4%でした。Cognitive Servicesを使用した場合の正答率は約17.4%だったので、Cognitive Servicesを使用したほうがかなり高い精度を得られていることがわかります。

ちなみに、Cognitive Servicesを使用したときと同様に、句点で区切らずに文章全体のレーベンシュタイン距離を求めたところ、になりました。Cognitive Servicesを使用したときの距離はだったので、4倍以上大きな値となってしまいました。このことからも、Cognitive Servicesを使用したほうがかなり高い精度を得られていることがわかります。

 

2.1.6   結論



正答率は約17.4%と低かったものの、日本語の文字認識を行うことができました。正答率という指標にしてしまうと低い値に感じられますが、Cognitive Servicesを使用せずに文字読み取りを行った場合と比較すると、かなり高い精度になっています。

Azure Machine Learningと異なりCognitive Servicesの場合は学習アルゴリズムに手を加えることができないため、学習モデルの改善についてはMicrosoft社に期待するしかありませんが、Cognitive Servicesに渡すデータを予め編集してあげることで読み取り精度が向上する可能性はあります。事前処理として考えられる加工の例を以下に示します。


項目

内容

画像拡大

画像中に含まれる文字の大きさを拡大する

ノイズ除去

文字情報以外の情報が画像に含まれないように取り除く

モアレ除去

液晶ディスプレーを撮影したために生じたモアレ(縞模様)を除去する

トリミング

余白を取り除くことで、画像全体に占める文字情報の割合を高める

画像回転

画像を回転させて、文字のベースラインを水平にする

フォント指定

読み取り対象となる画像中の文字のフォントを、Cognitive Servicesで既に学習されていると思われるフォントに統一する

 

また、今回は液晶ディスプレーに映した文章や、印刷物をタブレットPCのカメラで読み取ったものを入力データとしたため、ある程度の精度で認識させることができたものの、手書き文字を入力データとした場合には1文字も読み取ることができませんでした。これは、現状では学習データに英語のみ手書き文字のデータが含まれていることによります。今後Microsoft社から改善されたサービスが公開されることを期待したいと思います。


2.2.     Face, Emotion


2.2.1.    方針

Face API, Emotion APIの応用例として、Webカメラで撮影された写真に感情認識結果を重ねるアプリケーションを試してみます。

2.2.2.    手順


まず、Visual Studioを起動して、Visual C#のコンソールアプリケーションのプロジェクトを新規作成します。今回は、「CogFacePhoto」という名前にしました。

 

以下のクラスを新規作成します。


クラス名

内容

ClassEmotionApi

Emotion APIを利用した処理をまとめたクラス

ClassFaceApi

Face APIを利用した処理をまとめたクラス

ClassFileIo

ローカルでのファイル操作に関する処理を

まとめたクラス

ClassOpencv

OpenCVを利用した処理をまとめたクラス

 

構成ファイル(*.config)から設定値を読み込む処理を実装するために、「System.Configuration」への参照を追加します。ソリューションエクスプローラーにある「参照」の右クリックメニューを開き、「参照の追加」をクリックします。

 
参照マネージャー左上の「アセンブリ」をクリックした後、右上の「アセンブリの検索」欄に「System.Configuration」と入力します。



Syatem.Configuration」の左にあるチェックボックスをクリックして有効化し、「OK」をクリックします。





Program.csを開いて、以下のコードを記述します。


using System;

 

namespace CogFacePhoto

{

    class Program

    {

 

        /// <summary>

        /// 本プログラムのエントリポイントです。

        /// </summary>

        /// <param name="args"></param>

        static void Main(string[] args)

        {

            // 画像保存先フォルダが存在するか検査します。

            if (!ClassFileIo.FolderCheck())

                return; // 終了

 

            // 画像保存先ファイルパスを取得します。

            var imagePath = ClassFileIo.GetImagePath();

            var logPath = imagePath.Replace(".png", ".txt");

 

            // 使用するWebカメラのIDを構成ファイルから読み込みます。

            var cameraId = ClassFileIo.getCameraId();

 

            // 画像をWebカメラから取得します。

            ClassOpencv.GetBitmapFromCamera(imagePath, cameraId);

            ClassFileIo.WriteLine(logPath, "image: " + imagePath);

 

            // 画像ファイルが存在するか検査します。

            if (!ClassFileIo.FileCheck(imagePath))

                return; // 終了

 

            // Cognitive ServicesAPIを呼び出します。

            CallApi(imagePath, logPath);

 

            // API呼び出し結果を表示した後、キーを2回押すまでコンソールを開いたままにしておきます。

            Console.ReadKey();

            Console.ReadKey();

        }

 

        /// <summary>

        /// 顔認識・感情認識処理を行います。

        /// </summary>

        /// <param name="imagePath">画像ファイルの保存先パス</param>

        private static async void CallApi(string imagePath, string logPath)

        {

            // Face APIを使用して、画像中に存在する顔の検出を行います。

            var faces = await ClassFaceApi.Main(imagePath, logPath);

            ClassFaceApi.PrintFaceResultsToLog(logPath, faces);

            ClassFaceApi.printFaceResultsToPng(imagePath, faces);

 

            // Face APIで検出された顔領域に対して、Emotion APIを使用して感情分析を行います。

            var emotions = await ClassEmotionApi.Main(imagePath, logPath);

            ClassEmotionApi.PrintEmotionResultsToLog(logPath, emotions);

            ClassEmotionApi.PrintEmotionResultsToPng(imagePath, emotions);

        }

    }

}


ClassEmotionApi.csを開いて、以下のコードを記述します。
using Microsoft.ProjectOxford.Emotion;
using Microsoft.ProjectOxford.Emotion.Contract;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Threading.Tasks;
 
namespace CogFacePhoto
{
    class ClassEmotionApi
    {
        /// <summary>
        /// Cognitive ServicesEmotion API用のサブスクリプションキー
        /// </summary>
        public static string API_KEY = "********************************";
 
        /// <summary>
        /// 結果を画像に書き出すときの描画色を、解析対象の感情の種類とともに定義します。
        /// </summary>
        public static Dictionary<string, Color> colors = new Dictionary<string, Color>() {
            {"Anger", Color.Red},          // Anger        怒り
            {"Contempt", Color.DarkGray},  // Contempt     軽蔑
            {"Disgust", Color.DarkViolet}, // Disgust      嫌悪感
            {"Fear", Color.Brown},         // Fear         恐怖
            {"Happiness", Color.Yellow},   // Happiness    幸福
            {"Neutral", Color.Green},      // Neutral      中立
            {"Sadness", Color.Black},      // Sadness      悲しみ
            {"Surprise", Color.Orange}     // Surprise     驚き
            };
 
        /// <summary>
        /// このクラスの主処理を行います。
        /// </summary>
        /// <param name="imagePath">画像ファイルが保存されているパス</param>
        /// <returns></returns>
        public static async Task<Emotion[]> Main(string imagePath, string logPath)
        {
            ClassFileIo.WriteLine(logPath, "Executing Emotion Recognization...");
            Uri imageUri = new Uri(imagePath);
            Emotion[] emotions = await UploadAndRecognizeImage(imageUri.LocalPath);
            ClassFileIo.WriteLine(logPath, "Emotion Recognization complete");
            return emotions;
        }
 
        /// <summary>
        /// 感情スコアのキーと数値を文字列化します。
        /// </summary>
        /// <param name="emotion">感情分析結果</param>
        /// <param name="onlyTop">Trueのときは、最も確度の高い感情の名称のみを返します。</param>
        /// <returns></returns>
        static string GetEmotionScoresString(Emotion emotion, bool onlyTop)
        {
            List<KeyValuePair<string, float>> scores = new List<KeyValuePair<string, float>>() {
                new KeyValuePair<string, float>("Anger", emotion.Scores.Anger),         // 怒り
                new KeyValuePair<string, float>("Contempt", emotion.Scores.Contempt),   // 軽蔑
                new KeyValuePair<string, float>("Disgust", emotion.Scores.Disgust),     // 嫌悪感
                new KeyValuePair<string, float>("Fear", emotion.Scores.Fear),           // 恐怖
                new KeyValuePair<string, float>("Happiness", emotion.Scores.Happiness), // 幸福
                new KeyValuePair<string, float>("Neutral", emotion.Scores.Neutral),     // 中立
                new KeyValuePair<string, float>("Sadness", emotion.Scores.Sadness),     // 悲しみ
                new KeyValuePair<string, float>("Surprise", emotion.Scores.Surprise)    // 驚き
            };
 
            // 感情スコア値が大きい順にソートします。
            scores.Sort(
                delegate (KeyValuePair<string, float> p1, KeyValuePair<string, float> p2)
                {
                    return (p2.Value).CompareTo(p1.Value);
                }
                );
 
            // Trueのときは、最も確度の高い感情の名称のみを返します。
            if (onlyTop)
                return scores[0].Key;
 
            // 感情スコア値を整形して文字列として返します。
            StringBuilder sb = new StringBuilder();
            foreach (var score in scores)
            {
                var lab = string.Format("{0}: {1:f3}; ", score.Key, score.Value);
                sb.Append(lab);
            }
            return sb.ToString();
        }
 
        /// <summary>
        /// 表情認識結果を標準出力へ出力します。
        /// </summary>
        /// <param name="filePath">ログファイルパス</param>
        /// <param name="results">顔認識処理結果</param>
        public static void PrintEmotionResultsToLog(string filePath, Emotion[] emotions)
        {
            // 認識された顔ごとに処理を行います。
            foreach (var emotion in emotions)
            {
                // 標準出力に以下の5項目を書き出します。
                //  ・認識された感情
                //  ・認識された文字列のx座標
                //  ・認識された文字列のy座標
                //  ・認識された文字列の幅
                //  ・認識された文字列の高さ
                ClassFileIo.WriteLine(
                    filePath,
                    string.Format("{0}\t\t{1},\t{2},\t{3},\t{4}",
                    GetEmotionScoresString(emotion, false),
                    emotion.FaceRectangle.Left, emotion.FaceRectangle.Top,
                    emotion.FaceRectangle.Width, emotion.FaceRectangle.Height)
                    );
            }
        }
 
        /// <summary>
        /// 顔認識結果を画像ファイルに重ね描きします。
        /// </summary>
        /// <param name="imagePath">画像ファイル</param>
        /// <param name="results">顔認識結果</param>
        public static void PrintEmotionResultsToPng(string imagePath, Emotion[] emotions)
        {
            try
            {
                // PNGファイルを読み込み、Bitmapオブジェクトを生成します。
                using (Bitmap bitmap = (Bitmap)Image.FromFile(imagePath))
                {
                    // 描画対象となるGraphicsオブジェクトを作成します。
                    using (Graphics graphic = Graphics.FromImage(bitmap))
                    {
                        // 顔認識結果ごとに
                        // 結果の位置を示す四角形の枠と、IDを半透明で描画します。
                        int i = 0;
                        foreach (var emotion in emotions)
                        {
                            // 枠と文字列を描画するときのペン・ブラシの描画色を、IDを基に設定します。
                            Color color = colors[GetEmotionScoresString(emotion, true)];
 
                            // 半透明のペンを生成します。
                            using (Pen pen = new Pen(Color.FromArgb(128, color), 1))
                            {
                                // 枠を描画します。
                                graphic.DrawRectangle(
                                    pen,
                                    new Rectangle(
                                        emotion.FaceRectangle.Left, emotion.FaceRectangle.Top,    // x座標, y座標
                                        emotion.FaceRectangle.Width, emotion.FaceRectangle.Height // , 高さ
                                        )
                                    );
 
                                // 文字列を描画します。
                                Brush brush = new SolidBrush(Color.FromArgb(128, color));
                                Font font = new Font("MSゴシック", 8);
                                graphic.DrawString(GetEmotionScoresString(emotion, true),  // Score
                                    font, brush,                                          // 上で設定したフォントとブラシ
                                    emotion.FaceRectangle.Left, emotion.FaceRectangle.Top // x座標, y座標
                                    );
                            }
 
                            i++;
                        }
                    }
 
                    // 重ね描きしたBitmapオブジェクトをPNGファイルへ保存します。
                    bitmap.Save(imagePath.Replace(".png", ".e.png"), ImageFormat.Png);
                }
            }
            catch (Exception)
            {
                return;
            }
        }
 
        /// <summary>
        /// Emotion APIに画像を渡し、顔認識結果を取得します。
        /// </summary>
        /// <param name="imageFilePath">画像ファイルが保存されているパス</param>
        /// <returns>顔認識結果</returns>
        static async Task<Emotion[]> UploadAndRecognizeImage(string imageFilePath)
        {
            var emotionServiceClient = new EmotionServiceClient(API_KEY);
 
            using (Stream imageFileStream = File.OpenRead(imageFilePath))
            {
                // API呼び出し結果に含まれる、顔領域それぞれの感情スコアを配列に格納します。
                Emotion[] emotions = await emotionServiceClient.RecognizeAsync(imageFileStream);
                return emotions;
            }
        }
    }
}

ClassFaceApi.csを開いて、以下のコードを記述します。


using Microsoft.ProjectOxford.Face;

using Microsoft.ProjectOxford.Face.Contract;

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Imaging;

using System.IO;

using System.Threading.Tasks;

 

namespace CogFacePhoto

{

    class ClassFaceApi

    {

        /// <summary>

        /// Cognitive ServicesFace API用のサブスクリプションキー

        /// </summary>

        public static string API_KEY = "********************************";

 

        /// <summary>

        /// 結果を画像に書き出すときの描画色を配列で指定します。

        /// </summary>

        public static Color[] colors = new Color[] {

            Color.Red,

            Color.OrangeRed,

            Color.Orange,

            Color.Yellow,

            Color.YellowGreen,

            Color.Green

        };

 

        /// <summary>

        /// このクラスの主処理を行います。

        /// </summary>

        /// <param name="imagePath">画像ファイルが保存されているパス</param>

        /// <returns></returns>

        public static async Task<Face[]> Main(string imagePath, string logPath)

        {

            ClassFileIo.WriteLine(logPath, "Executing Face Detection...");

            Uri imageUri = new Uri(imagePath);

            List<Face> facelist = new List<Face>();

            Face[] faces = await UploadAndRecognizeImage(imageUri.LocalPath);

 

            foreach (var face in faces)

                facelist.Add(face);

 

            ClassFileIo.WriteLine(logPath, "Face Detection complete");

            return facelist.ToArray();

        }

 

        /// <summary>

        /// 顔認識結果を画像ファイルに重ね描きします。

        /// </summary>

        /// <param name="imagePath">画像ファイル</param>

        /// <param name="faces">顔認識結果</param>

        public static void printFaceResultsToPng(string imagePath, Face[] faces)

        {

            try

            {

                // PNGファイルを読み込み、Bitmapオブジェクトを生成します。

                using (Bitmap bitmap = (Bitmap)Image.FromFile(imagePath))

                {

                    // 描画対象となるGraphicsオブジェクトを作成します。

                    using (Graphics graphic = Graphics.FromImage(bitmap))

                    {

                        // 顔認識結果ごとに

                        // 結果の位置を示す四角形の枠と、IDを半透明で描画します。

                        int i = 0;

                        foreach (var face in faces)

                        {

                            // 枠と文字列を描画するときのペン・ブラシの描画色を、IDを基に設定します。

                            Color color = colors[i % colors.Length];

 

                            // 半透明のペンを生成します。

                            using (Pen pen = new Pen(Color.FromArgb(128, color), 1))

                            {

                                // 枠を描画します。

                                graphic.DrawRectangle(

                                    pen,

                                    new Rectangle(

                                        face.FaceRectangle.Left, face.FaceRectangle.Top,    // x座標, y座標

                                        face.FaceRectangle.Width, face.FaceRectangle.Height // , 高さ

                                        )

                                    );

 

                                // 文字列を描画します。

                                Brush brush = new SolidBrush(Color.FromArgb(128, color));

                                Font font = new Font("MSゴシック", 8);

                                graphic.DrawString(i.ToString(),                    // ID

                                    font, brush,                                    // 上で設定したフォントとブラシ

                                    face.FaceRectangle.Left, face.FaceRectangle.Top // x座標, y座標

                                    );

                            }

 

                            i++;

                        }

                    }

 

                    // 重ね描きしたBitmapオブジェクトをPNGファイルへ保存します。

                    bitmap.Save(imagePath.Replace(".png", ".f.png"), ImageFormat.Png);

                }

            }

            catch (Exception)

            {

                return;

            }

        }

 

        /// <summary>

        /// 顔認識結果を標準出力へ出力します。

        /// </summary>

        /// <param name="results">顔認識処理結果</param>

        public static void PrintFaceResultsToLog(string filePath, Face[] faces)

        {

            // 認識された顔ごとに処理を行います。

            int i = 0;

            foreach (var face in faces)

            {

                // 標準出力に以下の5項目を書き出します。

                //  ・認識されたID

                //  ・認識された文字列のx座標

                //  ・認識された文字列のy座標

                //  ・認識された文字列の幅

                //  ・認識された文字列の高さ

                ClassFileIo.WriteLine(

                filePath,

                    string.Format("{0}\t\t{1},\t{2},\t{3},\t{4}",

                    i.ToString(),

                    face.FaceRectangle.Left, face.FaceRectangle.Top,

                    face.FaceRectangle.Width, face.FaceRectangle.Height)

                    );

 

                i++;

            }

        }

 

        /// <summary>

        /// Face APIに画像を渡し、顔認識結果を取得します。

        /// </summary>

        /// <param name="imageFilePath">画像ファイルが保存されているパス</param>

        /// <returns>顔認識結果</returns>

        static async Task<Face[]> UploadAndRecognizeImage(string imageFilePath)

        {

            var faceServiceClient = new FaceServiceClient(API_KEY);

 

            using (Stream imageFileStream = File.OpenRead(imageFilePath))

            {

                Face[] faces = await faceServiceClient.DetectAsync(imageFileStream, false, true, new FaceAttributeType[] { FaceAttributeType.Gender, FaceAttributeType.Age, FaceAttributeType.Smile, FaceAttributeType.Glasses, FaceAttributeType.HeadPose, FaceAttributeType.FacialHair });

                return faces;

            }

        }

    }

}

次ページへ続く