Python OpenCV 365 天學(xué)習(xí)計(jì)劃耸袜,與橡皮擦一起進(jìn)入圖像領(lǐng)域吧友多。
基礎(chǔ)知識(shí)鋪墊
截止到本篇博客,已經(jīng)第二次聽到直方圖這個(gè)概念了堤框,有必要將其搞懂域滥。
圖像直方圖(histogram)是圖像統(tǒng)計(jì)學(xué)特征纵柿,用來統(tǒng)計(jì)像素值出現(xiàn)的頻次,常用在分析圖像的基本特征启绰。
創(chuàng)建直方圖一般分為兩個(gè)步驟:
- 統(tǒng)計(jì)數(shù)據(jù)
- 繪制直方圖
直方圖的定義
- 橫坐標(biāo):圖像中各個(gè)像素點(diǎn)的灰度級(jí)
- 縱坐標(biāo):該灰度級(jí)的像素個(gè)數(shù)
繪制直方圖需要 matplotlib
庫昂儒,這個(gè)需要自行安裝一下。
matplotlib 中 pyplot 繪制直方圖
在 pyplot
中提供了一個(gè)繪制直方圖的函數(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)用中只需掌握幾個(gè)重要參數(shù)撤缴。
最簡(jiǎn)單的測(cè)試代碼如下:
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()
是一個(gè)正態(tài)分布,normal
這里是正態(tài)的意思虎眨。
該函數(shù)原型為:
numpy.random.normal(loc=0.0, scale=1.0, size=None)
參數(shù)說明如下:
- loc:概率分布的均值蟋软,對(duì)應(yīng)著整個(gè)分布的中心 center
- scale:概率分布的標(biāo)準(zhǔn)差,對(duì)應(yīng)于分布的寬度嗽桩,scale 越大岳守,越矮胖,scale 越小碌冶,越瘦高
- size:數(shù)據(jù)類型為
int or tuple of ints
湿痢, 輸出的 shape,默認(rèn)為 None扑庞,只輸出一個(gè)值
其實(shí)該函數(shù)的目的就是譬重,輸出為高斯分布的一組數(shù)或一個(gè)值。
簡(jiǎn)單案例:
data = np.random.normal(loc=0, scale=1, size=2)
print(data)
繼續(xù)回顧 matplotlib.pyplot.hist()
函數(shù)的相關(guān)參數(shù)(官網(wǎng)說明):
只選取其中比較重要的幾個(gè)參數(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
指定直方圖條形的個(gè)數(shù)罐氨,integer 或 auto,也可以不設(shè)置.舉例[1,2,3,4],則第一個(gè)柱為取值[1,2),一次類推,最后一個(gè)是取值[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:圖像记盒,方括號(hào)方式傳入,即
[img]
外傅; - channels:選取圖像的哪個(gè)通道纪吮,用方括號(hào)給出的,計(jì)算直方圖的
channel
的索引萎胰,如果輸入時(shí)灰度圖碾盟,值就是[0]
,對(duì)于彩色圖片技竟,你可以傳[0]
冰肴,[1]
和[2]
來分別計(jì)算藍(lán)色,綠色和紅色通道的直方圖榔组; - mask:掩膜熙尉,如果要找整個(gè)圖像的直方圖,這里傳入"None"搓扯。如果你想找到特定區(qū)域圖片的直方圖检痰,需要使用掩膜,只計(jì)算值>0 的位置上像素的顏色直方圖
- histSize:直方圖大小锨推,BINS 數(shù)量(BINS 是啥铅歼,下面細(xì)說),要方括號(hào)傳入换可,對(duì)于全刻度椎椰,傳入
[256]
- ranges:直方圖范圍,一般來說是
[0,256]
關(guān)于上文提及的 BINS 等內(nèi)容涉及直方圖如下概念:
- BINS: 在上面的直方圖當(dāng)中沾鳄,如果像素值是 0 到 255慨飘,則需要 256 個(gè)值來顯示直方圖。
但是译荞,如果不需要知道每個(gè)像素值的像素?cái)?shù)目套媚,只想知道兩個(gè)像素值范圍內(nèi)的像素點(diǎn)數(shù)目即可?首先像素值在0--15
之間的像素點(diǎn)數(shù)目磁椒,然后是16--31
……直到240--255
堤瘤,即每次間隔 16 個(gè)數(shù)字,將 256 個(gè)值分成 16 份浆熔,每份計(jì)算綜合本辐。每個(gè)分成的小組就是一個(gè) 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é)果如下:
三個(gè)通道同時(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)
如果圖像的灰度分布不均勻叶骨,集中在一個(gè)比較窄的范圍內(nèi)茫多,這樣圖像的細(xì)節(jié)就會(huì)不清晰,對(duì)比度低忽刽。
這種情況可以使用直方圖均衡化天揖,對(duì)圖像進(jìn)行非線性拉伸夺欲,重新分配圖像的灰度值,使一定范圍內(nèi)圖像的灰度值大致相等今膊。
執(zhí)行之后些阅,直方圖中間峰值部分對(duì)比度增強(qiáng),兩側(cè)谷底部分對(duì)比度降低斑唬。圖像的灰度范圍拉伸之后市埋,灰度均勻分布,反差增大恕刘,增強(qiáng)圖像細(xì)節(jié)缤谎。
理論的東西就是上面那些了,實(shí)操起來才可以看到效果褐着。
函數(shù)原型介紹
使用 cv2.equalizeHist
方法來得到直方圖均衡化之后的圖像坷澡,函數(shù)原型如下:
cv2.equalizeHist(src[, dst])
參數(shù)說明:
- src:源圖像。圖像必須是灰度圖献起。
- dst:目標(biāo)圖像洋访。
測(cè)試代碼如下:
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)行直方圖均衡化之后的圖像如下:
上述案例為灰度圖直方圖均衡化,對(duì)于彩色圖像一樣可以進(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ù)分離圖像的顏色通道姻政,分別得到各個(gè)通道的直方圖,再使用cv2.merge
函數(shù)合并各通道岂嗓,得到彩色圖像的直方圖均衡化汁展。
以上提及的叫做全局直方圖均衡化,下面為大家在介紹一下局部直方圖均衡化厌殉。
局部直方圖均衡化在有的地方也被叫做 CLAHE 有限對(duì)比適應(yīng)性直方圖均衡化
食绿。
大概實(shí)現(xiàn)過程如下:
整幅圖像會(huì)被分成很多小塊,這些小塊被稱為 “tiles”(tiles 的默認(rèn)大小是 8x8)公罕,然后再對(duì)每一個(gè)小塊分別進(jìn)行直方圖均衡化器紧。
函數(shù)原型如下:
cv2.createCLAHE(clipLimit, tileGridSize)
參數(shù)說明:
- clipLimit:對(duì)比度限制的閾值
- 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í)了一下直方圖,寫了這么多罐柳,只有一個(gè)原因掌腰,就是這是第二次碰到了,當(dāng)一個(gè)知識(shí)點(diǎn)再次出現(xiàn)時(shí)张吉,就要在進(jìn)一步的學(xué)習(xí)下齿梁,因?yàn)榇蟾怕仕侵攸c(diǎn)知識(shí)。
相關(guān)閱讀
今天是持續(xù)寫作的第 <font color="red">65</font> / 100 天。
如果你有想要交流的想法酵幕、技術(shù)扰藕,歡迎在評(píng)論區(qū)留言缓苛。