[Python圖像處理] 三十七.OpenCV直方圖統計兩萬字詳解(掩膜直方圖、灰度直方圖對比、黑夜白天預測)

該系列文章是講解Python OpenCV圖像處理知識,前期主要講解圖像入門、OpenCV基礎用法,中期講解圖像處理的各種算法,包括圖像銳化算子、圖像增強技術、圖像分割等,後期結合深度學習研究圖像識別、圖像分類應用。希望文章對您有所幫助,如果有不足之處,還請海涵~

前面一篇文章介紹了圖像幾何變換,包括:圖像平移變換、圖像縮放變換、圖像旋轉變換、圖像鏡像變換、圖像仿射變換和圖像透視變換;這篇文章將詳細介紹直方圖統計,包括Matplotlib和OpenCV繪製直方圖、掩膜直方圖、灰度直方圖對比及通過直方圖預測黑夜白天,萬字長文整理,希望對您有所幫助。 同時,該部分知識均爲作者查閱資料撰寫總結,並且開設成了收費專欄,爲小寶賺點奶粉錢,感謝您的擡愛。當然如果您是在讀學生或經濟拮据,可以私聊我給你每篇文章開白名單,或者轉發原文給你,更希望您能進步,一起加油喔~

前文參考:


圖像灰度直方圖(Histogram)是灰度級分佈的函數,是對圖像中灰度級分佈的統計。灰度直方圖是將數字圖像中的所有像素,按照灰度值的大小,統計其出現的頻率並繪製相關圖形。本章主要講解Matplotlib和OpenCV繪製直方圖的兩種方法,對比了灰度處理算法前後的直方圖,實現掩膜直方圖繪製、圖像H-S直方圖、直方圖判斷黑夜白天等內容。

一.圖像直方圖概述

灰度直方圖是灰度級的函數,描述的是圖像中每種灰度級像素的個數,反映圖像中每種灰度出現的頻率。假設存在一幅6×6像素的圖像,接着統計其1至6灰度級的出現頻率,並繪製如圖1所示的柱狀圖,其中橫座標表示灰度級,縱座標表示灰度級出現的頻率。

在這裏插入圖片描述

如果灰度級爲0-255(最小值0爲黑色,最大值255爲白色),同樣可以繪製對應的直方圖,如圖2所示,左邊是一幅灰度圖像(Lena灰度圖),右邊是對應各像素點的灰度級頻率。

在這裏插入圖片描述

爲了讓圖像各灰度級的出現頻數形成固定標準的形式,可以通過歸一化方法對圖像直方圖進行處理,將待處理的原始圖像轉換成相應的標準形式[3]。假設變量r表示圖像中像素灰度級,歸一化處理後會將r限定在下述範圍:

在這裏插入圖片描述

在灰度級中,r爲0時表示黑色,r爲1時表示白色。對於一幅給定圖像,每個像素值位於[0,1]區間之內,接着計算原始圖像的灰度分佈,用概率密度函數P®實現。爲了更好地進行數字圖像處理,必須引入離散形式。在離散形式下,用rk表示離散灰度級,P(rk)代替P®,並滿足公式(2)。

在這裏插入圖片描述

公式中,nk爲圖像中出現rk這種灰度的像素數,n是圖像中像素總數,是概率論中的頻數,l是灰度級總數(通常l爲256級灰度)。接着在直角座標系中做出rk和P(rk)的關係圖,則成爲灰度級的直方圖。

假設存在一幅3×3像素的圖像,其像素值如公式(9-3)所示,則歸一化直方圖的步驟如下:

在這裏插入圖片描述

  • 首先統計各灰度級對應的像素個數。用x數組統計像素點的灰度級,y數組統計具有該灰度級的像素個數。其中,灰度爲1的像素共3個,灰度爲2的像素共1個,灰度爲3的像素共2個,灰度爲4的像素共1個,灰度爲5的像素共2個。

在這裏插入圖片描述

  • 接着統計總像素個數,如公式(5)所示。

在這裏插入圖片描述

  • 最後統計各灰度級的出現概率,通過公式(6)進行計算,其結果如下:

在這裏插入圖片描述

繪製的歸一化圖行如圖3所示,橫座標表示圖像中各個像素點的灰度級,縱座標表示出現這個灰度級的概率。

在這裏插入圖片描述

直方圖被廣泛應用於計算機視覺領域,在使用邊緣和顏色確定物體邊界時,通過直方圖能更好地選擇邊界閾值,進行閾值化處理。同時,直方圖對物體與背景有較強對比的景物的分割特別有用,可以應用於檢測視頻中場景的變換及圖像中的興趣點,簡單物體的面積和綜合光密度IOD也可以通過圖像的直方圖計算而得。


二.Matplotlib繪製直方圖

Matplotlib是Python強大的數據可視化工具,主要用於繪製各種2D圖形。本小節Python繪製直方圖主要調用matplotlib.pyplot庫中hist()函數實現,它會根據數據源和像素級繪製直方圖。其函數主要包括五個常用的參數,如下所示:

  • n, bins, patches = plt.hist(arr, bins=50, normed=1, facecolor=‘green’, alpha=0.75)
    – arr表示需要計算直方圖的一維數組
    – bins表示直方圖顯示的柱數,可選項,默認值爲10
    – normed表示是否將得到的直方圖進行向量歸一化處理,默認值爲0
    – facecolor表示直方圖顏色
    – alpha表示透明度
    – n爲返回值,表示直方圖向量
    – bins爲返回值,表示各個bin的區間範圍
    – patches爲返回值,表示返回每個bin裏面包含的數據,是一個列表







圖像直方圖的Python實現代碼如下所示,該示例主要是通過matplotlib.pyplot庫中的hist()函數繪製的。注意,讀取的“picture.bmp”圖像的像素爲二維數組,而hist()函數的數據源必須是一維數組,通常需要通過函數ravel()拉直圖像。

# -*- coding: utf-8 -*-
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np  
import matplotlib.pyplot as plt
 
#讀取圖片
img = cv2.imread('lena.bmp')

#灰度轉換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
#直方圖均衡化處理
result = cv2.equalizeHist(gray)

#顯示圖像
plt.subplot(221)
plt.imshow(gray, cmap=plt.cm.gray), plt.axis("off"), plt.title('(a)') 
plt.subplot(222)
plt.imshow(result, cmap=plt.cm.gray), plt.axis("off"), plt.title('(b)') 
plt.subplot(223)
plt.hist(img.ravel(), 256), plt.title('(c)') 
plt.subplot(224)
plt.hist(result.ravel(), 256), plt.title('(d)') 
plt.show()

讀取顯示的“lena”灰度圖像如圖4所示。

在這裏插入圖片描述

最終的灰度直方圖如圖5所示,它將Lena圖256級灰度和各個灰度級的頻數繪製出來,其中x軸表示圖像的256級灰度,y軸表示各個灰度級的頻數。

在這裏插入圖片描述

如果調用下列函數,則繪製的直方圖是經過標準化處理,並且顏色爲綠色、透明度爲0.75的直方圖,如圖6所示。

  • plt.hist(src.ravel(), bins=256, density=1, facecolor=‘green’, alpha=0.75)

在這裏插入圖片描述

彩色直方圖是高維直方圖的特例,它統計彩色圖片RGB各分量出現的頻率,即彩色概率分佈信息。彩色圖片的直方圖和灰度直方圖一樣,只是分別畫出三個通道的直方圖,然後再進行疊加,其代碼如下所示。Lena彩色原始圖像如圖7所示。

在這裏插入圖片描述

#coding:utf-8
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
src = cv2.imread('Lena.png')

#獲取BGR三個通道的像素值
b, g, r = cv2.split(src)
print(r,g,b)

#繪製直方圖
plt.figure("Lena")
#藍色分量
plt.hist(b.ravel(), bins=256, density=1, facecolor='b', edgecolor='b', alpha=0.75)
#綠色分量
plt.hist(g.ravel(), bins=256, density=1, facecolor='g', edgecolor='g', alpha=0.75)
#紅色分量
plt.hist(r.ravel(), bins=256, density=1, facecolor='r', edgecolor='r', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.show()

#顯示原始圖像
cv2.imshow("src", src)
cv2.waitKey(0)
cv2.destroyAllWindows()

繪製的彩色直方圖如圖8所示,包括紅色、綠色、藍色三種對比。

在這裏插入圖片描述

如果希望將三個顏色分量的柱狀圖分開繪製並進行對比,則使用如下代碼實現,調用plt.figure(figsize=(8, 6))函數繪製窗口,以及plt.subplot()函數分別繪製4個子圖。

#coding:utf-8
# By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#讀取圖像
src = cv2.imread('Lena.png')

#轉換爲RGB圖像
img_rgb = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)

#獲取BGR三個通道的像素值
b, g, r = cv2.split(src)
print(r,g,b)

plt.figure(figsize=(8, 6))

#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']

#原始圖像
plt.subplot(221)
plt.imshow(img_rgb)
plt.axis('off')
plt.title("(a)原圖像")

#繪製藍色分量直方圖
plt.subplot(222)
plt.hist(b.ravel(), bins=256, density=1, facecolor='b', edgecolor='b', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)藍色分量直方圖")

#繪製綠色分量直方圖
plt.subplot(223)
plt.hist(g.ravel(), bins=256, density=1, facecolor='g', edgecolor='g', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(c)綠色分量直方圖")

#繪製紅色分量直方圖
plt.subplot(224)
plt.hist(r.ravel(), bins=256, density=1, facecolor='r', edgecolor='r', alpha=0.75)
plt.xlabel("x")
plt.ylabel("y")
plt.title("(d)紅色分量直方圖")
plt.show()

最終輸出的圖形如圖9所示,,圖9(a)表示原圖像,圖9(b)表示藍色分量直方圖,圖9©表示綠色分量直方圖,圖9(d)表示紅色分類直方圖。

在這裏插入圖片描述


三.OpenCV繪製直方圖

前一小節講解了如何調用matplotlib庫繪製直方圖,接下來講解使用OpenCV庫繪製直方圖的方法。在OpenCV中可以使用calcHist()函數計算直方圖,計算完成之後採用OpenCV中的繪圖函數,如繪製矩形的rectangle()函數,繪製線段的line()函數來完成。其中,cv2.calcHist()的函數原型及常見六個參數如下:

  • hist = cv2.calcHist(images, channels, mask, histSize, ranges, accumulate)
    – hist表示直方圖,返回一個二維數組
    – images表示輸入的原始圖像
    – channels表示指定通道,通道編號需要使用中括號,輸入圖像是灰度圖像時,它的值爲[0],彩色圖像則爲[0]、[1]、[2],分別表示藍色(B)、綠色(G)、紅色(R)
    – mask表示可選的操作掩碼。如果要統計整幅圖像的直方圖,則該值爲None;如果要統計圖像的某一部分直方圖時,需要掩碼來計算
    – histSize表示灰度級的個數,需要使用中括號,比如[256]
    – ranges表示像素值範圍,比如[0, 255]
    – accumulate表示累計疊加標識,默認爲false,如果被設置爲true,則直方圖在開始分配時不會被清零,該參數允許從多個對象中計算單個直方圖,或者用於實時更新直方圖;多個直方圖的累積結果用於對一組圖像的直方圖計算






接下來的代碼是計算圖像各灰度級的大小、形狀及頻數,接着調用plot()函數繪製直方圖曲線。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#讀取圖像
src = cv2.imread('lena.bmp')

#計算256灰度級的圖像直方圖
hist = cv2.calcHist([src], [0], None, [256], [0,255])

#輸出直方圖大小、形狀、數量
print(hist.size)
print(hist.shape)
print(hist)

#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']

#顯示原始圖像和繪製的直方圖
plt.subplot(121)
plt.imshow(src, 'gray')
plt.axis('off')
plt.title("(a)Lena灰度圖像")

plt.subplot(122)
plt.plot(hist, color='r')
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)直方圖曲線")
plt.show()

上述代碼繪製的“Lena”灰度圖像所對應的直方圖曲線如圖10所示,圖10(a)表示原圖像,圖10(b)表示對應的灰度直方圖曲線。

在這裏插入圖片描述

同時輸出直方圖的大小、形狀及數量,如下所示:

256
(256L, 1L)
[[7.000e+00]
 [1.000e+00]
 [0.000e+00]
 [6.000e+00]
 [2.000e+00]
 ....
 [1.000e+00]
 [3.000e+00]
 [2.000e+00]
 [1.000e+00]
 [0.000e+00]]

彩色圖像調用OpenCV繪製直方圖的算法與灰度圖像一樣,只是從B、G、R三個放量分別進行計算及繪製,詳見代碼。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#讀取圖像
src = cv2.imread('Lena.png')

#轉換爲RGB圖像
img_rgb = cv2.cvtColor(src, cv2.COLOR_BGR2RGB)

#計算直方圖
histb = cv2.calcHist([src], [0], None, [256], [0,255])
histg = cv2.calcHist([src], [1], None, [256], [0,255])
histr = cv2.calcHist([src], [2], None, [256], [0,255])

#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']

#顯示原始圖像和繪製的直方圖
plt.subplot(121)
plt.imshow(img_rgb, 'gray')
plt.axis('off')
plt.title("(a)Lena原始圖像")

plt.subplot(122)
plt.plot(histb, color='b')
plt.plot(histg, color='g')
plt.plot(histr, color='r')
plt.xlabel("x")
plt.ylabel("y")
plt.title("(b)直方圖曲線")
plt.show()

最終繪製的“Lena”彩色圖像及其對應的彩色直方圖曲線如圖11所示,其中圖11(a)表示Lena原始圖像,圖11(b)表示對應的彩色直方圖曲線。

在這裏插入圖片描述


四.掩膜直方圖

如果要統計圖像的某一部分直方圖,就需要使用掩碼(蒙板)來進行計算。假設將要統計的部分設置爲白色,其餘部分設置爲黑色,然後使用該掩膜進行直方圖繪製,其完整代碼如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

#讀取圖像
img = cv2.imread('yxz.png')

#轉換爲RGB圖像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#設置掩膜
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:300] = 255
masked_img = cv2.bitwise_and(img, img, mask=mask)

#圖像直方圖計算
hist_full = cv2.calcHist([img], [0], None, [256], [0,256]) #通道[0]-灰度圖

#圖像直方圖計算(含掩膜)
hist_mask = cv2.calcHist([img], [0], mask, [256], [0,256])

plt.figure(figsize=(8, 6))

#設置字體
matplotlib.rcParams['font.sans-serif']=['SimHei']

#原始圖像
plt.subplot(221)
plt.imshow(img_rgb, 'gray')
plt.axis('off')
plt.title("(a)原始圖像")

#繪製掩膜
plt.subplot(222)
plt.imshow(mask, 'gray')
plt.axis('off')
plt.title("(b)掩膜")

#繪製掩膜設置後的圖像
plt.subplot(223)
plt.imshow(masked_img, 'gray')
plt.axis('off')
plt.title("(c)圖像掩膜處理")

#繪製直方圖
plt.subplot(224)
plt.plot(hist_full)
plt.plot(hist_mask)
plt.title("(d)直方圖曲線")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

其運行結果如圖12所示,它使用了一個200×200像素的掩膜進行實驗。其中圖12(a)表示原始圖像,圖12(b)表示200×200像素的掩膜,圖12©表示原始圖像進行掩膜處理,圖12(d)表示直方圖曲線,藍色曲線爲原始圖像的灰度值直方圖分佈情況,綠色波動更小的曲線爲掩膜直方圖曲線。

在這裏插入圖片描述


五.圖像灰度變換直方圖對比

前面詳細介紹了圖像灰度變換和閾值變換,本小節將結合直方圖分別對比圖像灰度變換前後的變化,方便讀者更清晰地理解灰度變換和閾值變換。

1.灰度上移變換圖像直方圖對比

圖像灰度上移變換使用的表達式爲:

  • DB=DA+50

該算法將實現圖像灰度值的上移,從而提升圖像的亮度,結合直方圖對比的實現代碼如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#圖像灰度上移變換 DB=DA+50
for i in range(height):
    for j in range(width):
        if (int(grayImage[i,j]+50) > 255):
            gray = 255
        else:
            gray = int(grayImage[i,j]+50)
            
        result[i,j] = np.uint8(gray)

#計算原圖的直方圖
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#計算灰度變換的直方圖
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#繪製掩膜設置後的圖像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#繪製直方圖
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其運行結果如圖13所示,其中(a)表示原始圖像,(b)表示對應的灰度直方圖,(c)表示灰度上移後的圖像,(d)是對應的直方圖。對比發現,圖13(d)比圖13(b)的灰度級整體高了50,曲線整體向右平移了50個單位。

在這裏插入圖片描述


2.灰度減弱圖像直方圖對比

該算法將減弱圖像的對比度,使用的表達式爲:

  • DB=DA×0.8

Python結合直方圖實現灰度對比度減弱的代碼如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#圖像對比度減弱變換 DB=DA×0.8
for i in range(height):
    for j in range(width):
        gray = int(grayImage[i,j]*0.8)
        result[i,j] = np.uint8(gray)

#計算原圖的直方圖
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#計算灰度變換的直方圖
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#繪製掩膜設置後的圖像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#繪製直方圖
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其運行結果如圖14所示,其中(a)和(b)表示原始圖像和對應的灰度直方圖,(c)和(d)表示灰度減弱或對比度縮小的圖像及對應的直方圖。圖14(d)比圖14(b)的灰度級整體縮小了0.8倍,繪製的曲線更加密集。

在這裏插入圖片描述


3.圖像反色變換直方圖對比

該算法將圖像的顏色反色,對原圖像的像素值進行反轉,即黑色變爲白色,白色變爲黑色,使用的表達式爲:

  • DB=255-DA

實現代碼如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#圖像灰度反色變換 DB=255-DA
for i in range(height):
    for j in range(width):
        gray = 255 - grayImage[i,j]
        result[i,j] = np.uint8(gray)

#計算原圖的直方圖
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#計算灰度變換的直方圖
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製掩膜
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#繪製掩膜設置後的圖像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#繪製直方圖
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其運行結果如圖15所示,其中(a)和(b)表示原始圖像和對應的灰度直方圖,(c)和(d)表示灰度反色變換圖像及對應的直方圖。圖15(d)與圖15(b)是反相對稱的,整個灰度值滿足DB=255-DA表達式。

在這裏插入圖片描述


4.圖像對數變換直方圖對比

該算法將增加低灰度區域的對比度,從而增強暗部的細節,使用的表達式爲:

在這裏插入圖片描述

下面的代碼實現了圖像灰度的對數變換及直方圖對比。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#圖像灰度對數變換
for i in range(height):
    for j in range(width):
        gray = 42 * np.log(1.0 + grayImage[i,j])
        result[i,j] = np.uint8(gray)

#計算原圖的直方圖
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#計算灰度變換的直方圖
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製原始圖像直方圖
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#灰度變換後的圖像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#灰度變換圖像的直方圖
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其運行結果如圖16所示,其中(a)和(b)表示原始圖像和對應的灰度直方圖,(c)和(d)表示灰度對數變換圖像及對應的直方圖。

在這裏插入圖片描述


5.圖像閾值化處理直方圖對比

該算法原型爲:

  • threshold(Gray,127,255,cv2.THRESH_BINARY)

當前像素點的灰度值大於thresh閾值時(如127),其像素點的灰度值設定爲最大值(如9位灰度值最大爲255);否則,像素點的灰度值設置爲0。二進制閾值化處理及直方圖對比的Python代碼如下所示。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]
result = np.zeros((height, width), np.uint8)

#圖像灰度對數變換
for i in range(height):
    for j in range(width):
        gray = 42 * np.log(1.0 + grayImage[i,j])
        result[i,j] = np.uint8(gray)

#計算原圖的直方圖
hist = cv2.calcHist([img], [0], None, [256], [0,255])

#計算灰度變換的直方圖
hist_res = cv2.calcHist([result], [0], None, [256], [0,255])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(221), plt.imshow(img, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製原始圖像直方圖
plt.subplot(222), plt.plot(hist), plt.title("(b)"), plt.xlabel("x"), plt.ylabel("y")

#灰度變換後的圖像
plt.subplot(223), plt.imshow(result, 'gray'), plt.title("(c)"), plt.axis('off')

#灰度變換圖像的直方圖
plt.subplot(224), plt.plot(hist_res), plt.title("(d)"), plt.xlabel("x"), plt.ylabel("y")
plt.show()

其運行結果如圖17所示,其中(a)和(b)表示原始圖像和對應的灰度直方圖,(c)和(d)表示圖像閾值化處理及對應的直方圖,圖17(d)中可以看到,灰度值僅僅分佈於0(黑色)和255(白色)兩種灰度級。

在這裏插入圖片描述


六.圖像H-S直方圖

爲了刻畫圖像中顏色的直觀特性,常常需要分析圖像的HSV空間下的直方圖特性。HSV空間是由色調(Hue)、飽和度(Saturation)、以及亮度(Value)構成,因此在進行直方圖計算時,需要先將源RGB圖像轉化爲HSV顏色空間圖像,然後將對應的H和S通道進行單元劃分,再其二維空間上計算相對應直方圖,再計算直方圖空間上的最大值並歸一化繪製相應的直方圖信息,從而形成色調-飽和度直方圖(或H-S直方圖)。該直方圖通常應用在目標檢測、特徵分析以及目標特徵跟蹤等場景。

由於H和S分量與人感受顏色的方式是緊密相連,V分量與圖像的彩色信息無關,這些特點使得HSV模型非常適合於藉助人的視覺系統來感知彩色特性的圖像處理算法。下面代碼是具體的實現代碼,使用matplotlib.pyplot庫中的imshow()函數來繪製具有不同顏色映射的2D直方圖。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#讀取圖像
img = cv2.imread('Lena.png')

#轉換爲RGB圖像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#圖像HSV轉換
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)

#計算H-S直方圖
hist = cv2.calcHist(hsv, [0,1], None, [180,256], [0,180,0,256])

#原始圖像
plt.figure(figsize=(8, 6))
plt.subplot(121), plt.imshow(img_rgb, 'gray'), plt.title("(a)"), plt.axis('off')

#繪製H-S直方圖
plt.subplot(122), plt.imshow(hist, interpolation='nearest'), plt.title("(b)")
plt.xlabel("x"), plt.ylabel("y")
plt.show()

圖18(a)表示原始輸入圖像,圖18(b)是原圖像對應的彩色直方圖,其中X軸表示飽和度(S),Y軸表示色調(H)。在直方圖中,可以看到H=140和S=130附近的一些高值,它對應於豔麗的色調。

在這裏插入圖片描述


七.直方圖判斷黑夜白天

接着講述兩個應用直方圖的案例,第一個是通過直方圖來判斷一幅圖像是黑夜或白天。常見的方法是通過計算圖像的灰度平均值、灰度中值或灰度標準差,再與自定義的閾值進行對比,從而判斷是黑夜還是白天。

  • 灰度平均值:該值等於圖像中所有像素灰度值之和除以圖像的像素個數。
  • 灰度中值:對圖像中所有像素灰度值進行排序,然後獲取所有像素最中間的值,即爲灰度中值。
  • 灰度標準差:又常稱均方差,是離均差平方的算術平均數的平方根。標準差能反映一個數據集的離散程度,是總體各單位標準值與其平均數離差平方的算術平均數的平方根。如果一幅圖看起來灰濛濛的, 那灰度標準差就小;如果一幅圖看起來很鮮豔,那對比度就很大,標準差也大。

下面的代碼是計算灰度“Lena”圖的灰度平均值、灰度中值和灰度標準差。

#coding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#函數: 獲取圖像的灰度平均值
def fun_mean(img, height, width):
    sum_img = 0
    for i in range(height):
        for j in range(width):
            sum_img = sum_img + int(img[i,j])
    mean = sum_img / (height * width)
    return mean

#函數: 獲取中位數
def fun_median(data):
    length = len(data)
    data.sort()
    if (length % 2)== 1: 
        z = length // 2
        y = data[z]
    else:
        y = (int(data[length//2]) + int(data[length//2-1])) / 2
    return y

#讀取圖像
img = cv2.imread('lena.bmp')

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#獲取圖像高度和寬度
height = grayImage.shape[0]
width = grayImage.shape[1]

#計算圖像的灰度平均值
mean = fun_mean(grayImage, height, width)
print("灰度平均值:", mean)

#計算圖像的灰度中位數
value = grayImage.ravel() #獲取所有像素值
median = fun_median(value)
print("灰度中值:", median)

#計算圖像的灰度標準差
std = np.std(value, ddof = 1)
print("灰度標準差", std)

其運行結果如圖9-19所示,圖9-19(a)爲原始圖像,圖9-19(b)爲處理結果。其灰度平均值爲123,灰度中值爲129,灰度標準差爲48.39。

在這裏插入圖片描述

下面講解另一種用來判斷圖像是白天還是黑夜的方法,其基本步驟如下:

  • 讀取原始圖像,轉換爲灰度圖,並獲取圖像的所有像素值;
  • 設置灰度閾值並計算該閾值以下的像素個數。比如像素的閾值設置爲50,統計低於50的像素值個數;
  • 設置比例參數,對比該參數與低於該閾值的像素佔比,如果低於參數則預測爲白天,高於參數則預測爲黑夜。比如該參數設置爲0.8,像素的灰度值低於閾值50的個數佔整幅圖像所有像素個數的90%,則認爲該圖像偏暗,故預測爲黑夜;否則預測爲白天。

具體實現的代碼如下所示。

#encoding:utf-8
#By:Eastmount CSDN 2021-02-05
import cv2  
import numpy as np
import matplotlib.pyplot as plt

#函數: 判斷黑夜或白天
def func_judge(img):
    #獲取圖像高度和寬度
    height = grayImage.shape[0]
    width = grayImage.shape[1]
    piexs_sum = height * width
    dark_sum = 0  #偏暗像素個數
    dark_prop = 0 #偏暗像素所佔比例
    
    for i in range(height):
        for j in range(width):
            if img[i, j] < 50: #閾值爲50
                dark_sum += 1

    #計算比例
    print(dark_sum)
    print(piexs_sum)
    dark_prop = dark_sum * 1.0 / piexs_sum 
    if dark_prop >=0.8:
        print("This picture is dark!", dark_prop)
    else:
        print("This picture is bright!", dark_prop)
               
#讀取圖像
img = cv2.imread('day.png')

#轉換爲RGB圖像
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

#圖像灰度轉換
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#計算256灰度級的圖像直方圖
hist = cv2.calcHist([grayImage], [0], None, [256], [0,255])

#判斷黑夜或白天
func_judge(grayImage)

#顯示原始圖像和繪製的直方圖
plt.subplot(121), plt.imshow(img_rgb, 'gray'), plt.axis('off'), plt.title("(a)")
plt.subplot(122), plt.plot(hist, color='r'), plt.xlabel("x"), plt.ylabel("y"), plt.title("(b)")
plt.show()

第一張測試圖輸出的結果如圖20所示,其中圖20(a)爲原始圖像,圖20(b)爲對應直方圖曲線,最終輸出結果爲“(‘This picture is bright!’, 0.010082704388303882)”,該預測爲白天。

在這裏插入圖片描述

第二張測試圖輸出的結果如圖21所示,其中圖21(a)爲原始圖像,圖21(b)爲對應直方圖曲線,最終輸出結果爲“(‘This picture is dark!’, 0.8511824175824175)”,該預測爲黑夜。

在這裏插入圖片描述

最後補充一段3D直方圖代碼,也請同學們下來進行深入的理解及嘗試。

# -*- coding: utf-8 -*-
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

#讀取圖像
img = cv.imread("yxz.png")
img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
imgd = np.array(img)      #image類轉numpy

#準備數據
sp = img.shape
h = int(sp[0])        #圖像高度(rows)
w = int(sp[1])        #圖像寬度(colums) of image

#繪圖初始處理
fig = plt.figure(figsize=(16,12))
ax = fig.gca(projection="3d")

x = np.arange(0, w, 1)
y = np.arange(0, h, 1)
x, y = np.meshgrid(x,y)
z = imgd
surf = ax.plot_surface(x, y, z, cmap=cm.coolwarm)  

#自定義z軸
ax.set_zlim(-10, 255)
ax.zaxis.set_major_locator(LinearLocator(10))   #設置z軸網格線的疏密
#將z的value字符串轉爲float並保留2位小數
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) 

# 設置座標軸的label和標題
ax.set_xlabel('x', size=15)
ax.set_ylabel('y', size=15)
ax.set_zlabel('z', size=15)
ax.set_title("surface plot", weight='bold', size=20)

#添加右側的色卡條
fig.colorbar(surf, shrink=0.6, aspect=8)  
plt.show()

輸出結果如下圖所示:

在這裏插入圖片描述


八.總結

寫到這裏,本文就介紹完畢。這篇文章主要講解圖像直方圖相關知識點,包括Matplotlib和OpenCV兩種統計及繪製直方圖的方法,接着講解了掩膜直方圖和H-S直方圖,並結合灰度變換對比了常見算法變換前後的直方圖,應用直方圖實現黑夜和白天的判斷。同時,讀者可以嘗試之前的文章和直方圖進行各種繪製,比如均衡化處理、特效處理等等,下圖就是圖像均衡化的直方圖。

在這裏插入圖片描述

時光嘀嗒嘀嗒的流失,這是我在CSDN寫下的第八篇年終總結,比以往時候來的更早一些。《敏而多思,寧靜致遠》,僅以此篇紀念這風雨兼程的一年,這感恩的一年。轉眼小寶白天了,哈哈~提前祝大家新年快樂!

2020年8月18新開的“娜璋AI安全之家”,主要圍繞Python大數據分析、網絡空間安全、人工智能、Web滲透及攻防技術進行講解,同時分享CCF、SCI、南核北核論文的算法實現。娜璋之家會更加系統,並重構作者的所有文章,從零講解Python和安全,寫了近十年文章,真心想把自己所學所感所做分享出來,還請各位多多指教,真誠邀請您的關注!謝謝。

在這裏插入圖片描述

(By:Eastmount 2021-02-06 晚上12點 http://blog.csdn.net/eastmount/ )


參考文獻,在此感謝這些大佬,共勉!

  • [1] 羅子江, 楊秀璋. Python中的圖像處理[M]. 2020.
  • [2]岡薩雷斯. 數字圖像處理(第3版)[M]. 北京:電子工業出版社, 2013.
  • [3]張恆博, 歐宗瑛. 一種基於色彩和灰度直方圖的圖像檢索方法[J]. 計算機工程, 2004.
  • [4]Eastmount. [數字圖像處理] 四.MFC對話框繪製灰度直方圖[EB/OL]. (2015-05-31). https://blog.csdn.net/eastmount/article/details/46237463.
  • [5]苗錫奎, 孫勁光, 張語涵. 圖像歸一化與僞Zernike矩的魯棒水印算法研究[J]. 計算機應用研究, 2010.
  • [6]阮秋琦. 數字圖像處理學(第3版)[M]. 北京:電子工業出版社, 2008.
  • [7]Eastmount. [Python圖像處理] 十一.灰度直方圖概念及OpenCV繪製直方圖[EB/OL]. (2018-11-06). https://blog.csdn.net/Eastmount/article/details/83758402.
  • [8]李立源, 龔堅. 基於二維灰度直方圖最佳一維投影的圖像分割方法[J]. 自動化學報, 1996.
  • [9]楊秀璋, 顏娜. Python網絡數據爬取及分析從入門到精通(分析篇)[M]. 北京:北京航天航空大學出版社, 2018.
  • [10]毛星雲, 冷雪飛. OpenCV3編程入門[M]. 北京:電子工業出版社, 2015.
  • [11]深思海數_willschang. Opencv-Python學習筆記七——圖像直方圖 calcHist,直方圖均衡化equalizeHist[EB/OL]. (2018-08-26). https://www.jianshu.com/p/bd12c4273d7d.
  • [12]ZJE_ANDY. python3+opencv 利用灰度直方圖來判斷圖片的亮暗情況[EB/OL]. (2018-06-20). https://blog.csdn.net/u014453898/article/details/80745987.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章