C++版本的OpenCV实现二维图像的卷积定理(通过傅里叶变换实现二维图像的卷积过程,附代码!!)

news2024/9/25 13:25:48

C++版本的OpenCV库实现二维图像的卷积定理过程详解

  • 前言
  • 一、卷积定理简单介绍
  • 二、不同卷积过程对应的傅里叶变换过程
    • 1、“Same”卷积
    • 2、“Full”卷积
    • 3、“Valid”卷积
  • 三、基于OpenCV库实现的二维图像卷积定理
  • 四、基于FFTW库实现的二维图像卷积定理
  • 五、总结与讨论

前言

工作中用到许多卷积过程,需要转成C++代码的实现,使用OpenCV库自带的二维卷积过程所耗费的时间比较久,为了提升代码的运行效率可以考虑使用卷积定理实现二维图像的卷积过程。

一、卷积定理简单介绍

卷积定理是傅立叶变换满足的一个重要性质。卷积定理指出,函数卷积的傅立叶变换是函数傅立叶变换的乘积。具体分为时域卷积定理和频域卷积定理,时域卷积定理即时域内的卷积对应频域内的乘积;频域卷积定理即频域内的卷积对应时域内的乘积,两者具有对偶关系。

二、不同卷积过程对应的傅里叶变换过程

1、“Same”卷积

假设原始图像的大小为mxm,卷积核的大小为nxn。此时进行same卷积,则卷积后生成的图像大小为mxm,此时进行卷积是需要对原始图像进行padding操作,需要对原始图像周围进行n-1个补零操作,此时被卷积图像大小padding为(m+n-1)x(m+n-1),然后进行卷积操作,进行傅里叶变化是需要先将被卷积图像以及卷积核的大小都padding为(m+n-1)x(m+n-1),因为要进行傅里叶变换后的乘积操作,因此被卷积图像以及卷积核的大小需要相等。经过卷积定理的操作之后,将生成的(m+n-1)x(m+n-1)大小的图像按照padding的逆操作进行裁剪,得到mxm的图像即为same卷积得到的卷积图像。
Python代码验证

import numpy as np
from scipy import signal

# 原始图像 f(x)
gray = np.uint16(np.random.randint(100, size=(7, 7)))
# 卷积核 g(x)
kenel = np.ones((3, 3))/9
# ----- Conv = f(x)*g(x) ----- #
# f(x)*g(x)
Conv = signal.convolve2d(gray, kenel, mode='same') # 使用full卷积类型,得到(M+N-1)X(M+N-1)大小

#--------- ifft{ F(f(x))·F(g(x)) } ---------#
# 傅里叶变换前进行 padding 填充,图像和卷积核都补零到 (M+N-1)x(M+N-1)大小。
img_pad = np.pad(gray, ((1, 1), (1, 1)), 'constant')
kenel_pad = np.pad(kenel, ((3, 3),(3, 3)), 'constant')

# F(f(x))
img_fft = np.fft.fftshift(np.fft.fft2(img_pad))
# F(g(x))
kenel_fft = np.fft.fftshift(np.fft.fft2(kenel_pad))
# ifft( F(f(x))·F(g(x)) )
FFT = np.fft.ifftshift(np.fft.ifft2(np.fft.fftshift(img_fft*kenel_fft)))

#--------- 打印结果 ---------#
print(" f(x) ↓")
print(gray)
print(" g(x) ↓")
print(kenel)

print("\n\n f(x)*g(x) ↓")
print(np.uint8(Conv))
print("\n ifft[F·G] ↓")
print(np.uint8(np.abs(FFT)))

结果展示
上述代码的结果如下图所示,可见卷积定理得到的结果经过裁剪后的红框中的内容与same卷积得到的结果一致。
在这里插入图片描述

2、“Full”卷积

假设原始图像的大小为mxm,卷积核的大小为nxn。此时进行full卷积,则卷积后生成的图像大小为(m+n-1)x(m+n-1),此时进行卷积是需要对原始图像进行padding操作,需要对原始图像周围进行2n-2个补零操作,此时被卷积图像大小padding为(m+2n-2)x(m+2n-12),然后进行卷积操作,进行傅里叶变化是需要先将被卷积图像以及卷积核的大小都padding为(m+n-1)x(m+n-1),因为要进行傅里叶变换后的乘积操作,因此被卷积图像以及卷积核的大小需要相等。经过卷积定理的操作之后,得到(m+n-1)x(m+n-1)的图像即为full卷积得到的卷积图像。
Python代码验证

import numpy as np
from scipy import signal

# 原始图像 f(x)
gray = np.uint16(np.random.randint(100, size=(9, 9)))
# 卷积核 g(x)
kenel = np.ones((5, 5))/9
# ----- Conv = f(x)*g(x) ----- #
# f(x)*g(x)
Conv = signal.convolve2d(gray, kenel, mode='full') # 使用full卷积类型,得到(M+N-1)X(M+N-1)大小


#--------- ifft{ F(f(x))·F(g(x)) } ---------#
# 傅里叶变换前进行 padding 填充,图像和卷积核都补零到 (M+N-1)x(M+N-1)大小。
img_pad = np.pad(gray, ((2, 2), (2, 2)), 'constant')
kenel_pad = np.pad(kenel, ((4, 4), (4, 4)), 'constant')

# F(f(x))
img_fft = np.fft.fftshift(np.fft.fft2(img_pad))
# F(g(x))
kenel_fft = np.fft.fftshift(np.fft.fft2(kenel_pad))
# ifft( F(f(x))·F(g(x)) )
FFT = np.fft.ifftshift(np.fft.ifft2(np.fft.fftshift(img_fft*kenel_fft)))

#--------- 打印结果 ---------#
print(" f(x) ↓")
print(gray)
print(" g(x) ↓")
print(kenel)

print("\n\n f(x)*g(x) ↓")
print(np.uint8(Conv))
print("\n ifft[F·G] ↓")
print(np.uint8(np.abs(FFT)))

结果展示
上述代码的结果如下图所示,可见卷积定理得到的结果与full卷积得到的结果一致。
在这里插入图片描述

3、“Valid”卷积

假设原始图像的大小为mxm,卷积核的大小为nxn。此时进行valid卷积,则卷积后生成的图像大小为(m-n+1)x(m-n+1),此时不需要对被卷积图像进行padding操作,直接进行卷积操作,进行傅里叶变化是需要先将卷积核的大小padding为mxm,因为要进行傅里叶变换后的乘积操作,因此被卷积图像以及卷积核的大小需要相等。经过卷积定理的操作之后,将生成的mxm的图像进行裁剪,得到(m-n+1)x(m-n+1)的图像即为valid卷积得到的卷积图像。
Python代码验证

import numpy as np
from scipy import signal

# 原始图像 f(x)
gray = np.uint16(np.random.randint(100, size=(9, 9)))
# 卷积核 g(x)
kenel = np.ones((5, 5))/9
# ----- Conv = f(x)*g(x) ----- #
# f(x)*g(x)
Conv = signal.convolve2d(gray, kenel, mode='valid') # 使用full卷积类型,得到(M+N-1)X(M+N-1)大小

#--------- ifft{ F(f(x))·F(g(x)) } ---------#
# 傅里叶变换前进行 padding 填充,图像和卷积核都补零到 (M+N-1)x(M+N-1)大小。
img_pad = np.pad(gray, ((0, 0), (0, 0)), 'constant')
kenel_pad = np.pad(kenel, ((2, 2), (2, 2)), 'constant')

# F(f(x))
img_fft = np.fft.fftshift(np.fft.fft2(img_pad))
# F(g(x))
kenel_fft = np.fft.fftshift(np.fft.fft2(kenel_pad))
# ifft( F(f(x))·F(g(x)) )
FFT = np.fft.ifftshift(np.fft.ifft2(np.fft.fftshift(img_fft*kenel_fft)))

#--------- 打印结果 ---------#
print(" f(x) ↓")
print(gray)
print(" g(x) ↓")
print(kenel)

print("\n\n f(x)*g(x) ↓")
print(np.uint8(Conv))
print("\n ifft[F·G] ↓")
print(np.uint8(np.abs(FFT)))

结果展示
上述代码的结果如下图所示,可见卷积定理得到的结果经过裁剪后的红框中的内容与valid卷积得到的结果一致。
在这里插入图片描述

三、基于OpenCV库实现的二维图像卷积定理

//本代码实现的是使用193x193大小的卷积核对193x193大小的被卷积图像进行卷积操作
Conv2Kernel(kernelImage, inputImage, outputImage, n, n);
cv::Mat matPaded1;
cv::Mat matPaded2;
cv::Mat kernelPaded;
cv::Mat fftKernel;
cv::Mat fftMat1;
cv::Mat fftMat2;
cv::Mat result1;
cv::Mat result2;

cv::copyMakeBorder(matKernel, kernelPaded, 192, 215, 192, 215, cv::BORDER_CONSTANT, cv::Scalar(0.f));
cv::copyMakeBorder(matConv1, matPaded1, 192, 215, 192, 215, cv::BORDER_CONSTANT, cv::Scalar(0.f));

cv::Mat planes1[] = { cv::Mat_<float>(kernelPaded),cv::Mat::zeros(kernelPaded.size(),CV_32F)
};
cv::Mat planes2[] = { cv::Mat_<float>(matPaded1),cv::Mat::zeros(matPaded1.size(),CV_32F)
};
cv::merge(planes1, 2, fftKernel);
cv::merge(planes2, 2, fftMat1);

cv::dft(fftKernel, fftKernel, cv::DFT_COMPLEX_OUTPUT);
cv::dft(fftMat1, fftMat1, cv::DFT_COMPLEX_OUTPUT);

fftshift(fftKernel);
fftshift(fftMat1);
cv::Mat fftMultiplication1;
cv::mulSpectrums(fftKernel, fftMat1, fftMultiplication1, cv::DFT_ROWS);
cv::idft(fftMultiplication1, result1, cv::DFT_INVERSE + cv::DFT_SCALE + cv::DFT_COMPLEX_OUTPUT);

ifftshift(result1);
cv::split(result1, planes1);
cv::magnitude(planes1[0], planes1[1], planes1[0]);

cv::Mat matConv3 = planes1[0](cv::Rect(192, 192, 193, 193)).clone();

代码详解
1、对被卷积图像和卷积核执行padding操作
由于使用的是same卷积,按照上文same卷积的过程对其进行padding操作,图像上下左右四个方向都补上192个0。下述代码右边和下面之所以补零数不是192,是因为在进行傅里叶变换时,特定长度的矩阵速度更快,因此补了更多的0。但是我在实验过程中并没有发现速度有变快
2、对padding后的图像矩阵进行傅里叶变换
对padding后的卷积核以及被卷积图像执行傅里叶变换,然后进行fftshift操作。
3、对傅里叶变换后的图像矩阵进行点乘操作
4、对点乘后的举证进行傅里叶逆变换
对点乘后的矩阵执行傅里叶逆变换,然后进行ifftshift操作。
5、截取合适位置的图像,具体位置根据padding过程确定,得到的图像矩阵即为卷积后的图像矩阵。

四、基于FFTW库实现的二维图像卷积定理

                fftw_complex* pImgOut = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * 600 * 600);
                fftw_plan pRef;
                pRef = fftw_plan_dft_2d(600, 600, pImgIn, pImgOut, FFTW_FORWARD, FFTW_ESTIMATE);

                fftw_execute(pRef);
                fftw_destroy_plan(pRef);
                fftw_free(pImgIn);
                fftw_free(pImgOut);

五、总结与讨论

经过不懈努力终于使用C++实现了卷积定理,想要进一步提升卷积过程的速度,优化代码性能。但是天不遂人愿,在我的任务中使用卷积定义实现卷积过程和使用OpenCV库中的卷积操作接口速度相差无几,几乎没有看到在计算速度上有提升,忙了半天白忙活了,难受~。分析原因可能是由于本次任务的卷积过程比较特殊,卷积核、被卷积图像的大小一致,导致进行padding操作时,padding后的图像几乎是原图像的9倍大小,造成傅里叶变换以及傅里叶逆变换的过程速度变慢,影响了整体流程的速度。

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

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

相关文章

基于matlab实现的弹簧振动系统模型程序(动态模型)

完整代码&#xff1a; clear all; %System data m1.0; zeta0.01; omega01.0; Dt1.0; f01.0; x00.0; dotx00.0; xmaxsqrt(x0^2(dotx0/omega0)^2)min([0.5*abs(f0)*Dt/(m*omega0) f0/omega0^2]); omegadomega0*sqrt(1-zeta^2); dt00.1*pi/omega0; nstep500; a0.70; b0.…

Python爬虫(二十)_动态爬取影评信息

本案例介绍从JavaScript中采集加载的数据。更多内容请参考:Python学习指南 #-*- coding:utf-8 -*- import requests import re import time import json#数据下载器 class HtmlDownloader(object):def download(self, url, paramsNone):if url is None:return Noneuser_agent …

Echarts 雷达图的详细配置过程

文章目录 雷达图 简介配置步骤简易示例 雷达图 简介 Echarts雷达图是一种常用的数据可视化图表类型&#xff0c;用于展示多个维度的数据在同一坐标系下的分布情况。雷达图通过不同的坐标轴表示不同的维度&#xff0c;数据点的位置表示了各个维度的数值大小。 Echarts雷达图的…

035:vue项目中 radio和checkbox美化方法

第035个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

基于matlab实现的中点放炮各类地震波时距曲线程序

完整程序&#xff1a; clear all dx50;x-500:dx:500;%炮检距 h100;V11500; theta25*pi/180; V2V1/sin(theta); t1sqrt(x.*x4*h*h)/V1;%反射波时距曲线 t2abs(x)./V1;%直达波时距曲线 %折射波时距曲线 xm2*h*tan(theta);%求盲区 k1; for i1:length(x) if x(i)<-xm …

JVM 篇

一、知识点汇总 其中内存模型&#xff0c;类加载机制&#xff0c;GC是重点方面。性能调优部分更偏向应用&#xff0c;重点突出实践能力。编译器优化和执行模式部分偏向于理论基础&#xff0c;重点掌握知识点。 内存模型&#xff1a;各部分作用&#xff0c;保存哪些数据。类加载…

Python if条件分支结构

视频版教程 Python3零基础7天入门实战视频教程 如果 Python程序的多行代码之间没有任何流程控制&#xff0c;则程序总是从上向下依次执行。 但是现实生活中&#xff0c;我经常遇到一些需要做判断的业务流程。比如去银行ATM取款&#xff0c;如果密码输入正确&#xff0c;则可以…

基于matlab实现的额特征线法管道瞬变流计算程序

完整曲线&#xff1a; % 假设阀门瞬间关闭 % 初始数据: clear tic L3000; % 管线长度 Hr70; % 泵压力 N10; % 分段数 NSN1; % 节点数 e0.001651; % 壁厚m,0.065 D0.00635-2*e; % 管道内径 K2.1e9; % 流体体积弹性系数 Rho…

软考高级架构师下篇-15嵌入式系统架构设计的理论

目录 1. 引言2. 嵌入式系统发展历程3. 嵌入式系统硬件4.嵌人式系统软件5.嵌入式系统软件架构设计方法6.嵌入式系统软件架构实践7. 前文回顾1. 引言 此章节主要学习嵌入式系统架构设计的理论和工作中的实践。根据新版考试大纲,本小时知识点会涉及案例分析题(25分)。在历年考…

Redis 配置文件详解 - 持久化(RDB、AOF)

目录 Redis 配置文件详解 单位 包含 INCLUDES 网络 NETWORK 通用 GENERAL 快照 SNAPSHOTTING &#xff08;持久化&#xff09; 复制 REPLICATION&#xff08;主从复制&#xff09; 安全 SECURITY&#xff08;账号密码设置&#xff09; ​编辑 限制 CLIENTS 追加模…

Redis - 超越缓存的多面手

文章目录 Redis 使用场景图解 Redis 使用场景 Redis可以在各种场景中使用 &#x1f539;会话 我们可以使用Redis在不同服务之间共享用户会话数据。 &#x1f539;缓存 我们可以使用Redis缓存对象或页面&#xff0c;特别是对于热点数据。 &#x1f539;分布式锁 我们可以使用…

gin框架再探

Gin框架介绍及使用 | 李文周的博客 (liwenzhou.com) lesson03_gin框架初识_哔哩哔哩_bilibili 1.路由引擎 //路由引擎 rgin.Default() 2.一些http请求方法 get post put delete等等 遇到什么路径&#xff0c;执行什么函数 r.GET("/hello",func{做你想做的事返回…

【Linux】缓冲区/回车换行

1、缓冲区 C程序默认有输出缓冲区。数据输出时&#xff0c;被及时看到&#xff0c;是立马刷新了&#xff1b;如果没被看到&#xff0c;是被暂存在数据缓冲区中。fflush(stdout); 【强制刷新】\n【行刷新&#xff0c;也是一种刷新方式】 2、回车换行 \n【回车换行】输入完一行内…

如何根据性能需求进行场景设计?

场景设计一 探索 测试环境 客户端: win10 这里可以用linux,但没用,因为想直观查看结果。 被测环境:linux X86 4核CPU16G内存 被测接口:登录接口,没有做数据驱动。 在测试执行前,先使用influxSQL把influxdb的数据清理一下,以防影响结果查看。 有这么一个需求,要求系…

docker中安装Ubuntu20,浏览器访问其图形界面

介绍 Kasm&#xff08;https://kasmweb.com/&#xff09;是一家提供虚拟化和远程访问解决方案的公司。他们的主要产品是 Kasm Workspaces&#xff0c;它是一个基于浏览器的虚拟桌面和应用程序交付平台。 Kasm Workspaces 基于开源项目 Apache Guacamole 和 Chromium 浏览器引…

python连接mysql数据库的练习

一、导入pandas内置的sqlite3模块&#xff0c;连接的信息&#xff1a;ip地址是本机, 端口号port 是3306, 用户user是root, 密码password是123456, 数据库database是lambda-xiaozhang import pymysql# 打开数据库连接&#xff0c;参数1&#xff1a;主机名或IP&#xff1b;参数…

安装Anaconda与pytorch,在IDEA中配置环境进行编程

1.官网下载与自己python版本匹配的Anaconda(注意&#xff0c;要想成功安装pytorch&#xff0c;python版本也要对应pytorch的相关版本) Anaconda官网最新版本 与自己python版本不否请查找自己版本anaconda版本对应 清华大学镜像下载 2.安装时勾选添加环境变量或者手动添加&am…

Vue.js vs React:哪一个更适合你的项目?

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

红外检漏技术

SF6气体绝缘设备发生泄漏后会造成运行开关闭锁、 内部绝缘击穿&#xff0c; 泄漏到空气中会造成环境污染&#xff0c; 并严重危害现场人员安全。 再加之SF6气体成本高&#xff0c; 频繁补气&#xff0c; 使维护成本增加&#xff0c; 造成经济损失。 红外检漏是依据SF6气体对红外…

SpringBoot如何保证接口安全?

对于互联网来说&#xff0c;只要你系统的接口暴露在外网&#xff0c;就避免不了接口安全问题。如果你的接口在外网裸奔&#xff0c;只要让黑客知道接口的地址和参数就可以调用&#xff0c;那简直就是灾难。 举个例子&#xff1a;你的网站用户注册的时候&#xff0c;需要填写手…