Tesseract-OCR识别中文与训练字库
转自:https://www.cnblogs.com/lcawen/articles/7040005.html
关于中文的识别,效果比较好而且开源的应该就是Tesseract-OCR了,所以自己亲身试用一下,分享到博客让有同样兴趣的人少走弯路。
文中所用到的身份证图片资源是百度找的,如有侵权可联系我删除。
一、准备工作
1、下载Tesseract-OCR引擎,注意要3.0以上才支持中文哦,按照提示安装就行,Windows下:https://github.com/UB-Mannheim/tesseract/wiki。
2、下载chi_sim.traindata字库。要有这个才能识别中文。下好后,放到Tesseract-OCR项目的tessdata文件夹里面。
3、下载jTessBoxEditor,这个是用来训练字库的。
以上的几个在百度都能找到下载,就不详细讲了。
二、识别
1、进入cmd,进入到要识别的图片的路径下。
2、输入命令
tesseract 图片名称 生成的结果文件的名称 字库
例如我的图片识别就是:
tesseract test.jpg result -l chi_sim
识别完后会生成result.txt文件
当然啦效果不太理想。所以我们要训练自己的字库。
三、训练
1、将图片转换成tif格式,用于后面生成box文件。可以通过画图,然后另存为tif即可。
更改图片名字,这个是有要求的=。=
tif文面命名格式[lang].[fontname].exp[num].tif
lang是语言 fontname是字体
比如我们要训练自定义字库 mjorcen字体名normal
那么我们把图片文件重命名 mjorcen.normal.exp0.jpg在转tif。
2、生成box文件。
tesseract mjorcen.normal.exp0.jpg mjorcen.normal.exp0 -l chi_sim batch.nochop makebox
box文件和对应的tif一定要在相同的目录下,不然后面打不开。
3、打开jTessBoxEditor矫正错误并训练
打开train.bat
找到tif图,打开,并校正。
4、训练。
只要在命令行输入命令即可。
tesseract mjorcen.normal.exp0.jpg mjorcen.normal.exp0 nobatch box.train
unicharset_extractor mjorcen.normal.exp0.box
在这我明明已经矫正好了,但是还是有1个字符不能识别出来,报的错跟实际上完全没有相关性,不知道是不是bug,到后面的结果就是“园”字没有识别出来。
先不管,毕竟只有一个样本。
新建一个font_properties文件
里面内容写入 normal 0 0 0 0 0 表示默认普通字体
继续敲命令
shapeclustering -F font_properties -U unicharset mjorcen.normal.exp0.tr mftraining -F font_properties -U unicharset -O unicharset mjorcen.normal.exp0.tr cntraining mjorcen.normal.exp0.tr
最后会生成五个文件,把目录下的unicharset、inttemp、pffmtable、shapetable、normproto这五个文件前面都加上normal.
如图:
命令行输入,合并五个文件:
combine_tessdata normal.
得到训练好的字库。
四、测试
1、把 normal.traineddata 复制到Tesseract-OCR 安装目录下的tessdata文件夹中
2、识别命令:
tesseract mjorcen.normal.exp0.jpg mjorcen.normal.exp0 -l normal
3、效果
对比:
总结:肯定要自己训练过后的字库识别效果好,接下来要把整个项目弄进android,还要研究怎么将多个字库合并成一个字库,因为我不可能一次训练完所有的图片文字的。到时候有什么成果了再分享博文。希望大家可以点赞!谢谢。
更新:没有错误的话命令行的提示应该是这样的
c# 引用Tesseract,Tesseract是Google维护的一个OCR识别图片文字的开源项目。而A .Net wrapper for the tesseract ocr Library则是可供.Net平台应用程序使用Tesseract的程序包,由Charles Weld在Github上创建的开源项目,下载量大且持续更新,
https://github.com/charlesw/tesseract通过nuget,如下:
以下是测试程序:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Imaging; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Tesseract; namespace WindowsFormsApplication2 { public partial class Form1 : Form { const int CHARSIZE = 5; public Form1() { InitializeComponent(); string code; this.pictureBox1.Image = ValidateCodeUtils.CreateImage(CHARSIZE, out code); this.textBox1.Text = code; } private void 验证码_Click(object sender, EventArgs e) { string code; this.pictureBox1.Image = ValidateCodeUtils.CreateImage(CHARSIZE, out code); this.textBox1.Text = code; } private void StartOCR() { string txt = ""; //string defaultList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; string defaultList = "2345689ABCDEFGHJKLMNPRSTWXY"; const string language = "eng"; //Nuget安装的Tessract版本为3.20,tessdata的版本必须与其匹配,另外路径最后必须以"\"或者"/"结尾 const string TessractData = @"D:\Program Files\Tesseract-OCR\tessdata\"; TesseractEngine test = new TesseractEngine(TessractData, language); test.SetVariable("tessedit_char_whitelist", defaultList); Bitmap tmpVal = (Bitmap)this.pictureBox1.Image; //灰度化,最新版本的Tesseract内部已经有了类似灰度化、二值化的封装,彩色一样可以识别,只是,验证码加上噪点或噪线识别的就乱七八糟了 var tmpImage = ToGray(tmpVal); this.pictureBox2.Image = tmpImage; Page tmpPage = test.Process(tmpImage, pageSegMode: test.DefaultPageSegMode); txt = tmpPage.GetText(); this.textBox2.Text = txt; } private void button1_Click(object sender, EventArgs e) { StartOCR(); } //图像灰度化:http://www.cnblogs.com/gdjlc/archive/2013/03/05/2943801.html //将彩色图像转化成为灰度图像的过程成为图像的灰度化处理。 //彩色图像中的每个像素的颜色有R、G、B三个分量决定,而每个分量有255中值可取, //这样一个像素点可以有1600多万(255*255*255)的颜色的变化范围。 //而灰度图像是R、G、B三个分量相同的一种特殊的彩色图像,其一个像素点的变化范围为255种, //所以在数字图像处理种一般先将各种格式的图像转变成灰度图像以使后续的图像的计算量变得少一些。 //灰度图像的描述与彩色图像一样仍然反映了整幅图像的整体和局部的色度和亮度等级的分布和特征。 //图像的灰度化处理可用两种方法来实现。 //第一种方法使求出每个像素点的R、G、B三个分量的平均值,然后将这个平均值赋予给这个像素的三个分量。 //第二种方法是根据YUV的颜色空间中,Y的分量的物理意义是点的亮度,由该值反映亮度等级, //根据RGB和YUV颜色空间的变化关系可建立亮度Y与R、G、B三个颜色分量的对应:Y=0.3R+0.59G+0.11B,以这个亮度值表达图像的灰度值。 /// <summary> /// 图像灰度化 /// </summary> /// <param name="bmp"></param> /// <returns></returns> public static Bitmap ToGray(Bitmap bmp) { for (int i = 0; i < bmp.Width; i++) { for (int j = 0; j < bmp.Height; j++) { //获取该点的像素的RGB的颜色 Color color = bmp.GetPixel(i, j); //利用公式计算灰度值 int gray = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11); Color newColor = Color.FromArgb(gray, gray, gray); bmp.SetPixel(i, j, newColor); } } return bmp; } //灰度反转: //把每个像素点的R、G、B三个分量的值0的设为255,255的设为0。 /// <summary> /// 图像灰度反转 /// </summary> /// <param name="bmp"></param> /// <returns></returns> public static Bitmap GrayReverse(Bitmap bmp) { for (int i = 0; i < bmp.Width; i++) { for (int j = 0; j < bmp.Height; j++) { //获取该点的像素的RGB的颜色 Color color = bmp.GetPixel(i, j); Color newColor = Color.FromArgb(255 - color.R, 255 - color.G, 255 - color.B); bmp.SetPixel(i, j, newColor); } } return bmp; } //灰度图像二值化: //在进行了灰度化处理之后,图像中的每个象素只有一个值,那就是象素的灰度值。它的大小决定了象素的亮暗程度。 //为了更加便利的开展下面的图像处理操作,还需要对已经得到的灰度图像做一个二值化处理。 //图像的二值化就是把图像中的象素根据一定的标准分化成两种颜色。在系统中是根据象素的灰度值处理成黑白两种颜色。 //和灰度化相似的,图像的二值化也有很多成熟的算法。它可以采用自适应阀值法,也可以采用给定阀值法。 /// <summary> /// 图像二值化1:取图片的平均灰度作为阈值,低于该值的全都为0,高于该值的全都为255 /// </summary> /// <param name="bmp"></param> /// <returns></returns> public static Bitmap ConvertTo1Bpp1(Bitmap bmp) { int average = 0; for (int i = 0; i < bmp.Width; i++) { for (int j = 0; j < bmp.Height; j++) { Color color = bmp.GetPixel(i, j); average += color.B; } } average = (int)average / (bmp.Width * bmp.Height); for (int i = 0; i < bmp.Width; i++) { for (int j = 0; j < bmp.Height; j++) { //获取该点的像素的RGB的颜色 Color color = bmp.GetPixel(i, j); int value = 255 - color.B; Color newColor = value > average ? Color.FromArgb(0, 0, 0) : Color.FromArgb(255, 255, 255); bmp.SetPixel(i, j, newColor); } } return bmp; } /// <summary> /// 图像二值化2 /// </summary> /// <param name="img"></param> /// <returns></returns> public static Bitmap ConvertTo1Bpp2(Bitmap img) { int w = img.Width; int h = img.Height; Bitmap bmp = new Bitmap(w, h, PixelFormat.Format1bppIndexed); BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed); for (int y = 0; y < h; y++) { byte[] scan = new byte[(w + 7) / 8]; for (int x = 0; x < w; x++) { Color c = img.GetPixel(x, y); if (c.GetBrightness() >= 0.5) scan[x / 8] |= (byte)(0x80 >> (x % 8)); } Marshal.Copy(scan, 0, (IntPtr)((int)data.Scan0 + data.Stride * y), scan.Length); } bmp.UnlockBits(data); return bmp; } } public class ValidateCodeUtils { public static Bitmap CreateImage(int length, out string validateCode) { validateCode = string.Empty; //颜色列表,用于验证码、噪线、噪点 Color[] color = { Color.Black, Color.Purple, Color.Red, Color.Blue, Color.Brown, Color.Navy }; //字体列表,用于验证码 string[] font = { "Times New Roman", "MS Mincho", "Book Antiqua", "Gungsuh", "PMingLiU", "Impact" }; //验证码的字符集,去掉了一些容易混淆的字符 char[] character = { '2', '3', '4', '5', '6', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'W', 'X', 'Y' }; Random rnd = new Random(); //生成验证码字符串 for (int i = 0; i < length; i++) { validateCode += character[rnd.Next(character.Length)]; } Bitmap bmp = new Bitmap(length * 20 + 20, 40); Graphics g = Graphics.FromImage(bmp); g.Clear(Color.White); ////画噪线 //for (int i = 0; i < 10; i++) //{ // int x1 = rnd.Next(20) * rnd.Next(5); // int y1 = rnd.Next(8) * rnd.Next(5); // int x2 = rnd.Next(20) * rnd.Next(5); // int y2 = rnd.Next(8) * rnd.Next(5); // Color clr = color[rnd.Next(color.Length)]; // g.DrawLine(new Pen(clr), x1, y1, x2, y2); //} //画验证码字符串 for (int i = 0; i < validateCode.Length; i++) { string fnt = font[rnd.Next(font.Length)]; Font ft = new Font(fnt, 18); Color clr = color[rnd.Next(color.Length)]; g.DrawString(validateCode[i].ToString(), ft, new SolidBrush(clr), (float)i * 20 + 8, (float)8); } ////画噪点 //for (int i = 0; i < 30; i++) //{ // int x = rnd.Next(bmp.Width); // int y = rnd.Next(bmp.Height); // Color clr = color[rnd.Next(color.Length)]; // bmp.SetPixel(x, y, clr); //} try { return bmp; } finally { //显式释放资源 g.Dispose(); } } } }
效果如下: