Bot Framework と Microsoft Azure を使ってあそんでまーす。
今回はタイトル通り、Bot に画像の URL を与えると、コメントを返してくれるのを作ってみました。
作ったのはこんな感じ。画像の URL を与えてください。
ひとまず、クラウディアさんの画像を教えると、こんな感じ。
人だってことはあってますが、サーフボードには載ってないっすね。。。
うちのワンコ画像を与えたら、カメラを向いた犬って応答がありました。正解!!
人の顔が映った画像だと、感情も推測してくれます。
去年、青森に遊びに行ったときっす。メッサ楽しかった!!
人が複数いることもコメントしてくれます。
もう一枚 AKB48 の 画像 URL を与えたら、名前まで教えてくれました。
多分、自分よりも詳しい。。露出が若干高いので、RacyContentが出ています。
AdultContent も判断できますが、目的がそれちゃうのでやめときます。
人が画像を見ればすぐにわかることですが、それに近いことをシステムが読み取って教えてくれるというのが面白いと思います。
しくみ
全部ゴリゴリ実装しているわけではないです。
画像解析の部分は、Microsoft Azure の Cognitive Service の API を利用しています。
利用したのは以下の2つ
Computer Vision は、
簡単にいうと、画像解析してその画像がなにかっていうのを解析結果として返してくれます。
例えば、人とか犬とか。
どんな分類があるかっていうと下の画像みたいです。沢山ありますね。
このカテゴリ以外にもいろんな情報があります。
例えば、露出高めか否かを「Is Racy Content」でTrue/Falseで教えてくれたり。
多分裏側ではMLが動いてるんでしょうね~。
Emotion API は、「顔」が認識できたときに、顔の表情から感情を分析してかえしてくれます。
返してくれる感情は、以下の8種類です。
- anger
- contempt
- disgust
- fear
- happiness
- neutral
- sadness
- surprise
これも裏ではMLが動いてるんでしょうねー。
で、肝心のBOTはというと、Bot Framework のドキュメントに書いてあります。
https://docs.botframework.com/en-us/bot-intelligence/vision/#example-vision-bot
ソースなど
GITにしようかと思ったのですが、Computer Vision API と Emotion API の接続キーをそのままUPしそうなので、今回もコード貼り付けにしたいと思います。
今回の環境は、Visual Studio 2015 + Bot Framework v3 です。
Microsoft Azure のポータルより、Vision API と Emotion API を作成します。
まずは、Vision API から
無料があってうれしい~
続いてEmotion API
こちらも無料があります。
で、続いてVisual Studio を起動して、Bot Application のテンプレート でプロジェクト作成後から。
最初にやったのは、Microsoft.Bot.Builder をv3.1.0に Update しました。
Microsoft.ProjectOxford.Vision をインストール
Microsoft.ProjectOxford.Emotion をインストール
んで、コード
Nugetから追加インストールした分を using に積みます
1 2 3 4 5 6 7 8 9 10 11 12 13 | using System; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using Microsoft.Bot.Connector; // Vision API 用 using Microsoft.ProjectOxford.Vision; using Microsoft.ProjectOxford.Vision.Contract; // Emotion API using Microsoft.ProjectOxford.Emotion; using Microsoft.ProjectOxford.Emotion.Contract; using System.Collections.Generic; |
で、Post
HandleSystemMessageは、テンプレートを内容をそのまま使いますので割愛します。
意外といろんなところで、例外が出てきたのでtryだらけになってしまいました。
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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | public async Task<HttpResponseMessage> Post([FromBody]Activity activity) { if (activity == null || activity.GetActivityType() != ActivityTypes.Message) { //add code to handle errors, or non-messaging activities HandleSystemMessage(activity); } else { ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl)); Activity reply = null; String ReplyText=""; try { // vision の処理用 const string visionApiKey = "<Vision API の Key>; //Vision SDK classes VisionServiceClient visionClient = new VisionServiceClient(visionApiKey); VisualFeature[] visualFeatures = new VisualFeature[] { VisualFeature.Adult, //recognize adult content VisualFeature.Categories, //recognize image features VisualFeature.Description //generate image caption }; AnalysisResult analysisResult = null; //Else, if the user did not upload an image, determine if the message contains a url, and send it to the Vision API try { analysisResult = await visionClient.AnalyzeImageAsync(activity.Text, visualFeatures); } catch (Exception e) { analysisResult = null; //on error, reset analysis result to null } if (analysisResult != null) { string imageCaption = "I think it's " + analysisResult.Description.Captions[0].Text +"."; if (analysisResult.Adult.IsRacyContent == true) { imageCaption += "[RacyContent]"; } if (analysisResult.Adult.IsAdultContent == true) { imageCaption += "[AdultContent]"; } ReplyText += imageCaption; } // Emotion 処理用 const string emotionApiKey = "Emotion API の Key"; //Emotion SDK objects that take care of the hard work EmotionServiceClient emotionServiceClient = new EmotionServiceClient(emotionApiKey); Emotion[] emotionResult = null; try { emotionResult = await emotionServiceClient.RecognizeAsync(activity.Text); } catch (Exception e) { emotionResult = null; } if (emotionResult != null) { string emotiontext; try { Scores emotionScores = emotionResult[0].Scores; //Retrieve list of emotions for first face detected and sort by emotion score (desc) IEnumerable<KeyValuePair<string, float>> emotionList = new Dictionary<string, float>() { { "angry", emotionScores.Anger}, { "contemptuous", emotionScores.Contempt }, { "disgusted", emotionScores.Disgust }, { "frightened", emotionScores.Fear }, { "happy", emotionScores.Happiness}, { "neutral", emotionScores.Neutral}, { "sad", emotionScores.Sadness }, { "surprised", emotionScores.Surprise} } .OrderByDescending(kv => kv.Value) .ThenBy(kv => kv.Key) .ToList(); KeyValuePair<string, float> topEmotion = emotionList.ElementAt(0); string topEmotionKey = topEmotion.Key; float topEmotionScore = topEmotion.Value; emotiontext = "I found a face! I am " + (int)(topEmotionScore * 100) + "% sure the person seems " + topEmotionKey + "."; } catch { emotiontext = ""; } ReplyText += emotiontext; } if (ReplyText.Length != 0) { reply = activity.CreateReply(); reply.Recipient = activity.From; reply.Type = ActivityTypes.Message; reply.Text = ReplyText; reply.Attachments = new System.Collections.Generic.List<Attachment>(); reply.Attachments.Add(new Attachment() { ContentUrl = activity.Text, ContentType = "image/png" }); } else { reply = activity.CreateReply("???"); } } catch (Exception e1) { Console.WriteLine(e1.Message); } await connector.Conversations.ReplyToActivityAsync(reply); } return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted); } |
まとめ
今回の検証では、以下の2点がとても楽しかったです。
- 画像をシステムが解析して応答してくれる楽しさ
- Bot Framework + Cognitive Service で、コードを書くのがあまり得意ではない自分で簡単に作れちゃう手軽さ
あと、Azure にデプロイして、Web Chat から呼び出したときと、エミュレーションで実行するときは、Post で飛んでくるタイミングが違うので若干はまったのですが楽しかったです。
リモートデバッグをずっとしてたなーw
コメント