上一篇文章介绍了目前大多数人在拟合手写笔迹的时候使用的算法, 这篇文章介绍一种本身首创的算法.html
这种算法具备如下优势:
1) 使用二次贝塞尔曲线拟合, 计算量大概比3次贝塞尔曲线少三分之一.
2) 没必要等到用户输入了下一个点以后, 才能绘制当前两个点之间的曲线, 这种算法能够先绘当前须要拟合的线段的一部分, 可以很是及时的把用户的输入反馈给用户, 用户体验马上提升了2个档次.
3) 不用计算控制点, 处理起来更加简单, 计算量也再次减小, 用户绘制体验获得进一步提升.
4) 笔迹拟合更加接近真实手写的笔迹.python
有如下缺点:程序员
我真尼玛没发现有缺点, 我真的不能欺骗你们, 它明明没有缺点, 我非要找一个缺点出来吗!!!?,做为一个程序员, 我不能说谎啊!!!!!O(∩_∩)O哈哈~算法
这么厉害的算法, 你们是否是已经火烧眉毛了. 下面就来给你们分享这个算法的思路, 先看下面的图解:canvas
可能你们只看图就已经知道应该怎么作了. 如今按照图中的标注, 假设:ABCDEFG为原笔迹点. 微信
1) 当用户经过点击鼠标或者点击手机屏幕手势, 输入点A时, 咱们在A的位置画下一个小圆点app
2) 首先须要设立一个系数k,取值为(0, 0.5]之间的小数. 当用于经过移动, 输入了第二个点B时, 咱们在线段AB上找到一个点A', 使得 |A'B| / |AB| = k, 并绘制线段AA', 将其做为手写笔迹的一部分. 学习
3) 当用户再次移动鼠标, 获得获得第三个点C时, 咱们在BC上, 找到两个点, B' 和 B'', 知足 |BB'| / |BC| = |B''C| / |BC| = k, 而后将前面的 A' 和 B' 做为两个端点, spa
点B做为控制点, 绘制A'BB' 描述的二次贝塞尔曲线. 做为手写笔迹的一部分.code
4) 链接B'B''的直线线段, 做为时候写笔迹的一部分.
5) 当用于输入点D,E,F.......时, 回到第2步, 循环执行2,3,4.
6) 当用于输入最后一个点G时, 执行2, 3步, 而后直接链接F'G, 结束绘制.
为何要把第4步单独分离出来呢, 由于当k取值为0.5的时候, B'B'', C'C''.....F'F'' 直接重合为同一个点, 就能够直接省略弟4步.(实践证实, k值取0.5, 不但速度快, 效果还很是好!!!!)
这个算法, 初看起来, 有一些问题, 整个曲线没有通过做为原笔迹点的BCDEF, 是否是效果不理想呢???..再细想一下:
使用点ABC来举例, 虽然没有通过点B, AA'和B'B两条线段的轨迹是彻底和原笔迹的连线重合的, 即便阈值取0.5的状况, 也有两个点(A', B')和原笔迹连线重合'
因此, 咱们虽然放弃了一棵树,获得了一片森林;放弃一个点, 重合了无数个点, 咱们还能够经过阈值k来控制曲线的拟合程度, k越小, 转角的地方越锐利; k越大, 拟合越平滑.
一样,为了你们学习方便, 我在前面一篇文章的基础上稍做修改, 把这种算法用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 self.ctl_point_1 = None 25 26 def on_press(self, event): # 鼠标按下调用 27 if event.inaxes != self.line.axes: return 28 self.press = 1 29 30 def on_motion(self, event): # 鼠标拖动调用 31 if event.inaxes != self.line.axes: return 32 if self.press is None: return 33 if self.pick is None: return 34 if self.motion is None: # 整个if获取鼠标选中的点是哪一个点 35 self.motion = 1 36 x = self.xs 37 xdata = event.xdata 38 ydata = event.ydata 39 index_01 = 0 40 for i in x: 41 if abs(i - xdata) < 0.02: # 0.02 为点的半径 42 if abs(self.ys[index_01] - ydata) < 0.02: break 43 index_01 = index_01 + 1 44 self.index_02 = index_01 45 if self.index_02 is None: return 46 self.xs[self.index_02] = event.xdata # 鼠标的坐标覆盖选中的点的坐标 47 self.ys[self.index_02] = event.ydata 48 self.draw_01() 49 50 def on_release(self, event): # 鼠标按下调用 51 if event.inaxes != self.line.axes: return 52 if self.pick is None: # 若是不是选中点,那就添加点 53 self.xs.append(event.xdata) 54 self.ys.append(event.ydata) 55 if self.pick == 1 and self.motion != 1: # 若是是选中点,但不是拖动点,那就降阶 56 x = self.xs 57 xdata = event.xdata 58 ydata = event.ydata 59 index_01 = 0 60 for i in x: 61 if abs(i - xdata) < 0.02: 62 if abs(self.ys[index_01] - ydata) < 0.02: break 63 index_01 = index_01 + 1 64 self.xs.pop(index_01) 65 self.ys.pop(index_01) 66 self.draw_01() 67 self.pick = None # 全部状态恢复,鼠标按下到稀放为一个周期 68 self.motion = None 69 self.press = None 70 self.index_02 = None 71 72 def on_picker(self, event): # 选中调用 73 self.pick = 1 74 75 def draw_01(self): # 绘图 76 self.line.clear() # 不清除的话会保留原有的图 77 self.line.set_title('Bezier曲线拟合手写笔迹') 78 self.line.axis([0, 1, 0, 1]) # x和y范围0到1 79 # self.bezier(self.xs, self.ys) # Bezier曲线 80 self.all_curve(self.xs, self.ys) 81 self.line.scatter(self.xs, self.ys, color='b', s=20, marker="o", picker=5) # 画点 82 # self.line.plot(self.xs, self.ys, color='black', lw=0.5) # 画线 83 self.line.figure.canvas.draw() # 重构子图 84 85 # def list_minus(self, a, b): 86 # list(map(lambda x, y: x - y, middle, begin)) 87 88 def controls(self, k, begin, end): 89 if k <= 0 or k >= 1: return 90 first_middle = begin + k * (end - begin) 91 second_middle = begin + (1 - k) * (end - begin) 92 return first_middle, second_middle 93 94 95 def all_curve(self, xs, ys): 96 le = len(xs) 97 if le < 2: return 98 self.ctl_point_1 = None 99 100 begin = [xs[0], ys[0]] 101 end = [xs[1], ys[1]] 102 self.one_curve(begin, end) 103 104 for i in range(2, le): 105 begin = end 106 end = [xs[i], ys[i]] 107 self.one_curve(begin, end) 108 109 end = [xs[le - 1], ys[le - 1]] 110 x = [self.ctl_point_1[0], end[0]] 111 y = [self.ctl_point_1[1], end[1]] 112 113 #linestyle='dashed', 114 self.line.plot(x, y, color='yellowgreen', marker='o', lw=3) 115 116 def one_curve(self, begin, end): 117 ctl_point1 = self.ctl_point_1 118 119 begin = np.array(begin) 120 end = np.array(end) 121 122 ctl_point2, self.ctl_point_1 = self.controls(0.4, begin, end) 123 color = 'red'; 124 if ctl_point1 is None : 125 xs = [begin[0], self.ctl_point_1[0]] 126 ys = [begin[1], self.ctl_point_1[1]] 127 self.line.plot(xs, ys, color=color, marker='o', linewidth='3') 128 else : 129 xs = [ctl_point1[0], begin[0], ctl_point2[0]] 130 ys = [ctl_point1[1], begin[1], ctl_point2[1]] 131 self.bezier(xs, ys) 132 xs = [ctl_point2[0], self.ctl_point_1[0]] 133 ys = [ctl_point2[1], self.ctl_point_1[1]] 134 self.line.plot(xs, ys, color=color, marker='o', linewidth='3') 135 136 def bezier(self, *args): # Bezier曲线公式转换,获取x和y 137 t = np.linspace(0, 1) # t 范围0到1 138 le = len(args[0]) - 1 139 140 self.line.plot(args[0], args[1], marker='o', linestyle='dashed', color='limegreen', lw=1) 141 le_1 = 0 142 b_x, b_y = 0, 0 143 for x in args[0]: 144 b_x = b_x + x * (t ** le_1) * ((1 - t) ** le) * comb(len(args[0]) - 1, le_1) # comb 组合,perm 排列 145 le = le - 1 146 le_1 = le_1 + 1 147 148 le = len(args[0]) - 1 149 le_1 = 0 150 for y in args[1]: 151 b_y = b_y + y * (t ** le_1) * ((1 - t) ** le) * comb(len(args[0]) - 1, le_1) 152 le = le - 1 153 le_1 = le_1 + 1 154 155 color = "mediumseagreen" 156 if len(args) > 2: color = args[2] 157 self.line.plot(b_x, b_y, color=color, linewidth='3') 158 159 fig = plt.figure(2, figsize=(12, 6)) 160 ax = fig.add_subplot(111) # 一行一列第一个子图 161 ax.set_title('手写笔迹贝赛尔曲线, 计算控制点图解') 162 163 handwriting = Handwriting(ax) 164 plt.xlabel('X') 165 plt.ylabel('Y') 166 167 # begin = np.array([20, 6]) 168 # middle = np.array([30, 40]) 169 # end = np.array([35, 4]) 170 # handwriting.one_curve(begin, middle, end) 171 # myBezier.controls(0.2, begin, middle, end) 172 plt.show()
下一篇文章,不出意外应该是这个手写笔迹系列的最后一篇文章.
我将把我实现笔锋效果的具体原理和细节, 还有用C++对算法的具体实现, 以及能够直接运行查看效果的Demo一块儿分享给你们.
无良公司老板拖欠两个月工资了, 穷得叮当响, .真尼玛坑啊,我靠!!!!!!!!如今天天吃8块钱的蛋炒饭, 早上点一份,中午吃一半, 晚上吃一半, 日子真实苦啊..
你们若是你们以为这篇文章对您有帮助, 又愿意打赏一些银两, 请拿起你的手机, 打开你的微信, 扫一扫下方二维码, 做为一个有骨气的程序员攻城狮, 我很是愿意接受你们的支助...哈哈哈!!!