You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 05:36:13