Delphi图像处理 -- Photoshop图像亮度/对比度调整

本人已经写过几篇关于亮度调整的文章,但是关于图像的对比度调整的过程和文章却一直没有写,其原因是一直没找到一个好的算法。可能有人会说,图像的亮度,对比度调整是最简单的图形操作,其算法网上可说是一搜一大把,确实如此,可就是这最简单的操作,网上的文章却五花八门,我拣几个试了一下,好像都不太理想,关键是算法太简单,实际操作效果不好,于是想,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, [edi] // mm0 = A1 R1 G1 B1 A0 R0 G0 B0 paddusb mm0, mm1 // mm0 += mm1 movq [edi], 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, [edi] // mm0 = 00 00 00 00 A0 R0 G0 B0 paddusb mm0, mm1 // mm0 += mm1 movd [edi], 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, [edi] // mm0 = A1 R1 G1 B1 A0 R0 G0 B0 psubusb mm0, mm1 // mm0 -= mm1 movq [edi], 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, [edi] // mm0 = 00 00 00 00 A0 R0 G0 B0 psubusb mm0, mm1 // mm0 -= mm1 movd [edi], mm0 // (int* )edi = mm0 @@2_2: // } pop ecx add edi, ebx // edi += Offset dec edx jnz @@yLoopS // } @@Exit: emms pop ebx pop edi pop esi end; 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; var height, 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, [esi] // 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 @@yLoop end; function ImageBrightContrast(var Dest: TImageData; const Source: TImageData; Bright, Contrast: Integer; Threshold: LongWord; Callback: TImageAbort; CallbackData: Pointer): Boolean; var cv: Integer; begin Result := 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, [Bright, Contrast, Threshold, cv], Callback, CallbackData) else Result := ExecuteProc(Dest, Source, @BrightContrast, [Bright, Contrast, Threshold, cv]); end; procedure ImageBrightContrast(var Dest: TImageData; const Source: TImageData; Bright, Contrast: Integer; Threshold: LongWord); begin ImageBrightContrast(Dest, Source, Bright, Contrast, Threshold, nil, nil); end; procedure ImageBrightContrast(var Data: TImageData; Bright, Contrast: Integer; Threshold: LongWord); begin ImageBrightContrast(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; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, ImageUtils, GpBitmapUtils; type TPanel = 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; var MainForm: TMainForm; implementation uses Math, Gdiplus; {$R *.dfm} type TBrightContrast = function(var Dest: TImageData; const Source: TImageData; Bright, Contrast: Integer; Threshold: LongWord; Callback: TImageAbort; CallbackData: Pointer): Boolean; var BrightContrast: TBrightContrast = ImageBrightContrast; function MyAbort(data: Pointer): BOOL; stdcall; begin Result := TMainForm(data).FAbort; end; procedure TMainForm.AdjustmentImage; begin if not FIsRun then begin 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; end else FAbort := True; end; procedure TMainForm.BBarChange(Sender: TObject); begin if not FLock then BEdit.Text := IntToStr(BBar.Position); end; procedure TMainForm.CBarChange(Sender: TObject); begin if not FLock then CEdit.Text := IntToStr(CBar.Position); end; procedure TMainForm.BEditChange(Sender: TObject); var v: Integer; begin with Sender as TEdit do begin 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); begin if (Key >= #32) and not (Key in ['0'..'9']) then Key := #0; end; procedure TMainForm.Button1Click(Sender: TObject); begin Close; end; procedure TMainForm.FormCreate(Sender: TObject); begin DoubleBuffered := 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); begin FreeImageData(FtmpData); FreeImageData(FData); end; procedure TMainForm.GrayMapPaint(Sender: TObject); var I: Integer; begin GrayMap.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 do begin 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[I] + 1)))); end; end; procedure TMainForm.GrayDiagram; begin ImageGrayStat(FtmpData, FGrayData); Average.Caption := '平均灰度:' + IntToStr(FGrayData.Average); GrayMap.Invalidate; end; procedure TMainForm.RadioButton1Click(Sender: TObject); begin BrightContrast := ImageBrightContrast; AdjustmentImage; end; procedure TMainForm.RadioButton2Click(Sender: TObject); begin BrightContrast := ImageLineBrightContrast; AdjustmentImage; end; { TPanel } procedure TPanel.Paint; begin with MainForm do begin DrawImage(Panel1.Canvas, 0, 0, FtmpData); DrawImage(Panel1.Canvas, 0, FtmpData.Height, FData); end; end; end.

例子代码中的图像显示过程DrawImage见《Delphi图像处理 -- 图像显示》,灰度统计过程ImageGrayStat见《Delphi图像处理 -- 图像的灰度化、二值化及反色》。

例子中有个线性亮度选项,有关线性亮度/对比度调整另文介绍。

下面是运行界面,其效果和Photoshop基本一致(如果前面实现代码不优化速度,由256替代255,与Photoshop应该是完全相同的):

文章中使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。

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

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

maozefa@hotmail.com

本文代码于2010.5.20重新修订过。增加了拷贝形式的调整过程和响应回调函数的调整过程(修改过的例子代码使用了这种处理过程)。代码中的ExecuteAbort过程和ExecuteProc过程见《Delphi图像处理 -- 图像像素结构与图像数据转换》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Delphi11 是最新的 Delphi 版本,而 `delphi-opencv-master` 是一个开源的 Delphi 与 OpenCV 集成的项目,它提供了一组 Delphi 封装的 OpenCV 函数和类,方便 Delphi 开发者使用 OpenCV 进行图像处理和计算机视觉操作。 在 Delphi11 中,你可以通过以下步骤来使用 `delphi-opencv-master`: 1. 首先,你需要下载 `delphi-opencv-master` 项目的源代码,可以从 GitHub 上下载:https://github.com/Laex/Delphi-OpenCV 2. 解压缩下载的源代码,并将其中的 `OpenCV` 文件夹复制到 Delphi11 项目的根目录下。 3. 在 Delphi11 中打开你的项目,然后在菜单栏中选择 `Project` -> `Options` -> `Delphi Compiler` -> `Search Path`,将 OpenCV 文件夹所在的路径添加到搜索路径中。 4. 在 Delphi11 中使用 `uses` 语句引入 `OpenCV_Image` 单元,并调用其中的函数和类来进行图像处理和计算机视觉操作。 例如,以下代码演示了如何使用 `delphi-opencv-master` 进行图像读取和灰度化: ``` uses OpenCV_Image; var img: IplImage; begin // 读取图像 img := LoadImage('lena.jpg'); // 将图像转换为灰度图像 cvCvtColor(img, img, CV_RGB2GRAY); // 显示图像 ShowImage('My Image', img); // 等待用户按下任意键 WaitKey; // 释放图像内存 img.Release; end; ``` 这段代码使用了 `OpenCV_Image` 单元中提供的 `LoadImage` 函数和 `ShowImage` 函数来读取和显示图像,使用了 OpenCV 库中的 `cvCvtColor` 函数将图像转换为灰度图像。你可以根据自己的需要,使用 `delphi-opencv-master` 中提供的其他函数和类来进行更加复杂的图像处理和计算机视觉操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值