阅读提示:
《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》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《Delphi图像处理 -- 文章索引》。