รูปปกบทความ

1. 🎯 ตอนที่ 23: การเชื่อมต่อกล้องและ Point Cloud เบิกเนตร 3 มิติให้หุ่นยนต์

สวัสดีครับน้องๆ วิศวกรหุ่นยนต์ทุกคน! ในตอนที่แล้วเราได้เรียนรู้วิธีการใช้ LiDAR เพื่อให้หุ่นยนต์ “คลำทาง” ในโลก 2 มิติกันไปแล้ว แต่ถ้าเราอยากให้หุ่นยนต์เสิร์ฟกาแฟของเราสามารถ “มองเห็น” ถ้วยกาแฟ, “จดจำ” ใบหน้าของลูกค้า หรือ “รับรู้” ความลึกของบันไดได้ล่ะ? ลำพังแค่เลเซอร์ 2 มิติคงไม่พอแน่นอนครับ!

วันนี้พี่จะพาน้องๆ ก้าวเข้าสู่โลกของ “Computer Vision” ใน ROS 2 เราจะมาเบิกเนตรให้หุ่นยนต์ด้วยการเชื่อมต่อกล้อง USB ธรรมดา ไปจนถึงกล้องระดับเทพอย่าง 3D Depth Sensors (เช่น Microsoft Kinect หรือ Intel RealSense) เตรียมตัวรับมือกับข้อมูลภาพและกลุ่มเมฆสามมิติกันให้พร้อมครับ!

2. 📖 เปิดฉาก (The Hook)

“พี่ครับ ผมเขียนโค้ด Python เปิดกล้องด้วย OpenCV ได้สบายมาก ทำไมผมต้องมาใช้ ROS 2 ด้วยล่ะครับ?” นี่คือคำถามที่พี่เจอบ่อยมากจากน้องๆ ที่เพิ่งย้ายสายจากงาน AI มาทำหุ่นยนต์ครับ

ความจริงก็คือ ถ้าน้องทำแค่โปรเจกต์ตรวจจับใบหน้าบนคอมพิวเตอร์เครื่องเดียว OpenCV เพียวๆ ก็เอาอยู่ครับ แต่ในโลกของหุ่นยนต์อุตสาหกรรม กล้องอาจจะติดอยู่ที่ปลายแขนกล (End-effector) สมองประมวลผล AI อาจจะอยู่บนบอร์ด Nvidia Jetson ที่ซ่อนอยู่ในตัวหุ่น และจอแสดงผลอาจจะอยู่บนคอมพิวเตอร์ของวิศวกรที่นั่งอยู่อีกห้อง!

ถ้าน้องไม่มี Middleware อย่าง ROS 2 น้องจะต้องมานั่งเขียนโค้ดส่งภาพผ่าน Network (Socket Programming) เองให้ปวดหัว! ใน ROS 2 แค่น้องประกาศ Topic เดียว ภาพจากกล้องก็จะถูกกระจาย (Publish) ไปให้ Node ทุกตัวในระบบที่ต้องการใช้ ไม่ว่าจะเป็น Node สำหรับทำ Face Recognition, Node สำหรับทำ Visual SLAM หรือแม้แต่แสดงผลผ่าน RViz ได้อย่างง่ายดาย นี่แหละครับคือพลังของระบบกระจายศูนย์!

3. 🧠 แก่นวิชา (Core Concepts)

การนำข้อมูลภาพเข้ามาใน ROS 2 จะมีมาตรฐานโครงสร้างข้อมูล (Message) ที่ถูกออกแบบมาอย่างรัดกุม โดยแบ่งออกเป็น 2 โลกหลักๆ คือ โลก 2 มิติ และ โลก 3 มิติครับ:

  • โลก 2 มิติ: sensor_msgs/Image และ กล้อง USB (usb_cam) เวลาเราเสียบกล้อง Web Camera เข้ากับหุ่นยนต์ เรามักจะใช้ Package มาตรฐานที่ชื่อว่า usb_cam เพื่อดึงภาพจากพอร์ต /dev/video0 ข้อมูลภาพที่ได้จะถูกแพ็กใส่กล่องพัสดุชนิด sensor_msgs/Image ซึ่งข้างในจะมีรายละเอียดเพียบ! เช่น width (ความกว้าง), height (ความสูง), encoding (เช่น rgb8, mono8), และ data (ก้อนอาร์เรย์ของพิกเซลภาพ)

  • สะพานเชื่อมสองโลก: cv_bridge ROS Image ไม่เหมือนกับภาพใน OpenCV (ซึ่งเป็นชนิด cv::Mat ใน C++ หรือ Numpy Array ใน Python) ถ้าน้องเอา ROS Image ไปโยนใส่ OpenCV ตรงๆ โปรแกรมจะพังทันที! เราจึงต้องมี “ล่ามแปลภาษา” ที่เรียกว่า cv_bridge เพื่อแปลงข้อมูลกลับไปกลับมาระหว่าง ROS 2 และ OpenCV อย่างไร้รอยต่อครับ

  • โลก 3 มิติ: sensor_msgs/PointCloud2 และกล้อง Depth Sensors กล้องอย่าง Kinect, Asus Xtion หรือ Intel RealSense (RGB-D Cameras) จะไม่ได้ให้แค่ภาพสี แต่จะให้ “ภาพความลึก (Depth Image)” มาด้วย! เมื่อเรานำภาพสองชนิดนี้มาซ้อนทับกันโดยใช้พารามิเตอร์การจัดตำแหน่ง (Registration) เราจะได้สิ่งที่เรียกว่า Point Cloud มันคือ “ฝูงละอองดาว 3 มิติ” ที่แต่ละจุด (Point) มีพิกัด X, Y, Z และมีสี RGB แปะอยู่ ข้อมูลมหาศาลเหล่านี้จะถูกบรรจุลงใน Message ชนิด sensor_msgs/PointCloud2 ซึ่งรองรับการจัดเก็บข้อมูลแบบไม่มีโครงสร้างตายตัว (Unstructured) ทำให้จัดการกับจุดบอด (Holes/NaN) ที่เซ็นเซอร์วัดไม่ถึงได้ดีครับ

รูปประกอบ Architecture การดึงภาพและ Point Cloud เข้าสู่ ROS 2

4. 💻 ร่ายมนต์โค้ดและคำสั่ง (Show me the Code)

เรามาลองเขียน Node ด้วย Python เพื่อดึงภาพจาก Topic ของกล้อง แล้วใช้ล่าม cv_bridge แปลงภาพมาโชว์บนหน้าต่าง OpenCV กันครับ!

ตัวอย่างโค้ด: การรับภาพและแปลงด้วย cv_bridge (image_processor.py):

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image # กล่องพัสดุภาพของ ROS 2
from cv_bridge import CvBridge, CvBridgeError # ล่ามแปลภาษา
import cv2 # ไลบรารี OpenCV สุดเก๋า

class ImageProcessor(Node):
    def __init__(self):
        super().__init__('image_processor_node')
        
        # 1. จ้างล่ามแปลภาษา
        self.bridge = CvBridge()
        
        # 2. สร้าง Subscriber ไปรอรับภาพจากกล้อง
        self.subscription = self.create_subscription(
            Image,
            '/usb_cam/image_raw',
            self.image_callback,
            10
        )
        self.get_logger().info('รอรับภาพจากกล้องอยู่จ้า...')

    # 3. Callback Function ทำงานทุกครั้งที่มีภาพใหม่วิ่งเข้ามา
    def image_callback(self, msg: Image):
        try:
            # 4. ให้ล่ามแปลภาพ ROS (msg) เป็น OpenCV format (bgr8)
            cv_image = self.bridge.imgmsg_to_cv2(msg, "bgr8")
            
            # 5. ตอนนี้เราได้ cv_image แล้ว! จะเอาไปทำ Face Detection ก็ลุยเลย!
            # ตัวอย่างนี้พี่ขอแค่แปลงเป็นภาพขาวดำ (Grayscale) ให้ดู
            gray_image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
            
            # โชว์ภาพออกทางหน้าจอ
            cv2.imshow("Processed Image", gray_image)
            cv2.waitKey(3) # หน่วงเวลาให้ OpenCV วาดภาพทัน
            
        except CvBridgeError as e:
            self.get_logger().error(f'งานเข้า! แปลงภาพไม่สำเร็จ: {e}')

def main(args=None):
    rclpy.init(args=args)
    node = ImageProcessor()
    rclpy.spin(node)
    node.destroy_node()
    cv2.destroyAllWindows()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

คำสั่งสำหรับการดู Point Cloud ของ RealSense/Kinect ใน RViz:

# 1. เปิด RViz2 ขึ้นมา
ros2 run rviz2 rviz2

# 2. เปลี่ยน Fixed Frame เป็น `camera_depth_optical_frame` (หรือตามสเปคเซ็นเซอร์)
# 3. กด Add -> เลือก PointCloud2 -> ตั้ง Topic ไปที่ `/camera/depth_registered/points`
# 4. ตรง Color Transformer ให้เลือก "RGB8" เพื่อให้จุดละอองดาวมีสีสันเหมือนจริง!

5. 🛡️ เคล็ดลับจากคัมภีร์ลับ (Under the Hood / Pro-Tips)

ในฐานะที่พี่เล่นกับกล้องจนคอมแฮงก์มาหลายรอบ นี่คือวิชาลับที่น้องๆ ต้องรู้ครับ:

  • วิกฤตจราจรแบนด์วิดท์ (Bandwidth Overhead): รู้หรือไม่ว่าการส่งภาพ sensor_msgs/Image ความละเอียด 720p ที่ 30fps แบบดิบๆ (Raw) จะกินแบนด์วิดท์เครือข่ายถึงเกือบ 80 MB/s! ถ้าน้องส่งผ่าน Wi-Fi เครือข่ายจะล่มทันทีครับ! กฎเหล็กคือในระบบจริง เราจะใช้ Package ที่ชื่อว่า image_transport เพื่อบีบอัดภาพ (เช่น เป็น JPEG) และส่งผ่าน Topic /usb_cam/image_raw/compressed แทนเสมอครับ
  • จุดบอดและการกรอง Point Cloud ด้วย PCL: Point Cloud จาก Kinect 1 เฟรมอาจมีจุด (Points) ถึง 3 แสนจุด! การนำไปประมวลผลสดๆ จะทำให้ CPU ไหม้ได้ เราจำเป็นต้องใช้ไลบรารี PCL (Point Cloud Library) เข้ามาช่วย โดยเทคนิคยอดฮิตคือการใช้ VoxelGrid Filter (คล้ายๆ การทำ Pixelate ให้ภาพแตกๆ) เพื่อลดจำนวนจุดลงแบบ Down-sampling หรือการกรองเอาพื้นห้องออกไป (Planar Segmentation) ครับ
  • กล้องตาเหล่? อย่าลืมทำ Camera Calibration!: กล้องราคาถูกมักจะมีเลนส์ที่ทำให้ภาพโค้งบิดเบี้ยว (Distortion) ถ้าน้องเอาภาพดิบไปคำนวณระยะทาง ข้อมูลจะเพี้ยนหมด! ใน ROS 2 เรามีฟีเจอร์ Calibration โดยให้กล้องมองภาพกระดานหมากรุก (Checkerboard) เพื่อคำนวณเมทริกซ์ความคลาดเคลื่อน แล้วใช้ Node ที่ชื่อ image_proc ในการรีดภาพให้ตรงเป๊ะ (Rectified Image) ก่อนนำไปใช้งานจริงครับ!

6. 🏁 บทสรุป (To be continued…)

เห็นไหมครับว่าการเชื่อมต่อกล้องเข้ากับ ROS 2 ไม่ใช่เรื่องยากเลย! เรามี usb_cam คอยดึงภาพ, มี cv_bridge เป็นล่ามแปลภาษาไปคุยกับ OpenCV, และมี sensor_msgs/PointCloud2 เป็นกล่องเก็บข้อมูลสามมิติระดับจักรวาล ทุกอย่างถูกเตรียมไว้ให้หมดแล้ว หน้าที่ของเราคือการหยิบเครื่องมือเหล่านี้มาประกอบร่างกันเท่านั้นเอง

ตอนนี้หุ่นยนต์ของเรามีทั้งดวงตา 2 มิติ และสัมผัสความลึก 3 มิติแล้ว! ในตอนต่อไป พี่จะพาน้องๆ เอาข้อมูลภาพและการรับรู้ 3 มิติเหล่านี้ ไปประยุกต์ใช้กับเทคโนโลยีขั้นสูงอย่าง AR Tags (Fiducial Markers) เพื่อให้หุ่นยนต์สามารถคำนวณท่าทางของวัตถุและกะระยะการหยิบจับได้อย่างแม่นยำ เตรียมตัวตื่นเต้นกันต่อได้เลยครับ!


ต้องการที่ปรึกษาด้านการออกแบบสถาปัตยกรรมหุ่นยนต์ (Robotics) และระบบ Automation ให้กับองค์กรของคุณ? ทีมงาน WP Solution พร้อมให้บริการออกแบบและพัฒนาซอฟต์แวร์แบบครบวงจร ดูรายละเอียดบริการของเราได้ที่: www.wpsolution2017.com หรือพูดคุยปรึกษาเบื้องต้นได้ที่ Line: wisit.p