一、文字切割
字迹在经过初始处理后,被制作成黑白二值图保存。这个步骤比较简单,可以使用PhotoShop等工具进行处理。剩下的工作就是从字迹中将一个一个的汉字摘出来,用来制作纹理图片。我采用的方法是通过字切割的方式,当然也有一些文献采用另外的较简单的方式进行处理(比如只是去掉行间、文字间的空白)。
1、行切割
对于得到的黑白二值图进行统计处理。统计黑白点阵图中每行中黑色像素的数量,得到一统计向量,该向量中极小值所对应的位置就应当是分行的地方。代码相对简单,不再赘述。
2、字切割
行切割完成后,就需要将每行中的文字切割下来,这次是对每行文字的黑色像素进行纵向统计,黑色像素数量极小的地方就有可能是分字的地方。之所以说“可能”是因为有很多汉字是左右结构(比如“朋”字),在两个“月”字中间的区域对应的黑色像素数量也很小,甚至是0,因此需要采取某种偏旁部首合并策略,将可能的左右结构汉字重新合并到一起,作为一个汉字处理。另外在进行字切割时还应当将可能是标点符号的字符去掉,防止标点符号影响字迹的识别。
我的程序中首先对初步字切割的结果进行判别,剔除标点符号。标点符号往往宽度较小,同时像素统计值比较少。然后,采用自右向左的顺序进行偏旁部首合并(之所以选择自右向左是因为汉字中左窄右宽的文字比重较大,自右向左合并的成功性较大),如果经尝试合并后的文字宽度与字迹中平均文字宽度差不多的化,就进行合并处理。
当然有时候需要进行文字切分,当发现某个切分结果过宽时,就可能预示两个汉字挨得太紧,需要进一步切分开。
经过一番文字切分、偏旁部首合并策略的的调整,我的程序能够较成功的将字迹中的文字逐一切割下来。切割结果如下图所示:
3、关键代码
在文字切割部分中用到的关键代码主要是黑白图像的读取代码。我程序中主要使用的是PixelFormat.Format1bppIndexed格式的PNG图像,一个二进制位对应一个像素点,这样比PixelFormat.Format1bppIndexed格式的BMP文件要节省磁盘空间。有关此类图像的处理技巧,可以参考:《Using the LockBits method to access image data》。这里将部分行切分时用到的代码放上来:
#region SetIndexedPixel and GetIndexedPixel for Format1bppIndexed png file protected void SetIndexedPixel(int x,int y,BitmapData bmd, bool pixel) { int index=y*bmd.Stride+(x>>3); byte p=Marshal.ReadByte(bmd.Scan0,index); byte mask=(byte)(0x80>>(x&0x7)); if(pixel) p |=mask; else p &=(byte)(mask^0xff); Marshal.WriteByte(bmd.Scan0,index,p); } private bool GetIndexedPixel(int x, int y, BitmapData bmd) { int index = y * bmd.Stride + (x>>3); byte p = Marshal.ReadByte(bmd.Scan0,index); byte mask=(byte)(0x80>>(x&0x7)); if(((int)(p & mask))== 0) return true; else return false; } #endregion private void CalcBlackDotsOfLine() { Bitmap bm = new Bitmap(this.ImageFileName); BlackDotsOfLine = new int[bm.Height]; BitmapData bmdn=bm.LockBits(new Rectangle(0,0,bm.Width,bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed); for(int y=0; y < bm.Height; y++) for(int x=0; x < bm.Width; x++) if(this.GetIndexedPixel(x, y, bmdn)) BlackDotsOfLine[y]++; bm.UnlockBits(bmdn); }
之所以使用LockBits方法是因为这样处理速度比较快,如果速度并不是很重要的因素的话,我建议使用Bitmap对象的SetPixel方法和GetPixel方法。
二、纹理制作
1、纹理制作
文字切割完成后,就需要制作纹理图像了。我这里主要参考了“刘宏 李锦涛 崔国勤 唐胜,基于SVM和纹理的笔迹鉴别方法,计算机辅助设计与图形学学报,Vol15(12),pp1479-1484”一文,将文字缩放至16×16点阵大小,并拼接成384×384规格的图片,每幅图片可以切割成9个128×128大小的图片作为训练样本。待测样本制作成256×256大小,可以切割成4个128×128大小的纹理图片。下面是一张训练样本图片和两张待测样本图片:
训练样本(384×384大小)
待测样本(256×256大小)
纹理制作好后就可以使用Gabor变换程序进行变换提取笔迹特征了。Gabor变换将在下一部分再做介绍。
2、关键代码
纹理制作过程中的关键代码主要是图像的缩放操作,将切割下来的文字缩放成16×16点阵并且进行拼接。这方面的资料很多,包括博客园在内的很多网站在对大图片进行显示之前都要进行尺寸处理。我这里将我程序中的关键代码放上来(略经删截):
private void BeginProcess() { Image imgSrc = this.spbSrc.PicBox.Image, imgDest; Rectangle destRect, srcRect; if(this.cboSize.SelectedIndex == 0) imgDest = new Bitmap(384, 384, PixelFormat.Format24bppRgb); else imgDest = new Bitmap(256, 256, PixelFormat.Format24bppRgb); Graphics g = Graphics.FromImage(imgDest); g.CompositingQuality = CompositingQuality.HighSpeed; g.SmoothingMode = SmoothingMode.HighSpeed; g.InterpolationMode = InterpolationMode.Bilinear; int current = 0; for(int y=0; y < imgDest.Height; y+=CharSize) for(int x = 0; x < imgDest.Width; x+=CharSize) { destRect = new Rectangle(x, y, CharSize, CharSize); srcRect = (Rectangle)CharsRectangle[current]; g.DrawImage(imgSrc, destRect, srcRect, GraphicsUnit.Pixel); } }
其中CharSize是一常量,值为16。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步