视觉控制结合视觉处理和运动控制 关注两个应用 :

目标跟踪object tracking  和 
 人体跟踪(跟随) person following

坐标系:

相机坐标系
右手坐标系
相机正前方为 z轴正方向
水平方向为 x轴
垂直方向为 y轴
  1. 目标跟踪object tracking 上面 使用 opencv 跟踪 面部 关键点 和 颜色 跟踪的结果为 目标在 图像中的区域 ROI region of interest 感兴趣区域 被发布在 ROS话题 /roi 上,如果相机安装在一个移动地盘上

我们可以使用 ROI 的 x_offset 水平坐标偏置(相对于整个图像) 我们可以旋转 移动底盘保证 ROI 的 x_offset位于图像的水平正中央 (偏左 向左旋转,逆时针; 偏右 向右旋转,顺时针) 如果相机加入垂直方向舵机 还可以 旋转舵机 使得 ROI 的 y_offset 位于 图像的垂直 正中央

还可以使用 roi区域对应的深度信息 控制 移动底盘 前进后者后退

深度较大 前进 深度较小 后退

保持深度值在一个固定的值 关键程序: rbx1_apps/nodes/object_tracker.py

1) 先启动一个深度摄像头或者 普通相机 深度摄像头 1 Microsoft Kinect: $ roslaunch freenect_launch freenect-registered-xyzrgb.launch

深度摄像头 2华硕 Asus Xtion, Xtion Pro, or Primesense 1.08/1.09 cameras: $ roslaunch openni2_launch openni2.launch depth_registration:=true

webcam : $ roslaunch rbx1_vision usb_cam.launch video_device:=/dev/video0 2)启动脸部追踪节点 roslaunch rbx1_vision face_tracker2.launch 运行了节点 /rbx1_vision/node/face_tracker2.py

3)启动目标追踪节点 roslaunch rbx1_apps object_tracker.launch 运行了节点 /rbx1_apps/nodes/object_tracker.py

4)使用 rqt_plot 显示 脸部 位置 和 移动速度信息 rqt_plot /cmd_vel/angular/z

5)仿真环境下测试跟踪效果

$ roslaunch rbx1_bringup fake_turtlebot.launch $ rosrun rviz rviz -d rospack find rbx1_nav/sim.rviz 移动脸部 , turtlebot会旋转 6)代码分析 /rbx1_apps/nodes/object_tracker.py

#!/usr/bin/env python import rospy from sensor_msgs.msg import RegionOfInterest, CameraInfo from geometry_msgs.msg import Twist import thread

class ObjectTracker(): def init(self): rospy.init_node("object_tracker")

# 节点关闭 Set the shutdown function (stop the robot)
    rospy.on_shutdown(self.shutdown)
    
    # 更新频率 How often should we update the robot's motion?
    self.rate = rospy.get_param("~rate", 10)
    r = rospy.Rate(self.rate) 
    
    # 移动底盘最大旋转速度 The maximum rotation speed in radians per second
    self.max_rotation_speed = rospy.get_param("~max_rotation_speed", 2.0)
    
    # 移动底盘最小旋转速度 The minimum rotation speed in radians per second
    self.min_rotation_speed = rospy.get_param("~min_rotation_speed", 0.5)
    
    # Sensitivity to target displacements.  Setting this too high
    # can lead to oscillations of the robot.
    self.gain = rospy.get_param("~gain", 2.0) # 灵敏度,增益 相当于一个比例控制器 
    
    # The x threshold (% of image width) indicates how far off-center
    # the ROI needs to be in the x-direction before we react
    self.x_threshold = rospy.get_param("~x_threshold", 0.1) # 偏移阈值

    # Publisher to control the robot's movement  发布运动信息控制指令
    self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist, queue_size=5)
    
    # Intialize the movement command
    self.move_cmd = Twist() # 初始化 运动信息控制指令
    
    # Get a lock for updating the self.move_cmd values
    self.lock = thread.allocate_lock() # 线程上锁
    
    # We will get the image width and height from the camera_info topic
    self.image_width = 0
    self.image_height = 0
    
    # Set flag to indicate when the ROI stops updating
    self.target_visible = False
    
    # Wait for the camera_info topic to become available 等待
    rospy.loginfo("Waiting for camera_info topic...")
    rospy.wait_for_message('camera_info', CameraInfo)
    
    #订阅话题,获取相机图像信息 Subscribe the camera_info topic to get the image width and height
    rospy.Subscriber('camera_info', CameraInfo, self.get_camera_info, queue_size=1)

    # 等待相机工作正常 Wait until we actually have the camera data
    while self.image_width == 0 or self.image_height == 0:
        rospy.sleep(1)
                
    # 订阅ROI话题 Subscribe to the ROI topic and set the callback to update the robot's motion
# 回调函数为 set_cmd_ve()
    rospy.Subscriber('roi', RegionOfInterest, self.set_cmd_vel, queue_size=1)
    
    # 等待ROI信息 Wait until we have an ROI to follow
    rospy.loginfo("Waiting for messages on /roi...")
    rospy.wait_for_message('roi', RegionOfInterest)
    
    rospy.loginfo("ROI messages detected. Starting tracker...")
    
    # 开始跟踪循环====
    while not rospy.is_shutdown():
        # Acquire a lock while we're setting the robot speeds
        self.lock.acquire() # 多线程锁
        
        try:
            # If the target is not visible, stop the robot
            if not self.target_visible:
                self.move_cmd = Twist()# 视野中未看到目标,不动
            else:
                # Reset the flag to False by default
                self.target_visible = False
                
            # Send the Twist command to the robot
            self.cmd_vel_pub.publish(self.move_cmd)# 发布运动指令
            
        finally:
            # Release the lock
            self.lock.release()
            
        # Sleep for 1/self.rate seconds
        r.sleep()
    
#  订阅ROI话题  的回调函数=================
def set_cmd_vel(self, msg):
    # Acquire a lock while we're setting the robot speeds
    self.lock.acquire()
    
    try:
        # If the ROI has a width or height of 0, we have lost the target
        if msg.width == 0 or msg.height == 0:
            self.target_visible = False # 目标不可见
            return
        
        # If the ROI stops updating this next statement will not happen
        self.target_visible = True # 目标可见

        # Compute the displacement of the ROI from the center of the image
    # roi 中心 和 图像 中心的 水平方向偏移量=======================
        target_offset_x = msg.x_offset + msg.width / 2 - self.image_width / 2
        
    # 计算一个偏移比例=============
        try:
            percent_offset_x = float(target_offset_x) / (float(self.image_width) / 2.0)
        except:
            percent_offset_x = 0

        # Rotate the robot only if the displacement of the target exceeds the threshold
    # 直到 偏移比例 小于阈值=====
        if abs(percent_offset_x) > self.x_threshold:
            # Set the rotation speed proportional to the displacement of the target
            try:
                speed = self.gain * percent_offset_x # 相当于一个比例控制器 
                if speed < 0:
                    direction = -1  方向
                else:
                    direction = 1
	    # 旋转角速度
                self.move_cmd.angular.z = -direction * max(self.min_rotation_speed,
                                            min(self.max_rotation_speed, abs(speed)))
            except:
                self.move_cmd = Twist()
        else:
            # Otherwise stop the robot
            self.move_cmd = Twist()

    finally:
        # Release the lock
        self.lock.release()

def get_camera_info(self, msg):
    self.image_width = msg.width
    self.image_height = msg.height

def shutdown(self):
    rospy.loginfo("Stopping the robot...")
    self.cmd_vel_pub.publish(Twist())
    rospy.sleep(1)

if name == 'main': try: ObjectTracker() rospy.spin() except rospy.ROSInterruptException: rospy.loginfo("Object tracking node terminated.") 2.目标跟随, 在目标跟踪上 引入深度信息 可旋转 前进 后退 可以使用 face tracker, CamShift or LK tracker 节点 发现目标

在 rbx1_apps/nodes/object_follower.py

/roi 话题 获取 目标 水平 和 垂直方向 的位置 让相机正对着 目标中央

/camera/depth_registered/image_rect 深度图 话题 消息类型 sensor_msgs/Image 深度单位 mm毫米 除以1000 得到米m 回调函数 使用 cv_bridge 将深度图 转换成 数组 可以计算 ROI区域的均值 还可以使用 pcl 点运数据

计算 roi 区域 的平均深度 来反应 目标物体 在 相机前方的距离 通过使移动底盘 前进、后退 保持一个给定的距离 (注意相机 和 底盘的安装 位置) 6)代码分析

/rbx1_apps/nodes/object_follower.py

#!/usr/bin/env python import rospy from sensor_msgs.msg import Image, RegionOfInterest, CameraInfo from geometry_msgs.msg import Twist from math import copysign, isnan from cv2 import cv as cv from cv_bridge import CvBridge, CvBridgeError import numpy as np import thread

class ObjectFollower(): def init(self): # 节点初始化 rospy.init_node("object_follower")

# 节点关闭 析构函数 清理 Set the shutdown function (stop the robot)
    rospy.on_shutdown(self.shutdown)
    
    # How often should we update the robot's motion?
    self.rate = rospy.get_param("~rate", 10)
    
    r = rospy.Rate(self.rate)
    
    # 感兴趣区域 Scale the ROI by this factor to avoid background distance values around the edges
    self.scale_roi = rospy.get_param("~scale_roi", 0.9)
    
    # The max linear speed in meters per second
    self.max_linear_speed = rospy.get_param("~max_linear_speed", 0.3)
    
    # The minimum linear speed in meters per second
    self.min_linear_speed = rospy.get_param("~min_linear_speed", 0.02) 
    
    # The maximum rotation speed in radians per second
    self.max_rotation_speed = rospy.get_param("~max_rotation_speed", 2.0)
    
    # The minimum rotation speed in radians per second
    self.min_rotation_speed = rospy.get_param("~min_rotation_speed", 0.5)
    
    # The x threshold (% of image width) indicates how far off-center
    # the ROI needs to be in the x-direction before we react
    self.x_threshold = rospy.get_param("~x_threshold", 0.1)
    
    # How far away from the goal distance (in meters) before the robot reacts
    self.z_threshold = rospy.get_param("~z_threshold", 0.05)
    
    # The maximum distance a target can be from the robot for us to track
    self.max_z = rospy.get_param("~max_z", 1.6)

    # The minimum distance to respond to
    self.min_z = rospy.get_param("~min_z", 0.5)
    
    # The goal distance (in meters) to keep between the robot and the person
    self.goal_z = rospy.get_param("~goal_z", 0.75)

    # How much do we weight the goal distance (z) when making a movement
    self.z_scale = rospy.get_param("~z_scale", 0.5)

    # How much do we weight (left/right) of the person when making a movement        
    self.x_scale = rospy.get_param("~x_scale", 2.0)
    
    # Slow down factor when stopping
    self.slow_down_factor = rospy.get_param("~slow_down_factor", 0.8)
    
    # Initialize the global ROI
    self.roi = RegionOfInterest()

    # Publisher to control the robot's movement
    self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist, queue_size=5)
    
    # Intialize the movement command
    self.move_cmd = Twist()
    
    # Get a lock for updating the self.move_cmd values
    self.lock = thread.allocate_lock()
    
    # We will get the image width and height from the camera_info topic
    self.image_width = 0
    self.image_height = 0
    
    # We need cv_bridge to convert the ROS depth image to an OpenCV array
    self.cv_bridge = CvBridge()
    self.depth_array = None
    
    # Set flag to indicate when the ROI stops updating
    self.target_visible = False
    
    # Wait for the camera_info topic to become available
    rospy.loginfo("Waiting for camera_info topic...")
    
    rospy.wait_for_message('camera_info', CameraInfo)
    
    # Subscribe to the camera_info topic to get the image width and height
    rospy.Subscriber('camera_info', CameraInfo, self.get_camera_info, queue_size=1)

    # Wait until we actually have the camera data
    while self.image_width == 0 or self.image_height == 0:
        rospy.sleep(1)
        
    # Wait for the depth image to become available
    rospy.loginfo("Waiting for depth_image topic...")
    
    rospy.wait_for_message('depth_image', Image)
                
    # Subscribe to the depth image
# 订阅深度图 话题 回调函数  convert_depth_image() 深度数据/1000 转换成 数组
# 话题名  在 launch 里 需要remap 重映射  
    self.depth_subscriber = rospy.Subscriber("depth_image", Image, self.convert_depth_image, queue_size=1)
    
    # Subscribe to the ROI topic and set the callback to update the robot's motion
    rospy.Subscriber('roi', RegionOfInterest, self.set_cmd_vel, queue_size=1)
    
    # Wait until we have an ROI to follow
    rospy.loginfo("Waiting for an ROI to track...")
    
    rospy.wait_for_message('roi', RegionOfInterest)
    
    rospy.loginfo("ROI messages detected. Starting follower...")
    
    # Begin the tracking loop
    while not rospy.is_shutdown():
        # Acquire a lock while we're setting the robot speeds
        self.lock.acquire()
        
        try:
            if not self.target_visible:
                # If the target is not visible, stop the robot smoothly
                self.move_cmd.linear.x *= self.slow_down_factor
                self.move_cmd.angular.z *= self.slow_down_factor
            else:
                # Reset the flag to False by default
                self.target_visible = False
                
            # Send the Twist command to the robot
            self.cmd_vel_pub.publish(self.move_cmd)
                
        finally:
            # Release the lock
            self.lock.release()
        
        # Sleep for 1/self.rate seconds
        r.sleep()

利用 ROI消息 和 深度数据 转换到 速度指令

def set_cmd_vel(self, msg):
    # Acquire a lock while we're setting the robot speeds
    self.lock.acquire()
    
    try:
        # If the ROI has a width or height of 0, we have lost the target
        if msg.width == 0 or msg.height == 0:
            self.target_visible = False
            return
        else:
            self.target_visible = True

        self.roi = msg
                    
        # Compute the displacement of the ROI from the center of the image
    # 原来 图像中心点 self.image_width / 2 水平方向
        # 目标区域 中心点 = 起点 + roi宽度  = msg.x_offset + msg.width / 2
    # 两者之差 为 目标 偏离相机中心为插值 
        target_offset_x = msg.x_offset + msg.width / 2 - self.image_width / 2

        try:
            percent_offset_x = float(target_offset_x) / (float(self.image_width) / 2.0)
        except:
            percent_offset_x = 0
                                        
        # Rotate the robot only if the displacement of the target exceeds the threshold
        if abs(percent_offset_x) > self.x_threshold: #设置阈值
            # Set the rotation speed proportional to the displacement of the target
            speed = percent_offset_x * self.x_scale
            self.move_cmd.angular.z = -copysign(max(self.min_rotation_speed,
                                        min(self.max_rotation_speed, abs(speed))), speed)
        else:
            self.move_cmd.angular.z = 0
         
    # 计算目标 深度信息   
        # Now compute the depth component
        
        # Initialize a few depth variables
        n_z = sum_z = mean_z = 0
         
        # Shrink the ROI slightly to avoid the target boundaries
    # 缩小 roi范围 精准  滤出边缘背景点 因子 0.9  roi是长方形 物体形状不规则
        scaled_width = int(self.roi.width * self.scale_roi)
        scaled_height = int(self.roi.height * self.scale_roi)
        # ROI像素坐标范围
        # Get the min/max x and y values from the scaled ROI
        min_x = int(self.roi.x_offset + self.roi.width * (1.0 - self.scale_roi) / 2.0)
        max_x = min_x + scaled_width
        min_y = int(self.roi.y_offset + self.roi.height * (1.0 - self.scale_roi) / 2.0)
        max_y = min_y + scaled_height
        # 得到 ROI区域深度信息 的均值
        # Get the average depth value over the ROI
        for x in range(min_x, max_x):
            for y in range(min_y, max_y):
                try:
                    # Get a depth value in meters
                    z = self.depth_array[y, x] #对应坐标的深度值
                    
                    # Check for NaN values returned by the camera driver
                    if isnan(z):# 验证是否存在
                        continue
                                               
                except:
                    # It seems to work best if we convert exceptions to 0
                    z = 0
                    
                # A hack to convert millimeters to meters for the freenect driver
	    # 毫米转换到 米m
                if z > 100:
                    z /= 1000.0
                    
                # Check for values outside max range
                if z > self.max_z: #滤除 异常值
                    continue
                
                # Increment the sum and count
                sum_z = sum_z + z # 深度值 纸盒
                n_z += 1# 深度值 点数计数 用来计算均值 
                
        # Stop the robot's forward/backward motion by default
        linear_x = 0 # 先停止前进后退 
        
        # If we have depth values...
        if n_z:#有记录到 有效的点
            mean_z = float(sum_z) / n_z #深度均值
                                                                                            
            # Don't let the mean fall below the minimum reliable range
            mean_z = max(self.min_z, mean_z)# 最小距离限制
                                                    
            # Check the mean against the minimum range
            if mean_z > self.min_z:
                # Check the max range and goal threshold
	    # 均值深度 在最小值和最大值之间 并且 均值深度值和 目标深度差值超过阈值  
                # 向前向后移动
                if mean_z < self.max_z and (abs(mean_z - self.goal_z) > self.z_threshold):
                    speed = (mean_z - self.goal_z) * self.z_scale
                    linear_x = copysign(min(self.max_linear_speed, max(self.min_linear_speed, abs(speed))), speed)

        if linear_x == 0:
            # Stop the robot smoothly
            self.move_cmd.linear.x *= self.slow_down_factor
        else:
            self.move_cmd.linear.x = linear_x
    
    finally:
        # Release the lock
        self.lock.release()#释放进程锁

# 深度图 话题 订阅 的回调函数  用cv_bridge 转换 深度图 到 深度数据数组
def convert_depth_image(self, ros_image):
    # Use cv_bridge() to convert the ROS image to OpenCV format
    try:
        # Convert the depth image using the default passthrough encoding
    # 转换	     
        depth_image = self.cv_bridge.imgmsg_to_cv2(ros_image, "passthrough")
    except CvBridgeError, e:
        print e

    # Convert the depth image to a Numpy array
    self.depth_array = np.array(depth_image, dtype=np.float32)

def get_camera_info(self, msg):
    self.image_width = msg.width
    self.image_height = msg.height

def shutdown(self):
    rospy.loginfo("Stopping the robot...")
    # Unregister the subscriber to stop cmd_vel publishing
    self.depth_subscriber.unregister()
    rospy.sleep(1)
    # Send an emtpy Twist message to stop the robot
    self.cmd_vel_pub.publish(Twist())
    rospy.sleep(1)

if name == 'main': try: ObjectFollower() rospy.spin() except rospy.ROSInterruptException: rospy.loginfo("Object follower node terminated.") 运行 1)传感器

深度摄像头 1 Microsoft Kinect: $ roslaunch freenect_launch freenect-registered-xyzrgb.launch

深度摄像头 2华硕 Asus Xtion, Xtion Pro, or Primesense 1.08/1.09 cameras: $ roslaunch openni2_launch openni2.launch depth_registration:=true

双面摄像头获取深度 可编写 2)启动脸部追踪节点

roslaunch rbx1_vision face_tracker2.launch 运行了节点 /rbx1_vision/node/face_tracker2.py 或者 颜色 追踪 roslaunch rbx1_vision camshift.launch 鼠标框选 有颜色的物体 或者 特征点 光流法追踪 roslaunch rbx1_vision lk_tracker.launch 鼠标框选 任意有结构纹理角点线的物体

3)启动目标跟随节点

roslaunch rbx1_apps object_follower.launch
运行了节点 /rbx1_apps/nodes/object_flower.py 4)使用 rqt_plot 显示 脸部 位置 和 移动速度信息

rqt_plot /cmd_vel/angular/z 5)仿真环境下测试跟踪效果

$ roslaunch rbx1_bringup fake_turtlebot.launch $ rosrun rviz rviz -d rospack find rbx1_nav/sim.rviz 移动脸部 / 有颜色物体 , turtlebot会旋转

走进 机器人退后 离开机器人跟随

深度距离object_follower.launch默认 0.7 米 可修改 3. 人体跟随 使用 PCL点运数据 ROS sensor_msgs 包 定义了一个类 PointCloud2 消息类型 和一个 point_cloud2.py 模块 由 点云数据 获取 深度

我们不知道 人体的确切形状 但是可是 找一个像人一样的 团块 距离相机 坐标系 一定距离的 空间范围内的点 避免认错 家具的一部分 椅子腿

对于深度相机 z轴 代表了深度信息 里人体的远近 1】 太远 前进 太近 后退 2】 靠左 左转 靠右 右转 /rbx1_apps/nodes/follower.py

代码分析

#!/usr/bin/env python

import rospy from roslib import message from sensor_msgs import point_cloud2 from sensor_msgs.msg import PointCloud2 from geometry_msgs.msg import Twist from math import copysign

class Follower(): def init(self): # 节点初始化 rospy.init_node("follower")

# 节点关闭 清理内存 Set the shutdown function (stop the robot)
    rospy.on_shutdown(self.shutdown)
    
    # The dimensions (in meters) of the box in which we will search
    # for the person (blob). These are given in camera coordinates
    # where x is left/right,y is up/down and z is depth (forward/backward)
    # 近似 人体的 参数
# 人体点在空间 相对于 移动底盘的 空间坐标点集 范围  所有的点云在这个范围内的 认为是人体的点 
    self.min_x = rospy.get_param("~min_x", -0.2)
    self.max_x = rospy.get_param("~max_x", 0.2)
    self.min_y = rospy.get_param("~min_y", -0.3)
    self.max_y = rospy.get_param("~max_y", 0.5)
    self.max_z = rospy.get_param("~max_z", 1.2)
    
    # The goal distance (in meters) to keep between the robot and the person
    self.goal_z = rospy.get_param("~goal_z", 0.6)
    
    # How far away from the goal distance (in meters) before the robot reacts
    self.z_threshold = rospy.get_param("~z_threshold", 0.05)
    
    # How far away from being centered (x displacement) on the person
    # before the robot reacts
    self.x_threshold = rospy.get_param("~x_threshold", 0.05)
    
    # 深度差值 转出 前进后退运动速度 权重 How much do we weight the goal distance (z) when making a movement
    self.z_scale = rospy.get_param("~z_scale", 1.0)

    # 水平差值 转成旋转运动速度 权重  How much do we weight left/right displacement of the person when making a movement        
    self.x_scale = rospy.get_param("~x_scale", 2.5)
    
    # The maximum rotation speed in radians per second
    self.max_angular_speed = rospy.get_param("~max_angular_speed", 2.0)
    
    # The minimum rotation speed in radians per second
    self.min_angular_speed = rospy.get_param("~min_angular_speed", 0.0)
    
    # The max linear speed in meters per second
    self.max_linear_speed = rospy.get_param("~max_linear_speed", 0.3)
    
    # The minimum linear speed in meters per second
    self.min_linear_speed = rospy.get_param("~min_linear_speed", 0.1)
    
    # Slow down factor when stopping
    self.slow_down_factor = rospy.get_param("~slow_down_factor", 0.8)
    
    # Initialize the movement command
    self.move_cmd = Twist()

    # 发布 控制质量 消息话题 Publisher to control the robot's movement
    self.cmd_vel_pub = rospy.Publisher('cmd_vel', Twist, queue_size=5)

    # 订阅点云数据 回调函数 set_cmd_vel()  Subscribe to the point cloud
    self.depth_subscriber = rospy.Subscriber('point_cloud', PointCloud2, self.set_cmd_vel, queue_size=1)

    rospy.loginfo("Subscribing to point cloud...")
    
    # Wait for the pointcloud topic to become available
    rospy.wait_for_message('point_cloud', PointCloud2)

    rospy.loginfo("Ready to follow!")
    
def set_cmd_vel(self, msg):
    # Initialize the centroid coordinates point count
    x = y = z = n = 0

    # Read in the x, y, z coordinates of all points in the cloud
    for point in point_cloud2.read_points(msg, skip_nans=True):#遍历 所有的三维 点云数据
        pt_x = point[0]
        pt_y = point[1]
        pt_z = point[2]
        
        # Keep only those points within our designated boundaries and sum them up
    # 在相对相机 特定范围内的点 认为是 人体 点
        if -pt_y > self.min_y and -pt_y < self.max_y and pt_x < self.max_x and pt_x > self.min_x and pt_z < self.max_z:
            x += pt_x # 所有人体点 哥哥坐标值 求和取均值
            y += pt_y
            z += pt_z
            n += 1
    
   # If we have points, compute the centroid coordinates
   # 计算 点云坐标 均值
    if n:
        x /= n 
        y /= n 
        z /= n
        
        # Check our movement thresholds
        # 深度距离差值 超过最小值阈值 进行 前进/后退 移动
        if (abs(z - self.goal_z) > self.z_threshold):
            # Compute the angular component of the movement
            linear_speed = (z - self.goal_z) * self.z_scale # 乘上 权重系数
            
            # Make sure we meet our min/max specifications
            self.move_cmd.linear.x = copysign(max(self.min_linear_speed, 
                                    min(self.max_linear_speed, abs(linear_speed))), linear_speed)
        else:
            self.move_cmd.linear.x *= self.slow_down_factor # 线速度 缓慢  减速  
            
        if (abs(x) > self.x_threshold): # x值为水平方向 x=0 为相机光心 坐标值 过大 就偏转了 
            # Compute the linear component of the movement
            angular_speed = -x * self.x_scale
            
            # Make sure we meet our min/max specifications
            # 最小最大角速度 限制
            self.move_cmd.angular.z = copysign(max(self.min_angular_speed, 
                                    min(self.max_angular_speed, abs(angular_speed))), angular_speed)
        else:
            # Stop the rotation smoothly
            self.move_cmd.angular.z *= self.slow_down_factor # 角速度 缓慢减速
            
    else: #未找到 人体点 
        # Stop the robot smoothly 缓慢减速
        self.move_cmd.linear.x *= self.slow_down_factor
        self.move_cmd.angular.z *= self.slow_down_factor
        
    # Publish the movement command
    self.cmd_vel_pub.publish(self.move_cmd)

    
def shutdown(self):
    rospy.loginfo("Stopping the robot...")
    
    # Unregister the subscriber to stop cmd_vel publishing
    self.depth_subscriber.unregister()
    rospy.sleep(1)
    
    # Send an emtpy Twist message to stop the robot
    self.cmd_vel_pub.publish(Twist())
    rospy.sleep(1)

if name == 'main': try: Follower() rospy.spin() except rospy.ROSInterruptException: rospy.loginfo("Follower node terminated.") 实验 1)传感器

深度摄像头 1 Microsoft Kinect: $ roslaunch freenect_launch freenect-registered-xyzrgb.launch

深度摄像头 2华硕 Asus Xtion, Xtion Pro, or Primesense 1.08/1.09 cameras: $ roslaunch openni2_launch openni2.launch depth_registration:=true

双面摄像头获取深度 可编写 2)启动 人体跟随节点

roslaunch rbx1_apps follower.py 5)仿真环境下测试 人体 跟踪效果

$ roslaunch rbx1_bringup fake_turtlebot.launch $ rosrun rviz rviz -d rospack find rbx1_nav/sim.rviz 走进 机器人退后 离开机器人跟随 深度距离object_follower.launch默认 0.7 米 可修改