Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[chatbot + AI = 下一代操作模式][26]賦予chatbot OCR的能力 - 加入對發票的功能

[chatbot + AI = 下一代操作模式][26]賦予chatbot OCR的能力 - 加入對發票的功能.jpg
圖片來源:https://pixabay.com/en/books-spine-colors-pastel-1099067/ 

在上一篇([25]使用Computer Vision - 如何設定、看文件以及使用REST API測試)看完了如何建立Computer Vision的Key,瞭解如何看REST Api的文件并且用Postman做服務測試。

這一篇將把OCR的功能整合到chatbot裡面,看看實際開發起來是個什麽感覺。

這篇的程式碼github頁面是alantsai-samples/mhat-hotelbot:blog/chapter-26

加入發票識別功能

一個訂房相關的chatbot哪裏會用到OCR其實我想不太出來,目前想到兩種情景:

  1. 訂房完有發票或訂房確認訊息 - 可以用OCR方式把他變成電子内容給使用者提供一些服務
  2. 飯店設施有錯誤的時候的查詢 - 例如假設有借電腦的服務,如果電腦有問題可能會出現錯誤訊息,但是這個錯誤訊息對不常用電腦的人來説可能看不懂,因此可以用OCR服務搭配顯示出任可看的訊息

OCR只是一個技術,怎麽使用就看大家的創意了。

今天要加入的功能是,讓chatbot可以透過拍照發票的方式直接識別出發票號碼,然後看有沒有中獎。(好吧,其實就是一個對發票的功能,沒什麽創意Orz)。

調整程式碼

那要加入這個功能大概會拆成以下幾個步奏:

  1. 建立一個新的Dialog專門處理對發票
  2. 建立一個Service把OCR服務包起來
  3. 把Dialog和Service結合起來
  4. 把RootLuisDialog和對發票結合
  5. 測試

建立一個新的Dialog專門處理對發票

建立一個Dialog叫做ReceiptRecognizerDialog。這個Dialog作用很簡單,就是會呼叫OCR的服務,然後返回識別出來的發票號碼。

[Serializable]
public class ReceiptRecognizerDialog : IDialog<string>
{
	public Task StartAsync(IDialogContext context)
	{
		throw new NotImplementedException();
	}
}

目前實作的部分先暫時不理他,等一下再回來處理。

建立一個Service把OCR服務包起來

還記得在上篇介紹API文件的時候有提到,在最下面有提供sample code。而C#的sample code是透過HttpClient直接對接REST Api取得結果。

不過在實際開發上還是希望透過物件的方式來呼叫服務,因此Sample Code的方式最好還是包一層比較好呼叫。

微軟在Computer Vision的部分有提供一個SDK,已經把REST Api包好了,因此將會使用這個SDK作爲基礎。

首先,先安裝nuget套件:Install-Package Microsoft.ProjectOxford.Vision

題外話,以前Cognitive Service有個另外個名稱是Project Oxford(牛津計劃),後來又改名了。不過SDK名稱沒有改。其他Cognitive Service有提供SDK也將會在ProjectOxford名稱下面。

安裝好了之後,建立出一個OCRService,這個Service提供了兩個方法:

  1. 傳入圖片url的方式來辨別
  2. 傳入一個Stream,可以用作本地檔案上傳的時候用
public class OCRService
    {
        public OCRService()
        {
            VisionServiceClientInstance =
                 new VisionServiceClient
                    (ConfigurationManager.AppSettings["ComputerVision.Key"],
                    ConfigurationManager.AppSettings["ComputerVision.Url"]);
        }

        public VisionServiceClient VisionServiceClientInstance { get; }

        public async Task GetOcrResultAsync
            (Stream imageStream, string languageCode = "unk")
        {
            return await VisionServiceClientInstance.RecognizeTextAsync(imageStream, languageCode);
        }

        public async Task GetOcrResultAsync
            (string imageUrl, string languageCode = "unk")
        {
            return await VisionServiceClientInstance.RecognizeTextAsync
                (imageUrl, languageCode);
        }

這邊有兩個值是從AppSetting取得,分別為:
  1. ComputerVision.Key:這個是取得建立服務得到的Key
  2. ComputerVision.Url:這個是服務的網址,例如:https://southeastasia.api.cognitive.microsoft.com/vision/v1.0

把Dialog和Service結合起來

這邊將整合兩種情況:

  1. 如果傳入的是一個網址
  2. 直接傳送圖片的方式

首先,調整Dialog裡面的内容:

public async Task StartAsync(IDialogContext context)
{
	await context.PostAsync
		("請上傳發票圖片或者發票圖片的網址");

	context.Wait(MessageReceivedAsync);
}

private async Task MessageReceivedAsync
	(IDialogContext context, 
		IAwaitable<IMessageActivity> result)
{
	var messageResult = await result;

	var cvs = new OCRService();

	var finalResult = string.Empty;

	// 上傳圖片的處理
	if (messageResult.Attachments
			?.Any(a => a.ContentType.Contains("image")) 
				?? false)
	{
		var attachment =
			messageResult.Attachments.FirstOrDefault
				(x => x.ContentType.Contains("image"));

		var imageStream = await
			messageResult.GetConnector()
				.GetImageStream(attachment);

		var ocrResult = await cvs
			.GetOcrResultAsync(imageStream, "zh-Hant");

		finalResult = ProcessImageOcrResult(context, ocrResult);
	}
	// 圖片網址的處理
	else if (Uri.IsWellFormedUriString
				(messageResult.Text, UriKind.Absolute))
	{
		var ocrResult = await cvs
			.GetOcrResultAsync(messageResult.Text, "zh-Hant");

		finalResult = ProcessImageOcrResult(context, ocrResult);
	}

	context.Done(finalResult);
}

上面這段的程式碼有幾個重要的地方:

圖片取得的邏輯
有些channel的圖片直接在Attachment裡面,不過有些要做額外處理。例如Skype就需要用特殊的API才能夠取得圖片。因此,裡面有幾個方法是從Extension Helper來的,分別為:
  • GetConnector()
  • GetImageStream()
這個程式碼就不貼在部落格裏面了,詳細可以參考github上面。 Helper/IActivityHelper.csHelper/IConnectorClientHelper.cs
處理OCR識別的結果
private string ProcessImageOcrResult(IDialogContext context,
	OcrResults ocrResult)
{
	var result = string.Empty;

	// 偷懶,發票號碼格式是:AA-12345678
	// 因此找出第3個字母是-的就算是發票號碼
	var foundErrorCode = ocrResult.Regions.SelectMany(x => x.Lines)
						.SelectMany(x => x.Words)
						.FirstOrDefault(x => x.Text.Length > 3 
							&& x.Text.Substring(2, 1) == "-");

	if (foundErrorCode != null)
	{
		result = foundErrorCode.Text;
	}

	return result;
}
這邊偷懒了,如果真的是production程式碼,判斷哪裏屬於發票號碼需要更加嚴謹一些。

把RootLuisDialog和對發票結合

到目前爲止,一切都准備好了,剩下就是怎麽觸發對發票的Dialog。

這邊,先去luis.ai上面加入一個新的intent叫做ReceiptRecognizer這個將會觸發剛剛建立的Dialog:

[LuisIntent("ReceiptRecognizer")]
public Task ReceiptRecognizer
	(IDialogContext context, LuisResult result)
{
	context.Call(new ReceiptRecognizerDialog(),
		ReceiptRecognizerAfterAsync);

	return Task.CompletedTask;
}

private async Task ReceiptRecognizerAfterAsync
	(IDialogContext context,
		IAwaitable<string> result)
{
	var finalResult = await result;

	if(string.IsNullOrEmpty(finalResult) == false)
	{
		await context.PostAsync($"您的發票號碼是:{finalResult}");
	}
	else
	{
		await context.PostAsync("識別發票號碼失敗");
	}

	context.Wait(MessageReceived);
}

測試結果

接下來就是測試執行結果,將會使用以下這個發票做測試:

test-receipt.png
測試發票來源:https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSlMVlVgbic3nVNTY23omioynJKej6jP9-N-Ccwu40dIiS3nPGK

首先測試用圖片上傳的方式:

botframework-emulator_2018-08-05_14-35-45.png
圖片上傳的結果

再來測試用圖片網址的方式:

botframework-emulator_2018-08-05_14-36-02.png
圖片網址的方式

結語

這篇介紹了如何把Computer Vision裡面的OCR服務整合到了chatbot裡面,并且模擬了一個對發票的情景把他運用了起來。

可是如果我今天有別的圖片識別服務想要做怎麽辦?例如說,在酒店裡面的冰箱有一些喝的,有沒有辦法用拍照的方式知道產品價錢?

Computer Vision做不到,但是Custom Vision可以。下一篇([27]Custom Vision - 自己的Model自己Train 建立圖片的分類模型)來介紹什麽是Custom Vision并且能夠做到什麽。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus