matlab实现双边滤波_【他山之石】pytorch 实现双边滤波

“他山之石,可以攻玉”,站在巨人的肩膀才能看得更高,走得更远。在科研的道路上,更需借助东风才能更快前行。为此,我们特别搜集整理了一些实用的代码链接,数据集,软件,编程技巧等,开辟“他山之石”专栏,助你乘风破浪,一路奋勇向前,敬请关注。

作者:知乎—尹相楠

原文地址:https://zhuanlan.zhihu.com/p/310710051

前几天研究了传统的美颜算法,了解到双边滤波(bilateral filtering)。在看懂原理后,为加深理解,抽时间用 pytorch 重新造了个轮子。虽然效率肯定比不上 opencv ,但当个小练习也不错。为了方便复习以及帮助初学者,在此记录。

01

高斯滤波1. 高斯核函数图像领域的高斯滤波器是个二维的矩阵。矩阵中每个元素的值与它与矩阵中心的距离有关,计算公式就是二维高斯函数的公式:5582e0dc-2859-eb11-8da9-e4434bdf6706.svg为了让卷积前后的图像亮度保持不变,需要对 (1) 计算的矩阵归一化(除以矩阵所有元素的和),因此 (1) 中 exp 之前的系数部分可以省略。生成高斯滤波器的代码如下:
@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
2. 高斯滤波器pytorch 自带的 conv2d 可以很方便地对图像施加高斯滤波,代码如下:
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
关于 group convolution,如果不熟悉可以看我这篇回答:什么是「Grouped Convolution」?https://www.zhihu.com/question/60484190/answer/1507783179

02

双边滤波高斯滤波器的权重完全由距离决定。在大块颜色差不多、偶有噪点的区域,它可以把颜色平均化,从而过滤掉噪点。但是在颜色变化剧烈的边缘区域,它还是一视同仁地把所有像素做加权平均,这让本应该清晰锐利的边缘也变得模糊不清了,这就造成了如下图所示的效果,在做人像美颜时是不希望看到的。9c6b678f7d7ad9a983fb777b6f6b684c.png这里,就引入了双边滤波(bilateral filtering)。双边滤波的权重公式也基于高斯函数。但和高斯滤波的区别是,决定卷积核权重的,不单纯是像素之间的空间距离,还包括像素之间的亮度差异。以卷积核中心为坐标原点,该处像素值为I(0,0)。那么,坐标为 (u, v) 处的像素,对应的权重为:5882e0dc-2859-eb11-8da9-e4434bdf6706.svg(2) 中 exp 的第一个指数项和高斯核函数相同,与像素的空间距离有关;第二个指数项则是像素值距离的函数。以e为底对这两项做指数运算,再相乘即得到了公式 (2)。根据公式 (2) 计算的卷积核有如下性质:
  • 距离中心像素越远的像素,其权重就越小
  • 亮度和中心像素亮度差异越大的像素,其权重就越小
第一条比较好理解,第二条的含义是,像素只和与自己相似的像素求平均,不和与自己差别太大的像素求平均。例如上图中,人脸部分的像素和背景部分的像素差异过于显著,这将导致在对边缘区域做卷积时,背景侧像素对人脸侧的像素的权重极小,反之同理。这就达到了保持边缘 (edge-preserving) 的特性。

03

代码实现由于 (2) 中卷积核的权重不仅仅依赖于空间距离,还依赖于像素的亮度,因此卷积核的权重是不固定的,不能简单地利用 pytorch 的 conv2d 来实现。pytorch 的 tensor 自带了一个 unfold 方法,正好可以用在这里。unfold 的作用是把图像拆分成 patch,每个patch 为卷积核覆盖的像素。下面举个小例子:
import torchx = torch.arange(12).view(3, 4)xOut[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.shapeOut[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 列,步长仍为 1z = y.unfold(dimension=1, size=3, step=1)z.shapeOut[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]])
实现的思路是:把原始图像 unfold 成一个个的 patch,对每个 patch 计算权重以及加权平均。代码如下:
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
最终结果为下图,雀斑都没了!同时人脸的轮廓和五官的细节依然被很好地保留下来:f053f4bbed10ec9f270aee0a99f073dd.png
输入图片尺寸为 256 x 256,ksize=15,sigmaColor=0.15,sigmaSpace=5 。
需要注意的是,由于 bilateral filter 的权重和像素值相关,因此设置 sigmaColor 时要注意输入图像的像素范围,看清楚到底是 0-1 还是 0-255(上图像素范围为 0-1)。

04

总结本文介绍了双边滤波的基本原理,并附带了 pytorch 的实现。虽然不如 opencv 快,但优点是 backward trackable ,适合包装为模块加入网络中。利用 unfold 实现的缺点是很占内存/显存,kernelsize 越大,unfold 出来的冗余数据就越多,如果有大神知道更高效的实现方式,还望不吝赐教。

05

后记我发现网上搜到的很多磨皮祛斑的算法,主要的目标是设计一个高通滤波器,从而得到一个基于像素亮度的 mask,亮的地方权重大(对应皮肤区域),暗的地方权重小(对应雀斑、噪点区域)。将原图 I 和 模糊化的图I_blur(各种模糊化方式都可以,目标是把较暗的斑点模糊掉)利用 mask 融合:I_mask+I_blur(1-mask) 。这种方法既保留了原图的细节,又能模糊掉斑点,不过在不同图片上应用时,仍然免不了调整一些超参数,而真有调参的功夫,直接调一下双边滤波的几个参数,最后得到的效果未必比那些复杂的算法差。

本文目的在于学术交流,并不代表本公众号赞同其观点或对其内容真实性负责,版权归原作者所有,如有侵权请告知删除。

他山之石”历史文章

  • 编译PyTorch静态库

  • 工业界视频理解解决方案大汇总

  • 动手造轮子-rnn

  • 凭什么相信你,我的CNN模型?关于CNN模型可解释性的思考

  • “最全PyTorch分布式教程”来了!

  • c++接口libtorch介绍& vscode+cmake实践

  • python从零开始构建知识图谱

  • 一文读懂 PyTorch 模型保存与载入

  • 适合PyTorch小白的官网教程:Learning PyTorch With Examples

  • pytorch量化备忘录

  • LSTM模型结构的可视化

  • PointNet论文复现及代码详解

  • SCI写作常用句型之研究结果&发现

  • 白话生成对抗网络GAN及代码实现

  • pytorch的余弦退火学习率

更多他山之石专栏文章,

请点击文章底部“阅读原文”查看

771e2c17a907284758e5ba0640a4191e.png909d8151e34fa32a60126b755dfcf6ad.gif

分享、点赞、在看,给个三连击呗!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/564311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新网 云服务器,云服务器的使用教程

现在购买云服务器的人越来越多&#xff0c;大家只需要在众多的云服务器品牌中找到适合自己的云服务器。并不是价格越贵就越适合自己&#xff0c;而且初学者购买贵的云服务器并不划算&#xff0c;接下来就由小编为大家介绍关于云服务器的使用教程&#xff0c;希望能给大家带来帮…

开红数显示服务器为空,网维大师常见问题:图标空白或红号问号

【问题现象】客户机打开游戏菜单后出现图标异常&#xff0c;只能看到游戏文字看不到图标或图标&#xff1f;号&#xff0c;分别如下图两种情况或【原因说明】出现这种问题一般是由于网维大师服务端的游戏图标缓存文件损坏导致。而导致图标缓存损坏的原因一般是由于&#xff1a;…

超微服务器开机启动项目怎么设置,超微服务器启动项设置

超微服务器启动项设置 内容精选换一换微服务部署完后&#xff0c;您可以根据微服务的运行情况进行微服务的治理。您可以先在“服务目录 > 微服务列表”中创建微服务&#xff0c;启动微服务后&#xff0c;根据yaml文件的配置&#xff0c;会在对应的服务下注册服务实例。如果没…

MySQL 正则表达式查询

MySQL中正式表达式通常被用来检索或替换符合某个模式的文本内容&#xff0c;根据指定的匹配模式匹配文中符合要求的特殊字符串。 例如&#xff0c;从一个文件中提取电话号码&#xff0c;查找一篇文章中重复的单词或替换用户输入的敏感语汇等&#xff0c;这些地方都可以使用正则…

onmp mysql端口_ONMP 安装教程

之前用过极路由 1s&#xff0c;刷了恩山 H大的固件&#xff0c;发现内置了 web 环境&#xff0c;没想到一个路由器都能跑的动 LNMP。我之前最极限的尝试就是在一个 4刀3年 的 sentris ovz 小鸡上跑过 WordPress &#xff0c;那台小鸡 配置很低&#xff0c;只有 64M 的内存&…

mysql添加索引后查询先用索引吗_mysql 添加索引后 在查询的时候是mysql就自动从索引里面查询了。还是查询的时候有单 独的参数查询索引?...

满意答案llt17112014.06.20采纳率&#xff1a;49% 等级&#xff1a;9已帮助&#xff1a;614人MYSQL创建索引对索引使用方式分两种&#xff1a;1 由数据库查询优化器自动判断否使用索引&#xff1b;2 用户写SQL语句时强制使用索引下面两种索引使用方式进行说明第种自动使用索…

win7 vs2017 程序报错_windows + VS2017 配置libpytorch

因为项目对实时性的要求&#xff0c;尝试一波c直接调用libpytorch。实现的同时&#xff0c;整理了网上的相关资料&#xff0c;希望对效率追求的同学提供一份帮助。0.下载准备下载安装VS2017 和 cmake &#xff08;3.0版本以上&#xff09;记得把bin目录添加到环境变量path中。这…

jdbc mysql api_JDBC Api详解

一.什么是JDBCJDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准&#xff0c;据此可以构建更高级的工具和接口&#xff0…

mysql业务数据库回退_理解MySQL数据库事务-隔离性

Transaction事务是指一个逻辑单元&#xff0c;执行一系列操作的SQL语句。事务中一组的SQL语句&#xff0c;要么全部执行&#xff0c;要么全部回退。在Oracle数据库中有个名字&#xff0c;叫做transaction ID在关系型数据库中&#xff0c;事务必须ACID的特性。原子性&#xff0c…

java socket 阻塞模式_Java中Socket Read阻塞问题

本人来说并不熟悉JAVA语言&#xff0c;只是近期在分析某个简单的java agent程序时&#xff0c;根据对应的代码写了一个对接的程序&#xff0c;两者之间是典型的C/S socket编程。客户端在向服务端发送相应的指令后&#xff0c;服务端(装agent的主机)执行后会返回执行的数据给客户…

java 窗体 源码_Java制作MDI窗体源代码

由于实际需要做一个MDI窗体&#xff0c;百度里面找到一个不错的源代码给大家分享一下。import javax.swing.*;import java.awt.event.*;import java.awt.*;class JInternalFrame1 extends JFrame implements ActionListener{JDesktopPane desktopPane;int count 1;public JInt…

idl文件生成java_IDL和生成代码分析

IDL:接口描述语言这里使用thrift-0.8.0-xsb这个版本来介绍IDL的定义以及简单实例分析。1. namespace 定义包名2.struct 结构体&#xff0c;定义服务接口的参数和返回值用到的类结构。基本类型不需要使用struct.3.service 定义接口&#xff1a;demo.thrift1 namespace java com.…

java从控制台读取数据_Java不同版本从控制台读取数据方法及优缺点分析

从JDK 5.0版本开始&#xff0c;能从控制台中输入数据的方法每增加一个版本号&#xff0c;就有一种新增的方法&#xff0c;这也增加了选择的种类&#xff0c;可以依据不同的要求来进行选择。下面和绿茶小编一起来了解一下各个版本从控制台中读取数据的方法以及各自的优缺点。1、…

python的pygame库使用方法_[宜配屋]听图阁

使用python pygame库实现一个双人弹球小游戏&#xff0c;两人分别控制一个左右移动的挡板用来拦截小球&#xff0c;小球会在两板间不停弹跳&#xff0c;拦截失败的一方输掉游戏&#xff0c;规则类似于简化版的乒乓球。因为是第一次用pygame写python小游戏并且只用了两三个小时&…

C++ 三种继承方式

C继承的一般语法为&#xff1a; class 派生类名:&#xff3b;继承方式&#xff3d; 基类名{派生类新增加的成员 };继承方式限定了基类成员在派生类中的访问权限&#xff0c;包括 public&#xff08;公有的&#xff09;、private&#xff08;私有的&#xff09;和 protected&am…

java项目 异常如何解决_Java项目中常见的异常处理

发生异常的情况有很多&#xff0c;其中包括以下几大类&#xff1a;1. 空指针异常&#xff1b;2. 用户输入异常&#xff1b;3. 多层异常捕获&#xff1b;想要知道Java是如何处理异常的&#xff0c;就需要掌握以下这三种异常的处理&#xff1a;1.检查性异常&#xff1a;最具代表的…

java i/o 流详解_java I/O流详解

概况I/O流主要分为二大类别&#xff1a;字符流和字节流。字节流(基本流)1、字节输入流 类名&#xff1a;FileInputStream 特点&#xff1a;读(对文件进行读取操作) 父类&#xff1a;InputStream2、字节输出流 类名&#xff1a;FileOutputStream 特点&#xff1a;写…

C++ 向上转型

在 C 中经常会发生数据类型的转换&#xff0c;例如将 int 类型的数据赋值给 float 类型的变量时&#xff0c;编译器会先把 int 类型的数据转换为 float 类型再赋值&#xff1b;反过来&#xff0c;float 类型的数据在经过类型转换后也可以赋值给 int 类型的变量。 数据类型转换…

C++ 获取类型信息

typeid 运算符用来获取一个表达式的类型信息。类型信息对于编程语言非常重要&#xff0c;它描述了数据的各种属性&#xff1a; 对于基本类型&#xff08;int、float 等C内置类型&#xff09;的数据&#xff0c;类型信息所包含的内容比较简单&#xff0c;主要是指数据的类型。对…

C 数字排列组合

编辑程序让任意四个数字排列组合并且每一个排列组合的数字都不相同。 结果展示 完整代码 #include <stdio.h> //定义头文件 int main() {int i,j,k;printf("\n");for(i1;i<5;i){ //三重循环 for(j1;j<5;j){for(k1;k<5;k){ //确保 i j k 位置不同 i…