今年软件杯有一个项目是工商图片文字提取。大致的要求就是将天猫给的50张样例图片中的企业名称和注册号提取出来。速度和准确率是这个项目的关键。
我觉得软件杯历年的项目都挺有难度的,很有挑战性。近几年由于人工智能和大数据的飞速发展,项目命题也偏向这方面而不是传统项目了。所以对于程序员来说,除了了解传统项目知识(比如java web三大框架),还要与时俱进,自主学习大数据和人工智能的前沿知识,我认为现在及时转型对个人发展是非常重要的。这可能决定未来的走向,因为在不远的将来,AI和大数据将是主流。
工商图片文字提取涉及到计算机视觉处理,也就是图像识别。本来是想用python写的,会方便点,但是那时候看项目的语言要求,好像不能使用python,只能用java写咯。java图像处理这方面还真不如python。由于底层涉及太多的像素处理等等,我用的是Tesseract-OCR,tess4j这个谷歌的开源框架,识别文字挺好的。能识别一些常用的文字,底层代码都是C++写的,然后用java封装起来,写好方法供上层调用。
这个框架自行百度下载然后配置好就ok了,下面看看我的maven项目结构。
有个特别重要的是tessdata文件夹中要放一些词库,比如英文词库eng.traineddata,中文词库chi_sim.traineddata(要自行下载)等等。如果你想提高这套框架识别的速度和准确率的话,可以去训练对应词库,然后加入到tessdata文件夹下,这样对特定的文字群将会有更好的识别效果。以前是接近0.9秒一张,训练词库以后可以达到0.1秒左右。
另外在识别图片之前,还需要对一些难点图片进行特殊处理,比如翻转,放大缩小,去水印(二值化),灰度处理,图片截取等等,这样预处理对后面的识别有很大的帮助。
总代码:
package Test;
import com.recognition.software.jdeskew.ImageDeskew;
import net.sourceforge.tess4j.ITessAPI;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.Word;
import net.sourceforge.tess4j.util.ImageHelper;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Date;
import java.util.List;
public class Test {
static final double MINIMUM_DESKEW_THRESHOLD = 0.05d;
static ITesseract instance;
public static void main(String[] args) throws Exception{
Date data1=new Date();
//testEn();
testZh();
//Wordbyword_extraction();
Date data2=new Date();
System.out.println((data2.getTime()-data1.getTime())/1000);
}
//使用英文字库 - 识别图片
public static void testEn() throws Exception {
File imageFile = new File("pictures/10.png");
BufferedImage image = ImageIO.read(imageFile);
//对图片进行处理
image = flipImage(image);
image = convertImage(image);
instance = new Tesseract();//JNA Interface Mapping
//划定区域
// x,y是以左上角为原点,width和height是以x,y为基础
//Rectangle rect = new Rectangle(20, 20, 1500, 200);
String result = instance.doOCR(image);
System.out.println(result);
}
//使用中文字库 - 识别图片
public static void testZh() throws Exception {
File imageFile = new File("pictures/10.png");
BufferedImage image = ImageIO.read(imageFile);
//对图片进行处理
image = flipImage(image);
image = convertImage(image);
instance = new Tesseract();//JNA Interface Mapping
instance.setLanguage("chi_sim");//使用中文字库
//划定区域
// x,y是以左上角为原点,width和height是以x,y为基础
//Rectangle rect = new Rectangle(20, 20, 800, 200);
String result = instance.doOCR(image);
System.out.println(result);
}
//逐词提取
public static void Wordbyword_extraction() throws Exception {
//按照每个字取词
int pageIteratorLevel = ITessAPI.TessPageIteratorLevel.RIL_SYMBOL;
File imageFile = new File("pictures/10.png");
BufferedImage image = ImageIO.read(imageFile);
//对图片进行处理
image = flipImage(image);
image = convertImage(image);
instance = new Tesseract();//JNA Interface Mapping
instance.setLanguage("chi_sim");//使用中文字库
List result = instance.getWords(image, pageIteratorLevel);
for (Word word : result) {
System.out.print(word.toString());
}
}
//对图片进行处理 - 提高识别度
public static BufferedImage convertImage(BufferedImage image) throws Exception {
//按指定宽高创建一个图像副本
image = ImageHelper.getSubImage(image, 0, 0, image.getWidth(), image.getHeight());
//图像转换成灰度的简单方法 - 黑白处理
image = ImageHelper.convertImageToGrayscale(image);
//图像缩放 - 放大n倍图像
image = ImageHelper.getScaledInstance(image, image.getWidth() * 3, image.getHeight() * 3);
return image;
}
//处理倾斜
public static BufferedImage flipImage(BufferedImage image) throws Exception {
//按指定宽高创建一个图像副本
image = ImageHelper.getSubImage(image, 0, 0, image.getWidth(), image.getHeight());
ImageDeskew id = new ImageDeskew(image);
double imageSkewAngle = id.getSkewAngle(); //获取倾斜角度
if ((imageSkewAngle > MINIMUM_DESKEW_THRESHOLD || imageSkewAngle < -(MINIMUM_DESKEW_THRESHOLD))) {
image = ImageHelper.rotateImage(image, -imageSkewAngle); //纠偏图像
}
return image;
}
}
去水印
package Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.awt.Color;
public class RemoveImageWatermark {
public static void binaryImage() throws IOException {
String frompath = "pictures/1.png";
String topath = "pictures/1.png";
File file1 = new File(frompath);
BufferedImage image = ImageIO.read(file1);
int w=image.getWidth();
int h=image.getHeight();
double[][] zuobiao = new double[w][h];
for(int i=0;i> 16;
int green = (pixel & 0xff00) >> 8;
int blue = (pixel & 0xff);
//System.out.println(red+" "+green+" "+blue);
zuobiao[i][j]=(red+green+blue)/3.0;
if(red>=229&&green>=229&&blue>=229)
{
int white=new Color(255,255,255).getRGB();
image.setRGB(i,j,white);
}
// if(red>=10&&red<=20&&green>=10&&green<=20&&blue>=10&&blue<=20)
// {
// int cover=new Color(red,green,blue).getRGB();
// image.setRGB(i,j,cover);
// }
// else
// {
// int white=new Color(255,255,255).getRGB();
// image.setRGB(i,j,white);
// }
}
}
File file2=new File(topath);
ImageIO.write(image,"png",file2);
}
public static void main(String[] args) throws IOException
{
new RemoveImageWatermark().binaryImage();
}
}
图片截取:
package Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
public class Screenshots {
private String srcpath ;
private String subpath ;
private int x ;
private int y ;
private int width ;
private int height ;
public Screenshots() {
}
public Screenshots( int x, int y, int width, int height) {
this.x = x ;
this.y = y ;
this.width = width ;
this.height = height ;
}
public void cut()throws IOException {
FileInputStream is = null ;
ImageInputStream iis = null ;
try {
is =new FileInputStream(srcpath);
Iterator < ImageReader > it=ImageIO.getImageReadersByFormatName("png");
ImageReader reader = it.next();
iis = ImageIO.createImageInputStream(is);
reader.setInput(iis, true ) ;
ImageReadParam param = reader.getDefaultReadParam();
Rectangle rect = new Rectangle(x, y, width, height);
param.setSourceRegion(rect);
BufferedImage bi=reader.read(0,param);
ImageIO.write(bi,"png",new File(subpath));
} finally {
if (is != null )
is.close() ;
if (iis != null )
iis.close();
}
}
public static void main(String[] args) {
Screenshots screenshots = new Screenshots(0, 0, 493, 80);
screenshots.srcpath = "pictures/1.png";
screenshots.subpath = "pictures/1.png";
try {
screenshots.cut();
} catch (IOException e) {
e.printStackTrace();
}
}
}
加入训练词库:
先将训练好的exercise.traineddata加入tessdata,然后在代码中这样修改就好了
instance.setLanguage("chi_sim+exercise");
训练词库的流程比较复杂,花了我挺多时间的,在这里不多说了,可以去看看训练词库的博客,有一个jTessBoxEditor软件,按照流程一步一步来就好了。
如果有好的图像识别的算法(可以是自己写的),欢迎交流。能理解图像识别原理并能自己写出算法或者运用经典算法的话,还是很不容易的。
详细代码可以访问我的github网址 https://github.com/29DCH/SoftwareCup-ImageTextExtraction