人类从外界环境接收的信息80%都来源于眼睛,那对于机器人而言,该如何通过视觉传感器对外界环境进行认知呢?今天我们就一起来学习学习。

移动机器人可以通过摄像头这类典型的视觉传感器,获取外界环境的数字图像信息,并以此对外界环境进行认知。

1.什么是数字图像

        通过摄像头获取的图像可以定义为一个二维函数f(x,y),其中x和y是空间坐标,而f在任意一对坐标(x,y)处的幅度称为该点处图像的亮度或灰度。灰度是用来表示黑白图像亮度的一个术语,而彩色图像是由单个二维图像组合形成的。

       例如,在RGB彩色系统中,一副彩色图像是由三幅独立的分量图像(红,绿,蓝)组成。因此,许多为黑白图像处理开发的技术依然适用于彩色图像处理,方法是分别处理三幅独立的分量图像即可。

       当x,y和f的幅值都是有限的离散值时,称该图像为数字图像。数字图像是由有限数量元素组成的,每个元素都有一个特殊的位置和数值,这些元素就是我们常听说的“像素”。

       但是,图像关于x和y坐标以及振幅都是连续的,要怎么才能让这些参数变为离散的呢?答案是,数字化坐标和幅值。对坐标数字化称为取样,对幅值数字化称为量化。一般我们为这些离散坐标取用整数值,这种量化方法也称为均匀量化。

       由于人类的视觉被限制在可见光波段,即对彩色的感知是由可见光波段内某几种或多种波段的二维图像组合而成,而市面上的视觉传感器种类繁多,成像所选取的波段也很多。

       机器人获取了f的x,y分量和振幅都是有限和离散的数字图像之后,如何对其处理呢?能用来干嘛呢?

2.什么是彩色模型

        我们人所认知的世界是五颜六色的,且知道这些颜色是通过光的三原色组合而成。那么机器人是怎么来描述我们这个五颜六色的世界呢?

        如前面所解释的那样,一张三维的彩色图像可以通过三张不同的二维图像所构成,机器人就是通过这样的彩色图像去认知世界。前面所说的RGB图像是众多彩色图像中的一种,我们称之为RGB彩色模型(又称为彩色空间)。RGB彩色模型如下所示:

        除了RGB彩色模型,还有NTSC、YCbCr、HSV、CMY、CMYK和HSI彩色模型,不同的彩色模型在不同的场景下作用不一样,不同的彩色空间之间可以相互转换。下面我们讲讲用的比较多的HSV彩色模型,HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种彩色空间, 也称六角锥体模型(Hexcone Model)。HSV彩色模型比RGB彩色模型更接近于人类的经验和对彩色的感知,HSV彩色模型如下所示。

        我们这里主要学习机器人如何通过RGB图像对纯色目标物体进行识别和跟踪。

3.机器人目标识别与跟踪

        首先,需要选择一个纯色目标物,为了方便获取,我们打印了一张红色图片,并将其粘贴在硬纸板上,如下图所示。

       

       接下来,打开机器人的摄像头,拍摄一张含有目标物的图像,再使用OpenCV读取出目标物的RGB值。

2.1读取目标RGB

       各种摄像头的打开方式不同,我们这里以Intel的RealSense为例,配置好RealSense之后,在ROS环境中运行如下命令启动摄像头:

$ roslaunch realsense2_camera rs_rgbd.launch

       使用rqt_image_graph命令查看当前图像,并将图像保存成图片文件,运行如下python程序对目标物体的RGB值进行读取。

# -*- coding:utf-8 -*-    
import cv2

img = cv2.imread('paper_image.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

def mouse_click(event, x, y, flags, para):
    if event == cv2.EVENT_LBUTTONDOWN:  # 左边鼠标点击
        #print('PIX:', x, y)
        print("BGR:", img[y, x])
        #print("GRAY:", gray[y, x])
        #print("HSV:", hsv[y, x])

if __name__ == '__main__':
    cv2.namedWindow("img")
    cv2.setMouseCallback("img", mouse_click)
    while True:
        cv2.imshow('img', img)

        cv2.imread(imgy)表示读取一张图像。img是读取图像的文件名,同文件夹下可以直接写文件名,不同文件夹下则需要写绝对路径;y表示读取灰度图像、彩色图像、还是包含图像的alpha通道,默认读取彩色图像。

       程序运行结果如下图所示,可以打印出鼠标点击位置像素的BGR值。

2.2设置RGB识别范围

        获取了目标物体的RGB大致范围,接下来就是通过OpenCV对此物体进行检测了。由于光照等因素会影响RGB的值,所以我们需要根据实际情况设定一个范围。例如,根据上图识别结果,我们设定如下RGB范围:

BLUE_LOW   = 20
BLUE_HIGH  = 100
GREEN_LOW  = 30
GREEN_HIGH = 100
RED_LOW    = 120
RED_HIGH   = 255

2.3 目标识别

        然后订阅摄像头发布的图像话题,通过cv_bridgeROS图像数据转换成OpenCV的图像格式,接下来就可以使用OpenCV做图像识别了。

核心代码如下:

# find the colors within the specified boundaries and apply the mask
mask = cv2.inRange(cv_image, lower, upper)
output = cv2.bitwise_and(cv_image, cv_image, mask = mask)

cvImg = cv2.cvtColor(output, 6) #cv2.COLOR_BGR2GRAY
npImg = np.asarray( cvImg )
thresh = cv2.threshold(npImg, 1, 255, cv2.THRESH_BINARY)[1]

# find contours in the thresholded image
img, cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
#cnts = cnts[0]

# loop over the contours
for c in cnts:
    # compute the center of the contour
    M = cv2.moments(c)

    if int(M["m00"]) not in range(500, 22500):
        continue

    cX = int(M["m10"] / M["m00"])
    cY = int(M["m01"] / M["m00"])
    
    cv2.drawContours(cv_image, [c], -1, (0, 0, 255), 2)
    cv2.circle(cv_image, (cX, cY), 1, (0, 0, 255), -1)
    objPose = Pose()
    objPose.position.x = cX;
    objPose.position.y = cY;
    objPose.position.z = M["m00"];
    self.target_pub.publish(objPose)
    print(objPose)

         mask为检测到的目标物体,M[“m00”]表示的是检测到目标物体的面积,cX,cY表示检测到目标物体中心点的像素坐标。此程序会将目标物体的中心坐标和面积以object_detect_pose的话题名发布出去;将目标物体在图像中标明并以object_detect_image的话题名发布出去,可以通过rqt_image_view查看。

        现在,我们已经能够获取目标物体的中心像素坐标和面积,接下来设置位姿相关的速度指令就可以实现机器人对目标物体的跟踪了。

 2.4 机器人目标跟踪

        首先,需要确定机器人与目标物体之间的距离,我们通过上述检测方法可以确定一定距离下目标物体的面积。当检测到的目标物体面积大于这个值时,说明机器人距离目标物体近了,需要朝着目标物体后退;当检测到的目标物体面积小于这个值时,说明机器人距离目标物体远了,需要朝着目标物体前进;当检测到的目标物体中心点坐标X轴分量小于图像X轴中心,说明机器人距离目标物体偏左,需要朝着目标物体左转;当检测到的目标物体中心点坐标X轴分量大于图像X轴中心,说明机器人距离目标物体偏左,需要朝着目标物体左转。

设定如下表所示:

状态 面积大于设定面积 面积小于设定面积 中心X轴分量<图像X轴中心 中心X轴分量>图像X轴中心
命令 后退 前进 左转 右转

实现此功能的代码如下图所示:

def velctory(self,Pose):

    X = Pose.position.x;
    Y = Pose.position.y;
    Z = Pose.position.z;

    if Z >= 12000 and Z <= 12100 :                            
        vel = Twist()

    elif Z < 12000 :
        vel = Twist()
        vel.linear.x = (1.0 - Z/12000) * 0.4
 
    elif Z > 12100:
        vel = Twist()
        vel.linear.x = (1.0 - Z/12000) * 0.4
    else:
        print("No Z,cannot control!")
    self.vel_pub.publish( vel )
    rospy.loginfo("Publsh velocity command[{} m/s, {} rad/s]".format(vel.linear.x, vel.angular.z))

    if X > 310 and X < 330 :
        vel = Twist()
    elif X < 310 :
        vel = Twist()
        vel.angular.z = (1.0 - X/320) * 0.4
        
    elif X > 330 :
        vel = Twist()
        vel.angular.z = (1.0 - X/320) * 0.4

    else:
        print("No X,cannot control!")
    self.vel_pub.publish(vel)
    rospy.loginfo("Publsh velocity command[{} m/s, {} rad/s]".format(vel.linear.x, vel.angular.z))

        Z表示识别到目标物体的面积,X表示目标物体中心X轴分量。为了让机器人较稳定的运行,设定的是一个范围而不是一个准确的值。

        理论都分析完了,以LIMO移动机器人为实验平台,让我们来看看实际的运行效果吧:

        这里只介绍了一种较为简单的数字图像处理方法,机器人还可以使用更多数字图像处理算法实现更为复杂的应用功能,大家可以去查找相关书籍和资料,将其与ROS结合,实现更多可能。

        科技创造无限可能,机器人等你来探索!!!