First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 2960 评论 :: 298 Trackbacks

一、文字切割

字迹在经过初始处理后,被制作成黑白二值图保存。这个步骤比较简单,可以使用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。

posted on 2006-02-05 11:22 吕震宇 阅读(3166) 评论(7)  编辑 收藏 所属分类: 笔迹鉴别

评论

#1楼  2006-02-05 12:05 Paker Liu      
虽然现在看起来一头雾水,但还是认为有价值。
有时间慢慢研究。
  回复  引用  查看    

#2楼  2006-12-16 09:33 pree [未注册用户]
谢谢!! 很好的东东!
  回复  引用    

#3楼  2007-06-25 21:58 Qq [未注册用户]
1楼的兄弟真是非常的逗阿,哈哈
  回复  引用    

#4楼  2007-07-29 11:57 多多 [未注册用户]
你好,我有个问题想问一下,
你的代码中 BitmapData bmdn=bm.LockBits(new rectangle(0,0,bm.Width,bm.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format1bppIndexed);
是对整幅图像进行锁定,然后对整幅图像进行处理是吗?
如果我只想对一部分图像进行处理,不如起始坐标是(100,50),宽为200,高为150 的部分处理,该怎么设置??
我原以为只用对改动rectangle(100,50,200,150)就可以了,不过我运行的结果并不是对rectangle设置的区域进行处理了的。所以想向您请教一下。
我的QQ:48396104
非常感谢
  回复  引用    

#5楼  2007-07-29 12:08 多多 [未注册用户]
我的图像是32位RGB图,用的是vb。net做的,以下是我求反色的代码,希望哪位大虾指正一下:
Dim pBitmap As New Bitmap(Me.PictureBox1.Image)
Dim width As Integer = pBitmap.Width
Dim height As Integer = pBitmap.Height
Dim rect As Rectangle = New Rectangle(100, 50, 200, 150)
Dim pbitmapData As Imaging.BitmapData = pBitmap.LockBits(rect, Drawing.Imaging.ImageLockMode.ReadWrite, pBitmap.PixelFormat)

Dim stride As Integer = pbitmapData.Stride
Dim RawData As IntPtr = pbitmapData.Scan0

Dim i As Integer
Dim l As Integer = pbitmapData.Width * pbitmapData.Height * 4 - 1
Dim b(l) As Byte
Dim m As Byte = 255
Dim aa As Integer
Dim bb As Integer
bb = 1

System.Runtime.InteropServices.Marshal.Copy(RawData, b, 0, l) '讲从rawData之后l字节的数据保存到b数组中

For i = 0 To l
If Not (bb Mod 4 = 0) Then '32位图四个字节存储一个像素。的存储为bgra bgra bgra.....等b为蓝色,g为绿,r为红,
aa = b(i)
b(i) = m - b(i)
End If
bb = bb + 1

Next
System.Runtime.InteropServices.Marshal.Copy(b, 0, RawData, l) '将b数组中的数据存入从rawdata之后到l个字节的内存中
pBitmap.UnlockBits(pbitmapData) '从内存中释放bitmap
pBitmap.Save("c:/111111111a.bmp")
Me.PictureBox1.Image = pBitmap

  回复  引用    

#6楼  2007-08-19 10:10 任我赢 [未注册用户]
丧尽天良阿!!!!!无耻啊!!!这个地方让我进来之后,居然不知道编程为何物,,以前多年累积似乎并未存在过。。郁闷,郁闷。。。待我闭关修炼。。。
  回复  引用    

#7楼  2008-06-14 10:43 笔迹 [未注册用户]
很好的东西啊
我怎么没早点看到呢
谢谢哈
  回复  引用    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-02-06 11:04 编辑过


相关链接:

历史上的今天:
2005-02-05 华容道与数据结构 (6)
2005-02-05 令人费解的效率问题