大家好,今天学习的时候遇到了图像金字塔的问题,那么今天就聊聊图像金字塔的话题吧!
其实图像金字塔也没有那么高大上,实际的功能也是为了实现图像的放大与放小。说到放大放小,图像金字塔也分为两种,一种就是高斯金字塔,也就是下采样,实现图像的缩小;而另一种就是拉普拉斯金字塔啦,即下采样,功能呢,相信大家已经可以猜到了,就是实现图像的放大。
那么下面,我们就具体的了解一下图像金字塔吧!
一、高斯金字塔
高斯金字塔是在图像处理、计算机视觉、信号处理上所使用的一项技术。高斯金字塔本质上为信号的多尺度表示法,亦即将同一信号或图片多次的进行高斯模糊,并且向下取样,藉以产生不同尺度下的多组信号或图片以进行后续的处理,例如在影像辨识上,可以借由比对不同尺度下的图片,以防止要寻找的内容可能在图片上有不同的大小。
想想金字塔为一层一层的图像,层级越高,图像越小。
如上图所示,我们将一层层的图像比喻为金字塔,层级越高,则图像尺寸越小,分辨率越低。
图像金字塔有两个高频出现的名词:上采样和下采样。现在说说他们俩。
上采样:就是图片放大(所谓上嘛,就是变大),使用PryUp函数
下采样:就是图片缩小(所谓下嘛,就是变小),使用PryDown函数
下采样将步骤:
1、对图像进行高斯内核卷积
2、将所有偶数行和列去除
下采样就是图像压缩,会丢失图像信息。
上采样步骤:
1、将图像在每个方向放大为原来的两倍,新增的行和列用0填充;
2、使用先前同样的内核(乘以4)与放大后的图像卷积,获得新增像素的近似值。
上、下采样都存在一个严重的问题,那就是图像变模糊了,因为缩放的过程中发生了信息丢失的问题。要解决这个问题,就得看拉普拉斯金字塔了。
注意:上采样和下采样是非线性处理,不可逆,有损的处理!
实例
#include "iostream"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace std;
using namespace cv;
const char* window_name = "Pyramids Demo";
int main(int argc, char** argv)
{
cout << "\n Zoom In-Out demo \n "
"------------------ \n"
" * [i] -> Zoom in \n"
" * [o] -> Zoom out \n"
" * [ESC] -> Close program \n" << endl;
const char* filename = "1.png";
// Loads an image
Mat src = imread(filename);
// Check if image is loaded fine
if (src.empty()) {
printf(" Error opening image\n");
printf(" Program Arguments: [image_name -- default default 1.png] \n");
return -1;
}
for (;;)
{
imshow(window_name, src);
char c = (char)waitKey(0);
if (c == 27)
{
break;
}
else if (c == 'i')
{
pyrUp(src, src, Size(src.cols * 2, src.rows * 2));
printf("** Zoom In: Image x 2 \n");
}
else if (c == 'o')
{
pyrDown(src, src, Size(src.cols / 2, src.rows / 2));
printf("** Zoom Out: Image / 2 \n");
}
}
return 0;
}
输出结果:
上采样的结果就不演示了,图像太大了...大家可以自己用这份代码测试一下,嘿嘿!
二、拉普拉斯金字塔
OpenCV4版本中的拉普拉斯金字塔位于imgproc模块中的Image Filtering子模块中,拉普拉斯金字塔主要应用于图像融合。
拉普拉斯金字塔是高斯金字塔的修正版,为了还原到原图。通过计算残差图来达到还原。
下式是拉普拉斯金字塔第i层的数学定义:
L(i)=G(i) - PyrUp(G(i+1))
将降采样之后的图像再进行上采样操作,然后与之前还没降采样的原图进行做差得到残差图!为还原图像做信息的准备!也就是说,拉普拉斯金字塔是通过源图像减去先缩小后再放大的图像的一系列图像构成的。保留的是残差!
整个拉普拉斯金字塔运算过程可以通过下图来概括:
在OpenCV中拉普拉斯金字塔的函数原型:
CV_EXPORTS_W void Laplacian( InputArray src, // 原图
OutputArray dst, // 目标图像
int ddepth, // 目标图像的深度
int ksize = 1, // 用于计算二阶导数滤波器的孔径大小;大小必须为正数和奇数。
double scale = 1, // 计算拉普拉斯值的可选比例因子; 默认情况下,不应用缩放。
double delta = 0, // 在将结果存储到dst之前添加到结果中的可选增量值
int borderType = BORDER_DEFAULT );//决定在图像发生几何变换或者滤波操作(卷积)时边沿像素的处理方式。
关于ksize,
该函数通过将使用Sobel运算符计算的x和y二阶导数相加来计算源图像的拉普拉斯算子;
当ksize>1下面的公式计算
当ksize=1时,即默认参数,则使用下面3x3的卷积
参数:
src 原图像
dst 与原图一样尺寸和通道的目标图像
ddepth 目标图像的深度
ksize 用于计算二阶导数滤波器的孔径大小;大小必须为正数和奇数。
scale 计算拉普拉斯值的可选比例因子; 默认情况下,不应用缩放。
delta 在将结果存储到dst之前添加到结果中的可选增量值。
borderType 决定在图像发生几何变换或者滤波操作(卷积)时边沿像素的处理方式。
borderType 各种类型
enum BorderTypes
{
BORDER_CONSTANT = 0, //!< `iiiiii|abcdefgh|iiiiiii` with some specified `i`
BORDER_REPLICATE = 1, //!< `aaaaaa|abcdefgh|hhhhhhh`
BORDER_REFLECT = 2, //!< `fedcba|abcdefgh|hgfedcb`
BORDER_WRAP = 3, //!< `cdefgh|abcdefgh|abcdefg`
BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`
BORDER_TRANSPARENT = 5, //!< `uvwxyz|absdefgh|ijklmno`
BORDER_REFLECT101 = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
BORDER_DEFAULT = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
BORDER_ISOLATED = 16 //!< do not look outside of ROI
};
BORDER_CONSTANT 就是边沿像素用i替换
BORDER_REPLICATE 复制边界像素
BORDER_REFLECT 反射复制边界像素
BORDER_REFLECT_101 以边界为对称轴反射复制像素
实例:使用拉普拉斯对视频图像进行边缘检测
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include
#include
#include
using namespace cv;
using namespace std;
static void help()
{
cout <<
"\nThis program demonstrates Laplace point/edge detection using OpenCV function Laplacian()\n"
"It captures from the camera of your choice: 0, 1, ... default 0\n"
"Call:\n"
"./laplace -c= -p=\n" << endl;
}
enum { GAUSSIAN, BLUR, MEDIAN };
int sigma = 3;
int smoothType = GAUSSIAN;
int main(int argc, char** argv)
{
cv::CommandLineParser parser(argc, argv, "{ c | 0 | }{ p | | }");
help();
VideoCapture cap;
string camera = parser.get("c");
if (camera.size() == 1 && isdigit(camera[0]))
cap.open(parser.get("c")); 。// 本机摄像头
else
cap.open(samples::findFileOrKeep(camera));
if (!cap.isOpened()) // 判断摄像头是否打开
{
cerr << "Can't open camera/video stream: " << camera << endl;
return 1;
}
cout << "Video " << parser.get("c") <<
": width=" << cap.get(CAP_PROP_FRAME_WIDTH) <<
", height=" << cap.get(CAP_PROP_FRAME_HEIGHT) <<
", nframes=" << cap.get(CAP_PROP_FRAME_COUNT) << endl;
int pos = 0;
if (parser.has("p"))
{
pos = parser.get("p");
}
if (!parser.check())
{
parser.printErrors();
return -1;
}
if (pos != 0)
{
cout << "seeking to frame #" << pos << endl;
if (!cap.set(CAP_PROP_POS_FRAMES, pos))
{
cerr << "ERROR: seekeing is not supported" << endl;
}
}
namedWindow("Laplacian", WINDOW_AUTOSIZE);
createTrackbar("Sigma", "Laplacian", &sigma, 15, 0);
Mat smoothed, laplace, result;
for (;;)
{
Mat frame;
cap >> frame;
if (frame.empty())
break;
int ksize = (sigma * 5) | 1;
if (smoothType == GAUSSIAN)
GaussianBlur(frame, smoothed, Size(ksize, ksize), sigma, sigma);
else if (smoothType == BLUR)
blur(frame, smoothed, Size(ksize, ksize));
else
medianBlur(frame, smoothed, ksize);
Laplacian(smoothed, laplace, CV_16S, 5);
convertScaleAbs(laplace, result, (sigma + 1)*0.25);
imshow("Laplacian", result);
char c = (char)waitKey(30);
if (c == ' ')
smoothType = smoothType == GAUSSIAN ? BLUR : smoothType == BLUR ? MEDIAN : GAUSSIAN;
if (c == 'q' || c == 'Q' || c == 27)
break;
}
return 0;
}
好了,本次OpenCV --- 图像金字塔的分享到这里就结束了,希望大家能给我点个赞哦!