别再死记公式了!用Python的NumPy和SciPy手把手带你玩转卷积(附代码和可视化)

张开发
2026/4/24 9:05:36 15 分钟阅读

分享文章

别再死记公式了!用Python的NumPy和SciPy手把手带你玩转卷积(附代码和可视化)
用Python可视化卷积从动画模拟到图像滤波实战卷积这个概念听起来很数学但它的核心思想其实非常直观——想象一下用一把梳子梳理毛毯时梳齿与毛毯纤维的互动过程。在数字世界里卷积就是这样一个数字梳子滑过我们的数据留下独特的印记。本文将带你用Python的NumPy和SciPy通过代码和可视化一步步揭开卷积的神秘面纱。1. 卷积的动画模拟翻转-滑动-相乘-求和理解卷积最直观的方式就是看它如何一步步计算。我们先从一维信号开始用动画思维分解卷积的四个关键步骤。首先准备两个简单的信号一个方波信号f和一个高斯脉冲gimport numpy as np import matplotlib.pyplot as plt from scipy.signal import convolve # 创建信号 t np.linspace(0, 1, 100, endpointFalse) f np.where((t 0.2) (t 0.5), 1, 0) # 方波 g np.exp(-50*(t-0.35)**2) # 高斯脉冲现在让我们手动实现卷积运算def manual_convolution(f, g): result np.zeros(len(f) len(g) - 1) # 翻转g g_flipped g[::-1] for n in range(len(result)): # 滑动并截取重叠部分 start max(0, n - len(g) 1) end min(n 1, len(f)) f_segment f[start:end] g_segment g_flipped[len(g)-end:len(g)-start] if start ! end else [] # 相乘并求和 if len(f_segment) 0: result[n] np.sum(f_segment * g_segment) return result为了更直观地理解这个过程我们可以创建一个动画来展示卷积的每个步骤from matplotlib.animation import FuncAnimation fig, (ax1, ax2) plt.subplots(2, 1, figsize(10, 6)) ax1.set_xlim(0, 2) ax1.set_ylim(-0.1, 1.1) ax2.set_xlim(0, 2) ax2.set_ylim(-0.1, 3) line_f, ax1.plot([], [], b-, labelf(t)) line_g, ax1.plot([], [], r-, labelg(t-n)) line_conv, ax2.plot([], [], g-, labelConvolution) dots ax1.scatter([], [], colorpurple) # 相乘点 ax1.legend() ax2.legend() def init(): line_f.set_data([], []) line_g.set_data([], []) line_conv.set_data([], []) dots.set_offsets(np.empty((0, 2))) return line_f, line_g, line_conv, dots def animate(n): # 滑动g g_shifted np.roll(g[::-1], n) # 计算当前卷积值 conv_result manual_convolution(f, g)[:n1] # 更新绘图 line_f.set_data(t, f) line_g.set_data(t n/100, g_shifted) line_conv.set_data(np.arange(n1)/100, conv_result) # 标记相乘点 overlap_indices np.where((t max(0, n/100 - 1)) (t min(1, n/100)))[0] if len(overlap_indices) 0: x_points t[overlap_indices] y_points f[overlap_indices] * g_shifted[overlap_indices] dots.set_offsets(np.column_stack((x_points, y_points))) return line_f, line_g, line_conv, dots ani FuncAnimation(fig, animate, frames150, init_funcinit, blitTrue, interval50) plt.close()这个动画会展示高斯脉冲如何翻转、滑动与方波相乘并求和的过程。你可以看到卷积结果是如何随着滑动逐步构建出来的。2. 卷积定理的代码验证时域与频域的桥梁卷积定理告诉我们时域中的卷积等于频域中的乘法。这个强大的定理可以大幅简化计算让我们用代码来验证它。首先我们创建两个信号# 创建两个信号 N 1000 t np.linspace(0, 1, N, endpointFalse) f np.sin(2*np.pi*5*t) # 5Hz正弦波 g np.exp(-50*(t-0.5)**2) # 高斯脉冲现在我们分别计算时域卷积和频域乘积from scipy.fft import fft, ifft, fftfreq # 时域卷积 conv_time convolve(f, g, modesame) * (t[1]-t[0]) # 乘以dt # 频域乘积 F fft(f) G fft(g) conv_freq ifft(F * G).real # 绘制结果 plt.figure(figsize(12, 4)) plt.plot(t, conv_time[:N], b-, label时域卷积) plt.plot(t, conv_freq, r--, label频域乘积) plt.legend() plt.title(卷积定理验证) plt.show()你会看到两条曲线几乎完全重合验证了卷积定理的正确性。这个定理在实际应用中非常有用特别是当我们需要计算大尺寸信号的卷积时通过FFT转换到频域计算可以大幅提升效率。3. 卷积模式对比full, same, validNumPy和SciPy提供了三种卷积模式理解它们的区别对实际应用至关重要。让我们通过一个例子来比较它们# 定义两个短信号 f np.array([1, 2, 3, 4, 5]) g np.array([0.5, 1, 0.5]) # 计算不同模式的卷积 full np.convolve(f, g, modefull) same np.convolve(f, g, modesame) valid np.convolve(f, g, modevalid) # 可视化结果 plt.figure(figsize(12, 6)) plt.stem(full, linefmtb-, markerfmtbo, basefmt , labelfull) plt.stem(same, linefmtr--, markerfmtrx, basefmt , labelsame) plt.stem(valid, linefmtg:, markerfmtg^, basefmt , labelvalid) plt.legend() plt.title(不同卷积模式比较) plt.show()三种模式的区别如下表所示模式输出长度边界处理适用场景fullNM-1完全重叠需要所有可能重叠结果samemax(N,M)中心对齐保持输入输出尺寸一致validmax(N,M)-min(N,M)1仅完全重叠部分避免边界效应在实际应用中full模式常用于信号处理获取完整的卷积结果same模式在图像处理中最常用保持图像尺寸不变valid模式适用于需要避免边界效应的情况4. 图像滤波实战卷积在计算机视觉中的应用卷积在图像处理中有着广泛的应用。让我们用Python实现几种常见的图像滤波器。首先加载一张测试图像from scipy import misc from scipy.ndimage import convolve as convolve2d # 加载图像 face misc.face(grayTrue) plt.imshow(face, cmapgray) plt.title(原始图像) plt.show()4.1 边缘检测边缘检测可以帮助我们找到图像中物体的轮廓。最常用的边缘检测滤波器是Sobel算子# 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]]) # 应用卷积 grad_x convolve2d(face, sobel_x, modesame) grad_y convolve2d(face, sobel_y, modesame) grad_magnitude np.sqrt(grad_x**2 grad_y**2) # 显示结果 plt.figure(figsize(12, 4)) plt.subplot(131) plt.imshow(grad_x, cmapgray) plt.title(水平边缘) plt.subplot(132) plt.imshow(grad_y, cmapgray) plt.title(垂直边缘) plt.subplot(133) plt.imshow(grad_magnitude, cmapgray) plt.title(边缘强度) plt.show()4.2 图像模糊高斯模糊是图像处理中常用的平滑技术可以去除噪声# 创建高斯核 def gaussian_kernel(size, sigma1): kernel np.fromfunction( lambda x, y: (1/(2*np.pi*sigma**2)) * np.exp(-((x-(size-1)/2)**2 (y-(size-1)/2)**2)/(2*sigma**2)), (size, size) ) return kernel / np.sum(kernel) gauss_kernel gaussian_kernel(15, sigma3) blurred convolve2d(face, gauss_kernel, modesame) plt.figure(figsize(12, 4)) plt.subplot(121) plt.imshow(face, cmapgray) plt.title(原始图像) plt.subplot(122) plt.imshow(blurred, cmapgray) plt.title(高斯模糊) plt.show()4.3 锐化滤波器锐化滤波器可以增强图像的细节# 锐化滤波器 sharpen np.array([[ 0, -1, 0], [-1, 5, -1], [ 0, -1, 0]]) sharpened convolve2d(face, sharpen, modesame) plt.figure(figsize(12, 4)) plt.subplot(121) plt.imshow(face, cmapgray) plt.title(原始图像) plt.subplot(122) plt.imshow(sharpened, cmapgray) plt.title(锐化图像) plt.show()在实际项目中我发现选择合适的卷积核尺寸和参数对结果影响很大。例如过大的高斯核会导致图像过度模糊而过小的核可能无法有效去除噪声。通常需要根据具体应用场景进行多次试验。

更多文章