1. 程式人生 > >微軟認知服務應用祕籍 – 漫畫翻譯篇

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

概述

微軟認知服務包括了影像、語音、語言、搜尋、知識五大領域,通過對這些認知服務的獨立或者組合使用,可以解決很多現實世界中的問題。作為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服務

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列而已。

 

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