图像的灰度化、二值化和反色是些较简单的图像像素处理过程,我在《GDI+ 在Delphi程序的应用 -- ColorMatrix与图像灰度化》和《GDI+ 在Delphi程序的应用 -- 图像二值化》二篇文章中讲了如何利用GDI+的ColorMatrix实现图像灰度化和二值化,但是那种处理只适合GDI+的图像类,本文的方法同时适用于GDI+图像和Delphi的TGraphic图像。
过程定义:
// 图像求反
procedure ImageInvert(var Data: TImageData);
// 灰度化图像
procedure ImageGray(var Data: TImageData);
// 灰度统计。GrayData为灰度统计结构;缺省统计彩色图灰度,
procedure ImageGrayStat(const Data: TImageData; var GrayData: TGrayStatData;
IsGrayImage: Boolean = False);
// 二值化图像。Threshold阀值,需要32位灰度图数据
procedure ImageTwoValues(var Data: TImageData; Threshold: LongWord = 127);
// 动态阀值(子图灰度平均值)二值化图像,SubSize子图大小,需要32位灰度图数据
procedure ImageDynamTwoValues(var Data: TImageData; SubSize: Integer);
实现代码:
procedure ImageInvert(var Data: TImageData);
asm
push edi
push ebx
call IsValid32
jc @@Exit
call SetDataRegs32
mov eax, 0ffffffh
@@yLoop:
push ecx
@@xLoop:
xor [edi], eax
add edi, 4
loop @@xLoop
pop ecx
add edi, ebx
dec edx
jnz @@yLoop
@@Exit:
pop ebx
pop edi
end;
procedure ImageGray(var Data: TImageData);
asm
push ebp
push esi
push edi
push ebx
call IsValid32
jc @@Exit
call SetDataRegs32
@@yLoop:
push ecx
@@xLoop:
movzx eax, [edi].TARGBQuad.Blue
movzx esi, [edi].TARGBQuad.Green
movzx ebp, [edi].TARGBQuad.Red
imul eax, 117 // blue 0.114 * 1024
imul esi, 601 // green 0.587 * 1024
imul ebp, 306 // red 0.299 * 1024
add eax, esi
add eax, ebp
add eax, 512 // Rounding
shr eax, 10 // eax = Round((R * 306 + G * 601 + B * 117) / 1024)
mov [edi].TARGBQuad.Red, al
mov [edi].TARGBQuad.Green, al
mov [edi].TARGBQuad.Blue, al
add edi, 4
loop @@xLoop
pop ecx
add edi, ebx
dec edx
jnz @@yLoop
@@Exit:
pop ebx
pop edi
pop esi
pop ebp
end;
procedure GrayStat(const Data: TImageData; var GrayData: TGrayStatData; IsGrayImage: Boolean);
asm
push ebp
push esi
push edi
push ebx
mov edi, edx // edi = GrayData
mov edx, ecx
mov esi, [eax].TImageData.Scan0
mov ecx, [eax].TImageData.Width
mov ebx, [eax].TImageData.Height
mov ebp, [eax].TImageData.Stride
mov eax, ecx
shl eax, 2
sub ebp, eax // ebp = offset
mov eax, ecx
imul eax, ebx
push eax // Total pixel count
push ecx // init Grays
push edi
mov ecx, 256
xor eax, eax
rep stosd
pop edi
pop ecx
cmp edx, TRUE
je @@yGrayLoop
// 建立彩色图的灰度数组
@@yLoop:
push ecx
@@xLoop:
movzx eax, [esi].TARGBQuad.Blue
movzx edx, [esi].TARGBQuad.Green
imul eax, 117 // blue 0.114 * 1024
imul edx, 601 // green 0.587 * 1024
add edx, eax
movzx eax, [esi].TARGBQuad.Red
imul eax, 306 // red 0.299 * 1024
add eax, edx
add eax, 512 // Rounding
shr eax, 10 // eax = Round((R * 306 + G * 601 + B * 117) / 1024)
inc dword ptr [edi].TGrayStatData.Grays[eax*4]// grayData.Grays[eax] ++
add esi, 4 // esi += 4
loop @@xLoop
pop ecx
add esi, ebp
dec ebx
jnz @@yLoop
jmp @@SumStart
// 建立灰度图的灰度数组
@@yGrayLoop:
push ecx
@@xGrayLoop:
movzx eax, [esi].TARGBQuad.Blue// eax = Gray = *esi
inc dword ptr [edi].TGrayStatData.Grays[eax*4]// grayData.Grays[eax] ++
add esi, 4 // esi +=4
loop @@xGrayLoop
pop ecx
add esi, ebp
dec ebx
jnz @@yGrayLoop
// 计算总的灰度值、最大灰度值及最小灰度值
@@SumStart:
push edi
xor eax, eax // edx:eax = 0 (GrayData.Total)
xor edx, edx
mov esi, edi // esi = ebx = &GrayData.Grays[0]
mov ebx, edi
xor ecx, ecx // for (index = 0; index < 256; index ++)
@@SumLoop: // {
mov ebp, [edi]
cmp [esi], ebp
jae @@1
mov esi, edi // if (*esi < *edi) esi = edi
@@1:
cmp [ebx], ebp
jbe @@2
mov ebx, edi // if (*ebx > *edi) ebx = edi
@@2:
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 // ebx = (ebx - &GrayData.Grays[0]) / 4
mov [edi].TGrayStatData.MinGray, ebx// GrayData.MinGray = ebx
sub esi, edi
shr esi, 2 // esi = (esi - &GrayData.Grays[0]) / 4
mov [edi].TGrayStatData.MaxGray, esi// GrayData.MaxGray = esi
pop ebx
mov [edi].TGrayStatData.Count, ebx // GrayData.Count = data.Width * data.Height
mov dword ptr [edi].TGrayStatData.Total, eax // GrayData.Total = edx:eax
mov dword ptr [edi].TGrayStatData.Total+4, edx
mov ecx, ebx
shr ecx, 1
add eax, ecx
adc edx, 0
idiv ebx
// GrayData.Average = (GrayData.Total + GrayData.Count / 2) / GrayData.Count)
mov [edi].TGrayStatData.Average, eax
@@Exit:
pop ebx
pop edi
pop esi
pop ebp
end;
procedure ImageGrayStat(const Data: TImageData;
var GrayData: TGrayStatData; IsGrayImage: Boolean);
begin
if not ImageEmpty(Data) then
GrayStat(Data, GrayData, IsGrayImage);
end;
procedure TwoValues(var Data: TImageData; Threshold: LongWord);
asm
push ebp
push esi
push edi
push ebx
push edx
call SetDataRegs32
pop eax
mov esi, 000ffffffh
mov ebp, esi
not ebp
@@yLoop:
push ecx
@@xLoop:
cmp [edi], al
jb @@1
or [edi], esi
jmp @@2
@@1:
and [edi], ebp
@@2:
add edi, 4
loop @@xLoop
pop ecx
add edi, ebx
dec edx
jnz @@yLoop
@@Exit:
pop ebx
pop edi
pop esi
pop ebp
end;
procedure ImageTwoValues(var Data: TImageData; Threshold: LongWord);
begin
if not ImageEmpty(Data) then
TwoValues(Data, Threshold);
end;
procedure ImageDynamTwoValues(var Data: TImageData; SubSize: Integer);
var
Sub: TImageData;
GrayData: TGrayStatData;
x, y: Integer;
begin
if ImageEmpty(Data) then Exit;
if SubSize <= 0 then
begin
GrayStat(Data, GrayData, True);
TwoValues(Data, GrayData.Average);
Exit;
end;
y := 0;
while y < Data.Height do
begin
x := 0;
while x < Data.Width do
begin
Sub := GetSubData(Data, x, y, SubSize, SubSize);
GrayStat(Sub, GrayData, True);
TwoValues(Sub, GrayData.Average);
Inc(x, SubSize);
end;
Inc(y, SubSize);
end;
end;
灰度化过程还是依照大多数图像灰度处理惯例,计算YUV颜色空间的Y分量作为灰度图,公式为:
Y = 0.299 * R + 0.587 * G + 0.114 * B
本文灰度过程使用了定点数处理,将上面公式中的常数乘上1024,加快了处理过程,伪代码为:
Y = (306 * R + 601 * G + 117 * B + 512) >> 10
代码中的+512是做四舍五入,右移10位等于除以1024。
图像灰度统计过程和图像灰度化过程采用了相同的原理和计算过程,只不过没有改变图像,而是以计算结果作为256色灰度阶数组的下标,增加该灰度阶的个数而已。所有图像灰度统计指标都存放在TGrayStatData类型的结构中。见《Delphi图像处理 -- 数据类型及内部过程》。
图像二值化过程是在图像灰度处理基础上进行的,由于R、G、B三个分量相等,所以只要把任何其中一个与阀值比较即可:大于阀值为255,否则为0。因灰度图像素格式是32位的,所以过程中直接以0x00FFFFFF或RGB三个分量为255,以0xFF000000与RGB三个分量为0,图像二值化的黑白效果取决于阀值的大小。
因有些图像的灰度分布不太均匀,为了加强图像的二值特征,本文尝试写了一个图像动态分组二值化过程ImageDynamTwoValues,即将图像分组为一定大小的子图,对各子图分别进行灰度统计后,以该子图的灰度平均值为阀值进行子图的二值化,不过在测试过程中,发现如果子图尺寸确定的不合适,各子图之间有很明显的区别,这对图像的二值分析显然是不利的。
至于图像的反色处理更简单,直接用0xFFFFFF和RGB异或就成。
图像动态分组二值化例子:
var
jpg: TJPEGImage;
Data: TImageData;
GrayData: TGrayStatData;
begin
jpg := TJPEGImage.Create;
jpg.LoadFromFile('D:\VclLib\GdiplusDemo\Media\20041001.jpg');
Canvas.Draw(0, 0, jpg);
Data := GetImageData(jpg);
ImageGray(Data);
// ImageTwoValues(Data);
ImageDynamTwoValues(Data, 128);
ImageDataAssignTo(Data, jpg);
Canvas.Draw(0, 0, jpg);
FreeImageData(Data);
jpg.Free;
end;