C#简单数字验证码解析
这几天都在研究验证码解析,写了一个DEMO,对于纯数字且生成位置变化频率较低的图片识别效果还算满意,准确率在80%以上,更高级别的验证码还有待研究。
具体实现思路:以4位数字的验证码为例
1、人工将验证码的4位数字每位对应的代码存入数据库中,每位存入0-9对应的代码,每个数可以多存这样可以提高识别率;
2、获取验证码以后,对其进行去背景、灰度处理、去噪点处理、分片处理以后生成每位数字对应的代码;
3、去背景色,这一步的目的是把验证码和背景颜色区别开来。
去除背景的算法,依赖于验证码图像的特征:
1、首先需要知道背景色
最简单的方法就是把最左上角的点的量化值作为背景色。
优化一点可以取图像矩形的topleft,topright,bottomleft,bottomright这4个点比对。
或者取更边界上更多点比对。2、扫描m*n图像矩形,从每个具有背景色的点出发找到整个连接块,设置matrix[i,j]=0。
这是一个很简单的带剪枝的DFS算法,很快就能把背景色去除,本例用第二种。
4、去噪声:这一步要取出图像上的孤立点。这些孤立点被认为是噪声。
孤立点的定义:某个点,周围没有与该点等值的点。
或者某个连接块,该连接块的元素的个数小于某个给定值K, 把元素个数很小的连接块也定义为孤立点,有助于去处噪声。
去噪声算法:参照去背景算法。
5、图像锐化:图像锐化的目的是增强边界。这一步是可选的。看验证码的情况,这一步可以跳过。
6、图片有效区域截取:这个操作是将图片除验证码字符以外的边框去掉,只留下验证码字符图片,这样保证分片的准确性。
7、图片分片处理,这个处理是将整个图片分割成单个字符图片。
1、连接块法:找到矩阵中除背景外所有的连接块,把每个连接块作为一个子图。
不适用于子图存在断裂的情形。
实现:带剪枝的DFS。2、子图分割:如果验证码是定长的,且字符之间等距。
可以用一个简单的垂直分割把子图提取出来。
这种方法适用于子图断裂,用连接块法提取失败的情形。 本例用第二种。3、有某些类型的验证码用上面2个方法都难以提取子图
8、拿分片图像生成的代码(eg:000111110011…..)与DB中已有代码相比较,取得相似度最高的即要验证码的字符;
注:如果DB的样本越多,识别的准确率也越高,但是速度会相应变慢
主要实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /// <summary> /// 根据RGB,计算灰度值 /// </summary> /// <param name="posClr">Color值</param> /// <returns>灰度值,整型</returns> private int GetGrayNumColor(System.Drawing.Color posClr) { return (posClr.R * 19595 + posClr.G * 38469 + posClr.B * 7472) >> 16; } /// <summary> /// 灰度转换,逐点方式 /// </summary> public Bitmap GrayByPixels() { for ( int i = 0; i < bmpobj.Height; i++) { for ( int j = 0; j < bmpobj.Width; j++) { int tmpValue = GetGrayNumColor(bmpobj.GetPixel(j, i)); bmpobj.SetPixel(j, i, Color.FromArgb(tmpValue, tmpValue, tmpValue)); } } return bmpobj; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | /// <summary> /// 得到图片有效区域,使切分图片更加精确 /// </summary> /// <param name="_bitmap"></param> /// <returns></returns> private Bitmap CutMap(Bitmap _bitmap) { Rectangle rg = new Rectangle( int .Parse(updX.Value.ToString()), int .Parse(updY.Value.ToString()), _bitmap.Width - int .Parse(updW.Value.ToString()), _bitmap.Height - int .Parse(updH.Value.ToString())); //Rectangle rg = new Rectangle(1, 0, _bitmap.Width - 10, _bitmap.Height ); Bitmap bitmap = _bitmap.Clone(rg, _bitmap.PixelFormat); return bitmap; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /// <summary> /// 切分图片 /// </summary> /// <param name="_bitmap"></param> /// <param name="row"></param> /// <param name="col"></param> /// <returns></returns> private Bitmap[] SplitImg(Bitmap _bitmap, int row, int col) { int singW = _bitmap.Width / row; int singH = _bitmap.Height / col; Bitmap[] arrmap = new Bitmap[row * col]; Rectangle rect; for ( int i = 0; i < col; i++) { for ( int j = 0; j < row; j++) { rect = new Rectangle(j * singW, i * singH, singW, singH); arrmap[i * row + j] = _bitmap.Clone(rect, _bitmap.PixelFormat); } } return arrmap; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /// <summary> /// 获取数字对应的二值化代码 /// </summary> /// <param name="_bitmap"></param> /// <param name="p"></param> /// <returns></returns> private string GetCodebybitmap(Bitmap _bitmap, Panel p) { StringBuilder code = new StringBuilder(); Graphics g = p.CreateGraphics(); for ( int i = 0; i < _bitmap.Width; i++) { for ( int j = 0; j < _bitmap.Height; j++) { int r = _bitmap.GetPixel(i, j).R; if (r < 100) //常用的是灰度128 { code.Append( "1" ); g.DrawString( "-" , new Font( "宋体" , 12), new SolidBrush(Color.Blue), new Rectangle(i * 5, j * 5, 12, 12)); } else { code.Append( "0" ); } } } return code.ToString(); } |
效果:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!