最近对非现实图像处理感兴趣。
在网上翻翻,发现了这篇挺有意思的论文——《Painterly Rendering with Curved Brush Strokes of Multiple Sizes》,作者Aaron Hertzmann。
这篇论文主要描述了一种用计算机模仿人类画油画的算法。该算法用不同尺寸的笔刷以不同的精细程度,从粗到细,层层叠加,最终完成绘画。
算法过程就是用从大到小的笔刷半径循环调用paint()函数
for Ri in R[1:n]; paint(sourceImage,Ri); end;
函数paint
paint函数:以某种半径的笔刷在画布上放置笔划。
算法的伪代码
输入:sourceImage表示原图,Ri表示,从大到小,不同的笔刷半径 1..n。
输出:canvas表示画布。
1. 先使用一个初始颜色铺满画布,为了让笔刷能够覆盖整个画布。因此这个颜色值需要设成一个和图像中所有颜色都足够差异的特殊值MAXINT。
2. 先以笔刷半径乘以一个常参数fσ 为标准差fσ Ri,产生高斯滤波器核,然后用原图卷积高斯滤波器,生成参考图像referenceImage。也就是说,对原图高斯模糊操作后生成参考图像referenceImage。
3. paintLayer() 在参考图像referenceImage与画布canvas有差异的地方,以笔刷半径Ri画上笔划stroke。
函数paintLayer
paintLayer:循环遍历画布,在参考图像referenceImage与画布canvas有颜色差异的网格点【11】,以笔刷半径R画上笔划stroke。
算法的伪代码
输入:参考图像referenceImage,笔刷半径R
输出:画布canvas
1. 初始化一个空笔划集合S,用存储生成的笔划。
2.由difference函数,计算画布canvas与参考图像referenceImage的每个点的“颜色距离”。
|(r1,g1,b1)-(r2,g2,b2)|=((r1-r2)^2+(g1-g2)^2+(b1-b2)^2)^1/2
产生差异矩阵D。
D=abs(referenceImage-canvas)
为了让笔刷能够覆盖整个画布,画布在初始状态时需要设一个和图像中所有颜色都足够差异的特殊值MAXINT。
3.设置网格的长度 grid=fg x R ,网格是正方形
4.以网格长度为步长遍历差异矩阵D,计算网格点(x,y)领域M(x,y)的平均颜色距离误差areaError(x,y)。
areaError(x, y) =∑ i, j∈M(x, y) Di, j/ grid^2
M(x, y)= D(x-grid/2:x+grid/2, y-grid/2:y+grid/2)
5.如果平均颜色距离误差areaError(x,y)大于阈值T,则在领域M(x,y)的范围内颜色距离最大的点(x1,y1)上,以笔刷半径R生成笔划 makeStroke(R, x1, y1, referenceImage)。
6.当画布的所有笔划生成完毕后,以随机顺序渲染到画布canvas上。
上述第5步中的生成笔划方法可以有不同的实现方式,可以简单地在(x,y)以笔刷半径R画圆点,也可以沿图像颜色距离梯度的法线方向画线条。
我们通过 “在具有更多细节(高频信息)的图像区域上使用更小半径的笔刷,在具有少量细节的区域只使用大半径笔刷” 的方法实现聚焦细节的目标。通过这种方法可以选择不同的笔刷半径适配源图像中不同的细节等级。这种选择背后的原因是因为在通常情况下细节区域包含更重要的视觉信息。当然也可以有别的选择,比如强调前景物体或者人物,但这样一来就需要对图像进行语义分析。图像语义分析是机器视觉领域一个非常困难的问题。我们还可以把选择操作交给用户完成【16】。
产生弧形笔划
我们以反锯齿三次B样条曲线为笔划建模,每个B样条包括颜色和厚度(透明度),然后通过圆形笔刷掩膜沿着曲线轨迹渲染笔划。
在我们的系统中,我们约定每一笔笔刷笔划使用不变的颜色,并且使用图像梯度的方向放置笔划。其他作者也使用这个概念【11,8,18】放置笔划。通过不变的颜色可以描出图像的大致轮廓。我们的方法是沿着图像梯度法线方向放置B样条曲线的控制点。当笔划颜色偏离曲线控制点下的颜色超过具体的阈值时笔划在那个控制点结束。可以认为这是使样条曲线和参考图像的等值线大致匹配。
下面更具体地解释一下这个样条放置算法。
我们使用一个控制点列表、一个颜色和一个笔刷半径共同表示一个笔划。
1. 这个算法始于图像中(x0,y0),笔刷半径R。将控制点(x0,y0)加入列表,并且参考图像在(x0,y0)点的颜色设置为这个笔划的颜色。
2. 接下来我们需要计算出曲线上的下一个控制点。
首先计算参考图像的亮度。亮度计算公式:
L(r,g,b) = 0.30r + 0.59g + 0.11b
对参考图像的亮度值进行索贝尔滤波(Sobel-filtered luminance)
然后计算控制点(x0,y0)的梯度方向角
θ0=arctan(Gx0/Gy0)
因为笔划是沿着梯度的法线方向(颜色在梯度的法线方向变化最小),所以需要加上π/2。
θ0+π/2
从(x0,y0)点沿方向角(θ0+π/2)距离笔刷半径R,放置下一个控制点(x1,y1)。
使用笔刷半径R作为两个控制点间的距离是因为R代表着我们能用笔刷捕捉的细节等级。先用大笔刷创建图像的概貌,后续再用小笔刷精雕细刻。
3.重复步骤2计算剩余的控制点。整个过程沿着图像梯度法线方向放置控制点,直到碰到如下两种情况后结束。
a)达到最大的预定义笔划长度。
最大笔划长度是为了防止出现无限循环。
b)参考图像中最后一个控制点位置的颜色与笔划的颜色之差,大于参考图像中最后一个控制点位置的颜色与画布上现有颜色的色差。
笔划的颜色即(x0,y0)点的参考图像颜色。控制点的颜色即控制点所在位置的参考图像颜色,画布上现有颜色即画布上控制点位置的颜色。
另外需要注意,实际上存在两个法线方向,所以下个控制点有两个方向可以选择,θi+π/2和θi-π/2。为了笔划曲率的最小化,我们选择小于π/2的方向角Di。
我们还可以在笔刷方向上使用一个无限冲击响应滤波器,这样可以夸大或者减少笔刷的曲率。使用一个预定义的常量系数fc来控制这个滤波器。
设上一个笔划控制点方向
D’i-1=(dx’i-1, dy’i-1)
当前笔划控制点方向
Di= (dxi,dyi)
则 滤波后的笔划控制点方向
D’i= fc Di+ ( 1- fc )D’i-1= ( fc dxi+ (1- fc )dx’i-1, fc dyi+ (1- fc ) dy’i-1)
算法的伪代码
输入: 笔划的起始点位置 (x0,y0),笔刷半径R,参考图像
输出:笔划样条曲线控制点列表
其中最小笔刷长度是为了防止出现非常短的笔划引起斑点现象。
渲染曲线笔划
先用细分方法(subdivision)计算样条曲线,然后用一个反锯齿圆形掩膜沿着曲线路径绘制。
渲染风格
为了量化表征绘画风格,建议使用风格参数控制渲染过程。
在上述算法中用到的一些参数:
近似阈值 T —— 值越大产生的图像越粗糙。
笔刷尺寸 —— 除了定义笔刷列表 (R1:Rn)之外,还可以定义 最小笔刷R1、笔刷尺寸变化律Ri+1/Ri,笔刷数n。
曲率滤波器系数 fc ——用于控制笔划曲率
模糊因子 fσ —— 控制模糊核的尺寸
最小和最大笔划长度 minLength、maxLength ——以控制点个数为单位
不透明度α——设置绘画的不透明度,范围0-1 之间
网格尺寸 fg——控制笔划之间的空隙
颜色抖动 系数 —— jh,js,jv或者jr,jg,jb表示在 H,S,V 或者 R,G,B 分量中加入随机抖动
0表示没有抖动。
不同的系统设置形成不同的风格
印象派——正常的画风。无曲率滤波,无随机色
T= 100,R=(8,4,2),fc=1,fs=.5, a=1,fg=1, minLength=4, maxLength=16
表现派——细长的笔划,加上颜色抖动。
T= 50,R=(8,4,2),fc=.25,fs=.5, a=.7,fg=1, minLength=10, maxLength=16,jv=.5
水彩——松散、半透明的笔划,随机抖动
T= 200,R=(8,4,2),fc=1,fs=.5, a=.5,fg=1, minLength=4, maxLength=16,jr=jg=jb=.3
点点——以随机色调和饱和度密集画圆点
T= 100,R=(4,2),fc=1,fs=.5, a=1,fg=.5, minLength=0, maxLength=0,jv=1,jh=.3.