微軟認知服務應用祕籍 – 漫畫翻譯篇
概述
微軟認知服務包括了影像、語音、語言、搜尋、知識五大領域,通過對這些認知服務的獨立或者組合使用,可以解決很多現實世界中的問題。作為AI小白,我們可以選擇艱難地攀登崇山峻嶺,也可以選擇像牛頓一樣站在巨人的肩膀上。本章節的內容就以"漫畫翻譯"為例,介紹如何靈活使用微軟認知服務來實現自己的AI夢想。
日本漫畫非常著名,如海賊王,神探柯南等系列漫畫在中國的少年一代中是非常普及。國內專門有一批志願者,全手工翻譯這些漫畫為中文版本,過程艱辛複雜,花費時間很長。能否使用AI來幫助加快這個過程呢?
小提示:漫畫是有版權的,請大家要在尊重版權的前提下做合法的事。
漫畫翻譯,要做的事情有三步:
- 呼叫微軟認知服務,用OCR(光學字元識別)服務識別出漫畫上所有文字;
- 呼叫微軟認知服務,用Text Translate(文字翻譯)服務把日文翻譯成中文;
- 自己寫邏輯程式碼把中文文字貼回到以前的漫畫中,覆蓋以前的日文,生成新的漫畫幀。
下圖是展示最後的翻譯效果,左側是原漫畫,右側是翻譯成中文的結果:
環境準備
安裝Windows 10版本 1803,低一些的Windows 10版本也可以使用。Windows 7也可以執行本示例程式,但不建議使用,Windows 7的官方技術支援到2020/01/14結束。
小提示:如果您的機器不能執行Windows 10,說明硬體效能還是有些不夠的。AI是建立在軟硬體快速發展的基礎上的,不建議您使用低配置的機器來做AI知識的學習。
安裝Visual Studio 2017 Community。
申請微軟認知服務金鑰
申請OCR服務金鑰
在上圖所示頁面中"計算機影像"下點選"免費試用":
根據自己的實際情況選擇以上三個選項之一,這裡以選擇第一個"來賓"選項為例:
選擇一個熱愛的國家/地區,在上下兩個複選框上("我同意","我接受")都打勾,點選"下一步":
上圖中以選擇"Microsoft"賬戶為例繼續:
最後得到了上面這個頁面,這裡的金鑰(Key)和終結點(Endpoint)要在程式中使用,請儲存好!
小提示:上面例子中的金鑰只能再使用1天了,因為是7天的免費試用版本。所以當你的程式以前執行正常,某一天忽然從伺服器不能得到正常的返回值時並且得到錯誤程式碼Unauthorized (401),請首先檢查金鑰狀態。
小提示:當試用的Key過期後,你是無法再申請試用Key的,只能申請正式的Key,這就要通過Azure門戶。在Azure門戶中申請好Computer Vision服務(包括OCR服務)的Key後,它會告訴你Endpoint是…../vision/v1.0,這個不用管它,在code裡還保持……/vision/v2.0就可以了,兩者的Key是通用的。
申請Text Translate文字翻譯服務金鑰
用自己的Azure賬號登入Azure門戶:
在上圖中點選左側的"All resources":
在上圖中點選上方的 "+ Add"圖示來建立資源,得到資源列表如下 :
在上圖中點選右側列表中的"AI + Machine Learning",得到下圖的具體服務專案列表:
這裡有個坑,文字翻譯不在右側的列表中,需要點選右上方的"See all"來展開所有專案:
哦,好吧,還是沒有!保持耐心,繼續點選Cognitive Services欄目的右側的"More"按鈕,得到更詳細的列表:
還是沒有?卷滾一下看看?到底,到底!OK,終於有了Translator Text,就是Ta:
建立這個服務時,我們選擇F0就可以了。如果要是做商用軟體的話,你可以選擇S1或其他,100萬個字元才花10美元,不貴不貴!
使用VS Tools for AI
是不是以上申請Key的過程太複雜了?那是因為Azure內容龐雜,網頁設計層次太多!其實這個過程是可以簡化的,因為我們有個Visual Studio Tools for AI擴充套件包!
開啟VS2017,選單上選擇"工具(Tools)->擴充套件和更新(Extensions and Updates)",在彈出的對話方塊左側選擇"聯機(Online)",在右側上方輸入"AI" 進行搜尋,會看到"Microsoft Visual Studio Tools for AI"擴充套件包,下載完畢後關閉VS,這個擴充套件包就會自動安裝。
安裝完畢後,再次開啟VS2017,點選選單View->Server Explorer。如果安裝了Tools for AI,此時會看到以下介面:
在AI Tools->Azure Cognitive Services下,可以看到我已經申請了2個service,ComputerVisionAPI和TranslateAPI就是我們想要的,這兩個名字是自己在申請服務時指定的。
假設你還沒有這兩個服務,那麼在Azure Cognitive Services上滑鼠右鍵,然後選擇Create New Cognitive Service,出現以下對話方塊:
在每個下拉框中顯示的內容可能會每個人都不一樣,絕大多數是用下拉框完成填充的,很方便。假設我想申請TextTranslation服務,那麼我在Service Name上填寫一個自己能看懂的名字就行了,比如我填寫了"TranslateAPI",這樣比較直接。同理可以建立ComputerVisionAPI服務。服務的名字不會在Code中使用。
小結
我們廢了老鼻子勁,得到了以下兩個REST API的Endpoint和相關的Key:
OCR服務
Text Translate文字翻譯服務
小提示:以上兩個Endpoint的URL是目前最新的版本,請不要使用舊的版本如v1.0等等。
咱們是洗洗睡了,還是寫程式碼?看天色還早,繼續寫程式碼吧!
構建程式碼
構建這個PC桌面應用,我們需要幾個步驟:
在得到第一次的顯示結果後,經過測試,有很大可能會根據結果再對介面進行調整,實際上也是一個區域性的軟體工程中的迭代開發。
介面設計
啟動Visual Studio 2017, 建立一個基於C#語言的WPF(Windows Presentation Foundation)專案:
WPF是一個非常成熟的技術,在有介面展示和互動的情況下,使用XAML設計/渲染引擎,比WinForm程式要強101倍,再加上有C#語言利器的幫助,是寫PC桌面前端應用的最佳組合。
給Project起個名字,比如叫"CartoonTranslate",選擇最新的.NET Framework (4.6以上),然後點選"OK"。我們先一起來設計一下介面:
Input URL:用於輸入網際網路上的一張漫畫圖片的URL
Engine:指的是兩個不同的演算法引擎,其中,OCR舊引擎可以支援25種語言,識別效果可以接受;而Recognize Text新引擎目前只能支援英文,但效果比較好。
Language:制定當前要翻譯的漫畫的語言,我們只以英文和日文為例,其它國家的漫畫相對較少,但一通百通,一樣可以支援。
右側的一堆Button瞭解一下:
Show:展示Input URL中的圖片到下面的圖片區
OCR:呼叫OCR服務
Translate:呼叫文字翻譯服務,將日文或者英文翻譯成中文
下側大面積的圖片區瞭解一下:
Source Image:原始漫畫圖片
Target Image:翻譯成中文對白後的漫畫圖片
介面設計程式碼
我們在MainWindow.xaml檔案裡面填好以下code:
<Window x:Class="CartoonTranslate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CartoonTranslate"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Grid.Row="0" Text="Input URL:"/>
<TextBox x:Name="tb_Url" Grid.Row="1" Width="600"
Text="http://stat.ameba.jp/user_images/20121222/18/secretcube/2e/19/j/o0800112012341269548.jpg"/>
<Button x:Name="btn_Show" Content="Show" Click="btn_Show_Click" Width="100"/>
<Button x:Name="btn_OCR" Content="OCR" Click="btn_OCR_Click" Width="100"/>
<Button x:Name="btn_Translate" Content="Translate" Click="btn_Translate_Click" Width="100"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBlock Text="Engine:"/>
<RadioButton x:Name="rb_V1" GroupName="gn_Engine" Content="OCR" Margin="20,0" IsChecked="True" Click="rb_V1_Click"/>
<RadioButton x:Name="rb_V2" GroupName="gn_Engine" Content="Recognize Text" Click="rb_V2_Click"/>
<TextBlock Text="Language:" Margin="20,0"/>
<RadioButton x:Name="rb_English" GroupName="gn_Language" Content="English"/>
<RadioButton x:Name="rb_Japanese" GroupName="gn_Language" Content="Japanese" IsChecked="True" Margin="20,0"/>
</StackPanel>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Source Image" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="2" Text="Target Image" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Image x:Name="imgSource" Grid.Column="0" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Image x:Name="imgTarget" Grid.Column="2" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Canvas x:Name="canvas_1" Grid.Column="0"/>
<Canvas x:Name="canvas_2" Grid.Column="2"/>
</Grid>
</Grid>
</Window>
處理事件
關於XAML語法的問題不在本文的討論範圍之內。上面的XAML寫好後,編譯時會出錯,因為裡面定義了很多事件,在C#檔案中還沒有實現。所以,我們現在把事件程式碼補上。
區域性變數定義(在MainWindow.xaml.cs的MainWindow class裡面):
// using “OCR” or “Recognize Text”
private string Engine;
// source language, English or Japanese
private string Language;
// OCR result object
private OcrResult.Rootobject ocrResult;
按鈕"Show"的事件
點選Show按鈕的事件,把URL中的漫畫的地址所指向的圖片載入到視窗中顯示:
private void btn_Show_Click(object sender, RoutedEventArgs e)
{
if (!Uri.IsWellFormedUriString(this.tb_Url.Text, UriKind.Absolute))
{
// show warning message
return;
}
// show image at imgSource
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(this.tb_Url.Text);
bi.EndInit();
this.imgSource.Source = bi;
this.imgTarget.Source = bi;
}
在上面的程式碼中,同時給左右兩個圖片區域賦值,顯示兩張一樣的圖片。
按鈕"OCR"的事件
點選OCR按鈕的事件,會呼叫OCR REST API,然後根據返回結果把所有識別出來的文字用紅色的矩形框標記上:
private async void btn_OCR_Click(object sender, RoutedEventArgs e)
{
this.Engine = GetEngine();
this.Language = GetLanguage();
if (Engine == "OCR")
{
ocrResult = await CognitiveServiceAgent.DoOCR(this.tb_Url.Text, Language);
foreach (OcrResult.Region region in ocrResult.regions)
{
foreach (OcrResult.Line line in region.lines)
{
if (line.Convert())
{
Rectangle rect = new Rectangle()
{
Margin = new Thickness(line.BB[0], line.BB[1], 0, 0),
Width = line.BB[2],
Height = line.BB[3],
Stroke = Brushes.Red,
//Fill =Brushes.White
};
this.canvas_1.Children.Add(rect);
}
}
}
}
else
{
}
}
在上面的程式碼中,通過呼叫DoOCR()自定義函式返回了反序列化好的類,再依次把返回結果集中的每個矩形生成一個Rectangle圖形類,它的left和top用Margin的方式來定義,width和height直接賦值即可,把這些Rectangle圖形類的例項新增到canvas_1的Visual Tree裡即可顯示出來(這個就是WPF的好處啦,不用處理繪圖事件,但效能不如用Graphics類直接繪圖)。
按鈕"Translate"的事件
點選Translate按鈕的事件:
private async void btn_Translate_Click(object sender, RoutedEventArgs e)
{
List<string> listTarget = await this.Translate();
this.ShowTargetText(listTarget);
}
private async Task<List<string>> Translate()
{
List<string> listSource = new List<string>();
List<string> listTarget = new List<string>();
if (this.Version == "OCR")
{
foreach (OcrResult.Region region in ocrResult.regions)
{
foreach (OcrResult.Line line in region.lines)
{
listSource.Add(line.TEXT);
if (listSource.Count >= 25)
{
List<string> listOutput = await CognitiveServiceAgent.DoTranslate(listSource, Language, "zh-Hans");
listTarget.AddRange(listOutput);
listSource.Clear();
}
}
}
if (listSource.Count > 0)
{
List<string> listOutput = await CognitiveServiceAgent.DoTranslate(listSource, Language, "zh-Hans");
listTarget.AddRange(listOutput);
}
}
return listTarget;
}
private void ShowTargetText(List<string> listTarget)
{
int i = 0;
foreach (OcrResult.Region region in ocrResult.regions)
{
foreach (OcrResult.Line line in region.lines)
{
string translatedLine = listTarget[i];
Rectangle rect = new Rectangle()
{
Margin = new Thickness(line.BB[0], line.BB[1], 0, 0),
Width = line.BB[2],
Height = line.BB[3],
Stroke = null,
Fill =Brushes.White
};
this.canvas_2.Children.Add(rect);
TextBlock tb = new TextBlock()
{
Margin = new Thickness(line.BB[0], line.BB[1], 0, 0),
Height = line.BB[3],
Width = line.BB[2],
Text = translatedLine,
FontSize = 16,
TextWrapping = TextWrapping.Wrap,
Foreground = Brushes.Red
};
this.canvas_2.Children.Add(tb);
i++;
}
}
}
上面這段程式碼中,包含了兩個函式:this.Translate()和this.ShowTargetText()。
我們先看第一個函式:最難理解的地方可能是有個"25"數字,這是因為Translate API允許一次提交多個字串並一起返回結果,這樣比你提交25次字串要快的多。翻譯好的結果按順序放在listOutput裡,供後面使用。
再看第二個函式:先根據原始文字的矩形區域,生成一些白色的實心矩形,把它們貼在右側的目標圖片上,達到把原始文字覆蓋(扣去)的目的。然後再根據每個原始矩形生成一個TextBlock,設定好它的位置和尺寸,再設定好翻譯後的結果(translatedLine),這樣就可以把中文文字貼到圖上了。
選項按鈕的事件
點選Radio Button的事件:
private void rb_V1_Click(object sender, RoutedEventArgs e)
{
this.rb_Japanese.IsEnabled = true;
}
private void rb_V2_Click(object sender, RoutedEventArgs e)
{
this.rb_English.IsChecked = true;
this.rb_Japanese.IsChecked = false;
this.rb_Japanese.IsEnabled = false;
}
private string GetLanguage()
{
if (this.rb_English.IsChecked == true)
{
return "en";
}
else
{
return "ja";
}
}
private string GetEngine()
{
if (this.rb_V1.IsChecked == true)
{
return "OCR";
}
else
{
return "RecText";
}
}
API資料訪問部分
我們需要在CatroonTranslate工程中新增以下三個.cs檔案:
- CognitiveServiceAgent.cs
- OcrResult.cs
- TranslateResult.cs
與認知服務互動
CognitiveServiceAgent.cs檔案完成與REST API互動的工作,包括呼叫OCR服務的和呼叫翻譯服務的程式碼:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace CartoonTranslate
{
class CognitiveServiceAgent
{
const string OcrEndPointV1 = "https://westcentralus.api.cognitive.microsoft.com/vision/v2.0/ocr?detectOrientation=true&language=";
const string OcrEndPointV2 = "https://westcentralus.api.cognitive.microsoft.com/vision/v2.0/recognizeText?mode=Printed";
const string VisionKey1 = "4c20ac56e1e7459a05e1497270022b";
const string VisionKey2 = "97992f0987e4be6b5be132309b8e57";
const string UrlContentTemplate = "{{\"url\":\"{0}\"}}";
const string TranslateEndPoint = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from={0}&to={1}";
const string TKey1 = "04023df3a4c499b1fc82510b48826c";
const string TKey2 = "9f76381748549cb503dae4a0d80a80";
public static async Task<List<string>> DoTranslate(List<string> text, string fromLanguage, string toLanguage)
{
try
{
using (HttpClient hc = new HttpClient())
{
hc.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", TKey1);
string jsonBody = CreateJsonBodyElement(text);
StringContent content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
string uri = string.Format(TranslateEndPoint, fromLanguage, toLanguage);
HttpResponseMessage resp = await hc.PostAsync(uri, content);
string json = await resp.Content.ReadAsStringAsync();
var ro = Newtonsoft.Json.JsonConvert.DeserializeObject<List<TranslateResult.Class1>>(json);
List<string> list = new List<string>();
foreach(TranslateResult.Class1 c in ro)
{
list.Add(c.translations[0].text);
}
return list;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}
private static string CreateJsonBodyElement(List<string> text)
{
var a = text.Select(t => new { Text = t }).ToList();
var b = JsonConvert.SerializeObject(a);
return b;
}
/// <summary>
///
/// </summary>
/// <param name="imageUrl"></param>
/// <param name="language">en, ja, zh</param>
/// <returns></returns>
public static async Task<OcrResult.Rootobject> DoOCR(string imageUrl, string language)
{
try
{
using (HttpClient hc = new HttpClient())
{
ByteArrayContent content = CreateHeader(hc, imageUrl);
var uri = OcrEndPointV1 + language;
HttpResponseMessage resp = await hc.PostAsync(uri, content);
string result = string.Empty;
if (resp.StatusCode == System.Net.HttpStatusCode.OK)
{
string json = await resp.Content.ReadAsStringAsync();
Debug.WriteLine(json);
OcrResult.Rootobject ro = Newtonsoft.Json.JsonConvert.DeserializeObject<OcrResult.Rootobject>(json);
return ro;
}
}
return null;
}
catch (Exception ex)
{
Debug.Write(ex.Message);
return null;
}
}
private static ByteArrayContent CreateHeader(HttpClient hc, string imageUrl)
{
hc.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", VisionKey1);
string body = string.Format(UrlContentTemplate, imageUrl);
byte[] byteData = Encoding.UTF8.GetBytes(body);
var content = new ByteArrayContent(byteData);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return content;
}
}
}
小提示:以上兩個Key是無法直接使用的,請使用自己申請的Key。
其中,DoTranslate()函式和DoOCR()函式都是HTTP呼叫,很容易理解。只有CreateJsonBodyElement函式需要解釋一下。前面提到過我們一次允許給伺服器提交25個字串做批量翻譯,因此傳進來的是個List<string>,經過這個函式的簡單處理,會得到以下JSON格式的資料作為HTTP的Body:
// JSON Data as Body
[
{“Text” : ”第1個字串”},
{“Text” : ”第2個字串”},
……..
{“Text” : ”第25個字串”},
]
OCR服務的資料類定義
OcrResult.cs檔案是OCR服務返回的JSON資料所對應的類,用於反序列化:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CartoonTranslate.OcrResult
{
public class Rootobject
{
public string language { get; set; }
public string orientation { get; set; }
public float textAngle { get; set; }
public Region[] regions { get; set; }
}
public class Region
{
public string boundingBox { get; set; }
public Line[] lines { get; set; }
}
public class Line
{
public string boundingBox { get; set; }
public Word[] words { get; set; }
public int[] BB { get; set; }
public string TEXT { get; set; }
public bool Convert()
{
CombineWordToSentence();
return ConvertBBFromString2Int();
}
private bool ConvertBBFromString2Int()
{
string[] tmp = boundingBox.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (tmp.Length == 4)
{
BB = new int[4];
for (int i = 0; i < 4; i++)
{
int.TryParse(tmp[i], out BB[i]);
}
return true;
}
return false;
}
private void CombineWordToSentence()
{
StringBuilder sb = new StringBuilder();
foreach (Word word in words)
{
sb.Append(word.text);
}
this.TEXT = sb.ToString();
}
}
public class Word
{
public string boundingBox { get; set; }
public string text { get; set; }
}
}
需要說明的是,伺服器返回的boundingBox是個string型別,在後面使用起來不太方便,需要把它轉換成整數,所以增加了CovertBBFromString2Int()函式。還有就是返回的是一個個的詞(Word),而不是一句話,所以增加了CombineWordToSentence()來把詞連成句子。
翻譯服務的資料類定義
TranslateResult.cs檔案翻譯服務返回的JSON所對應的類,用於反序列化:
namespace CartoonTranslate.TranslateResult
{
public class Class1
{
public Translation[] translations { get; set; }
}
public class Translation
{
public string text { get; set; }
public string to { get; set; }
}
}
小提示:在VS2017中,這種類不需要手工鍵入,可以在Debug模式下先把返回的JSON拷貝下來,然後新建一個.cs檔案,在裡面用Paste Special從JSON直接生成類就可以了。
執行程式
好啦,大功告成!現在要做的事就是點選F5來編譯執行程式。如果一切順利的話,將會看到介面設計部分所展示的視窗。
我們第一步先點選"Show"按鈕,會得到:
再點選"OCR"按鈕,等兩三秒(取決於網路速度),會看到左側圖片中紅色的矩形圍攏的一些文字。有些文字沒有被識別出來的話,就沒有紅色矩形。
最後點選"Translate"按鈕,稍等一小會兒,會看到右側圖片的變化:
Wow! 大部分的日文被翻譯成了中文,而且位置也擺放得很合適。
習題與進階學習
增加容錯程式碼讓程式健壯
目前的程式碼中沒有很多容錯機制,比如當伺服器返回錯誤時,訪問API的程式碼會返回一個NULL物件,在上層沒有做處理,直接崩潰。再比如,當用戶不按照從左到右的順序點選上面三個button時,會產生意想不到的情況。
改進本應用讓其自動化和產業化
本應用處理單頁的漫畫,並且提供了互動,目的是讓大家直觀理解工作過程,實際上這個過程可以做成批量自動化的,也就是輸入一大堆URL,做後臺識別/翻譯/重新生成圖片後,把圖片批量儲存在本地,再進行後處理。
當然,識別引擎不是萬能的,很多時候不可能準確識別/翻譯出所有對白文字。所以,可以考慮提供一個類似本應用的互動工具,讓漫畫翻譯從業者在機器處理之後,對有錯誤的地方進行糾正。
小提示:請嚴格遵守智慧財產權保護法!在合法的情況下做事。
使用新版本的Engine做字元識別
還記得前面提到過新舊引擎的話題嗎?我們在介面上做了一個Radio Button "Recognize Text",但是並沒有使用它。因為這個新引擎目前還只能支援英文的OCR,所以,如果大家對漫威Marvel漫畫(英文為主)感興趣的話,就可以用到這個引擎啦,與舊OCR引擎相比,不能同日而語,超級棒!
新Recognize Text引擎的文件在這裡:
新的引擎在API互動設計上,有一個不同的地方:當你提交一個請求後,伺服器會立刻返回Accepted (202),然後給你一個URL,用於查詢狀態的。於是需要在客戶端程式裡設個定時器,每隔一段時間(比如200ms),訪問那個URL,來獲得最終的OCR結果。
返回的結果JSON格式也有所不同,大家可以自己試著實現一下:
OCR糾錯處理
在下圖中,如綠色橢圓區域所示,OCR引擎犯了一個小錯誤,它把上下兩個不同對白氣泡的文字框在了一起。
這個是可以在自己的程式裡做後期糾錯處理來矯正的。大家可以仔細分析OCR的返回結果,看看如何實現。文件在這裡:
聚類處理待翻譯的文字
觀察力好的同學,可能會發現一個問題,如下圖所示,左側圖的一個對白氣泡裡,有四句話,但其實它們是一句話,分開寫到4列而已。
這種情況帶來的問題是:這四句話分別送給翻譯引擎做翻譯,會造成前後不連貫,語句不通順。可以考慮的解決方案是,先根據矩形的位置資訊,把這四句話合併成同一句話,再送給翻譯引擎。這就是標準的聚類問題,通過搜尋引擎可以找到一大堆參考文件,比如這些: