网站公告列表

  没有公告

加入收藏
设为首页
联系站长
您现在的位置: 网络学院 >> 程序设计 >> Delphi编程 >> 文章正文
  GDI+ 在Delphi程序的应用 -- 图像的卷积处理及高斯模糊            【字体:
GDI+ 在Delphi程序的应用 -- 图像的卷积处理及高斯模糊
作者:佚名    文章来源:不详    点击数:    更新时间:2007-9-12    

        在图像的处理过程中,经常要用到卷积模板,如图像锐化、图像平滑、高斯模糊、Hough变换等,为此,本人使用Delphi编写了GDI+图像通用卷积处理过程,代码如下:

// 通用卷积过程。
正在装载数据……

// Data:TGpBitmap的图像数据结构,要求32位ARGB格式
// ConvolMatrix卷积矩阵(必须是奇数矩阵),Nuclear卷积核,必须大于0
procedure _GdipConvolution(Data: TBitmapData; ConvolMatrix: array of Integer; Nuclear: LongWord);

  procedure SetPixelColor;
  asm
    cdq
    idiv    ebx
    test    eax, eax
    jns     @@
1
    xor     eax, eax
    jmp     @@
2
  @@
1:
    cmp     eax, 
255
    jle     @@
2
    mov     eax, 
255
  @@
2:
    mov     [edi], al
    inc     edi
  end;

var
  Radius, sOffset, mOffset, MatrixSize: Integer;
  x, y, w, h: Integer;
  v2, v3: Integer;
  Buf: Pointer;
begin
  MatrixSize :
= Trunc(Sqrt(Length(ConvolMatrix)));
  Radius :
= MatrixSize shr 1;
  sOffset :
= Data.Stride - Data.Width shl 2// 扫描线偏移量
  mOffset := (Data.Stride + 4* Radius;     // 卷积矩阵偏移量
  w := Data.Width - Radius;
  h :
= Data.Height - Radius;
  GetMem(Buf, Data.Height 
* Data.Stride);
  
try
    Move(Data.Scan0
^, Buf^, Data.Height * Data.Stride);
    asm
      push    esi
      push    edi
      push    ebx

      mov     esi, Buf;          
// esi = Buf
      mov     edi, Data.Scan0;   // edi = Data.Scan0
      mov     y, 0               // for (y = 0; y < Data.Height; y ++)
    @yLoop:                      // {
      mov     x, 0               //   for (x = 0; x < Data.Width; x ++)
    @xLoop:                      //   {
      push    esi                //     Save(esi)
      push    edi                //     Save(edi)
      sub     esi, mOffset       //     esi -= (Data.Stride * Radius + Radius * 4)
      mov     edi, ConvolMatrix  //     edi = ConvolMatrix

      xor     edx, edx           
//     v1 = v2 = v3 = 0
      mov     v2, edx
      mov     v3, edx

      mov     eax, y            
//      if (y >= Radius && y < R.Height &&
      cmp     eax, Radius       //          x >= Radius && x < R.Width)
      jl      @BorderPixel      //        goto @CenterPixel
      cmp     eax, h
      jge     @BorderPixel
      mov     eax, x
      cmp     eax, Radius
      jl      @BorderPixel
      cmp     eax, w
      jl      @CenterPixel
    
// 处理边界像素
    @BorderPixel:
      mov     ecx, Radius       
//      for (I = -Radius; I <= Radius; I ++)
      neg     ecx               //      {
      xor     ebx, ebx          //        ebx = 0
    @Loop1:
      mov     eax, y            
//        if (I + y < 0 || I + y >= Data.Heught)
      add     eax, ecx          //           continue
      js      @@3
      cmp     eax, Data.Height
      jge     @@
3               //        Save(esi)
      push    esi               //
      push    ecx
      mov     ecx, Radius
      neg     ecx               
//        for (J = -Radius; J <= Radius; J ++)
    @Loop2:                     //        {
      mov     eax, x            //          if (J + x < 0 || J + x >= Data.Width)
      add     eax, ecx          //             continue
      js      @@2
      cmp     eax, Data.Width
      jge     @@
2
      add     ebx, [edi]        
//          ebx += *edi
      movzx   eax, [esi]
      imul    eax, [edi]
      add     edx, eax          
//          v1 += *esi * *edi
      movzx   eax, [esi + 1]
      imul    eax, [edi]
      add     v2, eax           
//          v2 += *(esi+1) * *edi
      movzx   eax, [esi + 2]
      imul    eax, [edi]
      add     v3, eax           
//          v3 += *(esi+2) * *edi
    @@2:
      add     esi, 
4            //          esi += 4
      add     edi, 4            //          edi ++
      inc     ecx
      cmp     ecx, Radius
      jle     @Loop2            
//        }
      pop     ecx
      pop     esi               
//        Reset(esi)
    @@3:
      add     esi, Data.Stride
//        esi += Data.Stride
      inc     ecx
      cmp     ecx, Radius
      jle     @Loop1            
//      }
      test    ebx, ebx
      jg      @SetPixel
      mov     ebx, 
1
      jmp     @SetPixel
    
// 处理中间像素
    @CenterPixel:
      mov     ecx, MatrixSize   
//      for (I = 0; I < MatrixSize; I ++)
    @Loop3:                     //      {
      push    ecx
      push    esi               
//        Save(esi)
      mov     ecx, MatrixSize   //        for (J = 0; J <= MatrixSize; J ++)
    @Loop4:                     //        {
      mov     ebx, [edi]
      movzx   eax, [esi]
      imul    eax, ebx
      add     edx, eax          
//          v1 += *esi * *edi*
      movzx   eax, [esi + 1]
      imul    eax, ebx
      add     v2, eax           
//          v2 += *(esi+1) * *edi
      movzx   eax, [esi + 2]
      imul    eax, ebx
      add     v3, eax           
//          v3 += *(esi+2) * *edi
      add     esi, 4            //          esi += 4
      add     edi, 4            //          edi ++
      loop    @Loop4            //        }
      pop     esi               //        Reset(esi)
      add     esi, Data.Stride//        esi += Data.Stride
      pop     ecx
      loop    @Loop3            
//      }
      mov     ebx, Nuclear      //      ebx = Nuclear
    
// 写回图像数据
    @SetPixel:
      pop     edi               
//      Reset(edi)
      mov     eax, edx
      call    SetPixelColor     
//      *edi ++ = v1 / Nuclear  (Blue)
      mov     eax, v2
      call    SetPixelColor     
//      *edi ++ = v2 / Nuclear  (Green)
      mov     eax, v3
      call    SetPixelColor     
//      *edi ++ = v3 / Nuclear  (Red)
      inc     edi
      pop     esi               
//      Reset(esi)  esi += 4
      add     esi, 4
      inc     x
      mov     eax, x
      cmp     eax, Data.Width
      jl      @xLoop            
//   }
      add     esi, sOffset
      add     edi, sOffset
      inc     y
      mov     eax, y
      cmp     eax, Data.Height
      jl      @yLoop            
// }

      pop     ebx
      pop     edi
      pop     esi
    end;
  
finally
    FreeMem(Buf);
  end;
end;
// 通用卷积过程。
// bmp:TGpBitmap的图像,ConvolMatrix卷积矩阵(必须是奇数矩阵),Nuclear卷积核,必须大于0
procedure GdipConvolution(Bmp: TGpBitmap; ConvolMatrix: array of Integer; Nuclear: LongWord);
var
  Data: TBitmapData;
begin
  Data :
= Bmp.LockBits(GpRect(00, Bmp.Width, Bmp.Height), [imWrite], pf32bppARGB);
  
try
    _GdipConvolution(Data, ConvolMatrix, Nuclear);
  
finally
    Bmp.UnLockBits(Data);
  end;
end;

 

        上面的代码中,提供了2个卷积处理函数:_GdipConvolution和GdipConvolution,其中的_GdipConvolution过程是具体处理代码,也具有更大的通用性,使用的是GDI+图像的32位ARGB数据结构,便于图像的连续处理,因为要得到GDI+图像的数据结构必须使用TGpBitmap的LockBits和UnLockBits函数,而这2个函数是相当耗时的,在我的P464 2.8G、DDR2667双通道1G内存下,Lock一幅400W的图像耗时近200毫秒!GdipConvolution只是处理Lock图像数据后调用_GdipConvolution过程。

        一般来说,卷积处理相当慢,而通用的卷积过程要顾及的情况更多,因而复杂度也大,速度更是难以提高。先前我用用纯Pascal写的_GdipConvolution过程,在我的机器上使用Q=3、Radius=5的高斯模糊矩阵(即11 * 11的卷积矩阵)处理一幅1000W像素的图像,耗时近19000多毫秒!后来几乎全部改用BASM编写,速度提高了不少,同样条件的处理耗时为6600毫秒,只是先前的1/3!不过1000W像素的图片已经是较大的了,11 * 11的卷积矩阵也够复杂的了。

        从上面代码及注释可以看出,整个_GdipConvolution过程可分为4个部分:

        第一部分是处理准备,包括备份图像数据内存分配、备份图像的复制、图像扫描线和卷积矩阵偏移量的计算以及几个参数计算。

        备份图像是必须的,因为卷积处理一个像素点,需要用到卷积矩阵大小范围的像素数据,如果不用备份图像,前面的像素处理后,后面的像素处理就没法取得原始数据了。

        另外,关于过程的Nuclear参数,一般情况下,是由卷积矩阵各因子值累加起来的,但是有些特殊的矩阵,其因子值累加起来小于1,因而需要显示的给出,该参数不能小于1:等于0没法作除法运算,小于0则整个图像界面成黑色。

        第二部分是图像边界处理,我看过很多卷积过程,大多都将边界像素忽略,这样可提高处理速度和减少代码复杂度。

        忽略图像边界像素,对于小的卷积矩阵问题不大,如3*3矩阵,边界只有一个像素点,但是大的卷积处理就很难看了,如上面我使用的11*11高斯模糊矩阵,有5个像素的边界,如果不处理,可看到很明显的一个边框,而这样大的矩阵也是经常用的,在Photoshop中也就是相当半径3.0以下的高斯模糊,经常用Photoshop的人都知道这只是一个很普通的模糊。

        边界应该怎样处理?方法不一,有的说超出图像的部分用背景像素,也有的使用对角线像素处理(该像素点使用了2次),但我觉得效果都不太好。我采用的方法是忽略超出图像部分的矩阵因子。这个忽略与上面说的忽略是2码事,上面是忽略整个图像边界像素,便于代码简单和速度提高,而这里是忽略卷积矩阵的部分因子,相反比使用背景和对角线像素还复杂:随着部分矩阵因子的忽略,该因子的值必须从卷积核中减掉,因而要逐点的重新计算卷积核,这有可能造成卷积核<=0的情况,这就需要在代码中判断处理,从而增加了代码的复杂度。不过这种处理效果看起来还不错。

        第三部分是中间像素的处理,由于不用判断边界,这部分代码相对较简单,但这部分代码的质量也直接影响卷积处理的速度,如上面提到的11 * 11卷积矩阵,处理一个像素点要作11 * 11 * 3 = 363次矩阵因子与像素点的乘法运算,还得累加起来后除以卷积核才能写回图像。如果是某个专门卷积处理,可以直接写成类似r = (p[x-1, y-1] * m[i-1, j-1] + p[x-1, y] * m[i,j] +  ......) / Z; 的代码,可提高处理速度,而通用处理代码则必须使用2重循环围绕着卷积矩阵分别像素点的R、G、B值计算。

        第四部分只是将累加起来的R、G、B值除以卷积核,控制好R、G、B值的的范围(0-255),然后写入图像数据就行了,其实某些专用卷积处理过程只需要控制R、G、B的上限或者下限就行了,如高斯模糊处理,根本不可能出现负数。

        上面简单介绍了 通用卷积处理过程的实现方法,下面给出一个图像高斯模糊应用卷积处理的代码:

// 高斯模糊。Q为标准差,Radius模糊半径,如果为0,则自动按Q计算
procedure GdipGaussiabBlur(Bmp: TGpBitmap; Q: double; Radius: LongWord);
var
  Gauss: array of Integer;
  x, y, n, z: Integer;
  p: PInteger;
begin
  
if Radius <= 0 then
    Radius :
= Round(Abs(Q)) + 2;
  n :
= Radius shl 1 + 1;
  SetLength(Gauss, n 
* n);
  p :
= @Gauss[0];
  z :
= 0;
  
for x := -Radius to Radius do
    
for y := -Radius to Radius do
    begin
      p
^ := Round(Exp(-(x * x + y * y) / (2.0 * Q * Q)) / (2.0 * PI * Q * Q) * 1000.0);
      Inc(z, p
^);
      Inc(p);
    end;
  GdipConvolution(Bmp, Gauss, z);
end;

        可以看出,这个高斯模糊过程也是通用型的,不需要给出卷积矩阵,而由过程根据标准差Q和矩阵半径Radius参数自动计算,甚至Radius参数也可忽略。

        有的高斯模糊处理过程采用浮点数卷积矩阵,会影响处理速度,采用整数类型运算,相同的算法运算速度会成倍提高,因此本过程将浮点数*1000转换为了整数矩阵。下面是高斯模糊过程测试代码:

procedure TMainForm.Button1Click(Sender: TObject);
var
  Image: TGpBitmap;
  g: TGpGraphics;
//  vTickCount: Longword;
begin
  Image :
= TGpBitmap.Create('....mediamsn.jpg');
  g :
= TGpGraphics.Create(Handle, False);
  g.DrawImage(Image, 
1010, Image.Width, Image.Height);
//  vTickCount := GetTickCount;
  GdipGaussiabBlur(Image, 3);
//  Caption := IntToStr(GetTickCount - vTickCount);
  g.DrawImage(Image, 14010, Image.Width, Image.Height);
  g.Free;
  image.Free;
end;

        下面是采用不同Q值进行高斯模糊的运行结果图:

        左边是原始图像,其后分别是采用Q=1、2、3调用GdipGaussiabBlur过程高斯模糊后的图像,最右边一幅是使用Photoshop8.1cs高斯模糊(Q=3)处理的。

        可以看出,本过程处理的图片和Photoshop处理的图片效果基本接近,同等Q值(3)情况下,Photoshop处理的模糊程度似乎稍稍强一些,可能是算法上的差异,我的过程自动计算的Radius是Q+2,我用Q=3,Radius=6,也就是采用13*13卷积矩阵的模糊效果似乎更接近Photoshop的处理效果,难道Photoshop的Radius为Q * 2?这样一来,采用的卷积矩阵会更大,应该不是,不过,上面的高斯模糊过程中Radius可以任意给定而不通过自动计算,可以反复试试,以模仿Photoshop的处理;另外,当Q=3时图像好像已经模糊的很厉害了,其实,对于高分辨率(200-300)图像,Q=3的高斯模糊程度并没有这么明显,我采用的图片分辨率为72,而且这张图片已经是经过Photoshop过滤、模糊处理后合成的。

        下面给出一个直接调用GdipConvolution过程进行Hough变换的例子作为本文结束:


procedure TMainForm.Button2Click(Sender: TObject);
const
//  Ray: array[0..8] of Integer = (-1, 0, 1, -1, 0, 1, -1, 0, 1);
//  Ray: array[0..8] of Integer = (-1, -1, 0, -1, 0, 1, 0, 1, 1);
    Ray: array[0..8] of Integer = (-1-1-1000111);
var
  Image: TGpBitmap;
  g: TGpGraphics;
begin
  Image :
= TGpBitmap.Create('....mediamsn.jpg');
  g :
= TGpGraphics.Create(Handle, False);
  g.DrawImage(Image, 
1010, Image.Width, Image.Height);
  GdipConvolution(Image, Ray, 
1);
  g.DrawImage(Image, 
14010, Image.Width, Image.Height);
  g.Free;
  image.Free;
end;

运行效果如下图:

        左边是原始图像,后面分别为用不同的Hough矩阵的运行效果,见例子代码的Ray数组。

        由于本人水平有限,错误难免,请各位指正,而且对于一些算法提出好的建议,如图像边界像素的处理,高斯模糊中Q或者Radius参数自动计算问题等等。我的Email:maozefa@hotmail.com。

        再次说明,本人例子中使用的GDI+代码与网上流通的有些区别。

        另外,本人发现有很多网站转发了本人的文章,这本是好事,本人热烈欢迎,可是大多没写明文章作者和转载地址,更有甚者,干脆把作者改为了他自己的署名,这就似乎有些欠光明磊落了。

        补充:文章发布后,有位朋友来信问上面的过程能否直接处理Delphi的TBitmap,也就是说,不改动上面的代码,也不使用GDI+,直接处理TBitmap类型。答案是肯定的,不过有2点必须注意,一是_GdipConvolution过程使用的参数是GDI+的图形数据类型TBitmapData,应该填写该纪录;二是TBitmap的扫描数据是倒序的,即第一行图像数据在扫描地址中的最后一行,为此,对于上下不对称的卷积矩阵必须交换过来,才能得到想要的效果,如上面的Hough变换例子中的:

Ray: array[0..8] of Integer = (-1, -1, -1, 0, 0, 0, 1, 1, 1);

应该改成:

Ray: array[0..8] of Integer = (1, 1, 1, 0, 0, 0, -1, -1, -1);

至于高斯模糊矩阵,由于是对称的,所以可以直接使用,请看下面TBitmap的高斯模糊例子:


procedure GaussiabBlur(data: TBitmapData; Q: 
double; Radius: LongWord = 0);
var
  Gauss: array of Integer;
  x, y, n, z: Integer;
  p: PInteger;
begin
  
if Radius <= 0 then
    Radius :
= Round(Abs(Q)) + 2;
  n :
= Radius shl 1 + 1;
  SetLength(Gauss, n 
* n);
  p :
= @Gauss[0];
  z :
= 0;
  
for x := -Radius to Radius do
    
for y := -Radius to Radius do
    begin
      p
^ := Round(Exp(-(x * x + y * y) / (2.0 * Q * Q)) / (2.0 * PI * Q * Q) * 1000.0);
      Inc(z, p
^);
      Inc(p);
    end;
  _GdipConvolution(data, Gauss, z);
end;

procedure TMainForm.Button2Click(Sender: TObject);
var
  bmp: TBitmap;
  data: TBitmapData;
begin
  bmp :
= TBitmap.Create;
  bmp.LoadFromFile(
'....media?41001.bmp');
  bmp.PixelFormat :
= pf32bit;
  data.Width :
= bmp.Width;
  data.Height :
= bmp.Height;
  data.Scan0 :
= bmp.ScanLine[data.Height - 1];
  data.Stride :
= (((32 * data.Width) + 31) and $ffffffe0) shr 3;
  GaussiabBlur(data, 
3);
  Canvas.Draw(
00, bmp);
  bmp.Free;
end;

        可以看出,上面的GaussiabBlur过程只是将前面的GdipGaussiabBlur的TGpBitmap类型参数改为TBitmapData,同时把调用的卷积过程GdipConvolution修改为_GdipConvolution。

 




本文来源:http://blog.csdn.net/maozefa/archive/2007/08/22/1754011.aspx
站内文章搜索 高级搜索
文章录入:admin    责任编辑:admin 
  • 上一篇文章:

  • 下一篇文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
    最新热点 最新推荐 相关文章
     在delphi中使用xml文档有…
     初探delphi 7 中的插件编…
     delphi 2006(dexter) & …
     获得windows的版本信息。
     “序列号输入助手”源代…
     rs232串口通讯模块
     ado方式下判断数据表是否…
  • Java的网络编程(TCP/IP)

  • Ant入门-配置和使用     选…

  • 浅析Spring框架下PropertyPl…

  • jsp重定向forward和sendRedi…

  • MVP——Model-Viewer-Presen…

  • C++ Object Model

  • Solaris10下,使用SunStudio…

  • 内存管理内幕--Jonathan Bar…

  • constructor and destructor

  • delegate C#关键字 (委托类型…

  •   网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)
    网络学院©2007 www.23book.net
    为您提供web编程,vb编程,vc编程,服务器架设管理,数据库设计等方面的知识 站长:David