地瓜机器人 · 2022年12月09日 · 江苏

智能搬运机器人系列之利用X3派实现目标物块的识别与抓取

原理讲解

1.gif

根据目标物块的特征,首先通过机械结构使目标物块每次被识别时,目标物块都出现在摄像头的固定角度,固定距离。这样就保证了摄像头每次识别目标物块时,目标物块都会出现在拍摄照片的固定像素范围。

其次,根据目标物块的特点(物块之间只有颜色差异)只要完成对目标物块出现范围的像素点颜色的识别,就能判断出具体是哪一个目标物块。所以采用了对特定区域像素点的颜色识别算法。从演示视频中可以看出,每次抓取物块之前,都会先通过U型推手把目标物块先固定在车身正前方的U型推手内如下图。

2.png

此时补光灯进行补光,以减少环境光对识别结果的影响。从图片也可以看出,识别环节,摄像头是正对目标物块,而且距离很近,这个设计就保证了摄像头拍摄到的大部分像素点都被目标物块的颜色所填满,增加了识别面积。

3.png

可以看到,摄像头拍摄到的图片,目标物块几乎填满了整个图片。接下来就通过旭日X3派进行颜色识别,使用旭日X3派借助OpenCV,通过HSV颜色模型,实现对目标物块的颜色识别。

4.jfif

颜色识别

  • 导入需要用的库
import cv2 as cv
import time
import numpy as np
import sys
import os
import serial
import serial.tools.list_ports
  • 设置串口各种参数,波特率设置为115200,使用40PIN中的UART3
os.system('ls /dev/tty[a-zA-Z]*')
uart_dev= '/dev/ttyS3' #定义串口端口
baudrate = 115200 #波特率
ser = serial.Serial(uart_dev, int(baudrate), timeout=1)
  • 选择8号相机用作视频获取
cap_follow = cv.VideoCapture(8)

剪切获取到的图像,只显示和处理一正中小块

ret, frame = cap_color.read()
#cv.imshow("frame", frame)#代码在电脑上测试时候用于观察,放在X3派上要注释掉
ROI = frame[50:150, 50:200]#get useful ROI

获取一帧图片并进行裁剪,只保留小部分目标物块的像素点,这有两个原因:

(1)获取到的一整帧图片周围有非目标物块的周围环境,如果纳入计算过程的话会影响到最终识别结果

(2)缩小图片体积,可以减少CPU负载,提升运算速度

  • 把截取后的图片转化成HSV颜色模型,并创建三个数组分别用于存放转化后HSV模型图片中每一个像素点的H、S、V通道的值
hsv = cv.cvtColor(ROI, cv.COLOR_BGR2HSV)
#cv.imshow("hsv", hsv)
color_h = []
color_s = []
color_v = []
  • 把转化为HSV模型的图片中每一个像素点都取出来,相加以后取平均值(取平均值是为了减少噪点对最后结果的影响。再把取平均值后的H、S、V三个通道的值赋给新的变量用于最后的比较)
color_h.append(np.mean(hsv[:,:,0]))
color_s.append(np.mean(hsv[:,:,1]))
color_v.append(np.mean(hsv[:,:,2]))
h = color_h[0]
s = color_s[0]
v = color_v[0]
  • 比较最终值和颜色范围,确定识别结果,并通过串口把结果发送给下位机
if 35 <= h <= 77 and 43 <= s <= 255 and 46 <= v <= 255:
    print('green')
    ser.write(b'g')
#red_h 10 --> 20
elif 0 <= h <= 20 and 43 <= s <= 255 and 46 <= v <= 255:
    print('red')
    ser.write(b'r')
elif 156 <= h <= 180 and 43 <= s <= 255 and 46 <= v <= 255:
    print('red')
elif 100 <= h <= 124 and 43 <= s <= 255 and 46 <= v <= 255:
    print('blue')
    ser.write(b'b')
elif 0 <= h <= 180 and 0 <= s <= 255 and 0 <= v <= 46:
    print('black')
    ser.write(b'B')
#white_v 221 --> 200
elif 0 <= h <= 180 and 0 <= s <= 30 and 180 <= v <= 255:
    print('white')
    ser.write(b'w')
else:
    print('I do not know')
    ser.write(b'e')

解释一下串口发送字符的含义:

g——green

r——red

b——blue

B——black

w——write

e——error

(最后'e'一个表示识别的颜色不在既定范围内)

为什么选用使用HSV颜色模型而不是用RGB?

RGB 是我们接触最多的颜色空间,由三个通道表示一幅图像,分别为红色(R),绿色(G)和蓝色(B)。这三种颜色的不同组合可以形成几乎所有的其他颜色。但是人眼对于这三种颜色分量的敏感程度是不一样的,在单色中,人眼对红色最不敏感,蓝色最敏感,所以 RGB 颜色空间是一种均匀性较差的颜色空间。如果颜色的相似性直接用欧氏距离来度量,其结果与人眼视觉会有较大的偏差。对于某一种颜色,我们很难推测出较为精确的三个分量数值来表示。所以,RGB 颜色空间适合于显示系统,却并不适合于图像处理。

在图像处理中使用较多的是 HSV 颜色空间,它比 RGB 更接近人们对彩色的感知经验,可非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比(详细解释可参见地平线开发者社区。

在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。

HSV 表达彩色图像的方式由三个部分组成:Hue(色调、色相)、Saturation(饱和度、色彩纯净度)、Value(明度)。用下图圆柱体来表示 HSV 颜色空间,圆柱体的横截面可以看做是一个极坐标系 ,H 用极坐标的极角表示,S 用极坐标的极轴长度表示,V 用圆柱中轴的高度表示。

5.jfif

Hue 用角度度量,取值范围为0~360°,表示色彩信息,即所处的光谱颜色的位置,表示如下:

6.jfif

颜色圆环上所有的颜色都是光谱上的颜色,从红色开始按逆时针方向旋转,Hue=0 表示红色,Hue=120 表示绿色,Hue=240 表示蓝色等等。在 GRB中 颜色由三个值共同决定,比如黄色为即(255,255,0);在HSV中,黄色只由一个值决定,Hue=60即可。HSV 圆柱体的半边横截面(Hue=60):

7.jfif

其中水平方向表示饱和度,饱和度表示颜色接近光谱色的程度。饱和度越高,说明颜色越深,越接近光谱色饱和度越低,说明颜色越浅,越接近白色。饱和度为0表示纯白色。取值范围为0~100%,值越大,颜色越饱和。

竖直方向表示明度,决定颜色空间中颜色的明暗程度,明度越高,表示颜色越明亮,范围是 0-100%。明度为0表示纯黑色(此时颜色最暗)。

动作实现

下位机负责所有机器人动作的控制,包括直线行驶、转弯、转圈、目标物块抓取、放置等等,此处先讲解目标物块抓取动作部分。先看机械设计,机器人前方的圆柱形带传动可收纳式抓手是靠两个原动件提供动力的。

8.gif
(舵机,蓝色部分)
9.gif
(42步进电机,蓝色部分)

舵机提供动力控制抓手的开合,用于夹取目标物块和释放目标物块;42步进电机控制传送带从而控制抓手,用于使抓手升降。

10.gif

从视频可以看出,每抓取一个物块需要五个动作:

(1)抓手下降到一半高度;

(2)抓手张开;

(3)抓手下降到最低点;

(4)抓手闭合,抓取物块;

(5)抓手上升到最高点。

代码讲解

由于初步设计时时间较紧,故选择Arduino该平台作为主控。学习舵机、步进电机控制,有很多种主控方案可以选择,大家可按需选择。代码主要部分:

  • 导入需要用到的库、创建舵机、步进电机对象
#include <AccelStepper.h>
#include <Servo.h>
AccelStepper stepperArm(1,armstepPin,armdirPin);
Servo armServo;
  • 在 setup函数里面对舵机、步进电机进行初始化
  stepperArm.setMaxSpeed(1200.0);
  stepperArm.setAcceleration(400.0);
  armServo.attach(8);
  armServo.write(servoMid);
  • 抓取动作控制(五个步骤)
void Get(){
  //抓手下降一半
  if (getTurns == 0){
    stepperArm.moveTo (armStepperHigh1);
    //Serial.println("我是抓手,我现在在下降");
    if (stepperArm.currentPosition() == armStepperHigh1){
      getTurns ++;
    }
  }
  //抓手张开一点
  if ( getTurns == 1){
    for (armAngle = servoMid; armAngle <= servoEnd; armAngle ++) { 
      armServo.write(armAngle);              
      delay(5);                      
      }
    getTurns ++;
  }
  //抓手下降到最低端
  if (getTurns == 2){
    stepperArm.moveTo (armStepperHigh2);
    if (stepperArm.currentPosition() == armStepperHigh2){
      getTurns ++;
    }
  }
  //抓取物块
  if ( getTurns == 3){
    //delay(2000);
    for (armAngle = servoEnd; armAngle >= servoMid; armAngle --) { 
      armServo.write(armAngle);              
      delay(5);                     
      }
    getTurns ++;
  }
  //上升抓手到最高位置
  if (getTurns == 4){
    stepperArm.moveTo (armStepperHigh0);
    if(stepperArm.currentPosition() == armStepperHigh0){
        getTurns = 0;
        ifOverGet = 1;
      }
    }
      
}
原作者:衣柜旁的小明
原链接:本文转自地平线开发者社区(完整文档及代码点击此处一键直达)
推荐阅读
关注数
1255
内容数
71
我是地瓜机器人,为加速智能生长而来
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息