运用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.rhkb.cn/news/231267.html

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

相关文章

JAVA版鸿鹄云商B2B2C:解析多商家入驻直播带货商城系统的实现与应用

一、技术选型 java开发语言&#xff1a;java是一种跨平台的编程语言&#xff0c;适用于大型企业级应用开发。使用java开发直播商城可以保证系统的稳定性和可扩展性。 spring boot框架&#xff1a;spring boot是一个快速构建spring应用的框架&#xff0c;简化了开发过程&#xf…

ArkTS - @Builder自定义构建函数

这个Builder作用就是可以把组件样式抽离出来&#xff0c;写成公共组件&#xff0c;下边记录下全局自定义构建函数用法及注意的地方。 官方文档&#xff1a;开发者可以将重复使用的UI元素抽象成一个方法&#xff0c;在build方法里调用。 一、用法 下边代码&#xff0c;我在Co…

PostgreSQL10数据库源码安装及plpython2u、uuid-ossp插件安装

PostgreSQL10数据库源码安装及plpython2u、uuid-ossp插件安装 1、环境2、安装包下载3、安装3.1 、解压3.2、配置3.3、编译安装3.4 、启动与关闭 4、安装 uuid-ossp 、plpython2u插件5、参考 1、环境 centos 7 、 postgresql 10.19 2、安装包下载 postgres 源码安装包 3、安…

(三)STM32F407 cubemx串口中断通讯

&#xff08;三&#xff09;STM32F407 cubemx串口中断通讯 这篇文章主要是个人的学习经验&#xff0c;想分享出来供大家提供思路&#xff0c;如果其中有不足之处请批评指正哈。废话不多说直接开始主题&#xff0c;本人是基于STM32F407VET6芯片&#xff0c;但是意在你看懂这篇文…

MyBatisPlus学习二:常用注解、条件构造器、自定义sql

常用注解 基本约定 MybatisPlus通过扫描实体类&#xff0c;并基于反射获取实体类信息作为数据库表信息。可以理解为在继承BaseMapper 要指定对应的泛型 public interface UserMapper extends BaseMapper<User> 实体类中&#xff0c;类名驼峰转下划线作为表名、名为id的…

python的课后练习总结4(while循环)

for循环用于针对序列中的每个元素的一个代码块。 while循环是不断的运行&#xff0c;直到指定的条件不满足为止。 while 条件&#xff1a; 条件成立重复执行的代码1 条件成立重复执行的代码2 …….. i 1while i < 5:print(i)i i 11、使用wh…

后端中的Dao层、Service层、Impl层、utils层、Controller层

Java Dao层 dao层叫数据访问层&#xff0c;全称为data access object&#xff0c;属于一种比较底层&#xff0c;比较基础的操作&#xff0c;具体到对于某个表、某个实体的增删改查&#xff0c;对外提供稳定访问数据库的方法 Mapper:&#xff08;DAO&#xff09; 访问数据库&am…

工作中人员离岗识别摄像机

工作中人员离岗识别摄像机是一种基于人工智能技术的智能监控设备&#xff0c;能够实时识别员工离岗状态并进行记录。这种摄像机通常配备了高清摄像头、深度学习算法和数据处理系统&#xff0c;可以精准地监测员工的行为&#xff0c;提高企业的管理效率和安全性。 工作中人员离岗…

TDD-LTE TAU流程

目录 1. TAU成功流程 1.1 空闲态TAU 1.2 连接态TAU 2. TAU失败流程 当UE进入一个小区&#xff0c;该小区所属TAI不在UE保存的TAI list内时&#xff0c;UE发起正常TAU流程&#xff0c;分为IDLE和CONNECTED&#xff08;即切换时&#xff09;下。如果TAU accept分配了一个新的…

Java程序设计——GUI设计

一、目的 通过用户图形界面设计&#xff0c;掌握JavaSwing开发的基本方法。 二、实验内容与设计思想 实验内容&#xff1a; 课本验证实验&#xff1a; Example10_6 图 1 Example10_7 图 2 图 3 Example10_15 图 4 设计思想&#xff1a; ①学生信息管理系统&#xff1a…

如何在2024年编写Android应用程序

如何在2024年编写Android应用程序 本文将介绍以下内容&#xff1a; 针对性能进行优化的单活动多屏幕应用程序 &#x1f92b;&#xff08;没有片段&#xff09;。应用程序架构和模块化 → 每个层面。Jetpack Compose 导航。Firestore。应用程序架构&#xff08;模块化特征驱动…

JS 手写 new 函数

工作中我们经常会用到 new 关键字&#xff0c;new 一个构造函数生成一个实例对象&#xff0c;那么new的过程中发生了什么呢&#xff0c;我们今天梳理下 创建一个对象对象原型继承绑定函数this返回对象 先创建一个构造函数&#xff0c;原型上添加一个方法 let Foo function (n…

Python元组与字典的基础介绍

元组(tuple) 在Python中,元组是不可变的有序元素的序列 即创建后不可以被修改 创建方式val_name ([val],[val].....) #----------声明------------ tuple_1 (1,2,3) print(tuple_1)元组的运算 虽然说元组的额元素是不可以更改的,但元组之间可以使用,,*号进行运算,运算后会…

静态网页设计——宠物狗狗网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1nk4y1X74M/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

can/CANFD数据记录仪——冬标神器

冬测案例 新能源电池在冬标中要测试电池的电性能&#xff0c;热管理&#xff0c;充电&#xff0c;SOC的性能电动车的关键组之一是动力电池&#xff0c;动力电池的表现&#xff0c;除了依赖自身的材料&#xff0c;工艺等硬件素质外&#xff0c;还依赖电池管理系统的表现&#xf…

小型洗衣机哪个牌子质量好?五款内衣洗衣机便宜好用的牌子推荐

随着大家工作的压力越来越大&#xff0c;下了班之后只能想躺平&#xff0c;在洗完澡之后看着还需要手洗的内衣裤真的很头疼。有些小伙伴还有会攒几天再丢进去洗衣机里面一起&#xff0c;而且这样子是非常不好的&#xff0c;用过的内衣裤长时间不清洗容易滋生细菌&#xff0c;而…

Java设计模式-享元模式

目录 一、网站项目需求 二、传统方案 三、享元模式 &#xff08;一&#xff09;基本介绍 &#xff08;二&#xff09;原理类图 &#xff08;三&#xff09;内部状态和外部状态 &#xff08;四&#xff09;享元模式解决网站展现项目 &#xff08;五&#xff09;注意事项…

Linux系统安全

作为一种开放源代码的操作系统&#xff0c;linux服务器以其安全、高效和稳定的显著优势而得以广泛应用。 账号安全控制 用户账号是计算机使用者的身份凭证或标识&#xff0c;每个要访问系统资源的人&#xff0c;必须凭借其用户账号 才能进入计算机.在Linux系统中&#xff0c;提…

继电器光耦在微控制器中的应用

继电器是电子系统中的重要组件&#xff0c;用作使用低功率信号控制高功率电路的开关。继电器与微控制器的集成在各种应用中变得越来越普遍。该领域的一个重大进步是继电器光耦合器的使用&#xff0c;这是一种增强基于微控制器的系统的性能和可靠性的关键技术。 继电器光耦概述…

web3d-three.js场景设计器-TransformControls模型控制器

场景设计器-TransformControls 控制器 该控制器可以指定模型进入可控制模式-如图有三种控制方式 translate --移动模式 rotate -- 旋转模式 scale -- 缩放模式 方便布局过程中快捷对模型进行摆放操作。 引入方式 import { TransformControls } from three/examples/jsm/…