前言:
秦梓航同志是我的老朋友加老搭檔,他從國中開始接觸機器人,初三開始自己出項目,高二時他和冶子奕用樹莓派出的“小七”智能機器人進了高中機器人大賽的國賽,這些年獲得許多機器人相關比賽的國内省内獎項,是七号庫房的第一個社長。秦梓航同志到大學之後開始接觸OpenCV,早期在學校的Robomaster戰隊搞自瞄算法,後期在一家公司搞機器人/無人機算法。論專業水準,秦梓航同志一直是老吳的領路人,這篇部落格可以作為一個标杆,這是以後寫文章需要有的一種專業态度。

這篇文章主要講的通過對直方圖進行數學累計分布函數處理進而使得圖像參數直方圖均衡化。通過獲得像素值的機率密度求累計機率,然後乘位數最大量像素映射。文章同時包括了代碼實作,後面我會将其整理後發出。
正文:全局直方圖均衡化主要應用在圖像增益之中,用于提升圖像的對比度,簡單來說就是讓圖像亮的地方變暗一點,暗的地方變亮一些,整體提升圖像的動态範圍.上面的話聽起來可能不是那麼直覺,下面放兩張圖進行一下對比應該會好一些 P S :此處直方圖就是對圖像的灰階/亮度資訊進行統計記錄每個亮度等級的數量.
這是原圖像未經過處理,左邊是目前的亮度直方圖 ,可以看到亮度範圍比較集中在中間區域.
這是經過全局直方圖均衡化之後的圖像,經過參與處理之後的圖像對比度明顯提高.同樣的通過亮度直方圖可以看出目前的圖像動态範圍較之前有所提升。下面進入具體的理論部分首先看一下公式
或者是這個
當然這兩個公式其實差不多啦,表達的都是圖像的累計分布函數,乍一看确實有點摸不着頭腦,不知道這都是些啥?具體意義是什麼?這些我們先放到一邊,先來看幾個問題. 1.什麼是累計分布函數?
嚴格的數學定義和性質很容易查找到,我這裡還是引用一下
累積分布函數(Cumulative Distribution Function),又叫分布函數,是機率密度函數的積分,能完整描述一個實随機變量X的機率分布。一般以大寫CDF标記,,與機率密度函數probability density function(小寫pdf)相對。
累積分布函數表示:對離散變量而言,所有小于等于a的值出現機率的和.
如果是在圖像進行中,密度函數的含義也就是圖像中的亮度/灰階所占圖像總像素的多少,而累計分布函數則是目前亮度等級對于圖像整體出現的累計機率,根據CDF的定義這是很明顯的一件事.
舉個例子:有如下2*3單通道8位(0-255)圖像,像素值如圖所示
100 | 15 |
150 | 160 |
40 | 100 |
則密度函數的值為:
像素值 | 密度/出現的機率h(I) | 累計機率( C(I) ) |
40 | 出現1次:1/6 | 1/6 |
100 | 出現2次:2/6 = 1/3 | 1/6 + 1/3= 3/6 |
150 | 出現2次:2/6 = 1/3 | 1/6 + 1/3 +1/3 = 5/6 |
160 | 出現1次: 1/6 = 1/6 | 1/6 + 1/3 +1/3+1/6 = 1 |
2.為什麼使用累計分布函數可以完成全局直方圖均衡化?
直方圖均衡化的目标是提升圖像的動态範圍進而進行圖像增益,我們其實是通過累計分布函數建立一種映射關系,我們希望亮度值可以更加均勻的散布在整個亮度範圍之内對于8位圖像來說是(0-255),同樣的圖像的累計分布函數很好的反應了目前圖像亮度的分布規律,我們隻需要将其映射至[0,255]的整個空間之内便可以達到我們想要的效果.還是舉個例子:如上面的表格所示
像素值 | 密度機率h(I) | 累計機率 C(I) | 最終值(取整) |
40 | 出現1次:1/6 | 1/6 | 1/6*255 = 42.49=42 |
100 | 出現2次:2/6 = 1/3 | 1/6 + 1/3= 3/6 | 3/6*255 =128 |
150 | 出現2次:2/6 = 1/3 | 1/6 + 1/3 +1/3 = 5/6 | 5/6*255 = 213 |
160 | 出現1次: 1/6 = 1/6 | 1/6 + 1/3 +1/3+1/6 = 1 | 1*255 = 255 |
最終 圖像
128 | 213 |
213 | 255 |
42 | 128 |
可以看到圖像的整體動态範圍由原先的 160 - 40 = 120 變為了 255 - 42 = 213 範圍有所提升,從直方圖結果來看整體分布也更加均勻 ,
再來看一下之前忽略的函數部分:
其中c(I)也就是累計機率函數,1/N*h(I)就是密度機率我們要做的就是如下幾步:1.擷取圖像的灰階資訊.2.擷取像素值的密度機率3.根據密度機率求出累計機率4.将累計機率*255完成最終映射至此原理部分結束.程式部分
#include
#include
using namespace cv; using namespace std ; Mat src,gray,dst; void Test(Mat &img); int main(int argc, char *argv[])
{src =imread("/home/qinzihang/opencv-4.2.0/samples/data/lena.jpg"); resize(src, src, Size(200,200)); cvtColor(src, src,COLOR_BGR2GRAY); Test(src); imshow("dst", src); waitKey(0); }void Test(Mat &img)
{float gray_l[256] = {0}; float HI[256] = {0};//圖像亮度密度1/N*h(I) float CDF[256] = {0};//累積分布函數 int rows=img.rows; int cols=img.cols; Mat draw_mat = Mat::zeros(600,600,CV_8U); cout << rows << endl;//x for(int i=0; i; i++)
{for(int j=0; j; j++)
{
uchar t; if(img.channels()==1)
{
t=img.at(i,j); gray_l[t]++;//獲得圖像亮度資訊 }
}
}for(int i = 0; i <256; i++)
{
line(draw_mat,Point(i+150,600-1),Point(i+150, 600-gray_l[i]),254); imshow("draw", draw_mat); HI[i] = (gray_l[i]/(img.cols*img.rows*1.0f));//獲得密度機率 cout << HI[i] << endl; }for(int i = 0; i < 255; i++)
{if(i == 0)
{
CDF[0] = HI[ 0]; }else {
CDF[i] = (CDF[i-1] +HI[i]);//C(I) = C(I - 1 ) +h(I) cout << CDF[i] << endl; }
}for(int i=0; i; i++)
{for(int j=0; j; j++)
{
uchar t; if(img.channels()==1)
{
t=img.at(i,j); img.at(i,j) = 255*CDF[t];//完成圖像重映射至0-255 }
} draw_mat = Mat::zeros(600,600,CV_8U); }
imshow("yes",img); }
本文僅僅是我的一些淺薄的看法和對這個算法的一些思考,整個過程不是非常的嚴謹,希望看了之後多少對初學者了解全局直方圖均衡化這個算法有一些幫助,文章中的錯誤也希望大家可以指出。