图像金字塔,原理、实现及应用
什么是图像金字塔
图像金字塔是对图像的一种多尺度表达,将各个尺度的图像按照分辨率从小到大,依次从上到下排列,就会形成类似金字塔的结构,因此称为图像金字塔。
常见的图像金字塔有两类,一种是高斯金字塔(Gaussian Pyramid),另一种的拉普拉斯金字塔(Laplacian Pyramid)。
一般在图像处理中,高斯代表“模糊”,而拉普拉斯代表“差异”。
高斯金字塔通过不断对图像进行模糊且下采样而获得,下采样的因子一般是2倍。随着分辨率越来越小,图像会越来越模糊,高斯金字塔的最底层就是原始图像本身。
拉普拉斯金字塔在高斯金字塔的基础上,对所有层进行上采样(一般也是2倍上采样),然后使用原高斯金字塔结果减去通分辨率的上采样结果得到每一层差异,即为拉普拉斯金字塔。注意拉普拉斯金字塔中分辨率最小的图片等同于高斯金字塔通分辨率图片,其他层均为“求差”得到的结果。另外还需注意图像先下采样再上采样后不能复原,因为下采样会产生信息缺失,简单上采样无法弥补回这些信息缺失。
为什么要使用图像金字塔
图像金字塔有很多应用,特别是面对多尺度任务时尤为有用。比如在目标检测任务中,检测对象在图像中的大小往往非常多变,在单一图像尺度下进行滑框寻找往往不能覆盖所有目标,所以就需要在多个尺度下进行滑框,传统的目标检测和基于深度学习的目标检测均是如此。
拉普拉斯金字塔中,大部分的数值接近于0,所以一定程度上可以用于图像压缩。拉普拉斯金字塔还常用于图像融合,基于拉普拉斯金字塔的图像融合,融合边界的过渡往往会相对自然一些。
金字塔的构建
基础函数
-
模糊(卷积)
在金字塔的构建中,上下采样均需要做模糊,下采样中做模糊是为了防止锯齿现象,上采样中做模糊是因为图像金字塔分解中的上采样比较“特别”,不做模糊不行。这里一般使用一个固定的5x5卷积核做模糊,也可以1x5的卷积核,使用行列分离的卷积方法做模糊。 -
下采样
先对图像做模糊,然后直接每隔一个像素抽一个数据即可实现2倍下采样。 -
上采样
将每个像素扩展成2x2的小区域,原像素放在左上角,其他3个位置补0,然后将卷积核乘以4,再对扩展后的图像做模糊即可。
上采样还需注意一个关于数据类型的细节:拉普拉斯金字塔才需要用到上采样,生成拉普拉斯金字塔的过程中需要求差操作,并且拉普拉斯金字塔常常跟图像重建会扯上关系,而uint8在求差或者重建时会引起数据截断误差,所以有可能需要用到非uint8数据类型来作为输出。
特别注意:通过上面描述可以发现,在图像金字塔构建时,上下采样的操作非常简单粗暴,不需要用到常规resize时的图像插值。
代码如下:
文件起名resample.py
# -*- coding: utf-8 -*-
import cv2
import numpy as npdef blur(image, kernel_scale=1.0):"""Blur image using a fixed kernel. Kernel scale can be set.Parameters----------image: image data read by opencv.kernel_scale: the scale factor of kernel."""blur_kernel = np.array([[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]]) / 256.blurred_image = cv2.filter2D(image, ddepth=-1,kernel=blur_kernel * kernel_scale,borderType=cv2.BORDER_REFLECT101)return blurred_imagedef pyramid_down(image):"""Down sample an image by 2x.Parameters----------image: image data read by opencv."""blurred_image = blur(image)image_down = blurred_image[::2, ::2]return image_downdef pyramid_up(image, dst_size=None, dtype=np.uint8):"""Up sample an image by 2x. The output size and data type can be set.Parameters----------image: image data read by opencv.dst_size: the output size. Note that the difference of dst_size and2*image_size should be <=2.dtype: the output data type."""# check dst_sizeheight, width = image.shape[:2]if dst_size is None:dst_size = (width * 2, height * 2)else:if abs(dst_size[0] - width * 2) > 2 or \abs(dst_size[1] - height * 2) > 2:raise ValueError(r'the difference of dst_size and 2*image_size 'r'should be <=2.')# create a new buffer that has the dst_sizedst_width, dst_height = dst_sizeif image.ndim == 2:image_up = np.zeros(shape=(dst_height, dst_width), dtype=dtype)else:channel = image.shape[2]image_up = np.zeros(shape=(dst_height, dst_width, channel),dtype=dtype)image_up[::2, ::2] = imageimage_up = blur(image_up, 4.0)return image_up
高斯金字塔 & 拉普拉斯金字塔
高斯金字塔的实现非常简单,不断地使用pyramid_down进行下采样即可,没什么特别需要注意的。
拉普拉斯金字塔需要注意一些事项:1、在生成拉普拉斯金字塔的过程中需要求差,为了不引起数据类型的截断误差, 需要把待求差的两个变量的类型先从uint8转为float32,然后再做求差操作。2、由拉普拉斯金字塔重建图像的过程如果使用uint8也容易产生截断误差,所以在做加法时也需要转float32。
代码如下:
文件起名pyramid.py
# -*- coding: utf-8 -*-
import numpy as np
from resample import pyramid_down
from resample import pyramid_updef image_to_gaussian_pyramid(image, level, cut_size=(3, 3)):"""Build gaussian pyramid for an image. The size of the output component isarranged in descending order.Parameters----------image: input image data read by opencv.level: level of output pyramid.cut_size: the minimal size of pyramid component, smaller than which thebuilding process will be stopped."""gaussian_pyramid = [image]if level <= 1:return gaussian_pyramidfor i in range(level - 1):# check down-sampled image size, should be >= cut_sizeheight, width = image.shape[:2]height_down = (height + 1) // 2width_down = (width + 1) // 2if width_down < cut_size[0] or height_down < cut_size[1]:break# down sampleimage = pyramid_down(image)gaussian_pyramid.append(image)return gaussian_pyramiddef gaussian_to_laplacian_pyramid(gaussian_pyramid):"""Build a laplacian pyramid from gaussian pyramid. The size of the outputcomponent is arranged in ascending order."""laplacian_pyramid = [gaussian_pyramid[-1]]level = len(gaussian_pyramid)if level == 1:return laplacian_pyramidfor i in range(level - 1, 0, -1):up_size = gaussian_pyramid[i - 1].shape[:2][::-1]image_up = pyramid_up(gaussian_pyramid[i], up_size)# compute difference, use float type to avoid exceeding uint8 limitdiff = np.float32(gaussian_pyramid[i - 1]) - np.float32(image_up)laplacian_pyramid.append(diff)return laplacian_pyramiddef image_to_laplacian_pyramid(image, level, cut_size=(3, 3)):"""Build a laplacian pyramid from an image. The size of the output componentis arranged in an ascending order.Parameters----------image: input image data read by opencv.level: level of output pyramid.cut_size: the minimal size of pyramid component, smaller than which thebuilding process will be stopped."""gaussian_pyramid = image_to_gaussian_pyramid(image, level, cut_size)laplacian_pyramid = gaussian_to_laplacian_pyramid(gaussian_pyramid)return laplacian_pyramiddef laplacian_pyramid_to_image(laplacian_pyramid):"""Reconstruct an image from laplacian pyramid."""image = laplacian_pyramid[0]level = len(laplacian_pyramid)for i in range(1, level):up_size = laplacian_pyramid[i].shape[:2][::-1]image = pyramid_up(image, up_size, np.float32)image = np.float32(image) + laplacian_pyramid[i]image = np.uint8(np.clip(np.round(image), 0, 255))return imagedef get_pyramid_index(original_index, level):"""Get the index of a certain pyramid component corresponding to an index oforiginal imageParameters----------original_index: the index of original image.level: level for pyramid component."""if level < 0:raise ValueError("level can NOT be less than 0")if level == 0:return original_indexbase = 2 ** levelmod = original_index % baseif base == 2 * mod:# decimal part is 0.5return int(round(original_index / base / 2)) * 2else:return int(round(original_index / base))
以下代码是金字塔分解的demo示例。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pyramid import image_to_gaussian_pyramid
from pyramid import image_to_laplacian_pyramiddef gaussian_pyramid_test():level = 5image = cv2.imread(r'undead.png')height, width, channel = image.shapegau_pyr = image_to_gaussian_pyramid(image, level)# plotoutput = np.zeros((height, width * 2, channel), dtype=np.uint8)x = 0for i in range(level):height, width = gau_pyr[i].shape[:2]output[:height, x:x + width] = gau_pyr[i]x += widthcv2.imwrite('gaussian_pyramid_test.png', output)def laplacian_pyramid_test():level = 5image = cv2.imread(r'undead.png')height, width, channel = image.shapelap_pyr = image_to_laplacian_pyramid(image, level)# plotoutput = np.zeros((height, width * 2, channel), dtype=np.float32)x = width * 2for i in range(level - 1, -1, -1):height, width = lap_pyr[i].shape[:2]if i == 0:output[:height, x - width:x] = lap_pyr[i]else:output[:height, x - width:x] = lap_pyr[i] * 10x -= widthcv2.imwrite('laplacian_pyramid_test.png', output)if __name__ == '__main__':gaussian_pyramid_test()laplacian_pyramid_test()
得到的结果如下,分别是5层的高斯金字塔和拉普拉斯金字塔,注意拉普拉斯金字塔除了最小分辨率是正常图像外,其他分量均为残差。为了能够把残差看的更清楚,乘以了一个系数。


金字塔的应用
图像融合
这张图打魔兽的伙计们应该都挺熟的,这是阿尔萨斯在巫妖王形态和人类王子形态的融合体。

下面我分别找到了原始巫妖王形态和人类王子形态的图像,并作了一些裁减,让图像分辨率一样,并且上下位置也大概能拼在一起。


下面图像是直接对上述两个原始图像各切掉一部分,并拼接在一起的结果,可以看到整体过渡是不自然的。

下面我们将使用金字塔重建的方法进行拼接,让边缘的过渡自然一些。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pyramid import image_to_laplacian_pyramid
from pyramid import laplacian_pyramid_to_image
from pyramid import get_pyramid_indexif __name__ == '__main__':x1 = 160x2 = 154level = 5undead = cv2.imread(r'undead.png')human = cv2.imread(r'human.png')h1, w1 = undead.shape[:2]h2, w2 = human.shape[:2]laplacian_pyramid_undead = image_to_laplacian_pyramid(undead, level)laplacian_pyramid_human = image_to_laplacian_pyramid(human, level)laplacian_pyramid_blending = []for i in range(level):k = level - i - 1k1 = get_pyramid_index(w1 - x1, k)k2 = get_pyramid_index(x2, k)a = laplacian_pyramid_undead[i]b = laplacian_pyramid_human[i]splicing = np.concatenate([a[:, :k1], b[:, k2:]], axis=1)laplacian_pyramid_blending.append(splicing)blending_image = laplacian_pyramid_to_image(laplacian_pyramid_blending)cv2.imwrite('laplacian_pyramid_blending.png', blending_image)
上面代码的大体流程:1、两张图分别做拉普拉斯金字塔分解;2、把金字塔的各层分量都从鼻子中间拼接在一起;3、对拼接后的金字塔进行重建,即可得到输出。
拼接后的金字塔以及拼接结果如下:

下面是5层金字塔做拼接融合的结果。除了嘴部的过渡实在搞不定以外,其他部分如鼻子,额头和下巴的过渡比直接拼接的效果顺滑很多。

下面是3层金字塔做拼接融合的结果。过渡的顺滑性显然就不如5层金字塔。

相关文章:
图像金字塔,原理、实现及应用
什么是图像金字塔 图像金字塔是对图像的一种多尺度表达,将各个尺度的图像按照分辨率从小到大,依次从上到下排列,就会形成类似金字塔的结构,因此称为图像金字塔。 常见的图像金字塔有两类,一种是高斯金字塔࿰…...
08-Oracle游标管理(定义,打开、获取数据及关闭游标)
目标 1.确定何时需要显示游标2.声明、打开和关闭显示游标3.从显示游标中提取数据4.了解与游标有关的属性5.使用游标FOR循环检索游标中的数据6.在游标FOR循环的子查询中声明游标7.评估使用逻辑运算符结合在一起的布尔条件游标 1、在使用一个PL/SQL块来执行DML语句或只返回一行结…...
Python判断字符串是否包含特定子串的7种方法
目录1、使用 in 和 not in2、使用 find 方法3、使用 index 方法4、使用 count 方法5、通过魔法方法6、借助 operator7、使用正则匹配转自:https://cloud.tencent.com/developer/article/1699719我们经常会遇这样一个需求:判断字符串中是否包含某个关键词…...
aop实现接口访问频率限制
引言 项目开发中我们有时会用到一些第三方付费的接口,这些接口的每次调用都会产生一些费用,有时会有别有用心之人恶意调用我们的接口,造成经济损失;或者有时需要对一些执行时间比较长的的接口进行频率限制,这里我就简…...
Hive---窗口函数
Hive窗口函数 其他函数: Hive—Hive函数 文章目录Hive窗口函数开窗数据准备建表导入数据聚合函数window子句LAG(col,n,default_val) 往前第 n 行数据LEAD(col,n, default_val) 往后第 n 行数据ROW_NUMBER() 会根据顺序计算RANK() 排序相同时会重复,总数不会变DENSE…...
JavaSe第7次笔记
1. C语言里面,NULL是0地址。Java中null和0地址没关系。 2.数组可以做方法的返回值。 3.可以使用变量作为数组的个数开辟空间。 4.断言assert,需要设置。 5.排序:Arrays. sort(array); 6.查找: int index Arrays. binarySea…...
什么是 Service 以及描述下它的生命周期。Service 有哪些启动方法,有 什么区别,怎样停用 Service?
在 Service 的生命周期中,被回调的方法比 Activity 少一些,只有 onCreate, onStart, onDestroy, onBind 和 onUnbind。 通常有两种方式启动一个 Service,他们对 Service 生命周期的影响是不一样的。 1. 通过 startService Service 会经历 onCreate 到 onStart,然后处于运行…...
Redis部署
JAVA安装 mkdir /usr/local/javacd /usr/local/java/wget --no-check-certificate --no-cookies --header "Cookie: oraclelicenseaccept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u13…...
AT32F437制作Bootloader然后实现Http OTA升级
首先创建一个AT32F437的工程,然后发现调试工程配置这里的型号和创建工程选的型号不一致,手动更改一下,使用PW Link下载程序的话还要配置一下pyocd.exe的路径。 打开drv_clk.c文件的调试功能看下系统时钟频率。 项目使用的是AT32F437VMT7芯片&…...
Springboot项目启动初始化数据缓存
1.从Java EE5规范开始,Servlet中增加了两个影响Servlet生命周期的注解, PostConstruct和PreDestroy,这两个注解被用来修饰一个非静态的void()方法,被PostConstruct修饰的方法会在服务器加载Servlet的时候运…...
深度学习必备知识——模型数据集Yolo与Voc格式文件相互转化
在深度学习中,第一步要做的往往就是处理数据集,尤其是学习百度飞桨PaddlePaddle的小伙伴,数据集经常要用Voc格式的,比如性能突出的ppyolo等模型。所以学会数据集转化的本领是十分必要的。这篇博客就带你一起进行Yolo与Voc格式的相互转化&…...
数据、数据资源及数据资产管理的区别
整理不易,转发请注明出处,请勿直接剽窃! 点赞、关注、不迷路! 摘要:数据、数据资源、数据资产 数据、数据资源及数据资产的区别 举例 CRM系统建设完成后会有很多数据,这些数据就是原始数据,业务…...
标度不变性(scale invariance)与无标度(scale-free)概念辨析
文章目录标度标度种类名义标度序级标度等距标度比率标度常用标度方法不足标度不变性标度不变(Scale-invariant)曲线和自相似性(self-similarity)射影几何分形随机过程中的标度不变性标度不变的 Tweedie distribution普适性&#x…...
WMS仓库管理系统解决方案,实现仓库管理一体化
仓库是企业的核心环节,若没有对库存的合理控制和送货,将会造成成本的上升,服务品质的难以得到保证,进而降低企业的竞争能力。WMS仓库管理系统包括基本信息,标签,入库,上架,领料&…...
css常见定位、居中方案_css定位居中
一、 定位分类 1、静态定位 position:static;(默认,具备标准流条件) 2、相对定位 position:relative; 通过 top 或者 bottom 来设置 Y 轴位置 通过 left 或者 right 来设置 X 轴位置 特点: 相对定位不会脱离文档流相对于自…...
【微信小程序】-- 自定义组件 -- 创建与引用 样式(三十二)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...
ArangoDB——AQL编辑器
AQL 编辑器 ArangoDB 的查询语言称为 AQL。AQL与关系数据库管理系统 (RDBMS)区别在于其更像一种编程语言,更自然地适合无模式模型,并使查询语言非常强大,同时保持易于读写。数据建模概念 数据库是集合的集合。集合存储记录,称为文…...
Lesson 9.1 集成学习的三大关键领域、Bagging 方法的基本思想和 RandomForestRegressor 的实现
文章目录一、 集成学习的三大关键领域二、Bagging 方法的基本思想三、RandomForestRegressor 的实现在开始学习之前,先导入我们需要的库,并查看库的版本。 import numpy as np import pandas as pd import sklearn import matplotlib as mlp import sea…...
basic1.0链码部署(基于test-network 环境ubuntu20.04腾讯云)
解决了官方示例指令需要科学上网才能运行的问题(通过手动下载二进制文件和拉取官方fabric-samples)。具体的将bootstrap.sh脚本解读了一遍 具体可以参照我的博客 fabric中bootstrap.sh到底帮助我们干了什么?(curl -sSL https://bi…...
Android---系统启动流程
目录 Android 系统启动流程 init 进程分析 init.rc 解析 Zygote 概叙 Zygote 触发过程 Zygote 启动过程 什么时Runtime? System Server 启动流程 Fork 函数 总结 面试题 Android 是 google 公司开发的一款基于 Linux 的开源操作系统。 Android 系统启动…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
