說起來貝氏曲線,我們可能首先會想到下面這個男人:
圖1. 一位不願透露姓名的英國球員
但實際上我們說的不是這個叫貝克漢姆的英國男人,而是另外一個人,就是下面這個叫「皮埃爾·貝塞爾」(Pierre Bézier)的法國男人:
圖2. 皮埃爾·貝塞爾
貝塞爾論起知名度,也許不如小貝,但說起對人類的貢獻,那可是槓槓的,著名的「貝塞爾曲線」就出自他之手。1962年,貝塞爾發表了貝塞爾曲線的相關理論研究,當時在雷諾公司工作的他,主要運用貝塞爾曲線進行汽車設計。說到這裡可能還是有很多人沒明白貝塞爾曲線到底是什麼,看一下下面這個圖,大家就明白了。
圖3. 設計中用到的貝賽爾曲線
在Photoshop等多種設計軟體中,畫曲線時主要用到的是就是貝賽爾曲線,就是類似於上圖中的這個曲線,設計師們可以通過控制中間的控制點來畫出自己需要的曲線。早先設計師們想要用電腦畫出一條直線灰常簡單,但要畫出一條平滑的曲線卻非常難,而貝塞爾曲線的誕生,讓大家用電腦繪製出一條平滑曲線成為了現實,這也就是貝塞爾曲線的最大用途。
而今天我們就來說一下最簡單的二階貝塞爾曲線的推導,並用matplotlib進行展示。(實際上最簡單的是一階,但因為其只有一條直線,所以沒有什麼實際用途,就忽略了)
我們先來了解一下二階貝賽爾曲線的原理。假如連在一起的兩條線段AB和BC,如下圖:
圖4. 二階貝賽爾曲線原理圖 1
現在AB上取一點D,BC上取一點E,使得AD/AB=BE/BC,如下圖:
圖5. 二階貝賽爾曲線原理圖 2
而在線段DE上還要求一點F,使得DF/DE=AD/AB=BE/BC,如下圖:
圖6. 二階貝賽爾曲線原理圖 3
而當D在AB上不斷移動,E在BC上不斷移動,形成的F點的軌跡便是一條曲線,這條曲線就是二階貝塞爾曲線。這就是今天我們要推導並演示的曲線。
下面直接用Python代碼來展示一下。首先還是導入各種包:
import numpy as npimport matplotlib.pyplot as pltfrom matplotlib import animation因為我們用的是matplotlib來做的演示,所以要設置一下matplotlib的後端,也就是顯示方式。這一行代碼最好單獨使用,否則容易失效,所以把它單獨列出來:
這是非常重要的一步,這一步能讓matplotlib在繪圖時彈出一個新窗口,而不是在原窗口直接繪製,因為原窗口無法顯示動畫,這裡用了ipython的magic 命令,也就是在一個命令前面加上「%」。在這裡說明一下,筆者用的是win7系統,開發工具為Anaconda最新版,可以直接去Anaconda官網下載。
因為我們要用到A、B、C三個點,所以要設置一下三個點坐標。A點坐標為x1和y1,B點為x2和y2,C點為x3和y3,再設置一個區間內點的個數dots_num,這個dots_num後面會有解釋。所有這些變量的值都可以隨意設定,但有一些要求,因為筆者把繪圖的坐標系設定在100,也就是x軸和y軸的範圍都是0—100,所以上面三個點的坐標都不要超過這個範圍,而dots_num的數量儘可能大一些,做動畫時更連貫一些,所以上面這些參數的設置如下:
x1=10y1=80x2=50y2=10x3=90y3=80dots_num=100接下來是獲得貝塞爾曲線的軌跡的函數:
def two_degree_bc(x1=10, y1=80, x2=50, y2=10, x3=90, y3=80, dots_num=100): global xt, yt, x_dots12, x_dots23, y_dots12, y_dots23 xt = [] yt = [] x_dots12 = np.linspace(x1, x2, dots_num) y_dots12 = np.linspace(y1, y2, dots_num) x_dots23 = np.linspace(x2, x3, dots_num) y_dots23 = np.linspace(y2, y3, dots_num) for i in range(dots_num): x = x_dots12[i] + (x_dots23[i]-x_dots12[i])*i / (dots_num-1) y = y_dots12[i] + (y_dots23[i]-y_dots12[i])*i / (dots_num-1) xt.append(x) yt.append(y)這裡的xt和yt是兩個list,就是用來存放目標點的x坐標和y坐標,而x_dots12和y_dots12分別是線段AB的x和y坐標,x_dots23和y_dots23分別是線段BC的x和y坐標,這四個是numpy的array格式,所有這些數據都設置成全局變量。而從上面的代碼中我們可以看到變量dots_num的作用是在線段AB和BC上取這麼多的點,然後用這些點推導目標點的坐標,點的數量越多,目標點的坐標也就越多,繪製出來的曲線也就更平滑。
接下來是動畫函數,這個函數後面再解釋:
def run(i): art1.set_data(x_dots12[i], y_dots12[i]) art2.set_data(x_dots23[i], y_dots23[i]) art3.set_data([x_dots12[i], x_dots23[i]], [y_dots12[i], y_dots23[i]]) art4.set_data(xt[i], yt[i])return art1,art2,art3,art4最後就是繪製動畫了,代碼如下:
two_degree_bc() fig, ax = plt.subplots(figsize=(8,8))ax.set_aspect(1) plt.xlim([0,100]) plt.ylim([0,100])ax.plot([x1, x2], [y1, y2], color='#3e82fc') ax.plot([x2, x3], [y2, y3], color='#3e82fc') ax.plot(xt,yt,color='orange') art1, = ax.plot(x_dots12[0], y_dots12[0], color='green', marker='o') art2, = ax.plot(x_dots23[0], y_dots23[0], color='green', marker='o')art3, = ax.plot([x_dots12[0], x_dots23[0]], [y_dots12[0], y_dots23[0]], color = 'purple') art4, = ax.plot(xt[0], yt[0], color='red', marker='o') ani = animation.FuncAnimation( fig, run, frames=range(100), interval=2, save_count=50)plt.show()這裡首先運行two_degree_bc()函數得到目標點的軌跡,然後繪製AB、BC線段以及目標曲線,這些都是靜態圖。接著從變量art1開始就是繪製動畫的部分了。這部分比較複雜,一共有art1、art2、art3和art4這四個變量,其分別對應線段AB、BC、DE和目標曲線的移動軌跡,點在這四個軌跡上移動,才能形成動畫。而要生成動畫,就要用到animation的方法FuncAnimation,其含有多個參數,fig就是我們繪圖的那個畫布,run就是我們生成動畫時運行的函數,frames是幀畫面,其每一幀畫面包含了這些移動軌跡中的一個點所對應的靜態圖,把這些點的軌跡也就是每一幀連起來就是動畫軌跡,frames一般是一個sequence,也就是包含多個變量,每個變量都賦值給run函數,run函數利用這個參數生成一個靜態圖,這麼多靜態圖連起來就是動畫,這和我們在電影或電視中看到的動畫片是一樣道理。interval是幀之間的時間間隔,200代表0.2秒,這個可以隨意設定。save_count=50是把幀緩存起來用於回放,緩存幀的數量越多,回放越流暢,這個影響不大,隨意設定。
下面再說一下運行動畫的函數run的作用,可以看到run一共有5行代碼,前4行代碼是繪圖代碼,最後一個是返回參數的代碼,前4行中每一行都代表了前面我們說過的點的軌跡, art1是線段AB上的點,用set_data(x_dots12[i], y_dots12[i])方法就生成了一個對應的幀,變量i就是前面講的frames裡的一個參數,生成的這個幀返回給FuncAnimation,讓其用於連續播放,這就形成了動畫。後面art2、art3和art4的道理是一樣的。生成的動畫效果的靜態截圖如下:
圖7. 二階貝賽爾曲線靜態成圖
最後再放上一個動圖,讓我們在一個深V的運動中結束本次話題:
圖8. 二階貝賽爾曲線動態示意圖
二階貝塞爾曲線的推導相對還容易一些,而三階甚至更高階的推導就複雜一點,筆者目前正在研究三階貝賽爾曲線,以後會給大家分享一下。
完整代碼已經上傳:
https://gitee.com/leonmovie/two_degree_bc
有需要的可以去自行下載。
作者簡介:小李子,數據分析愛好者,擅長數據可視化,比較關注機器學習領域,希望能和業內朋友多學習交流,個人微信 tyrant100