【小林的OpenCV基礎課 16】直方圖/一定會再次啟程

宇宙よりも遠い場所

「還能回頭說明旅途還沒有開始;當你無法回頭的時候,才是真正的旅途!」

——三宅日向

這一話的結構啊:

  • 關於直方圖
  • 怎麼畫直方圖
  • 直方圖均衡化
  • 直方圖對比

關於直方圖

說到直方圖,同學們一定會想到統計學裡的直方圖(沒有想起來的抓緊去翻下中學數學課本)。所謂直方圖啊,就是把一大坨東西分成好幾部分(橫坐標),然後看看每一部分有多少(縱坐標)。比如說康娜醬的班裡要做個人愛好的統計,喜歡跳繩的有5人,喜歡唱歌的有8人,喜歡讀書的有6人,超喜歡康娜醬的有1人(才川同學了解一下)。

才川同學的日常高潮 黑化了?

那麼圖像中的直方圖又是個啥子呢?喜歡攝影的同學一定見過。圖像中常見的直方圖是亮度直方圖(在CV中還有一種H-S直方圖,即在HSV空間中計算,若沒有特殊說明,將會默認介紹亮度直方圖),橫坐標表示像素值的分布(通常0~255),縱坐標為像素個數,那麼圖像直方圖的含義就是數數看每個像素值對應的像素個數,進一步講,橫坐標的左側為暗的區域,右側為亮的區域,所以圖像的亮度分布情況就可以反應在亮度直方圖中。

直方圖是一個統計量,往往能反應圖像的色彩(亦可以說是光線)特徵,卻丟掉了像素位置信息,所以只要色彩信息即像素值不變,即使打亂像素的相對位置,直方圖也是不會變化的。

單反相機中的直方圖

直方圖的繪製

在OpenCV中,我們使用函數calcHist來計算直方圖(僅僅是計算),如果使用python,則可藉助matplotlib繪製出直方圖(python的話當然也可以用OpenCV中的繪圖函數,但有matplotlib這麼好的工具為什麼不用呢)。

注意了,這一話寫於B站在納斯達克上市之際,為了慶祝這一偉大時刻,小林選擇使用折線圖而不是數學上定義的直方圖。

API:

hist=cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

  • hist:直方圖的數據,是一個二維數組
  • image:數組集合,這裡就是圖像集合,可以是一幅或多幅圖像,需要有相同的深度(CV_8U或CV_32F)和尺寸
  • channels:要統計哪個通道的像素,彩色圖像的話就有3個通道
  • mask:掩膜,當我們想要對圖像中某一區域做直方圖時就要在圖像上加一層掩膜
  • histSize:要把橫軸分成多少份,實際的步長等於ranges/histSize
  • ranges:統計量的取值範圍,注意不是縱軸的取值範圍,而是橫軸的取值範圍比如統計量是像素,那麼通常就是像素值的取值範圍

注意

  • 參數images、channels、histSize和ranges參數都要寫成列表的形式,因為這個函數允許處理多幅圖像或多個通道
  • 當要處理多幅圖像或多個通道時,histSize和ranges中參數的順序是與待處理的矩陣一一對應,比如channels寫成[0,1],那麼ranges寫成[0,180,0,256],即通道0的ranges是[0,180],通道2的ranges是[0,256],這個數值組合其實是分析HSV時用到的

好的,我們來看一個Demo,要做下面幾個事,素材取自番劇《比宇宙更遠的地方》第13話:

  • 計算並繪製灰度圖像的直方圖
  • 計算並繪製彩色圖像的直方圖
  • 使用掩膜來繪製直方圖,掩膜是通過確定ROI並進行邏輯運算得到的

import cv2import numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread(Drone.jpg)grayImg = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)cv2.imshow(Gray Image, grayImg)# 灰度圖像的直方圖grayHist = cv2.calcHist([grayImg], [0], None, [256], [0, 256])plt.subplot(321), plt.plot(grayHist)# 彩色圖像生成直方圖color = (b, g, r)for i, col in enumerate(color): bgrHist = cv2.calcHist([img], [i], None, [256], [0, 256]) plt.subplot(322), plt.plot(bgrHist, color=col) plt.xlim([0, 256])# 掩膜處理# 創建掩膜mask = np.zeros(grayImg.shape[:2], np.uint8)mask[200:500, 300:600] = 255masked_img = cv2.bitwise_and(grayImg, grayImg, mask=mask)# 分別計算不帶掩膜和帶掩膜的直方圖數據hist_full = cv2.calcHist([grayImg], [0], None, [256], [0, 256])hist_mask = cv2.calcHist([grayImg], [0], mask, [256], [0, 256])plt.subplot(323), plt.imshow(grayImg, gray)plt.subplot(324), plt.imshow(mask, gray)plt.subplot(325), plt.imshow(masked_img, gray)plt.subplot(326), plt.plot(hist_full), plt.plot(hist_mask)plt.xlim([0, 256])plt.show()cv2.waitKey()cv2.destroyAllWindows()

直方圖均衡化(Histogram Equalization)

上文中講到亮度直方圖描述的是亮度的統計分布情況。所以一幅較亮的圖像中,直方圖將會偏向右側,較暗的圖像則會偏向左側。然而,一幅理想的圖像應該是亮暗適中,因此對應的亮度直方圖應該呈均勻分布的樣子,對於較亮或較暗的圖像我們需要進行直方圖的拉伸,這就是直方圖均衡化要做的事情。

拉伸直方圖使其均勻分布

用到的函數是equalizeHist,函數原型:

dst=cv2.equalizeHist(src[, dst])

  • src:需要進行均衡化處理的圖像
  • dist:均衡化後的圖像

來一個Demo,素材取自番劇《比宇宙更遠的地方》第13話

import cv2import numpy as npfrom matplotlib import pyplot as pltimg = cv2.imread(Drone.jpg)b, g, r = cv2.split(img)# OpenCV中的RGB模型順序為BGR matplotlib的RGB的順序就是RGBimgRGB = cv2.merge((r, g, b))equalHistImgBlue = cv2.equalizeHist(b)equalHistImgRed = cv2.equalizeHist(r)equalHistImgGreen = cv2.equalizeHist(g)equalHistImg = cv2.merge((equalHistImgBlue, equalHistImgGreen, equalHistImgRed))equalHistImgRGB = cv2.merge((equalHistImgRed, equalHistImgGreen, equalHistImgBlue))plt.subplot(221), plt.imshow(imgRGB)plt.subplot(222), plt.imshow(equalHistImgRGB)color = (b, g, r)for i, col in enumerate(color): srcHist = cv2.calcHist([img], [i], None, [256], [0, 256]) tarHist = cv2.calcHist([equalHistImg], [i], None, [256], [0, 256]) plt.subplot(223), plt.plot(srcHist, color=col) plt.subplot(224), plt.plot(tarHist, color=col) plt.xlim([0, 256])plt.show()cv2.waitKey()

直方圖對比

亮度直方圖反應了一幅圖像的亮度分布,這一特性可以用來比較兩幅圖像的相似性,也可以研究不同光照對同一幅圖像的影響。

要用到函數compareHist,函數原型:

retval=cv2.compareHist(H1, H2, method)

  • retval:兩個直方圖的相似度
  • H1和H2:需要比較的兩個直方圖
  • method:比較方法,這裡介紹三種,其他的方法可以查閱API

這三種方法依次是:

  • HISTCMP_CORREL(相關):函數返回值越大相似度越高, ar{H_{1}}ar{H_{2}} 是兩個直方圖的均值
  • HISTCMP_CHISQR(卡方):函數返回值越低相似度越高
  • HISTCMP_INTERSECT(直方圖相交):函數返回值越高相似度越高

來一個Demo,我們要比較三張圖片,這三張圖片是小林早上吃面的時候拍的(愛吃拉麵的小泉同學了解一下)

拉麵教主 小泉同學

斜視角 未使用閃光燈

斜視角 使用閃光燈

上帝視角 未使用閃光燈

import cv2import numpy as npfrom matplotlib import pyplot as pltimg1 = cv2.imread(noodle1.JPG)img2 = cv2.imread(noodle2.JPG)img3 = cv2.imread(noodle3.JPG)grayImg1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)grayImg2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)grayImg3 = cv2.cvtColor(img3, cv2.COLOR_BGR2GRAY)# 從BGR轉換到RGB 便於使用matplotlib顯示img1RGB = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)img2RGB = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)img3RGB = cv2.cvtColor(img3, cv2.COLOR_BGR2RGB)grayHist1 = cv2.calcHist([grayImg1], [0], None, [256], [0, 256])grayHist2 = cv2.calcHist([grayImg2], [0], None, [256], [0, 256])grayHist3 = cv2.calcHist([grayImg3], [0], None, [256], [0, 256])result1 = cv2.compareHist(grayHist1, grayHist2, cv2.HISTCMP_CORREL)result2 = cv2.compareHist(grayHist1, grayHist3, cv2.HISTCMP_CORREL)print(result1)print(result2)plt.subplot(231), plt.imshow(img1RGB)plt.subplot(232), plt.imshow(img2RGB)plt.subplot(233), plt.imshow(img3RGB)plt.subplot(234), plt.plot(grayHist1)plt.subplot(235), plt.plot(grayHist2)plt.subplot(236), plt.plot(grayHist3)plt.show()cv2.waitKey()

# 輸出結果如下 可以看出 亮度直方圖對比對光線較敏感0.527483236095986# 視角相同 光線不同0.885018249543686# 視角不同 光線相同

這一話的示例代碼已經同步到Github上了,分別對應Class 4 Image Processing文件夾下的C4 Hist1.py、C4 Hist2.py和C4 Hist Comparison.py。點個Star,手有餘香(づ ̄ 3 ̄)づ

KobayashiLiu/Kobayashi_OpenCV_py?

github.com圖標


KobayashiLiu/Kobayashi_OpenCV_py


最後的最後

如果喜歡小林的專欄,就收藏了吧!してください!


推薦閱讀:

使用opencv製作分類器
用 opencv和numpy進行圖片和字元串互轉,並保存至 json
每天一練P5-Python和OpenCV做圖像處理(GaussianBlur)
想用OpenCV做AR該如何入手?
如何在unity里使用opencv?

TAG:OpenCV | Python | 計算機視覺 |