上一篇文章介紹了目前大多數人在擬合手寫筆跡的時候使用的算法, 這篇文章介紹一種本身首創的算法.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塊錢的蛋炒飯, 早上點一份,中午吃一半, 晚上吃一半, 日子真實苦啊..
你們若是你們以爲這篇文章對您有幫助, 又願意打賞一些銀兩, 請拿起你的手機, 打開你的微信, 掃一掃下方二維碼, 做爲一個有骨氣的程序員攻城獅, 我很是願意接受你們的支助...哈哈哈!!!