Python OpenCV 圖像處理之圖像直方圖,取經(jīng)之旅第 25

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)建直方圖一般分為兩個步驟:

  1. 統(tǒng)計(jì)數(shù)據(jù)
  2. 繪制直方圖

直方圖的定義

  • 橫坐標(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)行效果如下:


20210125165332331[1].png

其中,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()
20210125171053391[1].png

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é)果如下:

20210125202235780[1].png

三個通道同時(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 直方圖如下:


2021012520254698[1].png

直方圖均衡化 (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é)果與直方圖:

20210125205905178[1].png

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


20210125205936297[1].png

上述案例為灰度圖直方圖均衡化,對于彩色圖像一樣可以進(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ù)合并各通道,得到彩色圖像的直方圖均衡化。

20210125210633576[1].png

以上提及的叫做全局直方圖均衡化,下面為大家在介紹一下局部直方圖均衡化。

局部直方圖均衡化在有的地方也被叫做 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()
20210125211452816[1].png

橡皮擦的小節(jié)

今天重點(diǎn)學(xué)習(xí)了一下直方圖,寫了這么多,只有一個原因,就是這是第二次碰到了,當(dāng)一個知識點(diǎn)再次出現(xiàn)時(shí),就要在進(jìn)一步的學(xué)習(xí)下,因?yàn)榇蟾怕仕侵攸c(diǎn)知識。

相關(guān)閱讀


  1. Python 爬蟲 100 例教程,超棒的爬蟲教程,立即訂閱吧
  2. Python 爬蟲小課,精彩 9 講

今天是持續(xù)寫作的第 <font color="red">65</font> / 100 天。
如果你有想要交流的想法、技術(shù),歡迎在評論區(qū)留言。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容