Delphi图像处理 -- 中值滤波(灰度分组统计法)

来源:互联网 发布:数据库面试题 编辑:程序博客网 时间:2024/05/29 03:17

阅读提示:

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括文章《Delphi图像处理 -- 数据类型及公用过程》中的ImageData.pas单元

 

    这是《Delphi图像处理 -- 中值滤波》一文的改进版。亦可参见《C++图像处理 -- 中值滤波》。

    中值滤波是图像处理中常用的一种噪声滤波方法。传统的图像中值滤波代码采用排序方法实现,处理速度主要取决于排序算法,但无论什么排序算法,总离不开大量的元素比较、交换或移动,而这些恰好是当前计算机处理的“弱项”(有经验的程序员都知道,计算机数据处理中,比较、转移、交换和频繁的数据移动比直接的算术运算和逻辑运算耗时多了),再加上没有一种好的排序算法能同时适应不同滤波半径的数据排序速度,所以在传统中值滤波实现代码中多使用选择排序、冒泡排序或者直接排序等简单排序算法,高级点的如快速排序法用在中值滤波代码中往往会使处理速度更慢。对于半径为1的中值滤波倒是有一种较好的排序算法,我在《Delphi图像处理 -- 中值滤波》一文中实现过,处理速度还是较快的。

    既然排序过程是图像中值滤波处理的瓶颈,能不能抛开它,用其它手段实现呢?这就是本文要探讨的问题。有朋友可能会有疑问,不排序怎么获取中间值呢,是否采用网上有些文章介绍的近似值来代替?不,本文介绍的方法决不是近似中间值,而是的的确确的“精确”中间值。

    图像中值滤波中的中间值。在统计学中叫做中位数,是平均数指标的一种。平均数指标按统计的复杂程度可分为简单平均数和加权平均数,所谓简单平均数就是对统计总体的每个个体进行累计计算后求得;而加权平均数则是先对统计总体的所有个体进行分组,然后以各个组的大小作为权数后进行累计计算所得。中位数既然是平均数指标的一种,当然也存在两种计算方法。加权中位数和加权算术平均数的区别在于后者是对各个分组用权数相乘进行累积后除以总体个数,而前者只需要对各组权数进行累积后除以2,累积权数大于或等于这个数的组的值就是中位数。传统中值滤波实现代码中采用的排序手段实质就是简单中位平均数统计方法,而本文要介绍的是采用分组加权的方法来获取中位平均数,即中值滤波的中间值。

    采用分组加权统计方法的前提条件是对无限统计总体进行有限的分组,例如,人口年龄统计,就是首先确定各个年龄段,这个年龄段可以是一岁、五岁、十岁等。而图像像素的R、G、B值在0 -- 255之间,正好符合有限分组的前提条件。说到这里,很多人可能已经明白我要表达的意思了:

    1、按R、G、B分别定义一个256大小的灰度统计数据;

    2、将图像像素总体的每个个体像素的R、G、B值进行归类;

    3、确定累积中间值权数的大小;

    4、从数组左端或者右端开始权数累计,累积到大于或等于中间值权数的那个灰度组就是中间值!

    从前面几个步骤不难看出,前3个步骤就是图像处理中灰度统计的分类方法,第4个累积步骤与灰度统计累积计算有2个不同点,一是灰度统计累积的是权数*灰度,而这里只累积灰度;二是灰度统计累积需要全部完成,而中值累积只要达到中间值权数那个组就可以终止。在这4个步骤中,前3个步骤是相当快的(实际上只是第二个步骤),因为其中既无乘除运算,也没有比较转移,更没有元素交换或移动等动作。而制约数据处理速度的瓶颈就在第4步,因为对每个像素都必须分别按R、G、B通道对一共768个元素大小的数据进行累积,哪怕是每个通道除了2个加法运算和唯一的一次比较判断外,没有其它运算,也不需要每次都必须累积到位,但仍然是比较耗时的,不过本文在代码实现过程中,尽可能地作了一些弥补。最后结果同排序方法比起来,可算是相当快捷了,而且滤波半径越大,差距越明显,几十倍的差距绝不是天方夜谭!就算是同我前面所说的半径为1的改进排序算法比较来,大多数情况下,也略有胜出。

procedure MedianValue(var Dest: TImageData; const Source: TImageData;  MedianGray, Size, Stride: Integer);var  buffer: array[0..767] of Integer;  redAddr, greenAddr, blueAddr, delta: Integer;  redOff, greenOff, blueOff: Integer;  width, height, dstOffset, srcOffset: Integer;  median, rowOffset, sizeOffset, mOffset: Integer;asm    push    esi    push    edi    push    ebx    push    ecx    call    _SetCopyRegs    mov     width, ecx    mov     height, edx    mov     dstOffset, ebx    add     eax, 4    mov     srcOffset, eax    pop     ecx    mov     eax, Size    mov     edx, Stride    mov     ebx, edx    imul    edx, eax    shl     eax, 2    mov     sizeOffset, eax    // sizeOffset = Size * 4    sub     ebx, eax    mov     rowOffset, ebx     // rowOffset = Stride - Size * 4    sub     edx, 4    mov     mOffset, edx       // mOffset = Stride * Size - 4    mov     eax, Size    mul     Size    inc     eax    shr     eax, 1    mov     median, eax         // median = (size * size + 1) / 2    mov     blueAddr, 0    mov     greenAddr, 256*4    mov     redAddr, 512*4    cmp     ecx, 128    jb      @@1    mov     blueOff, 256*4    mov     greenOff, 512*4    mov     redOff, 768*4    mov     delta, -4    jmp     @@2@@1:    mov     blueOff, -4    mov     greenOff, 256*4-4    mov     redOff, 512*4-4    mov     delta, 4@@2:    lea     eax, buffer    add     blueAddr, eax    add     greenAddr, eax    add     redAddr, eax    add     blueOff, eax    add     greenOff, eax    add     redOff, eax@@yLoop:    push    width    // RGB灰度统计缓冲区清零    push    edi    mov     edi, blueAddr    mov     ecx, 768    xor     eax, eax    rep     stosd    pop     edi    // 对每行第一个像素临近区域(Size*Size)的RGB进行灰度统计    push    esi    mov     ecx, Size@@statY:    push    ecx    mov     ecx, Size@@statX:    movzx   eax, [esi].TARGBQuad.Blue    movzx   edx, [esi].TARGBQuad.Green    movzx   ebx, [esi].TARGBQuad.Red    inc     dword ptr buffer[eax*4]    inc     dword ptr buffer[edx*4+256*4]    inc     dword ptr buffer[ebx*4+512*4]    add     esi, 4    loop    @@statX    pop     ecx    add     esi, rowOffset    loop    @@statY    pop     esi    jmp     @@setValue@@xLoop:    // 剔除上个坐标点最左边一列像素的RGB的统计值,    // 追加当前像素最右边一列像素RGB的统计值    mov     ecx, Size    mov     ebx, sizeOffset    add     ebx, esi@@subLoop:    movzx   eax, [esi].TARGBQuad.Blue    movzx   edx, [esi].TARGBQuad.Green    dec     dword ptr buffer[eax*4]    dec     dword ptr buffer[edx*4+256*4]    movzx   eax, [esi].TARGBQuad.Red    movzx   edx, [ebx].TARGBQuad.Blue    dec     dword ptr buffer[eax*4+512*4]    inc     dword ptr buffer[edx*4]    movzx   eax, [ebx].TARGBQuad.Green    movzx   edx, [ebx].TARGBQuad.Red    inc     dword ptr buffer[eax*4+256*4]    inc     dword ptr buffer[edx*4+512*4]    add     esi, Stride    add     ebx, Stride    loop    @@subLoop    sub     esi, mOffset@@setValue:    mov     edx, median    mov     ecx, delta    mov     ebx, blueOff    xor     eax, eax@@blueLoop:    add     ebx, ecx    add     eax, [ebx]    cmp     eax, edx    jb      @@blueLoop    sub     ebx, blueAddr    shr     ebx, 2    mov     [edi].TARGBQuad.Blue, bl    mov     ebx, greenOff    xor     eax, eax@@greenLoop:    add     ebx, ecx    add     eax, [ebx]    cmp     eax, edx    jb      @@greenLoop    sub     ebx, greenAddr    shr     ebx, 2    mov     [edi].TARGBQuad.Green, bl    mov     ebx, redOff    xor     eax, eax@@redLoop:    add     ebx, ecx    add     eax, [ebx]    cmp     eax, edx    jb      @@redLoop    sub     ebx, redAddr    shr     ebx, 2    mov     [edi].TARGBQuad.Red, bl    add     edi, 4    dec     width    jnz     @@xLoop@@xEnd:    add     esi, srcOffset    add     edi, dstOffset    pop     width    dec     height    jnz     @@yLoop    pop     ebx    pop     edi    pop     esiend;function GetMedianGray(const Data: TImageData): Integer;var  buffer: array[0..255] of Integer;asm    push    esi    push    edi    push    ebx    mov     esi, eax    lea     edi, buffer    mov     ecx, 256    xor     eax, eax    rep     stosd    mov     ecx, [esi].TImageData.Width    mov     edx, [esi].TImageData.Height    mov     ebx, [esi].TImageData.Stride    mov     edi, [esi].TImageData.Scan0    shr     ecx, 3    shr     edx, 3    sal     ebx, 3    mov     eax, ecx    shl     eax, 3+2    sub     ebx, eax    lea     esi, buffer    push    ebp    mov     ebp, edx    mov     eax, ecx    mul     ebp    lea     eax, [eax+eax*2]    shr     eax, 1            // MedianValue = data.width * data.height * 3 / 2    push    eax@@yLoop:    push    ecx@@xLoop:    movzx   eax, [edi].TARGBQuad.Blue    movzx   edx, [edi].TARGBQuad.Green    inc     dword ptr [esi+eax*4]    inc     dword ptr [esi+edx*4]    movzx   eax, [edi].TARGBQuad.Red    inc     dword ptr [esi+eax*4]    add     edi, 32    dec     ecx    jg      @@xLoop    pop     ecx    add     edi, ebx    dec     ebp    jg      @@yLoop    pop     edx    pop     ebp    mov     edi, esi    add     edi, 256*4    mov     ecx, -4    xor     eax, eax@@stat:    add     edi, ecx    add     eax, [edi]    cmp     eax, edx    jb      @@stat    mov     eax, edi    sub     eax, esi    shr     eax, 2    pop     ebx    pop     edi    pop     esiend;procedure ImageMedianValue(var Data: TImageData; Radius: Integer);var  exp: TImageData;begin  if Radius <= 0 then Radius := 1;  exp := _GetExpandData(Data, Radius);  MedianValue(Data, exp, GetMedianGray(Data), (Radius shl 1) + 1, exp.Stride);  FreeImageData(exp);end;


    实现代码中,在前面所说的4个步骤基础上作了3点完善:

    1、并非对图像的所有像素都按4个步骤进行。除了每行行首像素作了完整的分类统计外,其它像素只是从统计数组中剔除前一像素临近值的最左边一列和追加当前像素临近值的最右边一列,这无疑加快了分类统计速度,而且滤波半径越大,效果越明显。

    2、在对图像进行滤波处理前,对整个图像像素总体的1/8样本求了一次中间值权数,其作用是确定像素分类后的累积方向,如果这个总体中间值权数小于128,从灰度统计数据左边(低端)开始累积,否则则从灰度统计数据右边(高端)开始累积。通过这个总体中间值权数就可以大致确定该图像处理速度的快慢,如果这个值接近两端,处理速度相对就快些,反之如靠近128附近,处理速度则相应会慢些,同样大小的图片,因这个原因,可能造成成倍的差距,但是最坏的情况下,对灰度统计数组累积时的平均值也不会超过一半。

    3、制作滤波备份源图时按滤波半径对图像边缘作了扩充,这样有利于对图像边缘进行滤波处理。有些人在写中值滤波代码时往往对图像边缘略过不作处理,这在小半径滤波范围内还无所谓,但是对于较大半径的滤波处理后,由于未进行滤波处理的边缘较宽。会使得图像很难看。

    正式由于第2点中所说的差距原因,本文也不好给出具体的测试数据,但有一点是肯定的:同等条件下(语言、程序员水平及编译器优化程度等),比传统排序法实现的中值滤波要快不少,滤波半径越大,差距越大!

    最后还是贴个中值滤波测试代码和效果图:前面是源图,后面是50半径中值滤波图:

procedure TForm1.Button3Click(Sender: TObject);var  bmp: TGpBitmap;  g: TGpGraphics;  data: TImageData;begin  bmp := TGpBitmap.Create('..\media\source.jpg');  g := TGpGraphics.Create(Canvas.Handle);  g.DrawImage(bmp, 0, 0);  data := LockGpBitmap(bmp);  ImageMedianValue(data, 50);  UnlockGpBitmap(bmp, data);  g.DrawImage(bmp, data.Width, 0);  g.Free;  bmp.Free;end;

 

    《Delphi图像处理》系列使用GDI+单元下载地址和说明见文章《GDI+ for VCL基础 -- GDI+ 与 VCL》。

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《Delphi图像处理 -- 文章索引》。

 

原创粉丝点击