文章目錄:
1 基本概述
CLAHE是一個比較有意思的圖像增強的方法,主要用在醫學圖像上面。之前的比賽中,用到了這個,但是對其算法原理不甚了解。在這里做一個復盤。
CLAHE起到的作用簡單來說就是增強圖像的對比度的同時可以抑制噪聲
CLAHE的英文是Contrast Limited Adaptive Histogram Equalization 限制對比度的自適應直方圖均衡。在學習這個之前,我們要先學習一下下面的前置算法:
- 【Contrast Stretching】:對比度拉伸;
- 【HE】:直方圖均衡;
- 【CLHE】:對比度限制的HE
- 【AHE】:自適應直方圖均衡化
2 競賽中的CLAHE實現
在比賽中,我們往往使用albumentations庫函數進行圖像的預處理,因為這個預處理庫的運行速度非常的快,而且封裝了大量的圖像增強的方法。圖像任務的話這個庫函數非常滴奈斯。
本文中會介紹一下albumentations庫函數實現CLAHE的代碼,然后再用openCV實現。
import albumentations
RESIZE_SIZE = 1024 # or 768
train_transform = albumentations.Compose([
albumentations.Resize(RESIZE_SIZE, RESIZE_SIZE),
albumentations.OneOf([
albumentations.RandomGamma(gamma_limit=(60, 120), p=0.9),
albumentations.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.9),
albumentations.CLAHE(clip_limit=4.0, tile_grid_size=(4, 4), p=0.9),
]),
albumentations.HorizontalFlip(p=0.5),
albumentations.ShiftScaleRotate(shift_limit=0.2, scale_limit=0.2, rotate_limit=20,
interpolation=cv2.INTER_LINEAR, border_mode=cv2.BORDER_CONSTANT, p=1),
albumentations.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225), max_pixel_value=255.0, p=1.0)
])
這是一個圖像增強的pipline,其中的流程是:
- Resize就是拉伸圖片修改尺寸
- RandomGamma就是使用gamma變換
- RandomBrightnessContrast就是隨機選擇圖片的對比度和亮度
- CLAHE是一種對比度受限情況下的自適應直方圖均衡化算法
- HorizontalFlip水平翻轉
- ShiftScaleRotate這個就是平移縮放旋轉三合一,給力!
- Normalize這個就是圖像歸一化了。
本文主要講解的就是CLAHE這個直方圖均衡化的算法。
3 openCV繪制直方圖
使用openCV的代碼來獲取一個圖片的灰度直方圖:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def plot(grayHist):
plt.plot(range(256), grayHist, 'r', linewidth=1.5, c='red')
y_maxValue = np.max(grayHist)
plt.axis([0, 255, 0, y_maxValue]) # x和y的范圍
plt.xlabel("gray Level")
plt.ylabel("Number Of Pixels")
plt.show()
if __name__ == "__main__":
# 讀取圖像並轉換為灰度圖
img = cv2.imread(r'E:\dog.jpg', 0)
# 圖像的灰度級范圍是0~255
grayHist = cv2.calcHist([img], [0], None, [256], [0, 256])
# 繪制直方圖
plot(grayHist)
狗子的圖片就是左邊的這個,發現灰度值在100左右的像素個數最多:
4 對比度Contrast
在生活中,我們在PS圖片的時候,往往會用到圖片對比度,那么這個究竟是什么東西呢?
圖片對比度指的是一幅圖片中最亮的白和最暗的黑之間的反差大小。常用的定量度量方法是Michelson對比度:
\(C = \frac{I_{max}-I_{min}}{I_{max}+I_{min}}\)
- 當一幅圖像最白和最黑像素灰度都是128時,圖像對比度最低,C=0;
- 當一幅圖像最白像素灰度=255,最黑像素灰度=0時,圖像對比度最高,C=1.0。
【英文中如何描述高對比度與低對比度的?】
當一幅圖像最白和最黑像素灰度都在128附近浮動時,圖像的直方圖集中在中間的幾個桶,圖像看起來灰蒙蒙的,英語中使用dull描述這種效果。相反,如果圖像中黑白像素的跨度較大,則圖像富有通透感,英語中使用clarity描述這種效果。
圖片中左邊的圖片就是dull,灰度直方圖也是集中在中間區域,這就是低對比度;最右邊的圖片是clarity,直方圖感覺是被拉開了、舒展了,這就是高對比度。
5 Contrast Stretching
我們已經搞懂了圖片不通透的原因,就是灰度直方圖不夠舒展,集中在了一個小區域,這樣我們可以通過數學的方法把低對比度的圖像提高對比度。最簡單的方法就是對比度拉伸(Contrast Stretching)。
現在有這樣的一個低對比度的圖片,其灰度直方圖集中在中間的區域。然后我們想把這個灰度直方圖拉伸到整個0~255的區間,我們怎么做呢?(這里假設這個低對比度的圖片的灰度集中在100到200之間好了)
用一個這樣的分段線性函數,來處理上面那個低對比度圖片的時候,可以把(r2,s2)極端的設置成(100,0),把(r3,s3)設置成(200,255),這樣把原來的直方圖通過這個函數映射,其實就是把100~200范圍線性拉伸到0~255這么大。
這種方法最簡單,簡單的說就是線性拉伸直方圖。對於某些圖片可以起到效果:
但是對於比較復雜的圖片,並沒有什么效果:
6 Histogram Equalization
對比度解決不了的問題,我們來用HE試試。Histogram Equalization的思想就是用數學方法重新調整像素的亮度分布,來保證直方圖具有最大的動態范圍,也就是盡可能地讓灰度直方圖是一個矩形!
其實Contrast Stretching也是做的一樣的事情,只是它用的簡單的分段線性函數來重新映射灰度,現在用更巧妙地方法。
【定義一些數學符號】
- \(p(x)\):調整之前的直方圖的概率密度函數
- \(q(y)\):調整之后的直方圖的概率密度函數,可以看出來,是一個常數,所以用C來表示
因為不管怎么轉換,概率密度函數的累積總是1,而轉換前后的取值范圍都是[0,1],所以可以得到:
\(\int_0^1{p(x)dx=\int_0^1Cdy=1}\)
(當然,這里可以很快的算出來,C=1)
我們希望找到,一個x和y的映射關系,也就是\(y=f(x)\),不難想到,這個\(f(x)\)就應該是\(p(x)\)的累積分布函數,也就是:
\(f(x)=\int_0^xp(u)du\)
這個圖中,直觀的展示了,任何一個直方圖,只要按照該直方圖的累積分布函數進行拉伸,就可以得到一個矩形的直方圖。
下面是一個利用這樣的方法增強對比度的例子:
可以發現,在直方圖密集的地方,就會被拉的松散
再看另外一個例子:
可以發現,使用HE之后的直方圖的累積分布函數,是一個直線
7 CLAHE
HE算法在一種情況下,效果不好,如果一個圖片中有大塊的暗區或者亮區的話,效果非常不好。這個的原因,也非常好理解,因為HE其實要求一個圖片中必須有10%的最亮的像素點,必須有10%第二亮的像素點,必須有10%第三亮的像素點……假設有一張純黑的圖片,你想想經過HE處理之后,會出現什么情況?答案就是一部分黑的像素也會被強行搞成白的
下面是一個例子,發現經過HE之后的圖片出現了大量噪點:
【Histogram Equalization的缺點】
- 對於灰度非常集中的區域,直方圖會被拉的非常稀疏,從而導致對比度增強過大,成為噪音;
- 一些區域調整后丟失細節
7.1 Contrast Limited HE
針對第一個問題,提出了CLHE,加入對比度限制,其實原理很簡單置直方圖分布的閾值,將超過該閾值的分布“均勻”分散至概率密度分布上,由此來限制轉換函數(累計直方圖)的增幅。
這樣的話,直方圖就不會出現概率密度函數過大的區域,從而避免了某些集中區域被拉得過於系數。
7.2 Adaptive HE
Adaptive HE的基本思想是將原始圖片划分成子區域,然后對每個子區域進行HE變換。 當然,這樣做的問題應該是顯而易見的:
每一塊區域之間都會有非常大的不連續。因此為了解決這個問題,提出了優化方案雙線性插值的AHE ,然后這個基礎上再使用CLHE的截斷對比度的思想,就變成了我們現在的CLAHE算法。
【使用雙線性插值的方案】
-
將圖像分為多個矩形塊大小,對於每個矩形塊子圖,分別計算其灰度直方圖和對應的變換函數(累積直方圖)
-
將原始圖像中的像素按照分布分為三種情況處理:
- 紅色區域中的像素按照其所在子圖的變換函數進行灰度映射
- 綠色區域中的像素按照所在的兩個相鄰子圖變換函數變換后進行線性插值得到
- 紫色區域中的像素按照其所在的四個相鄰子圖變換函數變換后雙線性插值得到
8 結果對比與openCV實現
【這里是openCV實現HE的方法】
img = cv.imread(r'E:\dog.jpg', 0)
equ = cv.equalizeHist(img) # 輸入為灰度圖
res = np.hstack((img, equ)) # stacking images side-by-side
cv.imwrite('res.png',res)
運行結果:
【openCV實現CLAHE】
img = cv2.imread(r'E:\dog.jpg', 0)
# create a CLAHE object
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cl1 = clahe.apply(img)
res = np.hstack((img, cl1))
cv2.imwrite('res.jpg', res)
結果是:
【更多對比的例子】
- 左邊是原圖
- 中間是HE,有過亮過暗的區域;
- 右邊是CLAHE,沒有過亮過暗的區域。
然后我在另外一個博文,找到了上面那個例子的彩色版本哈哈:
參考文章: