【NPR】铅笔画
写在前面
今天打算写一篇与Unity基本无关的文章。起因是上周我偶然搜到一个网站,里面实现的效果很不错,后来发现其源自2012年NPAR会议的最佳论文。阅读论文后,我觉得实现难度不算大,便打算动手尝试。国内有两位博主写过相关文章,分别是风吹夏天和Imageshop的文章。通过研读论文和这两篇博客,我基本掌握了算法原理,接下来就是编码实现。原论文作者给出了算法第一部分的代码,风吹夏天的文章在此基础上给出了笔画部分的代码,但都不完整。
我这篇文章就不再详细阐述算法了,有兴趣的读者可以阅读原论文或上述两位博主的文章,他们都讲解得比较清晰。我将实现的代码上传到了GitHub的PencilDrawing项目中,包含了全部代码,基本能实现论文中的效果。不过由于没有使用论文中采用的铅笔纹理,所以无法做到100%还原。有兴趣的读者可以下载代码尝试一下。
代码
我的 GitHub的PencilDrawing项目(Matlab代码)。
相关论文
Lu C, Xu L, Jia J. Combining sketch and tone for pencil drawing production[C]//Proceedings of the Symposium on Non-Photorealistic Animation and Rendering. Eurographics Association, 2012: 65 - 73.
概述
本文的目标是输入一张图像,输出一张铅笔画风格的图像。你可能会说,PS有类似的滤镜功能。确实,PS具备相关功能,但效果欠佳。虽然技术娴熟的用户可以通过多步操作获得更好的效果,但本文介绍的算法能够对输入图像一次性生成效果较好的铅笔画。例如下面的图像:
仔细观察可以发现,该论文实现的效果具有以下特点:一是图像轮廓具有手绘的线条感,并非像许多传统算法那样仅进行边缘检测;二是图像整体色调更符合铅笔画的特征,具有铅笔的笔触感,色调也更接近真实的铅笔画。
算法
详细的算法内容我不再赘述,有兴趣的读者可以查阅原论文和前面提到的两篇文章。这里大致介绍一下算法流程:
- 生成笔画图像:通过对图像计算梯度,对像素进行方向分类,然后将每个方向的像素分别与对应的算子进行滤波,从而得到笔画图像。
- 生成色调图像:这一步采用了论文中所谓的“基于模型的色调转换”。具体做法是对真实铅笔画的直方图进行分析,将整个直方图划分为三个部分:最亮部分、中间灰度过渡部分和最暗部分。作者使用三个方程分别对这三个部分进行建模,再利用一组权重将它们混合,得到目标直方图。这些权重是通过实验学习得到的,论文中提到了三组不同的权重,我在GitHub的项目页也有相关说明。最后,根据目标直方图对图像进行直方图匹配,即可得到所需的色调图像。不过,这一步很难得到与论文完全一致的结果,推测是论文中省略了一些参数和小修改,可能进行了图像平滑操作。
- 生成铅笔画风格的图像:这一步需要使用一张铅笔画纹理,例如:[此处可补充纹理示例图片的说明或链接]。使用该纹理模拟上一步得到的色调图,具体方法是对铅笔画纹理进行指数运算,并保证局部平滑求最优解。这一步使用的纹理对最终画面效果至关重要,可惜作者未给出论文中使用的铅笔画纹理,我只能从网上寻找,因此效果无法完全一致。局部平滑度越低,得到的结果图像的铅笔笔触越明显。
- 合成最终图像:将第一步得到的笔画图像和第三步得到的图像相乘,即可得到最终的铅笔画风格图像。对于彩色图像,需要先将图像从RGB空间转换到YUV空间,仅对Y分量进行上述处理,最后再转换回RGB空间。
结果
该算法的健壮性较好,作者在其网站上展示了大量示例。但根据我的实践经验,很多图像要获得较好的效果,需要不断调整参数。比较重要的参数包括:
- 生成色调图时使用的三个混合权重:我通常使用最亮的那一组。
- 笔画的长度:对于小图(如400x400),可使用较小的算子,如6或7;大图则可适当使用10以上大小的算子,这会影响生成图像中轮廓的铅笔笔画长度。
- 图像亮度调整:对于较暗的图像,可能需要进行类似伽马计算的操作,以整体提亮画面。
下面展示一些效果示例:
- 作者给出的测试图之一:[此处可插入图片]
- 武汉大学的落叶:照片来自昨天我朋友圈中一位武大同学分享的内容。虽然单看一张落叶照片效果不明显,但不得不说武大的景色很美。[此处可插入图片]
- 上海交大徐汇校区的图书馆:这是我的母校。[此处可插入图片]
其他示例就不一一展示了,有兴趣的读者可以直接到GitHub上下载代码查看。最后,希望大家玩得开心!