6.4 直方图
6.4.1Opencv直方图定义
直方图是对数据的集合统计,并将统计结果分布于一系列预定义的bins中。这里的数据不仅仅指的是灰度值, 统计数据可能是任何能有效描述图像的特征。
看一个例子,假设有一个矩阵包含一张图像的信息(灰度值):
如果我们按照某种方式去统计这些数字,会发生什么情况呢? 既然已知数字的范围包含 256 个值, 我们可以将这个范围分割成子区域(称作bins), 如:
然后再统计掉入每一个的像素数目。采用这一方法来统计上面的数字矩阵,我们可以得到下图(x轴表示bin,y轴表示各个bin中的像素个数)。
以上只是一个说明直方图如何工作以及它的用处的简单示例。直方图可以统计的不仅仅是颜色灰度, 它可以统计任何图像特征 (如 梯度, 方向等等)。
直方图的一些具体细节:
dims: 需要统计的特征的数目,在上例中,dims = 1因为我们仅仅统计了灰度值(灰度图像)。
bins: 每个特征空间子区段的数目,在上例中,bins = 16
range: 每个特征空间的取值范围,在上例中,range = [0,255]
统计两个特征的直方图就是3维的了,x轴和y轴分别代表一个特征,z轴是掉入组合中的样本数目。同样的方法适用于更高维的情形(当然会变得很复杂)。
6.4.2 Opencv相关接口
void cv::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 输入数组(或数组集)。它们都应该具有相同的深度,CV_8U, CV_16U或CV_32F,以及相同的大小。它们中的每一个都可以有任意数量的通道。
nimages 源图像数量。(一通道的则为1)
channels 用于计算直方图的dims通道列表。第一个数组通道的编号是从0到图像[0].channels()-1,第二个数组通道的编号是从图像[0].channels()到图像[0].channels() +图像[1].channels()-1,以此类推。只是统计了灰度时为0。
mask 掩码( 0 表示忽略该像素), 如果未定义,则不使用掩码
hist 储存直方图的矩阵
dims 直方图维数
histSize 每个维度的bin数目
ranges 每个维度的取值范围
uniform 是否要把bin范围设定成同样大小(均一)
accumulate 是否开始统计前先清除直方图中的痕迹
void ES::ImageProcessing::histImageOper(cv::Mat* dst)
{
Mat src = imread("lena.jpg", IMREAD_COLOR);
cv::resize(src, src, Size(src.rows / 4 * 3, src.cols / 4 * 3));
/// 分割成3个单通道图像 ( R, G 和 B )
std::vector<Mat> rgb_planes;
split(src, rgb_planes);
/// 设定bin数目
int histSize = 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;
/// 计算直方图:
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 = src.cols; int hist_h = src.rows;
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());
int lineType = LINE_AA;
/// 在直方图画布上画出直方图
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, lineType, 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, lineType, 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, lineType, 0);
}
Mat mergeMat(src.rows, src.cols + histImage.cols, src.type());
Mat submat = mergeMat.colRange(0, src.cols);
src.copyTo(submat);
submat = mergeMat.colRange(src.cols, src.cols + histImage.cols);
histImage.copyTo(submat);
mergeMat.copyTo(*dst);
}