jashliao 用 VC++ 實現 fanfuhan OpenCV 教學037 ~ opencv-037-高斯金字塔圖像(pyrUp / pyrDown)
jashliao 用 VC++ 實現 fanfuhan OpenCV 教學037 ~ opencv-037-高斯金字塔圖像(pyrUp / pyrDown)
資料來源: https://fanfuhan.github.io/
https://fanfuhan.github.io/2019/04/11/opencv-037/
GITHUB:https://github.com/jash-git/fanfuhan_ML_OpenCV
https://github.com/jash-git/jashliao-implements-FANFUHAN-OPENCV-with-VC
★前言:

★主題:
圖像金字塔是對一張輸入圖像先模糊再下採樣為原來的大小的1/4(寬高縮小一半),不斷重複模糊與下採樣的過程就得到了不同分辨率的輸出圖像,疊加在一起就形成的圖像金字塔,所以圖像金字塔是圖像的空間多分辨率存在形式。
這裡的模糊是指高斯模糊,所以這個方式生成的金字塔圖像又稱為高斯金字塔圖像。
★C++
// VC_FANFUHAN_OPENCV037.cpp : 定義主控台應用程式的進入點。
//
/*
// Debug | x32
通用屬性
| C/C++
| | 一般
| | 其他 Include 目錄 -> C:\opencv\build\include
|
| 連結器
| |一一般
| | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib
|
| |一輸入
| | 其他相依性 -> opencv_world411d.lib;%(AdditionalDependencies)
// Releas | x64
組態屬性
| C/C++
| | 一般
| | 其他 Include 目錄 -> C:\opencv\build\include
|
| 連結器
| |一般
| | 其他程式庫目錄 -> C:\opencv\build\x64\vc15\lib
|
| |一輸入
| | 其他相依性 -> opencv_world411.lib;%(AdditionalDependencies)
*/
#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
void blur_demo(Mat &image, Mat &sum);
void edge_demo(Mat &image, Mat &sum);
int getblockSum(Mat &sum, int x1, int y1, int x2, int y2, int i);
void showHistogram(InputArray src, cv::String StrTitle);
void backProjection_demo(Mat &mat, Mat &model);
void blur3x3(Mat &src, Mat *det);
void add_salt_pepper_noise(Mat &image);
void add_gaussian_noise(Mat &image);
void USMImage(Mat src, Mat &usm, float fltPar);
void pyramid_up(Mat &image, vector<Mat> &pyramid_images, int level);
void pyramid_down(vector<Mat> &pyramid_images);
void pause()
{
printf("Press Enter key to continue...");
fgetc(stdin);
}
int main()
{
Mat src = imread("../../images/test.png");
if (src.empty())
{
cout << "could not load image.." << endl;
pause();
return -1;
}
else
{
imshow("input_src", src);
showHistogram(src, "Histogram_input_src");
vector<Mat> p_images;
pyramid_up(src, p_images, 2);
pyramid_down(p_images);
cout << "p_images size is " << p_images.size() << endl;
waitKey(0);
return 0;
}
return 0;
}
void pyramid_down(vector<Mat> &pyramid_images)//高斯金字塔01
{
for (int i = pyramid_images.size() - 1; i > -1; --i) {
Mat dst;
/*
pyrUp(tmp, dst, Size(tmp.cols * 2, tmp.rows * 2))
tmp: 當前影象, 初始化為原影象 src 。
dst : 目的影象(顯示影象,為輸入影象的兩倍)
Size(tmp.cols * 2, tmp.rows * 2) : 目的影象大小, 既然我們是向上取樣, pyrUp 期待一個兩倍於輸入影象(tmp)的大小。
*/
pyrUp(pyramid_images[i], dst);
imshow(format("pyramid_down_%d", i), dst);
}
}
void pyramid_up(Mat &image, vector<Mat> &pyramid_images, int level)//高斯金字塔02
{
Mat temp = image.clone();
Mat dst;
for (int i = 0; i < level; ++i)
{
/*
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ))
tmp: 當前影象, 初始化為原影象 src 。
dst: 目的影象( 顯示影象,為輸入影象的一半)
Size( tmp.cols/2, tmp.rows/2 ) :目的影象大小, 既然我們是向下取樣, pyrDown 期待一個一半於輸入影象( tmp)的大小。
注意輸入影象的大小(在兩個方向)必須是2的冥,否則,將會顯示錯誤。
最後,將輸入影象 tmp 更新為當前顯示影象, 這樣後續操作將作用於更新後的影象。
tmp = dst;
*/
pyrDown(temp, dst);
imshow(format("pyramid_up_%d", i), dst);
temp = dst.clone();
pyramid_images.push_back(temp);
}
}
void USMImage(Mat src, Mat &usm, float fltPar)//圖像銳化增强演算法(USM)
{
Mat blur_img;
/*
USM銳化公式表示如下:
(源圖像– w*高斯模糊)/(1-w);其中w表示權重(0.1~0.9),默認為0.6
OpenCV中的代碼實現步驟
– 高斯模糊
– 權重疊加
– 輸出結果
*/
GaussianBlur(src, blur_img, Size(0, 0), 25);
addWeighted(src, (1 + fltPar), blur_img, (fltPar*-1), 0, usm);//原圖 : 模糊圖片= 1.5 : -0.5 的比例進行混合
imshow("usm", usm);
showHistogram(usm, "Histogram_input_usm");
}
void blur_demo(Mat &image, Mat &sum)
{
int w = image.cols;
int h = image.rows;
Mat result = Mat::zeros(image.size(), image.type());
int x2 = 0, y2 = 0;
int x1 = 0, y1 = 0;
int ksize = 5;
int radius = ksize / 2;
int ch = image.channels();
int cx = 0, cy = 0;
for (int row = 0; row < h + radius; row++) {
y2 = (row + 1)>h ? h : (row + 1);
y1 = (row - ksize) < 0 ? 0 : (row - ksize);
for (int col = 0; col < w + radius; col++) {
x2 = (col + 1)>w ? w : (col + 1);
x1 = (col - ksize) < 0 ? 0 : (col - ksize);
cx = (col - radius) < 0 ? 0 : col - radius;
cy = (row - radius) < 0 ? 0 : row - radius;
int num = (x2 - x1)*(y2 - y1);
for (int i = 0; i < ch; i++) {
// 积分图查找和表,计算卷积
int s = getblockSum(sum, x1, y1, x2, y2, i);
result.at<Vec3b>(cy, cx)[i] = saturate_cast<uchar>(s / num);
}
}
}
imshow("blur_demo", result);
}
/**
* 3x3 sobel 垂直边缘检测演示
*/
void edge_demo(Mat &image, Mat &sum)
{
int w = image.cols;
int h = image.rows;
Mat result = Mat::zeros(image.size(), CV_32SC3);
int x2 = 0, y2 = 0;
int x1 = 0, y1 = 0;
int ksize = 3; // 算子大小,可以修改,越大边缘效应越明显
int radius = ksize / 2;
int ch = image.channels();
int cx = 0, cy = 0;
for (int row = 0; row < h + radius; row++) {
y2 = (row + 1)>h ? h : (row + 1);
y1 = (row - ksize) < 0 ? 0 : (row - ksize);
for (int col = 0; col < w + radius; col++) {
x2 = (col + 1)>w ? w : (col + 1);
x1 = (col - ksize) < 0 ? 0 : (col - ksize);
cx = (col - radius) < 0 ? 0 : col - radius;
cy = (row - radius) < 0 ? 0 : row - radius;
int num = (x2 - x1)*(y2 - y1);
for (int i = 0; i < ch; i++) {
// 积分图查找和表,计算卷积
int s1 = getblockSum(sum, x1, y1, cx, y2, i);
int s2 = getblockSum(sum, cx, y1, x2, y2, i);
result.at<Vec3i>(cy, cx)[i] = saturate_cast<int>(s2 - s1);
}
}
}
Mat dst, gray;
convertScaleAbs(result, dst);
normalize(dst, dst, 0, 255, NORM_MINMAX);
cvtColor(dst, gray, COLOR_BGR2GRAY);
imshow("edge_demo", gray);
}
int getblockSum(Mat &sum, int x1, int y1, int x2, int y2, int i) {
int tl = sum.at<Vec3i>(y1, x1)[i];
int tr = sum.at<Vec3i>(y2, x1)[i];
int bl = sum.at<Vec3i>(y1, x2)[i];
int br = sum.at<Vec3i>(y2, x2)[i];
int s = (br - bl - tr + tl);
return s;
}
void add_gaussian_noise(Mat &image)//高斯雜訊
{
Mat noise = Mat::zeros(image.size(), image.type());
// 产生高斯噪声
randn(noise, (15, 15, 15), (30, 30, 30));
Mat dst;
add(image, noise, dst);
image = dst.clone();//dst.copyTo(image);//圖像複製
//imshow("gaussian_noise", dst);
}
void add_salt_pepper_noise(Mat &image)//白雜訊
{
// 随机数产生器
RNG rng(12345);
for (int i = 0; i < 1000; ++i) {
int x = rng.uniform(0, image.rows);
int y = rng.uniform(0, image.cols);
if (i % 2 == 1) {
image.at<Vec3b>(y, x) = Vec3b(255, 255, 255);
}
else {
image.at<Vec3b>(y, x) = Vec3b(0, 0, 0);
}
}
//imshow("saltp_epper", image);
}
void blur3x3(Mat &src, Mat *det)
{
// 3x3 均值模糊,自定义版本实现
for (int row = 1; row < src.rows - 1; row++) {
for (int col = 1; col < src.cols - 1; col++) {
Vec3b p1 = src.at<Vec3b>(row - 1, col - 1);
Vec3b p2 = src.at<Vec3b>(row - 1, col);
Vec3b p3 = src.at<Vec3b>(row - 1, col + 1);
Vec3b p4 = src.at<Vec3b>(row, col - 1);
Vec3b p5 = src.at<Vec3b>(row, col);
Vec3b p6 = src.at<Vec3b>(row, col + 1);
Vec3b p7 = src.at<Vec3b>(row + 1, col - 1);
Vec3b p8 = src.at<Vec3b>(row + 1, col);
Vec3b p9 = src.at<Vec3b>(row + 1, col + 1);
int b = p1[0] + p2[0] + p3[0] + p4[0] + p5[0] + p6[0] + p7[0] + p8[0] + p9[0];
int g = p1[1] + p2[1] + p3[1] + p4[1] + p5[1] + p6[1] + p7[1] + p8[1] + p9[1];
int r = p1[2] + p2[2] + p3[2] + p4[2] + p5[2] + p6[2] + p7[2] + p8[2] + p9[2];
det->at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b / 9);
det->at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g / 9);
det->at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r / 9);
}
}
}
void backProjection_demo(Mat &image, Mat &model)//反向投影
{
Mat image_hsv, model_hsv;
cvtColor(image, image_hsv, COLOR_BGR2HSV);//彩色轉HSV
cvtColor(model, model_hsv, COLOR_BGR2HSV);
// 定义直方图参数与属性
int h_bins = 32, s_bins = 32;
int histSize[] = { h_bins, s_bins };//要切分的像素強度值範圍,預設為256。每個channel皆可指定一個範圍。例如,[32,32,32] 表示RGB三個channels皆切分為32區段
float h_ranges[] = { 0, 180 }, s_ranges[] = { 0, 256 };
const float* ranges[] = { h_ranges, s_ranges };
int channels[] = { 0, 1 };
Mat roiHist;//計算ROI的直方圖
calcHist(&model_hsv, 1, channels, Mat(), roiHist, 2, histSize, ranges);
normalize(roiHist, roiHist, 0, 255, NORM_MINMAX, -1, Mat());
Mat roiproj, backproj;
calcBackProject(&image_hsv, 1, channels, roiHist, roiproj, ranges);//使用反向投影 產生ROI(前景)的mask
bitwise_not(roiproj, backproj);//產生背景的mask
imshow("ROIProj", roiproj);
imshow("BackProj", backproj);
}
void showHistogram(InputArray src, cv::String StrTitle)//直方圖
{
bool blnGray = false;
if (src.channels() == 1)
{
blnGray = true;
}
// 三通道/單通道 直方圖 紀錄陣列
vector<Mat> bgr_plane;
vector<Mat> gray_plane;
// 定义参数变量
const int channels[1] = { 0 };
const int bins[1] = { 256 };
float hranges[2] = { 0, 255 };
const float *ranges[1] = { hranges };
Mat b_hist, g_hist, r_hist, hist;
// 计算三通道直方图
/*
void calcHist( const Mat* images, int nimages,const int* channels, InputArray mask,OutputArray hist, int dims, const int* histSize,const float** ranges, bool uniform=true, bool accumulate=false );
1.輸入的圖像數組
2.輸入數組的個數
3.通道數
4.掩碼
5.直方圖
6.直方圖維度
7.直方圖每個維度的尺寸數組
8.每一維數組的範圍
9.直方圖是否是均勻
10.配置階段不清零
*/
if (blnGray)
{
split(src, gray_plane);
calcHist(&gray_plane[0], 1, 0, Mat(), hist, 1, bins, ranges);
}
else
{
split(src, bgr_plane);
calcHist(&bgr_plane[0], 1, 0, Mat(), b_hist, 1, bins, ranges);
calcHist(&bgr_plane[1], 1, 0, Mat(), g_hist, 1, bins, ranges);
calcHist(&bgr_plane[2], 1, 0, Mat(), r_hist, 1, bins, ranges);
}
/*
* 显示直方图
*/
int hist_w = 512;
int hist_h = 400;
int bin_w = cvRound((double)hist_w / bins[0]);
Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
// 归一化直方图数据
if (blnGray)
{
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1);
}
else
{
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1);
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1);
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1);
}
// 绘制直方图曲线
for (int i = 1; i < bins[0]; ++i)
{
if (blnGray)
{
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(hist.at<float>(i))), Scalar(255, 255, 255),
2, 8, 0);
}
else
{
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0),
2, 8, 0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0),
2, 8, 0);
line(histImage, Point(bin_w * (i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w * (i), hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255),
2, 8, 0);
}
}
imshow(StrTitle, histImage);
}
★Python
import cv2 as cv
def pyramid_down(pyramid_images):
level = len(pyramid_images)
print("level = ",level)
for i in range(level-1, -1, -1):
expand = cv.pyrUp(pyramid_images[i])
cv.imshow("pyramid_down_"+str(i), expand)
def pyramid_up(image, level=3):
temp = image.copy()
# cv.imshow("input", image)
pyramid_images = []
for i in range(level):
dst = cv.pyrDown(temp)
pyramid_images.append(dst)
# cv.imshow("pyramid_up_" + str(i), dst)
temp = dst.copy()
return pyramid_images
src = cv.imread("D:/images/master.jpg")
cv.namedWindow("input", cv.WINDOW_AUTOSIZE)
cv.imshow("input", src)
# pyramid_up(src)
pyramid_down(pyramid_up(src))
cv.waitKey(0)
cv.destroyAllWindows()
★結果圖:

★延伸說明/重點回顧:
4 thoughts on “jashliao 用 VC++ 實現 fanfuhan OpenCV 教學037 ~ opencv-037-高斯金字塔圖像(pyrUp / pyrDown)”
高斯金字塔的作用
https://blog.csdn.net/weixin_38208741/article/details/78081810
整個高斯金字塔,或者說是差分高斯金字塔是我們確定SIFT特徵的基礎,
讓我們首先想想高斯金字塔到底乾了一件什麼事情,他到底模仿的是什麼?
答案很容易確定,高斯金字塔模仿的是圖像的不同的尺度,
尺度應該怎樣理解?對於一副圖像,你近距離觀察圖像,與你在一米之外觀察,看到的圖像效果是不同的,前者比較清晰,後者比較模糊,前者比較大,後者比較小,
通過前者能看到圖像的一些細節信息,通過後者能看到圖像的一些輪廓的信息,這就是圖像的尺度,圖像的尺度是自然存在的,並不是人為創造的。
好了,到這裡我們明白了,其實以前對一幅圖像的處理還是比較單調的,因為我們的關注點只落在二維空間,並沒有考慮到“圖像的縱深”這樣一個概念,
如果將這些內容考慮進去我們是不是會得到更多以前在二維空間中沒有得到的信息呢?於是高斯金字塔橫空出世了,它就是為了在二維圖像的基礎之上,榨取出圖像中自然存在的另一個維度:尺度。
因為高斯核是唯一的線性核,也就是說使用高斯核對圖像模糊不會引入其他噪聲,因此就選用了高斯核來構建圖像的尺度
高斯金字塔:
https://zhuanlan.zhihu.com/p/32815143
背景知識:
我們都知道,在處理圖像的過程中,由於圖像中某個像素與相鄰像素之間的有很強的相關性,即不管是從紋理還是從灰度級都很相似(CRF的性質,個人理解:一副圖像應該就可以看做是一個CRF吧?)
△如果物體的尺寸很小或者說對比度不高,通常則需要採用較高的分辨率來觀察。
△如果物體的尺寸很大或者說對比度很強,那麼就僅僅需要較低的分辨率就能夠來傳觀了。
△那如果現在物體的尺寸有大有小,對比度有強有弱,這些關係同時存在,這個時候我們該採用何種分辨率呢?
沒錯,這個時候,只能上多分辨率處理了。
為什麼要提出尺度空間?
https://www.jianshu.com/p/cefbdc2dc5b9
在現實世界中,物體在不同尺度下,有著不同的結構。這就表明,我們如果從不同的尺度去觀察同一個物體,會得出不一樣的結果。比如,觀察一棵樹的適當尺度應該是“米”,而觀察一片葉子可能需要更細粒度的尺度才能得出較好的結果。當計算機系統要對一個未知的場景進行分析時,並不能夠提前預知要用什麼樣的尺度來對圖像信息中的興趣結構(interesting structures)進行描述才是最合適的。因此,唯一可行的方案就是將多個不同尺度的描述都考慮進來,以便捕獲未知的尺度變化。
尺度空間理論和生物視覺之間也有著十分密切的聯繫。哺乳動物的視網膜以及視覺皮層第一階段所記錄的接受場的分佈,與許多尺度空間操作都高度近似。
自己認知紀錄:
圖像金字塔(高斯圖像金字塔) 是應用在分類器上
應用在 機器學習的非監督式學習