目錄
前言
我們在拍照的時候,經常需要通過調整圖像的亮度及對比度來調整圖像的美感,讓我們拍出的照片更有意境。特別是女孩子,喜歡拍一些美美噠皁片!
但如果你的男朋友掌握不好角度,亮度和對比度,那可能就是大型拍照翻車現場了。
好吧,說回正題,如果我們不考慮拍的醜不醜,我們只想讓圖片亮度對比度調整一下,還是可以做到滴!接下來讓我們走進今天的內容吧!
一、亮度與對比度
1、什麼是亮度和對比度
亮度與對比度其實是圖像處理中的概念。我們在調整臺式電腦的顯示界面,就可以調整亮度與對比度。
亮度是指畫面的明亮程度;
對比度指的是一幅圖像中明暗區域最亮的白和最暗的黑之間不同亮度層級的測量,即指一幅圖像灰度反差的大小。差異範圍越大代表對比越大,差異範圍越小代表對比越小,好的對比率120:1就可容易地顯示生動、豐富的色彩,當對比率高達300:1時,便可支持各階的顏色。
上面是百科的定義,我們怎麼去理解呢?
亮度比較容易理解,陽光下的圖片一般都亮,夜晚的圖片一般都暗。我們也經常調整手機屏幕和電腦屏幕的亮度。
對比度我們常常聽到,從對比二字來看,我們可以理解爲兩個像素差距大不大,對比起來差距明顯不明顯。對比度越高,那兩個像素的差距越大。其實就是色彩空間中能夠表示的範圍,範圍越廣,對比越強烈,對比度越大,同樣的,展現出的圖像的效果也就越好。如果我們拍景色,在一個伸手不見五指的黑夜去拍,還不開閃光燈,拍出一個近乎純黑的景色,但如果是白天,晴空萬里,陽光明媚,我們再去拍攝,景物分明,而且非常迷人。這就是對比度小和對比度大的區別。
2、亮度和對比度調整原理
那如果我們想要調整亮度和對比度,我們其實就是調整像素值。
亮度其實調整的就是每個像素的大小,像素值越大,越接近白色,就越亮,像素值越小,越接近黑色圖像就越黑。所以調整亮度其實就是給像素值增加一個變量值,變量值爲正,就是調亮,變量值爲負,就是調暗。公式表示如下:
對比度調整的是像素區別,一般來說,圖像中相鄰位置的像素差別較小,我們想增強對比度,那就需要將差別按倍數擴大,要降低對比度,就要按比例縮小。所以調整對比度就是給像素值添加一個倍數,倍數大於1,就是增強對比度,倍數小於1,就是減少對比度。公式表示如下:
我們使用一個統一的公式來表示,就可以同時調整亮度和對比度,公式如下:
3、亮度和對比度調整代碼實現
瞭解了原理,我們就使用代碼實現一下吧!上面公式中的f(x)表示原始像素,g(x)表示調整後的像素。所以我們還需要用到我們之前講的圖像像素指針,來修改像素值,我們要考慮,我們修改的是三通道彩色圖像,還是灰度圖像。
如果是灰度圖像,我們修改方式如下:
float f = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = saturate_cast<uchar>(f*alpha + beta);
如果是彩色圖像,我們就需要修改每一個通道的值:
float b = m1.at<Vec3f>(row, col)[0];// blue
float g = m1.at<Vec3f>(row, col)[1]; // green
float r = m1.at<Vec3f>(row, col)[2]; // red
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
而對於一幅圖像,我們需要根據其類型來選擇不同的方式,所以我們需要判斷一下圖像的通道數:
if (src.channels() == 3) {
float b = m1.at<Vec3f>(row, col)[0];// blue
float g = m1.at<Vec3f>(row, col)[1]; // green
float r = m1.at<Vec3f>(row, col)[2]; // red
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
}
else if (src.channels() == 1) {
float f = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = saturate_cast<uchar>(f*alpha + beta);
}
有了這些實現的核心代碼,我們就可以遍歷所有的像素點,來調整圖像了。全部代碼如下:
Mat YT = imread("./image/YiTian1.jpg");
if (!YT.data)
{
cout << "ERROR : could not load image.\n";
return -1;
}
imshow("倚天屠龍記", YT);
Mat new_YT = YT.clone();
float alpha = 1.0; // 調整對比度
float beta = -50; // 調整亮度
YT.convertTo(YT, CV_32F);
for (int row = 0; row < YT.rows; row++) {
for (int col = 0; col < YT.cols; col++) {
if (YT.channels() == 3) {
float b = YT.at<Vec3f>(row, col)[0];// blue
float g = YT.at<Vec3f>(row, col)[1]; // green
float r = YT.at<Vec3f>(row, col)[2]; // red
new_YT.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
new_YT.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
new_YT.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
}
else if (YT.channels() == 1) {
float f = YT.at<uchar>(row, col);
new_YT.at<uchar>(row, col) = saturate_cast<uchar>(f*alpha + beta);
}
}
}
imshow("倚天-調整亮度", new_YT);
執行結果如下:
4、API-convertScaleAbs
OpenCV中提供了API-convertScaleAbs,可以實現類似的功能,這個API的功能是:
縮放、計算絕對值並將結果轉換爲8位。
API如下:
void convertScaleAbs(
InputArray src,
OutputArray dst,
double alpha = 1,
double beta = 0
);
參數含義如下:
(1)InputArray類型的src,輸入圖像。
(2)OutputArray類型的dst,輸出圖像。
(3)double類型的alpha,可選比例因子。。
(4)double類型的beta,添加到縮放值的可選增量。
舉個栗子:
Mat YT = imread("./image/YiTian1.jpg");
if (!YT.data)
{
cout << "ERROR : could not load image.\n";
return -1;
}
imshow("倚天屠龍記", YT);
Mat YT_convertScaleAbs;
convertScaleAbs(YT, YT_convertScaleAbs,1.9,-50);
imshow("倚天屠龍記-convertScaleAbs", YT_convertScaleAbs);
二、伽馬校正
1、伽馬校正引入
我們前面通過線性的方式調整圖像的亮度和對比度,調整之後,效果可能不是那麼理想,畢竟“直男”太直了!
所以我們需要考慮非線性調整方式,看看如果是非線性,會有什麼樣的效果呢?
這就是我們接下來要講的伽馬校正。
2、伽馬校正原理
伽馬校正數學公式很簡單:
I表示的是輸入圖像,O表示輸出圖像,γ是參數,我們可以通過調整不同的γ得到不同的結果:
3、伽馬校正代碼實現
理解了公式,我們實現也很簡單:
if (YT.channels() == 3) {
float b = YT.at<Vec3f>(row, col)[0];// blue
float g = YT.at<Vec3f>(row, col)[1]; // green
float r = YT.at<Vec3f>(row, col)[2]; // red
new_YT.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(pow(b / 255.0, gamma) * 255.0);
new_YT.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(pow(g / 255.0, gamma) * 255.0);
new_YT.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(pow(r / 255.0, gamma) * 255.0);
}
else if (YT.channels() == 1) {
float f = YT.at<uchar>(row, col);
new_YT.at<uchar>(row, col) = saturate_cast<uchar>(pow(f / 255.0, gamma) * 255.0);
}
有了這些實現的核心代碼,我們就可以遍歷所有的像素點,來調整圖像了。全部代碼如下:
Mat YT = imread("./image/YiTian1.jpg");
if (!YT.data)
{
cout << "ERROR : could not load image.\n";
return -1;
}
imshow("倚天屠龍記", YT);
Mat new_YT = YT.clone();
YT.convertTo(YT, CV_32F);
float gamma = 1.6;
for (int row = 0; row < YT.rows; row++) {
for (int col = 0; col < YT.cols; col++) {
if (YT.channels() == 3) {
float b = YT.at<Vec3f>(row, col)[0];// blue
float g = YT.at<Vec3f>(row, col)[1]; // green
float r = YT.at<Vec3f>(row, col)[2]; // red
new_YT.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(pow(b / 255.0, gamma) * 255.0);
new_YT.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(pow(g / 255.0, gamma) * 255.0);
new_YT.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(pow(r / 255.0, gamma) * 255.0);
}
else if (YT.channels() == 1) {
float f = YT.at<uchar>(row, col);
new_YT.at<uchar>(row, col) = saturate_cast<uchar>(pow(f / 255.0, gamma) * 255.0);
}
}
}
imshow("倚天-伽馬校正", new_YT);
執行結果如下:
今天我們講的就到這裏啦,大家要多做練習才能掌握更深哦!