รูปปกบทความ

1. 🎯 ตอนที่ 22: การเชื่อมต่อเซนเซอร์ LiDAR เข้ากับ ROS (ดวงตาเลเซอร์ 2 มิติ)

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

เพื่อให้หุ่นยนต์รับรู้สภาพแวดล้อมรอบตัว เราต้องติด “ดวงตา” ให้กับมัน และอุปกรณ์ยอดฮิตที่เป็นพระเอกในวงการหุ่นยนต์เคลื่อนที่อัตโนมัติ (AMR) ก็คือ LiDAR (Light Detection and Ranging) นั่นเองครับ วันนี้พี่จะพาไปเจาะลึกว่าหุ่นยนต์รับรู้ข้อมูลเลเซอร์อย่างไร โครงสร้างแพ็กเกจข้อมูลหน้าตาเป็นแบบไหน และเราจะดึงข้อมูลนี้มาแสดงผลใน RViz ได้อย่างไร เตรียมตัวให้พร้อมครับ!

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

ลองนึกภาพน้องๆ กำลังเดินหลับตาอยู่ในห้องมืดๆ สิครับ วิธีเดียวที่จะรู้ว่ามีกำแพงอยู่ตรงหน้าคือการยื่นมือคลำไปเรื่อยๆ ใช่ไหมครับ? หุ่นยนต์ก็เหมือนกัน!

LiDAR คืออุปกรณ์ที่ทำหน้าที่ยื่น “มือเลเซอร์” ออกไปคลำทางแทน หลักการของมันคือการยิงแสงเลเซอร์ออกไปกระทบวัตถุ แล้วจับเวลาที่แสงสะท้อนกลับมา (Time of Flight) เพื่อคำนวณระยะทาง ความเจ๋งคือมันมีกระจกหมุนอยู่ข้างในด้วยมอเตอร์ ทำให้มันสามารถกวาดเลเซอร์รอบตัวได้ถึง 360 องศาภายในเสี้ยววินาที!

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

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

ในระบบ ROS ข้อมูลที่ส่งออกมาจาก LiDAR จะถูกบรรจุอยู่ในมาตรฐาน Message ที่ชื่อว่า sensor_msgs/LaserScan เสมอครับ (มักจะถูก Publish ออกมาที่ Topic ชื่อ /scan) โครงสร้างของกล่องพัสดุนี้ประกอบด้วยฟิลด์สำคัญๆ ดังนี้ครับ:

  • std_msgs/Header header: ส่วนหัวที่บอก stamp (เวลาที่สแกน) และ frame_id (ชื่อแกนพิกัดที่เลเซอร์ติดอยู่ เช่น laser_link หรือ laser_frame)
  • float32 angle_min และ angle_max: มุมเริ่มต้นและมุมสิ้นสุดของการกวาดเลเซอร์ (หน่วยเป็นเรเดียน)
  • float32 angle_increment: ความละเอียดของมุมระหว่างการยิงเลเซอร์แต่ละเส้น (เช่น เลเซอร์ยิงห่างกันทุกๆ 1 องศา)
  • float32 range_min และ range_max: ระยะทางใกล้สุดและไกลสุดที่เซนเซอร์ตัวนี้วัดได้ (เช่น วัดได้ตั้งแต่ 0.1 เมตร ถึง 10.0 เมตร)
  • float32[] ranges (พระเอกของเรา!): นี่คือ “อาร์เรย์ (Array)” ที่เก็บค่าระยะทาง (Distance) ที่วัดได้ในแต่ละมุมองศาเรียงต่อกันไปครับ!
  • float32[] intensities: ค่าความเข้มของแสงสะท้อน (ใช้วิเคราะห์พื้นผิววัตถุได้ แต่บางเซนเซอร์ก็ไม่มีค่านี้มาให้)

Trick ทางวิศวกรรม: ถ้าหุ่นยนต์ติด LiDAR หันหน้าตรง การหาว่า “มีกำแพงอยู่ข้างหน้าหุ่นยนต์ไหม?” เรามักจะไปดึงค่าจาก ตำแหน่งตรงกลางของอาร์เรย์ ranges ครับ! (เช่น ถ้ามี 720 ค่า ค่าตรงกลางแถวๆ Index 360 คือด้านหน้าสุดของหุ่นยนต์)

รูปประกอบโครงสร้าง sensor_msgs/LaserScan

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

เรามาลองเขียน Node Python สั้นๆ เพื่อ Subscribe ดักฟังข้อมูล /scan แล้วปริ้นต์ระยะทางด้านหน้าสุดออกมาดูกันครับ:

ไฟล์โค้ด Python (lidar_obstacle_detector.py):

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
import numpy as np

class LidarProcessor(Node):
    def __init__(self):
        super().__init__('lidar_processor_node')
        
        # 1. สร้าง Subscriber เพื่อดักฟัง Topic /scan ด้วย Message ชนิด LaserScan
        self.subscription = self.create_subscription(
            LaserScan,
            '/scan',
            self.scan_callback,
            10
        )
        self.get_logger().info('Lidar Processor Node Started!')

    # 2. ฟังก์ชัน Callback ที่จะทำงานทุกครั้งที่ได้รับข้อมูลเลเซอร์ 1 รอบ
    def scan_callback(self, msg: LaserScan):
        # 3. ดึงอาร์เรย์ระยะทางออกมา
        ranges = msg.ranges
        
        # 4. หากำแพงที่อยู่ตรงหน้าเป๊ะๆ (ตรงกลางของ Array)
        front_index = len(ranges) // 2
        front_distance = ranges[front_index]
        
        # 5. หาวัตถุที่อยู่ใกล้หุ่นยนต์ที่สุดจากการสแกนรอบตัว
        # (ข้อควรระวัง: ต้องกรองค่าที่วัดไม่ได้ หรือ inf ทิ้งไปก่อน)
        valid_ranges = [r for r in ranges if not np.isinf(r) and not np.isnan(r)]
        if valid_ranges:
            min_distance = min(valid_ranges)
            
            # แจ้งเตือนถ้าระยะใกล้กว่า 1 เมตร
            if min_distance < 1.0:
                self.get_logger().warn(f'⚠️ ระวัง! เจอสิ่งกีดขวางที่ระยะ {min_distance:.2f} เมตร!')
            else:
                self.get_logger().info(f'ทางสะดวก (ระยะหน้าสุด: {front_distance:.2f} m)')

def main(args=None):
    rclpy.init(args=args)
    node = LidarProcessor()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

การนำไปแสดงผลใน RViz (เพื่อให้เห็นเป็นภาพ!) การนั่งดูตัวเลขปริ้นต์รัวๆ คงไม่ใช่วิถีของวิศวกรครับ เราต้องเห็นภาพ!

  1. พิมพ์คำสั่ง ros2 run rviz2 rviz2
  2. ที่พาเนลซ้ายมือ (Displays) ตรง Global Options -> Fixed Frame ให้เลือกชื่อเฟรมของ LiDAR (เช่น laser, laser_link หรือ base_link)
  3. กดปุ่ม Add ด้านล่างซ้าย
  4. เลือกปลั๊กอิน LaserScan แล้วกด OK
  5. ขยายเมนู LaserScan ออกมา ตรงช่อง Topic ให้เลือก /scan
  6. (Optional) ปรับค่า Size (m) ให้จุดใหญ่ขึ้น (เช่น 0.05) และปรับ Color Transformer เป็น FlatColor หรือ Intensity

ปิ๊ง! น้องๆ จะเห็นจุดสีแดงๆ ลอยขึ้นมาประกอบกันเป็นรูปร่างของห้องรอบตัวหุ่นยนต์แบบ Real-time เลยครับ!

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

ในฐานะที่พี่เขียนโค้ดชนกำแพงมาแล้วนับไม่ถ้วน นี่คือสิ่งที่น้องๆ ต้องระวังครับ:

  • กับดักค่า inf (Infinity) และ NaN: วิศวกรมือใหม่มักจะเอา Array ของ ranges ไปเข้าสมการบวกลบคูณหารตรงๆ แล้วโปรแกรมก็ Crash ครับ! ต้องระวังไว้ว่า ถ้าเลเซอร์ยิงไปเจอวัตถุที่ ใกล้เกินไป (ต่ำกว่า range_min) หรือ ไกลเกินไป (เกิน range_max) เซนเซอร์จะพ่นค่า inf (อนันต์) หรือ NaN (Not a Number) ออกมา เราต้องเขียนโค้ดกรองค่าพวกนี้ทิ้งก่อนเสมอ!
  • RViz มองไม่เห็นเลเซอร์ (TF Error): ถ้าน้องเลือก Topic /scan แล้ว แต่หน้าจอ RViz ยังมืดสนิทพร้อม Error สีแดง “No tf data…” แปลว่าระบบยังไม่รู้จักความสัมพันธ์ของพิกัดครับ (TF Tree ขาดหาย) น้องต้องแน่ใจว่าได้เปิดโหนด robot_state_publisher ที่ปล่อย URDF ซึ่งเชื่อมต่อ base_link เข้ากับเฟรมของเลเซอร์ไว้เรียบร้อยแล้ว
  • LiDAR ยอดฮิตในวงการ ROS: ถ้าน้องๆ อยากทำหุ่นยนต์จริง พี่ขอแนะนำ LiDAR รุ่นประหยัดที่เข้ากันได้ดีกับ ROS เช่น RPLIDAR (จากค่าย SLAMTEC), Hokuyo (เช่นรุ่น URG-04LX) หรือ YDLIDAR ครับ อุปกรณ์พวกนี้มี Package เสียบปุ๊บ พ่น Topic /scan ให้ใช้ได้ทันที ประหยัดเวลาไปได้เยอะมาก!

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

การเชื่อมต่อเซนเซอร์ LiDAR เข้ากับ ROS ถือเป็นการเปิด “ดวงตา” ให้กับหุ่นยนต์อย่างเป็นทางการครับ ด้วยข้อความ sensor_msgs/LaserScan เราสามารถดึงตัวเลขนับร้อยค่ามาวิเคราะห์หาจุดที่ใกล้ที่สุด เพื่อสั่งให้หุ่นยนต์เบรกฉุกเฉิน หรือเดินหลบหลีกสิ่งกีดขวางได้

แต่แค่การหลบหลีกมันยังไม่พอหรอกครับ! การจะทำให้หุ่นยนต์เดินอัตโนมัติจากจุด A ไป B ได้ มันต้อง “จำ” ได้ด้วยว่าเคยเดินไปตรงไหนแล้วบ้าง ในตอนต่อไป พี่จะพาน้องๆ นำข้อมูลจาก LiDAR เหล่านี้ ไปประกอบกันเพื่อสร้าง “แผนที่ห้อง 2 มิติ (Mapping)” ด้วยอัลกอริทึม SLAM เตรียมตัวตื่นเต้นกันได้เลยครับ!


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