摄像机运动模糊图像处理

2013-11-14 23:31:00 aaa1230550 阅读数 1014

      运动模糊图像复原研究的整体思路主要是用matlab中的 imfilter()函数对图像进行线性空间滤波,产生运动模糊图像,建立退化模型 → 通过radon变换来获取模糊参数,即点扩散函数PSF → 最后由估计得出的PSF再用维纳滤波对图像进行复原。由仿真实验得知,在已知PSF的情况下使用自相关函数的维纳滤波法对图像进行复原可以获得较好的复原效果,因此难点在于如何精确地估计运动模糊参数PSF。

1、基本原理:

      点扩散函数PSF主要有两个重要参数:(1)模糊方向;(2)模糊尺度。本次主要是针对第一个参数----模糊方向的估计进行了研究。运动模糊方向是指运动方向与水平方向的夹角,由文献得知运动模糊主要是降低了运动方向的高频成分,而对其他方向的高频成分影响较小。常见的辨识方法有频域法和倒谱法,wym 两种方法都试过,仿真实验结果表两种方法各有好处。

      频域法的原理是将退化图像进行二维傅里叶变换,得到具有相互平行的规则明暗条纹的频谱。设暗纹与 x 轴正向夹角为 φ ,运动模糊方向与 x 轴夹角为 θ ,图像尺寸为 M × N,根据傅里叶变换的时频特性可以知道,可通过公式 tan(θ) = tan(φ − 90°) × M/N  得到模糊角度 θ ,因此只要通过 Radon 变换检测出频谱暗条纹与水平方向的夹角即可到运动模糊方向。

      倒谱法的主要原理是先将退化图像进行二维傅里叶变换,然后取对数,再进行反傅里叶变换得到退化图像的倒频谱,分离出退化图像的模糊信息,进而通过 Radon 变换得到运动模糊方向。

      Radon 变换是对频谱图上某一指定角度进行线积分,通过计算1°~180°的Radon变换得到180列的矩阵 R,每一列向量是图像在一个角度上沿一族直线的积分投影,因为积分直线束与频谱中的亮暗条纹平行,所以所得的投影向量中应有一个最大值,在频域法中最大值所对应的列数就等于模糊方向与x轴正方向水平夹角;在倒谱法中,最大值对应的列数 ±90°即为所求的模糊角度。

      具体理论和公式推导就不列出来了。。有兴趣的同学请 STFW。。(什么?不知道什么是 STFW? 请自行 STFW。。)

2、算法实现:

  (1)频域法

    1)     对模糊图像进行灰度化,并计算其二维傅里叶变换;

    2)     对傅里叶变换值的动态范围进行压缩;

    3)     对压缩后的结果进行循环移位,使其低频成分居中;

    4)     用canny算子对压缩居中后的频谱图像进行边缘检测使其二值化;

    5)     将二值化后的频谱图做从1°~180°的radon变换;

    6)     找出radon变换后的矩阵中的最大值,求出其对应的列数 n;

    7)     通过公式 tan(θ) = tan(φ − 90°) × M/N  = tan(n − 90°) × M/N 求出运动模糊方向。

    实现代码:

    

 1 close all;
 2 clear all;
 3 
 4 %% 读入并显示图像
 5 filename = 'ex.jpg';
 6 I = imread(filename);
 7 
 8 figure
 9 imshow(uint8(I));
10 title('原图');
11 
12 %% 生成运动模糊图像
13 PSF = fspecial('motion',50, 120);
14 g = imfilter(I, PSF, 'circular');
15 figure 
16 imshow(uint8(g));
17 title('运动模糊图');
18 
19 %% 对运动模糊图像进行灰度化,并进行二维快速傅里叶变换,生成其频谱图
20 gb = rgb2gray(g);
21 figure
22 imshow(uint8(gb));
23 PQ = paddedsize(size(gb));
24 F = fft2(gb, PQ(1), PQ(2));
25 figure
26 imshow(uint8(F));
27 
28 
29 %% 将频谱压缩,居中
30 H = log(1+abs(F));
31 Hc = fftshift(H);
32 figure
33 imshow(uint8(Hc));
34 
35 %% 用canny算子将压缩居中后的频谱图进行边缘检测,二值化
36 T = graythresh(Hc);
37 bw=edge(Hc, 'canny', T);
38 figure
39 imshow(bw);
40 
41 %% 对二值化后的频谱图进行radon变换
42 theta = 1:180;
43 R = radon(bw, theta);
44 figure
45 imshow(R);
46 
47 %% 计算出通过radon变换求出的模糊角度
48 MAX = max(max(R));
49 [m, n] = find(R == MAX);
50 [M,N] = size(Hc);
51 beita = atan(tan(n*pi/180)*M/N)*180/pi;

 

     (2)倒谱法:

        

       1)     对模糊图像进行灰度化,并计算其二维傅里叶变换;

       2)     对傅里叶变换取对数,平方后再进行一次反傅里叶变换得到其倒频谱;

       3)     对倒频谱动态范围进行压缩;

       4)     对压缩后的结果进行循环移位,使其低频成分居中;

       5)     用canny算子对压缩居中后的频谱图像进行边缘检测使其二值化;

       6)     将二值化后的频谱图做从1°~180°的radon变换;

       7)     找出radon变换后的矩阵中的最大值,其对应的列数±90°即为所求的模糊方向。

       实现代码:

       

 1 close all;
 2 clear all;
 3 
 4 %% 读入并显示图像
 5 filename = 'ex.jpg';
 6 I = imread(filename);
 7 
 8 figure
 9 imshow(uint8(I));
10 title('原图');
11 
12 %% 生成运动模糊图像
13 PSF = fspecial('motion',80, 150);
14 g = imfilter(I, PSF, 'circular');
15 figure 
16 imshow(uint8(g));
17 title('运动模糊图');
18 
19 %% 对运动模糊图像进行灰度化,并进行二维快速傅里叶变换,生成其频谱图
20 gb = rgb2gray(g);
21 figure
22 imshow(uint8(gb));
23 PQ = paddedsize(size(gb));
24 F = fft2(gb, PQ(1), PQ(2));
25 figure
26 imshow(uint8(F));
27 
28 %% 作出倒频谱
29 F1 = log(1+abs(F));
30 F2 = abs(F1).^2;
31 F3 = real(ifft2(F2));
32 figure
33 imshow(uint8(F3));
34 
35 
36 %% 将倒频谱压缩,居中
37 H = log(1+abs(F3)); % 将倒频谱动态范围进行压缩
38 Hc = fftshift(H); % 将压缩结果进行循环移位,使低频成分居中
39 figure
40 imshow(uint8(Hc));
41 
42 %% 通过阈值处理,边缘检测“canny”算子二值化倒频谱
43 T = graythresh(Hc);
44 bw=edge(Hc, 'canny', T);
45 figure
46 imshow(bw);
47 
48 %% 对倒频谱从1°到180°作radon变换,以求出模糊角度
49 theta = 1:180;
50 R = radon(bw, theta);
51 figure
52 imshow(R);
53 
54 %% 计算出通过倒频谱radon变换估计出的模糊角度
55 MAX = max(max(R));
56 [m, n] = find(R == MAX);
57 if 90 < n <= 180
58    beita = n - 90;
59 else if 0 < n < 90
60         beita = n + 90;
61     else if n == [90;90] | n == [180;180]
62             beita = n(1);
63         end;

 

7、仿真结果及算法评价:

     经过反复、多次、令人崩溃的调试参数,得出结论,模糊角度估计的精确度主要与边缘检测的阈值 T,运动模糊尺度和运动模糊方向这三个变量有关,经调试,阈值选取由graythresh 函数算出的全局阈值效果较好,而模糊尺度越小精度越低。

      频域法:当模糊尺度大于20像素时,模糊方向处于0°~90°时,检测效果很好,误差低于0.5°,但当模糊尺度低于20像素时,由于中心化处理致使频谱图中间出现的十字亮线使模糊方向常为90°或180°;而当模糊尺度大于90°时,估计到的模糊方向像脱缰的野马。。完全不知道怎么才能估计出如此恶心的数据的。。

     倒谱法:当模糊尺度大于40像素时,可以检测的模糊方向较大,基本从0°到180°都可以检测,不过误差在5°左右,模糊尺度小于40像素时误差就很大了,不能用作检测

 

                                                                                                                                       。。。。。。。。。。以上

 

转载于:https://www.cnblogs.com/yomman/p/3424494.html

2017-07-26 20:51:16 junmuzi 阅读数 3565

1,摄像机

摄像机其实就是把相机拍摄的图像转化为视频格式,摄像机每秒拍摄多少张图像,就是说该相机的快门频率。


2,视频运动模糊

归根结底,还是因为相机的快门不够快。快门速度越快,运动物体就会在底片呈现更清晰的影象,反之,快门速度越慢,运动的物体就越模糊。

可以参考下面的例子:

例子:
你看见一个人在步行
用1秒的慢快门拍摄,他应该步行了一到两步的距离
照片就会拍出他有残影
如果用1/1000秒的快门拍摄
纵使他就步行,但1/1000他能动得了多少?
所以拍出来的照片就会拍到一个步行的人了
图左是用慢快门 <--->图右是用较快的快门

你就可以明显的分别,移动中的物件与快门时间的关系

所以拍摄快速的运动,要用很快的快门才能够抓住清晰的主体

Ref:

https://zhidao.baidu.com/question/587586043.html

http://bbs.fengniao.com/forum/2857320_1.html

2019-01-21 10:17:59 xiaobai11as 阅读数 3289
  • 前言

1.什么是运动模糊

在用摄像机获取景物图像时,如果在相机曝光期间景物和摄像机之间存在相对运动,例如用照相机拍摄快速运动的物体,或者从行驶中的汽车上拍摄外面静止不动的景物时,拍得的照片都可能存在模糊的现象,这种由于相对运动造成图像模糊现象就是运动模糊。

2.运动模糊的图形修复的重要性

运动模糊在我们生活中很常见,例如在高速行驶的火车和汽车中拍摄窗外景色,拍摄高速运动的物体,等都有可能出现这种现象。因此运动模糊的图像复原成为重要的话题,广泛运用于工业控制、道路监控、军事、天文观测、医学图像以及刑侦领域,具有现实意义。

3.我的调查方式

首先学习运动模糊的相关基础知识,图像恢复的经典方法。然后根据Xin Tao、 Hongyun Gao等人的论文—— 用于深度图像去模糊的尺度递归网络(Scale-recurrent Network for Deep Image Deblurring CVPR 2018)仔细研究。大体总结一下运动模糊的图像修复大体发展情况。然后对里面参考文献进行简单调查。(调查发现大多数方法都会在这篇论文中提到)。同时过程中也对其他(这篇论文没有提到)运动模糊的图像修复的方法进行总结。

4.运动模糊的图像修复大体发展情况

经典方法

进一步改进 大提升

深度CNN加入1,遵循传统框架,其中几部分用CNN代替。

深度CNN,特殊设计图像处理网络:编码器-解码器网络,多尺度网络等

  • 调查结果

大体几类

格式:方法名,简单介绍,结果展示,适用性。

 

经典的方法

1.逆滤波法

逆滤波的方法是直接将退化过程H的逆变换与退化图像进行反卷积。利用傅里叶变换卷积特性,上述过程可以表述为:

结论:逆滤波对于没有被噪声污染的图像很有效,但是实际应用中,噪声通常无法计算,因此通常忽略加性噪声,而当噪声存在时,该算法就对噪声有放大作用,如果对一幅有噪声的图像进行恢复,噪声可能占据了整个恢复结果。

逆滤波算法不适合用来恢复含有噪声的图像。

2.维纳滤波

Wiener滤波恢复是在假定图像信号可近似看作平稳随机过程的前提下,按照使原图像      与恢复后的图像      之间的均方误差  达到最小的准则,来实现图像恢复的。

结论:

       随着k值不断减小,图像噪声越来越明显,但字符的轮廓越来越清晰。在极端情况下即k=0,维纳滤波退化为逆滤波。另一方面,当k值不断增大时,图像边缘越来越模糊。

      比较几幅复原图像可以发现,K越大,抑制噪声效果越好,恢复越不准确,文字较为模糊,边缘模糊不清,不能对文字进行很好地分割;而当K越小,恢复越准确,文字轮廓清晰,但对噪声抑制效果越不好,也无法很好地对文字进行分割。

      通过比较发现,当k=0.01时,能够取得较好的恢复效果,在同样的分割阈值下,能够去掉大部分噪声,字体轮廓也相对清晰,去噪后即可对文字进行较好地边缘提取。

  K的选取原则是:噪声大,则K适当增加,噪声小则K适当减小。一般取0.001一0.1之间,视具体情况而定。

3.Richardson-Lucy算法

Richardson-Lucy(RL)算法是一种迭代方法,在符合泊松统计前提下,迭代公式

结论:对于没有噪声干扰的理想模糊图像,迭代次数越多恢复的效果越好。在无噪声情况下,RL每次迭代时,都会提高解的似然性,随着迭代次数的增加,最终将会收敛在最大似然性的解,但随着迭代次数的增加也会增加计算量。

      但是对于含有噪声干扰的模糊图像,随着迭代次数的增加噪声被放大,而且迭代时间随之增长。当迭代超过50次后,恢复结果并没有明显改观。

进一步提高(抑制伪影,提高质量)

4.Rob Fergus等人的Removing Camera Shake from a Single Photograph

以 往的芒去卷积方法要么对卷积核有很强的先验假设, 要么对图像有一定的先验假设, 或者两者都有。 而且, 这些方法往往不大适合较大的模糊核。 Fergus等提出的方法完全抛弃了这些束缚, 实现了真正意义上的实用的BID。 在此文中, 图像去模糊主要被划分成了两步:卷积核的估计和去模糊。 其中去模糊可以采用任何一种现有的算法, 因此此文的重点集中在了模糊核的估计上。 基于对图像模型统计特性的分析, 此文深入研究了模糊图像和非模糊图像的梯度分布, 提出了一种基于梯度分布模型的去模糊算法。自然的清晰的图像满足特定的heavy-tailed distribution, 而模糊图像的梯度分布则想去甚远。 因此Fergus等基于这种特性构造了在已知观测图像情况下原始图像和模糊核的联合后验概率, 后验概率最大化对应的原始图像和卷 积核的组合就是所要的答案。

结论:恢复的效果不够好, 有的噪声实在太大。此文只是提出了一种思想,有新意, 效果有待完善。这篇文章留下了很多漏洞, 能让后人在此基础上对 这项工作有所完。果不其然,在随后几年的cvpr, siggraph, iccv, eccv上, 几乎每届都有更好的结果涌现。

5.Qi Shan等人High-quality Motion Deblurring from a Single Image(单幅图像的高质量运动去模糊)

在本论文当中,我们通过探究盲反卷积问题产生的可视人工痕迹例如振铃效应产生的原因开始。我们的研究表明,如果模糊图像没有噪声并且卷积核被准备无误的估计而没有误差,现有的反卷积方法能够高效的执行的很好。因此,我们注意到,一个固有的带噪图像的好的模型和一个更明确的处理由于卷积核估计误差造成的可视人工痕迹的方法,对于产生好的结果是有本质上的提高的。基于这样的想法,我们提出了一个统一的概率模型。不管是盲反卷积问题还是非盲反卷积问题,通过一种高级的迭代优化方案,交替地估计卷积核和复原图像直到收敛。这种方法的迭代过程就是求解相应的最大后验概率问题。我们的算法使用一个很粗糙的核估计方法来初始化卷积核【例如一条直线】,我们的方法收敛的结果能够保持复杂图像的结构和边缘细节的清晰,同时避免人工振铃的痕迹,参见图一。

为了实现这些结果,我们的技术主要得益于三方面的因素。首先,一种新的图像噪声空域随机分布模型。这个模型有助于我们分离在图像的噪声估计和卷积核估计过程中产生的误差。在之前的方法当中,这种混合误差是产生人工痕迹的关键因素。其次,引入新的平滑约束项。平滑约束是我们强加在自然图像当中的区域,这一区域在我们观测图像上具有低对比度。这一约束不仅能够在图像的平滑区域有效的抑制人工振铃效应,而且在图像纹理区域也能做到如此是效果。这种约束的扩散对于卷积核的估计阶段也是有效的。我们的最后一个策略是采用了一种有效的优化算法。优化算法采用了一些高级的优化策略和技术,例如变量替换,帕斯瓦尔定理,从而在计算上能够在频率域高效地执行优化过程。

结论:

效果不错,对于适度模糊的图像,可以令人满意地恢复边缘,颜色和纹理信息。需要特定硬件支持。可以成功地去除大多数运动模糊图像。 然而,当模糊图像受到不是移位不变的模糊影响时,例如,由于轻微的相机旋转或不均匀的物体运动,发生一种失效模式。

其他:

其他 自然图像先验被设计用于抑制伪影并提高质量的有:总变差(TV)(T. F. Chan and C.-K. Wong. Total variation blind deconvolution.),稀疏图像先验(A. Levin, Y. Weiss, F. Durand, and W. T. Freeman. Understanding and evaluating blind deconvolution algorithms),重尾梯度先验(Q. Shan, J. Jia, and A. Agarwala. High-quality motion deblurring from a single image. volume 27, page 73. ACM,2008.),超拉普拉斯先验(D. Krishnan and R. Fergus. Fast image deconvolution using hyper-laplacian priors. In NIPS, pages 1033–1041, 2009.),l0-范数梯度先验(L. Xu, S. Zheng, and J. Jia. Unnatural l0 sparse representation for natural image deblurring. In CVPR, pages 1107–1114. IEEE, 2013.)等。这些传统方法大多数遵循粗到细的框架,例外情况包括频域方法,它们仅适用于有限的情况。

卷积网络加入

在传统框架中用CNN模块部分代替。

6. Sun等人的Learning a Convolutional Neural Network for Non-uniform Motion Blur Removal(学习卷积神经网络用于非均匀运动模糊去除)

我们提出了一种深度学习方法,使用卷积神经网络(CNN)预测斑块水平的运动模糊的概率分布。 我们使用精心设计的图像旋转进一步扩展CNN预测的候选运动核集。 然后使用马尔可夫随机场模型来推断实施运动平滑度的密集非均匀运动模糊场。 最后,使用补丁级图像先验通过非均匀去模糊模型去除运动模糊。

27Two-phase kernel estimation for robust motion deblurring提出了一种基于使用空间先验和迭代支持检测(ISD)内核细化的高效且高质量的核估计方法,以从显着的运动模糊中恢复图像。我们的方法可以估计非常大的模糊内核(即PSF)并去除 显着模糊,没有太多的手动调整。(有GPU加速下速度几秒))

结论:

使用网络来预测模糊方向和宽度。可以有效地估计和消除以前的方法无法很好地处理的复杂的非均匀运动模糊。

其他CNN部分代替

(C. J. Schuler, M. Hirsch, S. Harmeling, and B. Schölkopf. Learning to deblur)以粗到细的方式堆叠多个CNN以模拟迭代优化,(A. Chakrabarti. A neural approach to blind motion deblurring)预测了频域中的反卷积核。

CNN其他网络结构

7. S. Su, M. Delbracio等人的.Deep video deblurring.

编码器 - 解码器网络与跳过连接学习框架

结论:

这项工作的一个限制是我们只解决视频中存在的模糊类型的一部分,特别是我们关注由于手持相机运动引起的相机抖动而产生的运动模糊。除了以上限制,我们的方法产生的结果通常与最先进的方法一样好或优于最先进的方法,没有参数调整且没有明确需要挑战图像对齐。一秒内产生高质量的结果,这比现有方法快得多,其中许多方法每帧需要几分钟

 

8. S. Nah, T. H. Kim, and K. M. Lee. Deep multi-scale convolutional neural network for dynamic scene deblurring(用于动态场景去模糊的深度多尺度卷积神经网络)

我们提出了一种多尺度卷积神经网络,以端到端的方式恢复清晰的图像,其中模糊是由各种来源引起的。总之,我们提出模仿传统粗到细的多尺度损失函数方法。此外,我们提出了一个新的大型数据集,它提供了一对逼真的模糊图像和相应的地面真实清晰图像这些图像是由高速摄像机获得的。

结论:

方法在动态场景去模糊中不仅在质量上,而且在数量上达到了最先进的性能(2017)。时间后面会有和它对比的时间。

9. Xin Tao , Hongyun Gao 等人Scale-recurrent Network for Deep Image Deblurring(用于深度图像去模糊的尺度递归网络)

完善了多尺度,每个尺度下的求解器和相应参数通常是相同的,跨尺度共享网络权重。优点是双重的。首先,它显着减少了可训练参数的数量;其次,我们提出的结构可以包含循环模块,其中隐藏状态捕获有用信息并有利于跨规模恢复。

在本文中,他们表明直接应用现有的编码器 - 解码器结构不能产生最佳结果。相反,他们的编码器 - 解码器ResBlock网络放大了各种CNN结构的优点,并产生了训练的可行性。它还产生一个非常大的感受野,这对于大动作去模糊是至关重要的。

 

结论:

训练效率快,可以在数量上和质量上产生比现有方法更高质量的结果。

 

  • 结尾

经过好多天的总结,写出来的,读者可以按照本文的研的方法,进行自己的研究。希望对大家有所帮助。

2018-12-13 14:34:21 h5502637 阅读数 870

运动模糊是个经常会用到的效果,常见的实现步骤是:

  1. 对深度纹理进行采样,取得当前片元的深度信息
  2. 根据深度信息建立当前片元的NDC空间的坐标curNDCPos
  3. 把curNDCPos乘以当前VP矩阵的逆矩阵(即View*Projection)-1,得到当前片元的世界空间坐标WorldPos
  4. 把WorldPos乘以上一帧的VP矩阵(即View*Projection),得到上一帧在裁切空间中的位置 lastClipPos
  5. 把lastClipPos除以其w分量,得到NDC空间位置lastNDCPos
  6. 用当前片元NDC空间位置 减去 上一帧NDC空间位置(即 curNDCPos-lastClipPos),得到速度的方向speed
  7. 沿speed方向进行多次采样,求出平均值作为当前片元的颜色

在Unity中实现运动模糊需要后处理的配合,在后处理代码中需要把 摄像机的depthTextureMode 设置为 DepthTextureMode.Depth(这样在shader中才能使用深度纹理),还要当前VP逆矩阵和上一帧的Vp矩阵传递给shader。

效果图:

C#代码:

using UnityEngine;

public class MotionBlur_CameraMove : MonoBehaviour
{
    [Range(0, 0.5f)]
    public float BlurSize;

    private Material m_mat;
    private const string ShaderName = "MJ/PostEffect/MotionBlur_CameraMove";
    private Matrix4x4 m_curVP_Inverse;                              // 当前 VP矩阵的逆矩阵 //
    private Matrix4x4 m_lastVP;                                           // 上一帧的Vp矩阵 // 
    private Camera m_cam;

    void Start()
    {
        Shader shader = Shader.Find(ShaderName);
        if (shader == null)
        {
            enabled = false;
            return;
        }

        m_mat = new Material(shader);

        m_cam = Camera.main;
        if (m_cam == null)
        {
            enabled = false;
            return;
        }

        m_cam.depthTextureMode = DepthTextureMode.Depth;
    }

    void OnRenderImage(RenderTexture srcRT, RenderTexture dstRT)
    {
        if (m_mat == null || m_cam == null)
        {
            return;
        }

        Matrix4x4 curVP = m_cam.projectionMatrix*m_cam.worldToCameraMatrix;
        m_curVP_Inverse = curVP.inverse;

        m_mat.SetFloat("_BlurSize", BlurSize);
        m_mat.SetMatrix("_CurVPInverse", m_curVP_Inverse);
        m_mat.SetMatrix("_LastVP", m_lastVP);

        Graphics.Blit(srcRT, dstRT, m_mat, 0);

        m_lastVP = curVP;
    }
}

Shader代码:

Shader "MJ/PostEffect/MotionBlur_CameraMove"
{
	Properties
	{
		_MainTex ("Main Texture", 2D) = "white" {}
		_BlurSize("Blur Size", Range(0, 10)) = 1
	}

	SubShader
	{
		Tags { "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" }

		Cull Off
		ZWrite Off
		ZTest Always
		
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag			
			#include "UnityCG.cginc"			

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				float2 uv : TEXCOORD0;
				float2 uv_depth : TEXCOORD1;
			};

			sampler2D _MainTex;
			float2 _MainTex_TexelSize;
			float4 _MainTex_ST;
			sampler2D _CameraDepthTexture;

			uniform float _BlurSize;
			uniform float4x4 _CurVPInverse;
			uniform float4x4 _LastVP;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);
				o.uv_depth = TRANSFORM_TEX(v.uv, _MainTex);

			#if UNITY_UV_STARTS_AT_TOP
				if (_MainTex_TexelSize.y < 0)
				{
					o.uv_depth.y = 1-o.uv_depth.y;
				}
			#endif
				return o;
			}

			float4 frag (v2f i) : SV_Target
			{
				float2 uv = i.uv;
				float depth = tex2D(_CameraDepthTexture, i.uv_depth);

				// float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
				// depth = Linear01Depth(depth);

				float4 curNDCPos = float4(uv.x*2-1, uv.y*2-1, depth*2-1, 1);
				float4 worldPos = mul(_CurVPInverse, curNDCPos);
				worldPos /= worldPos.w;											// 为了确保世界空间坐标的w分量为1 //
				// worldPos.w = 1;
				float4 lastClipPos = mul(_LastVP, worldPos);
				float4 lastNDCPos = lastClipPos/lastClipPos.w;					// 一定要除以w分量, 转换到 NDC空间, 然后再做比较 //

				float2 speed = (curNDCPos.xy - lastNDCPos.xy)*0.5;				// 转到ndc空间做速度计算 //
				float4 finalColor = float4(0,0,0,1);
				for(int j=0; j<4; j++)
				{
					float2 tempUV = uv+j*speed*_BlurSize;
					finalColor.rgb += tex2D(_MainTex, tempUV).rgb;
				}
				finalColor *= 0.25;
				return finalColor;				
			}
			ENDCG
		}
	}
	
	Fallback Off
}

根据 [官网文档] (https://docs.unity3d.com/Manual/PostProcessingWritingEffects.html) 中的说明 建议写上一些几句:

Cull Off
ZWrite Off
ZTest Always

后处理shader需要包含的一些状态

由于后处理shader中使用了一张以上的纹理(_MainTex和_CameraDepthTexture),因此需要手动把uv的y坐标翻转下,以保持两张图uv的y坐标方向保持一致:

#if UNITY_UV_STARTS_AT_TOP
	if (_MainTex_TexelSize.y < 0)
	{
		o.uv_depth.y = 1-o.uv_depth.y;
	}
#endif

对深度纹理进行采样可以使用 unity自带的方法SAMPLE_DEPTH_TEXTURE 也可以直接对 _CameraDepthTexture 进行采样:

float depth = tex2D(_CameraDepthTexture, i.uv_depth);

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);

两种方式都能获取到深度值,大部分平台上都可以用直接采样的方式获取深度值,但是一些平台需要做些特殊处理例如PSP2,因此使用 SAMPLE_DEPTH_TEXTURE 方式更安全,因为unity内部对各种宏进行了判断,能确保在不同的平台都能正确地得到深度值。

HLSLSupport.cginc文件中的描述

效果图:

最后感谢冯乐乐大神的书和博客。

package文件
提取码:vpud

参考链接:
https://docs.unity3d.com/Manual/PostProcessingWritingEffects.html
https://docs.unity3d.com/Manual/SL-PlatformDifferences.html