Delphi图像处理 -- 图像色阶调整

在Photoshop中,图像色阶调整应用很广泛,本文介绍的图像色阶调整过程与Photoshop处理效果基本一致。

Photoshop的色阶调整主要有3个调整点,即通常所说的黑场、白场及灰场调整,本文代码只涉及到黑场和白场调整,至于灰场调整算法以及与黑白场之间的关系等问题,因为本人还没完全琢磨透,只好以后再完善了(其实算法并不复杂,麻烦的是后者)。

色阶调整的基本算法并不复杂,用伪代码表示:

Diff = levelHigh - levelLow

newRGB = (oldRGB - levelLow) * 255 / Diff

其中levelLow为色阶低端数据(黑场),levelHigh为色阶高端数据(白场),Diff为二者的离差,oldRGB为调整前的像素颜色,newRGB为调整后的颜色。

色阶调整涉及四个通道,即R、G、B各分量通道及整体颜色通道,如果每个通道单独调整,将是比较麻烦和耗时的,本文过程采用MMX代码,可一次性处理R、G、B3个分量通道,加上整体颜色通道调整,最多需进行2遍调整处理,所以运行速度还是很快的,在我的P42.8G机器上处理一遍千万像素图片,不包括装载图片时间,只需47ms。

下面直接给出图像色阶调整的源代码:

数据及过程定义: type // 色阶调整数据 PColorLevelData = ^TColorLevelData; TColorLevelData = packed record BlueLow: LongWord; // 蓝色通道低阶值 BlueHigh: LongWord; // 蓝色通道高阶值 GreenLow: LongWord; // 绿色通道低阶值 GreenHigh: LongWord; // 绿色通道高阶值 RedLow: LongWord; // 红色通道低阶值 RedHigh: LongWord; // 红色通道高阶值 end; // 色阶调整。参数: // Dest输出图,Source原图,Data自身操作图像 // LevelData R、G、B各通道的色阶调整数据 // Callback回调函数,返回True终止操作,CallbackData回调函数参数地址 function ImageColorLevels(var Dest: TImageData; const Source: TImageData; const LevelData: TColorLevelData; Callback: TImageAbort = nil; CallbackData: Pointer = nil): Boolean; overload; procedure ImageColorLevels(var Data: TImageData; const LevelData: TColorLevelData); overload; 实现代码: procedure ColorLevels(mmxBuf: Pointer); pascal; asm push ebx mov ebx, mmxBuf // mm5 = 512 Coef Coef Coef movq mm5, qword ptr [ebx] movq mm6, qword ptr [ebx+8] pxor mm7, mm7 // mm6 = 00 00 LevelLow LevelLow LevelLow pop ebx @@yLoop: push ecx @@xLoop: movd mm0, [esi] punpcklbw mm0, mm7 psubw mm0, mm6 psllw mm0, 7 pmulhw mm0, mm5 packuswb mm0, mm7 // newRgb = (rgb - LevelLow) * 128 * Coef / 65536 movd [edi], mm0 // newAlpha = (alpha - 0) * 128 * 512 / 65536 add esi, 4 add edi, 4 loop @@xLoop add esi, eax add edi, ebx pop ecx dec edx jnz @@yLoop emms end; function ImageColorLevels(var Dest: TImageData; const Source: TImageData; const LevelData: TColorLevelData; Callback: TImageAbort; CallbackData: Pointer): Boolean; type TIntArray = array[0..5] of Integer; var mmxBuf: array[0..7] of Word; i, j, Diff: Integer; begin Result := not ImageEmpty(Dest) and not ImageEmpty(Source); if not Result then Exit; for i := 0 to 3 do begin mmxBuf[i] := 512; mmxBuf[i + 4] := 0; end; j := 0; for i := 0 to 2 do begin Diff := TIntArray(LevelData)[j + 1]; if Diff > 255 then Diff := 255; Dec(Diff, TIntArray(LevelData)[j]); // Diff = LevelHigh - LevelLow if (Diff >= 4) and (Diff < 255) then begin mmxBuf[i] := (255 * 512) div Diff; // Coef = 255 * 512 / Diff mmxBuf[i + 4] := TIntArray(LevelData)[j]; end; Inc(j, 2); end; if Assigned(Callback) then Result := ExecuteAbort(Dest, Source, @ColorLevels, [@mmxBuf], Callback, CallbackData) else Result := ExecuteProc(Dest, Source, @ColorLevels, [@mmxBuf]); end; procedure ImageColorLevels(var Data: TImageData; const LevelData: TColorLevelData); begin ImageColorLevels(Data, Data, LevelData); end;

上面的调整过程即可一次性调整R、G、B3个分量通道的色阶,也可进行整体颜色通道的色阶调整。当整体颜色通道和分量通道都需要进行色阶调整时,应先调整分量通道,再调整整体颜色通道。

需要说明的是,Photoshop色阶调整时,白场与黑场之间的离差最小值是2,而本文过程要求的白场与黑场之间离差最小值是4,否则,MMX代码就会溢出了。事实上,无论最小允许离差是2还是4,在实际运用中都没有多大意义。

下面给出一个完整的图像色阶调整例子:

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) LBar: TTrackBar; HBar: TTrackBar; Button1: TButton; Panel1: TPanel; Label3: TLabel; GrayMap: TPaintBox; ComboBox1: TComboBox; Label1: TLabel; LLabel: TLabel; HLabel: TLabel; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure GrayMapPaint(Sender: TObject); procedure LBarChange(Sender: TObject); procedure HBarChange(Sender: TObject); procedure Button1Click(Sender: TObject); procedure ComboBox1Change(Sender: TObject); private { Private declarations } FData: TImageData; FtmpData: TImageData; FGrayData: array[0..3] of TGrayStatData; FClData: TColorLevelData; FLevelLow: LongWord; FLevelHigh: LongWord; FLock: Boolean; FIsRun: Boolean; FAbort: Boolean; public { Public declarations } procedure AdjustmentImage; procedure GetGrayDatas; end; var MainForm: TMainForm; implementation uses Gdiplus; {$R *.dfm} // 回调函数 function MyAbort(data: Pointer): BOOL; stdcall; begin Result := TMainForm(data).FAbort; end; // 色阶调整 procedure TMainForm.AdjustmentImage; var clData: TColorLevelData; IsMembers: Boolean; Execute: Boolean; begin if not FIsRun then begin IsMembers := (FClData.BlueHigh - FClData.BlueLow <> 255) or (FClData.GreenHigh - FClData.GreenLow <> 255) or (FClData.RedHigh - FClData.RedLow <> 255); FIsRun := True; FAbort := False; // 如果分量通道和整个颜色通道都需要调整 // 先调整分量通道 if IsMembers then Execute := ImageColorLevels(FTmpData, FData, FClData, MyAbort, Self); // 然后再调整整个颜色通道 // 如果都不需调整,相当做一次拷贝 if not IsMembers or (Execute and (FLevelHigh - FLevelLow <> 255)) then begin clData.BlueLow := FLevelLow; clData.GreenLow := FLevelLow; clData.RedLow := FLevelLow; clData.BlueHigh := FLevelHigh; clData.GreenHigh := FLevelHigh; clData.RedHigh := FLevelHigh; if not IsMembers then ImageColorLevels(FTmpData, FData, clData, MyAbort, Self) else ImageColorLevels(FTmpData, FTmpData, clData, MyAbort, Self); end; Panel1.Paint; FIsRun := False; end else FAbort := True; end; // 计算各通道的灰度数据 procedure TMainForm.GetGrayDatas; var saveScan0: Pointer; i, j: Integer; minIdx, maxIdx: Integer; begin saveScan0 := FData.Scan0; for j := 0 to 255 do FGrayData[0].Grays[j] := 0; // 如果给定ImageGrayStat的是灰度图,每次只以蓝色为灰度像素进行统计, // 因此这里把彩色图作灰度图参数传递,依次改变RGB的位置,分别统计 // RGB各通道的灰度数据 for i := 1 to 3 do begin FData.Scan0 := Pointer(Integer(saveScan0) + 3 - i); ImageGrayStat(FData, FGrayData[i], True); for j := 0 to 255 do Inc(FGrayData[0].Grays[j], FGrayData[i].Grays[j]); end; // 整个图像的灰度数据不是常用的灰度统计方法,而是在RGB各分量通道灰度统计 // 数据的平均值 minIdx := 0; maxIdx := 0; FGrayData[0].Total := 0; FGrayData[0].Count := FGrayData[1].Count; for j := 0 to 255 do begin FGrayData[0].Grays[j] := Round(FGrayData[0].Grays[j] / 3); if FGrayData[0].Grays[j] > FGrayData[0].Grays[maxIdx] then maxIdx := j else if FGrayData[0].Grays[j] < FGrayData[0].Grays[minIdx] then minIdx := j; Inc(FGrayData[0].Total, FGrayData[0].Grays[j]); end; FGrayData[0].MaxGray := maxIdx; FGrayData[0].MinGray := minIdx; FGrayData[0].Average := FGrayData[0].Total div FGrayData[0].Count; FData.Scan0 := saveScan0; end; procedure TMainForm.FormCreate(Sender: TObject); begin DoubleBuffered := True; FData := GetImageData(TGpBitmap.Create('../../Media/source1.jpg'), True); FtmpData := NewImageData(FData.Width, FData.Height, 0); GetGrayDatas; FClData.BlueHigh := 255; FClData.GreenHigh := 255; FClData.RedHigh := 255; FLevelHigh := 255; ComboBox1.ItemIndex := 0; ComboBox1Change(nil); AdjustmentImage; end; procedure TMainForm.FormDestroy(Sender: TObject); begin FreeImageData(FtmpData); FreeImageData(FData); end; // 黑场改变 procedure TMainForm.LBarChange(Sender: TObject); var v: Integer; begin LLabel.Caption := IntToStr(LBar.Position); if FLock then Exit; v := HBar.Position - LBar.Position; if v < 4 then begin FLock := True; LBar.Position := HBar.Position - 4; FLock := False; end; case ComboBox1.ItemIndex of 1: FClData.RedLow := LBar.Position; 2: FClData.GreenLow := LBar.Position; 3: FClData.BlueLow := LBar.Position; else FLevelLow := LBar.Position; end; AdjustmentImage; end; // 白场改变 procedure TMainForm.HBarChange(Sender: TObject); var v: Integer; begin HLabel.Caption := IntToStr(HBar.Position); if FLock then Exit; v := HBar.Position - LBar.Position; if v < 4 then begin FLock := True; HBar.Position := LBar.Position + 4; FLock := False; end; case ComboBox1.ItemIndex of 1: FClData.RedHigh := HBar.Position; 2: FClData.GreenHigh := HBar.Position; 3: FClData.BlueHigh := HBar.Position; else FLevelHigh := HBar.Position; end; AdjustmentImage; end; // 通道改变 procedure TMainForm.ComboBox1Change(Sender: TObject); begin FLock := True; case ComboBox1.ItemIndex of 1: begin LBar.Position := FClData.RedLow; HBar.Position := FClData.RedHigh; end; 2: begin LBar.Position := FClData.GreenLow; HBar.Position := FClData.GreenHigh; end; 3: begin LBar.Position := FClData.BlueLow; HBar.Position := FClData.BlueHigh; end; else begin LBar.Position := FLevelLow; HBar.Position := FLevelHigh; end end; FLock := False; GrayMap.Invalidate; AdjustmentImage; end; // 画通道灰度图 procedure TMainForm.GrayMapPaint(Sender: TObject); const PenColor: array[0..3] of TColor = ($000000, $0000FF, $008000, $FF0000); var I, v, x: Integer; begin x := ComboBox1.ItemIndex; GrayMap.Canvas.Brush.Color := clSkyBlue; GrayMap.Canvas.FillRect(GrayMap.ClientRect); GrayMap.Canvas.Pen.Color := PenColor[x]; for I := 0 to 255 do begin v := Trunc(FGrayData[x].Grays[I] / FGrayData[x].Grays[FGrayData[x].MaxGray] * GrayMap.Height); GrayMap.Canvas.MoveTo(I, GrayMap.Height); GrayMap.Canvas.LineTo(I, GrayMap.Height - v); end; end; procedure TMainForm.Button1Click(Sender: TObject); begin Close; 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.

例子程序中,重要的地方都作了注释,所以这里不再解释。

下面是2张运行效果图,第一张效果图调整了整体颜色通道的色阶,第二张效果图是在整体颜色通道调整基础上再进行的绿色通道调整(其实例子原图没必要再进行色阶调整,这里只是反映一下代码调整效果):

整体颜色通道色阶调整

绿色通道色阶调整

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

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

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

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

maozefa@hotmail.com

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值