1. 개요
이번 글에서는 Ubuntu 22.04 Server가 설치된 TurtleBot3 Burger에서 ROS 2 Humble을 사용하고, 장착된 Pi Camera 2 영상을 기반으로 YOLO 객체 인식 딥러닝 노드를 연결하는 과정을 설명합니다.
카메라 ROS 2 토픽 → OpenCV 변환 → YOLO 객체 인식 → 인식 결과 ROS 2 토픽 발행 → rqt 또는 RViz2에서 확인
이 원리를 이해하면 TurtleBot3에 단순 카메라를 붙이는 수준을 넘어, 로봇이 주변 물체를 인식하고 그 결과를 이용해 주행 판단까지 할 수 있는 기반을 만들 수 있습니다.
2. 전체 시스템 구조
이번 강의에서 사용할 전체 구조는 다음과 같습니다.
Pi Camera 2
↓
turtlebot3_bringup camera.launch.py
↓
/camera/image_raw
↓
YOLO ROS 2 Node
↓
객체 인식 수행
↓
/yolo/image
/yolo/detections
↓
rqt_image_view, RViz2, 다른 제어 노드
각 역할은 다음과 같습니다.
- Pi Camera 2
TurtleBot3 Burger에 장착된 실제 카메라입니다. - camera.launch.py
TurtleBot3 bringup 패키지에서 카메라 드라이버 노드를 실행합니다. - /camera/image_raw
카메라 원본 영상이 발행되는 ROS 2 토픽입니다. - YOLO ROS 2 Node
/camera/image_raw를 구독하고 YOLO 모델을 이용해 객체를 인식합니다. - /yolo/image
인식 결과가 그려진 영상 토픽입니다. - /yolo/detections
객체 이름, 신뢰도, 바운딩 박스 좌표 등을 담은 인식 결과 토픽입니다. - 원격 PC
SSH로 TurtleBot3에 접속하거나, 같은 ROS_DOMAIN_ID 환경에서 rqt, RViz2로 결과를 확인합니다.
3. YOLO 실행 위치 결정
YOLO 객체 인식은 연산량이 큰 작업입니다.
TurtleBot3 Burger의 기본 보드는 Raspberry Pi 계열인 경우가 많고, GPU 연산 성능이 제한적입니다.
따라서 선택지는 크게 두 가지입니다.
1) TurtleBot3 내부에서 YOLO 실행
구조는 다음과 같습니다.
TurtleBot3
├─ Pi Camera 2
├─ /camera/image_raw 발행
└─ YOLO 노드 실행
장점은 다음과 같습니다.
- 구조가 단순합니다.
- 외부 PC 없이 로봇 단독 실행이 가능합니다.
- 네트워크 지연이 적습니다.
단점은 다음과 같습니다.
- 연산 성능이 낮으면 프레임이 매우 낮습니다.
- YOLO 모델 크기를 줄여야 합니다.
- 발열과 전원 문제가 생길 수 있습니다.
이 방식에서는 yolov8n, yolov5n, yolov8n.onnx 같은 작은 모델을 사용하는 것이 현실적입니다.
2) 원격 PC에서 YOLO 실행
구조는 다음과 같습니다.
TurtleBot3
├─ Pi Camera 2
└─ /camera/image_raw 발행
↓ ROS 2 네트워크
Remote PC
└─ YOLO 노드 실행
장점은 다음과 같습니다.
- GPU가 있는 PC에서 빠른 객체 인식이 가능합니다.
- 큰 YOLO 모델도 사용할 수 있습니다.
- 개발과 디버깅이 쉽습니다.
단점은 다음과 같습니다.
- 네트워크 품질에 영향을 받습니다.
- 카메라 영상 토픽 전송량이 큽니다.
- ROS_DOMAIN_ID, 네트워크, 방화벽 설정이 중요합니다.
강의용으로는 처음에는 원격 PC에서 YOLO 노드를 실행하는 방식을 추천합니다.
이유는 성능이 안정적이고, 학생들이 rqt와 터미널을 동시에 확인하기 쉽기 때문입니다.
4. YOLO ROS 2 패키지 생성
원격 PC에서 작업을 진행합니다. 작업 공간이 없다면 먼저 생성합니다.
패키지를 생성합니다.
cd ~/turtlebot3_ws/src
ros2 pkg create yolo_turtlebot3 --build-type ament_python --dependencies rclpy sensor_msgs std_msgs cv_bridge

생성된 구조는 다음과 같습니다.
yolo_turtlebot3/
├─ package.xml
├─ setup.py
├─ setup.cfg
├─ resource/
│ └─ yolo_turtlebot3
├─ test/
└─ yolo_turtlebot3/
├─ __init__.py

여기서 실제 YOLO 노드 파일을 추가합니다.
cd yolo_turtlebot3/yolo_turtlebot3
touch yolo_image_node.py
chmod +x yolo_image_node.py

5. 필요한 패키지 설치
YOLO 예제에서는 ultralytics 패키지를 사용합니다.
원격 PC 또는 TurtleBot3에서 다음을 설치합니다.
pip3 install ultralytics


OpenCV도 필요합니다.
sudo apt update
sudo apt install -y python3-opencv
cv_bridge는 ROS 2 패키지로 설치합니다.
sudo apt install -y ros-humble-cv-bridge
이미 TurtleBot3 관련 소스를 직접 빌드해서 사용 중이라면 cv_bridge가 설치되어 있는지 확인만 하면 됩니다.
ros2 pkg list | grep cv_bridge

정상이라면 다음처럼 출력됩니다.
cv_bridge
6. YOLO 이미지 노드 전체 소스 코드
다음은 /camera/image_raw를 구독하고, YOLO 객체 인식을 수행한 뒤, 결과가 그려진 이미지를 /yolo/image로 발행하는 기본 예제입니다.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from std_msgs.msg import String
from cv_bridge import CvBridge
import cv2
from ultralytics import YOLO
class YoloImageNode(Node):
def __init__(self):
super().__init__('yolo_image_node')
self.declare_parameter('image_topic', '/camera/image_raw')
self.declare_parameter('model_path', 'yolov8n.pt')
self.declare_parameter('confidence', 0.5)
self.image_topic = self.get_parameter('image_topic').value
self.model_path = self.get_parameter('model_path').value
self.confidence = float(self.get_parameter('confidence').value)
self.get_logger().info(f'Image topic: {self.image_topic}')
self.get_logger().info(f'YOLO model: {self.model_path}')
self.get_logger().info(f'Confidence threshold: {self.confidence}')
self.bridge = CvBridge()
self.model = YOLO(self.model_path)
self.image_sub = self.create_subscription(
Image,
self.image_topic,
self.image_callback,
10
)
self.image_pub = self.create_publisher(
Image,
'/yolo/image',
10
)
self.detection_pub = self.create_publisher(
String,
'/yolo/detections',
10
)
def image_callback(self, msg):
try:
frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
except Exception as e:
self.get_logger().error(f'cv_bridge conversion failed: {e}')
return
frame = cv2.flip(frame, -1)
results = self.model(frame, conf=self.confidence, verbose=False)
detection_text_list = []
annotated_frame = frame.copy()
for result in results:
boxes = result.boxes
if boxes is None:
continue
for box in boxes:
class_id = int(box.cls[0])
confidence = float(box.conf[0])
class_name = self.model.names[class_id]
x1, y1, x2, y2 = box.xyxy[0]
x1 = int(x1)
y1 = int(y1)
x2 = int(x2)
y2 = int(y2)
label = f'{class_name} {confidence:.2f}'
cv2.rectangle(
annotated_frame,
(x1, y1),
(x2, y2),
(0, 255, 0),
2
)
cv2.putText(
annotated_frame,
label,
(x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 255, 0),
2
)
detection_text = (
f'class={class_name}, '
f'confidence={confidence:.2f}, '
f'bbox=({x1},{y1},{x2},{y2})'
)
detection_text_list.append(detection_text)
detection_msg = String()
detection_msg.data = '; '.join(detection_text_list)
self.detection_pub.publish(detection_msg)
try:
yolo_image_msg = self.bridge.cv2_to_imgmsg(
annotated_frame,
encoding='bgr8'
)
yolo_image_msg.header = msg.header
self.image_pub.publish(yolo_image_msg)
except Exception as e:
self.get_logger().error(f'cv_bridge publish failed: {e}')
def main(args=None):
rclpy.init(args=args)
node = YoloImageNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()

7. 소스 코드 상세 설명
1) 기본 import 부분
import rclpy
from rclpy.node import Node
ROS 2 Python 노드를 만들기 위한 기본 모듈입니다.
rclpy는 ROS 2의 Python 클라이언트 라이브러리입니다.
C++에서 rclcpp를 사용하는 것처럼 Python에서는 rclpy를 사용합니다.
from sensor_msgs.msg import Image
from std_msgs.msg import String
ROS 2 메시지 타입을 가져옵니다.
Image는 카메라 영상 메시지 타입입니다.String은 간단한 객체 인식 결과를 문자열로 보내기 위해 사용합니다.
from cv_bridge import CvBridge
import cv2
cv_bridge는 ROS 이미지 메시지와 OpenCV 이미지를 서로 변환하는 역할을 합니다.
ROS 2 카메라 토픽은 sensor_msgs/msg/Image 타입입니다.
하지만 YOLO와 OpenCV는 일반적으로 numpy array 형태의 이미지를 사용합니다.
따라서 다음 변환 과정이 필요합니다.
ROS Image Message → OpenCV Image → YOLO 처리 → OpenCV Image → ROS Image Message
from ultralytics import YOLO
Ultralytics YOLO 모델을 사용하기 위한 import입니다.
이 예제에서는 yolov8n.pt 모델을 기본으로 사용합니다.
2) 클래스 선언
class YoloImageNode(Node):
ROS 2 노드를 클래스로 정의합니다.
Node를 상속받았기 때문에 이 클래스는 ROS 2 노드로 동작할 수 있습니다.
3) 생성자
def __init__(self):
super().__init__('yolo_image_node')
노드 이름을 yolo_image_node로 설정합니다.
4) 파라미터 선언
self.declare_parameter('image_topic', '/camera/image_raw')
self.declare_parameter('model_path', 'yolov8n.pt')
self.declare_parameter('confidence', 0.5)
ROS 2 파라미터를 선언합니다.
각 파라미터의 의미는 다음과 같습니다.
image_topic
YOLO 노드가 구독할 이미지 토픽 이름입니다.
기본값은/camera/image_raw입니다.model_path
사용할 YOLO 모델 파일 경로입니다.
기본값은yolov8n.pt입니다.confidence
객체 인식 신뢰도 기준값입니다.
기본값은0.5입니다.
즉, 신뢰도 50% 이상의 객체만 결과로 사용합니다.
이렇게 파라미터로 만들어두면 실행할 때 쉽게 변경할 수 있습니다.
ros2 run yolo_turtlebot3 yolo_image_node --ros-args -p confidence:=0.7
또는 모델을 바꿀 수도 있습니다.
ros2 run yolo_turtlebot3 yolo_image_node --ros-args -p model_path:=yolov8s.pt
5) 파라미터 읽기
self.image_topic = self.get_parameter('image_topic').value
self.model_path = self.get_parameter('model_path').value
self.confidence = float(self.get_parameter('confidence').value)
선언한 파라미터 값을 실제 변수로 가져옵니다.
이후 코드에서는 self.image_topic, self.model_path, self.confidence를 사용합니다.
6) 로그 출력
self.get_logger().info(f'Image topic: {self.image_topic}')
self.get_logger().info(f'YOLO model: {self.model_path}')
self.get_logger().info(f'Confidence threshold: {self.confidence}')
노드가 실행될 때 현재 설정값을 터미널에 출력합니다.
강의에서는 로그를 반드시 보여주는 것이 좋습니다.
학생들이 현재 어떤 토픽을 구독하고 어떤 모델을 쓰는지 바로 확인할 수 있기 때문입니다.
7) CvBridge 생성
self.bridge = CvBridge()
ROS 이미지와 OpenCV 이미지를 변환하기 위한 객체를 생성합니다.
이 객체를 이용해 다음 두 가지 변환을 수행합니다.
self.bridge.imgmsg_to_cv2()
self.bridge.cv2_to_imgmsg()
8) YOLO 모델 로딩
self.model = YOLO(self.model_path)
YOLO 모델을 로딩합니다.
기본값인 yolov8n.pt를 사용하면 처음 실행 시 모델 파일을 자동으로 다운로드할 수 있습니다.
인터넷 연결이 안 되는 환경이라면 미리 모델 파일을 받아두고 경로를 지정해야 합니다.
예를 들어 모델 파일을 다음 위치에 저장했다면,
/home/ubuntu/models/yolov8n.pt
실행 시 다음처럼 지정합니다.
ros2 run yolo_turtlebot3 yolo_image_node --ros-args -p model_path:=/home/ubuntu/models/yolov8n.pt
9) 이미지 구독자 생성
self.image_sub = self.create_subscription(
Image,
self.image_topic,
self.image_callback,
10
)
카메라 이미지 토픽을 구독합니다.
카메라 토픽이 들어올 때마다 image_callback() 함수가 호출됩니다.
10) 인식 결과 이미지 발행자 생성
self.image_pub = self.create_publisher(
Image,
'/yolo/image',
10
)
YOLO 인식 결과가 그려진 이미지를 발행합니다.
이 토픽은 다음 명령으로 확인할 수 있습니다.
ros2 topic echo /yolo/image
하지만 이미지 토픽은 echo로 보기 어렵습니다.
보통 다음 명령으로 확인합니다.
rqt_image_view
그리고 /yolo/image를 선택합니다.
11) 객체 인식 결과 문자열 발행자 생성
self.detection_pub = self.create_publisher(
String,
'/yolo/detections',
10
)
객체 인식 결과를 문자열로 발행합니다.
예를 들어 사람과 컵이 인식되면 다음과 비슷한 문자열이 발행됩니다.
class=person, confidence=0.82, bbox=(120,80,300,420); class=cup, confidence=0.76, bbox=(400,200,460,320)
이 토픽은 터미널에서 바로 확인할 수 있습니다.
ros2 topic echo /yolo/detections
12) 이미지 콜백 함수
def image_callback(self, msg):
카메라 이미지가 들어올 때마다 실행되는 함수입니다.
YOLO 처리의 핵심은 이 함수 안에 있습니다.
13) ROS 이미지 메시지를 OpenCV 이미지로 변환
try:
frame = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
except Exception as e:
self.get_logger().error(f'cv_bridge conversion failed: {e}')
return
ROS 이미지 메시지를 OpenCV 이미지로 변환합니다.
OpenCV는 일반적으로 BGR 색상 순서를 사용합니다.
그래서 desired_encoding='bgr8'을 사용합니다.
변환에 실패하면 에러 로그를 출력하고 함수를 종료합니다.
14) YOLO 객체 인식 실행
results = self.model(frame, conf=self.confidence, verbose=False)
현재 카메라 프레임을 YOLO 모델에 입력합니다.
conf=self.confidence는 신뢰도 기준값입니다.verbose=False는 YOLO 내부 로그를 줄이기 위한 설정입니다.
결과는 results에 저장됩니다.
15) 결과 저장 리스트 생성
detection_text_list = []
annotated_frame = frame.copy()
detection_text_list는 문자열 형태의 인식 결과를 저장합니다.
annotated_frame은 화면에 표시할 이미지입니다.
원본 이미지에 바운딩 박스와 클래스 이름을 그리기 위해 frame.copy()를 사용합니다.
원본을 직접 수정해도 되지만, 구조적으로는 복사본을 만들어 작업하는 것이 안전합니다.
16) YOLO 결과 반복 처리
for result in results:
boxes = result.boxes
YOLO 결과에서 바운딩 박스 정보를 꺼냅니다.
boxes에는 인식된 객체들의 위치, 클래스 번호, 신뢰도가 포함됩니다.
if boxes is None:
continue
인식된 객체가 없으면 다음 결과로 넘어갑니다.
17) 객체별 정보 추출
for box in boxes:
class_id = int(box.cls[0])
confidence = float(box.conf[0])
class_name = self.model.names[class_id]
각 객체의 클래스 번호, 신뢰도, 클래스 이름을 가져옵니다.
예를 들어 class_id가 0이면 COCO 모델 기준으로 person일 수 있습니다.
x1, y1, x2, y2 = box.xyxy[0]
x1 = int(x1)
y1 = int(y1)
x2 = int(x2)
y2 = int(y2)
바운딩 박스 좌표를 가져옵니다.
좌표의 의미는 다음과 같습니다.
x1, y1: 왼쪽 위 좌표
x2, y2: 오른쪽 아래 좌표
18) 화면에 표시할 라벨 생성
label = f'{class_name} {confidence:.2f}'
영상에 표시할 문자열을 생성합니다.
예를 들어 다음과 같은 형태입니다.
person 0.82
19) 바운딩 박스 그리기
cv2.rectangle(
annotated_frame,
(x1, y1),
(x2, y2),
(0, 255, 0),
2
)
OpenCV를 이용해 객체 주변에 사각형을 그립니다.
각 인자의 의미는 다음과 같습니다.
annotated_frame
그림을 그릴 이미지입니다.(x1, y1)
사각형의 왼쪽 위 좌표입니다.(x2, y2)
사각형의 오른쪽 아래 좌표입니다.(0, 255, 0)
색상입니다. OpenCV에서는 BGR 순서입니다.
이 값은 초록색입니다.2
선 두께입니다.
20) 클래스 이름과 신뢰도 표시
cv2.putText(
annotated_frame,
label,
(x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 255, 0),
2
)
바운딩 박스 위에 객체 이름과 신뢰도를 표시합니다.
예를 들어 사람이 인식되면 다음처럼 보입니다.
person 0.82
21) 문자열 인식 결과 생성
detection_text = (
f'class={class_name}, '
f'confidence={confidence:.2f}, '
f'bbox=({x1},{y1},{x2},{y2})'
)
터미널이나 다른 ROS 2 노드에서 사용할 수 있도록 문자열 형태의 결과를 만듭니다.
예시는 다음과 같습니다.
class=person, confidence=0.82, bbox=(120,80,300,420)
detection_text_list.append(detection_text)
생성한 문자열을 리스트에 추가합니다.
22) 객체 인식 결과 토픽 발행
detection_msg = String()
detection_msg.data = '; '.join(detection_text_list)
self.detection_pub.publish(detection_msg)
여러 객체 인식 결과를 하나의 문자열로 합친 뒤 /yolo/detections 토픽으로 발행합니다.
확인은 다음 명령으로 합니다.
ros2 topic echo /yolo/detections
인식 객체가 없다면 빈 문자열이 발행될 수 있습니다.
23) 결과 이미지 발행
try:
yolo_image_msg = self.bridge.cv2_to_imgmsg(
annotated_frame,
encoding='bgr8'
)
yolo_image_msg.header = msg.header
self.image_pub.publish(yolo_image_msg)
except Exception as e:
self.get_logger().error(f'cv_bridge publish failed: {e}')
OpenCV 이미지를 다시 ROS 이미지 메시지로 변환합니다.
yolo_image_msg.header = msg.header는 중요한 부분입니다.
원본 카메라 이미지의 시간 정보와 프레임 정보를 그대로 유지합니다.
이렇게 해야 나중에 RViz2, TF, 센서 융합, 동기화 작업에서 문제가 줄어듭니다.
24) main 함수
def main(args=None):
rclpy.init(args=args)
node = YoloImageNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
ROS 2 노드를 실행하는 기본 구조입니다.
rclpy.init()
ROS 2 Python 시스템 초기화node = YoloImageNode()
YOLO 노드 객체 생성rclpy.spin(node)
콜백 함수가 계속 실행되도록 대기node.destroy_node()
노드 정리rclpy.shutdown()
ROS 2 종료
8. setup.py 수정
ros2 run 명령으로 실행하려면 setup.py에 실행 파일을 등록해야 합니다.
~/turtlebot3_ws/src/yolo_turtlebot3/setup.py 파일을 수정합니다.
from setuptools import setup
package_name = 'yolo_turtlebot3'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='ubuntu',
maintainer_email='ubuntu@example.com',
description='YOLO object detection node for TurtleBot3 camera image',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'yolo_image_node = yolo_turtlebot3.yolo_image_node:main',
],
},
)
중요한 부분은 다음입니다.
entry_points={
'console_scripts': [
'yolo_image_node = yolo_turtlebot3.yolo_image_node:main',
],
},
이 설정이 있어야 다음 명령으로 노드를 실행할 수 있습니다.
ros2 run yolo_turtlebot3 yolo_image_node
9. package.xml 확인
package.xml에는 의존 패키지가 들어 있어야 합니다.
<?xml version="1.0"?>
<package format="3">
<name>yolo_turtlebot3</name>
<version>0.0.0</version>
<description>YOLO object detection node for TurtleBot3 camera image</description>
<maintainer email="ubuntu@example.com">ubuntu</maintainer>
<license>Apache-2.0</license>
<depend>rclpy</depend>
<depend>sensor_msgs</depend>
<depend>std_msgs</depend>
<depend>cv_bridge</depend>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
여기서 중요한 부분은 다음입니다.
<export>
<build_type>ament_python</build_type>
</export>
Python 패키지는 ament_python을 사용해야 합니다.
10. 패키지 빌드
작업 공간 루트로 이동합니다.
cd ~/turtlebot3_ws
빌드합니다.
colcon build --packages-select yolo_turtlebot3
환경 설정을 적용합니다.
source install/setup.bash
11. 실행 순서
강의에서는 반드시 실행 순서를 고정해서 보여주는 것이 좋습니다.
1) TurtleBot3 bringup 실행
카메라 launch를 실행합니다.
ros2 launch turtlebot3_bringup camera.launch.py format:=BGR888

환경에 따라 robot.launch.py 안에서 카메라가 같이 실행되지 않을 수 있습니다.
이 경우 별도 터미널에서 camera.launch.py를 실행합니다.
2) 카메라 토픽 확인
ros2 topic list | grep camera

다음 토픽이 보여야 합니다.
/camera/image_raw
/camera/camera_info
3) YOLO 노드 실행
ros2 run yolo_turtlebot3 yolo_image_node
비정상적으로 실행되며 에러가 발생할 경우 아래와 같은 명령어들을 실행하세요.
pip3 uninstall -y numpy
pip3 uninstall -y numpy
pip3 install --user "numpy==1.26.4"
pip3 install --user --upgrade "matplotlib<3.9"
sudo apt update
sudo apt install --reinstall -y ros-humble-cv-bridge
sudo apt install -y python3-opencv
python3 -c "from cv_bridge import CvBridge; print('cv_bridge ok')"
pip3 install --user --upgrade ultralytics
python3 -c "import numpy; print(numpy.__version__)"
python3 -c "import torch; print(torch.__version__)"
python3 -c "from ultralytics import YOLO; print('YOLO import ok')"
cd ~/turtlebot3_ws
rm -rf build/yolo_turtlebot3 install/yolo_turtlebot3 log
colcon build --packages-select yolo_turtlebot3
source install/setup.bash
ros2 run yolo_turtlebot3 yolo_image_node
아직도 에러가 수정되지 않았을 경우 아래의 제거 작업을 계속해야 합니다.
python3 -m pip uninstall -y numpy
python3 -m pip uninstall -y numpy
rm -rf ~/.local/lib/python3.10/site-packages/numpy
rm -rf ~/.local/lib/python3.10/site-packages/numpy-*.dist-info
rm -rf ~/.local/lib/python3.10/site-packages/numpy.libs
python3 -c "import numpy; print(numpy.__version__); print(numpy.__file__)"
python3 -m pip install --user "numpy==1.26.4"
python3 -c "import numpy; print(numpy.__version__); print(numpy.__file__)"
python3 -m pip install --user --upgrade "ultralytics" "numpy==1.26.4"
python3 -c "import numpy; print(numpy.__version__)"
python3 -c "from cv_bridge import CvBridge; print('cv_bridge ok')"
python3 -c "from ultralytics import YOLO; print('YOLO ok')"
python3 -m pip uninstall -y matplotlib
sudo apt install --reinstall -y python3-matplotlib
python3 -c "import matplotlib; print(matplotlib.__version__); print(matplotlib.__file__)"
cd ~/turtlebot3_ws
rm -rf build/yolo_turtlebot3
rm -rf install/yolo_turtlebot3
rm -rf log
colcon build --packages-select yolo_turtlebot3
source install/setup.bash
ros2 run yolo_turtlebot3 yolo_image_node
정상 실행되면 다음과 비슷한 로그가 출력됩니다.
[INFO] [yolo_image_node]: Image topic: /camera/image_raw
[INFO] [yolo_image_node]: YOLO model: yolov8n.pt
[INFO] [yolo_image_node]: Confidence threshold: 0.5

4) YOLO 결과 이미지 확인
rqt_image_view



토픽에서 다음을 선택합니다.
/yolo/image
정상이라면 객체 주변에 박스가 그려진 영상이 표시됩니다.
5) YOLO 인식 결과 문자열 확인
ros2 topic echo /yolo/detections

예상 출력은 다음과 같습니다.
data: "class=person, confidence=0.83, bbox=(102,45,320,410)"
12. Launch 파일 만들기
매번 명령을 따로 입력하기 번거롭기 때문에 YOLO 노드용 launch 파일을 만들 수 있습니다.
패키지 안에 launch 폴더를 생성합니다.
cd ~/turtlebot3_ws/src/yolo_turtlebot3
mkdir launch
touch launch/yolo_image.launch.py

다음 내용을 작성합니다.
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='yolo_turtlebot3',
executable='yolo_image_node',
name='yolo_image_node',
output='screen',
parameters=[
{
'image_topic': '/camera/image_raw',
'model_path': 'yolov8n.pt',
'confidence': 0.5,
}
]
)
])

13. launch 파일 setup.py에 등록
setup.py를 다음처럼 수정합니다.
import os
from glob import glob
from setuptools import setup
package_name = 'yolo_turtlebot3'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'),
glob('launch/*.launch.py')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='ubuntu',
maintainer_email='ubuntu@example.com',
description='YOLO object detection node for TurtleBot3 camera image',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'yolo_image_node = yolo_turtlebot3.yolo_image_node:main',
],
},
)

추가된 부분은 다음입니다.
import os
from glob import glob
그리고 다음 부분입니다.
(os.path.join('share', package_name, 'launch'),
glob('launch/*.launch.py')),
이 설정이 있어야 launch 파일이 설치 공간으로 복사됩니다.
다시 빌드합니다.
cd ~/ros2_ws
colcon build --packages-select yolo_turtlebot3
source install/setup.bash
이제 다음 명령으로 실행할 수 있습니다.
ros2 launch yolo_turtlebot3 yolo_image.launch.py
14. YOLO 결과를 로봇 제어와 연결하는 기본 개념
객체 인식은 단독으로는 큰 의미가 없습니다.
로봇에서는 인식 결과를 제어 판단에 연결해야 합니다.
예를 들어 다음과 같은 동작이 가능합니다.
- 사람을 인식하면 정지
- 특정 물체를 인식하면 접근
- 컵을 인식하면 회전하며 중앙 정렬
- 장애물을 인식하면 회피
- 특정 표지판을 인식하면 명령 수행
가장 기본적인 연결 구조는 다음과 같습니다.
/yolo/detections
↓
object_behavior_node
↓
/cmd_vel
↓
TurtleBot3 이동
즉, YOLO 노드는 인식만 담당하고, 주행 판단은 별도 노드가 담당하는 구조가 좋습니다.
이것이 ROS다운 구조입니다.
하나의 노드에 카메라, YOLO, 주행 제어를 전부 넣으면 처음에는 편해 보이지만, 유지보수가 나빠집니다.
15. 사람 인식 시 정지하는 예제 노드
다음은 /yolo/detections 문자열 안에 person이 포함되면 TurtleBot3를 정지시키는 간단한 예제입니다.
파일을 생성합니다.
cd ~/turtlebot3_ws/src/yolo_turtlebot3/yolo_turtlebot3
touch person_stop_node.py

소스 코드는 다음과 같습니다.
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from geometry_msgs.msg import Twist
class PersonStopNode(Node):
def __init__(self):
super().__init__('person_stop_node')
self.detection_sub = self.create_subscription(
String,
'/yolo/detections',
self.detection_callback,
10
)
self.cmd_vel_pub = self.create_publisher(
Twist,
'/cmd_vel',
10
)
self.get_logger().info('Person stop node started')
def detection_callback(self, msg):
if 'class=person' in msg.data:
stop_msg = Twist()
stop_msg.linear.x = 0.0
stop_msg.angular.z = 0.0
self.cmd_vel_pub.publish(stop_msg)
self.get_logger().warn('Person detected. TurtleBot3 stopped.')
def main(args=None):
rclpy.init(args=args)
node = PersonStopNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()

15. person_stop_node.py 소스 설명
1) 메시지 import
from std_msgs.msg import String
from geometry_msgs.msg import Twist
String은 /yolo/detections 토픽을 받기 위해 사용합니다.Twist는 TurtleBot3의 속도 명령인 /cmd_vel을 발행하기 위해 사용합니다.
TurtleBot3는 일반적으로 /cmd_vel 토픽으로 이동 명령을 받습니다.
2) 객체 인식 결과 구독
self.detection_sub = self.create_subscription(
String,
'/yolo/detections',
self.detection_callback,
10
)
YOLO 노드가 발행하는 /yolo/detections를 구독합니다.
인식 결과가 들어올 때마다 detection_callback()이 실행됩니다.
3) 속도 명령 발행자 생성
self.cmd_vel_pub = self.create_publisher(
Twist,
'/cmd_vel',
10
)
TurtleBot3에 속도 명령을 보내기 위한 publisher입니다.
/cmd_vel 토픽에 Twist 메시지를 보내면 로봇이 움직이거나 멈춥니다.
4) 사람 인식 여부 확인
if 'class=person' in msg.data:
문자열 안에 class=person이 있는지 확인합니다.
이 방식은 강의용으로는 쉽고 직관적입니다.
하지만 실전에서는 문자열 파싱보다 커스텀 메시지 또는 vision_msgs를 사용하는 것이 좋습니다.
5) 정지 명령 생성
stop_msg = Twist()
stop_msg.linear.x = 0.0
stop_msg.angular.z = 0.0
선속도와 각속도를 모두 0으로 설정합니다.
즉, 로봇을 정지시키는 명령입니다.
6) 정지 명령 발행
self.cmd_vel_pub.publish(stop_msg)
정지 명령을 /cmd_vel로 발행합니다.
TurtleBot3가 정상적으로 bringup 되어 있다면 로봇이 멈춥니다.
16. person_stop_node 등록
setup.py의 console_scripts에 다음을 추가합니다.
entry_points={
'console_scripts': [
'yolo_image_node = yolo_turtlebot3.yolo_image_node:main',
'person_stop_node = yolo_turtlebot3.person_stop_node:main',
],
},

package.xml에는 geometry_msgs 의존성을 추가합니다.
<depend>geometry_msgs</depend>
다시 빌드합니다.
cd ~/ros2_ws
colcon build --packages-select yolo_turtlebot3
source install/setup.bash

실행합니다.
ros2 run yolo_turtlebot3 person_stop_node
17. 실행 테스트 순서
강의 실습 순서는 다음처럼 구성하면 좋습니다.
- TurtleBot3 bringup 실행
ros2 launch turtlebot3_bringup robot.launch.py

- 카메라 launch 실행
ros2 launch turtlebot3_bringup camera.launch.py format:=BGR888

- 카메라 토픽 확인
ros2 topic list | grep camera

- YOLO 노드 실행
ros2 launch yolo_turtlebot3 yolo_image.launch.py

- YOLO 결과 영상 확인
rqt_image_view
- 객체 인식 결과 확인
ros2 topic echo /yolo/detections

- 전진 속도 지령
ros2 topic pub -r 10 /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.1, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}" --once

8. 사람 인식 정지 노드 실행
ros2 run yolo_turtlebot3 person_stop_node

9. /cmd_vel 명령 확인
ros2 topic echo /cmd_vel

18. 성능 최적화 포인트
YOLO를 로봇에 붙이면 가장 먼저 부딪히는 문제가 성능입니다.
특히 TurtleBot3 Burger처럼 작은 로봇에서는 다음을 고려해야 합니다.
1) 작은 모델 사용
처음에는 반드시 작은 모델을 사용합니다.
추천 순서는 다음과 같습니다.
yolov8n.ptyolov5n.pt- ONNX로 변환한 경량 모델
- TensorRT 또는 OpenVINO 가속 모델
강의 초반에는 yolov8n.pt로 충분합니다.
2) 이미지 해상도 줄이기
카메라 해상도가 높으면 YOLO 처리 속도가 느려집니다.
예를 들어 1280×720보다 640×480 또는 320×240이 훨씬 가볍습니다.
YOLO 노드 내부에서 resize를 적용할 수 있습니다.
frame_resized = cv2.resize(frame, (640, 480))
results = self.model(frame_resized, conf=self.confidence, verbose=False)
다만 resize 후 바운딩 박스 좌표를 원본 이미지에 다시 매칭하려면 좌표 보정이 필요합니다.
강의 초반에는 원본 프레임 그대로 처리하는 것이 설명하기 쉽습니다.
3) 처리 주기 제한
모든 프레임에 대해 YOLO를 돌릴 필요는 없습니다.
예를 들어 30 FPS 카메라라도 YOLO는 5 FPS만 처리해도 충분한 경우가 많습니다.
간단히 프레임 카운터를 사용할 수 있습니다.
self.frame_count = 0
self.process_every_n_frames = 3
콜백에서 다음처럼 처리합니다.
self.frame_count += 1
if self.frame_count % self.process_every_n_frames != 0:
return
이렇게 하면 3프레임 중 1프레임만 YOLO 처리합니다.
4) 원격 PC에서 실행
로봇 내부 보드 성능이 부족하다면 원격 PC에서 YOLO 노드를 실행하는 것이 현실적입니다.
이 경우 TurtleBot3는 카메라 토픽만 발행하고, 원격 PC가 /camera/image_raw를 구독해서 YOLO를 실행합니다.
다만 네트워크 트래픽이 커지므로 Wi-Fi 품질이 중요합니다.
19. vision_msgs로 확장하는 방향
ROS 2에서 비전 인식 결과를 구조적으로 표현하려면 vision_msgs 패키지를 사용할 수 있습니다.
Detection2DArray
├─ header
└─ detections[]
├─ bbox
│ ├─ center
│ └─ size_x, size_y
└─ results[]
├─ hypothesis
│ ├─ class_id
│ └─ score
이 구조를 사용하면 객체 인식 결과를 다른 ROS 2 노드에서 훨씬 안정적으로 사용할 수 있습니다.
20. 실전 프로젝트 확장 예시
YOLO와 TurtleBot3를 연결하면 다음과 같은 실습으로 확장할 수 있습니다.
1) 사람 감지 정지 로봇
사람이 감지되면 TurtleBot3가 정지합니다.
person detected → /cmd_vel = 0
2) 특정 물체 따라가기
예를 들어 bottle을 인식하면 바운딩 박스 중심을 계산해 로봇이 해당 물체를 따라가게 할 수 있습니다.
object center < image center → left turn
object center > image center → right turn
object size small → move forward
object size large → stop
3) 표지판 인식 주행
YOLO를 커스텀 학습해서 표지판을 인식할 수 있습니다.
예를 들어 다음 클래스를 학습합니다.
- stop
- left
- right
- goal
- charger
이후 로봇은 표지판을 보고 행동을 결정합니다.
4) 배송 로봇 응용
TurtleBot3를 작은 배송 로봇처럼 사용할 수도 있습니다.
예를 들어 다음 객체를 인식합니다.
- 사람
- 문
- 책상
- 충전 스테이션
- 배송 위치 마커
객체 인식 결과를 Nav2와 연결하면 자율주행 실습으로 확장할 수 있습니다.