阅读提示:
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
尽可能保持二者内容一致,可相互对照。
本文代码必须包括《C++图像处理 -- 数据类型及公用函数》文章中的BmpData.h头文件。
Photoshop CS的图像黑白调整功能,是通过对红、黄、绿、青、蓝和洋红等6种颜色的比例调节来完成的。能更精细地将彩色图片转换为高质量的黑白照片。
Photoshop CS图像黑白调整功能的计算公式为:
gray = (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min
公式中:gray为像素灰度值,max、mid和min分别为图像像素R、G、B分量颜色的最大值、中间值和最小值,ratio_max为max所代表的分量颜色(单色)比率,ratio_max_mid则为max与mid两种分量颜色所形成的复色比率。
用上面公式计算的灰度值,与我们通常所用的灰度计算方法有很大不同,通常所用的灰度公式为,是直接将颜色各分量乘以相应的比率相加而成,如:gray = 0.3R + 0.59G + 0.11B,而上面公式则是在最小值代表的颜色分量基础上,用最大值与最小值之差表示单色部分(红、绿、蓝),用中间值与最小值之差表示复色部分(黄、青、洋红),将单色和复色部分分别乘以与之对应的比率后相加,再加上最小值而得到灰度值。对于每个单独的像素来说,计算灰度值只需要用到上述6种颜色比率中的2种即可。在计算过程中可根据像素RGB相互关系选择对应的单色和复色比率,如像素RGB的大小关系为R>G>B,单色比率选最大值R红色,复色比率则为最大值R与中间值G所形成的复色黄色。
用程序代码实现上面的灰度计算公式并不复杂,难点还是前面所说的根据像素RGB相互关系选择对应的单色和复色比率。在前天我写的《C++图像处理 -- 图像颜色混合(上)》文章中,已经实现了这项功能,同时,Photoshop图像黑白调整功能中附加的着色功能,也在文章中实现。本文的在上面文章代码基础上,编写一个相对简单的图像黑白调整界面,来实现图像动态黑白调整。
下面是用BCB2007写的一个界面程序代码:
程序头文件部分:
//--------------------------------------------------------------------------- #ifndef bwMainH #define bwMainH //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ComCtrls.hpp> #include <Dialogs.hpp> #include <ExtCtrls.hpp> #define USE_GDIPLUS #include "BmpData.h" //--------------------------------------------------------------------------- enum TLockType {ltEdit, ltTrack}; typedef Set<TLockType, ltEdit, ltTrack> TLockTypes; class TForm1 : public TForm { __published: // IDE-managed Components TPaintBox *PaintBox1; TLabel *Label1; TLabel *Label2; TLabel *Label3; TLabel *Label4; TLabel *Label5; TLabel *Label6; TLabel *Label7; TLabel *Label8; TLabel *Label9; TLabel *Label10; TLabel *Label11; TLabel *Label12; TLabel *Label13; TLabel *Label18; TComboBox *ComboBox1; TEdit *Edit1; TTrackBar *TrackBar1; TEdit *Edit2; TTrackBar *TrackBar2; TEdit *Edit3; TTrackBar *TrackBar3; TEdit *Edit4; TTrackBar *TrackBar4; TEdit *Edit5; TTrackBar *TrackBar5; TEdit *Edit6; TTrackBar *TrackBar6; TCheckBox *CheckBox1; TGroupBox *GroupBox1; TLabel *Label14; TLabel *Label15; TLabel *Label16; TLabel *Label17; TPaintBox *PaintBox2; TEdit *Edit7; TTrackBar *TrackBar7; TEdit *Edit8; TTrackBar *TrackBar8; TColorDialog *ColorDialog1; void __fastcall FormCreate(TObject *Sender); void __fastcall FormDestroy(TObject *Sender); void __fastcall ComboBox1Change(TObject *Sender); void __fastcall TrackBar1Change(TObject *Sender); void __fastcall Edit1Change(TObject *Sender); void __fastcall Edit1KeyPress(TObject *Sender, char &Key); void __fastcall Edit1Exit(TObject *Sender); void __fastcall CheckBox1Click(TObject *Sender); void __fastcall TrackBar7Change(TObject *Sender); void __fastcall Edit7Change(TObject *Sender); void __fastcall Edit7KeyPress(TObject *Sender, char &Key); void __fastcall PaintBox2Click(TObject *Sender); void __fastcall PaintBox1Paint(TObject *Sender); void __fastcall PaintBox2Paint(TObject *Sender); void __fastcall TrackBar8Change(TObject *Sender); private: // User declarations Bitmap *Source; // 源图像 Bitmap *Dest; // 调整后的图像 BitmapData srcData; BitmapData dstData; float bwColors[6]; // 灰度选项数组 int Bright; // 亮度 TTrackBar *TrackBars[6]; // 灰度选项条元件数组 TEdit *Edits[6]; // 灰度选项编辑框数组 TLockTypes Lock; Gdiplus::Rect rect; ARGBQuad MixColor; // 混合颜色 int __fastcall GetHue(void); int __fastcall GetSat(void); void __fastcall SetHue(int hue); void __fastcall SetSat(int sat); void __fastcall MixColorToHSV(void); void __fastcall HSVToMixColor(void); void __fastcall Execute(void); void __fastcall MixColorChange(void); public: // User declarations __fastcall TForm1(TComponent* Owner); __property int Hue = {read=GetHue, write=SetHue}; // 色相 __property int Sat = {read=GetSat, write=SetSat}; // 饱和度 }; //--------------------------------------------------------------------------- const CustomIndex = 11; // 自定义选项索引 const DefaultTint = 0xe1d3b3; // 缺省混合颜色 const int DefOptions[][6] = // 预定义灰度选项 { {40, 60, 40, 60, 20, 80}, {128, 128, 100, 100, 128, 100}, {100, 100, 100, 100, 100, 100}, {0, 0, 0, 0, 0, 0}, {-40, 235, 144, -68, -3, -107}, {120, 110, -10, -50, 0, 120}, {50, 120, 90, 50, 0, 0}, {0, 0, 0, 110, 110, 110}, {120, 120, -10, -50, -50, 120}, {-50, -50, -50, 150, 150, 150}, {120, 110, 40, -30, 0, 70} }; extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif
代码文件部分:
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "bwMain.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- ULONG gdiplusToken; typedef FLOAT BWParams, *PBWParams; // 黑白调整缺省参数:红,黄,绿,洋红,蓝,青 CONST INT _BWDefault[] = {410, 614, 410, 819, 205, 614}; enum { BWIndexBlue = 0x40000, BWIndexGreen = 0x20000, BWIndexRed = 0x00000 }; enum { IndexBlue = 0x00000, IndexGreen = 0x10000, IndexRed = 0x20000 }; typedef union // 颜色分量交换结构 { INT tmp; // 交换时用的临时变量 struct { SHORT value; // 颜色分量值 SHORT index; // 颜色分量索引 }; }RGBIndex; //--------------------------------------------------------------------------- // 交换像素分量 FORCEINLINE VOID SwapRgb(RGBIndex &a, RGBIndex &b) { a.tmp ^= b.tmp; b.tmp ^= a.tmp; a.tmp ^= b.tmp; } //--------------------------------------------------------------------------- // 获取黑白灰度 FORCEINLINE INT GetBWGray(CONST PARGBQuad pixel, CONST PINT bwParams) { RGBIndex max, mid, min; min.tmp = pixel->Blue | BWIndexBlue; mid.tmp = pixel->Green | BWIndexGreen; max.tmp = pixel->Red | BWIndexRed; if (max.value < mid.value) SwapRgb(max, mid); if (max.value < min.value) SwapRgb(max, min); if (min.value > mid.value) SwapRgb(min, mid); return (((max.value - mid.value) * bwParams[max.index] + (mid.value - min.value) * bwParams[max.index + mid.index - 1] + 512) >> 10) + min.value; } //--------------------------------------------------------------------------- VOID ColorMix(PARGBQuad pd, CONST PARGBQuad ps, INT gray) { // 灰度计算常数:蓝,绿、红 CONST INT ys[3] = {113, 604, 307}; RGBIndex max, mid, min; min.tmp = ps->Blue | IndexBlue; mid.tmp = ps->Green | IndexGreen; max.tmp = ps->Red | IndexRed; if (max.value < mid.value) SwapRgb(max, mid); if (max.value < min.value) SwapRgb(max, min); if (min.value > mid.value) SwapRgb(min, mid); INT max_min = max.value - min.value; // 饱和度为0,返回灰度 if (max_min == 0) { pd->Blue = pd->Green = pd->Red = gray; return; } INT mid_min = mid.value - min.value; INT newMax, newMid, newMin; gray <<= 10; newMax = (gray + (max_min - mid_min) * ys[mid.index] + max_min * ys[min.index] + 512) >> 10; newMin = newMax - max_min; if (newMax > 255) { INT hueCoef = (mid_min << 10) / max_min; INT v0 = (ys[mid.index] * hueCoef) >> 10; INT v1 = ys[min.index] + ys[mid.index] - v0; newMin = (gray - (ys[max.index] + v0) * 255 + (v1 >> 1)) / v1; newMid = newMin + (((255 ^ newMin) * hueCoef + 512) >> 10); newMax = 255; } else if (newMin < 0) { INT hueCoef = (mid_min << 10) / max_min; INT tmp = ys[max.index] + ((ys[mid.index] * hueCoef + 512) >> 10); newMax = (gray + (tmp >> 1)) / tmp; newMid = (newMax * hueCoef + 512) >> 10; newMin = 1; } else newMid = newMin + mid_min; ((LPBYTE)pd)[max.index] = newMax; ((LPBYTE)pd)[mid.index] = newMid; ((LPBYTE)pd)[min.index] = newMin; } //--------------------------------------------------------------------------- // 图像黑白调整。 // 调整参数bwParams为元素数等于6的数组指针,分别为红,黄,绿,青,蓝,洋红 VOID ImageBWCopy(BitmapData *dest, CONST BitmapData *source, CONST PBWParams bwParams = NULL) { // 拷贝像素灰度参数,并交换青色和洋红色 INT params[6], *pparams; if (bwParams) { for (INT i = 0; i < 6; i ++) params[i] = (INT)(bwParams[i] * 1024 + 0.5); params[3] ^= params[5]; params[5] ^= params[3]; params[3] ^= params[5]; pparams = params; } else pparams = (INT*)_BWDefault; PARGBQuad pd, ps; UINT width, height; INT dstOffset, srcOffset; GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset); for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset) { for (UINT x = 0; x < width; x ++, pd ++, ps ++) { INT gray = GetBWGray(ps, pparams); pd->Blue = pd->Green = pd->Red = (gray & ~0xff) == 0? gray : gray > 255? 255 : 0; } } } //--------------------------------------------------------------------------- // 灰度图像染色。 VOID ImageTint(BitmapData *grayData, ARGB color) { ARGBQuad colorTable[256]; PARGBQuad p = colorTable; for (INT i = 0; i < 256; i ++, p ++) { ColorMix(p, (PARGBQuad)&color, i); p->Alpha = 255; } p = (PARGBQuad)grayData->Scan0; INT dataOffset = (grayData->Stride >> 2) - (INT)grayData->Width; for (UINT y = 0; y < grayData->Height; y ++, p += dataOffset) { for (UINT x = 0; x < grayData->Width; x ++, p ++) { p->Color = colorTable[p->Blue].Color; } } } //--------------------------------------------------------------------------- void __fastcall TForm1::FormCreate(TObject *Sender) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); TrackBars[0] = TrackBar1; TrackBars[1] = TrackBar2; TrackBars[2] = TrackBar3; TrackBars[3] = TrackBar4; TrackBars[4] = TrackBar5; TrackBars[5] = TrackBar6; Edits[0] = Edit1; Edits[1] = Edit2; Edits[2] = Edit3; Edits[3] = Edit4; Edits[4] = Edit5; Edits[5] = Edit6; // 从文件装入图像到tmp Bitmap *tmp = new Bitmap(L"source1.jpg"); rect.Width = tmp->GetWidth(); rect.Height = tmp->GetHeight(); // 分别建立新的源和目标图像数据到srcData和dstData GetBitmapData(rect.Width, rect.Height, &srcData); GetBitmapData(rect.Width, rect.Height, &dstData); // 将tmp图像数据分别锁定拷贝到srcData和dstData tmp->LockBits(&rect, ImageLockModeRead | ImageLockModeWrite | ImageLockModeUserInputBuf, PixelFormat32bppARGB, &srcData); tmp->UnlockBits(&srcData); tmp->LockBits(&rect, ImageLockModeRead | ImageLockModeWrite | ImageLockModeUserInputBuf, PixelFormat32bppARGB, &dstData); tmp->UnlockBits(&dstData); delete tmp; // 分别用图像数据srcData和dstData建立位图Source和Dest // 注:图像数据结构用于数据处理,位图用于显示,这样即可绑定数据结构和位图, // 又能避免每次处理图像数据时的锁定和解锁操作 Source = new Bitmap(srcData.Width, srcData.Height, srcData.Stride, PixelFormat32bppARGB, (BYTE*)srcData.Scan0); Dest = new Bitmap(dstData.Width, dstData.Height, dstData.Stride, PixelFormat32bppARGB, (BYTE*)dstData.Scan0); ComboBox1Change(NULL); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { delete Dest; delete Source; FreeBitmapData(&dstData); FreeBitmapData(&srcData); GdiplusShutdown(gdiplusToken); } //--------------------------------------------------------------------------- // 执行图像黑白调整 void __fastcall TForm1::Execute(void) { for (int i = 0; i < 6; i ++) // 获取灰度选项条数据 bwColors[i] = TrackBars[i]->Position / 100.0; ImageBWCopy(&dstData, &srcData, bwColors); // 源图黑白调整到目标图 if (CheckBox1->Checked && Sat) // 如果色调选项被选,着色 ImageTint(&dstData, MixColor.Color); PaintBox1Paint(NULL); // 显示图像 } //--------------------------------------------------------------------------- // 预设黑白调整选项改变 void __fastcall TForm1::ComboBox1Change(TObject *Sender) { if (ComboBox1->ItemIndex == CustomIndex) return; MixColor.Color = DefaultTint; // 设置缺省混合颜色 MixColorToHSV(); // 计算并设置缺省色相、饱和度控件 Lock = TLockTypes() << ltEdit << ltTrack; try { for (int i = 0; i < 6; i ++) // 装入预设的选项数据到相应的控件 { TrackBars[i]->Position = DefOptions[ComboBox1->ItemIndex][i]; Edits[i]->Text = DefOptions[ComboBox1->ItemIndex][i]; } if (CheckBox1->Checked) CheckBox1->Checked = false; // 取消色调选项 else Execute(); } __finally { Lock.Clear(); } } //--------------------------------------------------------------------------- // 黑白调整数据选项条改变 void __fastcall TForm1::TrackBar1Change(TObject *Sender) { if (Lock.Contains(ltTrack)) return; Lock = TLockTypes() << ltEdit; try { TTrackBar *bar = (TTrackBar*)Sender; Edits[bar->Tag]->Text = bar->Position; ComboBox1->ItemIndex = CustomIndex; // 预设下拉框设置为自定义 Execute(); } __finally { Lock.Clear(); } } //--------------------------------------------------------------------------- // 黑白调整数据编辑框改变 void __fastcall TForm1::Edit1Change(TObject *Sender) { if (Lock.Contains(ltEdit)) return; TEdit *edit = (TEdit*)Sender; if (edit->Text != "" && edit->Text != "-") TrackBars[edit->Tag]->Position = StrToInt(edit->Text); } //--------------------------------------------------------------------------- void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key) { if (Key >= ' ' && Key != '-' && (Key < '0' || Key > '9')) Key = 0; } //--------------------------------------------------------------------------- void __fastcall TForm1::Edit1Exit(TObject *Sender) { TEdit *edit = (TEdit*)Sender; if (edit->Text == "") edit->Text = TrackBars[edit->Tag]->Position; } //--------------------------------------------------------------------------- // 混合颜色改变,画混合颜色,显示其RGB值 void __fastcall TForm1::MixColorChange(void) { PaintBox2Paint(NULL); Label18->Caption = "R: " + IntToStr(MixColor.Red) + ", G: " + MixColor.Green + ", B: " + MixColor.Blue; Execute(); } //--------------------------------------------------------------------------- inline void RgbSwap(int &a, int &b) { a ^= b; b ^= a; a ^= b; } // 按混合颜色计算并改变HSV void __fastcall TForm1::MixColorToHSV(void) { int max, mid, min; max = MixColor.Red; mid = MixColor.Green; min = MixColor.Blue; if (max < mid) RgbSwap(max, mid); if (max < min) RgbSwap(max, min); if (min > mid) RgbSwap(min, mid); int max_min = max - min; if (max_min == 0) { Hue = 0; Sat = 0; } else { int H; if (max == MixColor.Red) H = ((MixColor.Green - MixColor.Blue) * 60 + 30) / max_min; else if (max == MixColor.Green) H = ((MixColor.Blue - MixColor.Red) * 60 + 30) / max_min + 120; else H = ((MixColor.Red - MixColor.Green) * 60 + 30) / max_min + 240; Hue = H < 0? H + 360 : H; Sat = (max_min * 100) / max; } Bright = max; } //--------------------------------------------------------------------------- inline ARGB RgbToColor(int r, int g, int b) { return (r << 16) | (g << 8) | b; } // 按HSV计算并改变混合颜色 void __fastcall TForm1::HSVToMixColor(void) { if (Sat == 0) { MixColor.Blue = MixColor.Green = MixColor.Red = Bright; } else { int index = Hue / 60; int f = Hue % 60; if ((index & 1) == 0) f = 60 - f; int a = Bright; int b = (Bright * (6000 - Sat * f)) / 6000; int c = (Bright * (100 - Sat)) / 100; switch (index) { case 0: MixColor.Color = RgbToColor(a, b, c); break; case 1: MixColor.Color = RgbToColor(b, a, c); break; case 2: MixColor.Color = RgbToColor(c, a, b); break; case 3: MixColor.Color = RgbToColor(c, b, a); break; case 4: MixColor.Color = RgbToColor(b, c, a); break; case 5: MixColor.Color = RgbToColor(a, c, b); } } MixColorChange(); } //--------------------------------------------------------------------------- int __fastcall TForm1::GetHue(void) { return TrackBar7->Position; } //--------------------------------------------------------------------------- int __fastcall TForm1::GetSat(void) { return TrackBar8->Position; } //--------------------------------------------------------------------------- void __fastcall TForm1::SetHue(int hue) { if (Hue == hue) return; Lock = TLockTypes() << ltEdit << ltTrack; try { TrackBar7->Position = hue; Edit7->Text = hue; } __finally { Lock.Clear(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::SetSat(int sat) { if (Sat == sat) return; Lock = TLockTypes() << ltEdit << ltTrack; try { TrackBar8->Position = sat; Edit8->Text = sat; } __finally { Lock.Clear(); } } //--------------------------------------------------------------------------- // 色调选盒改变 void __fastcall TForm1::CheckBox1Click(TObject *Sender) { Label14->Enabled = CheckBox1->Checked; Label15->Enabled = CheckBox1->Checked; Label16->Enabled = CheckBox1->Checked; Label17->Enabled = CheckBox1->Checked; Label18->Visible = CheckBox1->Checked; Edit7->Enabled = CheckBox1->Checked; Edit8->Enabled = CheckBox1->Checked; TrackBar7->SliderVisible = CheckBox1->Checked; TrackBar8->SliderVisible = CheckBox1->Checked; if (CheckBox1->Checked) ComboBox1->ItemIndex = CustomIndex; MixColorChange(); } //--------------------------------------------------------------------------- // 色相选项条改变 void __fastcall TForm1::TrackBar7Change(TObject *Sender) { if (!Lock.Contains(ltTrack)) Edit7->Text = TrackBar7->Position; } //--------------------------------------------------------------------------- // 饱和度选项条改变 void __fastcall TForm1::TrackBar8Change(TObject *Sender) { if (!Lock.Contains(ltTrack)) Edit8->Text = TrackBar8->Position; } //--------------------------------------------------------------------------- // 色相或者饱和度编辑框改变 void __fastcall TForm1::Edit7Change(TObject *Sender) { TEdit *edit = (TEdit*)Sender; if (Lock.Contains(ltEdit) || edit->Text == "") return; Lock = TLockTypes() << ltTrack; try { int val = StrToInt(edit->Text); TTrackBar *bar = edit->Tag == 0? TrackBar7 : TrackBar8; if (bar->Position != val) bar->Position = val; HSVToMixColor(); } __finally { Lock.Clear(); } } //--------------------------------------------------------------------------- void __fastcall TForm1::Edit7KeyPress(TObject *Sender, char &Key) { if (Key >= ' ' && (Key < '0' || Key > '9')) Key = 0; } //--------------------------------------------------------------------------- // 调用颜色对话框选择混合颜色 void __fastcall TForm1::PaintBox2Click(TObject *Sender) { if (CheckBox1->Checked && ColorDialog1->Execute(Handle)) { MixColor.Color = (ARGB)ColorDialog1->Color; MixColor.Blue = MixColor.Red; MixColor.Red = (BYTE)ColorDialog1->Color; MixColorToHSV(); MixColorChange(); } } //--------------------------------------------------------------------------- // 画黑白调整图像和源图像 void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { Gdiplus::Graphics *g = new Gdiplus::Graphics(PaintBox1->Canvas->Handle); try { g->DrawImage(Dest, rect); if (Sender != NULL) { g->TranslateTransform(0, rect.Height); g->DrawImage(Source, rect); } } __finally { delete g; } } //--------------------------------------------------------------------------- // 画混合颜色 void __fastcall TForm1::PaintBox2Paint(TObject *Sender) { if (CheckBox1->Checked) PaintBox2->Canvas->Brush->Color = (MixColor.Blue << 16) | (MixColor.Green << 8) | MixColor.Red; else PaintBox2->Canvas->Brush->Color = Color; PaintBox2->Canvas->Pen->Color = Color; PaintBox2->Canvas->Rectangle(PaintBox2->ClientRect); } //---------------------------------------------------------------------------
界面程序中,实现图像黑白调整功能主要靠Execute函数完成。
下面是几张程序运行界面图:
1、缺省黑白调整参数运行界面,其中右上边的下拉编辑框显示的是一些预设黑白效果选项:
2、选择红外线效果黑白调整参数运行界面:
3、使用缺省参数进行黑白调整后,再用所选颜色进行着色的界面:
4、在上面界面基础上,色调不变,加大黄色调参数,使图中人物衣服颜色明亮一些,同时减少蓝色调参数,使人物的围脖颜色变暗一些:
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《C++图像处理 -- 文章索引》。