ROS2 humble에서 yolov8을 사용하여 Gazebo 환경에서 실시간 영상 분석을 수행하고 rviz를 통해 현재 카메라 화면을 확인할 수 있는 예제입니다.
Reference
Linux Desktop에서 아래 과정들을 수행하였습니다.
위 zip 파일을 다운로드하여 압축을 풀고 home 디렉토리에 옮겨줍니다.
터미널을 열고 이미 존재하는 것들을 제외하고, 아래 라이브러리들을 설치해줍니다.
sudo apt install python3-pip
pip3 install ultralytics
sudo apt install gazebo9
sudo apt install python3-colcon-common-extensions
sudo apt install ros-humble-gazebo-ros
home/yolobot/models 에서 아래 폴더들을 다 복사해줍니다.
home 디렉토리로 이동해서 Show Hidden Files 옵션을 체크해 .gazebo 파일에 들어가줍니다.
.gazebo 디렉토리에서 models 디렉토리를 만들고 해당 디렉토리 안에 붙여넣기 합니다.
이미 models가 존재했다면 해당 디렉토리에 붙여 넣으시면 됩니다.
이제 실행해봅시다.
cd ~/yolobot
colcon build
source ~/yolobot/install/setup.bash
ros2 launch yolobot_gazebo yolobot_launch.py
실행하면 다음과 같은 gazebo 창이 나타납니다.
터미널에서 이미지 분석이 이루어지는 모습을 확인할 수 있습니다.
새로운 터미널을 열고 아래 명령어를 입력합니다.
source ~/yolobot/install/setup.bash
ros2 topic echo /Yolov8_Inference
위와 같이 탐지된 물체가 나타나는 것을 확인할 수 있습니다.
새로운 터미널을 열고 아래 명령어를 입력합니다.
rviz2
그리고 Add 버튼을 누르고 By topic 탭에서 /inference_result 의 Image를 선택합니다.
이것이 미니카가 보고 있는 화면입니다.
저는 미니카가 돌아가서 사람만 보고 있네요.
yolobot control 쪽 코드를 확인해보니 Joy stick을 통해 미니카를 이동시킬 수 있습니다.
저는 이게 없어서 미니카를 조작해보진 못했습니다.
내용 분석
이게 어떻게 가능한지 확인해봅시다.
yolov8_msgs
Yolov8Inference.msg
std_msgs/Header header
InferenceResult[] yolov8_inference
header msg 를 포함합니다.그리고 InfereceResult 리스트에 이 데이터가 저장됩니다.
header msg는 타임스탬프 및 frame과 data 표현을 위해서 존재합니다.
InferenceResult.msg
string class_name
int64 top
int64 left
int64 bottom
int64 right
어떤 데이터를 갖는지 정의합니다.
CmakeLists.txt
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/InferenceResult.msg"
"msg/Yolov8Inference.msg"
DEPENDENCIES std_msgs
)
ament_export_dependencies(rosidl_default_runtime)
yolov8 처리를 위해 package를 추가, Dependency를 설정합니다.
package.xml
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>std_msgs</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
buildtool_depend는 프로세스 빌드에 필요한 ros package name을 선언합니다.
exec_depend는 프로세스 실행에 필요한 ros package name을 선언합니다.
depend 는 필요한 ros package를 선언합니다.
yolobot_recognition
yolov8_ros2_pt.py
#!/usr/bin/env python3
# yolov8 의 실행 결과를 publish
from ultralytics import YOLO
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from yolov8_msgs.msg import InferenceResult
from yolov8_msgs.msg import Yolov8Inference
bridge = CvBridge()
class Camera_subscriber(Node):
def __init__(self):
super().__init__('camera_subscriber')
# yolov8n 모델을 불러오기
self.model = YOLO('~/yolobot/src/yolobot_recognition/scripts/yolov8n.pt')
# Yolov8Inference.msg 를 통해 헤더 msg를 가져온다
self.yolov8_inference = Yolov8Inference()
# 카메라 관련 정의
self.subscription = self.create_subscription(
Image,
'rgb_cam/image_raw',
self.camera_callback,
10)
self.subscription
self.yolov8_pub = self.create_publisher(Yolov8Inference, "/Yolov8_Inference", 1)
self.img_pub = self.create_publisher(Image, "/inference_result", 1)
def camera_callback(self, data):
# ros2 msg가 cv2로 변환
img = bridge.imgmsg_to_cv2(data, "bgr8")
results = self.model(img)
# time stamp
self.yolov8_inference.header.frame_id = "inference"
self.yolov8_inference.header.stamp = camera_subscriber.get_clock().now().to_msg()
# 각 물체에 대한 분석 결과를 가져온 뒤 저장
for r in results:
boxes = r.boxes
for box in boxes:
self.inference_result = InferenceResult()
b = box.xyxy[0].to('cpu').detach().numpy().copy() # get box coordinates in (top, left, bottom, right) format
c = box.cls
self.inference_result.class_name = self.model.names[int(c)]
self.inference_result.top = int(b[0])
self.inference_result.left = int(b[1])
self.inference_result.bottom = int(b[2])
self.inference_result.right = int(b[3])
self.yolov8_inference.yolov8_inference.append(self.inference_result)
#camera_subscriber.get_logger().info(f"{self.yolov8_inference}")
# cv2에서 ros msg로 변환
annotated_frame = results[0].plot()
img_msg = bridge.cv2_to_imgmsg(annotated_frame)
# publish
self.img_pub.publish(img_msg)
self.yolov8_pub.publish(self.yolov8_inference)
self.yolov8_inference.yolov8_inference.clear()
if __name__ == '__main__':
rclpy.init(args=None)
camera_subscriber = Camera_subscriber()
rclpy.spin(camera_subscriber)
rclpy.shutdown()
yolov8_ros2_subscriber.py
...
class Camera_subscriber(Node):
...
class Yolo_subscriber(Node):
...
rclpy.init(args=None)
# 2개의 노드 생성 yolo & camera
yolo_subscriber = Yolo_subscriber()
camera_subscriber = Camera_subscriber()
executor = rclpy.executors.MultiThreadedExecutor()
executor.add_node(yolo_subscriber)
executor.add_node(camera_subscriber)
executor_thread = threading.Thread(target=executor.spin, daemon=True)
executor_thread.start()
...
boundary box 의 좌표를 정확히 추출해내기 위해 사용합니다.
threading lib을 사용한다는 차이점이 있습니다.
Camera_subscriber(Node)
camera node를 생성하고 opencv form으로 변환합니다.
결과를 글로벌 변수화 합니다.
class Yolo_subscriber(Node)
이전에 처리된 것을 publish 합니다.
물체에 bounding box를 그립니다.
'Setup & Example > ROS2 + 예제' 카테고리의 다른 글
ROS2 humble에서 OpenManipulator-X Pick and Place + yolov8 으로 시작 명령 주기 (0) | 2024.01.05 |
---|---|
ROS2 humble에서 OpenManipulator-X 환경 구성 및 조작 (3) | 2024.01.05 |
ROS2 humble 에서 MoveIt2 Pick and Place Example 연습 (3) | 2023.12.29 |
ROS2 humble에서 MoveIt2 설치와 기본 튜토리얼 (1) | 2023.12.27 |
ubuntu 22.04에 ROS 2 humble 설치 + colcon + Gazebo (2) | 2023.12.26 |