Simple Inverse Kinematics กับ Animation


ความจริงตามคิวแล้วจะเขียนเรื่อง conic section ต่อแต่ด้วยความซน ได้ไปเจอซอฟต์แวร์ช่วยทำ 2D animation ตัวหนึ่งได้กล่าวถึงเรื่อง IK (ย่อมาจาก inverse kinematics) ที่เอามาช่วยในการสร้างการเคลื่อนไหวให้ตัวละคร ก็เลยเกิดแนวคิดน่าจะหาอะไรสนุกเกี่ยวกับคณิตศาสตร์ระดับมัธยมได้

กล่าวถึง Inverse Kinematics ท่านที่ผ่านเรื่อง Robotics มาก็คงจะถึงบางอ้อว่าหมายถึงอะไร ใช้ทำอะไร ทำไมต้องให้ความสำคัญ สำหรับท่านที่ยังไม่เคยผ่านมาก็ไม่เป็นไรครับ จะค่อยเล่ากันไป แนวทางที่ใช้ในการคิดคำนวณเรื่องนี้มีสองสามทาง ทางแรกเรียกว่า algebraic approach ทางนี้ขอข้ามไปก่อนครับ เด็กมัธยมยังไม่ได้เรียน มาที่อีกสายหนึ่งเรียกว่า geometric approach สายนี้ค่อยง่ายหน่อย ครับ geometry ระดับมัธยมก็เริ่มได้เลย ในบทความตอนนี้จะใช้หลักการนี้ครับ




สมมุติว่ารูปข้างบนคือรูปของ robot arm แบบง่าย แขนมีสองส่วน  แล้วจินตนาการต่อไปว่ามีฉากวางอยู่ข้างหลัง พร้อมพิกัดแนวตั้งและแนวนอน ปลายด้านหนึ่งยึดติดกับฐานสามารถหมุนได้ 0- 180 องศา เรียกปลายอีกด้านว่าเป็น target สมมุติว่าให้มีตำแหน่งเป็น (x,y) หากเราปรับตำแหน่งของ (x,y) ไปยังตำแหน่งต่าง ๆ กัน สิ่งที่จะเกิดขึ้นคือการเปลี่ยนแปลงของมุมที่ข้อต่อ ดังภาพ






สิ่งที่ inverse kinematics เข้ามาช่วยเราคือการหาค่าของมุมที่ joint ต่างๆ เปลี่ยนไป ซึ่งเป็นวิธีการคิดย้อนกลับ ในทาง robotics หมายถึงข้อมูลที่นำไปสั่งอุปกรณ์อย่างเช่น servo หรือ step motor ให้หมุนไปตามมุมที่ต้องการ  ในมุมของ computer animation ก็คือการสร้างภาพของ frame ต่าง ๆ 

ในการหาความสัมพันธ์ระหว่างตำแหน่งของ target กับค่าของมุมของ joint ผมจะละไว้ให้ไปศึกษาต่อจาก เรื่อง Simple Geometric Inverse Kinematics [1] ครับ ใช้ความรู้พื้นฐานทางคณิตศาสตร์นิดหน่อยก็อ่านเข้าใจได้แล้ว แล้วก็ใช้ Python Script ที่เขียนไว้ในบทความดังกล่าวมาใช้เพื่อแปลงค่าของตำแหน่ง (x,y) ออกมาเป็นค่าของมุมของแต่ละ joint ที่สอดคล้องกับตำแหน่งของ end effector จากนั้นก็นำค่ามุมที่ได้มากำหนดเหตุการณ์ที่จะเกิดขึ้นในแต่ละ frame ของ animation  ในขั้นตอนนี้จะใช้หลักการของ forward kinematics




ไปดู Python Script พร้อมคำอธิบายกันเลย


from numpy import sin, cos
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

จะเห็นได้ว่าคราวนี้เราได้นำเอา matplotlib.animation เข้ามาใช้งานได้ด้วย


#create plotting area
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False, xlim=(-20, 20), ylim=(-20, 20))
ax.set_aspect('equal')
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)

เป็นขั้นตอนการเตรียมพื้นที่สำหรับการวาดภาพ (plotting area) ข้อสังเกตุคือ
line, = ax.plot([],[],'o-',lw=2)

เป็นการสั่งให้ matplotlib ทำการสร้าง instance  ของ  line2D [2] ขึ้นมาด้วยคำสั่ง plot [4]  การที่เราใช้ line, นั้นเพราะว่าผลของคำสั่งนี้จะได้ list ของ line2D ขึ้นมา แต่เราต้องการเพียงตัวเดียวเลยที่เหลือก็ละไว้ด้วยเครื่องหมาย ,


L1 = 10.0  # length of arm 1
L2 = 10.0  # length of arm 2 
theta1 =np.radians([209,210,210,209,207,204,200,194,186,
177,166,154,143,132,123,114,107,100,94,88,83,84,85,86,87,
88,89,89,90,90,90,96,102,107,113,119,126,132,138,144,150,
156,161,165,169,173,175,177,179,180,180,183,186,189,192,
194,197,200,203,206])

theta2 = np.radians([97,92,86,80,73,65,57,48,37,27,15,4,354,
346,340,335,333,331,330,330,331,334,336,339,342,345,349,
351,355,357,360,1,2,2,4,7,11,15,19,24,30,36,42,48,54,61,66,
72,79,85,90,90,91,91,92,91,92,93,94,9])

L1,L2 แทนความยาวของ arm แต่ละช่วง ในที่นี้กำหนดให้เป็น 10 units
theta1 และ theta2 คือ array เก็บค่าของมุมที่ joint ตำแหน่งที่ 1 และ 2 ตามลำดับ โดยค่าเหล่านี้ได้มาจากการ target (x,y) ที่ผ่านการคำนวณด้วยหลักการ geometry inverse kinetmaics (กล่าวไว้ใน [1]) ขั้นตอนนี้บอกว่าเราต้องทำการวางแผนให้ target กวาดไปตามตำแหน่งต่างๆ ที่ต้องการก่อน อาจใช้ความรู้เรื่องภาคตัดกรวยมาช่วยก็ได้ จากนั้นก็เปลี่ยนค่าของตำแหน่งเหล่านั้นออกมาเป็นค่าของมุมที่แขนของ robot จะกางออก


def init():
 line.set_data([], [])
 return line, 


def animate(i):
 x1 = L1 * np.cos([theta1[i]])
 x2 = L2 * np.cos([theta2[i]])
 
 y1 = L1 * np.sin([theta1[i]])
 y2 = L2 * np.sin([theta2[i]])
 
 thisx = [0, x1,x1+x2]
 thisy = [0, y1,y1+y2]
 line.set_data(thisx, thisy)
 return line, 

def init() เป็นการเริ่มกำหนดค่าของตัวแปรที่เกี่ยวข้อง  ในที่นี้คือ line ซึ่งเป็น instance ของ line2D [2] ในที่นี้กำหนดข้อมูลเป็น array เปล่า 2 array

def animate(i) เป็นการกำหนดเหตุการณ์ที่จะเกิดขึ้นในแต่ละ frame ภาพของ animation  คือการนำค่าของมุมจากตัวแปร theta1 และ theta2 มาเปลี่ยนเป็นค่าพิกัดของ target ที่สัมพันธ์กันโดยอาศัยหลักการ Pythogorian theorem  ตัวแปร i ที่รับเข้ามาทำหน้าที่เป็นเหมือนกับ running number เพื่อให้เราทราบว่าจะใช้ค่าของ theta1 และ theta2 ที่ตำแหน่งใด

และเมื่อได้ค่าของพิกัด (x,y) ของ target แล้วก็นำค่าที่ได้ส่งให้ตัวแปร line เพื่อทำการวาดรูปใน frame นั้น ๆ ต่อไป

การที่ต้องทำไมต้องทำอะไรที่ย้อนไปย้อนมา เพราะเรากำลังเชื่อมโลกจริงกับโลกในคอมพิวเตอร์เข้าหากัน ซึ่งแต่ละโลกมีระบบพิกัดของตัวเอง เมื่อข้ามโลกก็ต้องการคำนวณกันนิดหน่อย


ani = animation.FuncAnimation(fig, animate, np.arange(1, len(theta1)),
                              interval=80, blit=True, init_func=init)

plt.show()

เป็นชุดคำสั่งที่สั่งให้ทำการสร้าง animation ขึ้นมา [3]

Full Script



from numpy import sin, cos
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

L1 = 10.0  # length of arm 1
L2 = 10.0  # length of arm 2 
theta1 =np.radians([209,210,210,209,207,204,200,194,186,
177,166,154,143,132,123,114,107,100,94,88,83,84,85,86,87,
88,89,89,90,90,90,96,102,107,113,119,126,132,138,144,150,
156,161,165,169,173,175,177,179,180,180,183,186,189,192,
194,197,200,203,206])

theta2 = np.radians([97,92,86,80,73,65,57,48,37,27,15,4,354,
346,340,335,333,331,330,330,331,334,336,339,342,345,349,
351,355,357,360,1,2,2,4,7,11,15,19,24,30,36,42,48,54,61,66,
72,79,85,90,90,91,91,92,91,92,93,94,9])

#create plotting area
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=False, xlim=(-20, 20), ylim=(-20, 20))
ax.set_aspect('equal')
ax.grid()
line, = ax.plot([], [], 'o-', lw=2)

def init():
 line.set_data([], [])
 return line, 


def animate(i):
 x1 = L1 * np.cos([theta1[i]])
 x2 = L2 * np.cos([theta2[i]])
 
 y1 = L1 * np.sin([theta1[i]])
 y2 = L2 * np.sin([theta2[i]])
 
 thisx = [0, x1,x1+x2]
 thisy = [0, y1,y1+y2]
 line.set_data(thisx, thisy)
 return line, 

ani = animation.FuncAnimation(fig, animate, np.arange(1, len(theta1)),
                              interval=80, blit=True, init_func=init)

ani.save('animation.gif', writer='imagemagick', fps=12)
plt.show()



ผลที่ได้คือ matplotlib ทำการวาดภาพขึ้นมาทีละภาพ (frame by frame)  เมื่อนำภาพมาซ้อนกันก็จะเห็นเป็นภาพเคลื่อนไหว (animation) ขึ้นมา









ไม่เลว เลยนะ ครับ อ่านรอบ แรกๆ อาจดูเหมือนวุ่นวายไปสักหน่อย แต่พอเข้าใจแล้วก็จะเห็นว่าไม่มีอะไรซับซ้อนเลย เป็นการใชัคณิตศาสตร์ที่เรียนกันในระดับมัธยมจริงๆ
การใช้ inverse kinematics มาช่วยในการทำ animation ส่วนตัวมองว่าจะมีประโยชน์ในเรื่องการกำหนดท่าทาง (pose) ของตัวละครได้ อาจช่วยลดต้นทุนด้านคนไปได้บ้างก็ได้ ในครั้งต่อไปจะกล่าวถึงการนำไปใช้กับอุปกรณ์อย่าง servo ดูนะครับ


เอกสารอ้างอิง




Previous
Next Post »