반응형

안녕하세요. 요즘 ROS 공부중인 맨 땅에 헤딩 '맨땅헤' 입니다.

 

ROS 를 아예 몰랐는데 실습해보면서 이해하고 있는 중입니다.

 

이번 포스팅에서는 따라할 수는 없겠지만, 코드 리뷰를 하면서 ROS 를 이용한 로봇제어 매커니즘에 대해 정리해보려고 합니다.

 

ros 를 다루기 전 필요한 환경세팅

 

gedit ~/.bashrc

bashrc 을 열어서

 

# ROS 2 Humble 환경 설정
alias humble="source /opt/ros/humble/setup.bash; echo \"ROS2 humble is activated!\""

 

을 마지막 줄에 추가해주면, humble 이라고 입력시 , setup.bash 가 활성화 됩니다.

 

# ROS_DOMAIN_ID 설정
export ROS_DOMAIN_ID=30
echo "ROS_DOMAIN_ID set to 30"

 

humble 활성화 설정 다음에 도메인 ID도 로봇과 동일하게 설정해주자.

 

우선, 해당 로봇에게 ssh로 연결하고, bring up 을 해줬습니다.

 

빨간색 글씨는  '토픽 (ex: /scan ) '입니다.

 

 

이 로봇에는 ros2 humble 환경이 설치 되어있고, 기본적인 패키지들이 있는 상태입니다.

 

로봇에게서 받아올 수 있는 정보는 라이다 /scan 정보 입니다.

 

(실제 토픽리스트로는 로봇에게 이동 명령을 줄 수 있는 /cmd_vel  도 있습니다. )

 

로봇과 DOMAIN ID 를 맞추고,

 

ros2 topic list

 

를 해보면,

 

/scan 을 확인할 수 있다

브링업만 했는데, 자동적으로 라이다센서가 작동되고 /scan 토픽을 발행하고 있는 것을 알 수 있다.

이를 이용해보려고 한 코드입니다.

 

 

obstacle_avoid.py

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
from geometry_msgs.msg import Twist
class ObstacleAvoidanceNode(Node):
    def __init__(self):
        super().__init__('obstacle_avoidance')
        self.get_logger().info('Obstacle Avoidance Node Initialized')
        # 라이다 데이터 구독
        self.subscription = self.create_subscription(
            LaserScan,
            '/scan',
            self.scan_callback,
            10
        )
        self.subscription  # prevent unused variable warning
        # 속도 명령 퍼블리셔
        self.publisher = self.create_publisher(Twist, '/cmd_vel', 1)
        # 속도 명령 메시지
        self.cmd_msg = Twist()
        # 장애물 탐지 범위 설정
        self.obstacle_distance_threshold = 0.35  # 20cm
        self.min_intensity_threshold = 1000.0  # 반사 강도가 너무 낮은 값 필터링
    def scan_callback(self, msg):
        # 라이다 데이터에서 유효한 거리 값 필터링
        filtered_ranges = self.filter_noise(msg.ranges, msg.intensities, msg.range_min, msg.range_max)
        # 가장 가까운 장애물 거리 확인
        closest_distance = min(filtered_ranges) if filtered_ranges else float('inf')
        # 충돌 회피 논리
        if closest_distance < self.obstacle_distance_threshold:
            self.avoid_obstacle()
        else:
            self.move_forward()
    def filter_noise(self, ranges, intensities, range_min, range_max):
        """
        라이다 데이터의 노이즈를 필터링.
        - 최소/최대 거리 필터링
        - 반사 강도(intensity) 기반 필터링
        """
        filtered = []
        for r, i in zip(ranges, intensities):
            if range_min <= r <= range_max and i >= self.min_intensity_threshold:
                filtered.append(r)
        return filtered
    def avoid_obstacle(self):
        """
        충돌 회피 동작.
        - 후진 또는 회전 명령을 발행.
        """
        self.get_logger().info("Obstacle detected! Avoiding...")
        self.cmd_msg.linear.x = -0.1  # 뒤로 이동
        self.cmd_msg.angular.z = 0.5  # 회전
        self.publisher.publish(self.cmd_msg)
    def move_forward(self):
        """
        장애물이 없을 때 앞으로 이동.
        """
        self.cmd_msg.linear.x = 0.2  # 직진
        self.cmd_msg.angular.z = 0.0
        self.publisher.publish(self.cmd_msg)
def main(args=None):
    rclpy.init(args=args)
    node = ObstacleAvoidanceNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()
if __name__ == '__main__':
    main()

 

 

def_init_(self):

1. 초기화

    def __init__(self):
        super().__init__('obstacle_avoidance')
        self.get_logger().info('Obstacle Avoidance Node Initialized')

 

  • Node 클래스를 상속하여 ROS 2 노드를 정의합니다. 노드 이름은 'obstacle_avoidance'입니다.
  • 노드 초기화 시, 로거를 통해 'Obstacle Avoidance Node Initialized'라는 메시지를 출력합니다.

 


 여기서 노드란?

그냥 쉽게 말해서 어떠한 '기능' 이라고 생각하면 쉽습니다. 이전 포스팅에서 썼던 걸 인용하자면,

rqt_graph 로 확인해보자

 

rqt_graph

 

우리가 실행시켰던 

ros2 run usb_cam usb_cam_node_exe

 

usb_cam   노드 이름이고( 카메라 실행 기능 ), 얘가 발행하고 있는 데이터 타입 (=토픽 ) 의 형태가 /image_raw

 데이터( 토픽) 을 구독하고 있는 노드 camera_subscriber ( 카메라 데이터 구독 기능 )

 


 

2. 라이다 데이터 구독 (/scan 을 구독 )

self.subscription = self.create_subscription(
    LaserScan,
    '/scan',
    self.scan_callback,
    10
)

 

  • 이 부분은 라이다 데이터(/scan 토픽)를 구독합니다.
  • 메시지 타입은 LaserScan이며, 구독된 데이터는 self.scan_callback 메서드로 전달됩니다.
  • 메시지 큐 크기는 10으로 설정되었습니다.

 

이 부분은 라이다 데이터(/scan) 를 가져왔다 라고 생각하면 편합니다.

메시지 형태는 LaserScan 입니다. 

 

3. 속도 명령 퍼블리셔

self.publisher = self.create_publisher(Twist, '/cmd_vel', 1)
self.cmd_msg = Twist()

 

 

로봇에게 움직이라고 명령 ( /cmd_vel 토픽 ) 을 발행합니다

메시지 형태는 Twsit 입니다.

4. 장애물 탐지 범위

self.obstacle_distance_threshold = 0.35
self.min_intensity_threshold = 1000.0

 

 

장애물 탐지 거리 임계값은 0.35m (35cm)로 설정되었습니다.

반사 강도 값이 1000.0 미만인 데이터는 노이즈로 간주하여 필터링합니다.

 

 


def scan_callback(self, msg):

- 라이다 데이터 처리 후 명령 

def scan_callback(self, msg):
    filtered_ranges = self.filter_noise(msg.ranges, msg.intensities, msg.range_min, msg.range_max)
    closest_distance = min(filtered_ranges) if filtered_ranges else float('inf')

    if closest_distance < self.obstacle_distance_threshold:
        self.avoid_obstacle()
    else:
        self.move_forward()

msg의 의미

msg는 LaserScan 메시지로, 라이다 센서에서 발행한 데이터를 담고 있습니다. 이 데이터는 ROS 2의 sensor_msgs.msg.LaserScan 타입이며, 라이다 스캔의 결과를 포함

 

filter_noise의 역할

  1. 거리 유효성 검증:
    • msg.ranges 값 중에서 range_min <= 거리 값 <= range_max 범위 내에 있는 값만 유효하다고 간주.
  2. 반사 강도 기반 필터링:
    • msg.intensities 값이 설정된 최소 강도(min_intensity_threshold) 이상인 데이터만 통과.

 

obstacle_distance_threshold = 0.35 ( def__init__ 에서 선언 )

 

즉 , 로봇과의 앞에 장애물 거리  ( 라이다 센서로 감지한 거리 ) 가 35cm 이하 일 경우,
avoid_obstacle() 실행  ( 밑에 나옴 )

 

아니면 move_forward() 실행   ( 앞으로 계속 가는 명령 )

 

 


def filter_noise(self, ranges, intensities, range_min, range_max):

ranges : 라이다가 각 박향(각도)에서 측정한 거리

ex) [0.5, 1.2, inf, 0.8] ( 단위 : m )    inf : 무한

 

intensities : 라이다로 감지한 반사 강도 값  -> 반사 강도가 낮으면 데이터를 신뢰할 수 없거나, 장애물 없다고 판단

ex) [1200.0, 900.0, 0.0, 1500.0]

 

range_min : 라이다의 최소 유효거리

range_max : 라이다의 최대 유효거리

self.min_intensity_threshold (float) : 반사 강도의 최소값(일정 값 이상만 유효 데이터로 간주)

ex) 800.0.

 

 

if 조건이 만족되면 filtered.append(r)

거리(r) 값이 두 조건을 만족하면 유효 데이터로 간주하고 filtered 리스트에 추가.

반복이 끝나면 return filtered

유효 데이터로만 구성된 리스트를 반환.

 

 


 

def avoid_obstacle(self):

회피 기동 함수

 

        self.get_logger().info("Obstacle detected! Avoiding...")
        self.cmd_msg.linear.x = -0.1  # 뒤로 이동
        self.cmd_msg.angular.z = 0.5  # 회전
        self.publisher.publish(self.cmd_msg)

 

cmd_msg 메시지 발행

이 메시지는 def__init__ 에서

 

self.cmd_msg = Twist() 라고 정의되었고,

그 뒤에

self.cmd_msg.linear.x = 0.1

 

여기서 Twist() 는 메시지 타입, 얘는

self.publisher = self.create_publisher(Twist, '/cmd_vel', 1)

 

/cmd_vel 토픽으로 발행

 

 

 

정리하자면,

 

(신문) : (/cmd_vel) 을 발행할꺼야.

근데 무슨 언어로?? -> ( 영어,한국어,일본어... )  : ( Twist ) 로 발행할거야.

무슨 내용을 담고 있는데? -> ( 기삿거리 ) : ( cmd_msg )


def move_forward(self):

앞으로 이동 함수

 

avoid_obstacle 와 구조가 같으므로 생략.


 

 

 

반응형
반응형

안녕하세요 맨 땅에 헤딩 '맨땅헤' 입니다.

ros2 가 어려워서 요즘 공부중인데, 기본 구조를 알기 위해 간단한 실습을 해봤습니다.

 

PC에서 USB Cam 발행 , 구독하기

0. 필요한 라이브러리 설치하기

sudo apt install ros-humble-usb-cam

 

1. usb 카메라 연결하고 토픽 발행하기

ros2 run usb_cam usb_cam_node_exe

 

뭐가 많이 나오지만 카메라 화면은 확인할 수 없다.

이 코드는 토픽 발행만 할 뿐, 받을 수 없기 때문

 

 

2. 카메라 토픽 확인

ros2 topic list

 

로 토픽 리스트를 확인해보자.

/image_raw 가 보통 카메라 토픽 형식이다. 잘 발행되고 있음을 확인할 수 있다.

 

시각적으로 확인하는 방법

 

 

- 영상 직접 확인하기

ros2 run rqt_image_view rqt_image_view

 

 

 

토픽이 잘 발행되고 있음을 알 수 있다. 그럼 이를 Python 으로 받아보자

 

3. 토픽을 구독해보자

우선, 추후 계속 쓸 워크스페이스를 만들어줍시다.

홈에 ros2_ws 폴더를 만들어주고 그 안에 camera_subscriber.py 파일을 만들어줍시다.

 

( 실제 전체 패키지 파일 구조는 다릅니다. 우선 파일만 생성)

 

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
import cv2
from cv_bridge import CvBridge

class CameraSubscriber(Node):
    def __init__(self):
        super().__init__('camera_subscriber')
        self.subscription = self.create_subscription(
            Image,
            '/image_raw',  # 카메라 토픽 이름
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning
        self.bridge = CvBridge()  # ROS 메시지를 OpenCV 형식으로 변환

    def listener_callback(self, msg):
        self.get_logger().info('Receiving image data...')
        # ROS 이미지 메시지를 OpenCV 이미지로 변환
        cv_image = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
        # OpenCV 이미지 처리 (예: 화면 출력)
        cv2.imshow('Camera Image', cv_image)
        cv2.waitKey(1)

def main(args=None):
    rclpy.init(args=args)
    node = CameraSubscriber()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info('Shutting down...')
    finally:
        cv2.destroyAllWindows()
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

 

아까 토픽 발행을 한 채로, 이 파이썬 코드를 실행시키면 영상 받아오기 ( 구독) 성공!!

ros2 humble 환경에서 설치해야할 라이브러리들이 있습니다.

 

sudo apt install ros-humble-cv-bridge

sudo apt install ros-humble-rclpy

 

이걸 입력해주고,

source /opt/ros/humble/setup.bash

를 해주세요.

 


 

구조 설명

1. 임의로 토픽 발행 ( 원래는 로봇에서 발행이지만, 임시로 PC )

 

2. 토픽이 잘 발행되고 있는지 궁금해!! 직접 눈으로 확인해보자

 

 

 

3. 잘 되고있으니 구독해보자

 

 

 

rqt_graph 로 확인해보자

 

rqt_graph

 

우리가 실행시켰던 

ros2 run usb_cam usb_cam_node_exe

 

usb_cam 노드 이름이고, 얘가 발행하고 있는 데이터 타입 (=토픽 ) 의 형태가 /image_raw

데이터( 토픽) 을 구독하고 있는 노드 camera_subscriber

반응형
반응형

안녕하세요 맨 땅에 헤딩 ' 맨땅헤 ' 입니다.

 

이번에는 Ubuntu 22.04 환경에서 ROS2 Humble 설치를 해보려고합니다.

 

https://docs.ros.org/en/humble/Installation/Alternatives/Ubuntu-Development-Setup.html

 

Ubuntu (source) — ROS 2 Documentation: Humble documentation

You're reading the documentation for an older, but still supported, version of ROS 2. For information on the latest version, please have a look at Jazzy. Ubuntu (source) Table of Contents The current Debian-based target platforms for Humble Hawksbill are:

docs.ros.org

 

 

0. ROS2 설치 전, 필수 설치

다음 코드를 터미널에 입력하여 설치해줍니다.

sudo apt install software-properties-common
sudo add-apt-repository universe

 

 

왜 설치해야하는가?

ROS 2와 관련된 패키지들은 유니버스 저장소에 의존할 수 있습니다. 따라서 설치를 원활히 진행하려면 이 저장소를 활성화해야 합니다.

 

 

 

다음 명령어를 입력하여 ROS 패키지 저장소의 GPG 키를 다운로드하여 시스템에 등록합니다.

sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg

 

왜 필요한가?:

  • ROS 패키지를 설치하려면 우분투 패키지 관리자가 저장소의 신뢰성을 검증해야 합니다.
  • 이 GPG 키를 등록하지 않으면 ROS 패키지를 설치하려 할 때 "미확인된 소스"라는 오류가 발생할 수 있습니다.

 

 

다음 명령어를 입력하여 소스 리스트에 추가해줍니다.

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

 

 

ROS2 패키지 설치

우선 패키지 업데이트를 해줍시다

sudo apt update
sudo apt upgrade

 

 

 

ROS2 설치

sudo apt install ros-humble-desktop

 

 

ROS2 Base 설치

sudo apt install ros-humble-ros-base

 

 

개발 도구 설치

sudo apt install ros-dev-tools

 

환경설정

변경사항을 적용시키려면 터미널을 열 때 마다 ( setup.bash) 를 활성화 시켜야 한다.

source /opt/ros/humble/setup.bash

 

 

번거로우니 bashrc 에 추가하자

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

 

 


 

ROS2 설치를 완료했으니 예제를 해봅시다

 

대표적인 예제로 talker , listener

 

 

ros2 run demo_nodes_cpp talker

 

 

첫번째 창에서 계속 보내고 있는 상태로 냅두고

ctrl + shift + E 를 눌러서 창을 2개로 분리해줍시다.

 

ros2 run demo_nodes_py listener

 

talker 와 listener

 

 

이 상태에서 터미널을 또 새로 열고, 아래 코드를 입력해보면

ros2 run rqt_graph rqt_graph

rgt_graph

 

이렇게 현재 실행중인 것들을 시각적으로 볼 수 있다.

 

다음 포스팅에서는 노드와 토픽. 좀더 심화적인 부분에 대해 다뤄보겠다

반응형

+ Recent posts