VS+openCV 用直方图统计像素(上)计算图像直方图、利用查找表修改图像外观
一、计算图像直方图
图像由各种数值的像素构成。例如在单通道灰度图像中,每个像素都有一个 0(黑色)~255(白色)的整数。对于每个灰度,都有不同数量的像素分布在图像内,具体取决于图片内容。
直方图是一个简单的表格,表示一幅图像(有时是一组图像)中具有某个值的像素的数量。因此,灰度图像的直方图有 256 个项目,也叫箱子(bin)。0 号箱子提供值为 0 的像素的数量,1 号箱子提供值为 1 的像素的数量,以此类推。很明显,如果把直方图的所有箱子进行累加,得到的结果就是像素的总数。你也可以把直方图归一化,即所有箱子的累加和等于 1。这时,每个箱子的数值表示对应的像素数量占总数的百分比。
【实现】
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;//创建灰度图像的直方图
class Histogram1D {
private:int histSize[1];//直方图中箱子的数量float hranges[2];//值范围const float* ranges[1];//值范围的指针int channels[1];//要检查的通道数量
public:Histogram1D() {//准备一维直方图的默认参数histSize[0] = 256;//256个箱子hranges[0] = 0.0;//从0开始(含)hranges[1] = 256.0;//到256(不含)ranges[0] = hranges;channels[0] = 0;//先关注通道0}cv::Mat getHistogram(const cv::Mat& image);
};//计算一维直方图
cv::Mat Histogram1D::getHistogram(const cv::Mat& image) {cv::Mat hist;//用calcHist函数计算一维直方图cv::calcHist(&image, 1, //仅为一幅图像的直方图channels, //使用的通道cv::Mat(), //不使用掩码hist, //作为结果的直方图1, //这是一维的直方图histSize, //箱子数量ranges //像素值的范围);return hist;
}int main()
{//读取输入的图像cv::Mat image = cv::imread("girl.jpg", 0);//以黑白方式打开//直方图对象Histogram1D h;//计算直方图cv::Mat histo = h.getHistogram(image);//循环遍历每个箱子for (int i = 0; i < 256; i++)cout << "Value" << i << "="<< histo.at<float>(i) << endl;
}
显然,只看这一系列数值很难得到任何有意义的信息。因此比较实用的做法是以函数的方式 显示直方图,例如用柱状图。
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;//创建灰度图像的直方图
class Histogram1D {
private:int histSize[1];//直方图中箱子的数量float hranges[2];//值范围const float* ranges[1];//值范围的指针int channels[1];//要检查的通道数量
public:Histogram1D() {//准备一维直方图的默认参数histSize[0] = 256;//256个箱子hranges[0] = 0.0;//从0开始(含)hranges[1] = 256.0;//到256(不含)ranges[0] = hranges;channels[0] = 0;//先关注通道0}cv::Mat getHistogram(const cv::Mat& image);cv::Mat getHistogramImage(const cv::Mat& image, int zoom = 1);static cv::Mat getImageOfHistogram(const cv::Mat& hist, int zoom);
};//计算一维直方图
cv::Mat Histogram1D::getHistogram(const cv::Mat& image) {cv::Mat hist;//用calcHist函数计算一维直方图cv::calcHist(&image, 1, //仅为一幅图像的直方图channels, //使用的通道cv::Mat(), //不使用掩码hist, //作为结果的直方图1, //这是一维的直方图histSize, //箱子数量ranges //像素值的范围);return hist;
}//创建一个表示直方图的图像(静态方法)
cv::Mat Histogram1D::getImageOfHistogram(const cv::Mat& hist, int zoom) {//取得箱子值的最大值和最小值double maxVal = 0;double minVal = 0;cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);//取得直方图的大小int histSize = hist.rows;//用于显示直方图的方形图像cv::Mat histImg(histSize * zoom, histSize * zoom,CV_8U, cv::Scalar(255));//设置最高点为90%(即图像高度)的箱子个数int hpt = static_cast<int>(0.9 * histSize);//为每个箱子画垂直线for (int h = 0; h < histSize; h++) {float binVal = hist.at<float>(h);if (binVal > 0) {int intensity = static_cast<int>(binVal * hpt / maxVal);cv::line(histImg, cv::Point(h * zoom, histSize * zoom),cv::Point(h * zoom, (histSize - intensity) * zoom),cv::Scalar(0), zoom);}}return histImg;
}//计算一维直方图,并返回它的图像
cv::Mat Histogram1D::getHistogramImage(const cv::Mat& image, int zoom) {zoom = 1;//先计算直方图cv::Mat hist = getHistogram(image);//创建图像return getImageOfHistogram(hist, zoom);
}int main()
{//读取输入的图像cv::Mat image = cv::imread("girl.jpg", 0);//以黑白方式打开//直方图对象Histogram1D h;cv::namedWindow("Histogram");cv::imshow("Histogram", h.getHistogramImage(image));cv::waitKey(0);
}
从图形化的直方图可以看出,在中等灰度值处有一个大的尖峰,并且比中等值更黑的像素有很多。巧的是,这两部分像素分别对应了图像的背景和前景。要验证这点,可以在这两部分的汇合处进行阈值化处理。OpenCV 中的 cv::threshold 函数可以实现这个功能。上一章介绍过,它是一个很实用的函数。我们取直方图中在升高为尖峰之前的最小值的位置(灰度值为 70), 对其进行阈值化处理,得到二值图像。
int main()
{//读取输入的图像cv::Mat image = cv::imread("girl.jpg", 0);//以黑白方式打开cv::Mat thresholded; //输出二值图像cv::threshold(image, thresholded, 70, //阈值255, //对超过阈值的像素赋值cv::THRESH_BINARY); //阈值化类型cv::namedWindow("Binary Image");cv::imshow("Binary Image", thresholded);cv::waitKey(0);
}
【实现原理】
为了适应各种场景,cv::calcHist 函数带有很多参数。
void calcHist(const Mat*images, // 源图像 int nimages, // 源图像的个数(通常为 1)const int*channels, // 列出通道InputArray mask, // 输入掩码(需处理的像素)OutputArray hist, // 输出直方图int dims, // 直方图的维度(通道数量)const int*histSize, // 每个维度位数const float**ranges, // 每个维度的范围bool uniform=true, // true 表示箱子间距相同bool accumulate=false) // 是否在多次调用时进行累加
大多数情况下,直方图是单个的单通道或三通道图像,但也可以在这个函数中指定一个分布在多幅图像(即多个 cv::Mat)上的多通道图像。这也是把输入图像数组作为函数第一个参数的原因。第六个参数 dims 指明了直方图的维数,例如 1 表示一维直方图。在分析多通道图像时,可以只把它的部分通道用于计算直方图,将需要处理的通道放在维数确定的数组 channel 中。在这个类的实现中只有一个通道,默认为 0。直方图用每个维度上的箱子数量(即整数数组histSize)以及每个维度(由 ranges 数组提供,数组中每个元素又是一个二元素数组)上的最小值(含)和最大值(不含)来描述。你也可以定义一个不均匀的直方图(倒数第二个参数应 设为false),这时需要指定每个箱子的限值。
和很多 OpenCV 函数一样,可以使用掩码表示计算时用到的像素(所有掩码值为 0 的像素都不使用)。此外还可以指定两个布尔值类型的附加参数,第一个表示是否采用均匀的直方图(默认为 true),第二个表示是否允许累加多个直方图计算的结果。如果第二个参数为 true,那么图像中的像素数量会累加到输入直方图的当前值中。在计算一组图像的直方图时,就可以使用这个参数。
得到的直方图存储在 cv::Mat 的实例中。事实上,cv::Mat 类可用于操作通用的 N 维矩阵。第 2 章讲过,cv::Mat 类定义了适用于一维、二维和三维矩阵的 at 方法。正因如此,我们才可以在 getHistogramImage 方法中用下面的代码访问一维直方图的每个箱子:
float binVal = hist.at(h);
注意,直方图中的值存储为 float 值。
【扩展阅读】
我们可以用同一个 cv::calcHist 函数计算多通道图像的直方图。例如,若想计算彩色 BGR 图像的直方图,可以这样定义这样一个类:
class ColorHistogram { private: int histSize[3]; // 每个维度的大小float hranges[2]; // 值的范围(三个维度用同一个值)const float* ranges[3]; // 每个维度的范围int channels[3]; // 需要处理的通道public: ColorHistogram() { // 准备用于彩色图像的默认参数// 每个维度的大小和范围是相等的histSize[0]= histSize[1]= histSize[2]= 256; hranges[0]= 0.0; // BGR 范围为 0~256 hranges[1]= 256.0; ranges[0]= hranges; // 这个类中ranges[1]= hranges; // 所有通道的范围都相等ranges[2]= hranges; channels[0]= 0; // 三个通道:B channels[1]= 1; // G channels[2]= 2; // R }
这里的直方图将会是三维的,因此需要为每个维度指定一个范围。本例中的 BGR 图像的三个通道范围都是[0,255]。准备好参数后,就可以用下面的方法计算颜色直方图了:
// 计算直方图
cv::Mat getHistogram(const cv::Mat &image) { cv::Mat hist; // 计算直方图cv::calcHist(&image, 1, // 单幅图像的直方图channels, // 用到的通道cv::Mat(), // 不使用掩码hist, // 得到的直方图3, // 这是一个三维直方图histSize, // 箱子数量ranges // 像素值的范围); return hist;
}
上述方法返回一个三维的 cv::Mat 实例。如果选用含有 256 个箱子的直方图,这个矩阵就有(256)^3 个元素,表示超过 1600 万个项目。在很多应用程序中,最好在计算直方图时减少箱子的数量。也可以使用数据结构 cv::SparseMat 表示大型稀疏矩阵(即非零元素非常稀少的矩阵),这样不会消耗过多的内存。cv::calcHist 函数具有返回这种矩阵的版本,因此只需要简单地修改一下前面的方法,即可使用 cv::SparseMatrix:
// 计算直方图
cv::SparseMat getSparseHistogram(const cv::Mat &image) { cv::SparseMat hist(3, // 维数histSize, // 每个维度的大小CV_32F); // 计算直方图cv::calcHist(&image, 1, // 单幅图像的直方图channels, // 用到的通道cv::Mat(), // 不使用掩码hist, // 得到的直方图3, // 这是三维直方图histSize, // 箱子数量ranges // 像素值的范围); return hist;
}
这是一个三维直方图,画起来比较困难。我们也可以通过显示独立的 R、G 和 B 通道的直方图来说明图像中颜色的分布情况。
【遇到的问题】
扩展阅读里的代码不完整,其他的地方我不会修改,所以运行不起来。
二、利用查找表修改图像外观
图像直方图提供了利用现有像素强度值进行场景渲染的方法。通过分析图像中像素值的分布 情况,你可以利用这个信息来修改图像,甚至提高图像质量。本节将解释如何用一个简单的映射函数(称为查找表)来修改图像的像素值。我们即将看到,查找表通常根据直方分布图生成。
【实现】
查找表是个一对一(或多对一)的函数,定义了如何把像素值转换成新的值。它是一个一维数组,对于规则的灰度图像,它包含 256 个项目。利用查找表的项目 i,可得到对应灰度级的新强度值。
newIntensity = lookup[oldIntensity];
OpenCV 中的 cv::LUT 函数在图像上应用查找表生成一个新的图像。查找表通常根据直方图生成,以下是完整代码。
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;class Histogram1D {
public:static cv::Mat applyLookUp(const cv::Mat& image, const cv::Mat& lookup);
};cv::Mat Histogram1D::applyLookUp(const cv::Mat& image, //输入图像const cv::Mat& lookup) {//uchar类型的1x256数组//输出图像cv::Mat result;//应用查找表cv::LUT(image, lookup, result);return result;
}int main()
{//读取输入的图像cv::Mat image = cv::imread("girl.jpg");//创建一个图像翻转的查找表cv::Mat lut(1, 256, CV_8U);//256x1矩阵for (int i = 0; i < 256; i++) {//0变成255,1变成254,以此类推lut.at<uchar>(i) = 255 - i;} //直方图对象Histogram1D h;cv::namedWindow("Negative image");cv::imshow("Negative image", h.applyLookUp(image, lut));cv::waitKey(0);
}
注意:这里我把上一部分的直方图代码删掉了,只存留了这次的查找表代码。其实也不一定非要定义一个类,直接用函数也可以实现。
【实现原理】
在图像上应用查找表后得到一个新图像,新图像的像素强度值被修改为查找表中规定的值。例如上述代码对像素强度进行了简单的反转,即强度 0 变成 255、1 变成 254、最后 255 变成0。对图像应用这种查找表后,会生成原始图像的反向图像。
【扩展阅读】
对于需要更换全部像素强度值的程序,都可以使用查找表。但是这个转换过程必须是针对整幅图像的。也就是说,一个强度值对应的全部像素都必须使用同一种转换方法。
1. 伸展直方图以提高图像对比度
定义一个修改原始图像直方图的查找表可以提高图像的对比度。例如,如果图中根本没有大于 200 的像素值。我们可以通过伸展直方图来生成一个对比度更高的图像。为此要使用一个百分比阈值,表示伸展后图像的最小强度值(0)和最大强度值(255) 像素的百分比。
我们必须在强度值中找到最小值(imin)和最大值(imax),使得所要求的最小的像素数量高于阈值指定的百分比。以下是完整代码:
#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;//创建灰度图像的直方图
class Histogram1D {
private:int histSize[1];//直方图中箱子的数量float hranges[2];//值范围const float* ranges[1];//值范围的指针int channels[1];//要检查的通道数量
public:Histogram1D() {//准备一维直方图的默认参数histSize[0] = 256;//256个箱子hranges[0] = 0.0;//从0开始(含)hranges[1] = 256.0;//到256(不含)ranges[0] = hranges;channels[0] = 0;//先关注通道0}cv::Mat getHistogram(const cv::Mat& image);cv::Mat getHistogramImage(const cv::Mat& image, int zoom = 1);static cv::Mat getImageOfHistogram(const cv::Mat& hist, int zoom);cv::Mat stretch(const Mat& image, int minValue = 0);static cv::Mat applyLookUp(const cv::Mat& image, const cv::Mat& lookup);
};//计算一维直方图
cv::Mat Histogram1D::getHistogram(const cv::Mat& image) {cv::Mat hist;//用calcHist函数计算一维直方图cv::calcHist(&image, 1, //仅为一幅图像的直方图channels, //使用的通道cv::Mat(), //不使用掩码hist, //作为结果的直方图1, //这是一维的直方图histSize, //箱子数量ranges //像素值的范围);return hist;
}//创建一个表示直方图的图像(静态方法)
cv::Mat Histogram1D::getImageOfHistogram(const cv::Mat& hist, int zoom) {//取得箱子值的最大值和最小值double maxVal = 0;double minVal = 0;cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);//取得直方图的大小int histSize = hist.rows;//用于显示直方图的方形图像cv::Mat histImg(histSize * zoom, histSize * zoom,CV_8U, cv::Scalar(255));//设置最高点为90%(即图像高度)的箱子个数int hpt = static_cast<int>(0.9 * histSize);//为每个箱子画垂直线for (int h = 0; h < histSize; h++) {float binVal = hist.at<float>(h);if (binVal > 0) {int intensity = static_cast<int>(binVal * hpt / maxVal);cv::line(histImg, cv::Point(h * zoom, histSize * zoom),cv::Point(h * zoom, (histSize - intensity) * zoom),cv::Scalar(0), zoom);}}return histImg;
}//计算一维直方图,并返回它的图像
cv::Mat Histogram1D::getHistogramImage(const cv::Mat& image, int zoom) {zoom = 1;//先计算直方图cv::Mat hist = getHistogram(image);//创建图像return getImageOfHistogram(hist, zoom);
}cv::Mat Histogram1D::applyLookUp(const cv::Mat& image, //输入图像const cv::Mat& lookup) {//uchar类型的1x256数组
//输出图像cv::Mat result;//应用查找表cv::LUT(image, lookup, result);return result;
}//伸展直方图
cv::Mat Histogram1D::stretch(const Mat& image, int minValue) {cv::Mat hist = getHistogram(image);//找到直方图的左极限int imin = 0;for (; imin < 256; imin++) {//小于或等于imin的像素数量必须>minValueif ((hist.at<float>(imin)) > minValue)break;}//找到直方图的右极限int imax = 255;for (; imax >= 0; imax--) {//大于或等于imax的像素必须>minValueif ((hist.at<float>(imax)) > minValue)break;}//minValue代表的是次数、个数,像素值最小(0左右的)以及像素值最大(255左右的)//这些极端的值都比较少,找到比较少的个数对应的像素值坐标(横坐标)Mat lookup(1, 256, CV_8U); //LUT查找表的像素重映射的规则for (int i = 0; i < 256; i++) //根据像素值大小划分{if (i < imin) //像素值(横坐标)imin左边的都置为0;极小的置0lookup.at<uchar>(i) = 0;else if (i > imax) //像素值(横坐标)右边的都置255;极大的置255lookup.at<uchar>(i) = 255;elselookup.at<uchar>(i) = cvRound(255.0 * (i - imin) / (imax - imin)); //[min,max]重新分配 cvRound为取整,中间的重新映射}Mat result;result = applyLookUp(image, lookup);return result; //返回处理好的增强的对比度 图片//这里需要分析下形参传进来的minValue。如果minValue过大,两边的0,255就会多;如果minValue过小,两边的0,255就会少
} int main()
{//读取输入的图像cv::Mat image = cv::imread("bluesky.jpg");//直方图对象Histogram1D h;cv::Mat streteched = h.stretch(image, 200);cv::namedWindow("Streched Image");cv::imshow("Streched Image", streteched);cv::namedWindow("Original Image");cv::imshow("Original Image", image);cv::waitKey(0);
}
2. 在彩色图像上应用查找表
之前我们定义了一个减色函数,通过修改图像中的 BGR 值减少可能的颜色数量。当时的实现方法是循环遍历图像中的像素,并对每个像素应用减色函数。实际上,更高效的做法是预先计算好所有的减色值,然后用查找表修改每个像素。利用本节的方法,这很容易实现。下面是新的减色函数。
void colorReduce(cv::Mat &image, int div=64) { // 创建一维查找表cv::Mat lookup(1,256,CV_8U); // 定义减色查找表的值for (int i=0; i<256; i++)lookup.at<uchar>(i)= i/div*div + div/2; // 对每个通道应用查找表cv::LUT(image,lookup,image);
}
这种减色方案之所以能起作用,是因为在多通道图像上应用一维查找表时,同一个查找表会独立地应用在所有通道上。如果查找表超过一个维度,那么它和所用图像的通道数必须相同。
VS+openCV 用直方图统计像素(上)计算图像直方图、利用查找表修改图像外观相关推荐
- OpenCV 【十二】OpenCV如何扫描图像、利用查找表和计时
目录 OpenCV如何扫描图像.利用查找表和计时 1.函数计算时间测试case 2. Mat图像的存储机理 3. 像素遍历的3--4种方式 4. 实例 OpenCV如何扫描图像.利用查找表和计时 如何 ...
- OpenCV之core 模块. 核心功能(1)Mat - 基本图像容器 OpenCV如何扫描图像、利用查找表和计时 矩阵的掩码操作 使用OpenCV对两幅图像求和(求混合(blending))
Mat - 基本图像容器 目的 从真实世界中获取数字图像有很多方法,比如数码相机.扫描仪.CT或者磁共振成像.无论哪种方法,我们(人类)看到的是图像,而让数字设备来"看"的时候,则 ...
- git统计历史上某一段时间代码的修改量
git统计历史上某一段时间代码的修改量 有两个方法,一个是git log的since - until,另外一个是git log after before,例如: git log --after=&qu ...
- OpenCV遍历图像性能比较、利用查找表
原文:http://blog.csdn.net/chenjiazhou12/article/details/21052849 对于如何扫描图像的方法实在是太多了,在浏览一些资料的时候也找到了一些好的方 ...
- sin查找表 matlab,利用Xilinx中的ROM构造查找表来计算sin和cos的方法探讨
1.使用matlab制作.coe文件 查找表的构造 构造256点的正余弦表 exp(-j*2*pi*(0:255)/256),分别得到 cos和sin的查找表 matlab代码: 求sin fid = ...
- opencv 直方图_OpenCV之图像直方图反向投影
python代码: import cv2 as cv import numpy as np from matplotlib import pyplot as pltdef back_projectio ...
- OpenCV学习笔记(十六):直方图均衡化:equalizeHist()
OpenCV学习笔记(十六):直方图均匀化:equalizeHist() 参考博客: 直方图均衡化的数学原理 直方图匹配的数学原理 直方图均衡化广泛应用于图像增强中: 直方图均衡化处理的"中 ...
- python利用opencv自带的颜色查找表(LUT)进行色彩风格变换
1 LUT颜色查找表介绍 Look Up Table(LUT)查找表 1.颜色查找表就是一种像素值映射的表,如下是一个对比度改变图像的查找表,从图中可以知道: 原图中像素值为40的像素,经过查找表映射 ...
- skimage中的图像直方图均衡化
skimage用于图像的直方图均衡化的函数有两个,分别是equalize_hist和equalize_adapthist函数,本文详细介绍了这两个函数. equalize_hist函数 语法: ...
最新文章
- 基于Linux的集群系统(一)
- 如何搭建自己的 pip 本地 cache
- 深度探讨验证码发展史,账户中心安全科普文
- 【Java】 获取当前项目所有的线程
- 代码也浪漫:用Python放一场烟花秀!
- Sublime Text3 总结笔记
- 除了PS,原来这个也可以轻松实现图像处理!
- 使用Spring Boot和Spring Security验证JWT
- Android 简介:Android SDK 和开发框架简介
- R 包的手动打包流程
- 《计算机科学概论(第12版)》—第1章1.3节海量存储器
- 搭建云免流服务器教程,搭建云免流服务器教程
- Low-Code is Low—— 低代码的使用
- java关于int极限值的测试
- 存在的hive插入数据_往hive表中插入数据以及导出数据
- 这篇文章来自我的微信朋友圈,并不特别好玩,但可以给创业者补点财务知识
- Infor CloudSuite Industrial (SyteLine) 工序外协基本流程
- matlab结束外循环,求单源最短路径的BellmanFord算法的matlab实现及其优化
- linux获取时间戳精确到毫秒,微妙
- 基于HTML5的捕鱼达人游戏网页版