Java实现印刷体转手写体 |
您所在的位置:网站首页 › 怎样将图片文字转换成手写文字 › Java实现印刷体转手写体 |
Java实现印刷体转手写体—妈妈再也不用担心我被罚抄作业了
文章目录
Java实现印刷体转手写体—妈妈再也不用担心我被罚抄作业了缘起开始开发测试效果开源地址和总结
郑重声明 因本文中涉及到爬虫程序,该爬虫源码仅用于交流学习 如果要使用本文中的技术或源码,请务必严格遵守每个网站根目录下的robots.txt爬虫协议 因擅自用作其他用途而产生的法律风险请自行承担! 缘起随着人工智能、深度学习的发展,OCR(通用文字识别)技术开始逐渐兴起 从一开始的印刷体识别,到手写体识别,识别的准确率随着学习数据的增长和学习模型的完善而越来越高 从初中进入少年编程班时候就开始幻想机器如果能像人类一样思考是怎样一番光景 但是没有想到这一天来得这么快 碍于笔者水平有限,不能带大家去复刻OCR背后的技术 那么今天,我们就来做一个“反人工智能程序”(简称“人工智障”) 简言之就是反其道而行之,OCR将手写文字转成了文本文件,我们此刻就要做一个把文本转成以假乱真手写体的程序 至于用处。。。 那可大有用处! 小时候只要犯了错就会被老师罚抄课本,而且动不动就是5遍10遍,给我的九年义务教育留下了深深的阴影,相信大家也感同身受(别人家的孩子请绕道) 那时候就在想:要是能有一个自动帮我抄课本的机器人该有多好! 作为连变色手机壳都能做出来的程序员们,天下没有能难倒我们的需求。 今天我们就一起来实现一个抄课本神器。 当然,我们离实现小时候的梦想还差一个时光机。(又是什么狗血穿越剧 - -) 废话不多说,我们进入正题! 开始开发在开始开发之前,我们首先需要解决几个问题: 文字怎么转成手写体(重点)?怎么把手写体“写到纸上”?怎么让手写体看起来尽量逼真?我这里提供一下自己的拙见,如果有更好的解决方案欢迎指正: 文字怎么转成手写体(重点)? 首先就是上网找找有没有现成的“轮子” 笔者度娘了一下“手写字体在线生成器”,除去第一条广告,第二条结果笔者认为是不二之选 点击进入的效果大概是这样 就决定是它了! 第一字体网 怎么把手写体“写到纸上”? 这个难不倒我,因为之前有用Java代码绘制海报的经历,也算半个熟手 决定用Java AWT 来处理绘制问题 怎么让手写体看起来尽量逼真? 这个需要对自己平时写字有个观察 同样的字写两遍大小、笔顺都不相同因为体态的原因,同一行字会略有倾斜总之就是四个字:“乱就完了”综上所述,处理方式也很简单——加个随机数 分析完毕,现在我们先来对“第一字体网”的请求来进行分析,然后写个简单的“爬虫” 首先打开浏览器,摁下F12,发送一个生成文字请求,分析请求 经过一段时间的观察,得出结论: 图中红框部分就是我们要的请求地址 点开可以看见我们的参数都是以form-data的形式传给后台的 接下来就是清洗数据,得到我们想要的结果 对返回结果进行分析,可以看出返回的是一整个页面 而我们需要的仅仅是生成后的这张图片 而这张图片所在元素的id为 imgResult 对整个请求流程分析完毕,我们接下来就可以开始创建项目撸代码了 新建一个SpringBoot项目text-generator(也可以是一个Maven工程或者是Java程序) 这是一个典型SpringBoot项目结构, 如果对SpringBoot不熟悉的小伙伴可以移步到我的Spring系列手写教案 首先我们需要编辑一下resources下的yml配置文件,将我们上面得出的结论给写到配置里 # 服务器运行端口 server: port: 80 # 请求地址 url: http://m.diyiziti.com/shouxie # 画布(纸张)大小 canvas: width: 720 height: 1000接下来我们需要配置一个RestTemplate用于发送请求 /** * @Description * @Author LaoQin * @Date 2020/03/15 22:42 **/ @Configuration public class RestConfig { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }然后我们需要写一个文字转图片的工具类(请求我们上面的接口) /** * @Description 文字转手写体图片 * @Author LaoQin * @Date 2020/03/15 22:40 **/ @Component public class TextToImg { @Autowired RestTemplate restTemplate; @Value("${url}") String url; /** * @Author LaoQin * @Description //TODO 将文字转成手写文字并返回 * @Date 22:47 2020/03/15 * @Param [text] 要转写的文字 * @return java.io.File 返回的文件 **/ public InputStream textToImg(String text,Integer fontInfoId,Integer fontSize,Integer imageWidth,Integer ImageHeight,String fontColor) throws IOException { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap map = new LinkedMultiValueMap(); map.add("Content",text); map.add("FontInfoId",fontInfoId); map.add("ActionCategory",1); map.add("FontSize",fontSize); map.add("ImageWidth",imageWidth); map.add("ImageHeight",ImageHeight); map.add("FontColor",fontColor); map.add("ImageBgColor",""); System.out.println("请求参数:"+map); HttpEntity entity = new HttpEntity(map,headers); String result = restTemplate.postForObject(url, entity, String.class); Document document = Jsoup.parse(result); Element element = document.getElementById("imgResult"); String src = element.attr("src"); //new一个URL对象 URL url = new URL(src); //打开链接 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); //设置请求方式为"GET" conn.setRequestMethod("GET"); //超时响应时间为5秒 conn.setConnectTimeout(5 * 1000); //通过输入流获取图片数据 InputStream inStream = conn.getInputStream(); return inStream; } }代码里有注释,这里我说一下核心流程: 发送一个携带各项参数(详见代码)的Post请求,获得一个网页将网页用Jsoup解析,并且获取id为imgResult的img元素的src属性其核心代码为 Document document = Jsoup.parse(result); Element element = document.getElementById("imgResult"); String src = element.attr("src"); 接下来就是将获取到的src(图片地址)作为请求地址,发送一个Get请求并获取到输入流返回其核心代码为 //new一个URL对象 URL url = new URL(src); //打开链接 HttpURLConnection conn = (HttpURLConnection)url.openConnection(); //设置请求方式为"GET" conn.setRequestMethod("GET"); //超时响应时间为5秒 conn.setConnectTimeout(5 * 1000); //通过输入流获取图片数据 InputStream inStream = conn.getInputStream(); return inStream;下一步我们需要建立Ctrl层和Service层 Ctrl层的代码为 /** * @Description * @Author LaoQin * @Date 2020/03/15 23:03 **/ @RestController public class BaseCtrl { @Autowired BaseService baseService; @PostMapping(value = "text") public String test(String title,String text,String color,HttpServletResponse response) throws Exception { baseService.text(title,text,color); return null; } }Service接口代码为 public interface BaseService{ void text(String title, String text, String color) throws Exception; }ServiceImpl代码为 package com.scj.text.generator.serviceImpl; import com.scj.text.generator.service.BaseService; import com.scj.text.generator.util.TextToImg; import com.sun.image.codec.jpeg.JPEGCodec; import com.sun.image.codec.jpeg.JPEGImageEncoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Random; @Service public class BaseServiceImpl implements BaseService { @Autowired TextToImg textToImg; @Value("${canvas.width}") int canvasWidth; @Value("${canvas.height}") int canvasHeight; private final int[] FONT_LIST = {455,464,465,81};//预设字体列表 private final int FONT_SIZE = 36;//预设字体大小 private static int pageNum = 0;//当前页数 @Override public void text(String title, String text, String color) throws Exception { //创建Image BufferedImage image = new BufferedImage(canvasWidth,canvasHeight,BufferedImage.TYPE_INT_RGB); File file = new File("D:\\img-output\\img"+pageNum+".png"); if(!file.exists()){//没有则创建 File folder = new File("D:\\img-output"); folder.mkdir();//创建文件夹 file.createNewFile(); } //创建输出流 FileOutputStream out = new FileOutputStream(file); //创建画笔 Graphics g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0,0,canvasWidth,canvasHeight); //画标题 //算出起笔位置 int beginX = (canvasWidth-FONT_SIZE*(title.length()))/2; if(beginX2){ x+=(imgWidth-FONT_SIZE*3/2)*count/2-10; }else{ x+=(imgWidth-FONT_SIZE*3/2); } if(x+FONT_SIZE>canvasWidth){ x = 0; y += (imgWidth-FONT_SIZE*3/2+10); if(y+FONT_SIZE>canvasHeight){ //换纸 pageNum++; //释放资源 g.dispose(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(image); //关闭流 out.close(); System.out.println("完成!"); text("",text.substring(i+1),color); return; } } } //释放资源 g.dispose(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(image); //关闭流 out.close(); System.out.println("完成!"); } }因为只是demo,并没有及时去重构自己的代码,让代码有一些“坏味道”,但是不影响核心思路,各位看官凑合先看着 这里说一下思路,这也是这个程序中最关键的代码 首先需要注入我们刚才写的工具类和配置文件里的内容 其核心代码如下 @Autowired TextToImg textToImg; @Value("${canvas.width}") int canvasWidth; @Value("${canvas.height}") int canvasHeight;然后上第一字体网寻找几个和我笔迹比较接近的手写字体,大家切换字体的时候在chrome控制台>network下就能看到id,和我笔迹比较相仿的我找了4个,分别是{455,464,465,81},将其定义为常量 其核心代码如下 private final int[] FONT_LIST = {455,464,465,81};//预设字体列表接下来是设置字体大小,因为我们的画布是700*1000的大小,再加上我平时写字都写的特别小,所以我的字体大小为36,当然一般正常的笔迹在这个大小的画布上应该是48~60,大家可以根据自己的习惯调整 其核心代码如下 private final int FONT_SIZE = 36;//预设字体大小接下来我们则要创建画布、画笔和最终输出的文件,这里默认输出到D盘下的img-output文件夹,且文件名为img+当前页数+.png后缀 其核心代码如下 //创建Image BufferedImage image = new BufferedImage(canvasWidth,canvasHeight,BufferedImage.TYPE_INT_RGB); File file = new File("D:\\img-output\\img"+pageNum+".png"); if(!file.exists()){//没有则创建 File folder = new File("D:\\img-output"); folder.mkdir();//创建文件夹 file.createNewFile(); } //创建输出流 FileOutputStream out = new FileOutputStream(file); //创建画笔 Graphics g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0,0,canvasWidth,canvasHeight); //画标题 //算出起笔位置 int beginX = (canvasWidth-FONT_SIZE*(title.length()))/2; if(beginX4?x-30:x+randomX,y+randomY,flag==0?FONT_SIZE:FONT_SIZE*count/2,FONT_SIZE,null); if(count>2){ x+=(imgWidth-FONT_SIZE*3/2)*count/2-10; }else{ x+=(imgWidth-FONT_SIZE*3/2); }我们还需要判断画笔当前位置,如果y坐标加上字体大小超过画布高度,我们就认为再写字就会超出画布,这时候就需要“换纸”了,同样x坐标+字体大小超过画布宽度我们则需要换行书写 换纸本质就是把已经书写内容截断后“交给”下一页继续书写,即一个递归调用 其核心代码如下 if(x+FONT_SIZE>canvasWidth){ x = 0; y += (imgWidth-FONT_SIZE*3/2+10); if(y+FONT_SIZE>canvasHeight){ //换纸 pageNum++; //释放资源 g.dispose(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(image); //关闭流 out.close(); System.out.println("完成!"); text("",text.substring(i+1),color);//递归书写 return; } }最后别忘了释放资源 核心代码如下 //释放资源 g.dispose(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(image); //关闭流 out.close(); System.out.println("完成!");到这里我们想要的效果就实现完毕了 我们来测试一下效果如何~ 测试效果我们使用Postman发送一个post请求来测试最终效果 首先我在网络上找到了一篇小学生课文《少年闰土》节选,相信这篇课文会勾起很多90后的童年回忆 我们看看这篇文章最终会转写成为什么样子: postman截图 请求过程中 最终生成效果 这个程序虽然不是很完美,但是也算圆了小时候的一个幻想 我想编程对于一个程序员可能不仅仅是一个谋生手段 我们也可以在其中收获许许多多别的东西 项目我已开源至github,感兴趣的小伙伴可以自己试试,因为是demo,难免会有不足,欢迎各位大佬指正。 感谢各位看官赏识,感兴趣小伙伴可以点个关注,我会不定时在博客里更新自己在工作和生活中的一些所见所闻所感。~from 老邋遢 -完- |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |