大家好,今天學習的時候遇到了圖像金字塔的問題,那麼今天就聊聊圖像金字塔的話題吧!
其實圖像金字塔也沒有那麼高大上,實際的功能也是爲了實現圖像的放大與放小。說到放大放小,圖像金字塔也分爲兩種,一種就是高斯金字塔,也就是下采樣,實現圖像的縮小;而另一種就是拉普拉斯金字塔啦,即下采樣,功能呢,相信大家已經可以猜到了,就是實現圖像的放大。
那麼下面,我們就具體的瞭解一下圖像金字塔吧!
一、高斯金字塔
高斯金字塔是在圖像處理、計算機視覺、信號處理上所使用的一項技術。高斯金字塔本質上爲信號的多尺度表示法,亦即將同一信號或圖片多次的進行高斯模糊,並且向下取樣,藉以產生不同尺度下的多組信號或圖片以進行後續的處理,例如在影像辨識上,可以藉由比對不同尺度下的圖片,以防止要尋找的內容可能在圖片上有不同的大小。
想想金字塔爲一層一層的圖像,層級越高,圖像越小。
如上圖所示,我們將一層層的圖像比喻爲金字塔,層級越高,則圖像尺寸越小,分辨率越低。
圖像金字塔有兩個高頻出現的名詞:上採樣和下采樣。現在說說他們倆。
上採樣:就是圖片放大(所謂上嘛,就是變大),使用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 <ctype.h>
#include <stdio.h>
#include <iostream>
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=<camera #, default 0> -p=<index of the frame to be decoded/captured next>\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<string>("c");
if (camera.size() == 1 && isdigit(camera[0]))
cap.open(parser.get<int>("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<string>("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<int>("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 — 圖像金字塔的分享到這裏就結束了,希望大家能給我點個贊哦!