? ? 圖像亮度、對比度和銳度是圖像質量感知的重要參數,調節這些屬性常用于圖像增強、圖像美化或圖像分析的預處理階段。本文將基于 OpenCV 實現這三項基礎圖像處理功能,并提供滑動條交互界面與直方圖可視化分析,方便調試和理解效果。
亮度調整
? ? 圖像亮度調整使得圖像整體變亮或變暗,其數學表達式為:dst(x,y)=src(x,y) + beta。圖像亮度調整本質上改變了圖像像素灰度分布。如亮度加30使得其灰度分布向正方向平移30,反之則向負方向平移30。這里的灰度分布我們稱之為圖像直方圖。我們使用眼底圖像作為示例來展示效果:


? ? 從直方圖上我們可以清晰看出,亮度調整會導致直方圖左右平移。我們需要注意的是,當亮度調整到飽和狀態,如調整后亮度大于255或者小于0,可能會因為階段而改變直方圖形狀。
? ? 如何使用代碼實現圖像亮度調整,直觀代碼如下:
for (int row = 0; row < src.rows; ++row)
{uchar* data = src.ptr<uchar>(row);for (int col = 0; col < src.cols * src.channels(); ++col){// 遍歷所有點,對每個點執行亮度調整adj_brightness操作int val = data[col] + adj_brightness;if (val < 0) val = 0;if (val > 255) val = 255;data[col] = val;}
}
? ? 以上代碼從原理上給出了一個實現邏輯,有利于我們理解具體實現。但該代碼效率較低,主要原因包括:
? ? 1)內層for循環的條件語句可能打斷流水線優化,流水線優化利用CPU的指令流水線結構實現多個指令并行處理。
? ? 2)可能沒有有效利用SIMD指令,CPU通過SIMD指令可以實現多條數據同時處理的效果,也稱作向量化。
? ? 由于OpenCV經過了大量的優化,通過調用OpenCV接口,在絕大多數情況下可以獲得極佳的性能,因此我們會盡量使用OpenCV提供的接口來構造功能。
? ?通過cv::Mat提供的接口convertTo可以實現圖像亮度的調整,該函數的核心功能包括:
? ?1)數據類型轉換,如從CV_8U轉換為CV_32F。
? ?2)在轉換過程中執行dst = src * alpha + beta操作,我們將alpha參數設置為1,beta參數設置為亮度調整參數即可實現亮度的調整。
? ? 為什么要調用convertTo實現亮度調整,而不是直接使用cv::add或者直接mat=mat+offset呢?
? ? 其實以上方法都可以實現亮度調整,這里使用convertTo的主要原因是該函數可以同步提高亮度調整與數據類型轉換功能。通過一個函數實現兩個功能,可以避免內存的多次拷貝,從而提升運行效率。
? ? 那么,為什么我們需要做數據類型轉換呢?主要有兩點理由:
? ? 1)我們一般將原始的8位無符號整數(CV_8U)圖像轉換為32位(CV_32F)浮點圖像。在后續圖像處理運算中,不可避免會產生浮點數值。如果仍舊使用整形表示則會產生階段誤差,從而降低調整后圖像的信噪比。
? ? 2)在某些運算中可能產生負數,CV_8U類型無法有效表示負數,從而產生運算錯誤。
? ?以下是亮度調整與類型轉換的代碼:
cv::Mat img_src_f;img_src.convertTo(img_src_f, CV_32F, 1., adj_brightness);
對比度調整?
? ??對比度控制圖像的明暗差異,通常用一個比例因子alpha控制像素的擴展或壓縮,其數學表達式可寫作:dst(x,y) = src(x,y) * alpha。當alpha>1時對比度增強,當alpha<1時對比度降低。
? ? 通過以上方法,我們會發現調整后圖像的對比度會顯著影響圖像亮度。如增強對比度時也同樣增強了圖像亮度。如下圖:

?

? ? 從直方圖上來看,圖像平均亮度確實增加了50%,同時直方圖的寬度也有所增加(即動態范圍更大)。在現實應用中,我們在增加對比度時更加希望圖像平均亮度基本保持不變,那么應該如何處理呢?
改進的對比度調整
? ? 為了保持圖像亮度基本不變,我們可以引入一些對稱性操作。在上一節內容中,圖像對比度的擴張是以0點為原點進行縮放,即dst(x,y)=[src(x,y) - 0] * alpha。如果我們以圖像均值為原點進行對比度縮放,則可以確保對比度縮放后的圖像平均亮度基本保持不變,其表達式為:,這里的mean為圖像的統計均值。
? ? 使用該改進后方案,我們的對比度調整有如下效果:


? ? 以上直方圖表明,由于對比度增加使得直方圖動態范圍增加,但確保持了平均亮度。這正是我們在對比度調整時所期望的效果。
? ? 另外,對比度調整參數的取值范圍理論上為。實際應用中,我們可能將其限制在
或者
。
銳度調整
? ? 我們之前討論的圖像亮度與圖像對比度都是從全局角度來控制圖像效果。因此,每一個有效的條件都會改變直方圖的基本形態,如導致直方圖平移或者縮放等。關于圖像銳度的調整主要改變圖像邊緣清晰程度,這是一個鄰域操作。也就是說,每一個像素變換后的值僅與其相鄰的像素相關。
? ?如果我們可以獲取圖像上每一個點的邊緣強度信息,那么我們就可以通過對該位置的強度進行疊加,從而獲得邊緣更強的圖像。因此,一個核心點就是如何獲得每個點的邊緣強度信息。
? ? 我們這里使用一個反銳化掩膜方式(Unsharp Masking)來獲取每個點的邊緣強度。公式如下:
?,通過原圖減去高斯模糊圖像獲取高頻信息,作為每個點的邊緣強度表征。
? ? 然后再在原圖上疊加邊緣強度信息,并通過參數控制疊加強度,如:
,
為增強系數,其范圍為
。


? ? 從以上直方圖來看,銳度增強基本沒有改變直方圖的動態范圍與均值,這也進一步驗證圖像銳化操作是一個鄰域操作而非全局變換。同時,我們的銳化效果似乎也比較理想。然而仔細觀察會發現:圖像邊緣雖然得到了有效增強,但圖像噪聲似乎也被增強了。我們需要進一步接近該問題。
改進的銳度調整
? ? 要想抑制銳化增強后的噪聲,我們首先需要明白噪聲的特點。噪聲相對于信號來說比較弱,在一個完美信號上疊加隨機噪聲有如下效果:
? ? 如上圖所示,上半部分是一個理想的階躍信息和疊加隨機噪聲的階躍信號,下圖是疊加隨機噪聲階躍信號的梯度響應。我們在做圖像增強時,期望增強的邊緣顯然是0坐標附近的階躍邊緣,而在非零附近區域的噪聲響應是我們期望忽略掉的。?從梯度響應上來看(紅色曲線),階躍信號有一個很強的響應,噪聲信號的響應較弱,這就帶給給我們一個自適應邊緣增強的啟示。
? ? 如果我們將每個點的梯度響應強度作為其增強系數因素,則可以通過選擇合適的策略在增強信號同時抑制噪聲。具體如下:
? ? 1)將梯度響應歸一化到(0,1)區間;
? ? 2)對現有銳度增強方案添加適當的控制因子,以實現增強信號同時抑制噪聲,公式如下:。
? ? 以下為改進后銳化增強結果:??

? ? 很明顯,通過引入梯度響應權值,我們能夠獲得更加理想的銳化增強圖像。
基于OpenCV的圖像調整實現?
? ? 下面給出一個完整的基于OpenCV圖像調整實現,通過引入Trackbar,我們可以做一個簡單的實時調節交互。具體代碼如下:
#include "opencv2/highgui/highgui_c.h"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"// 設置滑動條綁定參數變量,該變量必須為整數類型
// 同時分別設置滑塊的取值范圍,如下:
// 亮度[0,512], 對比度[0,500], 銳度[0,1000]
int bar_brightness = 256;
int bar_contrast = 0;
int bar_sharpness = 0;cv::Mat img_src, img_dst;void applyAdjustment(int, void*)
{// 根據對應滑塊的取值范圍換算出不同類型圖像調整的參數值// 換算后的取值范圍為:亮度[-256,256], 對比度[1.,6.],銳度[0.,10.]float adj_brightness = bar_brightness - 256;float adj_contrast = bar_contrast / 100. + 1.;float adj_sharpness = bar_sharpness / 100.;// 通過convertTo函數同步實現了浮點類型轉換和亮度調整,使得計算效率更高cv::Mat img_src_f;img_src.convertTo(img_src_f, CV_32F, 1., adj_brightness);// 對比度增強// 注意:以下矩陣img_src_f, img_diff, img_con均公用數據區(淺拷貝)cv::Scalar m = cv::mean(img_src_f);cv::Mat img_diff = img_src_f - m;cv::Mat img_con = img_diff * adj_contrast + m;// 計算自適應增強系數cv::Mat img_green;cv::extractChannel(img_con, img_green, 1);cv::GaussianBlur(img_green, img_green, cv::Size(3, 3), 0);cv::Mat grad_dx, grad_dy, grad_mag, grad_magc3;cv::Sobel(img_green, grad_dx, CV_32F, 1, 0, 3);cv::Sobel(img_green, grad_dy, CV_32F, 0, 1, 3);cv::magnitude(grad_dx, grad_dx, grad_mag);cv::normalize(grad_mag, grad_mag, 0.0, 1.0, cv::NORM_MINMAX);cv::cvtColor(grad_mag, grad_magc3, CV_GRAY2BGR);// 銳化增強cv::Mat img_blur;cv::GaussianBlur(img_con, img_blur, cv::Size(15, 15), 0);cv::Mat img_diff2 = cv::Mat::zeros(img_con.size(), img_con.type());img_diff2 = img_con - img_blur;cv::Mat img_con2= cv::Mat::zeros(img_con.size(), img_con.type());img_con2 = img_con + img_diff2.mul(grad_magc3) * adj_sharpness;// 轉換為8位深度圖像,用于顯示img_con2.convertTo(img_dst, CV_8U);cv::imshow("調整圖像", img_dst);
}void briConSharpAdjust()
{img_src = cv::imread("fundus3.png", cv::IMREAD_COLOR);if (img_src.empty()) return; // 檢查是否成功讀取圖像// 打開圖像調整窗口cv::namedWindow("調整圖像", cv::WINDOW_NORMAL);// 創建Trackbarcv::createTrackbar("亮度", "調整圖像", &bar_brightness, 512, applyAdjustment);cv::createTrackbar("對比度", "調整圖像", &bar_contrast, 500, applyAdjustment);cv::createTrackbar("銳度", "調整圖像", &bar_sharpness, 1000, applyAdjustment);applyAdjustment(0, 0);cv::waitKey(0);}int main()
{briConSharpAdjust();return 0;
}
結語
? ? 本文深入淺出的講解了圖像調整的基本算法,通過啟發式的方式引入一些深入問題研究。同時給出了一個完整的Demo,該Demo基于OpenCV進行UI交互,可以實現基本的圖像調整演示。通過對Demo改進可以快速落地一些相關應用。
? ? 另外,我們將所有的圖像處理均放在一個函數中,包括圖像亮度對比度,圖像銳度處理。對于小圖像來說,這樣做當然沒有太多問題。當我們在項目中要求更高的實時性,或者所處理圖像的尺寸特別大(如4000*3000),那么我們可以拆分每項處理,從而達到更好的實時性。主要策略包括:
? ? 1)拆分對比度與銳度處理,每次調整僅做一項調整,這也是用戶交互的一個自然邏輯;
? ? 2)保存中間圖像,如第一次實現銳度增強后,第二次進行對比度調整時基于銳度增強圖像即可;
? ? 3)所保存的中間圖像一定為浮點類型,盡可能規避整形圖像的截斷誤差累積;
? ? 4)在用戶拉動滑塊時(如銳度調整),一些公用數據僅需要計算一次即可,如自適應增強系數,差分圖等。?