识别主要步骤
1.图像预处理。包括确认图片有效区域,灰度化,二值化。
2.字符分割。即将识别信息最小化。由于密保卡图片文字宽度固定且无粘连,只需要使用固定宽度切割。
3.对分割后的信息提取特征,建立特征库
4.提取特征和特征库样本进行匹配,输出识别结果
首先看下密保卡图片
1、减少识别区域。由于密保卡有效区域固定,故将有效区域直接截取出来。
2、图片灰度化
图片灰度算法有平均值法,分量法,最大值法,加权平均法,本例用的加权平均法
public static Bitmap CorlorGray(Bitmap bmp){//位图矩形System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);//以可读写方式锁定全部位图像素System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);//得到首地址IntPtr ptr = bmpData.Scan0;//定义被锁定的数组大小,由位图数据与未用空间组成int bytes = bmpData.Stride * bmpData.Height;byte[] rgbValues = new byte[bytes];//复制被锁定的位图像素值到数组中System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);//灰度化double colorTemp = 0;for (int i = 0; i < bmpData.Height; i++){//只处理每行图像像素数据,舍弃未用空间for (int j = 0; j < bmpData.Width * 3; j += 3){colorTemp = rgbValues[i * bmpData.Stride + j + 2] * 0.299 + rgbValues[i * bmpData.Stride + j + 1] * 0.587 + rgbValues[i * bmpData.Stride + j] * 0.114;rgbValues[i * bmpData.Stride + j] = rgbValues[i * bmpData.Stride + j + 1] = rgbValues[i * bmpData.Stride + j + 2] = (byte)colorTemp;}}//把数组复位回位图System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);//解锁位图 bmp.UnlockBits(bmpData);return bmp;}
3、图片二值化
最常见的二值处理方法是计算像素的平均值K,扫描图像的每个像素值如像素值大于K
像素值设为255(白色),值小于等于K像素值设为0(黑色)
#region 阈值法二值化 public static Bitmap Threshoding(Bitmap b, byte threshold){int width = b.Width;int height = b.Height;BitmapData data = b.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);unsafe{byte* p = (byte*)data.Scan0;int offset = data.Stride - width * 4;byte R, G, B, gray;for (int y = 0; y < height; y++){for (int x = 0; x < width; x++){R = p[2];G = p[1];B = p[0];gray = (byte)((R * 19595 + G * 38469 + B * 7472) >> 16);if (gray >= threshold){p[0] = p[1] = p[2] = 255;}else{p[0] = p[1] = p[2] = 0;}p += 4;}p += offset;}b.UnlockBits(data);return b;}}#endregion
4、字符分割
由于密保卡每个小方块大小固定,如果识别A1,那么只需截取一个37* 20的图片块来进行处理。
循环遍历图片Y轴每个像素点,找到字符之间像素全为白色的X坐标集合,从而将图片切割。
public static List<Bitmap> Cut(Bitmap bitmap){List<ContentRectangle> lst = new List<ContentRectangle>();int width = bitmap.Width;int height = bitmap.Height;int[] xarray = new int[width];for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){int r = bitmap.GetPixel(x, y).R;if (r == 0)xarray[x] += 1;}}int temp = 0;int[] yarray = new int[height];for (int i = 0; i < width; i++){if (xarray[i] > 0 && i != 0 && xarray[i - 1] <= 0){temp = i;}if (xarray[i] > 0 && i < width - 1 && xarray[i + 1] <= 0){for (int j = 0; j < height; j++){for (int x = temp + 1; x < i + 1; x++){int r = bitmap.GetPixel(x, j).R;if (r == 0)yarray[j] += 1;}}}int ttmp = 0;for (int y = 0; y < height; y++){if (yarray[y] != 0){ttmp = y;break;}}for (int x = height - 1; x > -1; x--){if (yarray[x] != 0){ContentRectangle rectangle = new ContentRectangle();rectangle.X = temp;rectangle.Width = i + 1 - temp;rectangle.Y = ttmp;rectangle.Height = x + 1 - ttmp;lst.Add(rectangle);yarray = new int[height];break;}}}List<Bitmap> lstbmp = new List<Bitmap>();foreach (ContentRectangle rect in lst){var tempbmp = bitmap.Clone(new System.Drawing.Rectangle(rect.X, rect.Y, rect.Width, rect.Height), bitmap.PixelFormat);lstbmp.Add(tempbmp);}return lstbmp;}
5、建立特征库
字符切割后得到了类似以下图片。人眼可以直观的辨识出来,但机器却是不认识的。所以需要建立特征库,以便机器比对识别。
/// <summary>/// 获取数字对应的二值化代码/// </summary>/// <param name="bitmap"></param>/// <returns></returns>public static string GetCodebybitmap(Bitmap bitmap){StringBuilder code = new StringBuilder();for (int i = 0; i < bitmap.Width; i++){for (int j = 0; j < bitmap.Height; j++){int r = bitmap.GetPixel(i, j).R;code.Append(r > 127 ? "1" : "0");}}return code.ToString();}
将图片7像素点逐个扫描转换为0,、1表示的二值化字符串与数字7进行关联存储,建立特征库。
6、识别
按上述步骤进行图片处理后取得图片的0、1表示的二值化代码并与特征库中的代码进行比对,匹配对应代码完成识别。
匹配算法可以直接使用代码相等,但这样使得特征库必须完善,否则容易匹配失败。所以一般都会采用字符串相似度匹配,设置阈值,相似度大于阈值的即为同一个字符。相关算法自行百度。