前言
最近在阅读NPR相关论文时需要用到线积分卷积,因此查阅了一下相关论文以及网上已有的实现方法。本文主要讲解线积分卷积的快速实现版本(SIGGRAPH,1995)。为了便于实验和测试,用Python实现并进行了简单的封装。如果本文讲解部分有任何理解错误,希望明确指出。
线积分卷积原理
首先,我们需要获取一张图像的二维矢量场,基于每个像素点上矢量的方向来构造一条流线。构造流线的方式在最早的论文中使用了DDA算法。通过以下的公式来进行迭代:
P 0 = ( x + 0.5 , y + 0.5 ) P_0 = (x+0.5,y+0.5) P0=(x+0.5,y+0.5)
P i = P i − 1 + V ( P i − 1 ) ∣ V ( P i − 1 ) ∣ ∗ △ s i − 1 P_i = P_{i-1} + \frac{V(P_{i-1})}{|V(P_{i-1})|}*\bigtriangleup{s_{i-1}} Pi=Pi−1+∣V(Pi−1)∣V(Pi−1)∗△si−1
此处 V ( P i − 1 ) V(P_{i-1}) V(Pi−1)代表的是矢量场 P i − 1 P_{i-1} Pi−1坐标下的数值。
△ s i − 1 \bigtriangleup{s_{i-1}} △si−1的含义是在 P i − 1 P_{i-1} Pi−1点处上下左右四个方向中变化量最小的方向的数值,计算的方式也很简单,请参考论文1。
另外需要注意的是此处需要计算出一条流线,但是初始的点只有一个,所以要考虑到向前查找和向后查找,即:
P i = P i − 1 − V ( P i − 1 ) ∣ V ( P i − 1 ) ∣ ∗ △ s i − 1 P_i = P_{i-1} - \frac{V(P_{i-1})}{|V(P_{i-1})|}*\bigtriangleup{s_{i-1}} Pi=Pi−1−∣V(Pi−1)∣V(Pi−1)∗△si−1
这样逐步查找构造一条流线的方式本质上就是递归来寻找下一个点,结束条件依靠 △ s i − 1 \bigtriangleup{s_{i-1}} △si−1数值设定一个阈值来控制。
最后,通过刚才构筑好的多条流线通过卷积运算的方式就可以计算出每一个像素点的数值。此处计算方式不是很难,参考第一篇论文就可以进行计算。
时间原因,以后再补充关于快速运算的算法。TODO
不足
参考文献1的问题就在于运算速度过慢,因而可以通过第二篇的快速算法来进行一定的加速处理2。
当然这两篇的实现都是在CPU上进行的,可以通过并行处理的方式进行一定加速。
后续我会补充GPU上的实现算法。
实验结果
输入图片
输出图片
参考资料
- http://www.cs.utah.edu/~wmartin/cs523project/
- https://github.com/philogb/LIC
- https://github.com/tatsy/lime
---------------------------------------------20181019更新----------------------------------
附上WebGL版的demo
Cabral, Brian, and Leith Casey Leedom. "Imaging vector fields using line integral convolution." Proceedings of the 20th annual conference on Computer graphics and interactive techniques. ACM, 1993. ↩︎
Stalling, Detlev, and Hans-Christian Hege. "Fast and resolution independent line integral convolution." Proceedings of the 22nd annual conference on Computer graphics and interactive techniques. ACM, 1995. ↩︎