关于国税局发票验证码识别,应该是大多数从事发票查验的人员比较头疼的问题,但实际上发票验证码识别问题严格而言较为简单。
一、背景
首先,需要了解清楚国税局的发票验证码构成,如下图,正常而言都是中文、数字和英文,其中数字和英文不包括0和O,中文为常见字。
一般而言,在请求后会提示让你输入红色文字、黄色文字、蓝色文字、全部文字(黑色文字)。所有字符输入长度在1-6个。
二、方法
本文使用ddddocr框架即可实现识别,但是根本在于样本的处理以及样本的生成,这是较为关键的步骤,但实际而言训练的结果好坏取决于样本的好坏,最好的办法是人工获取官网的样本,但由于中文字符的存在,样本数量一般而言至少要达到百万以上,才能有比较好的识别效果。本文采用新的生成算法,拟合官网的样本,在此基础上使用生成的样本和真实样本可以实现单次识别准确率达到95.2%(综合识别率,含中文)。本文使用的生成算法采用C#实现。
三、生成方法
1、定义颜色:
readonly Color[] COLORS = { Color.Red, Color.Black, Color.Blue, Color.Yellow };
2、绘制验证码背景线条:
/// <summary>
/// 绘制杂线
/// </summary>
/// <param name="image"></param>
/// <param name="color"></param>
/// <returns></returns>
public Image create_Noise_Line(Image image, Color color)
{
int w = image.Width;
int h = image.Height;
Random random = new Random();
int num =2;
int x1, x2, y1, y2;
Graphics g = Graphics.FromImage(image);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
Brush brush = new SolidBrush(color);
Pen pen = new Pen(brush, 0.005f);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
while (num > 0)
{
x1 = random.Next(0, w);
y1 = random.Next(0, h);
x2 = random.Next(0, w);
y2 = random.Next(0, h);
g.DrawLine(pen, new Point(x1,y1), new Point(x2, y2));
num--;
}
g.Dispose();
return image;
}
3、绘制杂点,由于C#gdi限制这里选择是绘制小图,一个像素的:
public Image create_Noise_Dots(Image image, int number = 50)
{
int w = image.Width;
int h = image.Height;
Random random = new Random();
Graphics g = Graphics.FromImage(image);
g.SmoothingMode = SmoothingMode.HighSpeed;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit;
int x1, y1;
Bitmap p = new Bitmap(1, 1);
Graphics g1 = Graphics.FromImage(p);
while (number > 0)
{
x1 = random.Next(0, w);
y1 = random.Next(0, h);
g1.Clear(RanDomColor(0, 255));
g.DrawImage(p, new Point(x1, y1));
number--;
}
g.Dispose();
return image;
}
4、创建双曲线,这里创建sin曲线,实际上cos也可以:
public Image CreateSin(Image img,float s1=2f,float s2=1.0f,float s3=1.0f)
{
Graphics g = Graphics.FromImage(img);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// g.Clear(RanDomColor(100, 255));
// Pen pen1 = new Pen(RanDomColor());
Brush brush = new SolidBrush(RanDomColor());
float y = 50, y1, x1, x2;
Random random = new Random();
List<PointF> pointFs = new List<PointF>();
List<PointF> pointFss = new List<PointF>();
double t1 = random.Next(50, 65);
double t2 = random.Next(100, 240);
double t3 = random.Next(50, 65);
pointFss.Add(new PointF(0, 0));
pointFss.Add(new PointF(0, 120));
for (int x = 1; x < 360; x++)//画正弦曲线
{
x1 = (float)(x - t2);//t2越大,越往左
x2 = (float)(x - t2);
y1 = (float)(t1 + s1 * Math.Sin((3.1415926549382 / 180.0) * (x) * s2)) * s3;//t1越大越往下
// g.DrawLine(pen1, x1, y, x2, y1);
y = y1;
pointFs.Add(new PointF(x1, y));//下曲线
pointFss.Add(new PointF(x1, (float)(y - t3)));//上曲线
}
if (FindIsHaveEnd(pointFss))
{
pointFss.Add(new PointF(120, 0));
}
g.FillPolygon(brush, pointFs.ToArray());
g.FillPolygon(brush, pointFss.ToArray());
g.Dispose();
return img;
}
5、我们需要根据输入的真实样本图,来自动调参,匹配出最优的参数,需要注意的是可能涉及的指标,这里给出一个相似度算法:
public float ImageSimilar(Bitmap map1, Bitmap map2, bool jc)
{
try
{
int width = map1.Width;
int height = map1.Height;
long argv = 0;
Bitmap imag = new Bitmap(width, height);
for (int i = 0; i < map1.Width; i++)
{
for (int j = 0; j < map1.Height; j++)
{
Color color1 = map1.GetPixel(i, j);
Color color2 = map2.GetPixel(i, j);
if (color1.B == color2.B)
{
imag.SetPixel(i, j, Color.FromArgb(0, 0, 0));
}
else
{
imag.SetPixel(i, j, Color.FromArgb(1, 1, 1));
argv++;
}
}
}
long xsh = width * height;
float butongb = (float)argv / xsh;
return 1 - butongb;
}
catch
{
return 0.0f;
}
}
6、就是通过人工对样本进行分析,估计样本的大小范围和颜色点的个数等,进行循环判定,建议将输入的图片导出为数组,不在对文件进行操作。其中双曲线可以忽略,因为在本算法中背景颜色也会被处理,有和没有没啥区别。
四、示例
以4smpwu为例,下图为生成的样本,不包括双曲线。
这是未经迭代更新的,可以发现已经很接近了,余下的则是字体大小和背景颜色。
五、训练模型
这里我生成了50万张样本,并获取了100万张真实样本,做好了标签后进行了预处理。因为在本文中,我采用的是分离4中颜色的方式,这样就是一个模型即可。而且不用修改现有框架,直接就能用。
训练前的准备工作,需要对图片的背景进行预处理,去掉杂色:
处理后:
处理后:
同时要设定最大阈值,防止过滤时特征丢失。
经过上面的处理后:
使用ddddocr无脑训练即可。很快就能达到想要的结果 :
一般建议训练25轮左右,标签样本在840左右基本就能在实际识别时识别达到95 了。