微軟認知服務應用秘籍 – 漫畫翻譯篇

概述

微軟認知服務包括了影像、語音、語言、搜索、知識五大領域,通過對這些認知服務的獨立或者組合使用,可以解決很多現實世界中的問題。作為AI小白,我們可以選擇艱難地攀登崇山峻嶺,也可以選擇像牛頓一樣站在巨人的肩膀上。本章節的內容就以"漫畫翻譯"為例,介紹如何靈活使用微軟認知服務來實現自己的AI夢想。

日本漫畫非常著名,如海賊王,神探柯南等系列漫畫在中國的少年一代中是非常普及。國內專門有一批志愿者,全手工翻譯這些漫畫為中文版本,過程艱辛復雜,花費時間很長。能否使用AI來幫助加快這個過程呢?

小提示:漫畫是有版權的,請大家要在尊重版權的前提下做合法的事。

漫畫翻譯,要做的事情有三步:

  1. 調用微軟認知服務,用OCR(光學字符識別)服務識別出漫畫上所有文字;
  2. 調用微軟認知服務,用Text Translate(文本翻譯)服務把日文翻譯成中文;
  3. 自己寫邏輯代碼把中文文字貼回到以前的漫畫中,覆蓋以前的日文,生成新的漫畫幀。

下圖是展示最后的翻譯效果,左側是原漫畫,右側是翻譯成中文的結果:

環境準備

安裝Windows 10版本 1803,低一些的Windows 10版本也可以使用。Windows 7也可以運行本示例程序,但不建議使用,Windows 7的官方技術支持到2020/01/14結束。

小提示:如果您的機器不能運行Windows 10,說明硬件性能還是有些不夠的。AI是建立在軟硬件快速發展的基礎上的,不建議您使用低配置的機器來做AI知識的學習。

安裝Visual Studio 2017 Community。點擊這里下載,對于本案例,安裝時選擇".NET桌面開發"即可滿足要求。

申請微軟認知服務密鑰

申請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服務

Endpoint: https://westcentralus.api.cognitive.microsoft.com/vision/v2.0

Text Translate文本翻譯服務

Endpoint: https://api.cognitive.microsofttranslator.com/translate?api-version=3.0

小提示:以上兩個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 Japaneseprivate string Language;// OCR result objectprivate 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 messagereturn;}// show image at imgSourceBitmapImage 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引擎相比,不能同日而語,超級棒!

舊OCR引擎的文檔在這里:https://westus.dev.cognitive.microsoft.com/docs/services/5adf991815e1060e6355ad44/operations/56f91f2e778daf14a499e1fc

新Recognize Text引擎的文檔在這里:

https://westus.dev.cognitive.microsoft.com/docs/services/5adf991815e1060e6355ad44/operations/587f2c6a154055056008f200

新的引擎在API交互設計上,有一個不同的地方:當你提交一個請求后,服務器會立刻返回Accepted (202),然后給你一個URL,用于查詢狀態的。于是需要在客戶端程序里設個定時器,每隔一段時間(比如200ms),訪問那個URL,來獲得最終的OCR結果。

返回的結果JSON格式也有所不同,大家可以自己試著實現一下:

OCR糾錯處理

在下圖中,如綠色橢圓區域所示,OCR引擎犯了一個小錯誤,它把上下兩個不同對白氣泡的文字框在了一起。

這個是可以在自己的程序里做后期糾錯處理來矯正的。大家可以仔細分析OCR的返回結果,看看如何實現。文檔在這里:

https://westus.dev.cognitive.microsoft.com/docs/services/5adf991815e1060e6355ad44/operations/56f91f2e778daf14a499e1fc

聚類處理待翻譯的文字

觀察力好的同學,可能會發現一個問題,如下圖所示,左側圖的一個對白氣泡里,有四句話,但其實它們是一句話,分開寫到4列而已。

這種情況帶來的問題是:這四句話分別送給翻譯引擎做翻譯,會造成前后不連貫,語句不通順。可以考慮的解決方案是,先根據矩形的位置信息,把這四句話合并成同一句話,再送給翻譯引擎。這就是標準的聚類問題,通過搜索引擎可以找到一大堆參考文檔,比如這些:

https://blog.csdn.net/summer_upc/article/details/51475512

https://www.ibm.com/developerworks/cn/analytics/library/ba-1607-clustering-algorithm/index.html

轉載于:https://www.cnblogs.com/ms-uap/p/9481653.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/250922.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/250922.shtml
英文地址,請注明出處:http://en.pswp.cn/news/250922.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

01 React初步認知、React元素、渲染、工程化

定義 react&#xff1a;用于構建用戶界面的 JavaScript 庫 &#xff08;僅負責View層渲染、應在視圖上體現交互邏輯&#xff09;vue&#xff1a;漸進式JavaScript 框架&#xff08;MVVM&#xff09; 使用 引入CDN腳本添加根容器 div #app創建React組件 ReactDOM.render Re…

node --- 在express中配置使用模板引擎(art-template)

下載依賴: npm install --save art-template express-art-template配置: // app.js const express require("express"); const app express(); app.engine("html", require("express-art-template"));使用: 例如處理瀏覽器GET請求 /students…

PAM認證機制

一、PAM簡介 Sun公司1995年開發的一種與認證相關的通用框架機制&#xff0c;PAM只關注如何為服務驗證用戶的API&#xff0c;通過提供一些動態鏈接庫和一套統一的API&#xff0c;將系統提供的服務和該服務的認證方式分開&#xff1b;PAM只是一個框架而已&#xff0c;自身不做認證…

02 JSX學習

使用vite處理jsx vite引入的腳本必須是ESM的 npm init -y yarn add vite package.json 添加vite命令 index.html引入jsxJSX是什么 一種標簽語法&#xff0c;在JS基礎上進行的語法擴展不是字符串、也不是HTML是描述UI呈現與交互的直觀的表現形式JSX被編譯后會生成React元素 &am…

使用FreeCookies 控制瀏覽器cookies及修改http響應內容

FreeCookies 插件安裝 1&#xff1a;您的計算機需要已經安裝Fiddler &#xff08;如未安裝&#xff0c;請至官網下載安裝 http://docs.telerik.com/fiddler/configure-fiddler/tasks/configurefiddler&#xff09; 2&#xff1a;進入Fiddler安裝目錄下的Scripts目錄下&#xff…

node --- 使用node連接mysql

1.確保下載了mysql,且mysql處于打開狀態. 2.確保下載了node,并成功安裝:https://nodejs.org/en/ (小黑窗 node -v 查看) 3.安裝node操作mysql的依賴包: # 命令行 npm install --save -mysql# 注:如果沒有package.json 建議先使用 npm init -y 初始化正題 // app.js// 1. 引…

03 渲染元素ReactDOM.render

React與ReactDOM是2個不同的庫&#xff0c;根節點內的所有內容&#xff08;和DOM更新、渲染相關&#xff09;由ReactDOM來管理一個React應用只有一個根節點用ReactDOM.render將React元素渲染到根節點 ReactDOM.render 參數1 React元素&#xff08;React.createElement(類組件/…

javascript --- 異步按順序執行

使用promise可以很優雅的封裝一個異步函數,使其按指定順序執行: // 異步讀取文件操作 const fs require("fs"); function promiseReadFile(url) {return new Promise(function (resolve, reject) {fs.readFile(url, function(err, data) {if(err) {reject(err);} e…

web提高:負載均衡

1、集群 1、為什么建議在阿里云購買負載均衡 非常便宜&#xff0c;又好用&#xff0c;有穩定&#xff0c;有簡單。自己搭建不了負載均衡&#xff0c;因為共有云不支持組播跑不了vrp協議。你不會集群的概念&#xff0c;你還是蒙蒙的。2、為什么使用集群&#xff1f; 1、小規模 …

node --- 一個很好用的包json-server

這個第三方包,可以將json文件暴露出來,用http獲取. (data.json如下) 下載依賴: npm install --g json-server查看是否含有json-server json -sever --version啟動json-server 參考:https://www.npmjs.com/package/json-server

04 組件與Props

一些概念 組件&#xff1a;視圖的片段、內部管理數據集合&#xff08;state&#xff09;外部傳入配置結合&#xff08;props&#xff09;包含&#xff1a; 1. 視圖標記&#xff08;React的JSX、Vue的template&#xff09;需要經過轉換而成為真實的DOM 事件 數據 邏輯&#x…

利用ionic3進行上一行和左一行不動,中間移動的功能

首先在html中的寫法是 <ion-header><ion-navbar><ion-title>歷史數據</ion-title></ion-navbar></ion-header><ion-content ><div style"display:flex;width:625px;"><div class"head">地區</di…

05 state與setState、單向數據流

聲明周期與組件卸載 props配置&#xff1a;使用組件時傳入數據state私有數據&#xff1a;組件內部使用的數據 state的使用注意事項 必須使用setState方法來更改state多個setState會合并調用props和state更新數據要謹慎&#xff08;有可能在異步程序中更新&#xff09;setState…

HDU 3342 Legal or Not(拓撲排序)

描述 ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that just like a big family. Every day,many "holy cows" like HH, hh, AC, ZT, lcc, BF, Qinz and so on chat on-line to exchange their ideas. When someone…

jquery --- 阻止表單默認的提交行為,標準化表單的數據

表單如下: // .html <form id"topics_new_form" method"post" action"/topics/new"><div class"form-group"><label for"exampleInputEmail1">選擇模塊</label><selecet class"form-contr…

javascript --- spa初體驗

首先使用express創建一個簡單的服務器 創建文件夾 be-project # (確保安裝了node,并配置好了環境) 在be-project目錄下(命令行執行) npm init -y npm install --save express body-parse npm install --global nodemon// app.js const express require("express");…

vuex復習筆記

npm install vuex --save 進行安裝 vuex import Vuex from vuex 新建一個vuex文件夾&#xff08;這個不是必須的&#xff09;&#xff0c;并在文件夾下新建store.js文件&#xff0c;文件中引入我們的vue和vuex。 轉載于:https://www.cnblogs.com/jinsuo/p/8508699.html

python學習HTML之CSS(2)

1、邊框的屬性設置 PS&#xff1a;邊框的高度和寬度可以采用百分比&#xff0c;但是高度方向的百分比基本無用&#xff0c;因為基數沒定&#xff0c;參考沒意義&#xff01;&#xff01; 2、內邊距和外邊距 3、在右下角添加一個“回頂部”的標簽。 <div></div>中的…

06 事件處理函數綁定與事件對象

事件處理函數綁定 DOM事件處理 addEventListener or onclick function(){} 純小寫React元素也采用了類似DOM0標準中的事件屬性定義的方法 小駝峰 JSX <button onClick{ this.doSth }></button>直接創建React元素 React.createElement(button,{onClick: { this.…

css -- 兩種方法實現流式布局

Bootstrap將屏幕分為4個等級: 1.超小屏幕 (寬度小于768 px), 顯示寬度 100%; 2.小屏幕 (寬度在768px ~ 992px), 顯示寬度 750px; 3.中等屏幕 (寬度在992px ~ 1200px), 顯示寬度 970px; 4.大屏幕 (寬度大于1200px), 顯示寬度 1170px. js實現: window.addEventListener("r…