sogotobj 发表于 2013-1-28 12:11:22

Delphi图像处理 -

本人已经写过几篇关于亮度调整的文章,但是关于图像的对比度调整的过程和文章却一直没有写,其原因是一直没找到一个好的算法。可能有人会说,图像的亮度,对比度调整是最简单的图形操作,其算法网上可说是一搜一大把,确实如此,可就是这最简单的操作,网上的文章却五花八门,我拣几个试了一下,好像都不太理想,关键是算法太简单,实际操作效果不好,于是想,Photoshop的对比度还是较好的,而且也通用,但偏偏网上没有介绍它的算法,用了大半天时间研究了一下,再花了1个来小时写了个Delphi过程,试了一下,居然和Photoshop的对比度调整完全一样的效果!于是认真写了个测试程序,把亮度和对比度放在一起进行调整(亮度和对比度处理过程为各自独立的,其中亮度过程基本是本BLOG文章《GDI+ 在Delphi程序的应用 -- 调整图像亮度》的代码),可是效果却和Photoshop大不一样了,是什么原因呢,Photoshop的亮度调整算法是最简单的那种,与我的亮度过程做出来的是一样的(效果比较图参见《GDI+ 在Delphi程序的应用 -- 线性调整图像亮度》),而前面说了,对比度过程算法也是和Photoshop一样的,可放在一起调整就不行了,无论是先调整亮度,还是先调整对比度都这样。后来仔细分析了一下,Photoshop是用一个函数处理亮度/对比度,而且亮度调整是按对比度的正负分别对待的,下面是实现代码:
过程定义:// 图像亮度调整,Value亮度值procedure ImageBrightness(var Data: TImageData; Value: Integer);// 图像亮度/对比度调整。参数://   Dest输出图,Source原图,Data自身操作图像//   Bright亮度,Contrast对比度,Threshold对比度阀值(可用灰度统计数据的平均灰度)//   Callback回调函数,返回True终止操作,CallbackData回调函数参数地址procedure ImageBrightContrast(var Data: TImageData;    Bright, Contrast: Integer; Threshold: LongWord); overload;    {$IF RTLVersion >= 17.00}inline;{$IFEND}procedure ImageBrightContrast(var Dest: TImageData; const Source: TImageData;    Bright, Contrast: Integer; Threshold: LongWord); overload;    {$IF RTLVersion >= 17.00}inline;{$IFEND}// 无效参数或者被回调函数终止操作返回False。function ImageBrightContrast(var Dest: TImageData; const Source: TImageData;    Bright, Contrast: Integer; Threshold: LongWord;    Callback: TImageAbort; CallbackData: Pointer): Boolean; overload;实现代码:procedure ImageBrightness(var Data: TImageData; Value: Integer);asm    push      esi    push      edi    push      ebx    call      IsValid32    jc      @@Exit    mov       esi, edx    test      edx, edx    jz      @@Exit         // if (Value == 0) return    jns       @@1    neg       edx            // if (Value < 0) Value = -Value@@1:    and       edx, 0ffh    movd      mm1, edx       // mm1 = 00 00 00 00 00 00 00 Value    punpcklbw mm1, mm1    punpcklwd mm1, mm1    punpckldq mm1, mm1    psrld   mm1, 8         // mm1 = 00 Value(7 byte)    call      SetDataRegs32    test      ecx, 1    jz      @@2    add       ebx, 4@@2:    mov       eax, ecx    shr       ecx, 1         // ecx = Data.Width / 2    test      esi, esi    jl      @@yLoopS       // if (value < 0) BrightnessSub@@yLoopA:                  // for (y = Data.Height; y > 0; y--)    push      ecx            // {@@xLoopA:                  //   for (x = edx - 1; x >= 0; x --)    dec       ecx            //   {    js      @@1_1    movq      mm0,    //      mm0 = A1 R1 G1 B1 A0 R0 G0 B0    paddusb   mm0, mm1       //      mm0 += mm1    movq      , mm0   //      (int64* )edi = mm0    add       edi, 8         //      edi += 8    jmp       @@xLoopA       //   }@@1_1:    test      eax, 1         //   if (Data.Width % 2 == 1)    jz      @@1_2          //   {    movd      mm0,    //      mm0 = 00 00 00 00 A0 R0 G0 B0    paddusb   mm0, mm1       //      mm0 += mm1    movd      , mm0   //      (int* )edi = mm0@@1_2:                     //   }    pop       ecx    add       edi, ebx       //   edi += Offset    dec       edx    jnz       @@yLoopA       //}    jmp       @@Exit@@yLoopS:                  // for (y = Data.Height; y > 0; y--)    push      ecx            // {@@xLoopS:                  //   for (x = edx - 1; x >= 0; x --)    dec       ecx            //   {    js      @@2_1    movq      mm0,    //      mm0 = A1 R1 G1 B1 A0 R0 G0 B0    psubusb   mm0, mm1       //      mm0 -= mm1    movq      , mm0   //      (int64* )edi = mm0    add       edi, 8         //      edi += 8    jmp       @@xLoopS       //   }@@2_1:    test      eax, 1         //   if (Data.Width % 2 == 1)    jz      @@2_2          //   {    movd      mm0,    //      mm0 = 00 00 00 00 A0 R0 G0 B0    psubusb   mm0, mm1       //      mm0 -= mm1    movd      , mm0   //      (int* )edi = mm0@@2_2:                     //   }    pop       ecx    add       edi, ebx       //   edi += Offset    dec       edx    jnz       @@yLoopS         //}@@Exit:    emms    pop   ebx    pop   edi    pop   esiend;function ClacBrightContrast(value: Integer): Integer;asm    test    eax, eax    jg      @@1    cmp   eax, -255    jge   @@Exit    mov   eax, -255    jmp   @@Exit@@1:    push    ecx    push    edx    mov   ecx, 256    sub   ecx, eax    jg      @@2    mov   ecx, 1@@2:    mov   eax, 65536    xor   edx, edx    div   ecx    sub   eax, 256    pop   edx    pop   ecx@@Exit:end;procedure BrightContrast(bright, contrast, threshold, cv: Integer); pascal;varheight, count: Integer;dstOffset, srcOffset: Integer;asm    mov   height, edx    mov   dstOffset, ebx    mov   srcOffset, eax    mov   ebx, contrast       // ebx = contrast    mov   edx, threshold@@yLoop:    push    ecx@@xLoop:    push    ecx    mov   count, 3@@rgbLoop:    movzx   eax,           // eax = rgb    test    ebx, ebx    jz      @@21                // if (contrast > 0)    js      @@10                // {    add   eax, bright         //   rgb += bright    jns   @@2    xor   eax, eax@@2:    cmp   ebx, 255    jl      @@15                //   if (contrast >= 255)    cmp   eax, edx            //   {    jl      @@3               //   rgb = rgb >= threshold? 255 : 0    mov   eax, 255            //   goto @@next    jmp   @@next            //   }@@3:                            // }    xor   eax, eax    jmp   @@next@@10:    cmp   ebx, -255         // else if (contrast <= -255)    jg      @@15                // {    mov   eax, edx            //   rgb = threshold; goto @@20    jmp   @@20                // }@@15:    mov   ecx, eax            // rgb = rgb + (rgb - threshold) * cv / 256    sub   eax, edx    imul    eax, cv    sar   eax, 8    add   eax, ecx    jns   @@20    xor   eax, eax@@20:    test    ebx, ebx    jg      @@22@@21:    add   eax, bright         // if (contrast <= 0) rgb += bright    jns   @@22    xor   eax, eax@@22:    cmp   eax, 255    jbe   @@next    mov   eax, 255@@next:    stosb    inc   esi    dec   count    jnz   @@rgbLoop    pop   ecx    movsb    dec   ecx    jnz   @@xLoop    add   edi, dstOffset    add   esi, srcOffset    pop   ecx    dec   height    jnz   @@yLoopend;function ImageBrightContrast(var Dest: TImageData; const Source: TImageData;Bright, Contrast: Integer; Threshold: LongWord;Callback: TImageAbort; CallbackData: Pointer): Boolean;varcv: Integer;beginResult := False;if ImageEmpty(Dest) or ImageEmpty(Source) then Exit;if Threshold > 255 then Threshold := 255;cv := ClacBrightContrast(Contrast);if Assigned(Callback) then    Result := ExecuteAbort(Dest, Source, @BrightContrast,      , Callback, CallbackData)else    Result := ExecuteProc(Dest, Source, @BrightContrast,      );end;procedure ImageBrightContrast(var Dest: TImageData; const Source: TImageData;Bright, Contrast: Integer; Threshold: LongWord);beginImageBrightContrast(Dest, Source, Bright, Contrast, Threshold, nil, nil);end;procedure ImageBrightContrast(var Data: TImageData;Bright, Contrast: Integer; Threshold: LongWord);beginImageBrightContrast(Data, Data, Bright, Contrast, Threshold, nil, nil);end;
下面对亮度/对比度的原理简单介绍一下。
一、Photoshop对比度算法。可以用下面的公式来表示:
(1)、nRGB = RGB + (RGB - Threshold) * Contrast / 255
公式中,nRGB表示图像像素新的R、G、B分量,RGB表示图像像素R、G、B分量,Threshold为给定的阀值,Contrast为处理过的对比度增量。
Photoshop对于对比度增量,是按给定值的正负分别处理的:
当增量等于-255时,是图像对比度的下端极限,此时,图像RGB各分量都等于阀值,图像呈全灰色,灰度图上只有1条线,即阀值灰度;
当增量大于-255且小于0时,直接用上面的公式计算图像像素各分量;
当增量等于 255时,是图像对比度的上端极限,实际等于设置图像阀值,图像由最多八种颜色组成,灰度图上最多8条线,即红、黄、绿、青、蓝、紫及黑与白;
当增量大于0且小于255时,则先按下面公式(2)处理增量,然后再按上面公式(1)计算对比度:
(2)、nContrast = 255 * 255 / (255 - Contrast) - 255
公式中的nContrast为处理后的对比度增量,Contrast为给定的对比度增量。
二、图像亮度调整。本文采用的是最常用的非线性亮度调整(Phoposhop CS3以下版本也是这种亮度调整方式,CS3及以上版本也保留了该亮度调整方式的选项),本文亮度调整采用MMX,对亮度增量分正负情况分别进行了处理,每次处理2个像素,速度相当快,比常规BASM代码的亮度处理过程还要快几倍(参见《GDI+ 在Delphi程序的应用 -- 调整图像亮度》)。
三、图像亮度/对比度综合调整算法。这个很简单,当亮度、对比度同时调整时,如果对比度大于0,现调整亮度,再调整对比度;当对比度小于0时,则相反,先调整对比度,再调整亮度。
下面给出完整的调整亮度/对比度的Delphi代码,包括灰度统计、绘图等:
unit Main;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, ExtCtrls, ComCtrls, ImageUtils, GpBitmapUtils;typeTPanel = class(ExtCtrls.TPanel)public    procedure Paint; override;end;TMainForm = class(TForm)    Label1: TLabel;    Label2: TLabel;    BBar: TTrackBar;    CBar: TTrackBar;    Button1: TButton;    BEdit: TEdit;    CEdit: TEdit;    Panel1: TPanel;    Label3: TLabel;    GrayMap: TPaintBox;    Average: TLabel;    RadioButton1: TRadioButton;    RadioButton2: TRadioButton;    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);    procedure GrayMapPaint(Sender: TObject);    procedure BBarChange(Sender: TObject);    procedure BEditChange(Sender: TObject);    procedure CBarChange(Sender: TObject);    procedure Button1Click(Sender: TObject);    procedure BEditKeyPress(Sender: TObject; var Key: Char);    procedure RadioButton1Click(Sender: TObject);    procedure RadioButton2Click(Sender: TObject);private    { Private declarations }    FData: TImageData;    FtmpData: TImageData;    FLock: Boolean;    FGrayData: TGrayStatData;    FGrayAverage: Integer;    FIsRun: Boolean;    FAbort: Boolean;public    { Public declarations }    procedure GrayDiagram;    procedure AdjustmentImage;end;varMainForm: TMainForm;implementationuses Math, Gdiplus;{$R *.dfm}typeTBrightContrast = function(var Dest: TImageData; const Source: TImageData;    Bright, Contrast: Integer; Threshold: LongWord;    Callback: TImageAbort; CallbackData: Pointer): Boolean;varBrightContrast: TBrightContrast = ImageBrightContrast;function MyAbort(data: Pointer): BOOL; stdcall;beginResult := TMainForm(data).FAbort;end;procedure TMainForm.AdjustmentImage;beginif not FIsRun thenbegin    FIsRun := True;    FAbort := False;    if BrightContrast(FTmpData, FData, BBar.Position,      Round(CBar.Position * 255.0 / 100.0) , FGrayAverage, MyAbort, Self) then      GrayDiagram;    Panel1.Paint;    FIsRun := False;endelse FAbort := True;end;procedure TMainForm.BBarChange(Sender: TObject);beginif not FLock then    BEdit.Text := IntToStr(BBar.Position);end;procedure TMainForm.CBarChange(Sender: TObject);beginif not FLock then    CEdit.Text := IntToStr(CBar.Position);end;procedure TMainForm.BEditChange(Sender: TObject);varv: Integer;beginwith Sender as TEdit dobegin    FLock := True;    if Text = '' then v := 0    else v := StrToInt(Text);    if Tag = 1 then      CBar.Position := v    else      BBar.Position := v;    AdjustmentImage;    FLock := False;end;end;procedure TMainForm.BEditKeyPress(Sender: TObject; var Key: Char);beginif (Key >= #32) and not (Key in ['0'..'9']) then    Key := #0;end;procedure TMainForm.Button1Click(Sender: TObject);beginClose;end;procedure TMainForm.FormCreate(Sender: TObject);beginDoubleBuffered := True;FData := GetImageData(TGpBitmap.Create('..\..\Media\56-3.jpg'), True);FtmpData := NewImageData(FData.Width, FData.Height, 0);ImageGrayStat(FData, FGrayData);FGrayAverage := FGrayData.Average;AdjustmentImage;end;procedure TMainForm.FormDestroy(Sender: TObject);beginFreeImageData(FtmpData);FreeImageData(FData);end;procedure TMainForm.GrayMapPaint(Sender: TObject);varI: Integer;beginGrayMap.Canvas.Brush.Color := clSkyBlue;GrayMap.Canvas.FillRect(GrayMap.ClientRect);GrayMap.Canvas.Pen.Color := clRed;GrayMap.Canvas.Brush.Style := bsClear;GrayMap.Canvas.Rectangle(GrayMap.ClientRect);for I := 0 to 255 dobegin    GrayMap.Canvas.Pen.Color := RGB(I, I, I);    GrayMap.Canvas.MoveTo(I + 1, GrayMap.Height);    GrayMap.Canvas.LineTo(I + 1, GrayMap.Height -      Round(35.0 * (Log10(FGrayData.Grays + 1))));end;end;procedure TMainForm.GrayDiagram;beginImageGrayStat(FtmpData, FGrayData);Average.Caption := '平均灰度:' + IntToStr(FGrayData.Average);GrayMap.Invalidate;end;procedure TMainForm.RadioButton1Click(Sender: TObject);beginBrightContrast := ImageBrightContrast;AdjustmentImage;end;procedure TMainForm.RadioButton2Click(Sender: TObject);beginBrightContrast := ImageLineBrightContrast;AdjustmentImage;end;{ TPanel }procedure TPanel.Paint;beginwith MainForm dobegin    DrawImage(Panel1.Canvas, 0, 0, FtmpData);    DrawImage(Panel1.Canvas, 0, FtmpData.Height, FData);end;end;end.
例子代码中的图像显示过程DrawImage见《Delphi图像处理 -- 图像显示》,灰度统计过程ImageGrayStat见《Delphi图像处理 -- 图像的灰度化、二值化及反色》。
例子中有个线性亮度选项,有关线性亮度/对比度调整另文介绍。
下面是运行界面,其效果和Photoshop基本一致(如果前面实现代码不优化速度,由256替代255,与Photoshop应该是完全相同的):
http://p.blog.csdn.net/images/p_blog_csdn_net/maozefa/EntryImages/20091030/3333.jpg
文章中使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。
文章中所用数据类型及一些过程见《Delphi图像处理 -- 数据类型及内部过程》和《Delphi图像处理 -- 图像像素结构与图像数据转换》。
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
maozefa@hotmail.com
本文代码于2010.5.20重新修订过。增加了拷贝形式的调整过程和响应回调函数的调整过程(修改过的例子代码使用了这种处理过程)。代码中的ExecuteAbort过程和ExecuteProc过程见《Delphi图像处理 -- 图像像素结构与图像数据转换》。
页: [1]
查看完整版本: Delphi图像处理 -