一、图像金字塔(minmap)
我们经常会将某种尺寸的图像转换为其他尺寸的图像,如果要放大或者缩小图片的尺寸,笼统来说,可以使用OpenCV提供的如下两种方法。
- resize函数。这是最直接的方式
- pyrUpo), pyrDown()函数。即图像金字塔相关的两个函数,对图像进行向上采样和向下采样的操作。
pyrUp、pyrDown其实和专门用作放大缩小图像尺寸的resize在功能上差不多,披着图像金字塔的皮,说白了还是在对图像进行放大和缩小操作。另外需要指出的是, pyrUp, pyrDown在OpenCV的imgproc模块中的Image Filtering子模块里,而resize在imgproc模块的Geometric Image Transformations子模块里。
本部分,我们将先介绍图像金字塔的原理,接着介绍resize函数,然后是pyrUp和pyrDown函数。
1.1 高斯、拉普拉斯金字塔
- 高斯金字塔(Gaussianpyramid)-用来向下采样,主要的图像金字塔。
- 拉普拉斯金字塔(Laplacianpyramid)-用来从金字塔低层图像重建上层未采样图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。
两者的简要区别在于:高斯金字塔用来向下降采样图像,而拉普拉斯金字塔则用来从金字塔底层图像中向上采样,重建一个图像。
要从金字塔第i层生成第i+1层(我们将第i+1层表示为Gi+l),我们先要用高斯核对Gi,进行卷积,然后删除所有偶数行和偶数列,新得到图像面积会变为源图像的四分之一。按上述过程对输入图像Go执行操作就可产生出整个金字塔。
当图像向金字塔的上层移动时,尺寸和分辨率会降低。OpenCV中,从金字塔中上一级图像生成下一级图像的可以用PryDown,而通过PryUp将现有的图像在每个维度都放大两遍。
图像金字塔中的向上和向下采样分别通过OpenCV的函数pyrUp和pyrDown实现。
1.2 尺寸调整:resize函数
resize()为OpenCV中专门用来调整图像大小的函数。
此函数将源图像精确地转换为指定尺寸的目标图像。如果源图像中设置了ROI (Region Of Interest ,感兴趣区域),那么resize()函数会对源图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标图像中已经设置了ROI区域,不难理解resize)将会对源图像进行尺寸调整并填充到目标图像的ROI中。
很多时候,我们并不用考虑第二个参数dst的初始图像尺寸和类型(即直接定义一个Mat类型,不用对其初始化),因为其尺寸和类型可以由sre, dsize,fx和fy这几个参数来确定。
看一下它的函数原型:
C++: void resize (InputArray src, OutputArray dst, size dsize, double fx=0, double fy=0, int interpolation=INTER LINEAR
(1) 第一个参数, InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
(2) 第二个参数, OutputArray类型的dst,输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由sre.size)计算出来。
(3) 第三个参数, Size类型的dsize,输出图像的大小。如果它等于零,由下式进行计算:
其中, dsize,fx,fy都不能为0
(4) 第四个参数, double类型的fx,沿水平轴的缩放系数,有默认值0,且当其等于0时,由下式进行计算:
(5) 第五个参数, double类型的fy,沿垂直轴的缩放系数,有默认值0,且, 当其等于0时,由下式进行计算:
(6) 第六个参数, int类型的interpolation,用于指定插值方式,默认为INTER LINEAR (线性插值)。
可选的插值方式如下:
- INTER-NEAREST-最近邻插值
- INTER LINEAR-线性插值(默认值)INTER AREA-区域插值(利用像素区域关系的重采样插值)
- INTER CUBIC-三次样条插值(超过4x4像素邻域内的双三次插值)
- INTER LANCZOS4-Lanczos插值(超过8x8像素邻域的Lanczos插值) .
若要缩小图像,一般情况下最好用CVINTERAREA来插值;而若要放大图像,一般情况下最好用CV-INTER CUBIC (效率不高,慢,不推荐使用)或CV INTER LINEAR (效率较高,速度较快,推荐使用)。
resize示例:
int main()
{
//载入原始图
Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda.jpg");
Mat tmpImage, dstImage1, dstImage2;//临时变量和目标图的定义
tmpImage = srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行尺寸调整操作
resize(tmpImage, dstImage1, Size(tmpImage.cols / 3, tmpImage.rows / 3), (0, 0), (0, 0), 3);
resize(tmpImage, dstImage2, Size(tmpImage.cols * 1.5, tmpImage.rows * 1.5), (0, 0), (0, 0), 3);
//显示效果图
imshow("【效果图】之缩小", dstImage1);
imshow("【效果图】之放大", dstImage2);
waitKey(0);
return 0;
}
1.4 向上采样: pyrUp函数
pyrUp函数的作用是向上采样并模糊一张图像,说白了就是放大一张图片。
C++: void pyrUp (InputArray src, OutputArraydst, const Size dstsize-Size (), int borderType=BORDER DEFAULT )
-
第一个参数, InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
-
第二个参数, OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
-
第三个参数, const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size (sre.cols2, src.rows2)来进行计算,且一直需要满足下列条件:
-
第四个参数, int类型的borderType,边界模式,一般不用土管它。pyrUp函数执行高斯金字塔的采样操作,其实它也可以用于拉普拉斯金字塔的。首先,它通过插入可为零的行与列,对源图像进行向上取样操作,然后将果与pyrDown()乘以4的内核做卷积。
pyrUp 示例:
int main()
{
//载入原始图
Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda1.jpg");
Mat tmpImage, dstImage;//临时变量和目标图的定义
tmpImage = srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行向上取样操作
pyrUp(tmpImage, dstImage, Size(tmpImage.cols * 2, tmpImage.rows * 2));
//显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
1.5 采样: pyrDown函数
pyrDown()函数的作用是向下采样并模糊一张图片,说白了就是缩小一张图片。
C++: void pyrDown (InputArray src, OutputArray dst, const Sizes dstsize-size (), int borderType-BORDER DEFAULT)
- 第一个参数, InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
- 第二个参数, OutputArray类型的dst,输出图像,和源图片有一样的尺寸和类型。
- 第三个参数, const Size&类型的dstsize,输出图像的大小;有默认值Size(),即默认情况下,由Size Size((sc.cols+1)/2, (src.rows+1)/2)来进行计算,且一直需要满足下列条件:
该pyrDown函数执行了高斯金字塔建造的向下采样的步骤。首先,它将源图像与如下内核做卷积运算:
接着,它通过对图像的偶数行和列做插值来进行向下采样操作。
pyrDown 示例:
int main()
{
//载入原始图
Mat srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda1.jpg");
Mat tmpImage, dstImage;//临时变量和目标图的定义
tmpImage = srcImage;//将原始图赋给临时变量
//显示原始图
imshow("【原始图】", srcImage);
//进行向下取样操作
pyrDown(tmpImage, dstImage, Size(tmpImage.cols / 2, tmpImage.rows / 2));
//显示效果图
imshow("【效果图】", dstImage);
waitKey(0);
return 0;
}
二、阈值化
在对各种图形进行处理操作的过程中,我们常常需要对图像中的像素做出取舍与决策,直接剔除一些低于或者高于一定值的像素。
阈值可以被视作最简单的图像分割方法。比如,从一副图像中利用阈值分割出我们需要的物体部分(当然这里的物体可以是一部分或者整体),这样的图像分割方法基于图像中物体与背景之间的灰度差异,而且此分割属于像素级的分割。为了从一副图像中提取出我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断。注意:阈值的选取依赖于具体的问题。即物体在不同的图像中有可能会有不同的灰度值。
一旦找到了需要分割的物体的像素点,可以对这些像素点设定一些特定的值来表示。例如,可以将该物体的像素点的灰度值设定为“0" (黑色),其他的像素点的灰度值为"255" (白色)。当然像素点的灰度值可以任意,但最好设定的两种颜色对比度较强,以方便观察结果。
在OpenCV中, Threshold)函数(基本阈值操作)和adaptiveThreshold0)函数(自适应阈值操作)可以完成这样的要求。它们的基本思想是:给定一个数组和一个阈值,然后根据数组中的每个元素的值是高于还是低于阈值而进行一些处理。下面,我们将对这两个函数分别进行剖析。
2.1 固定國值操作: Threshol函数
函数Threshold对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像, (compare()函数也可以达到此目的)或者是去掉噪声,例如过滤很小或很大象素值的图像点。
C++: double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
- 第一个参数, InputArray类型的src,输入数组,填单通道, 8或32位浮点类型的Mat即可。
- 第二个参数, OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放输出结果,且和第一个参数中的Mat变量有一样的尺寸和类型
- 第三个参数, double类型的thresh,阈值的具体值。
- 第四个参数, double类型的maxval,当第五个参数阈值类型type取CV THRESH BINARY或CV THRESH BINARY INV时阈值类型时的最大值
- 第五个参数, int类型的type,阈值类型。threshold)函数支持的对图像取阈值的方法由其确定,具体用法如图.
2.2 自适应阈值操作: adaptiveThreshold函数
adaptiveThreshold()函数的作用是对矩阵采用自适应阈值操作,支持就地操作。函数原型如下
c++: void adaptiveThreshold(InputArray src, OutputArray dst, double maxvalue, int adaptiveMethod, int thresholdType, int blocksize, double c
- 第一个参数, InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为8位单通道浮点型图像。
- 第二个参数, OutputArray类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型
- 第三个参数, double类型的maxValue,给像素赋的满足条件的非零值。具体看下面的讲解。
- 第四个参数, int类型的adaptiveMethod,用于指定要使用的自适应阈值算法,可取值为ADAPTIVE_THRESH_MEAN_C或ADAPTIVE_THRESH_GAUSSIAN_C.
- 第五个参数, int类型的thresholdType , 阈值类型。取值必须为THRESH_BINARY, THRESH_BINARY INV其中之一。
- 第六个参数, int类型的blockSize,用于计算阈值大小的一个像素的邻域尺寸,取值为3、5、7等。
- 第七个参数, double类型的C,减去平均或加权平均值后的常数值。通常其为正数,但少数情况下也可以为零或负数。
adaptiveThreshold函数根据如下公式,将一幅灰度图变换为一幅二值图。
当第五个参数“阈值类型" thresholdType取值为THRESH_BINARY时,公式如下。
当第五个参数“阈值类型” thresholdType取值为THRESH_BINARY_INV时,公式为:
而其中的T(x,y)为分别计算每个单独像素的阈值,取值如下。
- 对于ADAPTIVE_THRESH_MEAN C方法,阈值T(x,y)为blockSizeblockSize邻域内(x, y)减去第七个参数C的平均值。
- 对于ADAPTIVE_THRESH_GAUSSIAN_C方法,阈值T(x.y)为blockSizex blockSize邻域内(xy)减去第七个参数c与高斯窗交叉相关((cross-correlation with a Gaussian window))的加权总和。
2.3 示例程序
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
#define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏
int g_nThresholdValue = 100;
int g_nThresholdType = 3;
Mat g_srcImage, g_grayImage, g_dstImage;
void on_Threshold(int, void*);//回调函数
int main()
{
//【0】改变console字体颜色
system("color 1F");
//【1】读入源图片
g_srcImage = imread("F:\\CV\\LearnCV\\files\\Zelda.jpg");
if (!g_srcImage.data) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
imshow("原始图", g_srcImage);
//【2】存留一份原图的灰度图
cvtColor(g_srcImage, g_grayImage, COLOR_RGB2GRAY);
//【3】创建窗口并显示原始图
namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
//【4】创建滑动条来控制阈值
createTrackbar("模式",
WINDOW_NAME, &g_nThresholdType,
4, on_Threshold);
createTrackbar("参数值",
WINDOW_NAME, &g_nThresholdValue,
255, on_Threshold);
//【5】初始化自定义的阈值回调函数
on_Threshold(0, 0);
// 【6】轮询等待用户按键,如果ESC键按下则退出程序
while (1)
{
int key;
key = waitKey(20);
if ((char)key == 27) { break; }
}
}
void on_Threshold(int, void*)
{
//调用阈值函数
threshold(g_grayImage, g_dstImage, g_nThresholdValue, 255, g_nThresholdType);
//更新效果图
imshow(WINDOW_NAME, g_dstImage);
}