平滑处理(smoothing)也成为模糊处理(bluring),是一种简单且使用频率很高的图像处理方法。平滑处理的用途有很多,最常见的是用来减少图像上的噪点或者失真。在涉及到降低图像分辨率时,平滑处理是非常好用的方法。
图像滤波,指在尽量保留图像细节特征的条件下对标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。
消除图像中的噪声成分叫做图像的平滑化或者滤波操作。信号或图像的能量大部分集中在幅度谱的低频和中频段,有用的信息经常被噪声淹没。因此一个能降低高频成分幅度的滤波器就能够减少噪声的影响。
图像滤波的目的有两个:一个是抽出对象的特征作为图像识别的特征模式;另一个是为适应图像处理的要求,消除图像数字化时所混入的噪声。
而对滤波处理的要求也有两条:一是不能损坏图像的轮廓及边缘等重要信息;二是使图像清晰视觉效果好。
平滑滤波是低频增强的空间域滤波技术。它的目的有两类:一类是模糊;另一类是消除噪音。
空间域的平滑滤波一般才用简单平均法进行,就是求邻近像元点的平均亮度值。邻域的的大小与平滑的效果直接相关,邻域越大平滑的效果越好,但邻域过大,平滑也会使边缘信息损失的越大,从而使输出的图像变得模糊,因此需合理选择邻域的大小。
关于滤波器,一种形象的比喻就是:可以把滤波器想象成一个包含加权系数的窗口,当使用这个滤波器平滑处理图像时,就把这个窗口放到图像上,透过这个窗口来看我们得到的图像。
滤波器的种类有很多:
线性滤波器经常用于提出输入信号中不想要的频率或者从许多频率中选择一个想要的频率。
滤波是将信号中特定波段频率滤除的操作,是防止和抑制干扰的一项重要措施。
以高斯滤波为例:滤波可分为低通滤波和高通滤波两种:高斯滤波是指用高斯函数作为滤波函数的滤波操作,至于是不是模糊,就要看是高斯低通还是高斯高通,低通就是模糊,高通就是锐化。
邻域算子(局部算子)是利用给定像素周围的像素值的决定此像素的最终输出值的一种算子。而线性邻域滤波就是一种常用的邻域算子,像素的输出值取决于输入像素的加权和。
邻域算子除了用于局部色调调整以外,还可以用于图像滤波,以实现图像的平滑和锐化、图像边缘增强或者图像噪声的去除。
方框滤波被封装在一个名为boxblur的函数中,即boxblur函数的作用是使用方框滤波器来模糊一行图片
void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), bool normalize=true, int borderType=BORDER_DEFAULT)
均值滤波,是最简单的一种滤波操作,输出图像的每一个像素是核窗口内输入图像对应像素的平均值。其实说白了它就是归一化后的方框滤波。
均值滤波的缺陷
均值滤滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。
void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1),int borderType=BORDER_DEFAULT)
参数可参考方框滤波。
void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT)
深度的概念
图像深度是指存储每个像素所用的位数,也用于量度图像的色彩分辨率.图像深度确定彩色图像的每个像素可能有的颜色数,或者确定灰度图像的每个像素可能有的灰度级数。他决定了彩色图像中可出现的最多颜色数,或灰度图像中的最大灰度等级。
#include
using namespace std;
using namespace cv;
//-----------------全局变量声明-------------------
Mat g_srcImage, g_dstImage1, g_dstImage2, g_dstImage3;//存储图
int g_nBoxFilterValue = 3; //方框滤波参数值
int g_nMeanBlurValue = 3;//均值滤波参数值
int g_nGaussianBlurValue = 3;//高斯滤波参数值
//方框滤波操作的回调函数
static void on_BoxFilter(int, void*) {
boxFilter(g_srcImage, g_dstImage1, -1, Size(g_nBoxFilterValue + 1, g_nGaussianBlurValue + 1));
imshow("方框滤波", g_dstImage1);
}
//均值滤波的回调函数
static void on_MeanBlur(int, void*) {
blur(g_srcImage, g_dstImage2, Size(g_nMeanBlurValue + 1, g_nMeanBlurValue + 1));
imshow("均值滤波",g_dstImage2);
}
//高斯滤波
static void on_GaussianBlur(int, void*) {
GaussianBlur(g_srcImage, g_dstImage3, Size(g_nGaussianBlurValue * 2 + 1, g_nGaussianBlurValue * 2 + 1), 0, 0);
imshow("高斯滤波", g_dstImage3);
}
int main() {
g_srcImage = imread("../../image/lena_color_256.tif");
if (!g_srcImage.data) cout << "图片读取错误" << endl;
//赋值原图到三个Mat类型中
g_dstImage1 = g_srcImage.clone();
g_dstImage2 = g_srcImage.clone();
g_dstImage3 = g_srcImage.clone();
namedWindow("原始窗口");
imshow("原始窗口",g_srcImage);
namedWindow("方框滤波");
createTrackbar("内核值:","方框滤波",&g_nBoxFilterValue,40,on_BoxFilter);
on_BoxFilter(g_nBoxFilterValue, 0);
namedWindow("均值滤波");
createTrackbar("内核值:", "均值滤波", &g_nMeanBlurValue, 40, on_MeanBlur);
on_MeanBlur(g_nMeanBlurValue, 0);
namedWindow("高斯滤波");
createTrackbar("内核值:", "高斯滤波", &g_nGaussianBlurValue, 40, on_GaussianBlur);
on_GaussianBlur(g_nGaussianBlurValue, 0);
waitKey();
return 0;
}
中值滤波是一种典型的非线性滤波技术,基本思想是用像素点邻域灰度值的中值来代替像素点的灰度值,该方法在去除脉冲噪声、椒盐噪声的同时又能保留图像的边缘细节。
中值滤波是基于排序统计理论的一种能够有效抑制噪声的非线性信号处理技术,其基本原理是把数字图像或数字序列中一点的值用该店的一个林与众个点至的中值代替,该周围的像素值接近真实值,从而消除孤立的噪声点。这对于斑点噪声和椒盐噪声来说尤其有用,因为他不依赖于邻域内那些典型值差别很大的值。中值滤波器在处理连续图像窗函数时与线性滤波器的工作方式类似,但滤波过程不再是加权运算。
中值滤波在一定的条件下可以克服常见线性滤波器,如最小均方滤波、方框滤波、均值滤波等带来的图像细节模糊,而且对滤除脉冲干扰及图像扫描噪声非常有效,也常用于保护边缘信息。保存边缘的特征使它在不希望出现边缘模糊的场合也很有用,是非常经典的平滑噪声处理方式。
中值滤波与均值滤波器比较
优势:在均值滤波器中,由于噪声成分被放入平均计算中,所以输出受到了噪声的影响。但是在中值滤波器中,由于噪声成分很难被选上,所以几乎不会影响到输出。因此同样用33区域进行处理,中值滤波消除的噪声能力更胜一筹。中值滤波无论是在消除噪声还是保存边缘方面都是一个不错的方法。
劣势:中值滤波花费的时间是均值滤波的5倍以上。
顾名思义,中值滤波选择每个像素的邻域像素中的中值作为输出,或者说中值滤波将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。
例如,去33的函数窗,计算以点[i,j]为中心的函数窗像素中值,具体步骤如下。
1)按强度值大小排列像素点
2)选择排序像素集的中间值作为点[i,j]的新值。
一般采用奇数点的邻域来计算中值,但像素点数为偶数时,中值就取排列像素中间两个点的平均值。
中值滤波在一定条件下,可以克服线性滤波器(如均值滤波等)所带来的图像细节模糊,对滤除脉冲干扰即图像扫描噪声最为有效,而且在实际运算过程中并不需要图像的统计特性,也给计算带来不少方便。但是对一些细节(特别是细、尖顶等)多的图像不太适合。
void medianBlur(InputArray src, OutputArray dst, int ksize)
双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的,具有简单、非迭代、局部的特点。
双边滤波器的好处是可以做边缘保存。以往常用维纳滤波或者高斯滤波去降噪,但二者都会较明显地模糊边缘,对于高频细节的保护效果并不明显。双边滤波器顾名思义,比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离得较远的像素不会对边缘上的像素值影响太多,这样就保证了边缘附近像素值的保存。但是,由于保存了过多的高频信息,多余彩色图像里的高频噪声,双边滤波器不能够干地滤掉,知识对于低频信息系进行了比较好的滤波。
void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT)
#include
using namespace std;
using namespace cv;
Mat srcImage, dstImage1, dstImage2;
int g_nMedianBlurValue = 10; //中值滤波参数值
int g_nBilateralFilterValue = 10;//双边滤波参数值
//中值滤波操作的回调函数
static void on_MedianBlur(int, void*) {
medianBlur(srcImage, dstImage1, g_nMedianBlurValue * 2 + 1);
imshow("中值滤波", dstImage1);
}
//双边滤波操作的回调函数
static void on_BilateralFilter(int, void*) {
bilateralFilter(srcImage, dstImage2, g_nBilateralFilterValue, g_nBilateralFilterValue * 2, g_nBilateralFilterValue / 2);
imshow("双边滤波", dstImage2);
}
int main() {
srcImage = imread("../../image/1.tif");
if (!srcImage.data) {
cout << "图片读入错误" << endl;
return false;
}
dstImage1 = srcImage.clone();
dstImage1 = srcImage.clone();
namedWindow("原图", 0);
imshow("原图", srcImage);
//中值滤波
namedWindow("中值滤波", 0);
createTrackbar("参数值:", "中值滤波", &g_nMedianBlurValue, 50, on_MedianBlur);
on_MedianBlur(g_nMedianBlurValue, 0);
//双边滤波
namedWindow("双边滤波", 0);
createTrackbar("参数值:", "双边滤波", &g_nBilateralFilterValue, 50, on_BilateralFilter);
on_BilateralFilter(g_nBilateralFilterValue, 0);
waitKey();
return 0;
}
形态学操作就是基于形状的一系列图像处理操作。最基本的形态学操作有两种,分别是:膨胀与腐蚀。
膨胀与腐蚀能实现多种多样的功能,主要如下。
膨胀(dialate)就是求局部最大值的操作。从数学角度来说,膨胀或者腐蚀操作就是将图像与核进行卷积。
核可以死任何形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点。多数情况下,核实一个小的,中间带有参考点和实心正方形或者圆盘。其实,可以把核视为模板或者掩码。
而膨胀就是求局部最大值的操作。核与图形卷积,计算核覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。
膨胀和腐蚀(erode)是相反的一对操作,所以腐蚀就是求局部最小值的操作。
#include
using namespace std;
using namespace cv;
Mat srcImage, dstImage;
int g_nTrackbarNumer = 0;//0表示腐蚀,1表示膨胀
int g_nStructElementSize = 3;//结构元素的尺寸
//进行自定义的腐蚀和膨胀操作
void Process() {
Mat element = getStructuringElement(MORPH_RECT, Size(2 * g_nStructElementSize * 2 + 1, 2 * g_nStructElementSize * 2 + 1),
Point(g_nStructElementSize, g_nStructElementSize));
if (g_nTrackbarNumer == 0)
erode(srcImage, dstImage, element);
else
dilate(srcImage, dstImage, element);
imshow("效果图", dstImage);
}
//膨胀和腐蚀之间切换开关的回调函数
void on_TrackbarNumChange(int, void*) {
Process();
}
//膨胀和腐蚀操作内核改变时的回调函数
void on_ElementSize(int, void*) {
Process();
}
int main() {
srcImage = imread("../../image/1.tif");
if (!srcImage.data) {
cout << "读取图片错误" << endl;
return false;
}
namedWindow("原图",0);
imshow("原图", srcImage);
namedWindow("效果图", 0);
Mat element = getStructuringElement(MORPH_RECT,
Size(2 * g_nStructElementSize + 1, 2 * g_nStructElementSize * 2 + 1),
Point(g_nStructElementSize, g_nStructElementSize));
erode(srcImage, dstImage, element);
imshow("效果图", dstImage);
//创建轨迹条
createTrackbar("腐蚀/膨胀", "效果图", &g_nTrackbarNumer, 1, on_TrackbarNumChange);
createTrackbar("内核尺寸", "效果图", &g_nStructElementSize, 21, on_ElementSize);
waitKey();
return 0;
}
本节的主角是OpenCV中的morphologyEx函数,他利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算、形态学梯度、顶帽、黑帽等。我们需要知道形态学的高级形态,往往都是建立在复试和膨胀这两个基本操作之上的。
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。
dst=open(src,element)=dilate(erode(src,element))
开运算可以用来消除小物体,在纤细点处分离物体,并且在平滑较边界的同时不明显改变其面积。
例如,请看下面的例子。左边的图像是原始图像,右边的图像是应用开运算后的结果。我们可以观察到,小圆点已经消失了。
先膨胀后腐蚀的过程成为闭运算(Closing Operation)
dst=close(src,element)=erode(dilate(src,element)
闭运算能够排除小型黑洞(黑色区域)。
形态学梯度(Morphological Gradient)是膨胀图与腐蚀图之差。
dst=morph-grad(src,element)=dialate(src,element)-erode(src,element)
它对寻找图像的轮廓很有用。
顶帽运算(Top Hat)又常常被译为"礼帽"运算,是源图像与开运算之差。
dst=tophat(src,element)=src-open(src,element)
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域。因此,从原图中减去开运算后的图,得到的效果突出了比原图轮廓周围的区域更明亮的区域,且这一操作与选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑点。在一幅图像具有大幅的背景,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
黑帽(Black Hat)运算是闭运算的结果图与原图像之差。
dst=blackhat(src,element)=close(src,element)-src
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以黑猫运算用来分离比邻近点暗一些的斑块,效果图有着非常完美的轮廓。
#include
using namespace std;
using namespace cv;
Mat g_srcImage, g_dstImage;
int g_nElementShape = MORPH_RECT;//元素结构的形状
//TrackBar参数
int g_nMaxIterationNum = 10;
int g_nOpenCloseNum = 0;
int g_nErodeDilateNum = 0;
int g_nTopBlackHatNum = 0;
// 开运算/闭运算窗口的回调函数
static void on_OpenClose(int, void*) {
//偏移量的定义
int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量
int Absolute_offset = offset > 0 ? offset : -offset;
//核
Mat element = getStructuringElement(g_nElementShape,
Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1),
Point(Absolute_offset, Absolute_offset));
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage, MORPH_OPEN, element);
else
morphologyEx(g_srcImage, g_dstImage, MORPH_CLOSE, element);
imshow("开运算/闭运算", g_dstImage);
}
// 腐蚀/膨胀窗口的回调函数
static void on_ErodeDilate(int, void*) {
//偏移量的定义
int offset = g_nErodeDilateNum - g_nMaxIterationNum;//偏移量
int Absolute_offset = offset > 0 ? offset : -offset;
//核
Mat element = getStructuringElement(g_nElementShape,
Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1),
Point(Absolute_offset, Absolute_offset));
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage, MORPH_ERODE, element);
else
morphologyEx(g_srcImage, g_dstImage, MORPH_DILATE, element);
imshow("腐蚀/膨胀", g_dstImage);
}
// 顶帽/黑帽窗口的回调函数
static void on_TopBlackHat(int, void*) {
//偏移量的定义
int offset = g_nErodeDilateNum - g_nMaxIterationNum;//偏移量
int Absolute_offset = offset > 0 ? offset : -offset;
//核
Mat element = getStructuringElement(g_nElementShape,
Size(Absolute_offset * 2 + 1, Absolute_offset * 2 + 1),
Point(Absolute_offset, Absolute_offset));
if (offset < 0)
morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT, element);
else
morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
imshow("顶帽/黑帽", g_dstImage);
}
int main() {
g_srcImage = imread("../../image/1.tif");
namedWindow("原始图",0);
imshow("原始图", g_srcImage);
namedWindow("开运算/闭运算",0);
namedWindow("腐蚀/膨胀", 0);
namedWindow("顶帽/黑帽", 0);
//参数赋值
g_nOpenCloseNum = 9;
g_nErodeDilateNum = 9;
g_nTopBlackHatNum = 2;
//分别为三个窗口创建滚动条
createTrackbar("迭代值", "开运算/闭运算", &g_nOpenCloseNum,
2 * g_nMaxIterationNum + 1, on_OpenClose);
on_OpenClose(g_nOpenCloseNum, 0);
createTrackbar("迭代值", "腐蚀/膨胀", &g_nErodeDilateNum,
2 * g_nMaxIterationNum + 1, on_ErodeDilate);
on_OpenClose(g_nOpenCloseNum, 0);
createTrackbar("迭代值", "顶帽/黑帽", &g_nTopBlackHatNum,
2 * g_nMaxIterationNum + 1, on_TopBlackHat);
while (1) {
int c;
on_OpenClose(g_nOpenCloseNum, 0);
on_ErodeDilate(g_nErodeDilateNum, 0);
on_TopBlackHat(g_nTopBlackHatNum, 0);
c = waitKey(0);//获取按键
//按下q或者esc程序退出
if ((char)c == 'q', (char)c == 27)
break;
if ((char)c == 49) //1的ASCII码
g_nElementShape = MORPH_ELLIPSE;
else if ((char)c == 50)
g_nElementShape = MORPH_RECT;
else if ((char)c == 51)
g_nElementShape = MORPH_CROSS;
else if ((char)c == ' ')
g_nElementShape = (g_nElementShape + 1) % 3;
}
return 0;
}
漫水填充是一种用特定的颜色填充连通区域,通过设置可连通像素的上下限以及连同方式, 来达到不同的填充效果的方法。漫水填充经常被用来标记或分离图像的一部分,以便其进行进一步处理或分析,也可以用来从输入图像获取掩码区域,掩码会加速处理的过程,或只处理掩码指定的像素点,操作的结果总是某个连续的区域。
第一个版本的floodFill():带有掩膜mask
第一个版本的floodFill():不带掩膜mask
#include
using namespace std;
using namespace cv;
int main() {
Mat src = imread("../../image/1.tif");
imshow("原图", src);
Rect ccomp;
floodFill(src, Point(250, 250), Scalar(0, 255, 0), &ccomp,
Scalar(20, 20, 20), Scalar(20, 20, 20));
imshow("效果图", src);
waitKey();
return 0;
}
我们经常会蒋某中尺寸的图像转换成其他尺寸的图像,如果要放大或者缩小图片的尺寸,可以用OpenCV提供的如下两种方法。
图像金字塔是图像中多尺度表达的一种,最主要用于图像的分割,是一种以多分辨率来解释图像的有效但概念简单的结构。
图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的,分辨率逐步降低且来源于同一张原始图片的几何。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。
金字塔的底部是待处理图像的高分辨率表示,而顶部是低分辨率的近似。我们将一层一层的图像比喻成金字塔,层级越高,则图像越小,分辨率越低。
一般有两种类型的图像金字塔出现在文献以及实际应用中。
高斯金字塔——用来向下采样,主要的图像金字塔
拉普拉斯金字塔——用来从金字塔底层图像重建上层未采样的图像,在数字图像处理中也即是预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。
要从金字塔第i层生成第i+1层(从下往上,第i+1层表示为Gi+1),我们先要用高斯核对Gi进行卷积,然后删除所有偶数行和偶数列,新得到图像面积会变成源图像的四分之一。按上诉过程对输入图像G0执行操作就可产生出整个金字塔。
对图像向上采样——pyrUp函数
对图像向下采样——pyrDown函数
这里的向下和向上采样,是针对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是尺寸减半。
但需要注意的是,pryUp和pryDown不是互逆的操作,即pryUp不是降采样的逆操作。这种情况下,图像首先在每个维度上扩大为原来的两倍,新增的行(偶数行)以0填充。然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大为原来两倍的过滤器)去估计“丢失”像素的近似值。
pryDown()是一个会丢新信息的函数。为了恢复原来更高的分辨率的图像,我们要获得由降采样操作丢失的信息,这些数据就和拉普拉斯金字塔有关了。
高斯金字塔是通过高斯平滑和亚采样获得一系列下采样图像,也就是说第K层高斯金字塔通过平滑 、亚采样就能获得K+1层高斯图像。高斯金字塔包含了一系列低通滤波器,期截止频率从上一层到下一层以因子2逐渐增加,所以高斯金字塔可以跨越很大的频率范围。
1.对图像的向下采样
为了获取层级为Gi+1的金字塔图像,我们采用如下方法:
1)对图像Gi进行高斯内核卷积
2)将所有偶数行和列去除
得到的图像即为Gi+1的图像。显而易见,结果图像只有原图像的四分之一。通过对输入图像Gi不停迭代以上步骤就会得到整个金字塔。同时我们也可以看到,向下取样会逐渐丢失图像的信息。
2.对图像的向上采样
如果想放大图像,则需要通过向上采样操作得到,具体方法如下。
1)将图像在每个方向扩大为原来的两倍,新增的行和列以0填充
2)使用先前同样的内核(乘以4)与放大后的图像卷积,获得"新增像素"的近似值。
得到的图像即为放大后的图像,但是与原来的图像相比会发觉比较模糊,因为在缩放的过程中已经丢失了一些信息。如果想在缩小和放大整个过程这能够减少信息的丢失,这些数据就形成了拉普拉斯金字塔。
关于图像金字塔非常重要的一个应用就是图像分割。图像分割的话,先要建立一个图像金字塔,然后对Gi和Gi+1的直接依照对应的关系,建立起"父与子"关系。而快速初始化分割可以现在金字塔高层的低分辨率上完成,然后逐层对分割加以优化。
1.向上采样pyrUp()函数
pyrUp()函数的作用是向上采样并模糊一张图像,说白了就是放大一张图片。
void pyrUp(InputArray src,OutputArray dst,const Size& dstsize=Size(),int borderType=BORDER_DEFAULT)
#include
using namespace std;
using namespace cv;
int main() {
Mat srcImage = imread("../../image/lena_color_256.tif");
Mat tmpImage, dstImage;
tmpImage = srcImage;
imshow("原图", srcImage);
pyrUp(tmpImage, dstImage, Size(tmpImage.cols * 2, tmpImage.rows * 2));
imshow("效果图", dstImage);
waitKey();
return 0;
}
#include
using namespace std;
using namespace cv;
int main() {
Mat srcImage = imread("../../image/lena_color_256.tif");
Mat tmpImage, dstImage;
tmpImage = srcImage;
imshow("原图", srcImage);
pyrDown(tmpImage, dstImage, Size(tmpImage.cols / 2, tmpImage.rows / 2));
imshow("效果图", dstImage);
waitKey();
return 0;
}
resize()函数将源图像精确地转换为指定尺寸的目标图像。如果原图像中设置了ROI,那么resize()函数会对原图像的ROI区域进行调整图像尺寸的操作,来输出到目标图像中。若目标图像中已经设置了ROI区域,不难理解resize()将会对源图像进行尺寸调整并填充到目标图像的ROI中。
很多时候,我们并不用考虑第二个参数dst的初始图像和尺寸(即直接定义一个Mat类型,不用对其初始化),因为其尺寸和类型可以由src,dsize、fx和fy这几个参数确定
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
d s i z e = S i z e ( r o u n d ( f x ∗ s r c . c o l s ) , r o u n d ( f y ∗ s r c . r o w s ) ) dsize = Size(round(fx*src.cols), round(fy*src.rows)) dsize=Size(round(fx∗src.cols),round(fy∗src.rows))
( d o u b l e ) d s i z e . w i d t h / s r c . c o l s (double)dsize.width/src.cols (double)dsize.width/src.cols
( d o u b l e ) d s i z e . h e i g h t / s r c . r o w s (double)dsize.height/src.rows (double)dsize.height/src.rows
Mat dstImage = Mat::zeros(512,512,CV_8UC3);
Mat srcImage=imread("../../image/1.tif");
//显示指定dsize=dstImage.size(),那么fx和fy会被计算出来,不用额外指定
resize(srcImage,dstImage,dstImage.size());
方式二:
Mat dstImage;
Mat srcImage=imread("../../1.tif");
//指定fx和fy,让函数计算出目标图像的大小
resize(srcImage,dstImage,Size(),0.5,0.5);
resize函数案例
#include
using namespace std;
using namespace cv;
int main() {
Mat srcImage = imread("../../image/lena_color_256.tif");
Mat tmpImage, dstImage1, dstImage2;
tmpImage = srcImage;
imshow("原始图", srcImage);
resize(tmpImage, dstImage1, Size(tmpImage.cols / 2, tmpImage.rows / 2),
(0, 0), (0, 0), 3);
resize(tmpImage, dstImage2, Size(tmpImage.cols * 2, tmpImage.rows * 2),
(0, 0), (0, 0), 3);
imshow("缩小", dstImage1);
imshow("放大", dstImage2);
waitKey();
return 0;
}
在对各种图形进行处理操作的工程中,我们常常需要对图像中的像素做出取舍与决策,直接提出一些低于或高于一定值的像素。
阈值可以被视作最简单的图像分割方法。比如,从一幅图像中利用阈值分割出我们需要的物体部分。这样的图像分割方法基于图像中物体与背景之间的灰度差异,而且此分割属于像素级的分割。为了从一幅图像中提取我们需要的部分,用该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断。注意:阈值的选取依赖于具体的问题。即物体在不同的图像中有可能会有不同的灰度值。
一旦找到了需要分割的物体的像素点,可以对这些像素点设定一些特定的值来表示。例如,可以将该物体的像素点的灰度值设置为0(黑色),其他的像素点灰度值为255(白色)。当然像素点的灰度值可以任意,但最好设定的两种颜色对比度较强,以方便观察结果。
函数Threshold()对单通道数组应用固定阈值操作。该函数的典型应用是对灰度图像进行阈值操作得到二值图像(compare()函数也可以达到此目的)或者是去掉噪声,例如过滤很小或很大像素值的图像点。
double threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
void adaptiveThreshold ( InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C )
#include
using namespace std;
using namespace cv;
#define WINDOW_NAME "程序窗口"
int g_nThreshouldValue = 100;
int g_nThreshouldType = 3;
Mat g_srcImage, g_grayImage, g_dstImage;
void on_Threshold(int, void*) {
threshold(g_grayImage, g_dstImage, g_nThreshouldValue,
255, g_nThreshouldType);
imshow(WINDOW_NAME, g_dstImage);
}
int main() {
g_srcImage = imread("../../image/1.tif");
if (!g_srcImage.data) {
cout << "读取图片失败" << endl;
return false;
}
cvtColor(g_srcImage, g_grayImage, COLOR_RGB2GRAY);
imshow("原图", g_grayImage);
namedWindow(WINDOW_NAME);
createTrackbar("模式", WINDOW_NAME, &g_nThreshouldType,
4, on_Threshold);
createTrackbar("参数值", WINDOW_NAME, &g_nThreshouldValue,
255, on_Threshold);
on_Threshold(0, 0);
while (1) {
int key;
key = waitKey(20);
if ((char)key == 27)
break;
}
}