ROS(๋ก๋ด ์ด์ ์ฒด์ ) ๋น ๋ฅธ ์์ ๊ฐ์ด๋
์คํ ๋ก๋ณดํฑ์ค์ ROS ์๊ฐ(์บก์ ) Vimeo์ ๋์์์ ๋๋ค.
ROS๋ ๋ฌด์์ธ๊ฐ์?
ROS(๋ก๋ด ์ด์ ์ฒด์ ) ๋ ๋ก๋ด ์ฐ๊ตฌ ๋ฐ ์ฐ์ ์์ ๋๋ฆฌ ์ฌ์ฉ๋๋ ์คํ ์์ค ํ๋ ์์ํฌ์ ๋๋ค. ROS๋ ๊ฐ๋ฐ์๊ฐ ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋๋ ๋ฐ ๋์์ด ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ๋๊ตฌ ๋ชจ์์ ์ ๊ณตํฉ๋๋ค. ROS๋ ๋ค์ํ ๋ก๋ด ํ๋ซํผ๊ณผ ํจ๊ป ์๋ํ๋๋ก ์ค๊ณ๋์ด ๋ก๋ด ์ ๋ฌธ๊ฐ๋ฅผ ์ํ ์ ์ฐํ๊ณ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค.
ROS์ ์ฃผ์ ๊ธฐ๋ฅ
-
๋ชจ๋์ ์ํคํ ์ฒ: ROS๋ ๋ชจ๋์ ์ํคํ ์ฒ๋ฅผ ๊ฐ์ถ๊ณ ์์ด ๊ฐ๋ฐ์๊ฐ ๋ ธ๋๋ผ๊ณ ํ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ๊ตฌ์ฑ ์์๋ฅผ ๊ฒฐํฉํ์ฌ ๋ณต์กํ ์์คํ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ๊ฐ ๋ ธ๋๋ ์ผ๋ฐ์ ์ผ๋ก ํน์ ๊ธฐ๋ฅ์ ์ํํ๋ฉฐ, ๋ ธ๋๋ ์ฃผ์ ๋๋ ์๋น์ค๋ฅผ ํตํด ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก ํต์ ํฉ๋๋ค.
-
ํต์ ๋ฏธ๋ค์จ์ด: ROS๋ ํ๋ก์ธ์ค ๊ฐ ํต์ ๊ณผ ๋ถ์ฐ ์ปดํจํ ์ ์ง์ํ๋ ๊ฐ๋ ฅํ ํต์ ์ธํ๋ผ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด๋ ๋ฐ์ดํฐ ์คํธ๋ฆผ(ํ ํฝ)์ ๋ํ ๊ฒ์-๊ตฌ๋ ๋ชจ๋ธ๊ณผ ์๋น์ค ํธ์ถ์ ๋ํ ์์ฒญ-์๋ต ๋ชจ๋ธ์ ํตํด ์ด๋ฃจ์ด์ง๋๋ค.
-
ํ๋์จ์ด ์ถ์ํ: ROS๋ ํ๋์จ์ด์ ๋ํ ์ถ์ํ ๊ณ์ธต์ ์ ๊ณตํ์ฌ ๊ฐ๋ฐ์๊ฐ ๋๋ฐ์ด์ค์ ๊ตฌ์ ๋ฐ์ง ์๋ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋๋ก ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ค์ํ ํ๋์จ์ด ์ค์ ์์ ๋์ผํ ์ฝ๋๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฏ๋ก ํตํฉ๊ณผ ์คํ์ด ๋ ์ฌ์์ง๋๋ค.
-
๋๊ตฌ ๋ฐ ์ ํธ๋ฆฌํฐ: ROS์๋ ์๊ฐํ, ๋๋ฒ๊น ๋ฐ ์๋ฎฌ๋ ์ด์ ์ ์ํ ๋ค์ํ ๋๊ตฌ์ ์ ํธ๋ฆฌํฐ๊ฐ ํจ๊ป ์ ๊ณต๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด RViz๋ ์ผ์ ๋ฐ์ดํฐ์ ๋ก๋ด ์ํ ์ ๋ณด๋ฅผ ์๊ฐํํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ, Gazebo๋ ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ก๋ด ์ค๊ณ๋ฅผ ํ ์คํธํ ์ ์๋ ๊ฐ๋ ฅํ ์๋ฎฌ๋ ์ด์ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
-
๊ด๋ฒ์ํ ์์ฝ์์คํ : ROS ์ํ๊ณ๋ ๋ฐฉ๋ํ๊ณ ์ง์์ ์ผ๋ก ์ฑ์ฅํ๊ณ ์์ผ๋ฉฐ, ๋ด๋น๊ฒ์ด์ , ์กฐ์, ์ธ์ ๋ฑ ๋ค์ํ ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์ฉํ ์ ์๋ ์๋ง์ ํจํค์ง๊ฐ ์์ต๋๋ค. ์ปค๋ฎค๋ํฐ๋ ์ด๋ฌํ ํจํค์ง์ ๊ฐ๋ฐ๊ณผ ์ ์ง ๊ด๋ฆฌ์ ์ ๊ทน์ ์ผ๋ก ๊ธฐ์ฌํ๊ณ ์์ต๋๋ค.
ROS ๋ฒ์ ์ ์งํ
2007๋ ๊ฐ๋ฐ ์ดํ ROS๋ ์ฌ๋ฌ ๋ฒ์ ์ ํตํด ๋ฐ์ ํด ์์ผ๋ฉฐ, ๊ฐ ๋ฒ์ ์ ๋ก๋ด ์ปค๋ฎค๋ํฐ์ ์ฆ๊ฐํ๋ ์๊ตฌ ์ฌํญ์ ์ถฉ์กฑํ๊ธฐ ์ํด ์๋ก์ด ๊ธฐ๋ฅ๊ณผ ๊ฐ์ ์ฌํญ์ ๋์ ํ์ต๋๋ค. ROS์ ๊ฐ๋ฐ์ ํฌ๊ฒ ๋ ๊ฐ์ง ์๋ฆฌ์ฆ๋ก ๋ถ๋ฅํ ์ ์์ต๋๋ค: ROS 1๊ณผ ROS 2. ์ด ๊ฐ์ด๋๋ ROS ๋ ธ์ํฑ ๋์ ๋ฏธ๋ก ์๋ ค์ง ROS 1์ ์ฅ๊ธฐ ์ง์(LTS) ๋ฒ์ ์ ์ด์ ์ ๋ง์ถ๊ณ ์์ผ๋ฉฐ, ์ด์ ๋ฒ์ ์์๋ ์ฝ๋๊ฐ ์๋ํฉ๋๋ค.
ROS 1๊ณผ ROS 2
ROS 1์ด ๋ก๋ด ๊ฐ๋ฐ์ ์ํ ํํํ ๊ธฐ๋ฐ์ ์ ๊ณตํ๋ค๋ฉด, ROS 2๋ ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ๋จ์ ์ ๋ณด์ํฉ๋๋ค:
- ์ค์๊ฐ ํผํฌ๋จผ์ค: ์ค์๊ฐ ์์คํ ๋ฐ ๊ฒฐ์ ๋ก ์ ๋์์ ๋ํ ์ง์์ด ๊ฐ์ ๋์์ต๋๋ค.
- ๋ณด์: ๋ค์ํ ํ๊ฒฝ์์ ์์ ํ๊ณ ์์ ์ ์ผ๋ก ์ด์ํ ์ ์๋๋ก ๋ณด์ ๊ธฐ๋ฅ์ด ๊ฐํ๋์์ต๋๋ค.
- ํ์ฅ์ฑ: ๋ฉํฐ ๋ก๋ด ์์คํ ๋ฐ ๋๊ท๋ชจ ๋ฐฐํฌ์ ๋ํ ์ง์ ๊ฐํ.
- ํฌ๋ก์ค ํ๋ซํผ ์ง์: Linux๋ฅผ ๋์ด Windows, macOS ๋ฑ ๋ค์ํ ์ด์ ์ฒด์ ์์ ํธํ์ฑ์ด ํ๋๋์์ต๋๋ค.
- ์ ์ฐํ ์ปค๋ฎค๋์ผ์ด์ : ๋ณด๋ค ์ ์ฐํ๊ณ ํจ์จ์ ์ธ ํ๋ก์ธ์ค ๊ฐ ์ปค๋ฎค๋์ผ์ด์ ์ ์ํด DDS๋ฅผ ์ฌ์ฉํฉ๋๋ค.
ROS ๋ฉ์์ง ๋ฐ ์ฃผ์
ROS์์ ๋ ธ๋ ๊ฐ์ ํต์ ์ ๋ฉ์์ง์ ํ ํฝ์ ํตํด ์ด๋ฃจ์ด์ง๋๋ค. ๋ฉ์์ง๋ ๋ ธ๋ ๊ฐ์ ๊ตํ๋๋ ์ ๋ณด๋ฅผ ์ ์ํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ด๋ฉฐ, ํ ํฝ์ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ช ๋ช ๋ ์ฑ๋์ ๋๋ค. ๋ ธ๋๋ ํ ํฝ์ ๋ฉ์์ง๋ฅผ ๊ฒ์ํ๊ฑฐ๋ ํ ํฝ์ ๋ฉ์์ง๋ฅผ ๊ตฌ๋ ํ์ฌ ์๋ก ์ํตํ ์ ์์ต๋๋ค. ์ด ๊ฒ์-๊ตฌ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉด ๋ ธ๋ ๊ฐ์ ๋น๋๊ธฐ ํต์ ๋ฐ ๋ถ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ๋ก๋ด ์์คํ ์ ๊ฐ ์ผ์ ๋๋ ์ก์ถ์์ดํฐ๋ ์ผ๋ฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํ ํฝ์ ๊ฒ์ํ๊ณ , ๋ค๋ฅธ ๋ ธ๋์์ ์ฒ๋ฆฌ ๋๋ ์ ์ด๋ฅผ ์ํด ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ๊ฐ์ด๋์์๋ ์ด๋ฏธ์ง, ๊น์ด, ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋ฉ์์ง์ ์นด๋ฉ๋ผ ํ ํฝ์ ์ด์ ์ ๋ง์ถ๊ฒ ์ต๋๋ค.
ROS๋ก Ultralytics YOLO ์ค์ ํ๊ธฐ
์ด ๊ฐ์ด๋๋ ROSbot ROS ๋ฆฌํฌ์งํ ๋ฆฌ์ ํฌํฌ์ธ ์ด ROS ํ๊ฒฝ์ ์ฌ์ฉํ์ฌ ํ ์คํธ๋์์ต๋๋ค. ์ด ํ๊ฒฝ์๋ Ultralytics YOLO ํจํค์ง, ์์ฌ์ด ์ค์ ์ ์ํ ๋์ปค ์ปจํ ์ด๋, ํฌ๊ด์ ์ธ ROS ํจํค์ง, ๋น ๋ฅธ ํ ์คํธ๋ฅผ ์ํ ๊ฐ์ ๋ณด ์๋๊ฐ ํฌํจ๋์ด ์์ต๋๋ค. ์ด ํ๊ฒฝ์ ํ์ฌ๋ฆฌ์จ ROSbot 2 PRO์ ํจ๊ป ์๋ํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ์ ๊ณต๋๋ ์ฝ๋ ์์ ๋ ์๋ฎฌ๋ ์ด์ ๊ณผ ์ค์ ํ๊ฒฝ์ ํฌํจํ ๋ชจ๋ ROS Noetic/Melodic ํ๊ฒฝ์์ ์๋ํฉ๋๋ค.
์ข ์์ฑ ์ค์น
ROS ํ๊ฒฝ ์ธ์๋ ๋ค์ ์ข ์์ฑ์ ์ค์นํด์ผ ํฉ๋๋ค:
-
ROS ๋ํผ ํจํค์ง: ROS ์ด๋ฏธ์ง ๋ฉ์์ง์ ์ซ์ ๋ฐฐ์ด ๊ฐ์ ๋น ๋ฅธ ๋ณํ์ ์ํด ํ์ํฉ๋๋ค.
-
Ultralytics ํจํค์ง์ ๋๋ค:
ROS์ ํจ๊ป Ultralytics ์ฌ์ฉ sensor_msgs/Image
๊ทธ๋ฆฌ๊ณ sensor_msgs/Image
๋ฉ์์ง ์ ํ ๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ํํํ๊ธฐ ์ํด ROS์์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ์ธ์ฝ๋ฉ, ๋์ด, ๋๋น, ํฝ์
๋ฐ์ดํฐ๋ฅผ ์ํ ํ๋๊ฐ ํฌํจ๋์ด ์์ด ์นด๋ฉ๋ผ๋ ๊ธฐํ ์ผ์๋ก ์บก์ฒํ ์ด๋ฏธ์ง๋ฅผ ์ ์กํ๋ ๋ฐ ์ ํฉํฉ๋๋ค. ์ด๋ฏธ์ง ๋ฉ์์ง๋ ์๊ฐ ์ธ์๊ณผ ๊ฐ์ ์์
์ ์ํด ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์
์์ ๋๋ฆฌ ์ฌ์ฉ๋ฉ๋๋ค, ๋ฌผ์ฒด ๊ฐ์ง๋ฐ ๋ด๋น๊ฒ์ด์
.
์ด๋ฏธ์ง ๋จ๊ณ๋ณ ์ฌ์ฉ ๋ฐฉ๋ฒ
๋ค์ ์ฝ๋ ์ค๋ํซ์ Ultralytics YOLO ํจํค์ง๋ฅผ ROS์ ํจ๊ป ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ์ด ์์์๋ ์นด๋ฉ๋ผ ํ ํฝ์ ๊ตฌ๋ ํ๊ณ YOLO ์ ์ฌ์ฉํ์ฌ ๋ค์ด์ค๋ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ ๋ค์ ๊ฐ์ง๋ ๊ฐ์ฒด๋ฅผ ์ ํ ํฝ์ ๊ฒ์ํ์ฌ ๊ฐ์ง ๋ฐ ๋ถํ ์ ์ํํฉ๋๋ค.
๋จผ์ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์์ ๋ค์ ๋ ๊ฐ์ง ๋ชจ๋ธ์ ์ธ์คํด์คํํฉ๋๋ค. ์ธ๋ถํ ๊ทธ๋ฆฌ๊ณ ํ๋๋ ํ์ง. ROS ๋
ธ๋๋ฅผ ์ด๊ธฐํํฉ๋๋ค( ultralytics
)๋ฅผ ์ฌ์ฉํ์ฌ ROS ๋ง์คํฐ์ ํต์ ํ ์ ์์ต๋๋ค. ์์ ์ ์ธ ์ฐ๊ฒฐ์ ๋ณด์ฅํ๊ธฐ ์ํด ์ ์ ์ผ์ ์ค์งํ์ฌ ๋
ธ๋๊ฐ ์ฐ๊ฒฐ์ ์ค์ ํ ์ ์๋ ์ถฉ๋ถํ ์๊ฐ์ ์ ๊ณตํ ํ ๊ณ์ ์งํํฉ๋๋ค.
import time
import rospy
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
segmentation_model = YOLO("yolov8m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
๋ ๊ฐ์ ROS ์ฃผ์ ๋ฅผ ์ด๊ธฐํํฉ๋๋ค. ํ์ง ๊ทธ๋ฆฌ๊ณ ํ๋๋ ์ธ๋ถํ. ์ด ์ฃผ์ ๋ ์ฃผ์์ด ๋ฌ๋ฆฐ ์ด๋ฏธ์ง๋ฅผ ๊ฒ์ํ๋ ๋ฐ ์ฌ์ฉ๋์ด ์ถ๊ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด ์ก์ธ์คํ ์ ์๋๋ก ํฉ๋๋ค. ๋
ธ๋ ๊ฐ์ ํต์ ์ ๋ค์์ ์ฌ์ฉํ์ฌ ์ด์ง๋ฉ๋๋ค. sensor_msgs/Image
๋ฉ์์ง.
from sensor_msgs.msg import Image
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)
๋ง์ง๋ง์ผ๋ก, ๋ค์์์ ๋ฉ์์ง๋ฅผ ์์ ํ๋ ๊ตฌ๋
์๋ฅผ ๋ง๋ญ๋๋ค. /camera/color/image_raw
ํ ํฝ์ ๋ง๋ค๊ณ ๊ฐ๊ฐ์ ์ ๋ฉ์์ง์ ๋ํด ์ฝ๋ฐฑ ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ด ์ฝ๋ฐฑ ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ์ ํ์ ๋ฉ์์ง๋ฅผ ์์ ํฉ๋๋ค. sensor_msgs/Image
๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋ฐฐ์ด๋ก ๋ณํํฉ๋๋ค. ros_numpy
๋ฅผ ํธ์ถํ์ฌ ์ด์ ์ ์ธ์คํด์คํ๋ YOLO ๋ชจ๋ธ๋ก ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ๊ณ ์ด๋ฏธ์ง์ ์ฃผ์์ ๋จ ๋ค์ ๊ฐ ์ฃผ์ ์ ๋ค์ ๊ฒ์ํฉ๋๋ค: /ultralytics/detection/image
ํ์ง ๋ฐ /ultralytics/segmentation/image
๋ฅผ ์ฌ์ฉํ์ฌ ์ธ๋ถํํ ์ ์์ต๋๋ค.
import ros_numpy
def callback(data):
"""Callback function to process image and publish annotated images."""
array = ros_numpy.numpify(data)
if det_image_pub.get_num_connections():
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
if seg_image_pub.get_num_connections():
seg_result = segmentation_model(array)
seg_annotated = seg_result[0].plot(show=False)
seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
์ฝ๋ ์์ฑ
import time
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
segmentation_model = YOLO("yolov8m-seg.pt")
rospy.init_node("ultralytics")
time.sleep(1)
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
seg_image_pub = rospy.Publisher("/ultralytics/segmentation/image", Image, queue_size=5)
def callback(data):
"""Callback function to process image and publish annotated images."""
array = ros_numpy.numpify(data)
if det_image_pub.get_num_connections():
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
if seg_image_pub.get_num_connections():
seg_result = segmentation_model(array)
seg_annotated = seg_result[0].plot(show=False)
seg_image_pub.publish(ros_numpy.msgify(Image, seg_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
๋๋ฒ๊น
ROS(๋ก๋ด ์ด์ ์ฒด์ ) ๋ ธ๋๋ฅผ ๋๋ฒ๊น ํ๋ ๊ฒ์ ์์คํ ์ ๋ถ์ฐ๋ ํน์ฑ์ผ๋ก ์ธํด ์ด๋ ค์ธ ์ ์์ต๋๋ค. ์ฌ๋ฌ ๋๊ตฌ๊ฐ ์ด ํ๋ก์ธ์ค๋ฅผ ์ง์ํ ์ ์์ต๋๋ค:
rostopic echo <TOPIC-NAME>
: ์ด ๋ช ๋ น์ ์ฌ์ฉํ๋ฉด ํน์ ์ฃผ์ ์ ๋ํด ๊ฒ์๋ ๋ฉ์์ง๋ฅผ ๋ณผ ์ ์์ผ๋ฏ๋ก ๋ฐ์ดํฐ ํ๋ฆ์ ๊ฒ์ฌํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.rostopic list
: ์ด ๋ช ๋ น์ ์ฌ์ฉํ๋ฉด ROS ์์คํ ์์ ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ํ ํฝ์ ๋์ดํ์ฌ ํ์ฑ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ ๋ํ ๊ฐ์๋ฅผ ๋ณผ ์ ์์ต๋๋ค.rqt_graph
: ์ด ์๊ฐํ ๋๊ตฌ๋ ๋ ธ๋ ๊ฐ์ ํต์ ๊ทธ๋ํ๋ฅผ ํ์ํ์ฌ ๋ ธ๋๊ฐ ์ํธ ์ฐ๊ฒฐ๋๋ ๋ฐฉ์๊ณผ ์ํธ ์์ฉํ๋ ๋ฐฉ์์ ๋ํ ์ธ์ฌ์ดํธ๋ฅผ ์ ๊ณตํฉ๋๋ค.- 3D ํํ๊ณผ ๊ฐ์ด ๋ ๋ณต์กํ ์๊ฐํ์ ๊ฒฝ์ฐ ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค. RViz. RViz(ROS ์๊ฐํ)๋ ROS๋ฅผ ์ํ ๊ฐ๋ ฅํ 3D ์๊ฐํ ๋๊ตฌ์
๋๋ค. ๋ก๋ด์ ์ํ์ ์ฃผ๋ณ ํ๊ฒฝ์ ์ค์๊ฐ์ผ๋ก ์๊ฐํํ ์ ์์ต๋๋ค. RViz๋ฅผ ์ฌ์ฉํ๋ฉด ์ผ์ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์์ต๋๋ค(์
sensors_msgs/Image
), ๋ก๋ด ๋ชจ๋ธ ์ํ ๋ฐ ๊ธฐํ ๋ค์ํ ์ ํ์ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ฌ ๋ก๋ด ์์คํ ์ ๋์์ ๋ ์ฝ๊ฒ ๋๋ฒ๊ทธํ๊ณ ์ดํดํ ์ ์์ต๋๋ค.
๊ฐ์ง๋ ํด๋์ค๋ฅผ ๋ค์์ ์ฌ์ฉํ์ฌ ๊ฒ์ std_msgs/String
ํ์ค ROS ๋ฉ์์ง์๋ ๋ค์๋ ํฌํจ๋ฉ๋๋ค. std_msgs/String
๋ฉ์์ง๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๋ง์ ์ ํ๋ฆฌ์ผ์ด์
์์๋ ์ฃผ์์ด ๋ฌ๋ฆฐ ์ด๋ฏธ์ง ์ ์ฒด๋ฅผ ๋ค์ ๊ฒ์ํ ํ์ ์์ด ๋ก๋ด์ ๋ณด๊ธฐ์ ์๋ ํด๋์ค๋ง ์์ผ๋ฉด ๋ฉ๋๋ค. ๋ค์ ์๋ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. std_msgs/String
๋ฉ์์ง ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ง๋ ํด๋์ค๋ฅผ ๋ค์ ๊ฒ์ํฉ๋๋ค. /ultralytics/detection/classes
ํ ํฝ์ผ๋ก ์ค์ ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋ฉ์์ง๋ ๋ ๊ฐ๋ณ๊ณ ํ์ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ฏ๋ก ๋ค์ํ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ฉํฉ๋๋ค.
์ฌ์ฉ ์ฌ๋ก ์์
์นด๋ฉ๋ผ์ ๋ฌผ์ฒด๊ฐ ์ฅ์ฐฉ๋ ์ฐฝ๊ณ ๋ก๋ด์ ์๋ก ๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ํ์ง ๋ชจ๋ธ. ๋ก๋ด์ ๋คํธ์ํฌ๋ฅผ ํตํด ์ฃผ์์ด ๋ฌ๋ฆฐ ๋์ฉ๋ ์ด๋ฏธ์ง๋ฅผ ์ ์กํ๋ ๋์ ๊ฐ์ง๋ ํด๋์ค ๋ชฉ๋ก์ ๋ค์๊ณผ ๊ฐ์ด ๊ฒ์ํ ์ ์์ต๋๋ค. std_msgs/String
๋ฉ์์ง๋ฅผ ์ ๋ฌํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ก๋ด์ด '์์', 'ํ๋ ํธ', '์ง๊ฒ์ฐจ'์ ๊ฐ์ ๋ฌผ์ฒด๋ฅผ ๊ฐ์งํ๋ฉด ์ด๋ฌํ ํด๋์ค๋ฅผ ๋ค์ ๋ฉ์์ง์ ๊ฒ์ํฉ๋๋ค. /ultralytics/detection/classes
ํ ํฝ์ผ๋ก ์ด๋ํฉ๋๋ค. ๊ทธ๋ฌ๋ฉด ์ค์ ๋ชจ๋ํฐ๋ง ์์คํ
์์ ์ด ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ ์ค์๊ฐ์ผ๋ก ์ฌ๊ณ ๋ฅผ ์ถ์ ํ๊ณ , ์ฅ์ ๋ฌผ์ ํผํ๋๋ก ๋ก๋ด์ ๊ฒฝ๋ก ๊ณํ์ ์ต์ ํํ๊ฑฐ๋, ๊ฐ์ง๋ ์์๋ฅผ ์ง์ด ์ฌ๋ฆฌ๋ ๋ฑ์ ํน์ ์์
์ ํธ๋ฆฌ๊ฑฐํ ์ ์์ต๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ํต์ ์ ํ์ํ ๋์ญํญ์ ์ค์ด๊ณ ์ค์ํ ๋ฐ์ดํฐ ์ ์ก์ ์ง์คํ ์ ์์ต๋๋ค.
๋ฌธ์์ด ๋จ๊ณ๋ณ ์ฌ์ฉ๋ฒ
์ด ์๋ Ultralytics YOLO ํจํค์ง๋ฅผ ROS์ ํจ๊ป ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ์ด ์์์๋ ์นด๋ฉ๋ผ ํ ํฝ์ ๊ตฌ๋
ํ๊ณ YOLO ์ ์ฌ์ฉํ์ฌ ๋ค์ด์ค๋ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ ๋ค์ ๊ฐ์ง๋ ๊ฐ์ฒด๋ฅผ ์ ํ ํฝ์ ๊ฒ์ํฉ๋๋ค. /ultralytics/detection/classes
์ฌ์ฉ std_msgs/String
๋ฉ์์ง. ๋ฉ์์ง ros_numpy
ํจํค์ง๋ YOLO ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ROS ์ด๋ฏธ์ง ๋ฉ์์ง๋ฅผ ๋ ๋ฐฐ์ด๋ก ๋ณํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
import time
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
rospy.init_node("ultralytics")
time.sleep(1)
classes_pub = rospy.Publisher("/ultralytics/detection/classes", String, queue_size=5)
def callback(data):
"""Callback function to process image and publish detected classes."""
array = ros_numpy.numpify(data)
if classes_pub.get_num_connections():
det_result = detection_model(array)
classes = det_result[0].boxes.cls.cpu().numpy().astype(int)
names = [det_result[0].names[i] for i in classes]
classes_pub.publish(String(data=str(names)))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
while True:
rospy.spin()
ROS ๋์ค ์ด๋ฏธ์ง์ ํจ๊ป Ultralytics ์ฌ์ฉ
ROS๋ RGB ์ด๋ฏธ์ง ์ธ์๋ ์นด๋ฉ๋ผ์์ ๋ฌผ์ฒด๊น์ง์ ๊ฑฐ๋ฆฌ์ ๋ํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๊น์ด ์ด๋ฏธ์ง๋ฅผ ์ง์ํฉ๋๋ค. ์ฌ๋ ์ด๋ฏธ์ง๋ ์ฅ์ ๋ฌผ ํํผ, 3D ๋งคํ, ๋ก์ปฌ๋ผ์ด์ ์ด์ ๊ณผ ๊ฐ์ ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋งค์ฐ ์ค์ํ ์์์ ๋๋ค.
๊น์ด ์ด๋ฏธ์ง๋ ๊ฐ ํฝ์ ์ด ์นด๋ฉ๋ผ์์ ๋ฌผ์ฒด๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ ๋ํ๋ด๋ ์ด๋ฏธ์ง์ ๋๋ค. ์์์ ์บก์ฒํ๋ RGB ์ด๋ฏธ์ง์ ๋ฌ๋ฆฌ ๊น์ด ์ด๋ฏธ์ง๋ ๊ณต๊ฐ ์ ๋ณด๋ฅผ ์บก์ฒํ์ฌ ๋ก๋ด์ด ์ฃผ๋ณ ํ๊ฒฝ์ 3D ๊ตฌ์กฐ๋ฅผ ์ธ์ํ ์ ์๊ฒ ํด์ค๋๋ค.
๊น์ด ์ด๋ฏธ์ง ์ป๊ธฐ
๋ค์ํ ์ผ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ ์ด๋ฏธ์ง๋ฅผ ์ป์ ์ ์์ต๋๋ค:
- ์คํ ๋ ์ค ์นด๋ฉ๋ผ: ๋ ๋์ ์นด๋ฉ๋ผ๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง ๋ถ๊ท ํ์ ๋ฐ๋ผ ๊น์ด๋ฅผ ๊ณ์ฐํฉ๋๋ค.
- ToF(๋นํ ์๊ฐ) ์นด๋ฉ๋ผ: ๋น์ด ๋ฌผ์ฒด์์ ๋์์ค๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ ์ธก์ ํฉ๋๋ค.
- ๊ตฌ์กฐํ ๊ด ์ผ์: ํจํด์ ํฌ์ฌํ๊ณ ํ๋ฉด์ ๋ณํ์ ์ธก์ ํฉ๋๋ค.
๊น์ด ์ด๋ฏธ์ง์ ํจ๊ป YOLO ์ฌ์ฉ
ROS์์ ๊น์ด ์ด๋ฏธ์ง๋ ๋ค์๊ณผ ๊ฐ์ด ํํ๋ฉ๋๋ค. sensor_msgs/Image
๋ฉ์์ง ์ ํ์๋ ์ธ์ฝ๋ฉ, ๋์ด, ๋๋น ๋ฐ ํฝ์
๋ฐ์ดํฐ ํ๋๊ฐ ํฌํจ๋ฉ๋๋ค. ๊น์ด ์ด๋ฏธ์ง์ ์ธ์ฝ๋ฉ ํ๋๋ ํฝ์
๋น 16๋นํธ ๋ถํธ ์๋ ์ ์๋ฅผ ๋ํ๋ด๋ "16UC1"๊ณผ ๊ฐ์ ํ์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ, ๊ฐ ๊ฐ์ ๋ฌผ์ฒด๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ ๋ํ๋
๋๋ค. ๊น์ด ์ด๋ฏธ์ง๋ ์ผ๋ฐ์ ์ผ๋ก RGB ์ด๋ฏธ์ง์ ํจ๊ป ์ฌ์ฉํ์ฌ ํ๊ฒฝ์ ๋ณด๋ค ํฌ๊ด์ ์ผ๋ก ๋ณผ ์ ์๋๋ก ํฉ๋๋ค.
YOLO ์ ์ฌ์ฉํ๋ฉด RGB ์ด๋ฏธ์ง์ ์ฌ๋ ์ด๋ฏธ์ง ๋ชจ๋์์ ์ ๋ณด๋ฅผ ์ถ์ถํ๊ณ ๊ฒฐํฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด YOLO ์ RGB ์ด๋ฏธ์ง ๋ด์ ๋ฌผ์ฒด๋ฅผ ๊ฐ์งํ ์ ์์ผ๋ฉฐ, ์ด ๊ฐ์ง๋ฅผ ํตํด ๊น์ด ์ด๋ฏธ์ง์์ ํด๋น ์์ญ์ ์ ํํ ์ฐพ์๋ผ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ์ง๋ ๋ฌผ์ฒด์ ๋ํ ์ ํํ ๊น์ด ์ ๋ณด๋ฅผ ์ถ์ถํ์ฌ ๋ก๋ด์ด ์ฃผ๋ณ ํ๊ฒฝ์ 3์ฐจ์์ผ๋ก ์ดํดํ๋ ๋ฅ๋ ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
RGB-D ์นด๋ฉ๋ผ
์ฌ๋ ์ด๋ฏธ์ง๋ก ์์ ํ ๋๋ RGB์ ์ฌ๋ ์ด๋ฏธ์ง๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ ๋ ฌ๋์ด ์๋์ง ํ์ธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. RealSense ์๋ฆฌ์ฆ( Intel )์ ๊ฐ์ RGB-D ์นด๋ฉ๋ผ๋ ๋๊ธฐํ๋ RGB ๋ฐ ์ฌ๋ ์ด๋ฏธ์ง๋ฅผ ์ ๊ณตํ๋ฏ๋ก ๋ ์์ค์ ์ ๋ณด๋ฅผ ์ฝ๊ฒ ๊ฒฐํฉํ ์ ์์ต๋๋ค. ๋ณ๋์ RGB ๋ฐ ์ฌ๋ ์นด๋ฉ๋ผ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ ํํ ์ ๋ ฌ์ ์ํด ๋ณด์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๊น์ด ๋จ๊ณ๋ณ ์ฌ์ฉ๋ฒ
์ด ์์์๋ YOLO ์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ถํ ํ๊ณ ์ถ์ถ๋ ๋ง์คํฌ๋ฅผ ์ ์ฉํ์ฌ ๊น์ด ์ด๋ฏธ์ง์์ ๋ฌผ์ฒด๋ฅผ ๋ถํ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์นด๋ฉ๋ผ์ ์ด์ ์ค์ฌ์์ ๊ด์ฌ ์๋ ๋ฌผ์ฒด์ ๊ฐ ํฝ์ ์ ๊ฑฐ๋ฆฌ๋ฅผ ๊ฒฐ์ ํ ์ ์์ต๋๋ค. ์ด ๊ฑฐ๋ฆฌ ์ ๋ณด๋ฅผ ์ป์ผ๋ฉด ์นด๋ฉ๋ผ์ ์ฅ๋ฉด์ ํน์ ๋ฌผ์ฒด ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ์ฐํ ์ ์์ต๋๋ค. ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ROS ๋ ธ๋๋ฅผ ์์ฑํ๊ณ , ์ธ๋ถํ ๋ชจ๋ธ๊ณผ ROS ํ ํฝ์ ์ธ์คํด์คํํ๋ ๊ฒ์ผ๋ก ์์ํ์ธ์.
import time
import rospy
from std_msgs.msg import String
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
๋ค์์ผ๋ก ๋ค์ด์ค๋ ๊น์ด ์ด๋ฏธ์ง ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ ์ํฉ๋๋ค. ์ด ํจ์๋ ๊น์ด ์ด๋ฏธ์ง์ RGB ์ด๋ฏธ์ง ๋ฉ์์ง๋ฅผ ๊ธฐ๋ค๋ ธ๋ค๊ฐ ์ด๋ฅผ ๋ ๋ฐฐ์ด๋ก ๋ณํํ ๋ค์ ์ธ๋ถํ ๋ชจ๋ธ์ RGB ์ด๋ฏธ์ง์ ์ ์ฉํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ ๊ฐ์ง๋ ๊ฐ ๋ฌผ์ฒด์ ๋ํ ๋ถํ ๋ง์คํฌ๋ฅผ ์ถ์ถํ๊ณ ๊น์ด ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ์ฌ ์นด๋ฉ๋ผ๋ก๋ถํฐ ๋ฌผ์ฒด์ ํ๊ท ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ์ฐํฉ๋๋ค. ๋๋ถ๋ถ์ ์ผ์์๋ ํด๋ฆฝ ๊ฑฐ๋ฆฌ๋ผ๊ณ ํ๋ ์ต๋ ๊ฑฐ๋ฆฌ๊ฐ ์์ผ๋ฉฐ, ๊ทธ ์ด์์ ๊ฐ์ inf(np.inf
). ์ฒ๋ฆฌํ๊ธฐ ์ ์ ์ด๋ฌํ null ๊ฐ์ ํํฐ๋งํ๊ณ ๊ฐ์ 0
. ๋ง์ง๋ง์ผ๋ก, ๊ฐ์ง๋ ์ค๋ธ์ ํธ๋ฅผ ํ๊ท ๊ฑฐ๋ฆฌ์ ํจ๊ป /ultralytics/detection/distance
์ฃผ์ .
import numpy as np
import ros_numpy
from sensor_msgs.msg import Image
def callback(data):
"""Callback function to process depth image and RGB image."""
image = rospy.wait_for_message("/camera/color/image_raw", Image)
image = ros_numpy.numpify(image)
depth = ros_numpy.numpify(data)
result = segmentation_model(image)
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()
์ฝ๋ ์์ฑ
import time
import numpy as np
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from std_msgs.msg import String
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
classes_pub = rospy.Publisher("/ultralytics/detection/distance", String, queue_size=5)
def callback(data):
"""Callback function to process depth image and RGB image."""
image = rospy.wait_for_message("/camera/color/image_raw", Image)
image = ros_numpy.numpify(image)
depth = ros_numpy.numpify(data)
result = segmentation_model(image)
for index, cls in enumerate(result[0].boxes.cls):
class_index = int(cls.cpu().numpy())
name = result[0].names[class_index]
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
obj = depth[mask == 1]
obj = obj[~np.isnan(obj)]
avg_distance = np.mean(obj) if len(obj) else np.inf
classes_pub.publish(String(data=str(all_objects)))
rospy.Subscriber("/camera/depth/image_raw", Image, callback)
while True:
rospy.spin()
ROS์ ํจ๊ป Ultralytics ์ฌ์ฉ sensor_msgs/PointCloud2
๊ทธ๋ฆฌ๊ณ sensor_msgs/PointCloud2
๋ฉ์์ง ์ ํ ๋ ROS์์ 3D ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋ฐ์ดํฐ๋ฅผ ํํํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์
๋๋ค. ์ด ๋ฉ์์ง ์ ํ์ ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์
์ ํ์์ ์ธ ์์๋ก 3D ๋งคํ, ๋ฌผ์ฒด ์ธ์, ๋ก์ปฌ๋ผ์ด์ ์ด์
๊ณผ ๊ฐ์ ์์
์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
ํฌ์ธํธ ํด๋ผ์ฐ๋๋ 3์ฐจ์ ์ขํ๊ณ ๋ด์ ์ ์๋ ๋ฐ์ดํฐ ํฌ์ธํธ์ ๋ชจ์์
๋๋ค. ์ด๋ฌํ ๋ฐ์ดํฐ ํฌ์ธํธ๋ 3D ์ค์บ๋ ๊ธฐ์ ์ ํตํด ์บก์ฒํ ๋ฌผ์ฒด ๋๋ ์ฅ๋ฉด์ ์ธ๋ถ ํ๋ฉด์ ๋ํ๋
๋๋ค. ํด๋ผ์ฐ๋์ ๊ฐ ํฌ์ธํธ์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค. X
, Y
๋ฐ Z
์ขํ๋ ๊ณต๊ฐ์์์ ์์น์ ํด๋นํ๋ฉฐ ์์ ๋ฐ ๊ฐ๋์ ๊ฐ์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ํฌํจํ ์๋ ์์ต๋๋ค.
์ฐธ์กฐ ํ๋ ์
๋ค์๊ณผ ํจ๊ป ์์
ํ ๋ sensor_msgs/PointCloud2
๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋ฐ์ดํฐ๋ฅผ ์์งํ ์ผ์์ ๊ธฐ์ค ํ๋ ์์ ๊ณ ๋ คํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ํฌ์ธํธ ํด๋ผ์ฐ๋๋ ์ฒ์์ ์ผ์์ ๊ธฐ์ค ํ๋ ์์์ ์บก์ฒ๋ฉ๋๋ค. ์ด ๊ธฐ์ค ํ๋ ์์ ๊ฒฐ์ ํ๋ ค๋ฉด ์ผ์์ /tf_static
์ฃผ์ ๋ฅผ ์ฐธ์กฐํ์ธ์. ๊ทธ๋ฌ๋ ํน์ ์ ํ๋ฆฌ์ผ์ด์
์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ํฌ์ธํธ ํด๋ผ์ฐ๋๋ฅผ ๋ค๋ฅธ ์ฐธ์กฐ ํ๋ ์์ผ๋ก ๋ณํํด์ผ ํ ์๋ ์์ต๋๋ค. ์ด ๋ณํ์ tf2_ros
์ขํ ํ๋ ์์ ๊ด๋ฆฌํ๊ณ ํ๋ ์ ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ ํจํค์ง์
๋๋ค.
ํฌ์ธํธ ํด๋ผ์ฐ๋ ๊ฐ์ ธ์ค๊ธฐ
ํฌ์ธํธ ํด๋ผ์ฐ๋๋ ๋ค์ํ ์ผ์๋ฅผ ์ฌ์ฉํ์ฌ ์ป์ ์ ์์ต๋๋ค:
- LIDAR(๋น ๊ฐ์ง ๋ฐ ๊ฑฐ๋ฆฌ ์ธก์ ): ๋ ์ด์ ํ์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌผ์ฒด๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ ์ธก์ ํ๊ณ ๊ณ ์ ๋ฐ 3D ์ง๋๋ฅผ ์์ฑํฉ๋๋ค.
- ๋์ค ์นด๋ฉ๋ผ: ๊ฐ ํฝ์ ์ ๋ํ ๊น์ด ์ ๋ณด๋ฅผ ์บก์ฒํ์ฌ ์ฅ๋ฉด์ 3D๋ก ์ฌ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
- ์คํ ๋ ์ค ์นด๋ฉ๋ผ: ๋ ๋ ์ด์์ ์นด๋ฉ๋ผ๋ฅผ ํ์ฉํ์ฌ ์ผ๊ฐ ์ธก๋์ ํตํด ๊น์ด ์ ๋ณด๋ฅผ ์ป์ต๋๋ค.
- ๊ตฌ์กฐ๊ด ์ค์บ๋: ์๋ ค์ง ํจํด์ ํ๋ฉด์ ํฌ์ฌํ๊ณ ๋ณํ์ ์ธก์ ํ์ฌ ๊น์ด๋ฅผ ๊ณ์ฐํฉ๋๋ค.
ํฌ์ธํธ ํด๋ผ์ฐ๋์ ํจ๊ป YOLO ์ฌ์ฉ
YOLO ๋ฅผ ๋ค์๊ณผ ํตํฉํ๋ ค๋ฉด sensor_msgs/PointCloud2
์ ํ ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ค ๋งต์ ์ฌ์ฉ๋๋ ๊ฒ๊ณผ ์ ์ฌํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํฌ์ธํธ ํด๋ผ์ฐ๋์ ํฌํจ๋ ์์ ์ ๋ณด๋ฅผ ํ์ฉํ์ฌ 2D ์ด๋ฏธ์ง๋ฅผ ์ถ์ถํ๊ณ YOLO ์ ์ฌ์ฉํ์ฌ ์ด ์ด๋ฏธ์ง์ ๋ถํ ์ ์ํํ ๋ค์ ๊ฒฐ๊ณผ ๋ง์คํฌ๋ฅผ 3์ฐจ์ ํฌ์ธํธ์ ์ ์ฉํ์ฌ ๊ด์ฌ ์๋ 3D ๊ฐ์ฒด๋ฅผ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
ํฌ์ธํธ ํด๋ผ์ฐ๋๋ฅผ ์ฒ๋ฆฌํ๋ ค๋ฉด Open3D(pip install open3d
), ์ฌ์ฉ์ ์นํ์ ์ธ Python ๋ผ์ด๋ธ๋ฌ๋ฆฌ. Open3D๋ ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๊ด๋ฆฌํ๊ณ ์๊ฐํํ๋ฉฐ ๋ณต์กํ ์์
์ ์ํํ๊ฒ ์คํํ ์ ์๋ ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ก์ธ์ค๋ฅผ ํฌ๊ฒ ๊ฐ์ํํ๊ณ YOLO-๊ธฐ๋ฐ ์ธ๋ถํ์ ํจ๊ป ํฌ์ธํธ ํด๋ผ์ฐ๋๋ฅผ ์กฐ์ํ๊ณ ๋ถ์ํ๋ ๋ฅ๋ ฅ์ ํฅ์์ํฌ ์ ์์ต๋๋ค.
ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋จ๊ณ๋ณ ์ฌ์ฉ ๋ฐฉ๋ฒ
ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ธ๋ถํ๋ฅผ ์ํด YOLO ๋ชจ๋ธ์ ์ธ์คํด์คํํฉ๋๋ค.
import time
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
ํจ์ ๋ง๋ค๊ธฐ pointcloud2_to_array
๋ฅผ ๋ณํํ๋ sensor_msgs/PointCloud2
๋ฉ์์ง๋ฅผ ๋ ๊ฐ์ ๋ ๋ฐฐ์ด๋ก ๋๋๋๋ค. ๋ ๊ฐ์ sensor_msgs/PointCloud2
๋ฉ์์ง์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค. n
ํฌ์ธํธ์ ๋ฐ๋ผ width
๊ทธ๋ฆฌ๊ณ height
๋ก ์ค์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด 480 x 640
์ด๋ฏธ์ง์๋ 307,200
์ ์
๋๋ค. ๊ฐ ์ ์๋ ์ธ ๊ฐ์ ๊ณต๊ฐ ์ขํ(xyz
)์ ํด๋น ์์๊ณผ RGB
ํ์์
๋๋ค. ์ด๋ ๋ ๊ฐ์ ๊ฐ๋ณ ์ ๋ณด ์ฑ๋๋ก ๊ฐ์ฃผํ ์ ์์ต๋๋ค.
์ด ํจ์๋ xyz
์ขํ์ RGB
๊ฐ์ ์๋ ์นด๋ฉ๋ผ ํด์๋ ํ์(width x height
). ๋๋ถ๋ถ์ ์ผ์์๋ ํด๋ฆฝ ๊ฑฐ๋ฆฌ๋ผ๊ณ ํ๋ ์ต๋ ๊ฑฐ๋ฆฌ๊ฐ ์์ผ๋ฉฐ, ๊ทธ ์ด์์ ๊ฐ์ inf(np.inf
). ์ฒ๋ฆฌํ๊ธฐ ์ ์ ์ด๋ฌํ null ๊ฐ์ ํํฐ๋งํ๊ณ ๊ฐ์ 0
.
import numpy as np
import ros_numpy
def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
"""
Convert a ROS PointCloud2 message to a numpy array.
Args:
pointcloud2 (PointCloud2): the PointCloud2 message
Returns:
(tuple): tuple containing (xyz, rgb)
"""
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
nan_rows = np.isnan(xyz).all(axis=2)
xyz[nan_rows] = [0, 0, 0]
rgb[nan_rows] = [0, 0, 0]
return xyz, rgb
๋ค์์ผ๋ก /camera/depth/points
ํ ํฝ์ ์ฌ์ฉํ์ฌ ํฌ์ธํธ ํด๋ผ์ฐ๋ ๋ฉ์์ง๋ฅผ ์์ ํ๊ณ sensor_msgs/PointCloud2
๋ฉ์์ง๋ฅผ XYZ ์ขํ์ RGB ๊ฐ์ ํฌํจํ๋ ๋ ๋ฐฐ์ด๋ก ๋ณํํฉ๋๋ค( pointcloud2_to_array
ํจ์). YOLO ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ RGB ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ์ฌ ์ธ๊ทธ๋จผํธํ๋ ๋ฌผ์ฒด๋ฅผ ์ถ์ถํฉ๋๋ค. ๊ฐ์ง๋ ๊ฐ ๊ฐ์ฒด์ ๋ํด ๋ถํ ๋ง์คํฌ๋ฅผ ์ถ์ถํ๊ณ RGB ์ด๋ฏธ์ง์ XYZ ์ขํ์ ๋ชจ๋ ์ ์ฉํ์ฌ 3D ๊ณต๊ฐ์์ ๊ฐ์ฒด๋ฅผ ๋ถ๋ฆฌํฉ๋๋ค.
๋ง์คํฌ๋ ์ด์ง ๊ฐ์ผ๋ก ๊ตฌ์ฑ๋๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. 1
๊ฐ์ฒด์ ์กด์ฌ๋ฅผ ๋ํ๋ด๊ณ 0
๋ฅผ ํ์ํ์ฌ ๋ถ์ฌ ์ค์์ ๋ํ๋
๋๋ค. ๋ง์คํฌ๋ฅผ ์ ์ฉํ๋ ค๋ฉด ์๋ณธ ์ฑ๋์ ๋ง์คํฌ๋ฅผ ๊ณฑํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ์ด ์์
์ ์ด๋ฏธ์ง ๋ด์์ ๊ด์ฌ ์๋ ๋ฌผ์ฒด๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๋ถ๋ฆฌํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก Open3D ํฌ์ธํธ ํด๋ผ์ฐ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ๊ด๋ จ ์์์ผ๋ก 3D ๊ณต๊ฐ์์ ์ธ๊ทธ๋จผํธํ๋ ๊ฐ์ฒด๋ฅผ ์๊ฐํํฉ๋๋ค.
import sys
import open3d as o3d
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
์ฝ๋ ์์ฑ
import sys
import time
import numpy as np
import open3d as o3d
import ros_numpy
import rospy
from ultralytics import YOLO
rospy.init_node("ultralytics")
time.sleep(1)
segmentation_model = YOLO("yolov8m-seg.pt")
def pointcloud2_to_array(pointcloud2: PointCloud2) -> tuple:
"""
Convert a ROS PointCloud2 message to a numpy array.
Args:
pointcloud2 (PointCloud2): the PointCloud2 message
Returns:
(tuple): tuple containing (xyz, rgb)
"""
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
nan_rows = np.isnan(xyz).all(axis=2)
xyz[nan_rows] = [0, 0, 0]
rgb[nan_rows] = [0, 0, 0]
return xyz, rgb
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((ros_cloud.height * ros_cloud.width, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((ros_cloud.height * ros_cloud.width, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
์์ฃผ ๋ฌป๋ ์ง๋ฌธ
๋ก๋ด ์ด์ ์ฒด์ (ROS)๋ ๋ฌด์์ธ๊ฐ์?
ROS(๋ก๋ด ์ด์ ์ฒด์ ) ๋ ๊ฐ๋ฐ์๊ฐ ๊ฐ๋ ฅํ ๋ก๋ด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋๋ ๋ฐ ๋์์ด ๋๋ ๋ก๋ด ๊ณตํ์์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์คํ ์์ค ํ๋ ์์ํฌ์ ๋๋ค. ๋ก๋ด ์์คํ ๊ตฌ์ถ ๋ฐ ์ธํฐํ์ด์ค๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๊ตฌ ๋ชจ์์ ์ ๊ณตํ์ฌ ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด๋ค ์ฝ๊ฒ ๊ฐ๋ฐํ ์ ์๋๋ก ํด์ค๋๋ค. ROS๋ ์ฃผ์ ๋๋ ์๋น์ค์ ๋ํ ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ์ฌ ๋ ธ๋ ๊ฐ์ ํต์ ์ ์ง์ํฉ๋๋ค.
์ค์๊ฐ ๊ฐ์ฒด ๊ฐ์ง๋ฅผ ์ํด Ultralytics YOLO ์ ROS์ ํตํฉํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ๋์?
Ultralytics YOLO ์ ROS์ ํตํฉํ๋ ค๋ฉด ROS ํ๊ฒฝ์ ์ค์ ํ๊ณ ์ผ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ํด YOLO ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ํ์ ์ข
์์ฑ์ ์ค์นํ์ฌ ์์ํ์ธ์. ros_numpy
๋ฐ Ultralytics YOLO :
๊ทธ๋ฐ ๋ค์ ROS ๋ ธ๋๋ฅผ ๋ง๋ค๊ณ ์ด๋ฏธ์ง ํ ํฝ์ ๊ตฌ๋ ํ์ฌ ๋ค์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ๋ค์์ ์ต์ํ์ ์์์ ๋๋ค:
import ros_numpy
import rospy
from sensor_msgs.msg import Image
from ultralytics import YOLO
detection_model = YOLO("yolov8m.pt")
rospy.init_node("ultralytics")
det_image_pub = rospy.Publisher("/ultralytics/detection/image", Image, queue_size=5)
def callback(data):
array = ros_numpy.numpify(data)
det_result = detection_model(array)
det_annotated = det_result[0].plot(show=False)
det_image_pub.publish(ros_numpy.msgify(Image, det_annotated, encoding="rgb8"))
rospy.Subscriber("/camera/color/image_raw", Image, callback)
rospy.spin()
ROS ์ฃผ์ ๋ ๋ฌด์์ด๋ฉฐ Ultralytics YOLO ์์ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์?
ROS ํ ํฝ์ ๊ฒ์-๊ตฌ๋ ๋ชจ๋ธ์ ์ฌ์ฉํ์ฌ ROS ๋คํธ์ํฌ์ ๋ ธ๋ ๊ฐ ํต์ ์ ์ฉ์ดํ๊ฒ ํฉ๋๋ค. ํ ํฝ์ ๋ ธ๋๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ช ๋ช ๋ ์ฑ๋์ ๋๋ค. Ultralytics YOLO ์ ๋งฅ๋ฝ์์ ๋ ธ๋๊ฐ ์ด๋ฏธ์ง ํ ํฝ์ ๊ตฌ๋ ํ๊ณ , YOLO ์ ์ฌ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ์ฌ ๊ฐ์ง ๋๋ ๋ถํ ๊ณผ ๊ฐ์ ์์ ์ ์ํํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ์ ํ ํฝ์ ๊ฒ์ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์นด๋ฉ๋ผ ํ ํฝ์ ๊ตฌ๋ ํ๊ณ ๋ค์ด์ค๋ ์ด๋ฏธ์ง๋ฅผ ์ฒ๋ฆฌํ์ฌ ๊ฐ์งํฉ๋๋ค:
ROS์์ ๋์ค ์ด๋ฏธ์ง( Ultralytics YOLO )๋ฅผ ์ฌ์ฉํ๋ ์ด์ ๋ ๋ฌด์์ธ๊ฐ์?
ROS์ ๊น์ด ์ด๋ฏธ์ง๋ ๋ค์๊ณผ ๊ฐ์ด ํํ๋ฉ๋๋ค. sensor_msgs/Image
๋ ์ฅ์ ๋ฌผ ํํผ, 3D ๋งคํ ๋ฐ ๋ก์ปฌ๋ผ์ด์ ์ด์
๊ณผ ๊ฐ์ ์์
์ ์ค์ํ ์นด๋ฉ๋ผ๋ก๋ถํฐ ๋ฌผ์ฒด๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ธฐ์ค ๊น์ด ์ ๋ณด ์ฌ์ฉ RGB ์ด๋ฏธ์ง์ ํจ๊ป ๋ก๋ด์ด 3D ํ๊ฒฝ์ ๋ ์ ์ดํดํ ์ ์์ต๋๋ค.
YOLO ์ ์ฌ์ฉํ๋ฉด RGB ์ด๋ฏธ์ง์์ ๋ถํ ๋ง์คํฌ๋ฅผ ์ถ์ถํ๊ณ ์ด ๋ง์คํฌ๋ฅผ ๊น์ด ์ด๋ฏธ์ง์ ์ ์ฉํ์ฌ ์ ํํ 3D ๋ฌผ์ฒด ์ ๋ณด๋ฅผ ์ป์ ์ ์์ผ๋ฏ๋ก ๋ก๋ด์ด ์ฃผ๋ณ ํ๊ฒฝ์ ํ์ํ๊ณ ์ํธ ์์ฉํ๋ ๋ฅ๋ ฅ์ด ํฅ์๋ฉ๋๋ค.
ROS์์ YOLO ์ ์ฌ์ฉํ์ฌ 3D ํฌ์ธํธ ํด๋ผ์ฐ๋๋ฅผ ์๊ฐํํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ๋์?
YOLO ์ ์ฌ์ฉํ์ฌ ROS์์ 3D ํฌ์ธํธ ํด๋ผ์ฐ๋๋ฅผ ์๊ฐํํ๋ ค๋ฉด:
- ๋ณํ
sensor_msgs/PointCloud2
๋ฉ์์ง๋ฅผ ๋ฐฐ์ด์ ์ ๋ฌํฉ๋๋ค. - YOLO ์ ์ฌ์ฉํ์ฌ RGB ์ด๋ฏธ์ง๋ฅผ ๋ถํ ํฉ๋๋ค.
- ํฌ์ธํธ ํด๋ผ์ฐ๋์ ์ธ๊ทธ๋จผํ ์ด์ ๋ง์คํฌ๋ฅผ ์ ์ฉํฉ๋๋ค.
๋ค์์ ์๊ฐํ๋ฅผ ์ํด Open3D๋ฅผ ์ฌ์ฉํ ์์ ์ ๋๋ค:
import sys
import open3d as o3d
import ros_numpy
import rospy
from sensor_msgs.msg import PointCloud2
from ultralytics import YOLO
rospy.init_node("ultralytics")
segmentation_model = YOLO("yolov8m-seg.pt")
def pointcloud2_to_array(pointcloud2):
pc_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud2)
split = ros_numpy.point_cloud2.split_rgb_field(pc_array)
rgb = np.stack([split["b"], split["g"], split["r"]], axis=2)
xyz = ros_numpy.point_cloud2.get_xyz_points(pc_array, remove_nans=False)
xyz = np.array(xyz).reshape((pointcloud2.height, pointcloud2.width, 3))
return xyz, rgb
ros_cloud = rospy.wait_for_message("/camera/depth/points", PointCloud2)
xyz, rgb = pointcloud2_to_array(ros_cloud)
result = segmentation_model(rgb)
if not len(result[0].boxes.cls):
print("No objects detected")
sys.exit()
classes = result[0].boxes.cls.cpu().numpy().astype(int)
for index, class_id in enumerate(classes):
mask = result[0].masks.data.cpu().numpy()[index, :, :].astype(int)
mask_expanded = np.stack([mask, mask, mask], axis=2)
obj_rgb = rgb * mask_expanded
obj_xyz = xyz * mask_expanded
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(obj_xyz.reshape((-1, 3)))
pcd.colors = o3d.utility.Vector3dVector(obj_rgb.reshape((-1, 3)) / 255)
o3d.visualization.draw_geometries([pcd])
์ด ์ ๊ทผ ๋ฐฉ์์ ์ธ๊ทธ๋จผํธํ๋ ๊ฐ์ฒด๋ฅผ 3D ์๊ฐํํ์ฌ ํ์ ๋ฐ ์กฐ์๊ณผ ๊ฐ์ ์์ ์ ์ ์ฉํฉ๋๋ค.