要調整正圖片的亮度和對比度, 首先要知道亮度和對比度的定義:
“明度”(Brightness)原來用做光度測定術語照度和(錯誤的)用於輻射測定術語輻射度的同義詞。按美國聯邦通信術語表(FS-1037C)的規定,明度現在只應用於非定量的提及對光的生理感覺和感知。[1]
一個給定目標亮度在不同的場景中可以引起不同的明度感覺;比如White錯覺和Wertheimer-Benary錯覺。
在 RGB 色彩空間中,明度可以被認爲是 R(紅色),G(綠色)和B(藍色)座標的算術平均 μ(儘管這三個成分中的某個要比其他看起來更明亮,但這可以被某些顯示系統自動補償):
- 。
- 即: dst[i] = (src[i + 0] + src[i + 1] + src[i + 2]) / 3 (三通道圖片)
明度也是 HSB 或 HSV 色彩空間(色相,飽和度和明度)中的顏色座標,它的值是這個顏色的 R,G 和 B 三者中的極大值。
對比度,具體的概念解釋可以參考Wiki或者百度百科。簡單的講對比度反應了圖片上亮區域和暗區域的層次感。而反應到圖像編輯上,調整對比度就是在保證平均亮度不變的情況下,擴大或縮小亮的點和暗的點的差異。既然是要保證平均亮度不變,所以對每個點的調整比例必須作用在該值和平均亮度的差值之上,這樣才能夠保證計算後的平均亮度不變,故有調整公式:
Out = Average + (In – Average) * ( 1 + percent)
其中In表示原始像素點亮度,Average表示整張圖片的平均亮度,Out表示調整後的亮度,而percent即調整範圍[-1,1]。證明這個公式的正確性相當簡單:
設圖上有n個像素點,各個點亮度爲Ai,平均亮度爲A,變化率爲alpha,則有:
|
但是實際處理中,並沒有太多的必要去計算一張圖的平均亮度:一來耗時間,二來在平均亮度上的精確度並不會給圖像的處理帶來太多的好處—-一般就假設一張圖的平均亮度爲128,即一半亮度,而一張正常拍照拍出來的圖平均亮度應該是在[100,150]。在肉眼看來兩者基本沒有任何區別,而如果真實地去計算平均亮度還會帶來很大的計算量。、
即: dst[i] = 128 + (src[i] – 128) * (nPercent) // nPercent = 1 + percent
簡單示例:
#include "highgui.h"
#pragma comment(lib,"cv200d.lib")
#pragma comment(lib,"cxcore200d.lib")
#pragma comment(lib,"highgui200d.lib")
int BrightnessAdjust(const IplImage* srcImg,
IplImage* dstImg,
float brightness)
{
assert(srcImg != NULL);
assert(dstImg != NULL);
int x,y,i;
float val;
for (i = 0; i < 3; i++)//彩色圖像需要處理3個通道,灰度圖像這裏可以刪掉
{
for (y = 0; y < srcImg->height; y++)
{
for (x = 0; x < srcImg->width; x++)
{
val = ((uchar*)(srcImg->imageData + srcImg->widthStep*y))[x*3+i];
val += brightness;
//對灰度值的可能溢出進行處理
if(val>255) val=255;
if(val<0) val=0;
((uchar*)(dstImg->imageData + dstImg->widthStep*y))[x*3+i] = (uchar)val;
}
}
}
return 0;
}
int ContrastAdjust(const IplImage* srcImg,
IplImage* dstImg,
float nPercent)
{
assert(srcImg != NULL);
assert(dstImg != NULL);
int x,y,i;
float val;
for (i = 0; i < 3; i++)//彩色圖像需要處理3個通道,灰度圖像這裏可以刪掉
{
for (y = 0; y < srcImg->height; y++)
{
for (x = 0; x < srcImg->width; x++)
{
val = ((uchar*)(srcImg->imageData + srcImg->widthStep*y))[x*3+i];
val = 128 + (val - 128) * nPercent;
//對灰度值的可能溢出進行處理
if(val>255) val=255;
if(val<0) val=0;
((uchar*)(dstImg->imageData + dstImg->widthStep*y))[x*3+i] = (uchar)val;
}
}
}
return 0;
}
int main(int argc, char** argv)
{
IplImage* srcImg = cvLoadImage("lena.jpg");
assert( srcImg != NULL );
IplImage* brightnessImg = cvCloneImage(srcImg);
//亮度變換,最後數值取值爲正時變亮,負則變暗
BrightnessAdjust(srcImg, brightnessImg, 80.0f);
IplImage* contrastImg = cvCloneImage(srcImg);
//對比度變換,數值小於1降低對比度,大於1增強對比度
ContrastAdjust(srcImg, contrastImg, 1.3f);
cvNamedWindow("Source",CV_WINDOW_AUTOSIZE);
cvNamedWindow("BrightnessAdjust",CV_WINDOW_AUTOSIZE);
cvNamedWindow("ContrastAdjust",CV_WINDOW_AUTOSIZE);
cvShowImage("Source",srcImg);
cvShowImage("BrightnessAdjust",brightnessImg);
cvShowImage("ContrastAdjust",contrastImg);
cvWaitKey(0);
cvReleaseImage(&srcImg);
cvReleaseImage(&brightnessImg);
cvReleaseImage(&contrastImg);
cvDestroyWindow("Source");
cvDestroyWindow("BrightnessAdjust");
cvDestroyWindow("ContrastAdjustrast");
return 0;
}
結果:
並且與photoshop的調整效果對比過
亮度變換與ps舊版效果一致,貌似ps對亮度變換的公式進行過調整,新版不是這麼單純的加減灰度值對比度就幾乎都差不多了
在《Delphi圖像處理 -- 亮度/對比度調整》一文實現了Photoshop的亮度/對比度調整功能,這是其C/C++版。
還是先簡單介紹一下Photoshop圖像亮度/對比度調整的原理:
一、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時,則相反,先調整對比度,再調整亮度。
下面是用BCB2007和GDI+位圖數據寫的Photoshop圖像亮度/對比度調整代碼,包括例子代碼:
//---------------------------------------------------------------------------
FORCEINLINE
INT CheckValue(INT value)
{
return (value & ~0xff) == 0? value : value > 255? 255 : 0;
}
//---------------------------------------------------------------------------
// 亮度/對比度調整
VOID BrightAndContrast(BitmapData *data, INT bright, INT contrast, BYTE threshold)
{
if (bright == 0 && contrast == 0)
return;
FLOAT cv = contrast <= -255? -1.0f : contrast / 255.0f;
if (contrast > 0 && contrast < 255)
cv = 1.0f / (1.0f - cv) - 1.0f;
BYTE values[256];
for (INT i = 0; i < 256; i ++)
{
INT v = contrast > 0? CheckValue(i + bright) : i;
if (contrast >= 255)
v = v >= threshold? 255 : 0;
else
v = CheckValue(v + (INT)((v - threshold) * cv + 0.5f));
values[i] = contrast <= 0? CheckValue(v + bright) : v;
}
PARGBQuad p = (PARGBQuad)data->Scan0;
INT offset = data->Stride - data->Width * sizeof(ARGBQuad);
for (UINT y = 0; y < data->Height; y ++, (BYTE*)p += offset)
{
for (UINT x = 0; x < data->Width; x ++, p ++)
{
p->Blue = values[p->Blue];
p->Green = values[p->Green];
p->Red = values[p->Red];
}
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L"..\\..\\media\\source1.jpg");
Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
g->DrawImage(bmp, 0, 0);
BitmapData data;
LockBitmap(bmp, &data);
BrightAndContrast(&data, 0, 255, 121);
UnlockBitmap(bmp, &data);
g->DrawImage(bmp, data.Width, 0);
delete g;
delete bmp;
}
//---------------------------------------------------------------------------