opencv-calcHist直方图计算
什么是直方图:
直方图是对数据集合的统计
看一个例子:假设有一个矩阵包含一张图像的信息 (灰度值 0-255):数字的范围包含 256个值,我们可以将这个范围分割成子区域(称作 bins), 如:
然后再统计每一个 的像素数目,采用这一方法来统计上面的数字矩阵,我们可以得到下图( x轴表示 bin, y轴表示各个bin中的像素个数)
直方图可以统计的不仅仅是颜色灰度, 它可以统计任何图像特征 (如 梯度, 方向等等)
直方图的一些具体细节:
1.dims: 需要统计的特征数目, 在上例中, dims = 1 因为我们仅仅统计了灰度值(灰度图像)
2.bins: 每个特征空间 子区段 的数目,在上例中, bins = 16
3.range: 每个特征空间的取值范围,在上例中, range = [0,255]
怎样去统计两个特征呢? 在这种情况下, 直方图就是3维的了,x轴和y轴分别代表一个特征, z轴是子区段 组合中的样本数目。 同样的方法适用于更高维的情形 (当然会变得很复杂)
OpenCV提供了一个简单的计算数组集(通常是图像或分割后的通道)的直方图函数 calcHist 。 支持高达 32 维的直方图
直方图理解
#include<opencv2/opencv.hpp> #include<iostream> int main(int argc, char** argv) { cv::Mat M(4, 4, CV_8UC1, cv::Scalar(0)); M.at<uchar>(0, 1) = 1; M.at<uchar>(0, 2) = 2; M.at<uchar>(0, 3) = 3; M.at<uchar>(1, 0) = 4; M.at<uchar>(1, 1) = 5; M.at<uchar>(1, 2) = 6; M.at<uchar>(1, 3) = 7; M.at<uchar>(2, 0) = 8; M.at<uchar>(2, 1) = 9; M.at<uchar>(2, 2) = 10; M.at<uchar>(2, 3) = 11; M.at<uchar>(3, 0) = 8; M.at<uchar>(3, 1) = 9; M.at<uchar>(3, 2) = 14; M.at<uchar>(3, 3) = 15; std::cerr << "M=" << std::endl << M << std::endl << std::endl; cv::Mat hist; //保存直方图 int bins = 4; //设定bins数目 float range[] = { 0, 16 };//设定取值范围 //注意:取值范围[0,16) //说明:把[0,16)分成4个区段:[0,3],[4,7],[8,11],[12,16) const float* histRange = { range }; calcHist(&M, 1, 0, cv::Mat(), hist, 1, &bins, &histRange, true, false); std::cerr << "hist=" << std::endl << hist << std::endl << std::endl; //输出直方图 cv::waitKey(0); return 0; }
在[0,3]区域有4个,在[4,7]区域有4个,在[8,11]区域有6个,在[12,15]区域有2个
实例源码
tiantan.png
#include<opencv2/opencv.hpp> #include<iostream> int main(int argc, char** argv) { cv::Mat src = cv::imread("D:/bb/tu/tiantan.png");//原图 std::vector<cv::Mat> rgb_planes; split(src, rgb_planes);//分割成3个单通道图像 ( B, G 和 R ) int histSize = 256; //设定bins数目 float range[] = { 0, 255 };//设定取值范围,【 [0,255) 】 const float* histRange = { range }; bool uniform = true; bool accumulate = false; cv::Mat r_hist, g_hist, b_hist; // 计算直方图: calcHist(&rgb_planes[0], 1, 0, cv::Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate); /* 参数1:Mat* images:输入的图像或数组,它们的深度必须为CV_8U, CV_16U或CV_32F中的一类,尺寸必须相同 参数2:输入数组个数,也就是第一个参数中存放了几张图像,有几个原数组 参数3:需要统计的通道channels索引,会按照索引顺序依次计算图像直方图(注意,第一张图片索引为 0,以此类推)
双通道计算实例看:https://www.cnblogs.com/liming19680104/p/15808731.html 参数4:mask: 可选的操作掩码。如果此掩码不为空,那么它必须为8位并且尺寸要和输入图像images[i]一致。非 零掩码用于标记出统计直方图的数组元素数据 参数5:输出的目标直方图,一个二维数组【bins行1列bins行,float类型】 参数6:dims: 需要计算直方图的维度(特征数目),必须是正数且并不大于CV_MAX_DIMS(在opencv中等于32)
【需要计算的通道数目】 参数7:histSize: 子区段的数目bins,x 轴将被分成 histSize 份 参数8:每个维度中bin的取值范围(range),x的取值范围 参数9:uniform: 直方图是否均匀的标识符,有默认值true 参数10:accumulate: 累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许 从多个阵列中计算单个直方图或者用于再特定的时间更新直方图 */ calcHist(&rgb_planes[1], 1, 0, cv::Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate); calcHist(&rgb_planes[2], 1, 0, cv::Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate); // 创建直方图画布 int hist_w = 512; int hist_h = 400; int bin_w = cvRound(hist_w / histSize); //横坐标间隔--->每个bin的宽度 cv::Mat histImage(hist_w, hist_h, CV_8UC3, cv::Scalar(0, 0, 0)); // 将直方图归一化到范围 [ 0, histImage.rows ] normalize(r_hist, r_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); normalize(g_hist, g_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); normalize(b_hist, b_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); // 在直方图画布上画出直方图 for (int i = 1; i < histSize; i++) { line(histImage, cv::Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))), cv::Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), cv::Scalar(0, 0, 255), 2, 8, 0); /* y坐标用hist_h - 原因:y的0坐标在上面,这样数据小的人眼看起来在上头;为了数据小的看起来 在下面,所以用hist_h - */ line(histImage, cv::Point((i - 1) * bin_w, hist_h - cvRound(b_hist.at<float>(i - 1))), cv::Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), cv::Scalar(255, 0, 0), 2, cv::LINE_AA); line(histImage, cv::Point((i - 1) * bin_w, hist_h - cvRound(g_hist.at<float>(i - 1))), cv::Point((i)*bin_w, hist_h - cvRound(g_hist.at<float>(i))), cv::Scalar(0, 255, 0), 2, cv::LINE_AA); } imshow("直方图", histImage); cv::waitKey(0); return 0; }