这样一个问题的来源,是我在思考怎样把一张二次元的彩色图片转化为一张完美的黑白的纯线稿图时遇到并需要解决的难关,这个问题听起来似乎并没有那么难,可实际上却超出想象,由于本来就不会复杂的算法,所以只能把问题简化了想,争取让什么算法都不会的人也能轻松明白其中的原理,目前整个解决方案也只是停留在思路阶段,下面主要整理这几天的思路作为学习笔记,方便之后完善代码。
解决该问题初步的步骤如下:
1.将图片黑白化,二值化
2.修复二值化后的白色和黑色杂质,去除大块黑色
3.搜索出整个线稿的路径(难点)
4.将路径的点连接的线段变成顺滑曲线(难点)
5.将曲线加粗
各步骤详细分析:
1.对于这种动漫图片来说可用公式
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
加权平均求出各点的灰度值(系数怎样确定暂且不讨论,我只是百度搬运工),然后使其R,G,B值都为此值
黑白化完毕。
然后规定一个阈值比如120,使大于此值的像素值变为255,小于此值变为0。
关于此阈值的选取方法,可多次改变阈值之后分析得到的图片,这里引入一个新概念叫连通分量,我们假设每个黑色像素都是一个结点,相邻的两个黑色像素之间有一条边连接,整张图片就可以就可以看成有许多连通分量的图,当我们取的阈值不合适时,就会导致连通分量特别大,原因见下图:
蓝圈处由于原图该处是渐变的颜色效果,就会导致出现黑白交错的块,零零碎碎的,所以我们只要改变阈值排除过大的连通分量即可。
二值化完毕。
2.修复小杂质我的思路是当那一块像素数量少于某个阈值时就整个抹去,对于大块黑暗,比如上图就有这样的大片黑暗,但是我们这次不能规定新的阈值使大于它的时候抹去这种大黑块了,因为这些黑块可能连着整个线稿,面临损失信息的风险,我的思路是,在图中找黑点,这些黑点满足条件:
以该点为圆心,R为半径做圆,该圆内所有点皆为黑色。
那我们就可以把这个圆内区域全都变成白色。其中R为图中的线稿线条宽度,这样,当黑块清理到边界时,就会留下黑色边界了(当然也有可能图象某些地方本来就有黑色的大块,这里也不讨论了),刚好是我们想要的效果。
3.这也是最棘手的问题,想了好几种方案,可能都不能完美解决这个问题,不过基本想法还是要有的,现在先简化问题,如图:
我们先来着手这个没有岔路没有分段且粗细均匀的“线稿”,如果能找出其轨迹,也算是一个大大的进步了。
针对上图的处理步骤:
1)以左上角为起点A,以曲线的宽度R为半径做一个圆(关于线稿的宽度,可以指定一个并不精确的值,也可以在图中先找一个线稿上的中心点然后看最大能做半径为多少的圆,使得圆内全为黑色,鉴于二值化得线稿并不一定粗细均匀,所以这样可能是徒劳)。
2)在圆边界上找一个点B,满足条件:A,B连线上所有点均为黑色。当然由于线是很粗的所以满足条件的点可能会有很多个,这时候只需要找出两个极端位置,求出倾斜角,求平均值作为最终B点即可(这只适用于没有分岔路的情况)。
3)以AB为直径做一个圆,把圆内的黑块全变成白块儿(主要是为了不走重复的路,所以走完就擦掉)
4)以B为新的起点,重复1,2步骤,不过要优先在AB延长线附近找满足条件的点,以此类推,直到没有满足条件的新的点诞生为止。
5)把这些按顺序存储到数组里,把相邻点之间生成一条直线,生成图片
完成。
对于岔路,我的想法是岔路之间是空的,应该是还可以分辨的,但是我不会多线程处理两条以上的路,所以只好把这个节点和方向先存到列表里,等一条路走完之后再回来走这条路。
对于多段断开的路,有必要在最开始就计算出整个图的连通分量,然后找到相应数量的起点,另外在探索之后也可以检测是否探索完了所有路,方法如下:
搜索全图,尝试找到满足如下条件的黑色点:
以该黑色点为圆心R为半径没有发现新的节点。
那么就可以断定这条路没走过,然后以它为起点向两端探索重复上述5个步骤即可。
4.最开始是打算把所有直线都近似成圆弧,使得节点处两圆弧顺滑对接,然而发现在急转弯处会有不协调的现象发生,于是改为
如下方法:
假设ABC三点依次相邻,AB的倾斜角设为a,现在:
1)从B点开始向(a+k)%360的方向延长d的距离,作为新的B点,而原来的B点变为A点
2)重复上述步骤,直到B点和C点重合(当B到C的方向已经和延长方向一致时,k变为0)
3)将中间经过的各点直线连接
其中,k和d为常数,当d足够小时,肉眼可认为是曲线。
顺滑化完成。
5.这一步就比较简单了,直接把第四步的单列(线稿)上的每一点存入数组,再一次以数组里的每一点为圆心R为半径的圆全变为黑色。
加粗完成。
(如果顺利的话可能会更新各步骤的代码)