ตอนที่ 14: จับการเคลื่อนไหวของเมาส์ด้วย HighGUI

1. 🎯 ตอนที่ 14: จับการเคลื่อนไหวของเมาส์ด้วย HighGUI สร้างหน้าต่างที่ตอบสนองได้ดั่งใจ
2. 📖 เปิดฉาก (The Hook)
น้องๆ เคยทำโปรเจกต์ AI แล้วเจอปัญหาแบบนี้ไหมครับ? สมมติว่าเราเขียนโปรแกรมตรวจจับรอยร้าวบนชิ้นงานสำเร็จแล้ว แต่หน้างานจริงมีแสงสะท้อนรบกวน AI เลยทำงานพลาด เราอยากจะสร้างระบบให้ User หรือ Operator สามารถ “ใช้เมาส์ลากคลุม” (Draw Bounding Box) เพื่อตีกรอบบอก AI ว่า “เฮ้ย สนใจแค่ตรงนี้นะ!” หรืออยากให้เอาเมาส์ไปจิ้มที่พิกเซลบนหน้าจอเพื่อดูค่าสีตรงนั้น
ถ้าเราใช้แค่ฟังก์ชัน imshow ธรรมดา หน้าต่างภาพของเราก็จะนิ่งสนิทเหมือนรูปถ่ายที่ตายแล้วครับ การจะทำให้โปรแกรมของเรามีชีวิตและโต้ตอบกับมนุษย์ได้ เราต้องพึ่งพาโมดูลที่ชื่อว่า HighGUI ซึ่งมีเวทมนตร์ในการดักจับเหตุการณ์ (Events) จากเมาส์ วันนี้พี่จะมาชงกาแฟ แล้วพาน้องๆ ไปรู้จักกับสถาปัตยกรรมแบบ Event-Driven ผ่านการสร้าง Callback Function รับรองว่าทำเป็นแล้ว Vision App ของเราจะดูโปรขึ้นอีกสิบระดับเลยครับ!
3. 🧠 แก่นวิชา (Core Concepts)
การจัดการกับเมาส์ใน OpenCV อาศัยกลไกที่เรียกว่า Callback Function หลักการทำงานคือ เราจะเขียนฟังก์ชันเตรียมไว้ แล้วเอาไปฝากไว้กับระบบของ OpenCV เหมือนเราจ้างยามไปเฝ้าหน้าต่าง (Window) แล้วสั่งยามไว้ว่า “ถ้ามีคนมาคลิกเมาส์ หรือขยับเมาส์ในหน้าต่างนี้ ให้โทรมาบอกที่ฟังก์ชันนี้นะ!”
เพื่อให้ทำงานได้สมบูรณ์ เราต้องรู้จักองค์ประกอบ 3 ส่วนนี้ครับ:
- 1. ฟังก์ชัน
setMouseCallback(การจ้างยาม): เป็นคำสั่งที่เราใช้บอก OpenCV ว่าหน้าต่างไหนที่จะให้ดักจับเมาส์ และจะให้เรียกใช้ Callback Function ตัวไหน โดยมีพารามิเตอร์สำคัญ 3 ตัวคือ ชื่อหน้าต่าง, ชื่อฟังก์ชัน Callback, และuserdata(เอาไว้แอบส่งข้อมูลไปให้ฟังก์ชันโดยไม่ต้องประกาศตัวแปร Global) - 2. รูปแบบของ Callback Function:
ฟังก์ชันที่เราจะให้ OpenCV เรียกใช้งาน “ต้อง” มีโครงสร้าง (Signature) ที่กำหนดไว้เป๊ะๆ คือรับค่า 5 ตัว ได้แก่
event,x,y,flags, และuserdataโดยที่:event: ชนิดของเหตุการณ์ที่เกิดขึ้น (เช่น คลิกซ้าย, คลิกขวา, เลื่อนเมาส์)x,y: พิกัดที่เมาส์ชี้อยู่บน Image Matrix (ไม่ได้อิงตามพิกัดหน้าจอคอมพิวเตอร์ แต่เป็นพิกัดบนรูปภาพ)flags: ใช้เช็กว่ามีการกดปุ่มพิเศษค้างไว้ไหม (เช่น ลากเมาส์ค้างไว้ หรือกด Shift/Ctrl ค้าง)userdata: ข้อมูลที่เราแนบมา (มักจะใช้ Pointer ชี้ไปที่cv::Matเพื่อให้เราแก้ภาพได้)
- 3. ประเภทของ Events ที่ใช้บ่อย:
EVENT_MOUSEMOVE: เมื่อเมาส์ถูกขยับEVENT_LBUTTONDOWN: เมื่อผู้ใช้ “กด” เมาส์ปุ่มซ้ายEVENT_LBUTTONUP: เมื่อผู้ใช้ “ปล่อย” เมาส์ปุ่มซ้ายEVENT_FLAG_LBUTTON: เป็น Flag ที่บอกว่าตอนนี้เมาส์ปุ่มซ้ายกำลังถูก “กดค้างไว้” (ลากเมาส์)

4. 💻 ร่ายมนต์คำสั่ง (Show me the Code)
เรามาดูตัวอย่างการเขียนโค้ด C++ สำหรับแอปพลิเคชันวาดรูปด้วยเมาส์กันครับ โค้ดนี้จะอนุญาตให้เราคลิกเพื่อวาดวงกลม และถ้าคลิกค้างแล้วลาก จะเป็นการวาดเส้นต่อเนื่องครับ
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
// 1. สร้าง Callback Function ที่มีโครงสร้างตามที่ OpenCV กำหนดเป๊ะๆ
void myMouseCallback(int event, int x, int y, int flags, void* userdata) {
// แปลง userdata ที่เป็น void pointer กลับมาเป็น Mat pointer เพื่อให้เราวาดรูปลงไปได้
Mat* img = (Mat*)userdata;
// ดักจับเหตุการณ์: ถ้ามีการคลิกเมาส์ซ้าย 1 ครั้ง
if (event == EVENT_LBUTTONDOWN) {
// วาดวงกลมสีเขียวทึบ ขนาดรัศมี 5 พิกเซล ลงบนพิกัด x,y ที่คลิก
circle(*img, Point(x, y), 5, Scalar(0, 255, 0), FILLED);
}
// ดักจับเหตุการณ์: ถ้ามีการ "ขยับเมาส์" และ "กำลังกดปุ่มซ้ายค้างไว้" (ลากเมาส์)
else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {
// วาดวงกลมเล็กๆ สีน้ำเงิน เพื่อสร้างเส้นต่อเนื่อง
circle(*img, Point(x, y), 2, Scalar(255, 0, 0), FILLED);
}
}
int main() {
// 2. สร้างผืนผ้าใบ (Image Matrix) สีดำขนาด 500x500 พิกเซล
Mat image = Mat::zeros(500, 500, CV_8UC3);
String windowName = "Interactive Mouse Tracking";
// 3. สร้างหน้าต่าง HighGUI ขึ้นมารอ
namedWindow(windowName, WINDOW_AUTOSIZE);
// 4. ผูกเวทมนตร์! ติดตั้ง Callback function ลงบนหน้าต่าง
// สังเกตว่าเราส่ง &image เข้าไปที่พารามิเตอร์ userdata เพื่อให้ฟังก์ชันเข้าถึงรูปภาพได้
setMouseCallback(windowName, myMouseCallback, &image);
cout << "ลองคลิกเมาส์ซ้ายเพื่อวาดวงกลม หรือคลิกค้างเพื่อวาดเส้นดูสิครับ!" << endl;
// 5. ลูปแสดงผลภาพแบบ Real-time
while (true) {
imshow(windowName, image);
// รอรับค่าคีย์บอร์ด 15 มิลลิวินาที ถ้ากด ESC (รหัส 27) ให้ออกจากลูป
char key = (char)waitKey(15);
if (key == 27) break;
}
destroyAllWindows();
return 0;
}5. 🛡️ เคล็ดลับจากคัมภีร์ลับ (Under the Hood / Pro-Tips)
- ศิลปะแห่งการ Casting Pointer (The
void*Magic): ใน C++ ฟังก์ชัน Callback รับค่าข้อมูลเสริมผ่านพารามิเตอร์void* userdataครับ ข้อดีคือมันทำให้เราส่งอ็อบเจ็กต์อะไรเข้าไปก็ได้ (ในโค้ดเราส่งที่อยู่ของรูปภาพ&imageไป) แต่เวลาจะใช้งานในฟังก์ชัน เราต้องทำ Type Casting แปลงมันกลับมาเป็นMat*เสมอ (Mat* img = (Mat*)userdata;) วิธีนี้คือท่ามาตรฐานที่วิศวกรระดับโปรใช้ เพื่อหลีกเลี่ยงการประกาศตัวแปรภาพแบบ Global ซึ่งทำให้โค้ดรันได้ปลอดภัยและนำไปสเกลต่อได้ง่ายครับ! - ตรวจสอบสถานะด้วย Bitwise AND (
&): สังเกตตรงเงื่อนไข(flags & EVENT_FLAG_LBUTTON)ไหมครับ? พารามิเตอร์flagsเป็นตัวแปรแบบ Bit-field ที่สามารถเก็บสถานะหลายๆ อย่างพร้อมกันได้ (เช่น กดทั้งเมาส์ซ้ายและปุ่ม Shift) การใช้เครื่องหมาย&(Bitwise AND) เป็นการทะลวงเช็กบิตว่าสถานะ “กดปุ่มซ้ายค้าง” ถูกเปิดใช้งานอยู่หรือไม่ มันทำงานได้เร็วกว่าการเขียนเช็ก==ทั่วไปหลายเท่าครับ - ระวังพิกัดทะลุขอบ: ตัวแปร
xและyที่ส่งเข้ามาคือพิกัดบนรูปภาพ ถ้าเราเขียนโปรแกรมดึงค่าสี (เช่น.at<Vec3b>(y,x)) อย่าลืมเขียนifเพื่อเช็กก่อนเสมอว่า x และ y ไม่ได้หลุดออกไปนอกขนาดของภาพ (img->cols,img->rows) ไม่งั้นถ้า User เผลอลากเมาส์ทะลุขอบหน้าต่าง โปรแกรมอาจจะ Crash ดับไปดื้อๆ ได้ครับ!
6. 🏁 บทสรุป (To be continued…)
ยอดเยี่ยมมากครับน้องๆ! ตอนนี้แอปพลิเคชัน AI ของเราไม่ใช่แค่ตัวรับภาพนิ่งๆ อีกต่อไป แต่กลายเป็นโปรแกรมที่สามารถสื่อสารโต้ตอบกับผู้ใช้งานผ่านการคลิกเมาส์และลากวาด Bounding Box ได้แล้ว เทคนิคนี้คือจุดเริ่มต้นสำคัญหากเราต้องการสร้างเครื่องมือสำหรับทำ Data Annotation ไว้เทรนโมเดล Deep Learning ในอนาคตเลยล่ะครับ
ในตอนต่อไป พี่จะพาน้องๆ ไปรู้จักกับอีกหนึ่งเครื่องมือโต้ตอบบน HighGUI ที่ยอดฮิตไม่แพ้กัน นั่นก็คือ “Trackbars” (แถบเลื่อน) ที่จะช่วยให้เราปรับค่าพารามิเตอร์ของ Filter ต่างๆ ได้แบบ Real-time โดยไม่ต้องแก้โค้ดคอมไพล์ใหม่ให้เสียเวลา เตรียมตัวพบกับความสะดวกสบายได้เลยครับ!
ต้องการที่ปรึกษาด้านการพัฒนาระบบ AI Camera หรือ Machine Vision ให้กับโรงงานของคุณ? ทีมงาน WP Solution พร้อมให้บริการออกแบบและติดตั้งระบบแบบครบวงจร ดูรายละเอียดบริการของเราได้ที่: www.wpsolution2017.com หรือพูดคุยปรึกษาเบื้องต้นได้ที่ Line: wisit.p