基于深度掩码的类散景模糊实现(Pillow/CV2)
基于深度图的伪景深(散景效果)实现方案
核心思路
伪景深的关键是根据深度图灰度值映射连续变化的模糊半径:近景(深度图中亮度较高区域,对应MiDaS生成的近距离物体)使用小半径模糊甚至保留清晰,远景(亮度较低区域)使用大半径模糊。同时用圆盘形状卷积核模拟真实散景的圆形光斑效果,替代普通的高斯/盒状模糊。
PIL 实现方案
PIL原生没有内置圆盘模糊,需自定义卷积核实现,再结合深度图做多尺度融合:
步骤1:自定义圆盘模糊函数
import numpy as np from PIL import Image, ImageFilter def disc_blur(image, radius): # 生成圆盘形状的卷积核 kernel_size = 2 * radius + 1 kernel = np.zeros((kernel_size, kernel_size), dtype=np.float32) center = radius y, x = np.ogrid[:kernel_size, :kernel_size] dist_from_center = np.sqrt((x - center)**2 + (y - center)**2) kernel[dist_from_center <= radius] = 1 # 归一化核权重 kernel /= kernel.sum() # 将PIL图像转为numpy数组进行卷积 img_array = np.array(image) channels = img_array.shape[2] if len(img_array.shape) == 3 else 1 result = np.zeros_like(img_array) for c in range(channels): channel = img_array[..., c] # 边缘填充避免黑边 padded = np.pad(channel, radius, mode='reflect') # 滑动窗口卷积 for i in range(img_array.shape[0]): for j in range(img_array.shape[1]): result[i, j, c] = np.sum(padded[i:i+kernel_size, j:j+kernel_size] * kernel) return Image.fromarray(result.astype(np.uint8))
步骤2:基于深度图的多尺度融合
# 加载原图与深度图 oimg = Image.open('2.png').convert('RGB') width, height = oimg.size mimg = Image.open('2_depth.png').resize((width, height)).convert('L') # 预处理深度图:归一化到0-1范围,反转(亮部为近景保留清晰,暗部为远景模糊) depth_array = np.array(mimg) / 255.0 depth_array = 1 - depth_array # 反转后值越大对应越远,模糊半径越大 # 定义模糊半径范围:近景半径0,远景最大半径10 min_radius = 0 max_radius = 10 # 生成多尺度模糊图像(分5个尺度,可按需调整) scales = 5 blur_images = [] for i in range(scales): radius = min_radius + (max_radius - min_radius) * (i / (scales - 1)) blur_img = disc_blur(oimg, int(radius)) blur_images.append(blur_img) # 将模糊图像转为numpy数组 blur_arrays = [np.array(img) for img in blur_images] # 根据深度值加权融合多尺度图像 result_array = np.zeros_like(np.array(oimg), dtype=np.float32) for i in range(scales): if i == scales - 1: weight = np.where(depth_array >= (i / (scales - 1)), 1.0, 0.0) else: lower = i / (scales - 1) upper = (i + 1) / (scales - 1) weight = np.where((depth_array >= lower) & (depth_array < upper), (depth_array - lower) / (upper - lower), 0.0) # 扩展权重到3通道 weight_3ch = np.stack([weight]*3, axis=-1) result_array += blur_arrays[i] * weight_3ch # 转为PIL图像并保存 rimg = Image.fromarray(result_array.astype(np.uint8)) rimg.save('pseudo_depth_of_field.png')
OpenCV 实现方案
OpenCV可高效生成圆盘核,结合高斯金字塔实现多尺度模糊融合:
import cv2 import numpy as np # 加载原图与深度图 oimg = cv2.imread('2.png') mimg = cv2.imread('2_depth.png', cv2.IMREAD_GRAYSCALE) mimg = cv2.resize(mimg, (oimg.shape[1], oimg.shape[0])) # 预处理深度图:归一化并反转 depth_array = mimg / 255.0 depth_array = 1 - depth_array # 定义模糊半径范围 min_radius = 0 max_radius = 10 # 生成高斯金字塔(多尺度模糊) pyr = [oimg] for i in range(5): pyr.append(cv2.pyrDown(pyr[-1])) # 将金字塔图像放大回原尺寸 scaled_pyr = [pyr[0]] for i in range(1, len(pyr)): scaled = cv2.resize(pyr[i], (oimg.shape[1], oimg.shape[0]), interpolation=cv2.INTER_LINEAR) scaled_pyr.append(scaled) # 生成圆盘模糊核并应用 def apply_disc_blur(img, radius): if radius == 0: return img kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2*radius+1, 2*radius+1)) kernel = kernel / kernel.sum() return cv2.filter2D(img, -1, kernel) # 对每个尺度应用圆盘模糊 blur_scales = [apply_disc_blur(img, int(min_radius + (max_radius - min_radius)*(i/(len(scaled_pyr)-1)))) for i, img in enumerate(scaled_pyr)] # 加权融合多尺度图像 result = np.zeros_like(oimg, dtype=np.float32) scales = len(blur_scales) for i in range(scales): if i == scales -1: weight = np.where(depth_array >= (i/(scales-1)), 1.0, 0.0) else: lower = i/(scales-1) upper = (i+1)/(scales-1) weight = np.where((depth_array >= lower) & (depth_array < upper), (depth_array - lower)/(upper - lower), 0.0) weight_3ch = np.stack([weight]*3, axis=-1) result += blur_scales[i] * weight_3ch # 转为uint8格式并保存 result = cv2.convertScaleAbs(result) cv2.imwrite('pseudo_depth_of_field_cv2.png', result)
关键改进说明
- 连续模糊半径:替代原方案的"清晰/固定模糊"二元合成,实现从近到远的平滑模糊过渡
- 圆盘模糊核:模拟真实散景的圆形光斑,比高斯/盒状模糊更贴近相机拍摄效果
- 多尺度融合:通过分层模糊+加权融合,平衡视觉效果与计算效率
内容的提问来源于stack exchange,提问作者WASasquatch
相关产品推荐
相关产品推荐

