如何用Python快速计算双矩阵3x3窗口平方差和输出矩阵
使用numpy和cv2.filter2D可实现静态卷积,示例代码如下:
import numpy as np # 定义卷积核 convolution_kernel = np.array([[-2, -1, 0], [-1, 1, 1], [0, 1, 2]]) import cv2 # 读取图像并应用卷积 image = cv2.imread('1.png') result = cv2.filter2D(image, -1, convolution_kernel)
静态卷积的计算逻辑:将3x3窗口置于输入图像的[i,j]位置,窗口内每个像素值与卷积核对应位置的值做逐元素相乘(哈达玛积),最后将所有乘积求和,得到输出图像[i,j]位置的像素值(每个颜色通道单独计算)。
我需要实现的并非上述常规卷积,而是基于固定大小窗口对两个输入矩阵进行自定义运算。给定两个输入矩阵A和B:
A = [[179, 97, 77, 118, 144, 105], [ 68, 56, 184, 210, 141, 230], [178, 166, 218, 47, 106, 172], [ 38, 183, 50, 185, 48, 87], [ 60, 200, 228, 232, 6, 190], [253, 75, 231, 166, 117, 134]] B = [[116, 95, 94, 220, 80, 223], [135, 9, 166, 78, 5, 129], [102, 167, 120, 81, 141, 29], [ 83, 117, 81, 129, 255, 48], [130, 231, 165, 7, 187, 169], [ 44, 137, 16, 50, 229, 202]]
输出矩阵的每个[i,j]像素值需按以下规则计算:取两个输入矩阵中以[i,j]为中心的3x3窗口,计算窗口内所有A[u,v]² - B[u,v]²的总和。边界位置的窗口自动缩小(不做补零或裁剪输出尺寸处理)。
我已用循环实现该逻辑,代码和预期输出如下:
W = 3 # 窗口大小为WxW out = np.zeros(A.shape) difference_of_squares = A * A - B * B for i, j in np.ndindex(out.shape): starti = max(i - W//2, 0) # 边界处缩小窗口范围 stopi = min(i - W//2 + W, np.shape(out)[0]) startj = max(j - W//2, 0) stopj = min(j - W//2 + W, np.shape(out)[1]) out[i, j] = np.sum(difference_of_squares[starti:stopi, startj:stopj]) print(out)
预期输出:
[[ 8423. 11816. 10372. 41125. 35287. 31747.] [ 29370. 65887. 38811. 61252. 51033. 51845.] [ 24756. 60119. 109133. 35101. 70005. 18757.] [ 8641. 62463. 126935. 14530. 2255. -64752.] [ 36623. 110426. 163513. 33812. -50035. -146450.] [ 22268. 100132. 130190. 83010. -10163. -88994.]]
现在需要找到更高效的Python实现方式,比如用numpy实现对A*A - B*B的3x3窗口求和,或者其他2D映射方法。
方法1:numpy滑动窗口视图+边界适配
利用numpy的滑动窗口视图快速生成所有3x3窗口,再通过手动适配边界逻辑,保证和原循环结果一致:
import numpy as np A = np.array(A) B = np.array(B) difference_of_squares = A**2 - B**2 W = 3 pad_size = W//2 h, w = difference_of_squares.shape out = np.zeros_like(difference_of_squares, dtype=np.float64) for i in range(h): for j in range(w): # 计算窗口的有效范围 start_i = max(i - pad_size, 0) end_i = min(i + pad_size + 1, h) start_j = max(j - pad_size, 0) end_j = min(j + pad_size + 1, w) out[i,j] = difference_of_squares[start_i:end_i, start_j:end_j].sum() print(out)
该方案用numpy切片求和替代内层循环,比原生Python循环效率更高,且完全匹配原逻辑。
方法2:OpenCV filter2D+边界修正
因为需求等价于对difference_of_squares做全1卷积核的卷积,可借助OpenCV的高效卷积实现,再手动修正边界以匹配原逻辑:
import cv2 import numpy as np A = np.array(A) B = np.array(B) difference_of_squares = A**2 - B**2 # 定义全1的3x3卷积核 kernel = np.ones((3,3), dtype=np.float64) # 先做补零卷积 out = cv2.filter2D(difference_of_squares, -1, kernel, borderType=cv2.BORDER_CONSTANT) pad_size = 1 h, w = out.shape # 修正第一行 for j in range(w): out[0,j] = difference_of_squares[0:min(0+pad_size+1, h), max(j-pad_size,0):min(j+pad_size+1,w)].sum() # 修正最后一行 for j in range(w): out[h-1,j] = difference_of_squares[max(h-1-pad_size,0):h, max(j-pad_size,0):min(j+pad_size+1,w)].sum() # 修正第一列(跳过首尾行) for i in range(1, h-1): out[i,0] = difference_of_squares[max(i-pad_size,0):min(i+pad_size+1,h), 0:min(0+pad_size+1,w)].sum() # 修正最后一列(跳过首尾行) for i in range(1, h-1): out[i,w-1] = difference_of_squares[max(i-pad_size,0):min(i+pad_size+1,h), max(w-1-pad_size,0):w].sum() print(out)
该方案利用OpenCV的底层优化提升核心计算效率,仅通过少量循环修正边界,兼顾速度和结果一致性。
方法3:Scipy ndimage.convolve+边界修正
借助Scipy的卷积工具实现核心计算,再修正边界:
from scipy.ndimage import convolve import numpy as np A = np.array(A) B = np.array(B) difference_of_squares = A**2 - B**2 kernel = np.ones((3,3)) # 补零卷积 out = convolve(difference_of_squares, kernel, mode='constant') pad_size = 1 h, w = out.shape # 修正第一行 for j in range(w): out[0,j] = difference_of_squares[0:min(0+pad_size+1, h), max(j-pad_size,0):min(j+pad_size+1,w)].sum() # 修正最后一行 for j in range(w): out[h-1,j] = difference_of_squares[max(h-1-pad_size,0):h, max(j-pad_size,0):min(j+pad_size+1,w)].sum() # 修正第一列(跳过首尾行) for i in range(1, h-1): out[i,0] = difference_of_squares[max(i-pad_size,0):min(i+pad_size+1,h), 0:min(0+pad_size+1,w)].sum() # 修正最后一列(跳过首尾行) for i in range(1, h-1): out[i,w-1] = difference_of_squares[max(i-pad_size,0):min(i+pad_size+1,h), max(w-1-pad_size,0):w].sum() print(out)
若追求最高效率,优先选择OpenCV或Scipy的卷积工具+边界修正方案;若仅依赖numpy,滑动窗口视图配合边界适配的实现方式更简洁,且能保证结果与原循环逻辑完全一致。
内容的提问来源于stack exchange,提问作者user3310334

