知之者 不如好之者, 好之者 不如樂之者

기계처럼 살지 말고, 즐기는 인간이 되자

Project/ROS2 + CV + Robot (2023.12~2024.02)

ROS2 humble - Gazebo에서 yolov8로 실시간 영상 분석 하기

코방코 2023. 12. 29. 14:05
728x90

ROS2 humble에서 yolov8을 사용하여 Gazebo 환경에서 실시간 영상 분석을 수행하고 rviz를 통해 현재 카메라 화면을 확인할 수 있는 예제입니다. 

 


Reference

How to Use YOLOv8 with ROS2

 


Linux Desktop에서 아래 과정들을 수행하였습니다.

 

Yolov8_ROS2 - Google Drive

 

Yolov8_ROS2 - Google Drive

이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

drive.google.com

위 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를 그립니다.

 

728x90
반응형