![]() |
|
||||||||||||||
| | 网站首页 | 数据库教程 | web编程 | 服务器 | 程序设计 | | ||
|
||
|
||||||
| 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(0, 0, 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, 10, 10, Image.Width, Image.Height); // vTickCount := GetTickCount; GdipGaussiabBlur(Image, 3); // Caption := IntToStr(GetTickCount - vTickCount); g.DrawImage(Image, 140, 10, 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, -1, 0, 0, 0, 1, 1, 1); var Image: TGpBitmap; g: TGpGraphics; begin Image := TGpBitmap.Create('....mediamsn.jpg'); g := TGpGraphics.Create(Handle, False); g.DrawImage(Image, 10, 10, Image.Width, Image.Height); GdipConvolution(Image, Ray, 1); g.DrawImage(Image, 140, 10, 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(0, 0, bmp); bmp.Free; end; 可以看出,上面的GaussiabBlur过程只是将前面的GdipGaussiabBlur的TGpBitmap类型参数改为TBitmapData,同时把调用的卷积过程GdipConvolution修改为_GdipConvolution。
本文来源:http://blog.csdn.net/maozefa/archive/2007/08/22/1754011.aspx
|
||||||
| 文章录入:admin 责任编辑:admin | ||||||
| 【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口】 | ||||||
| 网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!) |
| | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 网站地图 | 管理登录 | | |||
|