其他
【他山之石】pytorch 实现双边滤波
“他山之石,可以攻玉”,站在巨人的肩膀才能看得更高,走得更远。在科研的道路上,更需借助东风才能更快前行。为此,我们特别搜集整理了一些实用的代码链接,数据集,软件,编程技巧等,开辟“他山之石”专栏,助你乘风破浪,一路奋勇向前,敬请关注。
原文地址:https://zhuanlan.zhihu.com/p/310710051
前几天研究了传统的美颜算法,了解到双边滤波(bilateral filtering)。在看懂原理后,为加深理解,抽时间用 pytorch 重新造了个轮子。虽然效率肯定比不上 opencv ,但当个小练习也不错。为了方便复习以及帮助初学者,在此记录。
01
@torch.no_grad()
def getGaussianKernel(ksize, sigma=0):
if sigma <= 0:
# 根据 kernelsize 计算默认的 sigma,和 opencv 保持一致
sigma = 0.3 * ((ksize - 1) * 0.5 - 1) + 0.8
center = ksize // 2
xs = (np.arange(ksize, dtype=np.float32) - center) # 元素与矩阵中心的横向距离
kernel1d = np.exp(-(xs ** 2) / (2 * sigma ** 2)) # 计算一维卷积核
# 根据指数函数性质,利用矩阵乘法快速计算二维卷积核
kernel = kernel1d[..., None] @ kernel1d[None, ...]
kernel = torch.from_numpy(kernel)
kernel = kernel / kernel.sum() # 归一化
return kernel
def GaussianBlur(batch_img, ksize, sigma=None):
kernel = getGaussianKernel(ksize, sigma) # 生成权重
B, C, H, W = batch_img.shape # C:图像通道数,group convolution 要用到
# 生成 group convolution 的卷积核
kernel = kernel.view(1, 1, ksize, ksize).repeat(C, 1, 1, 1)
pad = (ksize - 1) // 2 # 保持卷积前后图像尺寸不变
# mode=relfect 更适合计算边缘像素的权重
batch_img_pad = F.pad(batch_img, pad=[pad, pad, pad, pad], mode='reflect')
weighted_pix = F.conv2d(batch_img_pad, weight=kernel, bias=None,
stride=1, padding=0, groups=C)
return weighted_pix
02
距离中心像素越远的像素,其权重就越小 亮度和中心像素亮度差异越大的像素,其权重就越小
03
import torch
x = torch.arange(12).view(3, 4)
x
Out[4]:
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
# 沿着行,以步长 1 拆分 x,每个 patch 为 2 行,列保持不变,
y = x.unfold(dimension=0, size=2, step=1)
y.shape
Out[6]: torch.Size([2, 4, 2])
y[0]
Out[7]:
tensor([[0, 4],
[1, 5],
[2, 6],
[3, 7]])
y[1]
Out[8]:
tensor([[ 4, 8],
[ 5, 9],
[ 6, 10],
[ 7, 11]])
# 直接对 y 的第二个维度拆分,例如拆分成 3 列,步长仍为 1
z = y.unfold(dimension=1, size=3, step=1)
z.shape
Out[10]: torch.Size([2, 2, 2, 3])
# 观察 z[0, 0],发现正是 x 左上角的六个元素
z[0,0]
Out[11]:
tensor([[0, 1, 2],
[4, 5, 6]])
# z[0, 1] 也同样符合预期
z[0,1]
Out[12]:
tensor([[1, 2, 3],
[5, 6, 7]])
def bilateralFilter(batch_img, ksize, sigmaColor=None, sigmaSpace=None):
device = batch_img.device
if sigmaSpace is None:
sigmaSpace = 0.15 * ksize + 0.35 # 0.3 * ((ksize - 1) * 0.5 - 1) + 0.8
if sigmaColor is None:
sigmaColor = sigmaSpace
pad = (ksize - 1) // 2
batch_img_pad = F.pad(batch_img, pad=[pad, pad, pad, pad], mode='reflect')
# batch_img 的维度为 BxcxHxW, 因此要沿着第 二、三维度 unfold
# patches.shape: B x C x H x W x ksize x ksize
patches = batch_img_pad.unfold(2, ksize, 1).unfold(3, ksize, 1)
patch_dim = patches.dim() # 6
# 求出像素亮度差
diff_color = patches - batch_img.unsqueeze(-1).unsqueeze(-1)
# 根据像素亮度差,计算权重矩阵
weights_color = torch.exp(-(diff_color ** 2) / (2 * sigmaColor ** 2))
# 归一化权重矩阵
weights_color = weights_color / weights_color.sum(dim=(-1, -2), keepdim=True)
# 获取 gaussian kernel 并将其复制成和 weight_color 形状相同的 tensor
weights_space = getGaussianKernel(ksize, sigmaSpace).to(device)
weights_space_dim = (patch_dim - 2) * (1,) + (ksize, ksize)
weights_space = weights_space.view(*weights_space_dim).expand_as(weights_color)
# 两个权重矩阵相乘得到总的权重矩阵
weights = weights_space * weights_color
# 总权重矩阵的归一化参数
weights_sum = weights.sum(dim=(-1, -2))
# 加权平均
weighted_pix = (weights * patches).sum(dim=(-1, -2)) / weights_sum
return weighted_pix
04
05
本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。
“他山之石”历史文章
编译PyTorch静态库
工业界视频理解解决方案大汇总
动手造轮子-rnn
凭什么相信你,我的CNN模型?关于CNN模型可解释性的思考
c++接口libtorch介绍& vscode+cmake实践
python从零开始构建知识图谱
一文读懂 PyTorch 模型保存与载入
适合PyTorch小白的官网教程:Learning PyTorch With Examples
pytorch量化备忘录
LSTM模型结构的可视化
PointNet论文复现及代码详解
SCI写作常用句型之研究结果&发现
白话生成对抗网络GAN及代码实现
pytorch的余弦退火学习率
更多他山之石专栏文章,
请点击文章底部“阅读原文”查看
分享、点赞、在看,给个三连击呗!