我对图像卷积很感兴趣。这是我使用 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()
你的代码在 Python 中执行 3x3 卷积时存在一些低效之处,以下是几个可以提高速度的方法:
scipy.signal.convolve2d
(最快的方法之一)numpy
的切片操作cv2.filter2D
(如果你不介意引入 OpenCV)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'
进行镜像填充)。
你可以避免 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 的广播机制:高效计算卷积,而不是逐像素计算。
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.convolve2d
或 cv2.filter2D
。
如果你想 避免额外依赖,推荐 NumPy 切片版本。
希望对你有帮助! 🚀