本讲我们将来学习OpencCV中的函数pryUp和pyrDown是如何对图像进行向上和向下采样的,以及专门用于缩放图像尺寸的resize函数。
1 图像金字塔
图像金字塔是图像中多尺度表达的一种方式,多用于机器视觉和图像压缩。金字塔的低部是待处理图像的原始高分辨率表示,而顶部是低分辩率的近似。层次越高,图像越小,分辨率越低。 OpenCV中有两种类型的图像金字塔经常使用:
-
高斯金字塔:用来向下采样,主要的图像金字塔。 -
拉普拉斯金字塔:用来从金字塔低层图像重建上层采样图像,在数字图像中用来预测残差,可以对图像进行最大程度的还原,配合高斯金字塔一起使用。 两者的主要区别在与:高斯金字塔采用向下降采用图像,而拉普拉斯金字塔则是采用从金字塔地层图像中向上采用,重建一个图像。 -
对图像向上采样——pyrUp函数; -
对图像向下采样——pyrDown函数; 这里的向上和向下采样,是针对图像的尺寸而言的(和金字塔的方向相反),向上就是图像尺寸加倍,向下就是图像尺寸减半。 但需要注意的是,pyrDown和pyrUp不是互逆的,即pryUp不是降采样的逆操作。这个操作图像首先在每个维度上扩大为原来的两倍,新增的行(偶数)以0填充,然后给指定的滤波器进行卷积(实际上是一个在每个维度都扩大原来的两倍的过滤器)去估计“丢失”像素的近似。 pyrDown()是一个会丢失信息的函数,为了恢复原来更高的分辨率的图像,我们要获取由降采用操作丢失的信息,这些数据就和拉普拉斯金字塔有关了。 1.1 高斯金字塔 高斯金字塔是通过高斯平滑和亚采样获得采用图像的,也就是说第k层高斯金字塔通过平滑,亚采样就可以获得k+1层高斯图像。高斯金字塔包含了一系列低通滤波器,其截至频率从上一层到下一层以2因子逐渐增加,所以高斯金字塔可以跨越很大的频率范围。 1> 对图像的向下采样 为了获取层级为
G
i
+
1
G_{i+1}
Gi+1?的金字塔,采用以下方法: (1)对图像
G
i
G_i
Gi?进行高斯内核卷积; (2) 将所有的偶数行和列去掉; 得到的图像即为
G
i
+
1
G_{i+1}
Gi+1?的图像。可以看出结果图像只有原图的
1
4
\frac{1}{4}
41?.通过对原图像不停的迭代就可以得到整个金字塔。同时可以看到向下取样会逐渐丢失图像的信息。以上就是对图像的向下取样操作,即缩小图像。 2> 对图像的向上采样 如果想放大图像,则需要通过向上取样操作; (1) 将图像在每个方向扩大为原来的两倍,新增的行和列以0填充; (2)使用之前同样的内核(乘以4)与放大后的图像做卷积,获得“新增像素"的近似值; 得到的图像即为放大后的图像,但是与原来的图像相比会变得模糊,因为在缩放的过程中已经丢失了一些信息。如果想在缩小和放大的过程中减少信息的丢失,这些信息就会形成拉普拉斯金字塔。 1.2 拉普拉斯金字塔 拉普拉斯金字塔第i层的数学定义:
L
i
=
G
i
?
U
P
(
G
i
+
1
)
?
g
5
x
5
L_i = G_i - UP(G_{i + 1}) \bigotimes g_{5 x 5}
Li?=Gi??UP(Gi+1?)?g5x5? 式中的
G
i
G_i
Gi?表示第
i
i
i层的图像。而UP()操作是将原图像中位置为
(
x
,
y
)
(x,y)
(x,y)的像素映射到目标图像的
(
2
x
+
1
,
2
y
+
1
)
(2x+1, 2y+1)
(2x+1,2y+1)的位置上,即在进行向上取样。
?
\bigotimes
?符号表示卷积,
g
5
x
5
g_{5x5}
g5x5?表示5x5的高斯内核。 OpenCV中的拉普拉斯变换:
L
i
=
G
i
?
P
y
r
U
p
(
G
i
+
1
)
L_i = G_i - PyrUp(G_{i + 1})
Li?=Gi??PyrUp(Gi+1?) 也就是说:拉普拉斯金字塔是通过原图像减去先缩小后放大的图像的一系列图像构成的。所以,我们可以将拉普拉斯金字塔理解为高斯金字塔的逆形式。 图像金字塔最重要的应用就是图像分割.
2. 图像尺寸缩放:resize()函数
2.1 resize()是OpenCV专门用来调整图像大小的函数。 函数原型:void resize(InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR) 参数: - 参数一: 输入图像,原图像。 - 参数二:输出图像,当其非零时,有着dsize(第三个参数)的尺寸,或者由src.size()计算出来。 - 参数三:输出图像的大小。,由下式计算:dsize = Size(round(fx*src.cols), round(fy * rows)) ,其中dsize,fx,fy都不为0; - 参数四:double类型的fx,沿水平轴的缩放系数,默认为0,如果不为0则(double)dsize.width/src.cols - 参数五:沿垂直轴的缩放系数,(double)dsize.height/src.rows ; - 参数六:指定插值方式,默认为INTER_LINEAR(线性插值); 插值方式的选择:
最邻近插值 | INTER_NEAREST |
---|
线性插值 | INTER_LINEAR | 区域插值(利用像素关系的重采样插值) | INTER_AREA | 三次样条插值(超过4x4像素邻域内的双三次插值) | INTER_CUBIC | Lanczos插值(超过8x8像素邻域的Lanczos插值) | INTER_LANCZOS4 |
若要缩小图像,最好用CV_INTER_AREA来插值;若要放大图像,最好用CV_INTER_LINEAR
2.2 resize的调用范例 (1)方法一
Mat dstImage = Mat::zeros(512, 512, CV_8UC3);
Mat srcImage = imread("1.jpg");
resize(srcImage, dstImage, dstImage.size());
(2)方法二
Mat dstImage;
Mat srcImage = imread("1.jpg");
resize(srcImage, dstImage, Size(), 0.5, 0.5);
2.3 resize实例的演示 程序代码:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage = imread("../1.jpg");
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("效果图1", dstImage1);
imshow("效果图2", dstImage2);
while(char (waitKey(1)) != 'q'){}
return 0;
}
效果:
3 图像金字塔函数解析
3.1 向上采样:pyrUp()函数 作用:向上采用模糊一张图像,就是放大图像; 原型:void pryUp(InputArray src, OutputArray dst, const Size& dstsize = Size(), int borderType=BORDER_DEFAULT) ; 参数:
- 参数一:输入图像;
- 参数二:和原图像相同尺寸和类型的输出图像;
- 参数三:输出图像的大小,默认下是Size(src.cols2, src.rows2)来计算,且一直满足
∣
d
s
t
s
i
z
e
.
w
i
d
t
h
?
s
r
c
.
c
o
l
s
?
2
∣
≤
(
d
s
t
s
i
z
e
.
w
i
d
t
h
m
o
d
2
)
|dstsize.width - src.cols*2 | \le(dstsize.width mod2)
∣dstsize.width?src.cols?2∣≤(dstsize.widthmod2),
∣
d
s
t
s
i
z
e
.
h
e
i
g
h
t
?
s
r
c
.
r
o
w
s
?
2
∣
≤
(
d
s
t
s
i
z
e
.
h
e
i
g
h
t
m
o
d
2
)
|dstsize.height- src.rows*2 | \le(dstsize.height mod2)
∣dstsize.height?src.rows?2∣≤(dstsize.heightmod2)
- 参数四:边界模式,不用管
pyrUp()函数执行高斯金字塔的采样操作,它也可以用于拉普拉斯金字塔操作。 它通过插入可为0的行与列,对原图像进行向上取样操作,然后将结果与pyrDown()乘以4的内核做卷积 实例:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
Mat srcImage = imread("../1.jpg");
Mat tmpIamge , dstImage;
tmpIamge = srcImage;
imshow("原图", srcImage);
pyrUp(tmpIamge, dstImage, Size(tmpIamge.cols *2, tmpIamge.rows * 2));
imshow("效果图", dstImage);
while(char (waitKey(1)) != 'q'){}
return 0;
}
效果: 3.2 采样:pyrDown()函数 作用:向下采样并模糊一张图片,就是缩小一张图。 函数原型:void pyrDown(InputArray src, OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT) 参数一,二和pyrUp一样 参数三:输出图像的大小,有默认值Size(), 就是Size((src.cols + 1)/2, (src.rows + 1)/2)来计算,且一直满足下列条件:
∣
d
s
t
s
i
z
e
.
w
i
d
t
h
?
2
?
s
r
c
.
c
o
l
s
∣
≤
2
|dstsize.width *2- src.cols | \le2
∣dstsize.width?2?src.cols∣≤2,
∣
d
s
t
s
i
z
e
.
h
e
i
g
h
t
?
2
?
s
r
c
.
r
o
w
s
∣
≤
2
|dstsize.height*2- src.rows | \le2
∣dstsize.height?2?src.rows∣≤2 实现代码:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
Mat srcImage = imread("../1.jpg");
Mat tmpIamge , dstImage;
tmpIamge = srcImage;
imshow("原图", srcImage);
pyrDown(tmpIamge, dstImage, Size(tmpIamge.cols / 2, tmpIamge.rows / 2));
imshow("效果图", dstImage);
while(char (waitKey(1)) != 'q'){}
return 0;
}
效果:
4 综合实例:图像金字塔和图片尺度缩放
用resize, pryUp, pyrDown来让原图进行缩放操作,分别用键盘键1,2,3,4来控制图像的放大缩小 代码:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
#define WINDOW_NAME "窗口"
Mat g_srcImage, g_dstImage, g_tmpImage;
int main()
{
g_srcImage = imread("../1.jpg");
namedWindow(WINDOW_NAME, WINDOW_AUTOSIZE);
g_tmpImage = g_srcImage;
g_dstImage = g_tmpImage;
int key = 0;
while(1) {
key = waitKey(9);
switch (key)
{
case 27:
return 0;
break;
case 'q':
return 0;
break;
case '1':
pyrUp(g_tmpImage, g_dstImage, Size(g_tmpImage.cols *2, g_tmpImage.rows * 2));
cout <<"1 pyrUp picture" << endl;
break;
case '2':
resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols *2, g_tmpImage.rows * 2));
cout << "2 resize picture" << endl;
break;
case '3':
pyrDown(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
cout << "3 pyrDown picture" << endl;
break;
case '4':
resize(g_tmpImage, g_dstImage, Size(g_tmpImage.cols / 2, g_tmpImage.rows / 2));
cout << "4 resize picture" << endl;
}
imshow(WINDOW_NAME, g_dstImage);
g_tmpImage = g_dstImage;
}
return 0;
}
效果:
4 阈值化
对图像中的像素做取舍,直接剔除一些低于或高于一定值的像素。 阈值可被视为最简单的图像分割,从一幅图中利用阈值分割出我们需要的部分。这样的分割极于物体与背景的灰度值差异,属于像素级别的分割。阈值的选取依赖具体的问题,即物体在不同的图像中有可能会有不同的灰度值。 OpenCV2X中,Threshold()函数(基本阈值操作)和adaptiveThreshold()函数(自适应阈值操作)可以完成着由那个的要求。思路:给定一个数组和一个阈值,然后根据数组中每个元素的值是高于还是低于阈值而进行一些处理。 4.1 固定阈值操作:Threshold()函数 Threshold()对单通道数组应用固定阈值操作。该函数的典型作用是对灰度图像进行阈值操作得到二值图像,或者是去掉噪声,例如过滤很大或很小像素值的图像点。 函数原型:double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
- 参数一:输入数组,填单通道,8或32位浮点型的Mat;
- 参数二:函数调用后的输出结果,与参数一同尺寸同大小;
- 参数三:阈值的具体值;
- 参数四:maxval, 当第五个参数阈值类型为CV_THRESH_BINARY或CV_THRESH_BIANRY_INV时阈值类型的最大值;
- 参数五:阈值类型。threshold()函数支持的对图像阈值的方法由其确定,标识依次取0,1,2,3,4
对应的图形化阈值 4.2 自适应阈值操作:adaptiveThreshold()函数 作用:对矩阵采用自适应阈值操作,支持就地操作; 原型:void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C) - 参数一:8位单通道浮点型图像;
- 参数二:运算后的输出结果;
- 参数三:给像素赋的满足条件的非零值;
- 参数四:用于指定要使用的自适应阈值算法,可取ADAPTIVE_THRESH_MEAN_C或ADAPTIVE_THRESH_GAUSSIAN_C
- 参数五:阈值类型;
- 参数六:blockSize, 用于计算阈值大小的一个像素的邻域尺寸,取3,5,7
- 参数七:C,减去平均或加权平均后的常数值。
adaptiveThreshold()函数根据如下公式,将一幅灰度图变换为一幅二值图。 对与其中的
T
(
x
,
y
)
T(x, y)
T(x,y)为分别计算每个单独像素的阈值,取值如下: - 对于ADAPTIVE_THRESH_MEAN_C方法,阈值
T
(
x
,
y
)
T(x,y)
T(x,y)为blockSize x blockSize邻域内(x, y)减去第七个参数C的平均值。
- 对于ADAPTIVE_THRESH_GAUSSIAN_C方法,阈值
T
(
x
,
y
)
T(x,y)
T(x,y)为blockSIze x blockSize邻域内(x, y)减去第七个参数C与高斯窗交叉相关的加权总和。
4.3 基本阈值操作实例 实现代码:
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
#define WINDOW_NAME "窗口"
Mat g_srcImage, g_grayImage, g_dstImage;
int g_nThresholdType = 3;
int g_nThresholdValue = 100;
void On_Threshold(int, void*);
int main()
{
g_srcImage = imread("../1.jpg");
cvtColor(g_srcImage, g_grayImage, COLOR_RGB2GRAY);
namedWindow(WINDOW_NAME, CV_WINDOW_NORMAL);
createTrackbar("阈值类型", WINDOW_NAME, &g_nThresholdType, 4, On_Threshold);
createTrackbar("参数值", WINDOW_NAME, &g_nThresholdValue, 255, On_Threshold);
On_Threshold(0, 0);
while(1){
int key;
key = waitKey(20);
if((char) key == 27){break;}
}
return 0;
}
void On_Threshold(int , void*)
{
threshold(g_grayImage, g_dstImage, g_nThresholdValue, 255, g_nThresholdType);
imshow(WINDOW_NAME, g_dstImage);
}
实现效果:
|