タイトル通り、「自然文で画像を検索して画像 / 感情解析をする 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
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
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 とかに展開する方法がまだわからない・・・