【OpenCV C++20 学习笔记】图像缩放-高斯金字塔

图像缩放-高斯金字塔

原理

在图像处理中,经常需要将图像转化成不同的尺寸,即放大或缩小。

除了直接用resize()函数重新设置图片尺寸,另一种常用的方法就是"图像金字塔 "。

图像金字塔是从底层的原始图像开始,通过层层放大叠加而成的金字塔形状的图像集合。

主要有两种图像金字塔:

  • 高斯金字塔:常用来生成图片的缩小版本
  • 拉普拉斯金字塔:常用来将图片的缩小版本放大

高斯金字塔

用图片来表示高斯金字塔会更形象:

图层越高,图像越小。

图像的编号又下往上依次增大。从下往上数第 i + 1 i+1 i+1个图层,记为 G i G_{i} Gi;则第 i + 2 i+2 i+2个图层 G i + 1 G_{i+1} Gi+1比 G i G_i Gi小。

要生成 G i + 1 G_{i+1} Gi+1就要对 G i G_i Gi进行如下操作:

  1. 用高斯卷积核 K K K对 G i G_i Gi进行卷积运算;
    K = 1 256 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] K=\frac{1}{256} \begin{bmatrix} 1 & 4 & 6 & 4 & 1 \\ 4 & 16 & 24 &16 & 4 \\ 6 & 24 & 36 & 24 & 6 \\ 4 & 16 & 24 & 16 & 4 \\ 1 & 4 & 6 & 4 & 1 \end{bmatrix} K=2561 1464141624164624362464162416414641
  2. 移除所有的偶数行和偶数列
    很显然,最后的结果会变成原来图片的1/4。将这个操作迭代地运用在原图 G 0 G_0 G0上,就会形成上图所示的金字塔。
    其实高斯金字塔方法也可以用来放大图片。只要进行以下操作:
  3. 将行和列都扩大到原来的2倍;
  4. 将上面的卷积核 K K K乘以4之后近似地模拟出填充值来填充扩大出来的行和列

注意:缩小图片实际上丢失了一部分原图的信息。

代码实现

这里使用的图片是OpenCV安装文件中自带的示例图,其路径为:"...\opencv\sources\samples\data\chicky_512.png"

放大

在OpenCV中可以pyrUp()函数来进行高斯金字塔中的放大操作。其函数原型如下:

cpp 复制代码
void cv::pyrUp(InputArray	src,
			OutputArray		dst,
			const Size& 	dstsize = size(),
			int				borderType = BORDER_DEFAULT)

第1个和第2个参数不用介绍了,分别是输入矩阵和输出矩阵。

第3个参数为放大后的尺寸,默认为Size(src.cols*2, src.rows*2),即行和列都放大为原来的2倍。但是如果用户要自定义放大倍数的话,需要满足以下条件:
{ ∣ 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 ) ∣ 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 ) \begin{cases} |dstsize.width - src.cols*2| \leq (dstsize.width mod 2) \\ |dstsize.height - src.rows*2| \leq (dstsize.height mod 2) \end{cases} {∣dstsize.width−src.cols∗2∣≤(dstsize.widthmod2)∣dstsize.height−src.rows∗2∣≤(dstsize.heightmod2)

乍一看有点复杂,我们来仔细分析一下:

首先看两个不等式的右边,因为图片的宽和高一般都是整数,所以无论是宽还是高,除以2的余数都只有两种情况:0或1。

  • 不等式右边为0:说明放大后的图片的宽或高是偶数,且等式左边必须小于等于0;又因为不等式左边为一个绝对值的结果,那么只有一种可能------不等式左边等于0,即 d s t s i z e . w i d t h = s r c . c o l s ∗ 2 dstsize.width = src.cols*2 dstsize.width=src.cols∗2或 d s t s i z e . h e i g h t = s r c . r o w s ∗ 2 dstsize.height = src.rows*2 dstsize.height=src.rows∗2,也就是这个参数的默认情况。
  • 不等式右边为1:说明放大的图片的宽或高是奇数,且等式左边必须小于等于1;因为奇数减偶数只能等于奇数,所以等式左边的结果只能为1,即只能是 s r c . c o l s ∗ 2 + 1 src.cols * 2 + 1 src.cols∗2+1或者 s r c . r o w s ∗ 2 + 1 src.rows*2+1 src.rows∗2+1。

综上所述,第3个参数只能有两种:

  1. 宽和高都放大为原来的2倍,即默认情况
  2. 宽和高都放大为原来的2倍加1。

为什么有这种限制?答案在最后一节。

第4个参数基本使用默认值,先不用管它。

我们看看宽和高都放大为原来的宽和高的2倍的具体实现:

cpp 复制代码
pyrUp(src,							//原图
	up_dst,							//输出图
	Size(src.cols*2, src.rows*2));	//放大后的尺寸:原图的2*2

一次放大后的效果:

缩小

类似的,可以使用pryDown()函数来缩小图片。这个函数的参数与pryUp()相同,但第3个参数,即缩小后的尺寸的具体条件不一样。

pryDown()中,第3个参数dstsize默认值为Size((src.cols+1)/2, (src.rows+1)/2),即原图的宽和高加1的一半。如果用户要自定义缩小尺寸,需要满足以下条件:
{ ∣ d s t s i z e . w i d t h ∗ 2 − s r c . c o l s ∣ ≤ 2 ∣ d s t s i z e . h e i g h t ∗ 2 − s r c . r o w s ∣ ≤ 2 \begin{cases} |dstsize.width*2 - src.cols| \leq 2 \\ |dstsize.height*2 - src.rows| \leq 2 \end{cases} {∣dstsize.width∗2−src.cols∣≤2∣dstsize.height∗2−src.rows∣≤2

这也就是说缩小之后的图片的宽和高的2倍不能与原来的宽高相差2以上,即缩小后的图片的尺寸只有这些选择:

  1. 原图的宽高的一半,此时不等式左边就是0
  2. 原图的宽高加1的一半,即默认情况,不等式左边为1
  3. 原图的宽高加2的一半,此时不等式左边为2

为什么有这种限制?答案在最后一节。

第1中情况的具体用法如下:

cpp 复制代码
pryDown(src,							//原图
		down_dst,						//输出图
		Size( src.cols/2, src.rows/2));	//缩小后的尺寸:原图的(1/2)*(1/2)

原图缩小一次的效果如下:

形成金字塔

要形成金字塔不能只进行单次缩放的操作,需要进行迭代。

正是因为要迭代,所以前面才会对pyrUp()pyrDown()函数的第3个参数有那么严格的限制。如果缩放的尺寸没有限制,那可以1次迭代就放大100或缩小100倍,那很有可能就没有进行第2次迭代的必要了。要形成有意义的金字塔,缩放的比例必须要控制在一个相对较小的范围内,而这两个函数都将比例限制在了2或1/2左右。

如果我们将迭代次数限定为5次,我们可以进行这样的缩小图片的迭代:

cpp 复制代码
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

using namespace cv;
using namespace std;

int main() {
	Mat src{ imread("chicky_512.png") };

	//缩小迭代
	String winName{ "缩小" };
	for (int i{ 0 }; i < 5; i++) {
		imshow(winName+to_string(i), src);
		pyrDown(src,
			src,
			Size(src.cols / 2, src.rows / 2));
	}
	waitKey(0);
}

结果如下:

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习