Delphi图像处理 -- 图像的灰度化、二值化及反色

图像的灰度化、二值化和反色是些较简单的图像像素处理过程,我在《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;

例子中,被注销的代码是使用缺省阀值(127)的全图二值化。

下面是2种方法处理的二值化效果图,上图为原图,中间为全局二值化效果,下图是以128大小的子图分组产生的动态二值化效果图:

原图

全局二值化图

分组动态二值化图(128)

文章中所用数据类型及一些过程见《Delphi图像处理 -- 数据类型及内部过程》和《Delphi图像处理 -- 图像像素结构与图像数据转换》。

尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:

maozefa@hotmail.com

注:本文代码于2010.5.20重新修订过。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值