【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);
}

结果如下:

相关推荐
爱吃西瓜的小菜鸡17 分钟前
【C语言】判断回文
c语言·学习·算法
小A15940 分钟前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
qq_589568101 小时前
数据可视化echarts学习笔记
学习·信息可视化·echarts
兔C2 小时前
微信小程序的轮播图学习报告
学习·微信小程序·小程序
海海不掉头发2 小时前
苍穹外卖-day05redis 缓存的学习
学习·缓存
小木_.3 小时前
【Python 图片下载器】一款专门为爬虫制作的图片下载器,多线程下载,速度快,支持续传/图片缩放/图片压缩/图片转换
爬虫·python·学习·分享·批量下载·图片下载器
小陈phd3 小时前
OpenCV学习——图像融合
opencv·计算机视觉·cv
一棵开花的树,枝芽无限靠近你3 小时前
【PPTist】组件结构设计、主题切换
前端·笔记·学习·编辑器