圖像像素操做

圖像像素操做一節中,介紹瞭如何訪問像素值、使用指針和迭代器遍歷圖像以及遍歷圖像和鄰域操做。接下來,咱們介紹如何用(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) )

 

其中函數中參數:
  • image 輸入圖像s (雖然也可使用 CvMat** ).
  • hist    直方圖指針
  • accumulate 累計標識。若是設置,則直方圖在開始時不被清零。這個特徵保證能夠爲多個圖像計算一個單獨的直方圖,或者在線更新直方圖。
  • mask 操做 mask, 肯定輸入圖像的哪一個象素被計數

 

函數 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 );

其中函數中參數:測試

  • images 表示須要用來計算直方圖的源圖像序列,所以能夠容許有多張大小同樣,數據類型相同的圖像被用來統計其直方圖特徵。
  • nimages 表示的就是使用多少張圖像序列中的圖像用於計算直方圖。
  • channels的出現主要是考慮到輸入的每一張圖像有多是多通道的,好比說RGB圖就是3通道的,那麼從統計意義上來說,一張RGB圖其實就是3張單通道的圖像,而計算直方圖時其本質也是針對單張圖像進行的。這裏雖然咱們輸入的圖像序列images中有不少圖片,可是並非每一張圖片的每個通道都會被用來計算。因此參數3的功能是指定哪些通道的圖像被用來計算(後面的解釋都假設圖像序列中圖像是3通道的,那麼有的圖像可能有多個通道都被用來計算,有的圖像可能連一個通道都沒有被採用),這時參數3裏面保存的是通道的序號,那麼圖像序列images中的第一張圖片的通道序號(假設圖像時3通道的)爲0,1,2;images中第二張圖片的圖像序列接着上一次的,爲3,4,5,;依次類推便可。
  • mask是mask掩膜操做,即指定每張圖片的哪些像素被用於計算直方圖,這個掩膜矩陣不可以針對特定圖像設定特定的掩膜,所以在這裏是一視同仁對待的。
  • hist是保存計算的直方圖結果的矩陣,有多是多維矩陣。
  • dims是須要計算的直方圖的維數。
  • histSize是所需計算直方圖的每一維的大小,即每一維bin的個數。
  • ranges是所需計算直方圖的每一維的範圍,若是參數9的uniform爲true,這此時的參數8的大小爲2,裏面的元素值表示的是每一維的上下限這兩個數字;若是參數9的uniform爲false,則此時的參數8的大小爲bin的個數,即參數7的值,參數8裏面的元素值須要人爲的指定,即每一維的座標值不必定是均勻的,須要人爲指定。
  • uniform 若是爲true的話,則說明所需計算的直方圖的每一維按照它的範圍和尺寸大小均勻取值;若是爲false的話,說明直方圖的每一維不是均勻分佈取值的,參考參數8的解釋。
  • accumulate, 若是爲false,則表示直方圖輸出矩陣hist在使用該函數的時候被清0了,若是爲true,則表示hist在使用calcHist()函數時沒有被清0,計算的結果會累加到前一次保存的值中。

使用該函數的時候須要注意,若是在默認參數的狀況下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直方圖計算。

C版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;
}

輸出結果以下圖所示


C plus plus版OPENCV直方圖計算

 

 

#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 的圖像直方圖,但局部細節還待於優化。

Python版OpenCV計算直方圖

下面來看下彩色圖像的直方圖處理。首先讀取並分離各通道和接着計算每一個通道的直方圖,這裏將其封裝成一個函數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]在圖像的左上角。

NumPy版的直方圖計算

在查閱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] "Learing OpenCV Computer Vision with the OpenCV Library(the First Edition)"

[5] Utkarsh, "Drawing Histograms in OpneCV"

=======================================================
轉載請註明出處: http://blog.csdn.net/utimes/article/details/8759676
=======================================================
相關文章
相關標籤/搜索