Python OpenCV如何优化可变镜头模糊算法 提升效率避免逐像素遍历
Holga镜头径向可变模糊的性能优化方案
问题背景
需要模拟Holga这类廉价塑料相机的镜头成像效果:
- 画面中心区域模糊程度极弱,越靠近图像边角,模糊效果越强
- 该效果不属于固定核大小的标准高斯模糊、常规二维滤波,无法直接调用OpenCV内置的固定核滤波函数实现
现有逐像素遍历的实现可正常运行,输出效果符合基础预期,但运行效率极低,存在较大的性能与效果优化空间。
现有低效实现代码如下:
import cv2 import numpy as np import requests from tqdm import tqdm import warnings warnings.filterwarnings("ignore") def blur(img=None, blur_radius=None, test=False): # 测试图像加载 if img is None: test=True print('test mode ON') print('loading image...') url = r'http://www.lenna.org/lena_std.tif' resp = requests.get(url, stream=True).raw img = np.asarray(bytearray(resp.read()), dtype="uint8") img = cv2.imdecode(img, cv2.IMREAD_COLOR) cv2.imwrite('img_input.png', img) print('image loaded') # 通道拆分 img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(img_lab) if test: cv2.imwrite('l_channel.png', l) print('l channel saved') # 生成模糊权重图 height, width = l.shape[:2] center = np.array([height/2, width/2]) diag = ((height / 2) ** 2 + (width / 2) ** 2) ** 0.5 blur_map = np.linalg.norm( np.indices(img.shape[:2]) - center[:,None,None] + 0.5, axis = 0 ) if blur_radius is None: blur_radius = int(max(height, width) * 0.03) blur_map = blur_map / diag blur_map = blur_map * blur_radius if test: blur_map_norm = cv2.normalize(blur_map, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_32F) cv2.imwrite('blur_map.png', blur_map_norm) print('blur map saved') # 低效逐像素模糊逻辑 l_blur = np.copy(l) for x in tqdm(range(width)): for y in range(height): kernel_size = int(blur_map[y, x]) if kernel_size == 0: l_blur[y, x] = l[y, x] continue kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size)) cut = l[ max(0, y - kernel_size):min(height, y + kernel_size), max(0, x - kernel_size):min(width, x + kernel_size) ] if cut.shape == kernel.shape: cut = (cut * kernel).mean() else: cut = cut.mean() l_blur[y, x] = cut if test: cv2.imwrite('l_blur.png', l_blur); print('l_blur saved') if test: print('done') return l_blur blur()
性能低效核心原因
原实现的性能瓶颈完全来自逐像素循环内的重复计算:
- 逐像素裁切邻域、生成结构元、计算加权均值,没有利用OpenCV底层SIMD、并行计算的优化
- 相同大小的核被反复创建,相同区域的像素均值被重复计算,冗余操作占比超过99%
优化方案
采用多尺度固定核模糊+权重融合的思路,完全移除逐像素遍历逻辑,全流程使用OpenCV原生向量化运算,速度可提升2~3个数量级,同时效果更平滑:
- 离散化模糊半径档位:模糊半径从0到设定的最大值,按整数拆分为N个档位(N取最大模糊半径值即可,档位间隔为1像素)
- 预计算每个档位对应的模糊结果:每个档位只做1次对应大小的椭圆核均值滤波,所有滤波操作均调用OpenCV原生
blur/filter2D函数,自动启用底层硬件加速 - 按模糊权重图融合结果:遍历每个半径档位,将blur_map中对应当前半径区间的像素,从对应档位的模糊结果中取值,最终合并为完整的输出图
如果需要进一步提升速度,还可以减少档位数量(比如每2像素一个档位),相邻档位的结果用线性插值过渡,在肉眼几乎无感知的前提下再提速数倍。
优化后参考代码
import cv2 import numpy as np import requests import warnings warnings.filterwarnings("ignore") def fast_holga_blur(img=None, blur_radius=None, test=False): # 测试图像加载 if img is None: test=True print('test mode ON') print('loading image...') url = r'http://www.lenna.org/lena_std.tif' resp = requests.get(url, stream=True).raw img = np.asarray(bytearray(resp.read()), dtype="uint8") img = cv2.imdecode(img, cv2.IMREAD_COLOR) cv2.imwrite('img_input.png', img) print('image loaded') # 通道拆分 img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(img_lab) if test: cv2.imwrite('l_channel.png', l) print('l channel saved') # 生成模糊权重图 height, width = l.shape[:2] center = np.array([height/2, width/2]) diag = ((height / 2) ** 2 + (width / 2) ** 2) ** 0.5 blur_map = np.linalg.norm( np.indices(l.shape) - center[:,None,None] + 0.5, axis = 0 ) if blur_radius is None: blur_radius = int(max(height, width) * 0.03) blur_map = np.clip(blur_map / diag * blur_radius, 0, blur_radius).astype(np.uint8) if test: blur_map_norm = cv2.normalize(blur_map, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U) cv2.imwrite('blur_map.png', blur_map_norm) print('blur map saved') # 预生成所有需要的椭圆核 kernels = {} max_r = blur_radius for r in range(1, max_r+1): ksize = 2*r + 1 kernels[r] = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (ksize, ksize)) kernels[r] = kernels[r] / kernels[r].sum() # 初始化输出 l_blur = np.copy(l) # 按半径档位批量滤波、填充结果 for r in range(1, max_r+1): # 找到所有对应当前模糊半径的像素位置 mask = (blur_map == r) if not np.any(mask): continue # 单次滤波得到当前半径的全图模糊结果 blurred = cv2.filter2D(l, cv2.CV_8U, kernels[r], borderType=cv2.BORDER_REFLECT) # 把对应位置的像素填充到输出 l_blur[mask] = blurred[mask] if test: cv2.imwrite('l_blur_fast.png', l_blur) print('l_blur saved') print('done') return l_blur fast_holga_blur()
注:如果需要更柔和的模糊过渡,可以将blur_map保留浮点精度,对相邻两个半径的模糊结果做线性加权融合,避免档位之间出现生硬的边界。
内容的提问来源于stack exchange,提问作者Maxim
相关产品推荐
相关产品推荐

