ไอเดีย
หลังจากทำโปรเจคที่แสดงอุณหภูมิบนจอ e-paper เป็นกราฟแท่งเสร็จแล้ว พี่อยากจะอัพเกรดให้มันดูเจ๋งขึ้น ด้วยการแสดงข้อมูลเป็นเส้นกราฟต่อเนื่องแบบเนียนๆ พี่เคยได้ยินเรื่อง Splines มาก่อน เลยตัดสินใจลองเอาแนวคิดของ Splines แบบต่อเนื่องมาใช้กับโปรเจคนี้ดู ผลลัพธ์คือได้แสดงข้อมูลในรูปแบบที่เข้าใจง่ายขึ้นและน่าสนใจกว่าเดิม ทำให้ใครๆ ก็มองเห็นแนวโน้มของอุณหภูมิได้ชัดเจนเลย
โปรเจคนี้ทั้งหมดคือการนำ Splines ไปใช้แสดงข้อมูลจากโลกจริงบนหน้าจอ e-paper มันเกี่ยวกับการสร้างฟังก์ชันต่างๆ สำหรับ Splines หลายแบบ ซึ่งสามารถนำไปใช้กับจอแสดงผลได้แทบทุกชนิด Splines ช่วยให้เราวาดภาพข้อมูลออกมาได้ทั้งสวยงามและให้ข้อมูลครบถ้วน ทำให้ใครๆ ก็เข้าใจแนวโน้มและรูปแบบในข้อมูลได้ง่าย โปรเจคนี้เหมาะมากสำหรับคนที่อยากโชว์ข้อมูลในแบบที่ทั้งดูดีและมีประสิทธิภาพ
ทฤษฎีเบื้องต้น
เส้นโค้งเบซิเยร์ (Bezier Curves)
เส้นโค้งเบซิเยร์คือเส้นโค้งที่นิยามด้วยคณิตศาสตร์ มักใช้ในงานคอมพิวเตอร์กราฟิกส์ และถูกนำมาใช้ใน Splines เพื่อสร้างเส้นโค้งที่เนียนและต่อเนื่องได้ง่ายๆ โดยการกำหนดรูปร่างผ่านชุดของจุดควบคุม (control points)
เส้นโค้งเบซิเยร์ลูกบาศก์ (Cubic Bezier curves) อาศัยการประมาณค่าเชิงเส้นระหว่างจุดควบคุม และสามารถเขียนเป็นฟังก์ชันที่คำนวณพิกัด x และ y ของจุดบนเส้นโค้งที่ค่า t ใดๆ ได้ เพื่อให้เข้าใจการทำงานของฟังก์ชันนี้ ลองนึกถึงเส้นโค้งเบซิเยร์ลูกบาศก์ที่กำหนดด้วยจุดควบคุมสี่จุด: P0, P1, P2 และ P3 เส้นโค้งจะเริ่มที่ P0 และสิ้นสุดที่ P3 โดยรูปร่างถูกกำหนดโดยจุดควบคุมกลาง P1 และ P2 ที่ t=0 ฟังก์ชันจะคืนค่าเป็นจุดเริ่มต้น P0 และที่ t=1 จะคืนค่าเป็นจุดสิ้นสุด P3 จุดต่างๆ บนเส้นโค้งระหว่างนั้นคำนวณมาจากการประมาณค่าเชิงเส้นระหว่างจุดควบคุม โดยน้ำหนักของการประมาณค่าถูกกำหนดโดยค่า t พูดให้ชัดคือ ที่ t=0.5 จุดที่ได้จะอยู่บนส่วนของเส้นตรงที่เชื่อมระหว่างจุดกึ่งกลางของส่วนเส้นตรงระหว่างจุดควบคุมแรกและสุดท้าย (P0 กับ P3) ไปยังจุดกึ่งกลางของส่วนเส้นตรงระหว่างจุดควบคุมที่สองและสาม (P1 กับ P2)

GIF ด้านบนแสดงให้เห็นภาพว่าเส้นโค้งเบซิเยร์ลูกบาศก์ถูกกำหนดรูปร่างโดยจุดควบคุมอย่างไร และการประมาณค่าเชิงเส้นระหว่างจุดเหล่านั้นมารวมกันสร้างเป็นเส้นโค้งได้อย่างไร
ฟังก์ชันสำหรับเส้นโค้งเบซิเยร์ลูกบาศก์คือการรวมกันของการประมาณค่าเชิงเส้นเหล่านี้ ซึ่งมีสูตรดังนี้:
B(t) = (1-t)^3 * P0 + 3(1-t)^2 * t * P1 + 3(1-t) * t^2 * P2 + t^3 * P3
B(t) คือจุดบนเส้นโค้งที่ค่า t
P0 คือจุดเริ่มต้นของเส้นโค้ง
P1 และ P2 คือจุดควบคุมกลางที่กำหนดรูปร่างเส้นโค้ง
P3 คือจุดสิ้นสุดของเส้นโค้ง
ฟังก์ชันนี้คำนวณค่า x และ y ของจุดบนเส้นโค้งโดยใช้ค่า t และจุดควบคุม มันคำนวณส่วนประกอบจาก P0, P1, P2 และ P3 ตามลำดับเพื่อกำหนดเส้นโค้ง เราสามารถใช้ฟังก์ชันนี้สร้างจุดหลายๆ จุดบนเส้นโค้งได้โดยการวนลูปค่า t จาก 0 ถึง 1 เมื่อพลอตจุดเหล่านี้ทั้งหมด มันจะกลายเป็นเส้นโค้งที่เนียนและต่อเนื่อง
สไปน์ (Splines)
ในสไปน์ จุดต่อ (knots หรือ joints) คือตำแหน่งที่เส้นโค้งสองเส้นหรือมากกว่ามาบรรจบกัน และจุดควบคุม (control points) จะกำหนดรูปร่างของเส้นโค้งระหว่างจุดต่อเหล่านั้น เพื่อให้แน่ใจว่ามีความต่อเนื่องที่เนียนระหว่างเส้นโค้งที่อยู่ติดกัน จุดควบคุมที่ตำแหน่งต่อต้องถูกวางในลักษณะที่เวกเตอร์แทนเจนต์ของเส้นโค้งที่อยู่ติดกันมีค่าเท่ากันที่จุดต่อนั้น ดังนั้นจุดควบคุมก่อนและหลังจุดต่อ ต้องอยู่บนเส้นตรงเดียวกันซึ่งลากผ่านจุดต่อที่อยู่ตรงกลางด้วย วิธีนี้ทำให้สไปน์ดูเป็นเส้นโค้งที่เนียนและต่อเนื่อง แทนที่จะเป็นเส้นโค้งหลายๆ เส้นแยกจากกัน ซึ่งนั่นคือสิ่งที่เราต้องการพอดีเลย
แม้ว่า Bezier Curves จะเป็นวิธีที่เจ๋งในการหาค่าที่อยู่ระหว่างจุดควบคุม (control points) แต่ก็ยังมีคำถามอยู่ว่า "แล้วเราจะมั่นใจได้ไงว่าเส้น Spline ที่ได้จะเนียนลื่น?" โดยเฉพาะเมื่อมีเงื่อนไขว่า จุดควบคุมก่อนและหลังจุดต่อ (joint) ต้องอยู่บนเส้นตรงเดียวกันที่ลากผ่านจุดต่อตรงกลางนั่นแหละ เพราะความชัน (slope) ของเส้นตรงนั้นมันเปลี่ยนแปลงได้นี่นา
เพื่อจัดการกับเรื่องนี้ Spline ประเภทต่างๆ เช่น Cardinal Splines, Catmull-Rom Splines, และ Hermite Splines จึงกำหนดจุดควบคุมสองจุดระหว่างจุดต่อ (ซึ่งแทนข้อมูลที่เรารู้) ขึ้นมา ทำให้แต่ละประเภทมีฟังก์ชันเฉพาะตัวที่สามารถเขียนในรูปเมทริกซ์ได้แบบนี้:
สำหรับ Cardinal และ Catmull-Rom Splines นั้น ความชันของเส้นที่ลากผ่านจุดควบคุมและจุดต่อจะถูกตั้งให้เท่ากับเส้นที่ลากผ่านจุดต่อที่อยู่ติดกัน ซึ่งนี่แหละที่กำหนดตำแหน่งของจุดควบคุมและเส้นสัมผัส (tangent) ที่จุดต่อ สำหรับ Cardinal Splines นั่นหมายความว่า เวกเตอร์แทนเจนต์จากจุดต่อไปยังจุดควบคุม จะเท่ากับเวกเตอร์ระหว่างจุดต่อที่อยู่ติดกัน เวกเตอร์พวกนี้สามารถปรับขนาดได้ด้วยการคูณกับเมทริกซ์ ส่วน Catmull-Rom Spline ก็คือ Cardinal Spline ประเภทหนึ่งที่ตั้งค่าตัวคูณปรับขนาด (scaling factor) เป็น 0.5 พอดี
Cardinal Spline:
[ -s 2-s s-2 s ]
[ 2s s-3 3-2s -s ]
[ -s 0 s 0 ]
[ 0 1 0 0 ]
Catmull-Rom Spline (สำหรับ s = 0.5):
1/2 * (
[-1 3 -3 1 ]
[ 2 -5 4 -1 ]
[-1 0 1 0 ]
[ 0 2 0 0 ] )
ในที่นี้ แต่ละแถวสอดคล้องกับเซตของจุดควบคุมสี่จุด และแต่ละคอลัมน์แทนน้ำหนัก (weights) ของฟังก์ชันพื้นฐาน (basis functions) ที่เกี่ยวข้อง
Hermite Spline:
[ 2 -2 1 1 ] * จุดต่อเริ่มต้น (start joint)
[-3 3 -2 -1 ] * เวกเตอร์แทนเจนต์จากจุดต่อเริ่มต้น
[ 0 0 1 0 ] * จุดต่อสิ้นสุด (end joint)
[ 1 0 0 0 ] * เวกเตอร์แทนเจนต์จากจุดต่อสิ้นสุด
ในที่นี้ สองแถวแรกสอดคล้องกับจุดควบคุมและเวกเตอร์แทนเจนต์สำหรับจุดเริ่มต้นของเส้นโค้ง ส่วนสองแถวหลังสอดคล้องกับจุดควบคุมและเวกเตอร์แทนเจนต์สำหรับจุดสิ้นสุดของเส้นโค้ง
B-Spline :
1/6 * (
[ 1 4 1 0 ]
[-3 0 3 0 ]
[ 3 -6 3 0 ]
[-1 3 -3 1 ] )
ในที่นี้ แต่ละแถวสอดคล้องกับเซตของจุดควบคุมสี่จุด และแต่ละคอลัมน์แทนน้ำหนักของฟังก์ชันพื้นฐานที่เกี่ยวข้อง
โค้ดของโปรเจคนี้มีการนำ Spline ประเภทต่างๆ มาประยุกต์ใช้แบบง่ายๆ ไว้ให้แล้ว ไปลองเล่นกันได้
การทำให้ข้อมูลทางคณิตศาสตร์เรียบเนียน (Mathematical Data Smoothing)
ข้อมูลเซนเซอร์ดิบ (เช่น อุณหภูมิหรือระดับแก๊ส) มักจะ "ขรุขระ" เนื่องจากสัญญาณรบกวน โปรเจคนี้ใช้ Cubic Spline Interpolation ในการสร้างกราฟที่ดูเนียนลื่นและเป็นมืออาชีพ
- ตรรกะของ Spline: แทนที่จะวาดเส้นตรงระหว่างจุดข้อมูล (Linear Interpolation) อัลกอริทึมจะคำนวณพหุนามดีกรีสามสำหรับแต่ละช่วงข้อมูล เพื่อให้แน่ใจว่าเส้นโค้งจะเนียนที่ทุกจุดต่อ
- การจัดการทรัพยากร: เนื่องจากการคำนวณ spline อาจใช้ทรัพยากร CPU เยอะ ระบบจึงคำนวณ "K-vectors" ล่วงหน้าและเก็บไว้ในบัฟเฟอร์ชั่วคราว ทำให้ Arduino สามารถวาดกราฟเนียนๆ ได้แบบเรียลไทม์
การนำเสนอภาพ (Visual Presentation)
- การแสดงผลบน LCD/OLED: พิกัดที่ถูกทำให้เรียบแล้วจะถูกแปลงเป็นตำแหน่งพิกเซลบนจอกราฟิก (เช่น SSD1306)
- UI ความเที่ยงตรงสูง: เทคนิคนี้จำเป็นมากสำหรับแดชบอร์ดในอุตสาหกรรมหรืออุปกรณ์ติดตามทางการแพทย์ ที่ความชัดเจนของภาพและการวิเคราะห์แนวโน้มสำคัญกว่าการดูข้อมูลจุดดิบที่มีสัญญาณรบกวน จัดไปวัยรุ่น!