24. [ROS RC์นด] ์์ฉ ์ค์ต, Object Following - 2
๊ณ์ํด์ ๋ค๋ฅธ node๋ค๋ ๋ถ์ํด ๋ณด๊ฒ ์ต๋๋ค.
๊ทธ ์ ์! ๋ณต์ต์ ํ๊ณ ๋์ด๊ฐ๊ฒ ์ต๋๋ค ๐
rqt_graph
- csi_pub : ์นด๋ฉ๋ผ ์ด๋ฏธ์ง๋ฅผ ๊ณ์ํด์ ์ก์ถํด์ฃผ๋ ๋ ธ๋์ด์ง์?
- blob_detector : ์ปดํจํฐ ๋น์ ์ด ์ ์ฉ๋ ๋ ธ๋๋ก, ๋ น์ ๋ฌผ์ฒด๋ฅผ ์ฐพ์๋ด๊ณ , ์ง์์ ์ผ๋ก ์ถ์ ํ๋ ๋ ธ๋
- chase_ball : ์ด์ ๋ ธ๋์์ ๋ น์ ๋ฌผ์ฒด์ ์์น๊ฐ ํฝ์ ๋น์จ ๋จ์๋ก ์ค๊ฒ ๋ฉ๋๋ค. ์ด ํฝ์ ์ ์์น์ ๋ฐ๋ผ์ ์ ์ด ์ ํธ๋ฅผ Twist ํ์์ผ๋ก ๋ณํํฉ๋๋ค.
- image_view : ์ด์ ์๋ ์ดํด๋ณด์๋, ์ด๋ฏธ์ง ํ ํฝ์ ๋ณผ ์ ์๊ฒ ํด์ฃผ๋ ๋ ธ๋์ ๋๋ค.
- blob_chase_node : ์ค์ง์ ์ธ PWM ์ ์ด๋ฅผ ๋ด๋นํ๋ ๋ ธ๋์ ๋๋ค.
chase_the_ball.py
#!/usr/bin/python
"""
Gets the position of the blob and it commands to steer the wheels
referenced from tizianofiorenzani/ros_tutorials
url: https://github.com/tizianofiorenzani/ros_tutorials
Subscribes to
/blob/point_blob
Publishes commands to
/dkcar/control/cmd_vel
"""
import math, time
import rospy
from geometry_msgs.msg import Twist
from geometry_msgs.msg import Point
K_LAT_DIST_TO_STEER = 2.0
def saturate(value, min, max):
if value <= min:
return min
elif value >= max:
return max
else:
return value
class ChaseBall:
def __init__(self):
self.blob_x = 0.0
self.blob_y = 0.0
self._time_detected = 0.0
self.sub_center = rospy.Subscriber("/blob/point_blob", Point, self.update_ball)
rospy.loginfo("Subscribers set")
self.pub_twist = rospy.Publisher("/dkcar/control/cmd_vel", Twist, queue_size=5)
rospy.loginfo("Publisher set")
self._message = Twist()
self._time_steer = 0
self._steer_sign_prev = 0
@property
def is_detected(self):
return time.time() - self._time_detected < 1.0
def update_ball(self, message):
self.blob_x = message.x
self.blob_y = message.y
self._time_detected = time.time()
# rospy.loginfo("Ball detected: %.1f %.1f "%(self.blob_x, self.blob_y))
def get_control_action(self):
"""
Based on the current ranges, calculate the command
Steer will be added to the commanded throttle
throttle will be multiplied by the commanded throttle
"""
steer_action = 0.0
throttle_action = 0.0
if self.is_detected:
# --- Apply steering, proportional to how close is the object
steer_action = -K_LAT_DIST_TO_STEER * self.blob_x
steer_action = saturate(steer_action, -1.5, 1.5)
rospy.loginfo("Steering command %.2f" % steer_action)
throttle_action = 1.0
return (steer_action, throttle_action)
def run(self):
# --- Set the control rate
rate = rospy.Rate(5)
while not rospy.is_shutdown():
# -- Get the control action
steer_action, throttle_action = self.get_control_action()
rospy.loginfo("Steering = %3.1f" % (steer_action))
# -- update the message
self._message.linear.x = throttle_action
self._message.angular.z = steer_action
# -- publish it
self.pub_twist.publish(self._message)
rate.sleep()
if __name__ == "__main__":
rospy.init_node("chase_ball")
chase_ball = ChaseBall()
chase_ball.run()
์ฃผ์์๋ ๋์ ์๋ฏ์ด ์ด node๋
โ /blob/point_blob
๋ฅผ Subscribes ๋ฐ์์
โ Twist ํ์์ผ๋ก ๋ณํํ ํ,
โ /dkcar/control/cmd_vel
์๊ฒ
Publish ํฉ๋๋ค.
- import
import math, time
import rospy
from geometry_msgs.msg import Twist
from geometry_msgs.msg import Point
/blob/point_blob
๋geometry_msgs/Point
ํ์์ ๊ฐ๋๋ฐ์, ์ด๋ป๊ฒ ์๊ฒผ์๊น์?
$ rosmsg show geometry_msgs/Point
float64 x
float64 y
float64 z
๋ค์ด์ค๊ฒ ๋๋ x,y,z ๊ฐ ์ด๋ ํ ์๋ฏธ๋ฅผ ๊ฐ๋์ง๋ ๊ทธ ์๋จ์ธ find_ball์์ ์๋ฏธ๋ฅผ ๊ฐ์ต๋๋ค.
์ง๊ธ์ ์ฐ์ ์ ์ฒด์ ์ธ ํ๋ฆ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
- main & run
def run(self):
# --- Set the control rate
rate = rospy.Rate(5)
while not rospy.is_shutdown():
# -- Get the control action
steer_action, throttle_action = self.get_control_action()
rospy.loginfo("Steering = %3.1f" % (steer_action))
# -- update the message
self._message.linear.x = throttle_action
self._message.angular.z = steer_action
# -- publish it
self.pub_twist.publish(self._message)
rate.sleep()
if __name__ == "__main__":
rospy.init_node("chase_ball")
chase_ball = ChaseBall()
chase_ball.run()
run์ ํ๋จ๋ถ๋ msg๋ฅผ ์ฑ์ฐ๊ณ , publish๋ฅผ ํ๋ ๋ถ๋ถ์ด๊ธฐ์ topic ๊ฐ์๋ฅผ ์๊ฐํ์
จ๋ค๋ฉด ๋ชจ๋
์ดํดํ์ค ์ ์์ผ๋ฉฐ, ์ฐ๋ฆฌ๋ ์๋จ์ get_control_action
ํจ์๋ฅผ ์ค์ ์ ์ผ๋ก ์ดํผ๊ฒ ์ต๋๋ค.
- get_control_action
def get_control_action(self):
"""
Based on the current ranges, calculate the command
Steer will be added to the commanded throttle
throttle will be multiplied by the commanded throttle
"""
steer_action = 0.0
throttle_action = 0.0
if self.is_detected:
# --- Apply steering, proportional to how close is the object
steer_action = -K_LAT_DIST_TO_STEER * self.blob_x
steer_action = saturate(steer_action, -1.5, 1.5)
rospy.loginfo("Steering command %.2f" % steer_action)
throttle_action = 1.0
return (steer_action, throttle_action)
K_LAT_DIST_TO_STEER
๊ฐ ๋ฌด์์ผ๊น์? ์๋ฅผ ๋ค์ด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋ง์๋๋ฆฌ์๋ฉด, blob_x๋ -1 ~ 1์ ๋์ ์ ์๋ ๊ฐ์ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด, K_LAT_DIST_TO_STEER = 2.0
์ธ ํ ์ํฉ์์
โ steer_action๋ -2.0 ~ 2.0์ ๊ฐ์ ๊ฐ์ง๋ค๊ณ ํ ์ ์๋๋ฐ์, ์ด๊ฒ์ saturate์ํค๋ฏ๋ก, ๋ค์
โ -1.5 ~ 1.5์ ๊ฐ์ ๊ฐ๋ steer_action์ ์ป๊ฒ ๋ฉ๋๋ค.
# -- update the message
self._message.linear.x = throttle_action
self._message.angular.z = steer_action
# -- publish it
self.pub_twist.publish(self._message)
์ด steer_action ๊ฐ์ Twist์ ๋ด์ ์ต์ข ์ ์ผ๋ก publishํ๊ฒ ๋๋ฉฐ,
๊ทธ๋ ๋ค๋ฉด K_LAT_DIST_TO_STEER
๋,
์ผ๋ง๋ ๋ฏผ๊ฐํ๊ฒ ์ ฏ์จ ๋ฐ์ค์ ๋ฐ์ํ ๊ฒ์ธ์ง๋ฅผ ๊ฒฐ์ ์ง๋ ์์๋ผ๊ณ ํ ์ ์์ต๋๋ค!
์์ง๊น์ง ํฌ๊ฒ ์๋ฟ์ง ์์ผ์ค ๊ฒ ๊ฐ์๋ฐ์, ์๋๋ฌ ๋ง์ง๋ง find_ball๋ก ๋์ด๊ฐ ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
find_ball
#!/usr/bin/env python
"""
ON THE RASPI: roslaunch raspicam_node camerav2_320x240.launch enable_raw:=true
0------------------> x (cols) Image Frame
|
| c Camera frame
| o---> x
| |
| V y
|
V y (rows)
referenced from tizianofiorenzani/ros_tutorials
url: https://github.com/tizianofiorenzani/ros_tutorials
SUBSCRIBES TO:
/raspicam_node/image: Source image topic
PUBLISHES TO:
/blob/image_blob : image with detected blob and search window
/blob/image_mask : masking
/blob/point_blob : blob position in adimensional values wrt. camera frame
"""
# --- Allow relative importing
if __name__ == "__main__" and __package__ is None:
from os import sys, path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
import sys
import rospy
import cv2
import time
from std_msgs.msg import String
from sensor_msgs.msg import Image
from geometry_msgs.msg import Point
from cv_bridge import CvBridge, CvBridgeError
from include.blob_detector import *
class BlobDetector:
def __init__(
self, thr_min, thr_max, blur=15, blob_params=None, detection_window=None
):
self.set_threshold(thr_min, thr_max)
self.set_blur(blur)
self.set_blob_params(blob_params)
self.detection_window = detection_window
self._t0 = time.time()
self.blob_point = Point()
print(">> Publishing image to topic image_blob")
self.image_pub = rospy.Publisher("/blob/image_blob", Image, queue_size=1)
self.mask_pub = rospy.Publisher("/blob/image_mask", Image, queue_size=1)
print(">> Publishing position to topic point_blob")
self.blob_pub = rospy.Publisher("/blob/point_blob", Point, queue_size=1)
self.bridge = CvBridge()
self.image_sub = rospy.Subscriber("/csi_image", Image, self.callback)
print("<< Subscribed to topic /csi_image")
(...)
def main(args):
pink_min = (135, 41, 95)
pink_max = (255, 196, 255)
green_min = (39, 81, 71)
green_max = (75, 255, 255)
blur = 5
min_size = 10
max_size = 40
# --- detection window respect to camera frame in [x_min, y_min, x_max, y_max] adimensional (0 to 1)
x_min = 0.1
x_max = 0.9
y_min = 0.1
y_max = 0.9
detection_window = [x_min, y_min, x_max, y_max]
params = cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold = 0
params.maxThreshold = 200
# Filter by Area.
params.filterByArea = True
params.minArea = 2000
params.maxArea = 70000 # 640 * 480 = 307,200
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0.1
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.2
# Filter by Inertia
params.filterByInertia = True
params.minInertiaRatio = 0.7
rospy.init_node("blob_detector", anonymous=True)
ic = BlobDetector(green_min, green_max, blur, params, detection_window)
try:
rospy.spin()
except KeyboardInterrupt:
print("Shutting down")
cv2.destroyAllWindows()
if __name__ == "__main__":
main(sys.argv)
์ด๋ฒ ์ฝ๋๋ ๊ธธ์ด๊ฐ ๊ต์ฅํ ๊ธธ์ง์? ๐๐
์ฝ๋ ๋ถ์ ์ ์, ์ฐ๋ฆฌ ์ฌ๋ฏธ์๋ ์ค์ต์ ํ๋ ํด๋ณด์์
Blob Thresholding
$ cd ~/catkin_ws/src/donkey_ros/donkey_cv/include/
$ python range_detector.py --image frame0000.jpg --filter HSV --preview
ํ๋จ์ ์ ๊ฐ ํด๋ ๋ต์ ๋ณด๊ธฐ ์ ์, H, S, V Min/Max ๊ฐ๋ค์ ๊ณ์ํด์ ๋ฐ๊ฟ๊ฐ๋ฉด์, ์ด๋ ํ ๋ณํ๊ฐ ์๊ธฐ๋์ง ์ดํด๋ณด์ธ์!!
-
my_value
HSV๊ฐ ๋ญ๊ฐ์ โโโ
์์์ ํํํ๋ ๋ํ์ ์ธ ๋ฐฉ์์ผ๋ก, ๋ชจ๋๊ฐ ์๊ณ ์๋ RGB๋ฐฉ์์ด ์์ง์? ์ด์ ๋๋ถ์ด,
์์(Hue), ์ฑ๋(Saturation), ๋ช ๋(Value) ์ ๊ฐ์ผ๋ก ์์์ ํํํ๋ ์ฒด๊ณ๊ฐ ์๋ต๋๋ค.
ppt๋ ํฌํ ์ต์ ์ฌ์ฉํ๋ค๋ณด๋ฉด, ์ด๋ ๊ฒ ์์์ ๊ณ ๋ฅด๋ gradation์ ๋ง์ด ๋ณด์ จ์ง์?
๋ฐ๋ก ์ด ๊ฒ์ด Hue/Saturation์ ํด๋นํฉ๋๋ค.
image from : wikipedia
์ด ์ธ๊ฐ์ง ๊ฐ์ ์์ ํ๋ฉด์, ์ ฏ์จ ๋ฐ์ค๋ง์ ๊ฑธ๋ฌ์ฃผ๋ ๋ฒ์๋ฅผ ์ ๋ณํ๊ณ , ์ด๊ฒ์ ํตํด ๋ฌผ์ฒด๋ฅผ ๋ฐ๋ผ๊ฐ๋๋ก ํ ์ ์์๋ ๊ฒ์ ๋๋ค. ๐๐ ์ฝ๋์์์๋ ๋ค์๊ณผ ๊ฐ์ด ํํ๋์์ง์.
green_min = (39, 81, 71)
green_max = (75, 255, 255)
์ฌ๋ฌ๋ถ๋ค๋ ์ง์ ์ฌ์ง์ ์ดฌ์ํ์ฌ ๋ค๋ฅธ ํํฐ๋ฅผ ๋ง๋ค์ด๋ณด์ธ์
$ roscore
$ rosrun csi_camera csi_pub.py
$ rosrun image_view image_view image:=/csi_image