戰(zhàn))
OpenCV 直方圖均衡化完全指南equalizeHist 原理、源碼剖析與 C/Python 實(shí)戰(zhàn)【免費(fèi)下載鏈接】opencvOpen Source Computer Vision Library項(xiàng)目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv本指南圍繞 OpenCV 官方直方圖均衡化教程 histogram_equalization.markdown 展開(kāi)系統(tǒng)講解圖像直方圖的基本概念、直方圖均衡化的數(shù)學(xué)原理CDF 重映射并深入cv::equalizeHist的函數(shù)實(shí)現(xiàn)與配套示例代碼。讀完本文你將理解什么是圖像直方圖、均衡化為何能提升對(duì)比度、equalizeHist的算法與邊界約束并能在 C 與 Python 項(xiàng)目中直接復(fù)現(xiàn)這一經(jīng)典圖像增強(qiáng)流程。本教程為 OpenCV 官方教程O(píng)riginal author: Ana Huamán兼容 OpenCV 3.0核心函數(shù)為cv::equalizeHist直方圖均衡化。學(xué)習(xí)目標(biāo)通過(guò)本篇教程你將掌握什么是圖像直方圖以及它為何有用什么是直方圖均衡化它如何拉伸像素強(qiáng)度分布如何使用 OpenCV 的cv::equalizeHist函數(shù)對(duì)圖像做直方圖均衡化均衡化前后圖像及其直方圖的變化規(guī)律作為效果驗(yàn)證手段。什么是圖像直方圖圖像直方圖是對(duì)圖像灰度強(qiáng)度分布的圖形化表示。它統(tǒng)計(jì)圖像中每一個(gè)強(qiáng)度值bin所對(duì)應(yīng)的像素個(gè)數(shù)橫軸是強(qiáng)度值Intensity縱軸是該強(qiáng)度下的像素?cái)?shù)量Pixels。對(duì)一幅 8 位灰度圖而言強(qiáng)度范圍是 0~255因此直方圖通常包含 256 個(gè)統(tǒng)計(jì)項(xiàng)。下圖是教程中的示例左側(cè)為灰度原圖右側(cè)紅色部分即其直方圖——可以明顯看到像素強(qiáng)度大多聚集在較低強(qiáng)度的區(qū)域直方圖的價(jià)值在于它能用一張二維圖直觀(guān)暴露圖像的整體明暗分布與對(duì)比度狀況。例如低對(duì)比度圖像通常表現(xiàn)為像素集中在較窄的強(qiáng)度區(qū)間內(nèi)而高對(duì)比度圖像的直方圖則會(huì)更均勻地鋪滿(mǎn)整個(gè)強(qiáng)度范圍。直方圖的繪制方法參見(jiàn)倉(cāng)庫(kù)中的 直方圖計(jì)算教程。什么是直方圖均衡化直方圖均衡化是一種提升圖像對(duì)比度的方法其目的就是把聚集在狹窄區(qū)間內(nèi)的像素強(qiáng)度向整個(gè)可用強(qiáng)度范圍拉伸stretch out。如下圖所示左側(cè)原始直方圖中像素強(qiáng)度大量聚集在中低強(qiáng)度區(qū)域中間是均衡化后的直方圖分布明顯更寬、更均勻右側(cè)為均衡化后增強(qiáng)出的圖像亮度與細(xì)節(jié)都得到改善。圖中綠色圓圈標(biāo)出的即是均衡化前像素稀疏underpopulated的強(qiáng)度區(qū)間均衡化之后這些區(qū)間也被合理填充這種方法的本質(zhì)是將圖像原有的直方圖這一種概率分布重映射mapping為另一種更寬、更均勻的強(qiáng)度分布使圖像明暗層次鋪滿(mǎn)整個(gè)取值范圍從而增強(qiáng)視覺(jué)對(duì)比度。均衡化的數(shù)學(xué)原理為什么用累積分布函數(shù)均衡化實(shí)質(zhì)上是一個(gè)強(qiáng)度重映射過(guò)程。要讓像素在新分布中盡可能均勻地鋪開(kāi)需要使用原直方圖的累積分布函數(shù)Cumulative Distribution Function, CDF作為映射函數(shù)。設(shè)原直方圖為 \(H(i)\)其中 \(i\) 為強(qiáng)度值其累積分布 \(H(i)\) 定義為H(i) Σ_{0 ≤ j i} H(j)即強(qiáng)度小于i的所有像素總數(shù)。為了把它直接用作重映射查找表LUT還需要對(duì) \(H(i)\) 做歸一化使其最大值等于圖像允許的最大強(qiáng)度值對(duì) 8 位圖即 255。歸一化后的累積分布函數(shù)曲線(xiàn)如下圖所示最后只需按下面的映射式逐像素重映射即可得到均衡化后的圖像equalized(x, y) H( src(x, y) )也就是說(shuō)對(duì)原圖每個(gè)像素用其灰度值在歸一化 CDF 中查表把查到的累積概率放大到 0~255 后作為新灰度寫(xiě)入輸出圖。由于 CDF 是單調(diào)遞增的原圖中較暗的區(qū)域會(huì)被分配到較低的灰度、較亮區(qū)域被分配到較高灰度累積概率差較大的區(qū)間被拉開(kāi)于是整體對(duì)比度增強(qiáng)。在 OpenCV 的官方頭文件注釋中該算法被描述為四個(gè)步驟見(jiàn) imgproc.hpp計(jì)算輸入src的直方圖 \(H\)歸一化直方圖使各 bin 之和為 255計(jì)算直方圖的積分 \(Hi \sum{0 \le j i} H(j)\)將 \(H\) 作為查找表對(duì)圖像逐像素變換\(\texttt{dst}(x,y) H(\texttt{src}(x,y))\)。頭文件同時(shí)給出了函數(shù)語(yǔ)義與約束該算法會(huì)歸一化圖像亮度并提升對(duì)比度src必須是8 位單通道灰度圖像dst與src同尺寸、同類(lèi)型。實(shí)戰(zhàn)示例程序做什么倉(cāng)庫(kù)中隨教程配套了完整可運(yùn)行的示例程序流程為加載一張輸入圖像將原圖轉(zhuǎn)換為灰度圖調(diào)用cv::equalizeHist對(duì)灰度圖做直方圖均衡化在同一窗口區(qū)域顯示原圖與均衡化后的圖像。C 版本示例源碼位于 samples/cpp/tutorial_code/Histograms_Matching/EqualizeHist_Demo.cpp完整代碼#include opencv2/imgcodecs.hpp #include opencv2/highgui.hpp #include opencv2/imgproc.hpp #include iostream using namespace cv; using namespace std; int main( int argc, char** argv ) { // [Load image] CommandLineParser parser( argc, argv, {input | lena.jpg | input image} ); Mat src imread( samples::findFile( parser.getString( input ) ), IMREAD_COLOR ); if( src.empty() ) { cout Could not open or find the image!\n endl; cout Usage: argv[0] Input image endl; return -1; } // [Load image] // [Convert to grayscale] cvtColor( src, src, COLOR_BGR2GRAY ); // [Convert to grayscale] // [Apply Histogram Equalization] Mat dst; equalizeHist( src, dst ); // [Apply Histogram Equalization] // [Display results] imshow( Source image, src ); imshow( Equalized Image, dst ); // [Display results] // [Wait until user exits the program] waitKey(); // [Wait until user exits the program] return 0; }編譯時(shí)鏈接opencv_imgcodecs、opencv_highgui、opencv_imgproc模塊即可。運(yùn)行時(shí)默認(rèn)輸入圖片為lena.jpg可通過(guò)第一個(gè)命令行參數(shù)指定任意圖片例如./EqualizeHist_Demo path/to/your/image。Python 版本示例源碼位于 samples/python/tutorial_code/Histograms_Matching/histogram_equalization/EqualizeHist_Demo.py完整代碼from __future__ import print_function import cv2 as cv import argparse # [Load image] parser argparse.ArgumentParser(descriptionCode for Histogram Equalization tutorial.) parser.add_argument(--input, helpPath to input image., defaultlena.jpg) args parser.parse_args() src cv.imread(cv.samples.findFile(args.input)) if src is None: print(Could not open or find the image:, args.input) exit(0) # [Load image] # [Convert to grayscale] src cv.cvtColor(src, cv.COLOR_BGR2GRAY) # [Convert to grayscale] # [Apply Histogram Equalization] dst cv.equalizeHist(src) # [Apply Histogram Equalization] # [Display results] cv.imshow(Source image, src) cv.imshow(Equalized Image, dst) # [Display results] # [Wait until user exits the program] cv.waitKey() # [Wait until user exits the program]運(yùn)行方式python EqualizeHist_Demo.py --input /path/to/your/image.jpg代碼逐步講解下面把示例拆成幾個(gè)關(guān)鍵步驟逐一說(shuō)明。1. 加載源圖像C 側(cè)使用CommandLineParser解析參數(shù)默認(rèn)圖片名lena.jpg再調(diào)用imread以彩色模式讀入并通過(guò)src.empty()判空兜底Python 側(cè)使用argparse接收--input同樣用cv.imreadNone判斷讀圖失敗。Mat src imread( samples::findFile( parser.getString( input ) ), IMREAD_COLOR ); if( src.empty() ) { /* 報(bào)錯(cuò)并返回 -1 */ }2. 轉(zhuǎn)換為灰度圖由于equalizeHist只接受單通道 8 位圖像需先用cvtColor把 BGR 三通道圖轉(zhuǎn)成灰度cvtColor( src, src, COLOR_BGR2GRAY );對(duì)應(yīng) Pythonsrc cv.cvtColor(src, cv.COLOR_BGR2GRAY)。注意若對(duì)三通道彩色圖直接調(diào)用equalizeHist會(huì)觸發(fā)源碼中的CV_Assert( _src.type() CV_8UC1 )斷言失敗詳見(jiàn)下文源碼剖析。如需增強(qiáng)彩色圖應(yīng)先將圖像轉(zhuǎn)換到Y(jié)CrCb/HSV/Lab等亮度與色度分離的色彩空間僅對(duì)亮度/明度通道做均衡化后再合并回來(lái)或?qū)γ總€(gè)通道單獨(dú)均衡化。這些屬于基于函數(shù)約束的工程處理方式官方示例保持灰度圖演示。3. 調(diào)用 equalizeHist 做直方圖均衡化Mat dst; equalizeHist( src, dst );對(duì)應(yīng) Pythondst cv.equalizeHist(src)。從調(diào)用方式可見(jiàn)該函數(shù)唯一的兩個(gè)參數(shù)就是輸入圖與輸出圖src為源 8 位單通道圖dst為與src同尺寸、同類(lèi)型的輸出圖。4. 顯示與退出用imshow分別展示 Source image 與 Equalized Image再調(diào)用waitKey()等待用戶(hù)按鍵退出程序。源碼級(jí)剖析equalizeHist 究竟怎么算cv::equalizeHist的實(shí)現(xiàn)位于 modules/imgproc/src/histogram.cpp與其頂層 SIMD 分發(fā)相關(guān)的內(nèi)核在 equalize_hist.dispatch.cpp 與 equalize_hist.simd.hpp。結(jié)合源碼可以看出完整執(zhí)行鏈路如下① 入?yún)⑿r?yàn)與運(yùn)行分支CV_Assert( _src.type() CV_8UC1 )強(qiáng)約束輸入必須是8 位單通道彩色圖/多通道圖在此直接報(bào)斷言錯(cuò)誤_src.empty()時(shí)空輸入直接返回CV_OCL_RUN(... ocl_equalizeHist(...))當(dāng)輸入為二維UMat且啟用了 OpenCL 時(shí)走 OpenCL 加速路徑CALL_HAL(equalizeHist, ...)優(yōu)先嘗試調(diào)用可插拔的 HAL 硬件加速實(shí)現(xiàn)。② 并行計(jì)算灰度直方圖代碼使用EqualizeHistCalcHist_Invoker計(jì)算直方圖其內(nèi)部枚舉HIST_SZ 256對(duì)應(yīng) 8 位灰度的全部取值見(jiàn) histogram.cpp。該類(lèi)派生自cv::ParallelLoopBody每個(gè)線(xiàn)程先在棧上維護(hù)localHistogram[256]處理完若干行后加總到全局直方圖當(dāng)EqualizeHistCalcHist_Invoker::isWorthParallel(src)判定并行收益足夠時(shí)通過(guò)parallel_for_按行切分并行執(zhí)行。③ 計(jì)算歸一化累積查找表LUT首先跳過(guò)直方圖中首個(gè)非零 bin記為索引iint i 0; while (!hist[i]) i;特殊情形若圖像所有像素都落在同一個(gè) binhist[i] total即整幅圖是單一灰度的平坦圖無(wú)法也不需要進(jìn)行均衡化直接dst.setTo(i)返回原值計(jì)算縮放因子并構(gòu)建 LUTfloat scale (hist_sz - 1.f)/(total - hist[i]); int sum 0; for (lut[i] 0; i hist_sz; i) { sum hist[i]; lut[i] saturate_castuchar(sum * scale); }可見(jiàn)其核心正是文檔中描述的對(duì)直方圖做累積積分后按 256/總像素?cái)?shù)歸一化lut從首個(gè)有效 bin 之后逐項(xiàng)累加直方圖值sum再乘以scale并用saturate_castuchar把結(jié)果安全截?cái)嗟?0~255。于是lut就是一條單調(diào)遞增、最大輸出接近 255 的歸一化累積分布查找表。④ 用 LUT 重映射整幅圖像最后創(chuàng)建EqualizeHistLut_Invoker把lut_u8查找表應(yīng)用到每一個(gè)像素dst(x,y) lut[src(x,y)]同樣地當(dāng)圖像足夠大時(shí)通過(guò)parallel_for_多線(xiàn)程并行完成。LUT 的實(shí)際讀寫(xiě)在內(nèi)核層由equalizeHistLut_系列實(shí)現(xiàn)并支持通過(guò) dispatch 機(jī)制針對(duì)不同 CPU 指令集選用 SIMD 優(yōu)化版本。綜上所述equalizeHist的內(nèi)存/時(shí)間成本為 O(N)N 為像素總數(shù)只額外使用 256 長(zhǎng)度的直方圖與 LUT 數(shù)組非常輕量。倉(cāng)庫(kù)內(nèi)對(duì)應(yīng)的回歸測(cè)試見(jiàn) modules/imgproc/test/test_histograms.cppOpenCL 路徑與 UMat 路徑分別在 ocl/test_imgproc.cpp 與 test_imgproc_umat.cpp 中覆蓋。運(yùn)行結(jié)果與效果驗(yàn)證教程推薦使用一張本身對(duì)比度較低的圖像來(lái)體會(huì)均衡化的改善。例如這張?jiān)紙D像低對(duì)比度注意其畫(huà)面整體偏灰、層次不清觀(guān)察它的直方圖Histogram_Equalization_Original_Histogram.jpg可以看到像素明顯聚集在直方圖的中心/中低區(qū)域兩端基本空缺這正是對(duì)比度不足的直接證據(jù)。經(jīng)過(guò)equalizeHist處理后得到如下結(jié)果圖像的整體對(duì)比度顯著提升明暗層次變得豐富再看它新生成的直方圖Histogram_Equalization_Equalized_Histogram.jpg像素已從原來(lái)聚集的窄區(qū)間鋪展到幾乎整個(gè) 0~255 強(qiáng)度范圍分布明顯更均勻。前后直方圖的這種從集中到鋪開(kāi)的變化就是均衡化生效的量化驗(yàn)證。想復(fù)現(xiàn)上圖中直方圖的繪制代碼可參考倉(cāng)庫(kù)內(nèi)的直方圖計(jì)算教程。使用建議與注意事項(xiàng)equalizeHist只接受 8 位單通道CV_8UC1灰度圖這是源碼CV_Assert強(qiáng)制的約束輸入其他類(lèi)型會(huì)直接拋出斷言異常輸入與輸出尺寸、類(lèi)型一致調(diào)用前無(wú)需手動(dòng)創(chuàng)建輸出矩陣平坦圖全部像素同灰度會(huì)被特殊處理函數(shù)直接輸出原灰度不產(chǎn)生無(wú)意義的增強(qiáng)均衡化是全局操作它依據(jù)整幅圖的累積直方圖映射不感知局部細(xì)節(jié)。對(duì)含噪圖像或存在大面積過(guò)曝/欠曝區(qū)域的場(chǎng)景全局均衡化可能放大噪聲或造成局部過(guò)增強(qiáng)此時(shí)可考慮 CLAHE對(duì)比度受限自適應(yīng)直方圖均衡化等局部方法作為替代若確實(shí)要增強(qiáng)彩色圖應(yīng)基于其亮度通道如YCrCb的Y或HSV的V進(jìn)行均衡化避免直接對(duì) BGR 三通道分別處理導(dǎo)致色偏。延伸閱讀直方圖均衡化是直方圖系列教程之一本教程的下一篇是直方圖計(jì)算教程其中演示了如何用 OpenCV 繪制上文所示的各類(lèi)直方圖直方圖系列還包括直方圖對(duì)比、反向投影與模板匹配等教程本文相關(guān)源碼與樣例匯總教程文檔histogram_equalization.markdownC 示例EqualizeHist_Demo.cppPython 示例EqualizeHist_Demo.py核心實(shí)現(xiàn)histogram.cpp、equalize_hist.dispatch.cpp、equalize_hist.simd.hppAPI 聲明imgproc.hpp單元測(cè)試test_histograms.cpp【免費(fèi)下載鏈接】opencvOpen Source Computer Vision Library項(xiàng)目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考