Delphi图像处理 -- 灰度化和灰度直方图

阅读提示:

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

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

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

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

 

    常用图像的灰度化方法有最大值法、平均值法和YUV法。最大值法是取像素R、G、B分量中的最大值为灰度值;平均值法是以像素R、G、B分量的平均值作为灰度值;而YUV法则是根据YUV颜色空间原理,以亮度值Y来作为像素的灰度值,其计算公式为:

    Y = 0.299 * R + 0.587 * G + 0.114 * B

    本文的图像灰度化过程以这个公式来计算像素灰度值。

    图像灰度化和灰度直方图统计过程代码如下:

type
  // 256色灰度统计数组,每个元素表示该下标对应的颜色个数
  PGrayArray = ^TGrayArray;
  TGrayArray = array[0..255] of LongWord;
  // 灰度信息结构
  PImageGrayInfo = ^TImageGrayInfo;
  TImageGrayInfo = packed record
    Grays: TGrayArray;          // 灰度数组
    Total: int64;               // 总的灰度值
    Count: LongWord;            // 总的像素点数
    MaxValue: LongWord;         // 像素点最多的灰度值
    MinValue: LongWord;         // 像素点最少的灰度值
    Average: LongWord;          // 平均灰度值(Total / Count)
  end;

  // 通道灰度信息结构
  PChannelsInfo = ^TChannelsInfo;
  TChannelsInfo = packed record
    RGB: TImageGrayInfo;
    Red: TImageGrayInfo;
    Green: TImageGrayInfo;
    Blue: TImageGrayInfo;
  end;

const
  GRAY_MMX: TMMType = (3735, 19235, 9798, 0); // [0.114,0.587,0.229] * 32768

// 图像数据Source灰度拷贝到Dest
procedure ImageGray(var Dest: TImageData; const Source: TImageData); overload;
asm
    push      esi
    push      edi
    push      ebx
    call      _SetCopyRegs
    pxor      xmm7, xmm7
    pcmpeqd   xmm6, xmm6
    psllq     xmm6, 48
    movq      xmm2, GRAY_MMX
    movlhps   xmm2, xmm2
    test      ecx, 1
    jz        @@yLoop
    add       ebx, 4
@@yLoop:
    push      ecx
    shr       ecx, 1
    jz        @@1
@@xLoop:
    movq      xmm0, [esi]
    punpcklbw xmm0, xmm7
    movaps    xmm3, xmm0
    pand      xmm3, xmm6
    pmaddwd   xmm0, xmm2
    movaps    xmm1, xmm0
    psrlq     xmm1, 32
    paddd     xmm0, xmm1
    psrlq     xmm0, 15
    pshuflw   xmm0, xmm0, 11000000b
    pshufhw   xmm0, xmm0, 11000000b
    por       xmm0, xmm3
    packuswb  xmm0, xmm7
    movq      [edi], xmm0
    add       esi, 8
    add       edi, 8
    loop      @@xLoop
@@1:
    pop       ecx
    test      ecx, 1
    jz        @@next
    movd      xmm0, [esi]
    punpcklbw xmm0, xmm7
    movaps    xmm3, xmm0
    pand      xmm3, xmm6
    pmaddwd   xmm0, xmm2
    movaps    xmm1, xmm0
    psrlq     xmm1, 32
    paddd     xmm0, xmm1
    psrld     xmm0, 15
    pshuflw   xmm0, xmm0, 11000000b
    por       xmm0, xmm3
    packuswb  xmm0, xmm7
    movd      [edi], xmm0
@@next:
    add       esi, eax
    add       edi, ebx
    dec       edx
    jnz       @@yLoop
    pop       ebx
    pop       edi
    pop       esi
end;

// 图像数据灰度化
procedure ImageGray(var Data: TImageData); overload;
begin
  ImageGray(Data, Data);
end;

// 获取图像灰度信息
function ImageGetGrayInfo(const Data: TImageData; var GrayInfo: TImageGrayInfo;
  isGrayImage: Boolean = False): Integer;
asm
    push      ebp
    push      esi
    push      edi
    push      ebx
    push      ecx
    push      eax
    mov       edi, edx
    mov       esi, edx
    xor       eax, eax
    mov       ecx, 256
    rep       stosd               // init Grays
    pop       eax
    call      _SetDataRegs
    mov       eax, ecx
    imul      eax, edx
    xchg      eax, [esp]          // pixel count
    test      al, al
    jnz       @@yGrayLoop
    // 建立彩色图的灰度数组
    pxor      mm7, mm7
    movq      mm2, GRAY_MMX
@@yLoop:
    push      ecx
@@xLoop:
    movd      mm0, [edi]
    punpcklbw mm0, mm7
    pmaddwd   mm0, mm2
    movq      mm1, mm0
    psrlq     mm1, 32
    paddd     mm0, mm1
    movd      eax, mm0
    shr       eax, 15
    inc       [esi].TImageGrayInfo.Grays[eax*4].Integer
    add       edi, 4              // grayData.Grays[gray] ++
    loop      @@xLoop
    pop       ecx
    add       edi, ebx
    dec       edx
    jnz       @@yLoop
    emms
    jmp       @@SumStart
    // 建立灰度图的灰度数组
@@yGrayLoop:
    push      ecx
@@xGrayLoop:
    movzx     eax, [edi].TARGBQuad.Blue       // gray = *edi
    inc       [esi].TImageGrayInfo.Grays[eax*4].Integer// grayData.Grays[gray] ++
    add       edi, 4
    loop      @@xGrayLoop
    pop       ecx
    add       edi, ebx
    dec       edx
    jnz       @@yGrayLoop
    // 计算总的灰度值、最大灰度值及最小灰度值
@@SumStart:
    push      esi
    mov       edi, esi            // esi = ebx = &GrayData.Grays[0]
    mov       ebx, esi
    xor       eax, eax            // edx:eax = 0 (GrayData.Total)
    xor       edx, edx
    xor       ecx, ecx            // for (index = 0; index < 256; index ++)
@@SumLoop:                      // {
    mov       ebp, [edi]
    cmp       [esi], ebp
    cmovb     esi, edi            //   if (*esi < *edi) esi = edi
    cmp       [ebx], ebp
    cmova     ebx, edi            //   if (*ebx > *edi) ebx = edi
    imul      ebp, ecx            //   ebp = *edi * index
    add       eax, ebp            //   edx:eax += ebp
    adc       edx, 0
    add       edi, 4              //   edi += 4
    inc       ecx
    cmp       ecx, 255
    jle       @@SumLoop           // }
    pop       edi
    sub       ebx, edi
    shr       ebx, 2              // min = (ebx - &GrayData.Grays[0]) / 4
    mov       [edi].TImageGrayInfo.MinValue, ebx
    sub       esi, edi
    shr       esi, 2              // max = (esi - &GrayData.Grays[0]) / 4
    mov       [edi].TImageGrayInfo.MaxValue, esi
    pop       ebx                 // count = data.Width * data.Height
    mov       [edi].TImageGrayInfo.Count, ebx
    mov       dword ptr[edi].TImageGrayInfo.Total, eax // total = edx:eax
    mov       dword ptr[edi].TImageGrayInfo.Total+4, edx
    mov       ecx, ebx
    shr       ecx, 1
    add       eax, ecx
    adc       edx, 0
    div       ebx                 // average = (total + count / 2) / count
    mov       [edi].TImageGrayInfo.Average, eax
@@Exit:                           // return GrayData.Average
    pop       ebx
    pop       edi
    pop       esi
    pop       ebp
end;

// 获取图像通道灰度信息
function ImageGetChannelsInfo(const Data: TImageData; var ChannelsInfo: TChannelsInfo): Integer;
asm
    push    esi
    push    edi
    push    ebx
    mov     ebx, eax
    mov     edi, edx
    mov     esi, edi
    xor     eax, eax
    mov     ecx, 256+6
    rep     stosd
    mov     ecx, 3
    add     [ebx].TImageData.Scan0, ecx
@@statLoop:
    push    ecx
    push    esi
    dec     [ebx].TImageData.Scan0
    mov     ecx, True
    mov     edx, edi
    mov     eax, ebx
    call    ImageGetGrayInfo
    mov     ecx, 256
@@SumLoop:
    mov     eax, [edi]
    add     [esi], eax
    add     esi, 4
    add     edi, 4
    loop    @@SumLoop
    add     edi, 6*4
    pop     esi
    pop     ecx
    loop    @@statLoop
    mov     eax, [ebx].TImageData.Width
    mul     [ebx].TImageData.Height
    mov     [esi].TImageGrayInfo.Count, eax
    push    esi
    push    ebp
    xor     eax, eax
    xor     edx, edx
    mov     edi, esi
    mov     ebp, esi
    mov     ecx, 3
    push    ecx
    xor     ecx, ecx
@@calcLoop:
    fild    dword ptr[ebp]          //   grayData[0].Grays[j] /= 3
    fidiv   dword ptr[esp]
    fistp   dword ptr[ebp]
    fwait
    mov     ebx, [ebp]
    cmp     [esi], ebx
    cmovb   esi, ebp
    cmp     [edi], ebx
    cmova   edi, ebp
    imul    ebx, ecx
    add     eax, ebx                //   total += grayData[0].Grays[j]
    adc     edx, 0
    add     ebp, 4
    inc     ecx
    cmp     ecx, 256
    jb      @@calcLoop
    pop     ecx
    pop     ebp
    pop     ebx
    sub     esi, ebx
    sub     edi, ebx
    shr     esi, 2
    shr     edi, 2
    mov     [ebx].TImageGrayInfo.MaxValue, esi
    mov     [ebx].TImageGrayInfo.MinValue, edi
    mov     dword ptr[ebx].TImageGrayInfo.Total, eax
    mov     dword ptr[ebx].TImageGrayInfo.Total+4, edx
    div     [ebx].TImageGrayInfo.Count
    mov     [ebx].TImageGrayInfo.Average, eax
@@Exit:
    pop     ebx
    pop     edi
    pop     esi
end;

    本文灰度化过程和灰度直方图统计过程在计算RGB灰度时使用了定点数处理,将前面公式中的常数乘扩大了32768倍;同时在图像灰度化过程使用了SSE指令,每次处理2个像素,大大加快了图像灰度化执行效率。

    灰度直方图统计过程有2个,ImageGetGrayInfo过程是对整个图像进行灰度数据统计,并计算出最大、最小灰度值以及平均灰度值;而ImageGetChannelsInfo过程则是分别调用ImageGetGrayInfo过程统计出R、G、B各通道的灰度信息,然后在R、G、B通道灰度直方图统计基础上再进行全图的RGB灰度直方图统计,这个RGB灰度直方图统计结果与ImageGetGrayInfo结果是不一样的,后者是直接分类统计图像的灰度值数量,而前者的灰度信息则是在R、G、B通道灰度分类统计的基础上平均得来的。

 下面是一个简单的界面例子程序代码和运行界面图:

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    PaintBox2: TPaintBox;
    PaintBox3: TPaintBox;
    PaintBox4: TPaintBox;
    PaintBox5: TPaintBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure PaintBox2Paint(Sender: TObject);
  private
    { Private declarations }
    Bmp: TBitmap;
    GrayBmp: TBitmap;
    ChannelsInfo: TChannelsInfo;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses JPEG, ImageData;

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  jpeg: TJPEGImage;
  data: TImageData;
begin
  // 装入图像到Bmp
  Bmp := TBitmap.Create;
  jpeg := TJPEGImage.Create;
  try
    jpeg.LoadFromFile('..\..\media\source1.jpg');
    Bmp.Assign(jpeg);
  finally
    jpeg.Free;
  end;
  // 获取Bmp的通道灰度信息
  data := GetBitmapData(Bmp);
  ImageGetChannelsInfo(data, ChannelsInfo);
  // 建立新的位图GrayBmp并灰度化
  GrayBmp := TBitmap.Create;
  GrayBmp.Assign(Bmp);
  data := GetBitmapData(GrayBmp);
  ImageGray(data);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  GrayBmp.Free;
  Bmp.Free;
end;

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
  PaintBox1.Canvas.Draw(0, 0, GrayBmp);         // 画灰度图
  PaintBox1.Canvas.Draw(0, GrayBmp.Height, Bmp);// 画源图
end;

procedure TForm1.PaintBox2Paint(Sender: TObject);
const
  PenColor: array[0..3] of TColor = ($000000, $0000FF, $008000, $FF0000);
var
  I, v: Integer;
  PInfo: PImageGrayInfo;
begin
  PInfo := @ChannelsInfo.RGB;         // 灰度信息指针指向RGB通道
  with TPaintBox(Sender) do
  begin
    Inc(PInfo, Tag);                  // 按预设的TPaintBox控件属性Tag指向相应通道
    Canvas.Brush.Color := clSkyBlue;
    Canvas.FillRect(ClientRect);      // 填充蓝灰色背景
    Canvas.Pen.Color := PenColor[Tag];// 按Tag获取通道直方图画笔颜色
    for I := 0 to 255 do              // 按256级灰度和TPaintBox高度比例画直方图
    begin
      v := Round(PInfo^.Grays[I] / PInfo^.Grays[PInfo^.MaxValue] * Height);
      Canvas.MoveTo(I, Height);
      Canvas.LineTo(I, Height - v);
    end;
  end;
end;

end.

 运行界面图:

 界面左下是原图,左上是灰度图,而右边是通道灰度直方图,从上到下分别为RGB通道、红色通道、绿色通道和蓝色通道直方图。


 

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

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

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

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值