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

上一篇文章介绍了目前大多数人在拟合手写笔迹的时候使用的算法, 这篇文章介绍一种本身首创的算法.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块钱的蛋炒饭, 早上点一份,中午吃一半, 晚上吃一半, 日子真实苦啊..

你们若是你们以为这篇文章对您有帮助, 又愿意打赏一些银两, 请拿起你的手机, 打开你的微信, 扫一扫下方二维码, 做为一个有骨气的程序员攻城狮, 我很是愿意接受你们的支助...哈哈哈!!!

 

相关文章
相关标签/搜索