很多朋友对于第四章:像素分布的直方图分析和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
为了简化,这里我们指定一个专门用于处理单通道灰度图像的类。
该专用类的初始化代码为:
直方图1D 类{
私人:
int histSize[1]; //直方图中的bin 数量
浮动范围[2]; //取值范围
常量浮点*范围[1]; //指向值范围的指针
整数通道[1]; //要检查的通道数
公共:
Histogram1D() { //准备一维直方图的默认参数
组大小[0]=256; //256 个盒子
hranges[0]=0.0; //从0开始(含0)
hranges[1]=256.0; //到256(不包括)
范围[0]=hrange;
通道[0]=0; //首先关注通道0
}定义完成员变量后,就可以使用下面的方法来计算灰度直方图:
//计算一维直方图
cv:Mat getHistogram(const cv:Mat 图像) {
cv:Mat 历史;
//使用calcHist函数计算一维直方图
cv:calcHist(image, 1, //只是图像的直方图
通道, //使用的通道
cv:Mat(), //不使用掩码
hist, //结果直方图
1, //这是一个一维直方图
histSize, //盒子数量
range //像素值的范围
);
返回历史记录;
}程序只需要打开一张图像,创建一个Histogram1D实例,然后调用getHistogram方法:
//读取输入图像
cv:Mat image=cv:imread("group.jpg", 0); //以黑白方式打开
//直方图对象
直方图1D h;
//计算直方图
cv:Mat histo=h.getHistogram(image);这里的histo对象是一个包含256项的一维数组。因此,只需迭代该数组并读取每个框:
for (int i=0; i256; i++)
cout "值" i "="(i) endl;更实用的做法是以函数的形式显示直方图,例如直方图。有几种方法可以创建这种图形:
cv:Mat getHistogramImage(const cv:Mat 图像, int Zoom=1) {
//先计算直方图
cv:Mat hist=getHistogram(image);
//创建图像
返回getImageOfHistogram(hist, 缩放);
}
//创建表示直方图的图像(静态方法)
静态cv:Mat getImageOfHistogram(const cv:Mat hist, int Zoom) {
//获取框值的最大值和最小值
双最大值=0;
双最小值值=0;
cv:minMaxLoc(hist, minVal, maxVal, 0, 0);
//获取直方图的大小
int histSize=hist.rows;
//用于显示直方图的方形图像
cv:Mat histImg(histSize * 缩放, histSize * 缩放,
CV_8U,cv:标量(255));
//设置最高点为90%(即图像高度)的框数量
int hpt=static_cast(0.9 * histSize);
//为每个框绘制垂直线
for (int h=0; h histSize; h++) {
浮动binVal=hist.at(h);
如果(binVal 0){
int 强度=static_cast(binVal * hpt/maxVal);
cv:line(histImg, cv:Point(h * 缩放, histSize * 缩放),
cv:Point(h * 缩放, (histSize - 强度) * 缩放),
cv:标量(0),缩放);
}
}
返回histImg;
}使用getImageOfHistogram方法获取直方图图像。它用线条绘制并以条形图的形式呈现:
//将直方图显示为图像
cv:namedWindow("直方图");
cv:imshow("Histogram",h.getHistogramImage(image));得到的结果如下图所示。
result.jpg 从上面的图形直方图可以看出,在中等灰度值处有一个很大的尖峰,并且有很多像素比中间值更暗。巧合的是,这两部分像素分别对应图像的背景和前景。为了验证这一点,可以在这两部分的交叉点处执行阈值处理。
OpenCV中的cv:threshold函数可以实现此功能。我们取直方图中最小值在达到峰值之前的位置(灰度值为70),对其进行阈值处理,得到二值图像:
cv:Mat 阈值; //输出二值图像
cv:threshold(图像, 阈值, 70, //阈值
255, //给超过阈值的像素赋值
cv:THRESH_BINARY); //阈值类型result.jpg
cv::calcHist
为了适应各种场景,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 acquire=false) //多次调用时是否累加
计算彩色图像的直方图
我们可以使用相同的cv:calcHist 函数来计算多通道图像的直方图。例如,如果要计算彩色BGR 图像的直方图,可以定义如下类:
类颜色直方图{
私人:
int histSize[3]; //每个维度的大小
浮动范围[2]; //取值范围(三个维度使用相同的值)
const float* 范围[3]; //各个维度的范围
整数通道[3]; //待处理的通道
公共:
颜色直方图() {
//准备彩色图像的默认参数
//各个维度的大小和范围相等
histSize[0]=histSize[1]=histSize[2]=256;
hranges[0]=0.0; //BGR范围是0~256
hranges[1]=256.0;
范围[0]=hrange; //在这个类中
范围[1]=hrange; //所有通道的范围相等
范围[2]=hrange;
通道[0]=0; //三个通道:B
通道[1]=1; //G
通道[2]=2; //右
}准备好参数后,可以使用以下方法计算颜色直方图:
//计算直方图
cv:Mat getHistogram(const cv:Mat 图像) {
cv:Mat 历史;
//计算直方图
cv:calcHist(image, 1, //单幅图像的直方图
通道, //使用的通道
cv:Mat(), //不使用掩码
hist, //获取直方图
3, //这是一个三维直方图
histSize, //盒子数量
range //像素值的范围
);
返回历史记录;
}上面的方法返回一个三维的cv:Mat实例。如果您选择具有256 个bin 的直方图,则该矩阵具有(256)^3 个元素,代表超过1600 万个项目。在许多应用中,在计算直方图时最好减少箱的数量。
还可以使用数据结构cv:SparseMat 来表示大型稀疏矩阵(即非零元素很少的矩阵),这样不会消耗太多内存。
//计算直方图
cv:SparseMat getSparseHistogram(const cv:Mat 图像) {
cv:SparseMat hist(3, //维度
histSize, //每个维度的大小
CV_32F);
//计算直方图
cv:calcHist(image, 1, //单幅图像的直方图
通道, //使用的通道
cv:Mat(), //不使用掩码
hist, //获取直方图
3, //这是一个三维直方图
histSize, //盒子数量
range //像素值的范围
);
返回历史记录;
}
4.3 利用查找表修改图像外观
本节介绍如何使用称为查找表的简单映射函数修改图像的像素值。正如我们稍后将看到的,查找表通常是从直方图生成的。
查找表是一个一对一(或多对一)的函数,它定义了如何将像素值转换为新值。它是一个一维数组,包含256 个常规灰度图像项。利用查找表的第i项,可以得到该灰度级对应的新的强度值,如下:
新强度=查找[旧强度]; OpenCV 中的cv:LUT 函数将查找表应用于图像以生成新图像。查找表通常是根据直方图生成的,因此这个函数被添加到Histogram1D 类中:
static cv:Mat applyLookUp(const cv:Mat image, //输入图像
const cv:Mat Lookup) {//uchar类型的1256数组
//输出图像
cv:Mat 结果;
//应用查找表
cv:LUT(图像、查找、结果);
返回结果;
}下面是一个简单的转换过程:
//创建图像反转的查找表
cv:马特·鲁特(1, 256, CV_8U); //2561 矩阵
for (int i=0; i 256; i++) {
//0 变为255,1 变为254,依此类推
lut.at(i)=255 - i;
}这个转换过程只是简单地反转像素强度,即强度0变成255,1变成254,最后255变成0。将此查找表应用于图像会得到原始图像的反转图像。得到的结果如下所示。
result.jpg
伸展直方图以提高图像对比度
定义修改原始图像直方图的查找表可以提高图像的对比度。例如,如果观察4.2节中的图像直方图,可以发现根本不存在大于200的像素值。我们可以通过拉伸直方图来生成对比度更高的图像。为此使用百分比阈值,表示拉伸图像中具有最小强度值(0) 和最大强度值(255) 的像素的百分比。
我们必须找到强度值中的最小值(imin)和最大值(imax),使得所需的最小像素数高于阈值指定的百分比。这可以使用以下循环来实现(其中hist 是计算的一维直方图):
//像素百分比
浮点数=image.total() * 百分位数;
//求直方图的左极限
int 亚敏=0;
for (浮点计数=0.0; imin 256; imin++) {
//小于等于imin的像素数必须为number
if ((count +=hist.at(imin))=number)
休息;
}
//求直方图的右极限
整数imax=255;
for (浮点计数=0.0; imax=0; imax--) {
//大于等于imax的像素数必须为number
if ((count +=hist.at(imax))=number)
休息;
然后重新映射强度值,使imin的值变成强度值0,imax的值变成强度值255。两者之间i的线性映射:
255.0*(i-imin)/(imax-imin);拉伸1%后的图像如下所示。
result.jpg 的拉伸直方图如下所示。
result0.jpg
在彩色图像上应用查找表
第2 章定义了减色函数,该函数通过修改图像中的BGR 值来减少可能的颜色数量。当时的实现方式是循环遍历图像中的像素并对每个像素应用减色函数。事实上,预先计算出所有减色值,然后使用查找表来修改每个像素,效率更高。使用本节中的方法可以轻松做到这一点。这是新的减色函数:
void colorReduce(cv:Mat 图像,int div=64) {
//创建一维查找表
cv:Mat 查找(1, 256, CV_8U);
//定义减色查找表的值
for (int i=0; i 256; i++)
Lookup.at(i)=i/div * div + div/2;
//将查找表应用于每个通道
cv:LUT(图像、查找、图像);
}这种减色方案之所以有效,是因为当一维查找表应用于多通道图像时,相同的查找表将独立应用于所有通道。如果查找表具有多个维度,则它必须具有与所使用的图像相同的通道数。
4.4 直方图均衡化
OpenCV 提供了易于使用的直方图均衡处理功能。该函数被称为:
cv:equalizeHist(image,result);将此函数应用于图像后,结果如下。
result.jpg 均衡后图像的直方图如下所示。
result.jpg 在完美平衡的直方图中,所有箱包含相同数量的像素。这意味着50% 的像素的强度值小于128(中值强度),25% 的像素的强度值小于64,依此类推。这种现象可以用一个规则来表示:p%像素的强度值必须小于或等于255*p%。该规则用于直方图均衡处理,表示强度值i的图像对应的强度值小于i的像素的百分比。因此,可以使用以下语句构建所需的查找表:
Lookup.at(i)=static_cast(255.0*p[i]/image.total());一般来说,直方图均衡化会极大地改善图像的外观,但改善效果会根据图像的视觉内容而有所不同。不同的。
4.5 反向投影直方图检测特定图像内容
如果图像的某个区域包含特定的纹理或物体,则该区域的直方图可以看成一个函数,它返回某个像素属于这个特殊纹理或物体的概率。本节将介绍如何利用直方图反投影的概念来方便地检测特定图像内容。
假设您想要检测图像中的特定内容(例如,检测下面天空中的云),首先要做的是选择包含所需样本的感兴趣区域。下图中的该区域就位于矩形内部。
cv:rectangle(image, cv:Rect(216, 33, 24, 30), cv:Scalar(0, 255, 0));result.jpg 在程序中使用以下方法获取这个感兴趣的区域:
cv:Mat图像ROI;
imageROI=图像(cv:Rect(216,33,24,30)); //云区域然后提取ROI的直方图。
直方图1D h;
cv:Mat hist=h.getHistogram(imageROI);通过对直方图进行归一化,我们可以得到一个函数,从中我们可以得到具有特定强度值的像素属于该区域的概率:
cv:归一化(直方图,直方图,1.0);对直方图进行反投影的过程包括:从归一化直方图中读取概率值,并将输入图像中的每个像素替换为其对应的概率值。 OpenCV中有一个函数可以做到这一点:
cv:calcBackProject(图像,
1, //图像
channels, //使用的通道取决于直方图的维度
histogram, //需要反投影的直方图
result, //反投影得到的结果
range, //值的范围
255.0 //选择的转换系数
//将概率值从1 映射到255
);得到的结果就是下面的概率分布图。为了提高可读性,图像被反转,属于该区域的概率范围从亮(低概率)到暗(高概率),如下所示。
result.jpg 如果对该图像进行阈值处理,则可以获得最有可能是“云”的像素:
cv:threshold(result, result, 30, 255, cv:THRESH_BINARY);result.jpg 之前的结果并不令人满意。因为除了云之外,其他区域也会被错误检测。重要的是要理解这个概率函数是从简单的灰度直方图中提取的。许多其他像素具有与云像素相同的强度值,并且当直方图被反投影时,具有相同强度值的像素被相同的概率值替换。
改进检测的一种方法是使用颜色信息。要实现此目的,请更改调用cv:calBackProject 的方式。
反向映射颜色直方图
多维直方图也可以反映射到图像上。我们定义一个类来封装反向映射过程,首先定义所需的参数并初始化它们:
类内容查找器{
私人:
//直方图参数
浮动范围[2];
const float* 范围[3];
整数通道[3];
浮动阈值; //判断阈值
cv:Mat 直方图; //输入直方图
公共:
ContentFinder() : 阈值(0.1f) {
//该类中所有通道的范围都是相同的
范围[0]=hrange;
范围[1]=hrange;
范围[2]=hrange;
}这里引入阈值参数来创建显示检测结果的二元分布图。如果该参数设置为负数,则返回原始概率分布图。输入直方图按如下方式标准化(但这不是必需的):
无效setHistogram(const cv:Mat h) {
直方图=h;
cv:归一化(直方图,直方图,1.0);
}find方法可以进行反投影。它有两个版本,一个使用图像的三个通道并调用通用版本的方法:
cv:Mat 查找(const cv:Mat 图像) {
cv:Mat 结果;
hranges[0]=0.0; //默认范围[0,256]
hranges[1]=256.0;
通道[0]=0; //三个通道
通道[1]=1;
通道[2]=2;
返回find(图像, hranges[0], hranges[1], 通道);
}
//查找属于直方图的像素
cv:Mat find(const cv:Mat 图像, float minValue, float maxValue, int* 通道) {
cv:Mat 结果;
hranges[0]=最小值;
hranges[1]=最大值;
//直方图的维数与通道列表一致
for (int i=0; i histogram.dims; i++)
这个频道[i]=频道[i];
cv:calcBackProject(image, 1, //仅使用一张图像
频道, //频道
直方图, //直方图
result, //反投影图像
范围
s, // 每个维度的值范围 255.0 // 选用的换算系数 // 把概率值从1 映射到255 ); } // 对反向投影结果做阈值化,得到二值图像 if (threshold >0.0) cv::threshold(result, result, 255.0 * threshold, 255.0, cv::THRESH_BINARY); return result; }现在把前面用过的图像换成彩色版本(访问本书的网站查看彩色图像),并使用一个BGR 直方图。这次来检测天空区域。首先装载彩色图像,定义ROI,然后计算经过缩减的色彩空间上的3D 直方图,代码如下所示: // 装载彩色图像 ColorHistogram hc; cv::Mat image = cv::imread("H:\opencv_c++\waves.jpg"); if (image.empty()) return 0; cv::Mat imageROI; // 提取ROI imageROI = image(cv::Rect(0, 0, 100, 45)); // 蓝色天空区域 // 取得3D 颜色直方图(每个通道含8 个箱子) hc.setSize(8); // 8×8×8 cv::Mat shist = hc.getHistogram(imageROI);下一步是计算直方图,并用find 方法检测图像中的天空区域: // 创建内容搜寻器 ContentFinder finder; // 设置用来反向投影的直方图 finder.setHistogram(shist); finder.setThreshold(0.05f); // 取得颜色直方图的反向投影 cv::Mat result = finder.find(image);彩色图像的检测结果如下所示。 result.jpg通常来说,采用BGR 色彩空间识别图像中的彩色物体并不是最好的方法。为了提高可靠性,我们在计算直方图之前减少了颜色的数量(要知道原始BGR 色彩空间有超过1600 万种颜色)。提取的直方图代表了天空区域的典型颜色分布情况。用它在其他图像上反向投影,也能检测到天空区域。注意,用多个天空图像构建直方图可以提高检测的准确性。4.6 用均值平移算法查找目标
直方图反向投影的结果是一个概率分布图,表示一个指定图像片段出现在特定位置的概率。 如果我们已经知道图像中某个物体的大致位置,就可以用概率分布图找到物体的准确位置。窗口中概率最大的位置就是物体最可能出现的位置。因此,我们可以从一个初始位置开始,在周围反复移动以提高局部匹配概率,也许就能找到物体的准确位置。这个实现方法称为均值平移算法。 假设我们已经识别出一个感兴趣的物体(例如狒狒的脸),如下图所示: result.jpg这次采用HSV 色彩空间的色调通道来描述物体。这意味着需要把图像转换成HSV 色彩空间并提取色调通道,然后计算指定ROI 的一维色调直方图。参见以下代码: // 读取参考图像 cv::Mat image= cv::imread("baboon01.jpg"); // 狒狒脸部的ROI cv::Rect rect(110, 45, 35, 45); cv::Mat imageROI= image(rect); // 得到狒狒脸部的直方图 int minSat=65; ColorHistogram hc; cv::Mat colorhist= hc.getHueHistogram(imageROI,minSat);我们在ColorHistogram 类中增加了一个简便的方法来获得色调直方图,代码如下所示: cv::Mat getHueHistogram(const cv::Mat& image, int minSaturation = 0) { cv::Mat hist; // 转换成HSV 色彩空间 cv::Mat hsv; cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV); // 掩码(可能用到,也可能用不到) cv::Mat mask; // creating the mask if required if (minSaturation >0) { // 将3 个通道分割进3 幅图像 std::vectorv; cv::split(hsv, v); // 屏蔽低饱和度的像素 cv::threshold(v[1], mask, minSaturation, 255, cv::THRESH_BINARY); } // 准备一维色调直方图的参数 hranges[0] = 0.0; // 范围为0~180 hranges[1] = 180.0; channels[0] = 0; // 色调通道 // 计算直方图 cv::calcHist(&hsv, 1, // 只有一幅图像的直方图 channels, // 用到的通道 mask, // 二值掩码 hist, // 生成的直方图 1, // 这是一维直方图 histSize, // 箱子数量 ranges // 像素值范围 ); return hist; }然后把得到的直方图传给ContentFinder 类的实例,代码如下所示: ContentFinder finder; finder.setHistogram(colorhist);现在打开第二幅图像,我们想在它上面定位狒狒的脸部。首先,需要把这幅图像转换成HSV色彩空间,然后对第一幅图像的直方图做反向投影,参见下面的代码: image = cv::imread("baboon02.jpg"); if (image.empty()) return 0; // 转换成HSV 色彩空间 cv::Mat hsv; cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV); // 得到色调直方图的反向投影 int ch[1] = { 0 }; finder.setThreshold(-1.0f); // 不做阈值化 cv::Mat result = finder.find(hsv, 0.0f, 180.0f, ch);rect 对象是一个初始矩形区域(即初始图像中狒狒脸部的位置),现在OpenCV 的cv::meanShift 算法将会把它修改成狒狒脸部的新位置,代码如下所示: #include// 用均值偏移法搜索物体 cv::TermCriteria criteria( cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS, 10, // 最多迭代10 次 1); // 或者重心移动距离小于1 个像素 cv::meanShift(result, rect, criteria);脸部的初始位置(红色框)和新位置(绿色框)显示如下。 result.jpg本例为了突出被寻找物体的特征,使用了HSV 色彩空间的色调分量。之所以这样做,是因为狒狒脸部有非常独特的粉红色,使用像素的色调很容易标识狒狒脸部。 在使用颜色的色调分量时,要把它的饱和度考虑在内(饱和度是向量的第二个入口),这一点通常很重要。如果颜色的饱和度很低,它的色调信息就会变得不稳定且不可靠。这是因为低饱和度颜色的B、G 和R 分量几乎是相等的,这导致很难确定它所表示的准确颜色。因此,我们决定忽略低饱和度颜色的色彩分量,也就是不把它们统计进直方图中(在getHueHistogram 方法中使用minSat 参数可屏蔽掉饱和度低于此阈值的像素)。 均值偏移算法是一个迭代过程,用于定位概率函数的局部最大值,方法是寻找预定义窗口内部数据点的重心或加权平均值。然后,把窗口移动到重心的位置,并重复该过程,直到窗口中心收敛到一个稳定的点。 OpenCV 实现该算法时定义了两个停止条件:迭代次数达到最大值(MAX_ITER);窗口中心的偏移值小于某个限值(EPS),可认为该位置收敛到一个稳定点。这两个条件存储在一个cv::TermCriteria 实例中。cv::meanShift 函数返回已经执行的迭代次数。 显然,结果的好坏取决于指定初始位置提供的概率分布图的质量。注意,这里用颜色直方图表示图像的外观。也可以用其他特征的直方图(例如边界方向直方图)来表示物体。4.7 比较直方图搜索相似图像
我们已经学过,直方图是标识图像内容的一种有效方式,因此值得研究一下能否用它来解决基于内容的图像检索问题。 这里的关键是,要仅靠比较它们的直方图就测量出两幅图像的相似度。我们需要定义一个测量函数,来评估两个直方图之间的差异程度或相似程度。人们已经提出了很多测量方法,OpenCV在cv::compareHist 函数的实现过程中使用了其中的一些方法。 为了将一个基准图像与一批图像进行对比并找出其中与它最相似的图像,我们创建了类ImageComparator。 这个类引用了一个基准图像和一个输入图像(连同它们的直方图)。另外,因为要用颜色直方图来进行比较,因此ImageComparator 中用到了ColorHistogram 类: class ImageComparator { private: cv::Mat refH; // 基准直方图 cv::Mat inputH; // 输入图像的直方图 ColorHistogram hist; // 生成直方图 int nBins; // 每个颜色通道使用的箱子数量 public: ImageComparator() :nBins(8) { }为了得到更加可靠的相似度测量结果,需要在计算直方图时减少箱子的数量。可以在类中指定每个BGR 通道所用的箱子数量。 用一个适当的设置函数指定基准图像,同时计算参考直方图,代码如下所示: void setReferenceImage(const cv::Mat& image) { hist.setSize(nBins); refH = hist.getHistogram(image); }最后,compare 方法会将基准图像和指定的输入图像进行对比。下面的方法返回一个分数,表示两幅图像的相似程度: double compare(const cv::Mat& image) { inputH = hist.getHistogram(image); // 用交叉法比较直方图 return cv::compareHist(refH, inputH, cv::HISTCMP_INTERSECT); }前面的类可用来检索与给定的基准图像类似的图像。类的实例中使用了基准图像,代码如下所示: ImageComparator c; c.setReferenceImage(image); cv::Mat input = cv::imread("H:\opencv_c++\marais.jpg"); std::cout<< "waves vs dog: "<< c.compare(input)<< std::endl;大多数直方图比较方法都是基于逐个箱子进行比较的。正因如此,在测量两个颜色直方图的相似度时,把邻近颜色组合进同一个箱子显得十分重要。 对cv::compareHist 的调用非常简单,只需要输入两个直方图,函数就会返回它们的差距。你可以通过一个标志参数指定想要使用的测 量方法。ImageComparator 类使用了交叉点方法(带有cv::HISTCMP_INTERSECT 标志)。该方法只是逐个箱子地比较每个直方图中的数值,并保存最小的值。然后把这些最小值累加,作为相似度测量值。因此,两个没有相同颜色的直方图得到的交叉值为0,而两个完全相同的直方图得到的值就等于像素总数。4.8 用积分图像统计像素
现在假设需要对图像中的多个感兴趣区域计算几个此类直方图,这些计算过程马上都会变得非常耗时。这种情况下,有一个工具可以极大地提高统计图像子区域像素的效率,那就是积分图像。 本节将使用下面的图像来做演示,识别出图像中的一个感兴趣区域,区域内容为一个骑自行车的女孩。 result.jpg在累加多个图像区域的像素时,积分图像显得非常有用。通常来说,要获得感兴趣区域全部像素的累加和,常规的代码如下所示: // 打开图像 cv::Mat image= cv::imread("bike55.bmp",0); // 定义图像的ROI(这里为骑自行车的女孩) int xo=97, yo=112; int width=25, height=30; cv::Mat roi(image,cv::Rect(xo,yo,width,height)); // 计算累加值 // 返回一个多通道图像下的Scalar 数值 cv::Scalar sum= cv::sum(roi);cv::sum 函数只是遍历区域内的所有像素,并计算累加和。使用积分图像后,只需要三次加法运算即可实现该功能。不过你得先计算积分图像,代码如下所示: // 计算积分图像 cv::Mat integralImage; cv::integral(image,integralImage,CV_32S);可以在积分图像上用简单的算术表达式获得同样的结果(下一节会详细解释),代码为: // 用三个加/减运算得到一个区域的累加值 int sumInt = integralImage.at(yo + height, xo + width)– integralImage.at(yo + height, xo)– integralImage.at(yo, xo + width) + integralImage.at(yo, xo);两种做法得到的结果是一样的。但计算积分图像需要遍历全部像素,因此速度比较慢。关键在于,一旦这个初始计算完成,你只需要添加四个像素就能得到感兴趣区域的累加和,与区域大小无关。 因此,如果需要在多个尺寸不同的区域上计算像素累加和,最好采用积分图像。 为了理解积分图像的实现原理,我们先对它下一个定义: 取图像左上方的全部像素计算累加和,并用这个累加和替换图像中的每一个像素,用这种方式得到的图像称为积分图像。 计算积分图像时,只需对图像扫描一次。实际上,当前像素的积分值等于上方像素的积分值加上当前行的累计值。因此积分图像就是一个包含像素累加和的新图像。 为防止溢出,积分图像的值通常采用int 类型(CV_32S)或float 类型(CV_32F)。例如在下图中,积分图像的像素A 包含左上角区域,即双阴影线图案标识的区域的像素的累加和。 image.png计算由A、B、C、D 四个像素表示区域的像素累加和,先读取D的积分值,然后减去B 的像素值和C 的左手边区域的像素值。但是这样就把A 左上角的像素累加和减了两次,因此需要重新加上A 的积分值。所以计算A、B、C、D 区域内的像素累加的正式公式为:A-B-C+D。 不管感兴趣区域的尺寸有多大,使用这种方法计算的复杂度是恒定不变的。注意,为了简化,这里使用了cv::Mat 类的at 方法,它访问像素值的效率并不是最高的。自适应的阈值化
通过对图像应用阈值来创建二值图像是从图像中提取有意义元素的好方法。假设有下面这个关于本书的图像。 result.jpg为了分析图像中的文字,对该图像应用一个阈值,代码如下所示: // 使用固定的阈值 cv::Mat binaryFixed; cv::threshold(image,binaryFixed,70,255,cv::THRESH_BINARY);得到如下结果。 result.jpg实际上,不管选用什么阈值,图像都会丢失一部分文本,还有部分文本会消失在阴影下。 要解决这个问题,有一个办法就是采用局部阈值,即根据每个像素的邻域计算阈值。这种策略称为自适应阈值化,将每个像素的值与邻域的平均值进行比较。如果某像素的值与它的局部平均值差别很大,就会被当作异常值在阈值化过程中剔除。 因此自适应阈值化需要计算每个像素周围的局部平均值。这需要多次计算图像窗口的累计值,可以通过积分图像提高计算效率。正因为如此,方法的第一步就是计算积分图像: 现在就可以遍历全部像素,并计算方形邻域的平均值了。我们也可以使用IntegralImage类来实现这个功能,但是这个类在访问像素时使用了效率很低的at 方法。我们可以使用指针遍历图像以提高效率,循环代码如下所示: int nl = image.rows; // 行数 int nc = image.cols * image.channels(); int blockSize = 21; // 邻域的尺寸 int threshold = 10; // 像素将与(mean-threshold)进行比较 // 逐行 int halfSize = blockSize / 2; for (int j = halfSize; j< nl - halfSize - 1; j++) { // 得到第j 行的地址 uchar* data = image.ptr(j); int* idata1 = iimage.ptr(j - halfSize); int* idata2 = iimage.ptr(j + halfSize + 1); // 一个线条的每个像素 for (int i = halfSize; i< nc - halfSize - 1; i++) { // 计算累加值 int sum = (idata2[i + halfSize + 1] - idata2[i - halfSize] - idata1[i + halfSize + 1] + idata1[i - halfSize]) / (blockSize * blockSize); // 应用自适应阈值 if (data[i]< (sum - threshold)) data[i] = 0; else data[i] = 255; } }本例使用了21×21 的邻域。为计算每个平均值,我们需要访问界定正方形邻域的四个积分像素:两个在标有idata1 的线条上,另两个在标有idata2 的线条上。将当前像素与计算得到的平均值进行比较。为了确保被剔除的像素与局部平均值有明显的差距,这个平均值要减去阈值(这里设为10)。由此得到下面的二值图像。 result.jpg很明显,这比用固定阈值得到的结果好得多。自适应阈值化是一种常用的图像处理技术。OpenCV 中也实现了这种方法: cv::adaptiveThreshold(image, // 输入图像 binaryAdaptive, // 输出二值图像 255, // 输出的最大值 cv::ADAPTIVE_THRESH_MEAN_C, // 方法 cv::THRESH_BINARY, // 阈值类型 blockSize, // 块的大小 threshold); // 使用的阈值最后需要注意,我们也可以用OpenCV 的图像运算符来编写自适应阈值化过程。具体方法如下所示: cv::Mat filtered; cv::Mat binaryFiltered; // boxFilter 计算矩形区域内像素的平均值 cv::boxFilter(image,filtered,CV_8U,cv::Size(blockSize,blockSize)); // 检查像素是否大于(mean + threshold)OK,关于第四章:像素分布的直方图分析和的内容到此结束了,希望对大家有所帮助。
【第四章:像素分布的直方图分析】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
厉害了!第四章就开篇用直方图统计像素,这内容看起来很有深度。
有14位网友表示赞同!
看来这个教程要教我们如何分析图像数据的结构啊,直方图是必备工具的
有20位网友表示赞同!
想学习如何用程序处理图片,这个章节一定很实用!
有18位网友表示赞同!
我一直好奇图像数据背后隐藏的信息,这章能揭开些谜底吗?
有14位网友表示赞同!
直方图统计像素值的分布,这种方法好像在信号处理也常用到吧
有16位网友表示赞同!
看到“直方图”就想起了我之前学的概率分布知识
有14位网友表示赞同!
学习一下直方图的应用场景,这应该是图像分析的基础吧
有6位网友表示赞同!
期待这个章节能深入浅出地讲解直方图的基本概念和应用方法
有15位网友表示赞同!
我想知道不同类型的图像,它们的直方图会有怎样的区别?
有15位网友表示赞同!
会不会讲解如何利用直方图来进行图像的特征提取?
有7位网友表示赞同!
感觉这个章节的内容会非常有挑战性,但我准备好好学习!
有5位网友表示赞同!
终于盼到这章了!我对像素值统计和图像分析一直很感兴趣
有13位网友表示赞同!
希望能在讲解过程中看到一些实际案例演示,更直观地理解
有11位网友表示赞同!
这个教程应该能够帮助我更好地理解图像数据的本质吧
有15位网友表示赞同!
通过学习第四章的内容,可以增强我对图像处理技术的认知吗?
有9位网友表示赞同!
直方图的应用场景非常广泛,期待学习更多新知识!
有8位网友表示赞同!
我已经迫不及待想看看如何利用直方图来实现图像分割效果了
有16位网友表示赞同!
不知道第四章里会介绍什么相关的数学公式和算法?
有6位网友表示赞同!
我想把学到的课程内容应用到我的个人项目中,希望能有所突破!
有20位网友表示赞同!