📶

[VMware-ROS1-3] ROS1과 YOLOv5 연결하기

생성일
2023/01/30 08:21
태그

0. Outline

1. ROS1과 Camera 연동, Workspace 설정하기

외장 UVC-Camera를 연결하여 설정하였습니다.
DRGO WC720 카메라입니다.

1.1. roscore 실행

$ roscore
Shell
복사

1.2. uvc-camera 연동

새 터미널을 열어 다음 라이브러리를 설치합니다.
$ sudo apt install ros-melodic-uvc-camera
Shell
복사
아래 명령어를 통해 uvc-camera-node를 실행합니다.
_skip_frames:=5 옵션을 통해 6프레임 중 5프레임을 쉬고 1프레임을 전송 (default=0)
$ rosrun uvc_camera uvc_camera_node _skip_frames:=5
Shell
복사
아래 명령을 통해 카메라의 영상을 확인합니다.
$ rosrun image_view image_view image:=image_raw
Shell
복사
프레임을 강제로 낮췄기에 끊기는게 정상입니다.

1.3. ROS topic flow 확인

$ rqt_graph
Shell
복사
위 명령어를 통해 ros topic의 흐름에 대해 볼 수 있습니다.
ros의 topic에 대한 내용은 다음 포스팅을 참고해주세요.

1.4. 본격적인 ROS Workspace Setting

Workspace 폴더 생성 및 setup.bash를 실행합니다.
$ mkdir ros_workspace $ cd ros_workspace $ source /opt/ros/melodic/setup.bash
Shell
복사
catkin Workspace을 생성합니다.
catkin Workspace에 대한 자세한 내용은 밑 링크를 참고하세요.
$ mkdir -p src/ $ cd src/ $ catkin_init_workspace Creating symlink "/home/kkim/ros_workspace/CMakeLists.txt" pointing to "/opt/ros/melodic/share/catkin/cmake/toplevel.cmake"
Shell
복사
이후 catkin_make를 통해 작업공간을 빌드?합니다.
$ catkin_make
Shell
복사
만약 The specified base path "/home/kkim/ros_workspace" contains a CMakeLists.txt but "catkin_make" must be invoked in the root of workspace 오류가 발생한다면 아래 링크처럼 해결하세요.
unlink CMakeLists.txt
catkin_make 명령어가 실행됬다면 다음 명령어를 통해 작업공간을 셋업합니다.
$ source devel/setup.bash
Shell
복사

2. YOLOv5 - ros 패키지 기능 구현하기

먼저 yolov5_ros 패키지를 생성합니다.
$ catkin_create_pkg yolov5_ros rospy roscpp std_msgs sensor_msgs message_generation
Shell
복사
catkin_create_pkg 명령어의 첫 번째 인자는 생성되는 패키지 명을 의미합니다.
두번째에 들어가는 rospy, roscpp 등은 옵션으로, 패키지에서 필요한 dependency들을 리스팅한 것입니다.
yolov5_ros의 src 폴더로 이동하여 yolov5를 다운로드받습니다.
$ cd yolov5_ros/src $ git clone https://github.com/ultralytics/yolov5
Shell
복사
다음 명령어를 통해 설치하면 됩니다.
다만 PyYAML은 오류가 발생하니 따로 설치합니다.
$ cd yolov5 $ pip3 install --ignore-installed PyYAML $ pip3 install -r requirements.txt
Shell
복사
이제 Object Detection 결과를 전송하기 위한 message를 정의하기 위해 msg 폴더를 생성합니다.
$ cd ~/ros_workspace/row_ws/src/yolov5_ros $ mkdir msg
Shell
복사
메세지를 다운로드하여 폴더에 저장합니다.
$ cd msg $ wget https://raw.githubusercontent.com/raghavauppuluri13/yolov5_pytorch_ros/master/msg/BoundingBox.msg $ wget https://raw.githubusercontent.com/raghavauppuluri13/yolov5_pytorch_ros/master/msg/BoundingBoxes.msg
Shell
복사
이제 CMakeLists.txt에 다음 내용을 추가합니다.
$ cd ~/ros_workspace/src/yolov5_ros $ vim CMakeLists.txt ################################################ ## Declare ROS messages, services and actions ## ################################################ # 밑에 추가해주세요. 31번째 줄 즈음입니다. add_message_files( FILES BoundingBox.msg BoundingBoxes.msg ) generate_messages( DEPENDENCIES geometry_msgs sensor_msgs std_msgs ) $ cd ~/ros_workspace $ catkin_make
Shell
복사
~/ros_workspace/src/yolov5_ros/srcdetect.py 파일을 생성합니다.
#!/usr/bin/env python3 # https://github.com/mats-robotics/yolov5_ros/blob/main/src/detect.py를 일부 수정하였습니다. # ------------------------------------------------------------------------------ # - import # ------------------------------------------------------------------------------ # : importing system libraries import sys import os import time # : importing externel libraries import rospy import numpy import torch import torch.backends.cudnn as cudnn # : importing - from from pathlib import Path from sensor_msgs.msg import Image from yolov5_ros.msg import BoundingBox, BoundingBoxes # : importing - yolov5 # FILE : 현재 파일의 경로 # Path(__file__) : 현재 파일의 구상 경로를 인스턴스화 # resolve() : 절대경로 반환 # - result : /home/kkim/ros_workspace/src/yolov5_ros/src/detect.py FILE = Path(__file__).resolve() # ROOT : yolov5 폴더의 경로 # - yolov5 폴더의 path를 구한 뒤 sys.path에 append하여 import 가능하도록 설정 ROOT = FILE.parents[0] / "yolov5" if str(ROOT) not in sys.path: sys.path.append(str(ROOT)) # - 상대 경로로 변환 ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # main importing from models.common import DetectMultiBackend from utils.general import ( check_img_size, non_max_suppression, scale_boxes) from utils.torch_utils import select_device from utils.augmentations import letterbox # ------------------------------------------------------------------------------ # - defines # ------------------------------------------------------------------------------ IMG_W = 640 IMG_H = 480 # ------------------------------------------------------------------------------ # - class : Yolov5Detector # ------------------------------------------------------------------------------ def param(_name, _value): return rospy.get_param(_name, _value) class Yolov5Detector: def __init__(self): # print("Yolov5 __init__") # get rospy parameters self.cnf_thr = param("~confidence_threshold", 0.75) self.iou_thr = param("~iou_threshold", 0.45) self.agn_nms = param("~agnostic_nms", True) self.max_det = param("~maximum_detections", 1000) self.classes = param("~classes", None) self.dnn = param("~dnn", True) # initialize weights self.weight = param("~weights", "yolov5s.pt") # initialize model self.device = select_device(str(param("~device", "cpu"))) self.model = DetectMultiBackend( self.weight, device = self.device, dnn = self.dnn) model = self.model _, self.stride, self.names, self.pt, self.jit, self.onnx, self.engine = (None, model.stride, model.names, model.pt, model.jit, model.onnx, model.engine) # setting inference size self.img_siz = [ param("~inference_size_w", IMG_W), param("~inference_size_h", IMG_H) ] self.img_siz = check_img_size(self.img_siz, s=self.stride) # half self.half = param("~half", False) self.half &= ( self.pt or self.jit or self.onnx or self.engine) and self.device.type != "cpu" if self.pt or self.jit: self.model.model.half() if self.half else self.model.model.float() batch_size = 1 # set True to speed up constant image size inference cudnn.benchmark = True self.model.warmup(imgsz=(1 if self.pt else batch_size, 3, *self.img_siz)) # initialize subscriber to image topic rospy.Subscriber('/image_raw', Image, self.callback) self.pre_pub = rospy.Publisher( rospy.get_param("~output_topic", "/yolov5_ros"), BoundingBoxes, queue_size=10) def callback(self, _img_msg): # print("Yolov5 callback") typ = numpy.dtype("uint8") typ = typ.newbyteorder('>' if _img_msg.is_bigendian else '<') img_cv = numpy.ndarray( shape=( _img_msg.height, _img_msg.width, 3), dtype=typ, buffer=_img_msg.data) if _img_msg.is_bigendian == (sys.byteorder == 'little'): img_cv = img_cv.byteswap().newbyteorder() img, img_0 = self.preprocess(img_cv) img = torch.from_numpy(img) img = img.half() if self.half else img.float() img /= 255 if len(img.shape) == 3: img = img[None] pre = self.model(img, augment=False, visualize=False) pre = non_max_suppression(pre, self.cnf_thr, self.iou_thr, self.classes, max_det = self.max_det) det = pre[0].cpu().numpy() box = BoundingBoxes() box.header = _img_msg.header box.image_header= _img_msg.header if len(det): det[:, :4] = scale_boxes(img.shape[2:], det[:, :4], img_0.shape).round() for *xyxy, conf, cls in reversed(det): c = int(cls) box_msg = BoundingBox() box_msg.Class = self.names[c] box_msg.probability = conf box_msg.xmin = int(xyxy[0]) box_msg.ymin = int(xyxy[1]) box_msg.xmax = int(xyxy[2]) box_msg.ymax = int(xyxy[3]) box.bounding_boxes.append(box_msg) self.pre_pub.publish(box) def preprocess(self, _img): _cpy = _img.copy() _img = numpy.array( [letterbox( _img, self.img_siz, stride=self.stride, auto=self.pt)[0]]) _img = _img[..., ::-1].transpose((0, 3, 1, 2)) _img = numpy.ascontiguousarray(_img) return _img, _cpy if __name__ == "__main__": # print("MAIN start") rospy.init_node("yolov5", anonymous=True) detector = Yolov5Detector() rospy.spin()
Python
복사
파일을 작성한 뒤 실행 권한을 부여해야 합니다.
$ cd ~/ros_workspace/src/yolov5_ros/src $ chmod +x detect.py
Shell
복사
실행 권한 이후에 catkin_make에 등록합니다.
$ cd ~/ros_workspace/src/yolov5_ros $ vim CMakeLists.txt ############# ## Install ## ############# # ... # 밑에 추가해주세요. catkin_install_python(PROGRAMS src/detect.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
Shell
복사
이제 실행하겠습니다.
$ cd ~/ros_workspace $ catkin_make $ source devel/setup.bash $ rosrun yolov5_ros detect.py
Shell
복사
roscore 등의 명령어로 ROS를 실행하는 걸 잊지 마세요.

2.1. Topic 흐름 확인하기

$ rqt_graph
Shell
복사

3. robot_ctrl 패키지 기능 구현하기

/uvc_camera node와 /yolov5_ros node의 정보를 받아서 로봇을 제어하는 패키지를 만듭니다.
구체적인 액션을 주지 않고, 수신 확인만 동작하도록 작성하겠습니다.
먼저 robot_ctrl 패키지를 생성합니다. yolov5 패키지와 비슷해요!
$ cd ~/ros_workspace/src $ catkin_create_pkg robot_ctrl rospy roscpp std_msgs sensor_msgs yolov5_ros
Shell
복사
~/ros_workspace/src/robot_ctrl/srccontrol.py를 생성합니다.
#!/usr/bin/env python3 # ------------------------------------------------------------------------------ # - import # ------------------------------------------------------------------------------ # : importing system libraries import sys # : importing externel libraries import numpy import cv2 import rospy import message_filter # : importing - from from sensor_msgs.msg import Image from yolov5_ros.msg import BoundingBoxes # ------------------------------------------------------------------------------ # - class: robotctrl # ------------------------------------------------------------------------------ class RobotControl: def __init__(self): img_cb = message_filter.Subscriber('/image_raw', Image) yolov5 = message_filter.Subscriber('/yolov5_ros', BoundingBoxes) timsyn = message_filter.TimeSynchronizer([img_cb, yolov5], 10) timsyn.registerCallback(self.callback) def callback(self, _img_msg, _box_msg): typ = numpy.dtype("uint8") typ = typ.newbyteorder('>' if _img_msg.is_bigendian else '<') img = numpy.ndarray( shape =( _img_msg.height, _img_msg.width, 3), dtype =typ, buffer=_img_msg.data) if _img_msg.is_bigendian == (sys.byteorder == 'little'): img = img.byteswap().newbyteorder() img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for box in _box_msg.bounding_boxes: print(box) cv2.rectangle( img, (box.xmin, box.ymin), (box.xmax, box.ymax), (255, 0, 0), 2) cv2.imwrite('tmp.jpg', img) if __name__ == "__main__": rospy.init_node("robot_control", anonymous=True) control = RobotControl() rospy.spin()
Shell
복사
마찬가지로 실행 권한 부여하고 catkin_make에 등록해주세요.
$ cd ~/ros_workspace/src/robot_ctrl/src $ chmod +x control.py
Shell
복사
$ cd ~/ros_workspace/src/robot_ctrl $ vim CMakeLists.txt ############# ## Install ## ############# # ... # 밑에 추가해주세요. catkin_install_python(PROGRAMS src/control.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} )
Shell
복사
이제 실행하겠습니다.
$ cd ~/ros_workspace $ catkin_make $ source devel/setup.bash $ rosrun yolov5_ros control.py
Shell
복사
이렇게 실행하면, ~/ros_workspace 안에 tmp.jpg가 생성됩니다.
이미지 내에는 detection 된 물체의 box도 그려져 있습니다.

4. 원활한 실행을 위해 흐름 이해하기

rqt_graph 명령어를 통해 얻은 실행 플로우
ROS 실행
1.
ros를 실행한다.
$ roscore
Shell
복사
카메라 실행
1.
카메라를 연결한다
2.
vmware의 설정을 통해 /dev/에 videoN이 뜨는지 확인한다.
$ ls /dev/video*
Shell
복사
3.
다음 명령어를 통해 캠을 uvc-camera-node를 통해 image_raw topic으로 송출한다.
$ rosrun uvc_camera uvc_camera_node _skip_frames:=5 $ rosrun image_view image_view image:=image_raw
Shell
복사
build(?)
1.
다음 명령어를 실행하여 yolov5와 robot control을 실행한다.
$ cd ~/ros_workspace $ catkin_make $ source devel/setup.bash
Shell
복사
yolov5 & robot_ctrl 실행
1.
yolov5을 실행한다.
$ rosrun yolov5_ros detect.py
Shell
복사
2.
robot_ctrl을 실행한다.
$ rosrun robot_ctrl control.py
Shell
복사
roscore, image_view, yolov5_ros, robot_ctrl 4가지는 각각의 탭에서 실행되야합니다.