映像的灰階化、二值化和反色是些較簡單的映像像素處理過程,我在《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 ediend;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 ebpend;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 ebpend;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 ebpend;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;