运用AI翻译漫画(二)

构建代码

构建这个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 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;}}
}

其中,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直接生成类就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/608737.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

并发程序设计--D10线程池及gdb调试多线程

线程池 概念&#xff1a; 通俗的讲就是一个线程的池子&#xff0c;可以循环的完成任务的一组线程集合 必要性&#xff1a; 我们平时创建一个线程&#xff0c;完成某一个任务&#xff0c;等待线程的退出。但当需要创建大量的线程时&#xff0c;假设T1为创建线程时间&#xf…

贯穿设计模式-中介模式+模版模式

样例代码 涉及到的项目样例代码均可以从https://github.com/WeiXiao-Hyy/Design-Patterns.git获取 需求 购买商品时会存在着朋友代付的场景&#xff0c;可以抽象为购买者&#xff0c;支付者和中介者之间的关系 -> 中介者模式下单&#xff0c;支付&#xff0c;发货&#xff0…

什么是软件测试

一、软件测试的定义 软件测试的经典定义是在规定条件下对程序进行操作&#xff0c;以发现错误&#xff0c;对软件质量进行评估。因为软件是由文档、数据以及程序组成的&#xff0c;所以软件测试的对象也就不仅仅是程序本身&#xff0c;而是包括软件形成过程的文档、数据以及程…

什么是博若莱新酒节?

在红酒圈儿里混&#xff0c;一定不能不知道博若莱新酒节&#xff0c;这是法国举世闻名的以酒为主题的重要节日之一。现已成为世界范围内庆祝当年葡萄收获和酿制的节日&#xff0c;被称为一年一度的酒迷盛会。 云仓酒庄的品牌雷盛红酒LEESON分享博若莱位于法国勃艮第南部&#x…

Spark Core------算子介绍

RDD基本介绍 什么是RDD RDD:英文全称Resilient Distributed Dataset&#xff0c;叫做弹性分布式数据集&#xff0c;是Spark中最基本的数据抽象&#xff0c;代表一个不可变、可分区、里面的元素可并行计算的集合。 Resilient弹性&#xff1a;RDD的数据可以存储在内存或者磁盘…

C# OpenCvSharp DNN FreeYOLO 目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 目标检测 效果 模型信息 Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- Outp…

Eureka注册中心Eureka提供者与消费者,Eureka原理分析,创建EurekaServer和注册user-service

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Eureka提供者与消费者二、Eureka原理分析eurekaeureka的作用eureka总结 三、创建EurekaServer和注册user-service创建EurekaServer总结 服务的拉取总结-Eur…

docker拉取镜像提示 remote trust data does not exist for xxxxxx

1、How can I be sure that I am pulling a trusted image from docker 2、docker: you are not authorized to perform this operation: server returned 401. 以上两个问题可以试试以下解决办法 DOCKER_CONTENT_TRUSTfalse 本人是使用jenkins部署自己的项目到docker容器出现…

Spring MVC参数接收、参数传递

Springmvc中&#xff0c;接收页面提交的数据是通过方法形参来接收&#xff1a; 处理器适配器调用springmvc使用反射将前端提交的参数传递给controller方法的形参 springmvc接收的参数都是String类型&#xff0c;所以spirngmvc提供了很多converter&#xff08;转换器&#xff0…

Contingency Planning学习记录

Contingency Planning over Probabilistic Hybrid Obstacle Predictions for Autonomous Road Vehicles Contingency Planning over Probabilistic Hybrid Obstacle Predictions for Autonomous Road Vehicles - 知乎 Contingency Planning over Probabilistic Hybrid Obstac…

QT第二天

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为&…

赠送葡萄酒:为别人选择合适的葡萄酒

葡萄酒可以在许多不同的场合成为很好的礼物&#xff0c;因为它可以用来庆祝许多不同的事情。当被邀请去别人家时&#xff0c;你可以带酒去吃饭。葡萄酒可以用来纪念婚礼、出生、毕业和各种纪念日&#xff0c;来自云仓酒庄品牌雷盛红酒分享这是一个非常合适的专业礼物。但是你怎…

1878_emacs company backend的选择尝试

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/editors_skills: Summary for some common editor skills I used. 1872_emacs company backend的选择尝试 从C语言开发的使用场景角度&#xff0c;通过测试尝试看看这个company的backend应该来如何配置。 主题由来介…

React Native 桥接原生实现 JS 调用原生方法

一、为什么需要桥接原生 为了满足在React 层无法实现的需求 复杂高性能的组件&#xff1a;复杂表格、视频播放原生层开发能力&#xff1a;传感器编程、widget平台属性&#xff1a;系统信息、设备信息对接第三方应用&#xff1a;相机、相册、地图 真实的开发过程中是不可能完…

Spring循环引用和三级缓存

前言 Spring 解决 Bean 之间的循环引用关系用到了三级缓存&#xff0c;那么问题来了。三级缓存是怎么用的&#xff1f;每一层的作用是什么&#xff1f;非得用三级吗&#xff1f;两级缓存行不行&#xff1f; 理解循环引用 所谓的“循环引用”是指 Bean 之间的依赖关系形成了一…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷④

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷4 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷4 模块一 …

一键搭建elk

一键启动elk 1. 生成环境的脚本 setup.sh #!/usr/bin/bash# logstash enviroment mkdir -p logstash touch logstash/logstash.conf # shellcheck disableSC1078 echo input {tcp {mode > "server"host > "0.0.0.0"port > 4560codec > jso…

多PC文件夹同步方案

在多个工作终端独立具备svn版本库的情况下&#xff0c;可使用本工具进行一键同步。 相较于传统的SVN中心检出更新方案中移动存储设备硬件及文件目录系统多终端间易损坏&#xff0c;本方案更加稳定 资料同步结构&#xff1a; 使用步骤&#xff1a; 1.修改config.ini配置文件 2…

【C++】HP-Socket(二):框架介绍、功能说明

1、接口 1.1 接口模型 1.2 三类接口 HP-Socket 定义了三类接口 组件接口&#xff08;如&#xff1a;ITcpServer / IUdpClient&#xff09;&#xff1a;声明组件操作方法&#xff0c;应用程序创建组件对象后通过该接口来使用组件组件实现类&#xff08;如&#xff1a;CTcpSer…

Mac 安装Nginx教程

Nginx官网 Nginx官网英文 1.在终端输入brew search nginx 命令检查nginx是否安装了 2. 安装命令&#xff1a;brew install nginx 3. 查看Nginx信息命令brew info nginx 4. 启动 nginx方式&#xff1a;在终端里输入 nginx 5.查看 nginx 是否启动成功 在浏览器中访问http://l…