影象增強 | CLAHE 限制對比度自適應直方圖均衡化
阿新 • • 發佈:2020-07-27
# 1 基本概述
CLAHE是一個比較有意思的影象增強的方法,主要用在醫學影象上面。之前的比賽中,用到了這個,但是對其演算法原理不甚瞭解。在這裡做一個覆盤。
**CLAHE起到的作用簡單來說就是增強影象的對比度的同時可以抑制噪聲**
CLAHE的英文是Contrast Limited Adaptive Histogram Equalization 限制對比度的自適應直方圖均衡。在學習這個之前,我們要先學習一下下面的前置演算法:
1. **【Contrast Stretching】**:對比度拉伸;
1. **【HE】**:直方圖均衡;
1. **【CLHE】**:對比度限制的HE
2. **【AHE】**:自適應直方圖均衡化
# 2 競賽中的CLAHE實現
在比賽中,我們往往使用albumentations庫函式進行影象的預處理,因為這個預處理庫的執行速度非常的快,而且封裝了大量的影象增強的方法。影象任務的話這個庫函式非常滴奈斯。
本文中會介紹一下albumentations庫函式實現CLAHE的程式碼,然後再用openCV實現。
```python
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的程式碼來獲取一個圖片的灰度直方圖:
```python
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左右的畫素個數最多:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_9e3a597c8acbf0f1aa00d5689717b161.jpg)
# 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**描述這種效果。
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_edfe5d3f6382dbd7cfa4089fc51f1d77.jpg)
圖片中左邊的圖片就是**dull**,灰度直方圖也是集中在中間區域,這就是**低對比度**;最右邊的圖片是**clarity**,直方圖感覺是被拉開了、舒展了,這就是**高對比度**。
# 5 Contrast Stretching
我們已經搞懂了圖片不通透的原因,就是灰度直方圖不夠舒展,集中在了一個小區域,這樣我們可以通過數學的方法把低對比度的影象提高對比度。最簡單的方法就是**對比度拉伸(Contrast Stretching)**。
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_2c2d4af405523448b10e3f6d76942b5f.jpg)
現在有這樣的一個低對比度的圖片,其灰度直方圖集中在中間的區域。然後我們想把這個灰度直方圖拉伸到整個0~255的區間,我們怎麼做呢?**(這裡假設這個低對比度的圖片的灰度集中在100到200之間好了)**
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_f7b84d403e4794c3d18af748935ea9ea.jpg)
用一個這樣的分段線性函式,來處理上面那個低對比度圖片的時候,可以把(r2,s2)極端的設定成(100,0),把(r3,s3)設定成(200,255),這樣把原來的直方圖通過這個函式對映,其實就是把100\~200範圍線性拉伸到0\~255這麼大。
這種方法最簡單,簡單的說就是線性拉伸直方圖。對於某些圖片可以起到效果:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_39912dc3dbeb37712de495e02d4a88c7.jpg)
但是對於比較複雜的圖片,並沒有什麼效果:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_d93a48ff1bcb621d1a108b104c5d577e.jpg)
# 6 Histogram Equalization
對比度解決不了的問題,我們來用HE試試。**Histogram Equalization的思想就是用數學方法重新調整畫素的亮度分佈,來保證直方圖具有最大的動態範圍,也就是儘可能地讓灰度直方圖是一個矩形!**
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_dbbad9d5dc5a18b0f0856d38e3f3300a.jpg)
其實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$
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_d5bc19a3514609bfaea3af45a670d2ca.jpg)
這個圖中,直觀的展示了,任何一個直方圖,只要按照該直方圖的累積分佈函式進行拉伸,就可以得到一個矩形的直方圖。
下面是一個利用這樣的方法增強對比度的例子:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_f45beb5e1136451388069f7211e91659.jpg)
**可以發現,在直方圖密集的地方,就會被拉的鬆散**
再看另外一個例子:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_007e9fa2004554f51e794b20c7759ab5.jpg)
**可以發現,使用HE之後的直方圖的累積分佈函式,是一個直線**
# 7 CLAHE
HE演算法在一種情況下,效果不好,**如果一個圖片中有大塊的暗區或者亮區的話,效果非常不好。**這個的原因,也非常好理解,因為HE其實要求一個圖片中必須有10%的最亮的畫素點,必須有10%第二亮的畫素點,必須有10%第三亮的畫素點……假設有一張純黑的圖片,你想想經過HE處理之後,會出現什麼情況?**答案就是一部分黑的畫素也會被強行搞成白的**
下面是一個例子,發現經過HE之後的圖片出現了大量噪點:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_af8fd3de9ceedfe85a9daa52510c7311.jpg)
**【Histogram Equalization的缺點】**
1. 對於灰度非常集中的區域,直方圖會被拉的非常稀疏,從而導致對比度增強過大,成為噪音;
2. 一些區域調整後丟失細節
## 7.1 Contrast Limited HE
針對第一個問題,提出了CLHE,加入對比度限制,其實原理很簡單**置直方圖分佈的閾值,將超過該閾值的分佈“均勻”分散至概率密度分佈上,由此來限制轉換函式(累計直方圖)的增幅。**
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_36acb199dcdd194ab303896679063521.jpg)
這樣的話,直方圖就不會出現概率密度函式過大的區域,從而避免了某些集中區域被拉得過於係數。
## 7.2 Adaptive HE
Adaptive HE的基本思想是**將原始圖片劃分成子區域,然後對每個子區域進行HE變換。**當然,這樣做的問題應該是顯而易見的:
[![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_5e82da53d500d0d2eb28fca9021e7b30.jpg)](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_5e82da53d500d0d2eb28fca9021e7b30.jpg)
每一塊區域之間都會有非常大的不連續。因此為了解決這個問題,提出了優化方案**雙線性插值的AHE**,然後這個基礎上再使用CLHE的截斷對比度的思想,就變成了我們現在的CLAHE演算法。
**【使用雙線性插值的方案】**
1. 將影象分為多個矩形塊大小,對於每個矩形塊子圖,分別計算其灰度直方圖和對應的變換函式(累積直方圖)
2. 將原始影象中的畫素按照分佈分為三種情況處理:
- 紅色區域中的畫素按照其所在子圖的變換函式進行灰度對映
- 綠色區域中的畫素按照所在的兩個相鄰子圖變換函式變換後進行線性插值得到
- 紫色區域中的畫素按照其所在的四個相鄰子圖變換函式變換後雙線性插值得到
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_29603e81424f07b56c95af3320877359.jpg)
# 8 結果對比與openCV實現
**【這裡是openCV實現HE的方法】**
```python
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)
```
執行結果:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_9cec6a6a53592d6d62a0a1ca0513391d.jpg)
**【openCV實現CLAHE】**
```python
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)
```
結果是:
[![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_3f881e3fd43aa51b324ba3703dcd92d9.jpg)](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_3f881e3fd43aa51b324ba3703dcd92d9.jpg)
----
**【更多對比的例子】**
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_b19a8ddcee407b34f3e6ac946e07df82.jpg)
- 左邊是原圖
- 中間是HE,有過亮過暗的區域;
- 右邊是CLAHE,沒有過亮過暗的區域。
然後我在另外一個博文,找到了上面那個例子的彩色版本哈哈:
![](http://helloworld2020.net/wp-content/uploads/2020/07/wp_editor_md_74a7c325e31a84e2d2422be5f4b5ae83.jpg)
參考文章:
1. https://zhuanlan.zhihu.com/p/98541241
2. https://blog.csdn.net/lwx309025167/article/details/103770834
3. https://blog.csdn.net/u013066730/article/details/82970380
4. https://www.cnblogs.com/imageshop/archive/2013/04/07/
5. http://helloworld2020.net/393/
![](https://imgconvert.csdnimg.cn/aHR0cDovL2hlbGxvd29ybGQyMDIwLm5ldC93cC1jb250ZW50L3VwbG9hZHMvMjAyMC8wNy8lRTklQkIlOTglRTglQUUlQTQlRTYlQTAlODclRTklQTIlOThfJUU1JThBJUE4JUU2JTgwJTgxJUU1JTg4JTg2JUU1JTg5JUIyJUU3JUJBJUJGXzIwMjAtMDctMjAtMC5naWY)
![](https://imgconvert.csdnimg.cn/aHR0cDovL2hlbGxvd29ybGQyMDIwLm5ldC93cC1jb250ZW50L3VwbG9hZHMvMjAyMC8wNy93cF9lZGl0b3JfbWRfMzEyZGQyZDliYmNmZmNiZDk0Y2YwODlkYTE4YzVjNGEuanBn?x-oss-process=image/format,png)
![](https://imgconvert.csdnimg.cn/aHR0cDovL2hlbGxvd29ybGQyMDIwLm5ldC93cC1jb250ZW50L3VwbG9hZHMvMjAyMC8wNy8lRTklQkIlOTglRTglQUUlQTQlRTYlOTYlODclRTQlQkIlQjYxNTk1MjUxNjIxMTEyLnBuZw?x-oss-process=image/for