这一期要实现对目标物块的识别和抓取。目标物块是一些表面涂有不同颜色的圆柱体塑料桶,目标物块的识别环节交给旭日X3派加上USB摄像头,目标物块的抓取则交给下位机(Arduino Mega2560)和其控制的步进电机、舵机等。
先看最终效果视频(直接在文章里面看这个动图一卡一卡的播放地很慢,直接把图片拉到桌面,或者另存到电脑再打开看就很流畅、清晰)
1旭日X3派对目标物块识别原理讲解
根据目标物块的特征,我选择的是解决方案是:
首先通过机械结构使目标物块每次被识别时,目标物块都出现在摄像头的固定角度,固定距离。这样就保证了摄像头每次识别目标物块时,目标物块都会出现在拍摄照片的固定像素范围。
其次,根据目标物块的特点(物块之间只有颜色差异)只要完成对目标物块出现范围的像素点颜色的识别,就能判断出具体是哪一个目标物块。所以采用了对特定区域像素点的颜色识别算法。
从演示视频中可以看出,每次抓取物块之前,都会先通过U型推手把目标物块先固定在车身正前方的U型推手内如图。
也可看到,此时补光灯会进行补光,以减少环境光对识别结果的影响。
从图片也可以看出,识别环节,摄像头是正对目标物块,而且距离很近,这个设计就保证了摄像头拍摄到的大部分像素点都被目标物块的颜色所填满,增加了识别面积。
摄像头拍摄到的图片是这样的,目标物块几乎填满了整个图片
接下来就只剩通过旭日X3派进行颜色识别
2使用旭日X3派借助opencv,通过HSV颜色模型,实现对目标物块的颜色识别;以及通过串口把识别结果发送给下位机代码原理解释
导入需要用的库
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)
获取一帧图片并进行裁剪,只保留小部分目标物块的像素点,这有两个原因。
1:获取到的一整帧图片周围有非目标物块的周围环境,如果纳入计算过程的话会影响到最终识别结果
2:缩小图片体积,可以减少CPU负载,提升运算速度
代码下面附上截取前后图片对比
ret, frame = cap_color.read()
#cv.imshow("frame", frame)#代码在电脑上测试时候用于观察,放在X3派上要注释掉
ROI = frame[50:150, 50:200]#get useful ROI
前:
后:
把截取后的图片转化成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]
比较最终值和颜色范围,确定识别结果,并通过串口把结果发送给下位机
解释一下串口发送字符的含义
g——green
r——red
b——blue
B——black
w——write
e——error
前面几个都是颜色的首字母,最后’e’一个表示识别的颜色不在既定范围内
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')
2为什么选用使用HSV颜色模型而不是用RGB
此处解释一下为什么要用HSV格式的图片而不是灰度或者RGB
RGB 是我们接触最多的颜色空间,由三个通道表示一幅图像,分别为红色(R),绿色(G)和蓝色(B)。这三种颜色的不同组合可以形成几乎所有的其他颜色。但是人眼对于这三种颜色分量的敏感程度是不一样的,在单色中,人眼对红色最不敏感,蓝色最敏感,所以 RGB 颜色空间是一种均匀性较差的颜色空间。如果颜色的相似性直接用欧氏距离来度量,其结果与人眼视觉会有较大的偏差。对于某一种颜色,我们很难推测出较为精确的三个分量数值来表示。所以,RGB 颜色空间适合于显示系统,却并不适合于图像处理。
在图像处理中使用较多的是 HSV 颜色空间,它比 RGB 更接近人们对彩色的感知经验。非常直观地表达颜色的色调、鲜艳程度和明暗程度,方便进行颜色的对比。
在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。
HSV 表达彩色图像的方式由三个部分组成:
- Hue(色调、色相)
- Saturation(饱和度、色彩纯净度)
- Value(明度)
用下面这个圆柱体来表示 HSV 颜色空间,圆柱体的横截面可以看做是一个极坐标系 ,H 用极坐标的极角表示,S 用极坐标的极轴长度表示,V 用圆柱中轴的高度表示。
Hue 用角度度量,取值范围为0~360°,表示色彩信息,即所处的光谱颜色的位置。,表示如下:
颜色圆环上所有的颜色都是光谱上的颜色,从红色开始按逆时针方向旋转,Hue=0 表示红色,Hue=120 表示绿色,Hue=240 表示蓝色等等。
在 GRB中 颜色由三个值共同决定,比如黄色为即 (255,255,0);在HSV中,黄色只由一个值决定,Hue=60即可。
HSV 圆柱体的半边横截面(Hue=60):
其中水平方向表示饱和度,饱和度表示颜色接近光谱色的程度。饱和度越高,说明颜色越深,越接近光谱色饱和度越低,说明颜色越浅,越接近白色。饱和度为0表示纯白色。取值范围为0~100%,值越大,颜色越饱和。
竖直方向表示明度,决定颜色空间中颜色的明暗程度,明度越高,表示颜色越明亮,范围是 0-100%。明度为0表示纯黑色(此时颜色最暗)。
可以通俗理解为:
在Hue一定的情况下,饱和度减小,就是往光谱色中添加白色,光谱色所占的比例也在减小,饱和度减为0,表示光谱色所占的比例为零,导致整个颜色呈现白色。
明度减小,就是往光谱色中添加黑色,光谱色所占的比例也在减小,明度减为0,表示光谱色所占的比例为零,导致整个颜色呈现黑色。
HSV 对用户来说是一种比较直观的颜色模型。我们可以很轻松地得到单一颜色,即指定颜色角H,并让V=S=1,然后通过向其中加入黑色和白色来得到我们需要的颜色。增加黑色可以减小V而S不变,同样增加白色可以减小S而V不变。例如,要得到深蓝色,V=0.4 S=1 H=240度。要得到浅蓝色,V=1 S=0.4 H=240度。
HSV 的拉伸对比度增强就是对 S 和 V 两个分量进行归一化(min-max normalize)即可,H 保持不变。
RGB颜色空间更加面向于工业,而HSV更加面向于用户,大多数做图像识别这一块的都会运用HSV颜色空间,因为HSV颜色空间表达起来更加直观!
以上解释采样与文章三分钟带你快速学习RGB、HSV和HSL颜色空间 - 知乎 (zhihu.com),要是想要了解更多,可以参考原文。-
3下位机控制抓取目标物块动作实现
下位机负责所有机器人动作的控制,包括直线行驶、转弯、转圈、目标物块抓取、放置等等,此处暂时先讲解目标物块的抓取动作。
先看机械设计,机器人前方的圆柱形带传动可收纳式抓手是靠两个原动件提供动力的,首先是这个舵机(蓝色部分)
其次是42步进电机(蓝色部分)
舵机提供动力控制抓手的开合,用于夹取目标物块和释放目标物块
42步进电机控制传送带从而控制抓手,用于使抓手升降
从视频可以看出,每抓取一个物块需要五个动作
1.抓手下降到一半高度
2.抓手张开
3.抓手下降到最低点
4.抓手闭合,抓取物块
5.抓手上升到最高点
最后是动作代码讲解
先说明要是学习舵机、步进电机控制,有很多种主控方案可以选择,这里选择Arduino平台是因为最开始设计的时候时间比较急,就选择了这块主控,因为Arduino开发起来比较节约时间,刚好这款单片机的各项性能都达标,就选择了。但是目前大部分做竞赛都是选用的STM32,这里就根据自己实际情况选用
接下来代码部分因为不是有关旭日X3派的,就不一一讲解了,就讲解一下总体的动作控制,要是想要了解详细的代码含义,可以参考以下太极创客出的相关教程还有讲解
【太极创客】零基础入门学用Arduino 第二部分 meArm机械臂 合辑_哔哩哔哩_bilibili
太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料 (taichi-maker.com)
代码主要部分
导入需要用到的库、创建舵机、步进电机对象
#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;
}
}
}
「地平线旭日X3派,开启你的嵌入式开发之旅」,欢迎正在阅读的你申请试用,一起交流开发心得