タイトル通り、「自然文で画像を検索して画像 / 感情解析をする Bot」を作って遊んでみました。
コード書いてる時間よりも、LUIS でチューニングしている時間の方が長かったかもw
まずは成果物
使い方は、「野球の画像を探して」とか、「猫の画像をみつけて」とか、「空飛ぶペンギン」とか、「怒っている人の画像をさがして」とか。
画像を探す系の文章を入力してください。
Speech to Text を人間系でやってるみたいな感覚でw
まぁー、今回は LUIS のお勉強です。
今回の構成はこんな感じです。
Web Chat から受け取った文字列を、LUISを使ってキーワード抽出をします。
- 「野球の画像を探して」からだと、「野球」
- 「猫の画像をみつけて」からだと、「猫」
- 「空飛ぶペンギン」からは、「空飛ぶペンギン」
- 「怒っている人の画像をさがして」からは、「怒っている人」
「空飛ぶペンギン」は、「空飛ぶ」と「ペンギン」
「怒っている人」は、「怒っている」と「人」
に分けるのもありだと思いますが、今回はまとめた感じにしました。
あとは、LUIS で Get したキーワードでBing Search API で画像検索して、
その画像をComputer Vision とEmotion APIで解析しています。
画像解析しているとこは、今回のつくりではおまけ的な感じですが、
LUISのキーワード抽出で感情的な要素を抜き出して、Emotion API のスコアを評価してあげても面白いかもですね。
LUIS
LUISは、Language Understanding Intelligent Service の略です。技術情報は以下をごらんください。
https://azure.microsoft.com/ja-jp/services/cognitive-services/language-understanding-intelligent-service/
今回やったことの雑なまとめ
まずは、Entities でキーワード抽出する属性を定義します。
今回は、ImageName という名前を付けましたが、Bing Search API に渡すための検索キーワードを受け取る属性です。
複数作ることもできますよ。
続いて、Entities にデータを振り分ける intent を作ります。
GetImageName という名前で intent を作り、先ほど作った ImageName を振り分けるパラメータに指定します。
こんな感じで適当に文章を入力して、Intent と パラメータにあたる箇所を選んでいきます。
学習させる例文ができましたら、Train で学習させ、Publish します。
うまく動くか動作テスト
「赤い家の画像」で確認しました。
で、うまく「赤い家」が Get できました。
こんな感じです。
ソース
ひとまず、Bot framework を使って追加したソースをぺたぺた。
色んなAPI KeyがあるからGitにUPしづらいのですが、なんかその辺簡単にクリアできたりしないのかな。。。
MessagesController.cs
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | using System; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; using Microsoft.Bot.Connector; using Newtonsoft.Json; using Microsoft.Cognitive.LUIS; using Microsoft.ProjectOxford.Vision; using Microsoft.ProjectOxford.Vision.Contract; using Microsoft.ProjectOxford.Emotion; using Microsoft.ProjectOxford.Emotion.Contract; using y9demoimgaearch.Model; using System.Collections.Generic; namespace y9demoimgaearch { [BotAuthentication] public class MessagesController : ApiController { /// <summary> /// POST: api/Messages /// Receive a message from a user and reply to it /// </summary> public async Task<HttpResponseMessage> Post([FromBody]Activity activity) { if (activity.Type == ActivityTypes.Message) { ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl)); string msg = ""; string ImgUrl =""; // ★★ 文字数をチェック ★★ if (activity.Text.Length > 0) { // ★★ LUISを実行(検索するImageNameを取得) ★★ String ImgName = await ExecLUIS(activity.Text); if (ImgName.Length == 0) { // LUISで検索キーワードが引っこ抜けないのでそのままの文字で検索 ImgName = activity.Text; } // ★★ 画像検索を実行(画像のURLをGet) ★★ ImgUrl = await ExecBingSearch(ImgName); // 画像のURLを取得できなかった時。 if (ImgUrl.Length > 0) { // ★★ Computer Vision で画像分析(分析結果の文字列を取得) ★★ String VisionMsg = await ExecVisionAnalysis(ImgUrl); // ★★ Emotion APIで感情分析(一番高いスコアの感情を取得) ★★ String EmotionMsg = await ExecEmotionAnalysis(ImgUrl); msg = VisionMsg + EmotionMsg; } else { msg = "画像が見つかりませんでした。"; } } else { // 文字入力してよ!!! msg = "何か入力してください!!"; } // Bot の応答 var reply = activity.CreateReply(); reply.Recipient = activity.From; reply.Type = ActivityTypes.Message; reply.Text = msg; reply.Attachments = new System.Collections.Generic.List<Attachment>(); if(ImgUrl.Length>0) { reply.Attachments.Add(new Attachment() { ContentUrl = ImgUrl, ContentType = "image/png" }); } await connector.Conversations.ReplyToActivityAsync(reply); } else { HandleSystemMessage(activity); } var response = Request.CreateResponse(HttpStatusCode.OK); return response; } private async Task<string> ExecLUIS(string Input) { // Application ID, Application Key をセットして LuisClient を作成 string luisAppId = "[APP ID]"; string luisAppKey = "[APP Key]"; LuisClient luisClient = new LuisClient(luisAppId, luisAppKey, true); // LUIS に受信したメッセージを送って解析 (※activity.text はユーザー入力値) LuisResult luisResult = await luisClient.Predict(Input); String retStr = ""; // 解析結果 (Intent) に応じた処理を実行 if (luisResult != null) { try { if (luisResult.Intents[0].Actions[0].Name.Equals("GetImageName")) { var entities = luisResult.GetAllEntities(); foreach (Microsoft.Cognitive.LUIS.Entity entity in entities) { switch (entity.Name) { case "ImageName": retStr = entity.Value.Replace(" ", ""); break; } } } else { // LUISがうまく機能してない Console.Out.WriteLine("LUISがうまく機能してない"); } } catch (Exception e) { // LUISの実行は成功したけど、検索キーワードはうまく取得できず Console.Out.WriteLine("LUISで検索キーワードをうまく取れてない"); Console.Out.WriteLine(e.Message); } } return (retStr); } private async Task<string> ExecBingSearch(string ImgUri) { String retStr = ""; string apiKey = "[API Key]"; string queryUri = "https://api.cognitive.microsoft.com/bing/v5.0/images/search" + "?q=" + System.Web.HttpUtility.UrlEncode(ImgUri); HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey); //authentication header to pass the API key client.DefaultRequestHeaders.Add("Accept", "application/json"); string bingRawResponse = null; BingImageSearchResponse bingJsonResponse = null; try { bingRawResponse = await client.GetStringAsync(queryUri); bingJsonResponse = JsonConvert.DeserializeObject<BingImageSearchResponse>(bingRawResponse); ImageResult[] imageResult = bingJsonResponse.value; // 最初のURLだけを取得 retStr = imageResult[0].contentUrl; } catch (Exception e) { //Bing SearchのNG Console.Out.WriteLine("Bing Img Searchのエラー"); Console.Out.WriteLine(e.Message); } return (retStr); } private async Task<string> ExecVisionAnalysis(string url) { string retstr = ""; string visionApiKey = "[API Key]"; 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; try { analysisResult = await visionClient.AnalyzeImageAsync(url, visualFeatures); if (analysisResult != null) { if (analysisResult.Adult.IsAdultContent == true) { retstr += "[Adult]"; } else if (analysisResult.Adult.IsRacyContent == true) { retstr += "[Racy]"; } // 最初のをセット(日本語化したいけど、Transfer 使うしかないかな?) retstr += analysisResult.Description.Captions[0].Text; } } catch (Exception e) { //Vision APIの実行エラー Console.Out.WriteLine("Vision APIの実行エラー"); Console.Out.WriteLine(e.Message); } return (retstr); } private async Task<string> ExecEmotionAnalysis(string url) { string retstr = ""; string emotionApiKey = "[API Key]"; EmotionServiceClient emotionServiceClient = new EmotionServiceClient(emotionApiKey); try { Emotion[] emotionResult = null; // Emotion API の実行 emotionResult = await emotionServiceClient.RecognizeAsync(url); Scores emotionScores = emotionResult[0].Scores; // 一番高いスコアの取得 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; int topEmotionScore = (int)(topEmotion.Value*100); retstr = "[Emotion]"+topEmotionKey + "[" + topEmotionScore.ToString() + "%]"; } catch (Exception e) { //Emotion APIの実行エラー Console.Out.WriteLine("Emotion APIの実行エラー"); Console.Out.WriteLine(e.Message); } return (retstr); } private Activity HandleSystemMessage(Activity message) { if (message.Type == ActivityTypes.DeleteUserData) { // Implement user deletion here // If we handle user deletion, return a real message } else if (message.Type == ActivityTypes.ConversationUpdate) { // Handle conversation state changes, like members being added and removed // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info // Not available in all channels } else if (message.Type == ActivityTypes.ContactRelationUpdate) { // Handle add/remove from contact lists // Activity.From + Activity.Action represent what happened } else if (message.Type == ActivityTypes.Typing) { // Handle knowing tha the user is typing } else if (message.Type == ActivityTypes.Ping) { } return null; } } } |
Model/BingImageSearchResponse.cs
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 | using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace y9demoimgaearch.Model { public class BingImageSearchResponse { public string _type { get; set; } public int totalEstimatedMatches { get; set; } public string readLink { get; set; } public string webSearchUrl { get; set; } public ImageResult[] value { get; set; } } public class ImageResult { public string name { get; set; } public string webSearchUrl { get; set; } public string thumbnailUrl { get; set; } public object datePublished { get; set; } public string contentUrl { get; set; } public string hostPageUrl { get; set; } public string contentSize { get; set; } public string encodingFormat { get; set; } public string hostPageDisplayUrl { get; set; } public int width { get; set; } public int height { get; set; } public string accentColor { get; set; } } } |
おまけ
ついでに Skype Bot も試してみました~。Bot Framework から Add to Skype のボタンを押しただけだけどw
Skype for Business とかに展開する方法がまだわからない・・・
コメント