卷积和快速傅里叶变换(FFT)的实现

2023-05-16

卷积运算

卷积可以说是图像处理中最基本的操作。线性滤波通过不同的卷积核,可以产生很多不同的效果。假如有一个要处理的二维图像,通过二维的滤波矩阵(卷积核),对于图像的每一个像素点,计算它的领域像素和滤波器矩阵的对应元素的乘积,然后累加,作为该像素位置的值。关于图像卷积和滤波的一些知识点可以参考这篇博客。

下面是通过python模拟实现的图像卷积操作,模拟了sobel算子,prewitt算子和拉普拉斯算子。python的np包中有convolve函数可以直接调用,scipy中也有scipy.signal.convolve函数可以直接调用。

import matplotlib.pyplot as plt
import pylab
import cv2
import numpy as np
from PIL import Image
import os

def conv(image, kernel):
    height, width = image.shape        # 获取图像的维度
    h, w = kernel.shape                 # 卷积核的维度

    # 经过卷积操作后得到的新的图像的尺寸
    new_h = height - h + 1
    new_w = width - w + 1
    # 对新的图像矩阵进行初始化
    new_image = np.zeros((new_h, new_w), dtype=np.float)     

    # 进行卷积操作,矩阵对应元素值相乘
    for i in range(new_w):
        for j in range(new_h):
            new_image[i, j] = np.sum(image[i:i+h, j:j+w] * kernel)    # 矩阵元素相乘累加

    # 去掉矩阵乘法后的小于0的和大于255的原值,重置为0和255
    # 用clip函数处理矩阵的元素,使元素值处于(0,255)之间
    new_image = new_image.clip(0, 255)   
    
    # 将新图像各元素的值四舍五入,然后转成8位无符号整型
    new_image = np.rint(new_image).astype('uint8')     
    return new_image
    
if __name__ == "__main__":

    # 读取图像信息,并转换为numpy下的数组
    image = Image.open("图片.jpg", 'r')
    output_path = "./outputPic/"
    if not os.path.exists(output_path):
        os.mkdir(output_path)
    a = np.array(image)

    # sobel 算子
    sobel_x = np.array(([-1, 0, 1],
                        [-2, 0, 2],
                        [-1, 0, 1]))
    sobel_y = np.array(([-1, -2, -1],
                        [0, 0, 0],
                        [1, 2, 1]))
    sobel = np.array(([-1, -1, 0],
                      [-1, 0, 1],
                      [0, 1, 1]))

    # prewitt各个方向上的算子
    prewitt_x = np.array(([-1, 0, 1],
                          [-1, 0, 1],
                          [-1, 0, 1]))
    prewitt_y = np.array(([-1, -1, -1],
                          [0, 0, 0],
                          [1, 1, 1]))
    prewitt = np.array(([-2, -1, 0],
                        [-1, 0, 1],
                        [0, 1, 2]))

    # 拉普拉斯算子
    laplacian = np.array(([0, -1, 0],
                          [-1, 4, -1],
                          [0, -1, 0]))
    laplacian_2 = np.array(([-1, -1, -1],
                            [-1, 8, -1],
                            [-1, -1, -1]))

    kernel_list = ("sobel_x", "sobel_y", "sobel", "prewitt_x", "prewitt_y", "prewitt", "laplacian", "laplacian_2")

    print("Gridient detection\n")
    for w in kernel_list:
        print("starting %s....." % w)
        print("kernel:\n")
        print("R\n")
        R = conv(a[:, :, 0], eval(w))
        print("G\n")
        G = conv(a[:, :, 1], eval(w))
        print("B\n")
        B = conv(a[:, :, 2], eval(w))

        I = np.stack((R, G, B), axis=2)     # 合并三个通道的结果
        Image.fromarray(I).save("%s//bigger-%s.jpg" % (output_path, w))

快速傅里叶变换(FFT)

通过上面的方法实现卷积操作之后,发现可以通过快速傅里叶变换提升卷积运算的效率。python提供了很多标准工具和封装来计算它。NumPySciPy 都有经过充分测试的封装好的FFT库,分别位于子模块 numpy.fftscipy.fftpack 。有关FFT算法的原理和推导可以参见参考链接的博客。

离散傅里叶变换

傅里叶变换

xn 到 Xk 的转化就是空域到频域的转换,转化为点值表示法。

def DFT_slow(x):
    # Compute the discrete Fourier Transform of the 1D array x
    x = np.asarray(x, dtype=float)              # 转化为ndarray
    N = x.shape[0]                              # 维度
    n = np.arange(N)                            # 0~N组成一个一维向量
    k = n.reshape((N, 1))                       # 转换为一个N维向量
    M = np.exp(-2j * np.pi * k * n / N)         # 离散傅里叶公式 -2j复数表示
    return np.dot(M, x)

快速傅里叶变换

离散傅里叶变换具有对称性,利用这种对称性,可以推出递归的快速傅里叶算法。

def FFT(x):
    # A recursive implementation of the 1D Cooley-Tukey FFT
    x = np.asarray(x, dtype=float)                 # 浅拷贝
    N = x.shape[0]

    if N % 2 > 0:
        raise ValueError("size of x must be a power of 2")
    elif N <= 32:
        return DFT_slow(x)
    else:
        X_even = FFT(x[::2])          # 从0开始,2为间隔
        X_odd = FFT(x[1::2])          # 从1开始,2为间隔
        factor = np.exp(-2j * np.pi * np.arange(N) / N)
        '''
        使用/会出现下面的错误,改为// 向下取整
        TypeError: slice indices must be integers or None or have an __index__ method
        '''
        return np.concatenate([X_even + factor[:N // 2] * X_odd,
                               X_even + factor[N // 2:] * X_odd])

向量化的FFT

使用numpy进行矩阵向量化。

def FFT_vectorized(x):
    # A vectorized, non-recurisive version of the Cooley_Tukey FFT
    x = np.asarray(x, dtype=float)
    N = x.shape[0]

    if np.log2(N) % 1 > 0:
        raise ValueError("size of x must be a power of 2")

    # N_min here is equivalent to the stopping condition above,
    # and should be a power of 2
    N_min = min(N, 32)

    # Perform an O[N^2] DFT on all length-N_min sub-problems at once
    n = np.arange(N_min)
    k = n[:, None]
    M = np.exp(-2j * np.pi * n * k / N_min)
    X = np.dot(M, x.reshape((N_min, -1)))

    # build-up each level of the recursive calculation all at once
    while X.shape[0] < N:
        X_even = X[:, :X.shape[1] // 2]
        X_odd = X[:, X.shape[1] // 2:]
        factor = np.exp(-1j * np.pi * np.arange(X.shape[0]) / X.shape[0])[:, None]
        X = np.vstack([X_even + factor * X_odd,
                       X_even - factor * X_odd])

    return X.ravel()     # 降维

用快速傅里叶变换实现卷积

根据卷积定理,时域上卷积运算对应于频域上的傅里叶变换的乘积。

def fft_convolve(a, b):
    n = len(a) + len(b) -1
    N = 2 ** (int(np.log2(n))+1)
    A = np.fft.fft(a, N)
    B = np.fft.fft(b, N)
    return np.fft.ifft(A*B)[:n]

c++实现的递归版

void FFT(Complex* a,int len){
    if(len==1) return;
    Complex* a0=new Complex[len/2];
    Complex* a1=new Complex[len/2];
    for(int i=0;i<len;i+=2){
        a0[i/2]=a[i];
        a1[i/2]=a[i+1];
    }
    FFT(a0,len/2);FFT(a1,len/2);
    Complex wn(cos(2*Pi/len),sin(2*Pi/len));
    Complex w(1,0);
    for(int i=0;i<(len/2);i++){
        a[i]=a0[i]+w*a1[i];
        a[i+len/2]=a0[i]-w*a1[i];
        w=w*wn;
    }
    return;
}

c++实现的非递归版

const double PI = acos(-1.0);

// 复数
struct complex
{
    double r,i;
    complex(double _r = 0.0, double _i = 0.0)
    {
        r = _r;
        i = _i;
    }
    complex operator +(const complex &b)
    {
        return complex(r + b.r, i + b.i);
    }
    complex operator -(const complex &b)
    {
        return complex(r - b.r, i - b.i);
    }
    complex operator *(const complex &b)
    {
        return complex(r*b.r - i*b.i, r*b.i + i*b.r);
    }
};

// 雷德算法 -- 倒位序
void Rader(complex F[], int len)
{
    int j = len >> 1;
    for(int i=1; i<len-1; i++)
    {
        if(i < j)
            swap(F[i], F[j]);
        int k = len >> 1;
        while(j >= k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k)
            j += k;
    }
}

void FFT(complex F[], int len, int on)
{
    Rader(F, len);
    for(int h=2; h<=len; h<<=1)    //计算长度为h的DFT
    {
        complex wn(cos(-on*2*PI/h), sin(-on*2*PI/h));   //单位复根e^(2*PI/m),用欧拉公式展开
        for(int j=0; j<len; j+=h)
        {
            complex w(1,0);       //旋转因子
            for(int k=j; k<j+h/2; k++)
            {
                complex u = F[k];
                complex t = w * F[k + h/2];
                F[k] = u + t;        //蝴蝶合并操作
                F[k + h/2] = u - t;
                w = w * wn;          //更新旋转因子
            }
        }
    }
    //求逆傅里叶变换
    if(on == -1)
    {
        for(int i=0; i<len; i++)
        {
            F[i].r /= len;
        }
    }
}

//求卷积
void Conv(complex a[], complex b[], int len)
{
    FFT(a, len, 1);
    FFT(b, len, 1);
    for(int i=0; i<len; i++)
    {
        a[i] = a[i] * b[i];         //卷积定理
    }
    FFT(a, len, -1);
}

参考链接:
http://blog.jobbole.com/58246/ (快速傅里叶变换)
https://blog.csdn.net/acdreamers/article/details/39005227 (快速傅里叶变换)
http://blog.miskcoo.com/2015/04/polynomial-multiplication-and-fast-fourier-transform#i-15 (快速傅里叶变换)
https://blog.csdn.net/WADuan2/article/details/79529900 (快速傅里叶变换)
https://blog.csdn.net/oliverkingli/article/details/79243731 (图像卷积)
https://blog.csdn.net/zlh_hhhh/article/details/75604333 (快速傅里叶变换)
https://www.cnblogs.com/youmuchen/p/6724780.html (图像卷积)
http://blog.sina.com.cn/s/blog_154d272c90102x98p.html (雷德算法)
https://www.cnblogs.com/Sakits/p/8405098.html (快速傅里叶变换)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

卷积和快速傅里叶变换(FFT)的实现 的相关文章

  • alpine 下编译php5.4的源码报Invalid configuration `x86_64-linux-musl'错误

    Invalid configuration 96 x86 64 linux musl 39 system 96 musl 39 not recognized configure error bin bash 在alpine3 7上编译php
  • golang GRPC安装

    1 下载protoc编译器 https github com protocolbuffers protobuf releases 将protoc exe放到系统环境变量设置的目录下 2 安装golang相关的package go get g
  • reStructuredText

    段落 段落必须由空行代替 段落1 段落2 内联标记 span class hljs emphasis 斜体 span span class hljs strong 粗体 span span class hljs code 96 96 代码块
  • win10下安装kubernets

    win10下安装docker for windows后 xff0c 新版是有一个kubernets选项 xff0c 选择启动后 xff0c 一直报 kubernets is starting 的错误 xff0c 原因是 xff0c kube
  • 嵌入式工程师的自我修养

    文章目录 前言一 认知的四个阶段1 不知不知2 知道不知3 知道己知3 1 软硬件3 2 网络3 3 安全技术 xff08 换成你自己的领域 xff09 3 4 真正知道的三个阶段3 4 1 会用3 4 2 了解怎么实现3 4 3 明白为什
  • 利用uORB机制实现数据在不同进程中通信

    uORB实际上是一种设计模式中的观察者模式 xff0c 用于实现一种一对多的依赖关系 xff0c 让多个观察者 xff08 Observer xff09 同时监听某一主题对象 xff08 Subject xff09 当这个主题对象 xff0
  • Android-注解篇

    1 什么是注解 从JDK 5 开始 xff0c Java 增加了注解 xff0c 注解是代码里的特殊标记 xff0c 这些标记可以在编译 类加载 运 行时被读取 xff0c 并执行相应的处理 通过使用注解 xff0c 开发人员可以在不改变原
  • 新品BCM6755A1KFEBG/MT7921LE/MT7921AU WiFi芯片

    博通在WiFi市场具有相当的实力 在WiFi6上有下面这几个解决方案 xff1a 型号 xff1a BCM6755 BCM6755A1KFEBG 类型 xff1a 四核1 5GHz CPU 封装 xff1a BGA 批次 xff1a 新 B
  • Ubuntu : GPG签名验证错误 解决之道sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 6DFBCBAE

    Ubuntu GPG签名验证错误 解决之道 转载 sudo apt key adv keyserver keyserver ubuntu com recv keys Key Where key 61 61 the gpg key id Th
  • T265深度图像输出

    1 T265深度图像输出 1 1 环境依赖 T265摄像头python3pip3opencv pythonpyrealsense2 1 2 安装运行环境 安装秘钥 span class token function sudo span ap
  • Linux版本号串记录(ubuntu系列)

    Linux version 4 4 0 112 generic buildd 64 lgw01 amd64 010 gcc version 5 4 0 20160609 Ubuntu 5 4 0 6ubuntu1 16 04 5 135 U
  • 死锁的四个必要条件

    死锁 在高并发中是一个常见的名词 产生的四个必要条件如下 xff1a 互斥条件 xff1a 一个资源同一时间能且只能被一个线程访问 xff1b 不可掠夺 xff1a 当资源被一个线程占用时 xff0c 其他线程不可抢夺该资源 xff1b 请
  • Sphinx index.rst

    假设我们有两个文本file1 rst和file2 rst他们的内容如下 file1 rst span class hljs header file1 title1 61 61 61 61 61 61 61 61 61 61 61 61 sp
  • Git - 图形化界面操作

    目录 1 新建仓库 2 代码提交 3 代码回滚 4 新建分支 5 合并分支 6 重置合并 7 分支变基 8 分支优选 Git 的图形化界面操作 xff0c 使用 Idea 进行演示 1 新建仓库 对于一个代码仓库 Create Git re
  • CMakeLists

    1 指定 cmake 的最小版本 cmake minimum required VERSION 3 4 1 2 设置项目名称 xff0c 它会引入两个变量 demo BINARY DIR 和 demo SOURCE DIR xff0c 同时
  • 七步实现STM32MP157多核协同工作(Cortex-A7与Cortex-M4通信)

    写在前面 xff1a STM32MP157是ST进军Linux的首款微处理器 xff0c 采用MCU 43 MPU的组合 xff0c 集成两颗主频微800MHz的Cortex A7应用处理器内核 xff08 支持开源linux操作系统 xf
  • 【实战】STM32 FreeRTOS移植系列教程4:FreeRTOS 软件定时器

    写在前面 xff1a 本文章为 STM32MP157开发教程之FreeRTOS操作系统篇 系列中的一篇 xff0c 笔者使用的开发平台为华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 stm32mp157是
  • 【实战】STM32 FreeRTOS移植系列教程5:FreeRTOS消息队列

    写在前面 xff1a 本文章为 STM32MP157开发教程之FreeRTOS操作系统篇 系列中的一篇 xff0c 笔者使用的开发平台为华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 stm32mp157是
  • 学习嵌入式linux为什么推荐stm32mp157开发板?

    stm32mp157是ST推出的一款双A7 43 M4多核异构处理器 xff0c 既可以学习linux xff0c 又可以学习stm32单片机开发 xff0c 还可以拓展物联网 人工智能方向技术学习 xff0c 并极大丰富linux应用场景
  • STM32 Linux开发板——教程+视频+项目+硬件

    STM32 Linux开发板 适合入门进阶学习的Linux开发板 xff1a 华清远见FS MP1A开发板 xff08 STM32MP157开发板 xff09 开发板介绍 FS MP1A开发板是华清远见自主研发的一款高品质 高性价比的Lin

随机推荐

  • 编程语言对比 面向对象

    C 43 43 面向对象 java面向对象 python面向对象 java中是public int a 61 10 C 43 43 中是 public int a 61 10 C 43 43 中有拷贝构造
  • 嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板

    stm32mp157开发板FS MP1A是华清远见自主研发的一款高品质 高性价比的Linux 43 单片机二合一的嵌入式教学级开发板 开发板搭载ST的STM32MP157高性能微处理器 xff0c 集成2个Cortex A7核和1个Cort
  • CMake(一)

    CMake xff08 一 xff09 简述 在之前的文章中介绍了 qmake的使用 相比qmake xff0c CMake稍微复杂一点 xff0c 它使用CMakeList txt文件来定制整个编译流程 同时 xff0c CMake会根据
  • LTE网元功能

    LTE 网元功能 2015 03 30 22 33 31 分类 xff1a NetworkProtocols 举报 字号 订阅 下载LOFTER 我的照片书 主要网元功能 eNodeB Radio Resou
  • [C++] 32位C++程序,计算sizeof的值

    sizeof str 61 6 字符串数组 xff0c 大小是六个字节 加上 39 0 39 共六个 sizeof p 61 4 指针的内容就是一个指向目标地址的整数 xff0c 所以不管指向char int还是其他 xff0c 32位机指
  • 串口打印printf

    串口打印printf 1 配置串口2 添加代码3 使用MDK勾选Mircro LIB 1 配置串口 打开STM32CubeMX xff0c 创建工程 xff0c 配置串口 2 添加代码 重写fputc函数 xff0c 需要包含头文件 inc
  • 22.Ubuntu出现“由于没有公钥,无法验证下列签名”

    由于没有公钥 xff0c 无法验证下列签名 1 无公钥错误2 输入命令导入公钥3 注意 1 无公钥错误 使用sudo apt update时出现以下错误 xff1a 我图中的公钥就是 xff1a 3B4FE6ACC0B21F32 xff08
  • nyist 27 水池数目(dfs搜索)

    xfeff xfeff 水池数目 时间限制 xff1a 3000 ms 内存限制 xff1a 65535 KB 难度 xff1a 4 描述 南阳理工学院校园里有一些小河和一些湖泊 xff0c 现在 xff0c 我们把它们通一看成水池 xff
  • XTUOJ 1176 I Love Military Chess(模拟)

    xfeff xfeff I Love Military Chess Accepted 45 Submit 141Time Limit 1000 MS Memory Limit 65536 KB 题目描述 陆军棋 xff0c 又称陆战棋 xf
  • 数据结构课程设计之一元多项式的计算

    数据结构不是听会的 xff0c 也不是看会的 xff0c 是练会的 xff0c 对于写这么长的代码还是心有余也力不足啊 xff0c 对于指针的一些操作 xff0c 也还是不熟练 xff0c 总出现一些异常错误 xff0c 对于数据结构掌握还
  • Unity官方文档(英文)

    地址 xff1a https docs unity3d com Manual UnityManual html
  • 数据结构课程设计之通讯录管理系统

    数据结构的第二个课程设计 xff0c 在c语言课程设计的基础上加以改进 xff0c xff08 加强版 xff09 xff0c 保存一下代码 xff0c 对文件的处理 xff0c 还是有一点一问题 xff0c 还有待改进 include l
  • 在网页中添加音乐

    最近在折腾一个网页 xff0c 对于一个有强迫症的人来说 xff0c 就想在网页中插入音乐 xff0c xff08 当做背景音乐 xff09 xff0c 然后自己百度了好多资料 xff1b 就在这里总结一下 xff1a 第一步 xff1a
  • nyist oj 214 单调递增子序列(二) (动态规划经典)

    单调递增子序列 二 时间限制 xff1a 1000 ms 内存限制 xff1a 65535 KB 难度 xff1a 4 描述 给定一整型数列 a1 a2 an xff08 0 lt n lt 61 100000 xff09 xff0c 找出
  • 思科CCNA第一学期期末考试答案

    1 第 3 层头部包含的哪一项信息可帮助数据传输 xff1f 端口号 设备物理地址 目的主机逻辑地址 虚拟连接标识符 2 IP 依靠 OSI 哪一层的协议来确定数据包是否已丢失并请求重传 xff1f 应用层 表示层 会话层 传输层 3 请参
  • hexo博客出现command not found解决方案

    由于前一段时间忙于考试 xff0c 也有好久没有去更新博客了 xff0c 今天去添加友链的时候 xff0c 突然发现用不了了 xff0c 出现了conmand not found的提示 xff1a 按照字面上的翻译就是 找不到所使用的命令
  • 思科CCNA第二学期期末考试答案

    1 关于数据包通过路由器传输时的封装和解封的叙述 xff0c 下列哪三项是正确的 xff1f xff08 选择三项 xff09 路由器修改 TTL 字段 xff0c 将其值减 1 路由器将源 IP 更改为送出接口的 IP 路由器保持相同的源
  • Hexo版本升级和Next主题升级之坑

    缘起 差不多用了一年hexo的3 2 0版本 xff0c next主题版本也用的5 0的 xff0c 本来用的好好的 xff0c 但是最近访问其他人的博客 xff0c 发现访问速度比我的提升了不止一点点 xff0c 遂决定折腾一番 过程 H
  • Python中JSON的基本使用

    JSON JavaScript Object Notation 是一种轻量级的数据交换格式 Python3 中可以使用 json 模块来对 JSON 数据进行编解码 xff0c 它主要提供了四个方法 xff1a dumps dump loa
  • 卷积和快速傅里叶变换(FFT)的实现

    卷积运算 卷积可以说是图像处理中最基本的操作 线性滤波通过不同的卷积核 xff0c 可以产生很多不同的效果 假如有一个要处理的二维图像 xff0c 通过二维的滤波矩阵 xff08 卷积核 xff09 xff0c 对于图像的每一个像素点 xf