Python OpenCV 365 天學(xué)習(xí)計(jì)劃,與橡皮擦一起進(jìn)入圖像領(lǐng)域吧。
基礎(chǔ)知識鋪墊
截止到本篇博客,已經(jīng)第二次聽到直方圖這個概念了,有必要將其搞懂。
圖像直方圖(histogram)是圖像統(tǒng)計(jì)學(xué)特征,用來統(tǒng)計(jì)像素值出現(xiàn)的頻次,常用在分析圖像的基本特征。
創(chuàng)建直方圖一般分為兩個步驟:
- 統(tǒng)計(jì)數(shù)據(jù)
- 繪制直方圖
直方圖的定義
- 橫坐標(biāo):圖像中各個像素點(diǎn)的灰度級
- 縱坐標(biāo):該灰度級的像素個數(shù)
繪制直方圖需要 matplotlib 庫,這個需要自行安裝一下。
matplotlib 中 pyplot 繪制直方圖
在 pyplot 中提供了一個繪制直方圖的函數(shù),名稱為 hist。
函數(shù)原型介紹
matplotlib.pyplot.hist() 函數(shù)原型如下
(n, bins, patches)=matplotlib.pyplot.hist(x, bins=None, range=None, density=None, weights=None,
cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical',
rwidth=None, log=False, color=None, label=None, stacked=False, normed=None, *, data=None, **kwargs)
參數(shù)非常多,實(shí)際應(yīng)用中只需掌握幾個重要參數(shù)。
最簡單的測試代碼如下:
import numpy as np
import matplotlib.pyplot as plt
# 生成數(shù)據(jù),以 10000 組均值為0,標(biāo)準(zhǔn)差為 1 的高斯分布數(shù)據(jù)為例
data = np.random.normal(0,1,10000)
n, bins, patches = plt.hist(data)
plt.show()
運(yùn)行效果如下:

其中,np.random.normal(0,1,10000) 函數(shù)說明如下,np.random.normal() 是一個正態(tài)分布,normal這里是正態(tài)的意思。
該函數(shù)原型為:
numpy.random.normal(loc=0.0, scale=1.0, size=None)
參數(shù)說明如下:
- loc:概率分布的均值,對應(yīng)著整個分布的中心 center
- scale:概率分布的標(biāo)準(zhǔn)差,對應(yīng)于分布的寬度,scale 越大,越矮胖,scale 越小,越瘦高
- size:數(shù)據(jù)類型為
int or tuple of ints, 輸出的 shape,默認(rèn)為 None,只輸出一個值
其實(shí)該函數(shù)的目的就是,輸出為高斯分布的一組數(shù)或一個值。
簡單案例:
data = np.random.normal(loc=0, scale=1, size=2)
print(data)
繼續(xù)回顧 matplotlib.pyplot.hist() 函數(shù)的相關(guān)參數(shù)(官網(wǎng)說明):
只選取其中比較重要的幾個參數(shù)如下:
- x:(n,) array or sequence of (n,) arrays
指定要繪制直方圖的數(shù)據(jù),必須是一維數(shù)組.使用.ravel()將你的通道值轉(zhuǎn)為一維數(shù)組 - bins:integer or sequence or ‘a(chǎn)uto’, optional
指定直方圖條形的個數(shù),integer 或 auto,也可以不設(shè)置.舉例[1,2,3,4],則第一個柱為取值[1,2),一次類推,最后一個是取值[3,4].默認(rèn) taken from the rcParam hist.bins. - range:tuple or None, optional
數(shù)組或者不給.給出數(shù)組將指定直方圖數(shù)據(jù)的上下界,超出范圍的舍棄.不設(shè)置的話包含繪圖數(shù)據(jù)的最大值和最小值;默認(rèn)為 None
基于上述內(nèi)容,將一副圖像的直方圖顯示出來。
做一些準(zhǔn)備工作
- x: 圖像,必須是一維數(shù)組
- 其中函數(shù) ravel b = a.ravel()
功能: 將多維數(shù)組降為一維數(shù)組
格式: 一維數(shù)組=多維數(shù)組.revel() - bins: 一般是 256,指[0, 255]
以上內(nèi)容掌握之后,就可以處理圖像的直方圖了,代碼如下:
import cv2
from matplotlib import pyplot as plt
def plot_demo(image):
# numpy 的 ravel 函數(shù)功能是將多維數(shù)組降為一維數(shù)組
plt.hist(image.ravel(), 256, [0, 256])
plt.show()
if __name__ == "__main__":
img = cv2.imread("./106.jpg")
cv2.namedWindow("input image", cv2.WINDOW_AUTOSIZE)
cv2.imshow("input image", img)
plot_demo(img)
cv2.waitKey(0)
cv2.destroyAllWindows()

python opencv 直方圖(histogram)
函數(shù)原型介紹
在 Python OpenCV 中實(shí)現(xiàn)直方圖的函數(shù)為cv2.calcHist,原型如下:
# 返回 hist
cv2.calcHist(img, channels, mask, histSize, ranges[, hist[, accumulate ]])
參數(shù)說明:
- img:圖像,方括號方式傳入,即
[img]; - channels:選取圖像的哪個通道,用方括號給出的,計(jì)算直方圖的
channel的索引,如果輸入時(shí)灰度圖,值就是[0],對于彩色圖片,你可以傳[0],[1]和[2]來分別計(jì)算藍(lán)色,綠色和紅色通道的直方圖; - mask:掩膜,如果要找整個圖像的直方圖,這里傳入"None"。如果你想找到特定區(qū)域圖片的直方圖,需要使用掩膜,只計(jì)算值>0 的位置上像素的顏色直方圖
- histSize:直方圖大小,BINS 數(shù)量(BINS 是啥,下面細(xì)說),要方括號傳入,對于全刻度,傳入
[256] - ranges:直方圖范圍,一般來說是
[0,256]
關(guān)于上文提及的 BINS 等內(nèi)容涉及直方圖如下概念:
- BINS: 在上面的直方圖當(dāng)中,如果像素值是 0 到 255,則需要 256 個值來顯示直方圖。
但是,如果不需要知道每個像素值的像素?cái)?shù)目,只想知道兩個像素值范圍內(nèi)的像素點(diǎn)數(shù)目即可?首先像素值在0--15之間的像素點(diǎn)數(shù)目,然后是16--31……直到240--255,即每次間隔 16 個數(shù)字,將 256 個值分成 16 份,每份計(jì)算綜合。每個分成的小組就是一個 BIN(箱)。在 opencv 中使用 histSize 表示 BINS。
彩色圖像,不同通道的直方圖
首先繪制藍(lán)色通道的直方圖,代碼如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('./106.jpg')
hist = cv.calcHist([img], [0], None, [256], [0,256])
plt.plot(hist, label='B', color='b')
plt.show()
運(yùn)行結(jié)果如下:

三個通道同時(shí)繪制代碼如下:
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('./106.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
hist = cv.calcHist([img], [i], None, [256], [0, 256])
plt.plot(hist, color=col)
plt.xlim([0, 256])
plt.show()
BGR 直方圖如下:

直方圖均衡化 (Histogram Equalization)
如果圖像的灰度分布不均勻,集中在一個比較窄的范圍內(nèi),這樣圖像的細(xì)節(jié)就會不清晰,對比度低。
這種情況可以使用直方圖均衡化,對圖像進(jìn)行非線性拉伸,重新分配圖像的灰度值,使一定范圍內(nèi)圖像的灰度值大致相等。
執(zhí)行之后,直方圖中間峰值部分對比度增強(qiáng),兩側(cè)谷底部分對比度降低。圖像的灰度范圍拉伸之后,灰度均勻分布,反差增大,增強(qiáng)圖像細(xì)節(jié)。
理論的東西就是上面那些了,實(shí)操起來才可以看到效果。
函數(shù)原型介紹
使用 cv2.equalizeHist 方法來得到直方圖均衡化之后的圖像,函數(shù)原型如下:
cv2.equalizeHist(src[, dst])
參數(shù)說明:
- src:源圖像。圖像必須是灰度圖。
- dst:目標(biāo)圖像。
測試代碼如下:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
image = cv.imread("2o.jpg")
# 將圖片轉(zhuǎn)換為灰度圖
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("gray", gray)
# 直方圖繪制
hist = cv.calcHist([gray], [0], None, [256], [0, 256])
plt.plot(hist)
plt.show()
# 應(yīng)用直方圖均衡化
dst = cv.equalizeHist(gray)
cv.imshow("dst", dst)
# 直方圖繪制
hist = cv.calcHist([dst], [0], None, [256], [0, 256])
plt.plot(hist)
plt.show()
運(yùn)行結(jié)果與直方圖:

運(yùn)行直方圖均衡化之后的圖像如下:

上述案例為灰度圖直方圖均衡化,對于彩色圖像一樣可以進(jìn)行圖像增強(qiáng)操作。
import cv2 as cv
import numpy as np
img = cv.imread('th.jpeg')
cv.imshow('img', img)
b, g, r = cv.split(img)
bH = cv.equalizeHist(b)
gH = cv.equalizeHist(g)
rH = cv.equalizeHist(r)
dst = cv.merge((bH, gH, rH))
cv.imshow('dst', dst)
cv.waitKey(0)
使用cv2.split函數(shù)分離圖像的顏色通道,分別得到各個通道的直方圖,再使用cv2.merge函數(shù)合并各通道,得到彩色圖像的直方圖均衡化。

以上提及的叫做全局直方圖均衡化,下面為大家在介紹一下局部直方圖均衡化。
局部直方圖均衡化在有的地方也被叫做 CLAHE 有限對比適應(yīng)性直方圖均衡化。
大概實(shí)現(xiàn)過程如下:
整幅圖像會被分成很多小塊,這些小塊被稱為 “tiles”(tiles 的默認(rèn)大小是 8x8),然后再對每一個小塊分別進(jìn)行直方圖均衡化。
函數(shù)原型如下:
cv2.createCLAHE(clipLimit, tileGridSize)
參數(shù)說明:
- clipLimit:對比度限制的閾值
- tileGridSize:圖像分割每塊的尺寸,默認(rèn) 8x8
運(yùn)行下述代碼,得到的結(jié)果可以與全局直方圖均衡化做一下比較。
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
image = cv.imread("2o.jpg")
# 將圖片轉(zhuǎn)換為灰度圖
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("gray", gray)
# 直方圖繪制
hist = cv.calcHist([gray], [0], None, [256], [0, 256])
plt.plot(hist)
plt.show()
# 應(yīng)用直方圖均衡化
# 1. 實(shí)例化自適應(yīng)直方圖均衡化函數(shù)
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
# 2. 進(jìn)行自適應(yīng)直方圖均衡化
dst = clahe.apply(gray)
cv.imshow("dst", dst)
# 直方圖繪制
hist = cv.calcHist([dst], [0], None, [256], [0, 256])
plt.plot(hist)
plt.show()

橡皮擦的小節(jié)
今天重點(diǎn)學(xué)習(xí)了一下直方圖,寫了這么多,只有一個原因,就是這是第二次碰到了,當(dāng)一個知識點(diǎn)再次出現(xiàn)時(shí),就要在進(jìn)一步的學(xué)習(xí)下,因?yàn)榇蟾怕仕侵攸c(diǎn)知識。
相關(guān)閱讀
今天是持續(xù)寫作的第 <font color="red">65</font> / 100 天。
如果你有想要交流的想法、技術(shù),歡迎在評論區(qū)留言。