原笔迹手写实现平滑和笔锋效果之:笔迹的平滑(一)

您所在的位置:网站首页 js手写签名像素多少 原笔迹手写实现平滑和笔锋效果之:笔迹的平滑(一)

原笔迹手写实现平滑和笔锋效果之:笔迹的平滑(一)

#原笔迹手写实现平滑和笔锋效果之:笔迹的平滑(一)| 来源: 网络整理| 查看: 265

之前研究过一种用于 模拟真实 手写笔迹签名 的算法,  要求能够保持原笔迹平滑,并有笔锋的效果.

在网上看了一些资料, 资料很多, 能够达到用于正式产品中的效果的一个都没有找到.

我看到最靠谱的一篇文章是这个:Interpolation with Bezier Curves

但是即使按照这篇文章讲的方法去实现手写笔迹, 表现的效果也非常的不理想.

而且, 这篇文章还只是涉及到了笔迹平滑的问题, 没有涉及到如何解决笔锋的问题

经过我一段时间的研究, 终于在上厕所的时候(有没有被duang了一下的感觉, 哈哈~O(∩_∩)O), 想出来了一种方法..先给大家展示两张在正式产品中的效果图:

前面两张图片是在手机上测试的效果,后面两张是在电脑上用鼠标写出来的效果.

 

当然, 必须承认, 图片中展示的效果效果的文字, 我反复写了很多次...随便画几条线大概是这样:

我将要介绍的这种算法, 还可以通过对某些参数的修改,  模拟出毛笔, 钢笔, 签字笔等各种笔...真实书写效果....

 

如果你还对贝塞尔曲线不了解, 我推荐查看这篇文章:史上最全的贝塞尔曲线(Bezier)全解,   所以, 在这里我会假设读者已经对Bezier曲线已经比较了解.

本文主要讲解 如何通过已知所有笔迹点, 计算出控制点, 使用3次bezier曲线拟合笔迹, 达到笔迹平滑的效果, 解决笔迹平滑的问题,.

除了本篇文章意外, 后面应该还会有两篇文章:

第二篇:介绍自己开发的一种笔迹拟合算法.

第三篇:主要介绍实现笔锋的效果.并提供最终的c++对此算法的实现的源代码和演示程序.

 Bezier曲线是通过简单地指定端点和中间的控制点(Control Point)来描绘出一条光滑的曲线, 三次贝塞尔曲线的效果是图片中这样:

当红色的圆点代表原笔迹点时, 想必大家想要的效果是下面图片中的蓝色线条, 而不是红色线条吧:

贝赛尔曲线拟合会经过前后两个端点, 但不会经过中间的控制点,所以, 我们通过贝塞尔曲线来拟合笔迹点的时候, 是要:

对于所有的笔迹点, 每相邻的一对笔迹点作为前后端点来绘制Bezier曲线, 所有我们需要找出一些满足某种规律的点作为这些端点中间的控制点.

下面请看下图:

图中, 点A, B, C为我们的原笔迹点, B' 和 B''为我们计算出来的控制点.

计算控制点的方法是:

1) 设定一个0到1的系数k,  在AB和BC上找到两点, b'和c', 使得距离比值, Bb' / AB = Bc' / BC = k  , 计算出两个点 b' 和 c'..(k的大小决定控制点的位置,最终决定笔迹的平滑程度, k越小, 笔迹越锐利; k越大,则笔迹越平滑.)

2) 然后在b' c'这条线段上再找到一个点 t, 且线段的长度满足比例: b't / tc' = AB / BC,

3) 把b' 和 c', 沿着 点 t 到 点B的方向移动, 直到 t 和 B重合. 由b'移动后得到 B', 由 c'移动后的距离得到B'', B'和B''就是我们要计算的位于顶点B附近的两个控制点.

实际项目过程中, 使用下面的规则进行绘制笔迹:

1) 当我们在手写原笔迹绘制的时候, 得到第3个点(假设分别为ABC)的时候, 可以计算出B点附近的两个控制点., 由于是点A为起始点,, 所以直接把点A作为第一个控制点, 计算出来的B'作为第二个控制点,  这样AAB'B 4个点,就可以画出点A到点B的平滑贝塞尔曲线.(或者可以直接把AB'B这3个点, 把B'作为控制点, 用二次贝塞尔曲线来拟合, 也是可以的哦~.)

2) 当得到第4个点(假设为D)的时候, 我们通过BCD, 计算出在点C附近的两个控制点, C'和C'', 通过BB''C'C绘制出B到C的平滑曲线..

3) 当得到第i个点的时候, 进行第2个步骤.........

4) 当得到最后一个点Z的时候,  直接把Z作为第二个控制点(假设前一个点为Y),  即, 使用YY'ZZ来绘制Bezier曲线.

为了让阅读者能够更好的理解, 用Python实现了这个算法, 鼠标点击空白处可以增加笔迹点, 选中笔迹点可以动态拖动, 单击已有笔迹点执行删除:

效果图如下:

Python代码我就不再解释了, 直接提供出来:

1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 import numpy as np 4 from scipy.special import comb, perm 5 import matplotlib.pyplot as plt 6 7 plt.rcParams['font.sans-serif'] = ['SimHei'] 8 # plt.rcParams['font.sans-serif'] = ['STXIHEI'] 9 plt.rcParams['axes.unicode_minus'] = False 10 11 class Handwriting: 12 def __init__(self, line): 13 self.line = line 14 self.index_02 = None # 保存拖动的这个点的索引 15 self.press = None # 状态标识,1为按下,None为没按下 16 self.pick = None # 状态标识,1为选中点并按下,None为没选中 17 self.motion = None # 状态标识,1为进入拖动,None为不拖动 18 self.xs = list() # 保存点的x坐标 19 self.ys = list() # 保存点的y坐标 20 self.cidpress = line.figure.canvas.mpl_connect('button_press_event', self.on_press) # 鼠标按下事件 21 self.cidrelease = line.figure.canvas.mpl_connect('button_release_event', self.on_release) # 鼠标放开事件 22 self.cidmotion = line.figure.canvas.mpl_connect('motion_notify_event', self.on_motion) # 鼠标拖动事件 23 self.cidpick = line.figure.canvas.mpl_connect('pick_event', self.on_picker) # 鼠标选中事件 24 25 self.ctl_point_1 = None 26 27 def on_press(self, event): # 鼠标按下调用 28 if event.inaxes != self.line.axes: return 29 self.press = 1 30 31 def on_motion(self, event): # 鼠标拖动调用 32 if event.inaxes != self.line.axes: return 33 if self.press is None: return 34 if self.pick is None: return 35 if self.motion is None: # 整个if获取鼠标选中的点是哪个点 36 self.motion = 1 37 x = self.xs 38 xdata = event.xdata 39 ydata = event.ydata 40 index_01 = 0 41 for i in x: 42 if abs(i - xdata) < 0.02: # 0.02 为点的半径 43 if abs(self.ys[index_01] - ydata) < 0.02: break 44 index_01 = index_01 + 1 45 self.index_02 = index_01 46 if self.index_02 is None: return 47 self.xs[self.index_02] = event.xdata # 鼠标的坐标覆盖选中的点的坐标 48 self.ys[self.index_02] = event.ydata 49 self.draw_01() 50 51 def on_release(self, event): # 鼠标按下调用 52 if event.inaxes != self.line.axes: return 53 if self.pick is None: # 如果不是选中点,那就添加点 54 self.xs.append(event.xdata) 55 self.ys.append(event.ydata) 56 if self.pick == 1 and self.motion != 1: # 如果是选中点,但不是拖动点,那就降阶 57 x = self.xs 58 xdata = event.xdata 59 ydata = event.ydata 60 index_01 = 0 61 for i in x: 62 if abs(i - xdata) < 0.02: 63 if abs(self.ys[index_01] - ydata) < 0.02: break 64 index_01 = index_01 + 1 65 self.xs.pop(index_01) 66 self.ys.pop(index_01) 67 self.draw_01() 68 self.pick = None # 所有状态恢复,鼠标按下到稀放为一个周期 69 self.motion = None 70 self.press = None 71 self.index_02 = None 72 73 def on_picker(self, event): # 选中调用 74 self.pick = 1 75 76 def draw_01(self): # 绘图 77 self.line.clear() # 不清除的话会保留原有的图 78 self.line.set_title('Bezier曲线拟合手写笔迹') 79 self.line.axis([0, 1, 0, 1]) # x和y范围0到1 80 # self.bezier(self.xs, self.ys) # Bezier曲线 81 self.all_curve(self.xs, self.ys) 82 self.line.scatter(self.xs, self.ys, color='b', s=20, marker="o", picker=5) # 画点 83 self.line.plot(self.xs, self.ys, color='black', lw=0.5) # 画线 84 self.line.figure.canvas.draw() # 重构子图 85 86 # def list_minus(self, a, b): 87 # list(map(lambda x, y: x - y, middle, begin)) 88 89 def controls(self, k, begin, middle, end): 90 # if k > 0.5 or k


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3