博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
简单的直方图绘制
阅读量:4148 次
发布时间:2019-05-25

本文共 7972 字,大约阅读时间需要 26 分钟。

图像直方图是反映一个图像像素分布的统计表,其实横坐标代表了图像像素的种类,可以是灰度的,也可以是彩色,图像是由像素构成,因为反映像素分布的直方图往往可以作为图像一个很重要的特征。

在opencv中,直方图可以通过函数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 );

其中,参数1:images(arrays): 源输入(图像)数组,必须是相同深度的CV_8U或者CV_32F(即uchar或者float),相同大小,每一个可以是任意通道的;

   参数2:nimages(narrays): 源输入数组中的元素个数(本例只有一幅图像,故为1)

   参数3:channels: 图像的通道,它是一个数组,如果是灰度图像(单通道)则channels[1]={0},如果是彩色图像则channels[3]={0,1,2};如果是只是求彩色图像第2个通道的直方图,则channels[1]={1};

   参数4:mask: 可选掩码,是一个遮罩图像用于确定哪些点参与计算,实际应用中是个很好的参数,默认情况我们都设置为一个空图像,即:Mat()。

   参数5:hist: 输出直方图,是一个稠密或者稀疏的dims维的数组;

   参数6:dims: 直方图的维数,(得到的直方图的维数,灰度图像为1维,彩色图像为3维。)必须为正,并且不大于CV_MAX_DIMS(32)(本例中为1,因为统计的是每幅单通道图像的灰度直方图

   参数7:histSize:用于指出直方图数组每一维的大小的数组,即指出每一维的bin的个数的数组(本例中,只有1维,所以例子1中直接对int取地址作为参数,即该维的bin的个数为256

   参数8: ranges:用于指出直方图每一维的每个bin的上下界范围数组的数组。

   参数9:uniform:判断直方图是均匀与否(均匀:true,非均匀:false)

   参数10:累加标志(单幅图像不进行累计所以例子1中为false)

需要注意的是:

(1)如果channels参数为0,则nimages(narrays):和dims必须相等。

(2)当channels不是0的时候,用于计算直方图的图像是images(arrays)中由channels指定的通道的图像,channels与images(arrays)中的图像的对应关系,如channels的参数说明的,将images(arrays)中的图像从第0幅开始按照通道摊开排列起来,然后channels中的指定的用于计算直方图的就是这些摊开的通道;

例如images(arrays)中只有一幅三通道的图像image,那么nimages(narrays)应该为1,如果是想计算3维直方图【最大也只能是3维的】,想将images的通道2作为第一维,通道0作为第二维,通道1作为第三维,则可以将channels设置为channesl={2,0,1};这样calcHist函数计算时就按照这个顺序来统计直方图。
可以看出channels不为0时narrays可以和dims不相等,只要保证images(arrays)中至少有channels指定的通道就可以。

 下面给出一个详细的例子:

#include "stdafx.h"#include 
#include
#include
#include
#pragma comment(lib, "opencv_core246d.lib")#pragma comment(lib, "opencv_highgui246d.lib")#pragma comment(lib, "opencv_imgproc246d.lib")using namespace cv;using namespace std;#define HIST_DIM1int main( int argc, char** argv ){#ifdef HIST_DIM1//----------------------example 1-------------------------------// Mat src, dst; /// Load image //加载图像 src = imread("D:\\EgtProject\\image\\test.jpg"); if( !src.data ) { cout<<"load image failed"<
rgb_planes;//#define SHOW_HSV#ifdef SHOW_HSV Mat hsv; cvtColor(src, hsv, COLOR_BGR2HSV); split(hsv, rgb_planes ); #else split(src, rgb_planes ); #endif /// Establish the number of bins int histSize = 256; //用于形成直方图的“收集箱”的个数(级灰度级) /// Set the ranges ( for R,G,B) ) float range[] = { 0, 255 } ; const float* histRange = { range }; bool uniform = true; bool accumulate = false; Mat r_hist, g_hist, b_hist; /// Compute the histograms: calcHist( &rgb_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate ); calcHist( &rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate ); calcHist( &rgb_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate ); //r_hist中的data指针指向的数据,是每一个收集箱中,对应像素值得个数,即每个像素都有属于 //它的一个收集箱 for( int i = 1; i < histSize; i++ ) { int rt=r_hist.at
(i-1); //r_hist.data[256]={14094,1373,1474,1188,...,722,825,1012} cout << "ii=" <
<<"->"<<
(i-1); //每个收集箱上面的值 //由于图像的原点在左上角,故需要hist_h-cvRound(rt) line( histImage, Point( bin_w*(i-1), hist_h-cvRound(rt) ) , //r_hist[0-255]:存储的数据为,如:0对应3个点,1对应30个点,...,255对应20个点 Point( bin_w*(i), hist_h-cvRound(r_hist.at
(i)) ), //r_hist.data={3,30,...,20} Scalar( 0, 0, 255), 4); //BGR line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at
(i-1)) ) , Point( bin_w*(i), hist_h-cvRound(g_hist.at
(i)) ), Scalar( 0, 255, 0), 4); line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at
(i-1)) ) , Point( bin_w*(i), hist_h-cvRound(b_hist.at
(i)) ), Scalar( 255, 0, 0), 4); } //将计数绘制到图像上(BGR) putText(histImage, "R", Point(300+10, 30), FONT_HERSHEY_SIMPLEX, 1, Scalar( 0, 0, 255), 3); putText(histImage, "G", Point(300-20, 250), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 255, 0), 3); putText(histImage, "B", Point(300+10, 300), FONT_HERSHEY_SIMPLEX, 1, Scalar( 255, 0, 0), 3); //绘制一个矩形(条形) for (int j=0; j
(r_hist.at
(j)); //每个收集箱(bin)中的数值 //rgb_hist[0].row是rgb_hist[0]这个图像的高 //由于图像的原点在左上角,因此rgb_hist[0].rows-val //点 Point(j*2+10, rgb_hist[0].rows):矩形的左下角,点Point((j+1)*2+10, rgb_hist[0].rows-val):矩形的右上角 rectangle(rgb_hist[0], Point(j*2+10, rgb_hist[0].rows), Point((j+1)*2+10, rgb_hist[0].rows-val), Scalar(0,0,255),1,8); val = saturate_cast
(g_hist.at
(j)); rectangle(rgb_hist[1], Point(j*2+10, rgb_hist[1].rows), Point((j+1)*2+10, rgb_hist[1].rows-val), Scalar(0,255,0),1,8); val = saturate_cast
(b_hist.at
(j)); rectangle(rgb_hist[2], Point(j*2+10, rgb_hist[2].rows), Point((j+1)*2+10, rgb_hist[2].rows-val), Scalar(255,0,0),1,8); /**********直方图绘制倒立了-因为图像的原点从左上角开始的***********/ val = saturate_cast
(r_hist.at
(j)); //每个收集箱(bin)中的数值 //rgb_hist[0].row是rgb_hist[0]这个图像的高 //由于图像的原点在左上角,因此rgb_hist[0].rows-val rectangle(rgb_hist1[0], Point(j*2+10, 0), Point((j+1)*2+10, val), Scalar(0,0,255),1,8); val = saturate_cast
(g_hist.at
(j)); rectangle(rgb_hist1[1], Point(j*2+10, 0), Point((j+1)*2+10,val), Scalar(0,255,0),1,8); val = saturate_cast
(b_hist.at
(j)); rectangle(rgb_hist1[2], Point(j*2+10, 0), Point((j+1)*2+10, val), Scalar(255,0,0),1,8); } /// Display namedWindow("histImage", CV_WINDOW_AUTOSIZE ); namedWindow("wnd"); imshow("histImage", histImage ); imshow("wnd", src); imshow("R", rgb_hist[0]); imshow("G", rgb_hist[1]); imshow("B", rgb_hist[2]); imshow("R1", rgb_hist1[0]); imshow("G1", rgb_hist1[1]); imshow("B1", rgb_hist1[2]);#else//----------------------example 2-------------------------------// Mat src, hsv; if(!(src=imread("D:\\EgtProject\\image\\test.jpg")).data) return -1; cvtColor(src, hsv, CV_BGR2HSV); // Quantize the hue to 30 levels // and the saturation to 32 levels int hbins = 60, sbins = 64; int histSize[] = {hbins, sbins}; // hue varies from 0 to 179, see cvtColor float hranges[] = { 0, 180 }; // saturation varies from 0 (black-gray-white) to // 255 (pure spectrum color) float sranges[] = { 0, 256}; const float*ranges[] = { hranges, sranges }; MatND hist; // we compute the histogram from the 0-th and 1-st channels int channels[] = {0, 1}; calcHist( &hsv, 1, channels, Mat(),hist, 2, histSize, ranges,true, false ); double maxVal=0; minMaxLoc(hist, 0, &maxVal, 0, 0); int scale = 8; Mat histImg = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3); for( int h = 0; h < hbins; h++ ) { for( int s = 0; s < sbins; s++ ) { float binVal = hist.at
(h, s); int intensity = cvRound(binVal*255/maxVal); rectangle( histImg, Point(h*scale, s*scale),Point((h+1)*scale-1, (s+1)*scale-1), Scalar::all(intensity), CV_FILLED); } } namedWindow( "Source", 1 ); imshow( "Source", src ); namedWindow( "H-S Histogram", 1 ); imshow( "H-S Histogram", histImg );#endif //-------------------------------------------------------------------------// waitKey(0); destroyAllWindows(); return 0;}
运行结果:

图1 原始图

图2 RGB三个通道的直方图曲线

图3 R通道的直方图(正立)

图4 R通道的直方图(倒立)

图5 G通道的直方图(正立)

图 6 G通道的直方图(倒立)

图 7 B通道的直方图(正立)

图8 B 通道的直方图(倒立)

分析:

    (1)出现倒立的原因是因为,opencv中图像默认是把图像的左上角作为原点。

    (2)在opencv中RGB图像中的排列顺序是:BGR,因此,在设置颜色的时候也需要注意,如设置颜色值为红色, 是使用Scalar( 0, 0, 255)。

    (3)在直方图中,横坐标是收集箱(bins)的个数,纵坐标是相应的频数,即与收集箱区域的值对应的像素个数的统计。

    (4)对于函数calcHist,它的输出变量hist,是一个Mat类型,这个变量对应的data指针是指向,收集箱(bins)的频数,如在上面例子中data指向序列{14094,1373,1474,1188,...,722,825,1012},其中14094是第1个bins中的频数,1373是第2个bin中的频数,...,1012是第256个bins中的频数(本例中收集箱(bins)的个数是256个)。

下面我们通过matlab,来测试一下,我们只打算输出R分量,同样我们打算设置收集箱(bins)的个数为256,一般情况下, bins的个数或者说条状的个数,可以认为是灰度级的个数,比如收集箱(bins)的个数为128,数据的变化范围是0-255,那么我们可以在0-255这256个数据区间中,分割128个区间,然后统计像素掉落在每个区间个数,即得到每个区间的频数。这128个区间,在数学上,可以这样分,[-0.5,1.5],[1.5,3.5],...,[253.5,255.5],也就是所分区间的最大值要比255稍微大,区间的最小值比0要稍微小,在这里,我们去了-0.5和255.5,下面是测试结果:

图9 matlab中测试的R分量的柱状图

图10 matlab中R分量的曲线图直方图

代码:

close all

clc,clear
img=imread('test.jpg');
r=img(:,:,1); %R分量
g=img(:,:,2); %G分量
b=img(:,:,3); %B分量
%nbins=128;    %区间(收集箱,即灰度级的个数)的个数
%[n,xout]=imhist(r,nbins); %n:是一个nbins*1大小的向量,记录了每个bin内的像素的个数
                          %xout是区间分界值
                          
h=imhist(r,256);
h1=h(1:1:256);
horz = 1:1:256;
bar(horz,h1);
axis([0,255,0 2600])
ylabel('频数');
xlabel('灰度级')
title('R分量')
figure
plot(1:256,h)
ylabel('频数');
xlabel('灰度级')
title('R分量')
axis([0,255,0 2600])

参考文献: 

1.

2.

你可能感兴趣的文章
Pentaho 开发: 在eclipse中构建Pentaho BI Server工程
查看>>
JSP的内置对象及方法
查看>>
android中SharedPreferences的简单例子
查看>>
android中使用TextView来显示某个网址的内容,使用<ScrollView>来生成下拉列表框
查看>>
andorid里关于wifi的分析
查看>>
Spring MVC和Struts2的比较
查看>>
Hibernate和IBatis对比
查看>>
Spring MVC 教程,快速入门,深入分析
查看>>
Android 的source (需安装 git repo)
查看>>
Commit our mod to our own repo server
查看>>
LOCAL_PRELINK_MODULE和prelink-linux-arm.map
查看>>
Simple Guide to use the gdb tool in Android environment
查看>>
Netconsole to capture the log
查看>>
Build GingerBread on 32 bit machine.
查看>>
How to make SD Card world wide writable
查看>>
Detecting Memory Leaks in Kernel
查看>>
Linux initial RAM disk (initrd) overview
查看>>
Timestamping Linux kernel printk output in dmesg for fun and profit
查看>>
There's Much More than Intel/AMD Inside
查看>>
CentOS7 安装MySQL 5.6.43
查看>>