在圖像像素操做一節中,介紹瞭如何訪問像素值、使用指針和迭代器遍歷圖像以及遍歷圖像和鄰域操做。接下來,咱們介紹如何用(C語言版和C++語言版的)OpenCV來計算一維直方圖計算,而後,給合python開發工具和NumPy計算和繪製直方圖。python
在數字圖像處理中,灰度直方圖是一種最簡單、最有用的工具之一,它歸納了一幅圖像的灰度級內容。一個圖像是由不一樣顏色值的像值組成。像素值在圖像中的分佈狀況是這幅圖像的一個重要特徵。OpenCV裏面提供了很多有關直方圖處理的函數。其中最基本的是計算直方圖的函數calcHist( )。數組
首先來看看OpenCV1.1中函數calcHist()以下:
函數
/* Calculates array histogram */ CVAPI(void) cvCalcArrHist( CvArr** arr, CvHistogram* hist, int accumulate CV_DEFAULT(0), const CvArr* mask CV_DEFAULT(NULL) ); CV_INLINE void cvCalcHist( IplImage** image, CvHistogram* hist, int accumulate CV_DEFAULT(0), const CvArr* mask CV_DEFAULT(NULL) )
函數 cvCalcHist 計算一張或多張單通道圖像的直方圖(若要計算多通道,可像如下例子那樣用多個單通道圖來表示)。 用來增長直方塊的數組元素可從相應輸入圖像的一樣位置提取。工具
接下來看看OpenCV2.xx中imgproc.hpp頭文件有關於計算直方圖的3個重載函數calcHist()中最重要的一個,以下所示:開發工具
//! computes the joint dense histogram for a set of images. CV_EXPORTS void calcHist( const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize,const float** ranges, bool uniform=true, bool accumulate=false );
其中函數中參數:測試
使用該函數的時候須要注意,若是在默認參數的狀況下uniform = true,則此時的ranges大小必須是histSize大小的兩倍,而且channels的大小必須等於dims維數。從上面能夠理解,channels裏的值已經指定了使用哪些單通道的圖像來計算目標直方圖,所以當channels的尺寸肯定,則對應的直方圖的維數也就肯定了,因此咱們不能使用多張圖像來計算一個一維的直方圖。優化
另外一個重載函數的形式以下:
ui
//! computes the joint sparse histogram for a set of images. CV_EXPORTS void calcHist( const Mat* images, int nimages, const int* channels, InputArray mask, SparseMat& hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false ); CV_EXPORTS_W void calcHist( InputArrayOfArrays images, const vector<int>& channels, InputArray mask, OutputArray hist, const vector<int>& histSize, const vector<float>& ranges, bool accumulate=false );
雖然從名字上看第一個參數是一個圖像序列,可是咱們並不能經過該函數來計算這些圖像序列的一個一維的直方圖。這個函數中並不像前面的函數那樣須要指定一個參數代表有多少圖像參與計算,由於在images中已經體現有了。另外這個函數也不須要像上面的函數同樣指定直方圖的維數,由於使用這個重載函數就表示默認爲直方圖的維數和channels的尺寸同樣。最後本重載函數中的uniform在函數內部設定了爲true,表面直方圖中每一維都必須是均勻分佈的。總之,上面2個函數是計算多個圖像的直方圖,直方圖能夠是多維的,該維數等於最終用於計算直方圖的單通道的圖像的個數。spa
在使用OpenCV內部的判斷條件時應該使用CV_Assert( )函數,而不是CV_ASSERT()。經過實驗測試發現,雖然通過calcHist()函數計算事後的直方圖保存在hist中,這裏hist是一個Mat類型,而且若是計算的是一維的直方圖的話,則hist是一個列向量。.net
如今,咱們來使用不一樣版的OPENCV直方圖計算。
#include "stdafx.h" #include <cv.h> #include <cxcore.h> #include <highgui.h> IplImage* DrawHistogram(CvHistogram* hist,float scaleX =2,float scaleY =2){ float histMax =0; cvGetMinMaxHistValue(hist,0,&histMax,0); IplImage*imghist =cvCreateImage(cvSize(256*scaleX,64*scaleY),8,1); cvZero(imghist); for( int i=0; i<255; i++){ float histValue =cvQueryHistValue_1D(hist,i); float nextValue =cvQueryHistValue_1D(hist,i+1); CvPoint pt1 = cvPoint( i*scaleX,64*scaleY); CvPoint pt2 = cvPoint((i+1)*scaleX,64*scaleY); CvPoint pt3 = cvPoint((i+1)*scaleX,64*scaleY-(nextValue/histMax)*64*scaleY); CvPoint pt4 = cvPoint(i*scaleX,64*scaleY-(histValue/histMax)*64*scaleY); int numPts =5; CvPoint pts[5]; pts[0] = pt1; pts[1] = pt2; pts[2] = pt3; pts[3] = pt4; pts[4] = pt1; cvFillConvexPoly(imghist,pts,numPts,cvScalar(255,250,0,0)); } return imghist; } int _tmain(int argc, _TCHAR* argv[]) { IplImage*src = cvLoadImage("iris.tif"); cvNamedWindow("Sr"); cvShowImage("Sr",src); int bins =1; int dims =1; int size = 256/bins; float range[] ={0,255}; float* ranges[] ={range}; CvHistogram * hist = cvCreateHist(dims,&size,CV_HIST_ARRAY,ranges,1); cvClearHist(hist); IplImage *imgRed = cvCreateImage(cvGetSize(src),8,1); IplImage *imgGreen = cvCreateImage(cvGetSize(src),8,1); IplImage *imgBlue = cvCreateImage(cvGetSize(src),8,1); cvSplit(src,imgBlue,imgGreen,imgRed,NULL); cvCalcHist(&imgBlue,hist,0,0); IplImage*histBlue = DrawHistogram(hist); cvClearHist(hist); cvNamedWindow("Blue"); cvShowImage("Blue",histBlue); cvCalcHist(&imgGreen,hist,0,0); IplImage* histGreen = DrawHistogram(hist); cvClearHist(hist); cvNamedWindow("Green"); cvShowImage("Green",histGreen); cvCalcHist(&imgRed,hist,0,0); IplImage* histRed = DrawHistogram(hist); cvClearHist(hist); cvNamedWindow("Red"); cvShowImage("Red",histRed); cvWaitKey(0); cvDestroyAllWindows(); return 0; }
輸出結果以下圖所示
#include "stdafx.h" #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int _tmain(int argc, _TCHAR* argv[]) { Mat src = imread("iris.tif"); //Load image Mat dst; if(!src.data) exit(0); // Separate the image in 3 places ( B, G and R ) vector<Mat> bgr_planes; split( src, bgr_planes ); int histSize = 256; // Establish the number of bins float range[] = {0, 256}; /// Set the ranges ( for B,G,R) ) const float* histRange = {range}; bool uniform = true; bool accumulate = false; Mat b_hist, g_hist, r_hist; // Compute the histograms: calcHist(&bgr_planes[0],1,0,Mat(),b_hist,1,&histSize,&histRange,uniform,accumulate); calcHist(&bgr_planes[1],1,0,Mat(),g_hist,1,&histSize,&histRange,uniform,accumulate); calcHist(&bgr_planes[2],1,0,Mat(),r_hist,1,&histSize,&histRange,uniform,accumulate); // Draw the histograms for B, G and R int hist_w = 600; int hist_h = 400; int bin_w = cvRound( (double) hist_w/histSize ); Mat histImage(hist_h,hist_w,CV_8UC3,Scalar(0,0,0)); // Normalize the result to [ 0, histImage.rows ] normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() ); normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() ); normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() ); // Draw for each channel for( int i = 1; i < histSize; i++ ){ line(histImage,Point(bin_w*(i-1),hist_h-cvRound(b_hist.at<float>(i-1))), Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255,0,0),2,8,0); line(histImage,Point(bin_w*(i-1),hist_h-cvRound(g_hist.at<float>(i-1))), Point(bin_w*(i),hist_h-cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0),2,8,0); line(histImage,Point(bin_w*(i-1),hist_h-cvRound(r_hist.at<float>(i-1))), Point(bin_w*(i),hist_h-cvRound(r_hist.at<float>(i))), Scalar(0,0,255),2,8,0); } namedWindow("calcHist", CV_WINDOW_AUTOSIZE ); imshow("calcHist", histImage ); waitKey(0); destroyAllWindows(); return 0; }
輸出結果以下:
在這裏,輸出圖像總體反應了iris.tif 的圖像直方圖,但局部細節還待於優化。
下面來看下彩色圖像的直方圖處理。首先讀取並分離各通道和接着計算每一個通道的直方圖,這裏將其封裝成一個函數calcAndDrawHist:
import cv2 import numpy as np def calcAndDrawHist(image, color): hist= cv2.calcHist([image], [0], None, [256], [0.0,255.0]) minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist) histImg = np.zeros([256,256,3], np.uint8) hpt = int(0.9* 256); for h in range(256): intensity = int(hist[h]*hpt/maxVal) cv2.line(histImg,(h,256), (h,256-intensity), color) return histImg; if __name__ == '__main__': img = cv2.imread("..//iris.tif") b, g, r = cv2.split(img) histImgB = calcAndDrawHist(b, [255, 0, 0]) histImgG = calcAndDrawHist(g, [0, 255, 0]) histImgR = calcAndDrawHist(r, [0, 0, 255]) cv2.imshow("histBlue", histImgB) cv2.imshow("histGreen", histImgG) cv2.imshow("histRed", histImgR) cv2.imshow("Src", img) cv2.waitKey(0) cv2.destroyAllWindows()
這樣就能獲得三個通道的直方圖了,以下:
參考abid rahman的作法,無需分離通道,用折線來描繪直方圖的邊界可在一副圖中同時繪製三個通道的直方圖。方法以下:
import cv2 import numpy as np img = cv2.imread('..//iris.tif') h = np.zeros((256,256,3)) bins = np.arange(256).reshape(256,1) color = [ (255,0,0),(0,255,0),(0,0,255) ] for ch, col in enumerate(color): originHist = cv2.calcHist([img],[ch],None,[256],[0,256]) cv2.normalize(originHist, originHist,0,255*0.9,cv2.NORM_MINMAX) hist=np.int32(np.around(originHist)) pts = np.column_stack((bins,hist)) cv2.polylines(h,[pts],False,col) h=np.flipud(h) cv2.imshow('colorhist',h) cv2.waitKey(0)
結果以下圖所示:
說明:
這裏的for循環是對三個通道遍歷一次,每次繪製相應通道的直方圖的折線。for循環的第一行是計算對應通道的直方圖,通過上面的介紹,應該很容易就能明白。
這裏所不一樣的是沒有手動的計算直方圖的最大值再乘以一個係數,而是直接調用了OpenCV的歸一化函數。該函數將直方圖的範圍限定在0-255×0.9之間,與以前的同樣。
語句hist= np.int32(np.around(originHist))先將生成的原始直方圖中的每一個元素四捨六入五湊偶取整(cv2.calcHist函數獲得的是float32類型的數組),接着將整數部分轉成np.int32類型。即61.123先轉成61.0,再轉成61。注意,這裏必須使用np.int32(...)進行轉換,numpy的轉換函數能夠對數組中的每一個元素都進行轉換,而Python的int(...)只能轉換一個元素,若是使用int(...),將致使only length-1 arrays can be converted to Python scalars錯誤。
語句pts = np.column_stack((bins,hist))是將直方圖中每一個bin的值轉成相應的座標。好比hist[0] =3,...,hist[126] = 178,...,hist[255] = 5;而bins的值爲[[0],[1],[2]...,[255]]。使用np.column_stack將其組合成[0, 3]、[126, 178]、[255, 5]這樣的座標做爲元素組成的數組。
最後使用cv2.polylines函數根據這些點繪製出折線,第三個False參數指出這個折線不須要閉合。第四個參數指定了折線的顏色。
當全部完成後,別忘了用h = np.flipud(h)反轉繪製好的直方圖,由於繪製時,[0,0]在圖像的左上角。
在查閱abid rahman的資料時,發現他用NumPy的直方圖計算函數np.histogram也實現了相同的效果。以下:
import cv2 import numpy as np img = cv2.imread('../iris.tif') h = np.zeros((300,256,3)) bins = np.arange(257) bin = bins[0:-1] color = [ (255,0,0),(0,255,0),(0,0,255) ] for ch,col in enumerate(color): item = img[:,:,ch] N,bins = np.histogram(item,bins) v=N.max() N = np.int32(np.around((N*255)/v)) N=N.reshape(256,1) pts = np.column_stack((bin,N)) cv2.polylines(h,[pts],False,col) h=np.flipud(h) cv2.imshow('img',h) cv2.waitKey(0)
輸出結果以下:
效果圖和上面的一個相同。
未完待續。。。若有錯誤,請多多指正。謝謝!
[1] Robert Lagnaiere "OpenCV 2 Computer Vision Application Programming Cookbook".
[2] Daniel Lelis Baggio,Shervin Emami,"Mastering OpenCV with Practical Computer Vision Projects"
[3] Joseph Howse "OpenCV Computer Vision with Python".
[4]Gary Bradski, Adrian Kaehler "Learing OpenCV Computer Vision with the OpenCV Library(the First Edition)"
[5] Utkarsh, "Drawing Histograms in OpneCV"