本文档尝试解答如下问题:
Note
在上一篇中 (直方图均衡化) 我们介绍了一种特殊直方图叫做 图像直方图 。现在我们从更加广义的角度来考虑直方图的概念,继续往下读!
直方图是对数据的集合 统计 ,并将统计结果分布于一系列预定义的 bins 中。
这里的 数据 不仅仅指的是灰度值 (如上一篇您所看到的), 统计数据可能是任何能有效描述图像的特征。
先看一个例子吧。 假设有一个矩阵包含一张图像的信息 (灰度值 ):
如果我们按照某种方式去 统计 这些数字,会发生什么情况呢? 既然已知数字的 范围 包含 256 个值, 我们可以将这个范围分割成子区域(称作 bins), 如:
然后再统计掉入每一个 的像素数目。采用这一方法来统计上面的数字矩阵,我们可以得到下图( x轴表示 bin, y轴表示各个bin中的像素个数)。
以上只是一个说明直方图如何工作以及它的用处的简单示例。直方图可以统计的不仅仅是颜色灰度, 它可以统计任何图像特征 (如 梯度, 方向等等)。
让我们再来搞清楚直方图的一些具体细节:
怎样去统计两个特征呢? 在这种情况下, 直方图就是3维的了,x轴和y轴分别代表一个特征, z轴是掉入 组合中的样本数目。 同样的方法适用于更高维的情形 (当然会变得很复杂)。
本程序做什么?
下载代码: 点击 这里
代码一瞥:
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
/** @函数 main */
int main( int argc, char** argv )
{
Mat src, dst;
/// 装载图像
src = imread( argv[1], 1 );
if( !src.data )
{ return -1; }
/// 分割成3个单通道图像 ( R, G 和 B )
vector<Mat> rgb_planes;
split( src, rgb_planes );
/// 设定bin数目
int histSize = 255;
/// 设定取值范围 ( R,G,B) )
float range[] = { 0, 255 } ;
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat r_hist, g_hist, b_hist;
/// 计算直方图:
calcHist( &rgb_planes[0], 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[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
// 创建直方图画布
int hist_w = 400; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );
/// 将直方图归一化到范围 [ 0, histImage.rows ]
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
/// 在直方图画布上画出直方图
for( int i = 1; i < histSize; i++ )
{
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 );
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(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 );
}
/// 显示直方图
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
}
创建一些矩阵:
Mat src, dst;
装载原图像
src = imread( argv[1], 1 );
if( !src.data )
{ return -1; }
使用OpenCV函数 split 将图像分割成3个单通道图像:
vector<Mat> rgb_planes;
split( src, rgb_planes );
输入的是要被分割的图像 (这里包含3个通道), 输出的则是Mat类型的的向量。
现在对每个通道配置 直方图 设置, 既然我们用到了 R, G 和 B 通道, 我们知道像素值的范围是
设定bins数目 (5, 10...):
int histSize = 255;
设定像素值范围 (前面已经提到,在 0 到 255之间 )
/// 设定取值范围 ( R,G,B) )
float range[] = { 0, 255 } ;
const float* histRange = { range };
我们要把bin范围设定成同样大小(均一)以及开始统计前先清除直方图中的痕迹:
bool uniform = true; bool accumulate = false;
最后创建储存直方图的矩阵:
Mat r_hist, g_hist, b_hist;
下面使用OpenCV函数 calcHist 计算直方图:
/// 计算直方图:
calcHist( &rgb_planes[0], 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[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
参数说明如下:
创建显示直方图的画布:
// 创建直方图画布
int hist_w = 400; int hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );
在画直方图之前,先使用 normalize 归一化直方图,这样直方图bin中的值就被缩放到指定范围:
/// 将直方图归一化到范围 [ 0, histImage.rows ]
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
该函数接受下列参数:
请注意这里如何读取直方图bin中的数据 (此处是一个1维直方图):
/// 在直方图画布上画出直方图
for( int i = 1; i < histSize; i++ )
{
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 );
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(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 );
}
使用了以下表达式:
.. code-block:: cpp
r_hist.at<float>(i)
:math:`i` 指示维度,假如我们要访问2维直方图,我们就要用到这样的表达式:
.. code-block:: cpp
r_hist.at<float>( i, j )
最后显示直方图并等待用户退出程序:
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
imshow("calcHist Demo", histImage );
waitKey(0);
return 0;
使用下图作为输入图像:
产生以下直方图:
niesu@ OpenCV中文网站 <sisongasg@hotmail.com>