图像金字塔,原理、实现及应用

news2024/12/27 1:22:25

什么是图像金字塔

图像金字塔是对图像的一种多尺度表达,将各个尺度的图像按照分辨率从小到大,依次从上到下排列,就会形成类似金字塔的结构,因此称为图像金字塔。

常见的图像金字塔有两类,一种是高斯金字塔(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 np


def 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_image


def 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_down


def 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 and
        2*image_size should be <=2.
    dtype: the output data type.
    """
    # check dst_size
    height, 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_size
    dst_width, dst_height = dst_size
    if 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] = image
    image_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_up


def image_to_gaussian_pyramid(image, level, cut_size=(3, 3)):
    """
    Build gaussian pyramid for an image. The size of the output component is
    arranged 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 the
        building process will be stopped.
    """
    gaussian_pyramid = [image]
    if level <= 1:
        return gaussian_pyramid

    for i in range(level - 1):
        # check down-sampled image size, should be >= cut_size
        height, width = image.shape[:2]
        height_down = (height + 1) // 2
        width_down = (width + 1) // 2
        if width_down < cut_size[0] or height_down < cut_size[1]:
            break
        # down sample
        image = pyramid_down(image)
        gaussian_pyramid.append(image)
    return gaussian_pyramid


def gaussian_to_laplacian_pyramid(gaussian_pyramid):
    """
    Build a laplacian pyramid from gaussian pyramid. The size of the output
    component is arranged in ascending order.
    """
    laplacian_pyramid = [gaussian_pyramid[-1]]
    level = len(gaussian_pyramid)
    if level == 1:
        return laplacian_pyramid

    for 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 limit
        diff = np.float32(gaussian_pyramid[i - 1]) - np.float32(image_up)
        laplacian_pyramid.append(diff)
    return laplacian_pyramid


def image_to_laplacian_pyramid(image, level, cut_size=(3, 3)):
    """
    Build a laplacian pyramid from an image. The size of the output component
    is 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 the
        building process will be stopped.
    """
    gaussian_pyramid = image_to_gaussian_pyramid(image, level, cut_size)
    laplacian_pyramid = gaussian_to_laplacian_pyramid(gaussian_pyramid)
    return laplacian_pyramid


def 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 image


def get_pyramid_index(original_index, level):
    """
    Get the index of a certain pyramid component corresponding to an index of
    original image

    Parameters
    ----------
    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_index

    base = 2 ** level
    mod = original_index % base

    if base == 2 * mod:
        # decimal part is 0.5
        return int(round(original_index / base / 2)) * 2
    else:
        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_pyramid


def gaussian_pyramid_test():
    level = 5
    image = cv2.imread(r'undead.png')
    height, width, channel = image.shape
    gau_pyr = image_to_gaussian_pyramid(image, level)

    # plot
    output = np.zeros((height, width * 2, channel), dtype=np.uint8)
    x = 0
    for i in range(level):
        height, width = gau_pyr[i].shape[:2]
        output[:height, x:x + width] = gau_pyr[i]
        x += width
    cv2.imwrite('gaussian_pyramid_test.png', output)


def laplacian_pyramid_test():
    level = 5
    image = cv2.imread(r'undead.png')
    height, width, channel = image.shape
    lap_pyr = image_to_laplacian_pyramid(image, level)

    # plot
    output = np.zeros((height, width * 2, channel), dtype=np.float32)
    x = width * 2
    for 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] * 10
        x -= width
    cv2.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_index

if __name__ == '__main__':
    x1 = 160
    x2 = 154
    level = 5

    undead = 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 - 1
        k1 = 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层金字塔。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/401568.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么越来越多的人开始学习大数据

因为根据国内的发展形势&#xff0c;大数据未来的发展前景会非常好&#xff0c;前景好需求高&#xff0c;自然会吸引越来越多的人进入大数据行业 我国市场环境处于急需大数据人才但人才不足的阶段&#xff0c;所以未来大数据领域会有很多的就业机遇。 2022年春季&#xff0c;…

camunda流程引擎基本使用(笔记)

文章目录一、camunda基础1.1 安装与部署流程引擎1.2 流程引擎结构1.3 流程引擎的基本使用1.3.1 创建一个BPMN Diagram1.3.2 实现一个外部工作者1.3.3 部署流程1.3.4 创建一个流程实例并消费1.3.5 向流程中添加用户任务1.3.6 添加网关1.3.7 业务规则二、Java 集成流程引擎2.1 为…

酷开科技大数据揭秘!酷开系统中的千屏千面究竟指的是什么?

互联网行业的快速发展&#xff0c;给我们带来了极大的便利。回顾整个互联网行业的发展历程&#xff0c;从PC时代到移动互联网时代&#xff0c;从移动互联网时代到物联网时代&#xff0c;现在又即将从物联网时代迈入人工智能时代。这些飞速发展的背后&#xff0c;其实是对数据利…

ICG-alkyne,吲哚菁绿-炔基结构式,实验室科研试剂,CAS号:1622335-41-4

ICG-alkyne,吲哚菁绿-炔基 中文名称&#xff1a;吲哚菁绿-炔基 CAS号&#xff1a;1622335-41-4 英文名称&#xff1a;ICG-alkyne 英文别名&#xff1a;ICG-alk 性状&#xff1a;绿色粉末 化学式&#xff1a;C48H53N3O4S 分子量&#xff1a;768.03 溶剂&#xff1a;溶于…

3BHE029110R0111 ABB

3BHE029110R0111 ABB变频器控制方式低压通用变频输出电压为380&#xff5e;650V&#xff0c;输出功率为0.75&#xff5e;400kW&#xff0c;工作频率为0&#xff5e;400Hz&#xff0c;它的主电路都采用交—直—交电路。其控制方式经历了以下四代。1U/fC的正弦脉宽调制&#xff0…

ggplot2的组图拓展包(1):patchwork(中篇)

专注系列化、高质量的R语言教程推文索引 | 联系小编 | 付费合集上篇和上篇续介绍了使用操作符进行组图的方法&#xff0c;这里默认读者已经能够理解各种操作符在本篇推文中的使用场景。本篇目录如下&#xff1a;0 示例图形6 plot_layout函数&#xff08;下&#xff09;6.1 guid…

CSS3-数据可视化

2D动画 - transform CSS3 transform属性允许你旋转&#xff0c;缩放&#xff0c;倾斜或平移给定元素。 Transform是形变的意思&#xff08;通常也叫变换&#xff09;&#xff0c;transformer就是变形金刚 常见的函数transform function有&#xff1a; 平移&#xff1a;transl…

实际开发中如何存储密码(md5加盐bcrypt)golang

文章目录简介加盐的加密方式md5 加盐方式bcrypt 方式简介 一般前端把用户密码发给服务端&#xff0c;服务端实际业务中如何存储密码呢&#xff0c;如何存储密码才能保证密码不被开发者获取或者被截取呢&#xff0c;保证密码的安全 加盐的加密方式 现在的企业开发大都采用这种…

凌恩生物文献分享|颠覆性的宏基因组新思路,速来get!

非人灵长类动物&#xff08;NHP&#xff09;是人类的近亲&#xff0c;为宿主-微生物互作的研究提供了一个很好的例子。近年来研究主要集中在野生灵长类动物的肠道微生物群&#xff0c;这将有助于了解灵长类及其肠道微生物群的进化&#xff0c;但仍然缺乏关于野生种群肠道微生物…

通用后台管理系统-前端搭建

一 背景 基于vuespringboot 搭建一套通用管理后台 主要包括用户管理模块、权限模块、菜单模块 二 环境信息 2.1 前端工具版本 2.1.1 npm 版本 PS D:\front> npm -v 8.5.0PS D:\front> npm config get registry https://registry.npm.taobao.org/ PS D:\front>2.1…

埋点tracker:前端埋点服务-技术要点梳理

一、背景埋点方案&#xff0c;前端涉及到哪些技术要点&#xff0c;本文做简单的梳理和总结。二、指纹追踪技术&#xff1a;识别到用户及设备浏览器&#xff1a;浏览器指纹_snowli的博客-CSDN博客三、用户设备信息&#xff08;navigator&#xff09;navigator.userAgent四、页面…

利用Mysql存储过程造百万级数据

1.准备工作&#xff08;1&#xff09;由于是使用存储过程&#xff0c;mysql从5.0版开始支持存储过程&#xff0c;那么需要mysql的版本在5.0或者以上。如何查看mysql的版本&#xff0c;使用下面sql语句查看&#xff1a;&#xff08;2&#xff09;创建两张表&#xff0c;表结构一…

Android Crash和ANR监控

文章目录一、Crash1.1 概念1.2 类型二、ANR2.1 概念2.2 类型2.2.1 KeyDispatchTimeout&#xff08;常见&#xff09;2.2.2 BroadcastTimeout2.2.3 ServiceTimeout2.2.4 ContentProviderTimeout三、测试中如何关注3.1 Crash测试关注方法3.2 ANR测试关注方法四、如何记录与处理4.…

C++复习笔记--STL的string容器和vector容器

1--string容器string 本质上是一个类&#xff0c;其不同于指针 char*&#xff0c;string 类的内部封装了 char*&#xff0c;用于管理字符串&#xff0c;是一个 char* 型的容器&#xff1b;1-1--string构造函数string 的构造函数原型&#xff1a;string(); // 创建一个空的字符串…

媒体邀约的形式和步骤

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 做媒体服务很多年&#xff0c;今天就与大家分享下媒体邀约都有哪些形式&#xff1a; 1&#xff0c;电话邀约&#xff1a;通过电话与媒体记者进行沟通&#xff0c;邀请其参加活动或接受采…

CDC 长沙站丨云原生技术研讨会:数字兴链,云化未来!

一、活动信息&#xff1a;活动主题&#xff1a;CDC 长沙站丨云原生技术研讨会活动时间&#xff1a;2023 年 3 月 14 日下午 14&#xff1a;30-17&#xff1a;30活动地点&#xff1a;长沙市岳麓区-拓维信息总部 1 楼多功能厅活动参与方式&#xff1a;免门票参与&#xff0c;戳此…

船舶自动驾驶避撞规则

1无人船避碰阶段 如图1所示。 第一阶段&#xff1a;感知阶段。使用雷达、AIS、激光雷达和视觉传感器等感知传感器进行障碍物检测。利用感知到的信息&#xff0c;获得障碍物的运动信息。 第二阶段&#xff1a;决策阶段。利用障碍物的运动信息做出避免冲突的决策。在这一阶段&am…

数据结构排序比较

排序的概念及其运用 (1)排序的概念 排序:所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&am…

Spring-AOP工作流程

Spring-AOP工作流程 3&#xff0c;AOP工作流程 3.1 AOP工作流程 由于AOP是基于Spring容器管理的bean做的增强&#xff0c;所以整个工作过程需要从Spring加载bean说起: 流程1:Spring容器启动 容器启动就需要去加载bean,哪些类需要被加载呢?需要被增强的类&#xff0c;如:B…

C++ Qt自建网页浏览器

C Qt自建网页浏览器如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01;前言这篇博客针对<<C Qt自建网页浏览器>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与应用推荐首选。文…