小能豆

python 2d卷积优化

py

我对图像卷积很感兴趣。这是我使用 3x3 内核执行卷积的代码。我正在寻找有关如何使其运行更快的任何想法。

import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from PIL import Image
import numpy as np
img = mpimg.imread('benfrank.png')
imgCopy = img.copy()
Width = 1200
Height = 1464
x1 = 0
y1 = 0
cWidth = 3
cHeight = 3
convul = np.array([[0,0,-5],
                  [0,1,0],
                  [-5,0,0]])

summ = convul[2,2]+convul[2,1]+convul[2,0]+convul[1,2]+convul[1,1]+convul[1,0]+convul[0,2]+convul[0,1]+convul[0,0]

def convulute3x3(x,y):
    global convul
    global img,imgCopy, Width, Height, summ

    i = x
    j = y
    if(i < 1 or i > Width-2 ):
        return
    elif(j < 1 or j > Height-2 ):
        return
    for c in range(3):
        n11 = img[j-1,i-1,c]*convul[0,0]
        n22 = img[j-1,i,c]*convul[1,0]
        n33 = img[j-1,i+1,c]*convul[2,0]
        n44= img[j,i-1,c]*convul[0,1]
        n55 = img[j,i,c]*convul[1,1]
        n66 = img[j,i+1,c]*convul[2,1]
        n77 = img[j+1,i-1,c]*convul[0,2]
        n88 = img[j+1,i,c]*convul[1,2]
        n99 = img[j+1,i+1,c]*convul[2,2]   
        color = (n11+n22+n33+n44+n55+n66+n77+n88+n99)/summ       
        imgCopy[j,i,c] = color               
for x in img:
    x1=0
    for y in x:
        convulute3x3(x1,y1) 
        x1 = x1+1
    y1 = y1+1
plt.imshow(imgCopy)
plt.show()

阅读 62

收藏
2025-03-02

共1个答案

小能豆

你的代码在 Python 中执行 3x3 卷积时存在一些低效之处,以下是几个可以提高速度的方法:


改进方案

  1. 使用 NumPy 的 scipy.signal.convolve2d(最快的方法之一)
  2. 避免 for 循环,改用 numpy 的切片操作
  3. 使用 OpenCV 的 cv2.filter2D(如果你不介意引入 OpenCV)

1. 用 scipy.signal.convolve2d(最快的方法之一)

SciPy 提供了优化的 convolve2d,它可以高效地执行 2D 卷积:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from scipy.signal import convolve2d

# 读取图像
img = mpimg.imread('benfrank.png')
if img.dtype == np.uint8:  # 归一化到 0-1(避免溢出)
    img = img.astype(np.float32) / 255.0  

# 3x3 卷积核
kernel = np.array([[0, 0, -5],
                   [0, 1, 0],
                   [-5, 0, 0]])

# 对每个颜色通道应用卷积
img_filtered = np.zeros_like(img)
for c in range(img.shape[2]):  # 遍历 R/G/B 通道
    img_filtered[:, :, c] = convolve2d(img[:, :, c], kernel, mode='same', boundary='wrap')

plt.imshow(img_filtered)
plt.show()

为什么更快?
- convolve2d 是用 C 语言实现的,比 Python 的 for 循环快几个数量级。
- mode='same' 让输出图像大小与输入一致。
- boundary='wrap' 避免边界问题(也可以用 'symm' 进行镜像填充)。


2. 直接用 NumPy 切片操作

你可以避免 for 循环,使用 NumPy 的切片操作执行卷积:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

# 读取图像
img = mpimg.imread('benfrank.png').astype(np.float32)

# 3x3 卷积核
kernel = np.array([[0, 0, -5],
                   [0, 1, 0],
                   [-5, 0, 0]])

# 计算卷积(使用 NumPy 切片)
def apply_convolution(img, kernel):
    h, w, c = img.shape
    output = np.zeros_like(img)

    # 遍历每个颜色通道
    for ch in range(c):
        img_padded = np.pad(img[:, :, ch], pad_width=1, mode='constant')

        # 滑动窗口操作(使用 NumPy 的广播机制)
        output[:, :, ch] = (img_padded[:-2, :-2] * kernel[0, 0] +
                            img_padded[:-2, 1:-1] * kernel[0, 1] +
                            img_padded[:-2, 2:] * kernel[0, 2] +
                            img_padded[1:-1, :-2] * kernel[1, 0] +
                            img_padded[1:-1, 1:-1] * kernel[1, 1] +
                            img_padded[1:-1, 2:] * kernel[1, 2] +
                            img_padded[2:, :-2] * kernel[2, 0] +
                            img_padded[2:, 1:-1] * kernel[2, 1] +
                            img_padded[2:, 2:] * kernel[2, 2])

    return output

img_filtered = apply_convolution(img, kernel)
plt.imshow(img_filtered)
plt.show()

为什么更快?
- 避免 for 循环:通过 NumPy 的切片操作,同时处理整个图像块。
- 使用 NumPy 的广播机制:高效计算卷积,而不是逐像素计算。


3. 使用 OpenCV (cv2.filter2D)

如果你愿意使用 OpenCV,这是最优化的方式之一:

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 读取图像
img = cv2.imread('benfrank.png')  # OpenCV 读取的是 BGR 格式
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换为 RGB

# 3x3 卷积核
kernel = np.array([[0, 0, -5],
                   [0, 1, 0],
                   [-5, 0, 0]], dtype=np.float32)

# 进行卷积
img_filtered = cv2.filter2D(img, -1, kernel)

plt.imshow(img_filtered)
plt.show()

为什么更快?
- cv2.filter2D 是 C++ 代码实现的,并且支持硬件加速。


结论

方法 速度 代码复杂度 依赖
scipy.signal.convolve2d 最快 简单 SciPy
NumPy 切片 中等 NumPy
OpenCV cv2.filter2D 非常快 简单 OpenCV
纯 Python for 循环 最慢 复杂

如果你想要 最快的方法,推荐 scipy.signal.convolve2dcv2.filter2D

如果你想 避免额外依赖,推荐 NumPy 切片版本

希望对你有帮助! 🚀

2025-03-02