游戏密保卡图片识别
识别主要步骤
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表示的二值化代码并与特征库中的代码进行比对,匹配对应代码完成识别。
匹配算法可以直接使用代码相等,但这样使得特征库必须完善,否则容易匹配失败。所以一般都会采用字符串相似度匹配,设置阈值,相似度大于阈值的即为同一个字符。相关算法自行百度。