以下代码为了做笔记方便,希望有错误的地方大家能给予修改意见,感谢!
代码均来自https://github.com/uArm-Developer/Vision-Pick-and-Place
Arduino参考链接:https://www.arduino.cc/reference/en/
使用 ArduinoMega
1. Arduino 函数简要介绍
-
Serial相关函数
Arduino串行端口(也称为UART或USART),ArduinoMega有多个:
//指示指定的串行端口是否已就绪。如果指定的串行端口可用,则返回true
if(Serial)
//获取可用于从串行端口读取的字节数(字符)。这是已经到达并存储在串行接收缓冲区(包含64个字节)中的数据。返回可读取的字节数。
Serial.available()
//获取可用于在串行缓冲区中进行写入而不阻止写入操作的字节数(字符)。返回可以写入的字节数。
Serial.availableForWrite()
//设置以每秒比特数(波特)为单位的串行数据传输的数据速率。speed:以每秒位数(波特)为单位。允许的数据类型:long;config:设置数据,奇偶校验和停止位。无返回值。如:Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
Serial.begin(speed)
Serial.begin(speed, config)
//禁用串行通信,允许将RX和TX引脚用于常规输入和输出。无返回值。
Serial.end()
//从串行缓冲区读取数据,直到找到目标为止。如果找到目标,该函数将返回true;如果目标超时返回false。target:要搜索的字符串。允许的数据类型:char。length:目标的长度。允许的数据类型:size_t。
Serial.find(target)
Serial.find(target, length)
//从串行缓冲区读取数据,直到找到给定长度的目标字符串或终止符字符串。如果找到目标字符串,该函数将返回true,如果超时则返回false。target:要搜索的字符串。允许的数据类型:char。terminal:搜索中的终端字符串。允许的数据类型:char。
Serial.findUntil(target, terminal)
//等待输出串行数据的传输完成。无返回值。
Serial.flush()
//从串行缓冲区返回第一个有效的浮点数。parseFloat()以不是浮点数的第一个字符终止。返回浮点数据。ignore:用于跳过搜索中指示的字符。详细参见:https://www.arduino.cc/reference/en/language/functions/communication/serial/parsefloat/
Serial.parseFloat()
Serial.parseFloat(lookahead)
Serial.parseFloat(lookahead, ignore)
//在输入的序列中查找下一个有效整数。如果超时,该函数终止。返回long类型的下一个有效整数的
Serial.parseInt()
Serial.parseInt(lookahead)
Serial.parseInt(lookahead, ignore)
//返回传入串行数据的下一个字节(字符),而不将其从内部串行缓冲区中删除。也就是说,对的后续调用peek()将返回相同的字符,而对的下一个调用也将返回相同的字符read()。
Serial.peek()
//将数据作为人类可读的ASCII文本打印到串行端口。返回写入的字节数,尽管读取该数字是可选的。详细参见:https://www.arduino.cc/reference/en/language/functions/communication/serial/print/
Serial.print(val)
Serial.print(val, format)
//将数据作为人类可读的ASCII文本打印到串行端口,后跟回车符(ASCII 13,或'\ r')和换行符(ASCII 10,或'\ n')。此命令的格式与Serial.print()相同。返回写入的字节数,尽管读取该数字是可选的。
Serial.println(val)
Serial.println(val, format)
//读取传入的串行数据。输入的串行数据的第一个字节的可用数据(如果没有可用数据,则为-1)。数据类型:int
Serial.read()
//从串行端口读取字符到缓冲区。返回放置在缓冲区中的字节数。
Serial.readBytes(buffer, length)
//将来自串行缓冲区的字符读取到数组中。返回读入缓冲区的字符数。0表示length参数<= 0,在任何其他输入之前发生超时,或者在任何其他输入之前发现终止字符。character:要搜索的字符。允许的数据类型:char。buffer:用于存储字节的缓冲区。允许的数据类型:char或的数组byte。length:要读取的字节数。允许的数据类型:int。
Serial.readBytesUntil(character, buffer, length)
//从串行缓冲区读取字符到字符串。返回从串行缓冲器读的一个String。
Serial.readString()
//从串行缓冲区读取字符到字符串。返回从串行缓冲区读取的整个String,直到终止符。terminator:要搜索的字符。允许的数据类型:char。
Serial.readStringUntil(terminator)
//设置等待串行数据的最大毫秒数。默认值为1000毫秒。没有返回值。
Serial.setTimeout(time)
//将二进制数据写入串行端口。该数据以字节或一系列字节的形式发送;要发送代表数字数字的字符,请改用print()函数。val:要作为单个字节发送的值。str:作为一系列字节发送的字符串。buf:要作为一系列字节发送的数组。len:要从数组发送的字节数。返回写入的字节数。
Serial.write(val)
Serial.write(str)
Serial.write(buf, len)
//有数据时调用。使用Serial.read()捕捉到了这个数据。没有返回值。
serialEvent()
- Digital I/O相关函数
//将指定的引脚配置为充当输入或输出。pin:用于设置模式的Arduino引脚号。mode:INPUT,OUTPUT,或INPUT_PULLUP。无返回值。
pinMode(pin, mode)
//从指定的数字引脚读取值,HIGH或LOW,返回HIGH 或者 LOW。
digitalRead(pin)
//将HIGH或LOW值写入数字引脚。pin:Arduino引脚号。value:HIGH或LOW。无返回值。
digitalWrite(pin, value)
2. Vision.ino代码解析
//After finishing the wiring, press the D5 button to run the code
int inByte = 0,//serial buf
num = 0;//buf counter
int x_openmv=0, y_openmv=0;
int x_uarm=0, y_uarm=0;
unsigned long times;
char buf[20],
flag=0;
char color_sel=1;// 0:yellow 1:red 2:green
unsigned char get_openmv_data();
void pick_and_palce();
void wait_for_finish_moving()
{
inByte=0;//clear the buffer
while(inByte!='@'){
if (Serial2.available() > 0) {
inByte = Serial2.read();
}
}
}
void setup() {
pinMode(5,INPUT);//button
pinMode(A3,OUTPUT);//orange led,A3也是一个引脚编号,十六进制形式
digitalWrite(A3,LOW);
//设置数据传输速率
Serial.begin(115200);//usb or xbee
Serial1.begin(115200);//openmv
Serial2.begin(115200);//uarm
//Move to XYZ(mm), F is speed(mm/min),参见链接:http://download.ufactory.cc/docs/en/Swift-Quick-Start-Guide.pdf
Serial2.write("G0 X200 Y0 Z160 F10000\n");
//if button is pressed, then start the program
while(digitalRead(5)==HIGH);
digitalWrite(A3,HIGH);
Serial.write("V2 START!\n");
Serial2.write("M2400 S0\n");//set the mode of uarm
delay(4000);
Serial2.write("M2400 S0\n");//set the mode of uarm
Serial2.write("M2122 V1\n");//report when finish the movemnet
//返回自Arduino开发板开始运行当前程序以来经过的毫秒数。 大约50天后,该数字将溢出(返回零)。
times = millis();
}
void loop() {
if(flag == 0)
{
digitalWrite(A3,HIGH);
Serial2.write("G0 X200 Y0 Z159 F10000\n");// in order to trig the report of finish movement '@'
wait_for_finish_moving();
//Serial2.write("G2202 N0 V90\n");
//wait_for_finish_moving();
Serial2.write("G0 X200 Y0 Z160 F10000\n");
delay(100);//wait for the uarm to finish the moving then start the vision tracking
wait_for_finish_moving();
flag = 1;//vision start
switch(color_sel){
case 0: Serial1.write('y');break;
case 1: Serial1.write('r');break;
case 2: Serial1.write('g');break;
default: break;
}
Serial1.write('S');//send vision start command
Serial.write("vision start for finding the cube\n");//send vision start command
times = millis();
}
digitalWrite(A3,LOW);
//get commands from pc,读取openMv的数据,并传给uArm
if (Serial.available() > 0)
{
inByte = Serial.read(); //读取openMv的数据
Serial2.write(inByte); //传给uArm
}
//get object coordinates from openmv
if(get_openmv_data()==1)
{
flag = 0;//vision end
Serial.write("move\n");//confirm the openmv data
//new algorithm
x_uarm = y_openmv*(-0.7035)-3.635 + 88 + 70 + 200;
y_uarm = x_openmv*(-0.7488)+12.391 + 107.5 + 15 +0;
String commands="G0 X";
commands.concat(x_uarm);
commands+=" Y";
commands.concat(y_uarm);
commands+=" Z100 F10000\n";
Serial2.print(commands);
Serial.print(commands);
pick_and_palce();
}
}
//get object coordinates from openmv
unsigned char get_openmv_data()
{
if (Serial1.available() > 0)
{
inByte = Serial1.read();
buf[num++] = inByte;
Serial.write(inByte);
if((inByte=='\n')&&(buf[0]=='x'))
{
Serial.write("get openmv data\n");
int counters=1;//jump the letter x
x_openmv=0;
do{
x_openmv = x_openmv*10;
x_openmv += buf[counters++] - 48;
}while((buf[counters]>=0x30)&&(buf[counters]<=0x39));
y_openmv=0;
counters++;//jump the letter y
do{
y_openmv = y_openmv*10;
y_openmv += buf[counters++] - 48;
}while(counters+1<num);
num = 0;
return 1;
}
//Serial.println(x_openmv,DEC);
//Serial.println(y_openmv,DEC);
}
if((millis()-times>10000)&&(flag==1))//if no object detected, reset the flag every 10s
{
//clear the uart buffers
while(Serial1.available() > 0)
{
inByte = Serial1.read();
}
//reset the count of uart
num = 0;
times = millis();
flag = 0;
Serial.write("status 1\n");//NO OBJECT IN CAMERA
}
return 0;
}
//move the detected object to the fixed position
void pick_and_palce()
{
Serial2.write("G0 Z23 F10000\n");
Serial2.write("M2231 V1\n");
Serial2.write("G0 Z120 F10000\n");
delay(500);
Serial2.write("G2202 N0 V15\n");
Serial2.write("G0 Z50 F10000\n");
Serial2.write("M2231 V0\n");
Serial2.write("G0 Z80 F10000\n");
Serial2.write("G2202 N0 V90\n");
delay(8000);
//change the color of tracking
//color_sel++;
//color_sel = color_sel%3;
}
3. color_tracking_test.py代码解析
参考链接:
https://docs.singtown.com/micropython/zh/latest/openmvcam/index.html
https://docs.singtown.com/micropython/zh/latest/openmvcam/library/pyb.UART.html#pyb-uart
https://docs.singtown.com/micropython/zh/latest/openmvcam/library/pyb.LED.html#pyb-led
https://docs.singtown.com/micropython/zh/latest/openmvcam/library/omv.image.html?highlight=blob%20rect#blob.rect
# Single Color Code Tracking Example
#
# This example shows off single color code tracking using the OpenMV Cam.
#
# A color code is a blob composed of two or more colors. The example below will
# only track colored objects which have both the colors below in them.
import sensor, image, time
from pyb import UART
from pyb import LED
blue_led = LED(3) # LED(3) -> 蓝色 RGB LED Segment
green_led = LED(2) # LED(2) -> 绿色 RGB LED Segment
red_led = LED(1) # LED(1) -> 红色 RGB LED Segment
uart = UART(3, 115200, timeout_char = 1000) # 使用给定波特率初始化
blue_led.on() # 打开LED,达到最大强度
# Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max)
# The below thresholds track in general red/green things. You may wish to tune them...
#thresholds = [(30, 100, 15, 127, 15, 127), # generic_red_thresholds -> index is 0 so code == (1 << 0)
# (30, 100, -64, -8, -32, 32)] # generic_green_thresholds -> index is 1 so code == (1 << 1)
# 创建颜色编码元组
thresholds = [(55, 100,-24, 11, 32, 86), #1#yellow
(27, 100, 42, 80, 30, 64), #2#red
(39, 100,-51,-12, 10, 57)] #4#green
# Codes are or'ed together when "merge=True" for "find_blobs".
sensor.reset() # 初始化相机传感器
sensor.set_pixformat(sensor.RGB565) # 初始化相机传感器-sensor.GRAYSCALE: 8-bits per pixel
sensor.set_framesize(sensor.QVGA) # 设置相机模块的帧大小
sensor.skip_frames(time = 2000) # 通过关键字参数 time 来跳过几毫秒的帧数,让相机图像在改变相机设置后稳定下来
sensor.set_auto_gain(False) # 若您想追踪颜色,则需关闭白平衡
sensor.set_auto_whitebal(False) # 若您想追踪颜色,则需关闭白平衡
clock = time.clock()
blue_led.off()
green_led.off()
red_led.off()
# 色块(int)的中心x、y位置
object_x_old = 0
object_y_old = 0
code = 2 ## 1:yellow 2:red 4:green
buf = "00"
# Only blobs that with more pixels than "pixel_threshold" and more area than "area_threshold" are
# returned by "find_blobs" below. Change "pixels_threshold" and "area_threshold" if you change the
# camera resolution. "merge=True" must be set to merge overlapping color blobs for color codes.
while(True):
clock.tick() # 更新图像的帧率
# 关闭所有的灯
blue_led.off()
green_led.off()
red_led.off()
img = sensor.snapshot() # 使用相机拍摄一张照片,并返回 image 对象
# img.find_blobs:查找图像中所有色块,并返回一个包括每个色块的色块对象的列表
# thresholds 必须是元组列表。 [(lo, hi), (lo, hi), ..., (lo, hi)] 定义你想追踪的颜色范围。 对于灰度图像,每个元组需要包含两个值 - 最小灰度值和最大灰度值。 仅考虑落在这些阈值之间的像素区域。 对于RGB565图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi) - 分别是LAB L,A和B通道的最小值和最大值。
# 若一个色块的边界框区域小于 area_threshold ,则会被过滤掉。
# 若一个色块的像素数小于 pixel_threshold ,则会被过滤掉。
# merge 若为True,则合并所有没有被过滤掉的色块,这些色块的边界矩形互相交错重叠。 margin 可在相交测试中用来增大或减小色块边界矩形的大小。例如:边缘为1、相互间边界矩形为1的色块将被合并。
for blob in img.find_blobs(thresholds, pixels_threshold=100, area_threshold=100, merge=False):
#check with color should be detect
if uart.any()>0 : # 检查是否有内容有待读取
buf=uart.read() # 读取字符,返回值:包含读入字节的bytes对象
print (buf[0])
if buf[0]==ord('y') : #ord()函数主要用来返回对应字符的ascii码
code = 1
if buf[0]==ord('r') :
code = 2
if buf[0]==ord('g') :
code = 4
#check if there is object with right color
#Blob 类 – 色块对象
#blob.code()返回一个32位的二进制数字,其中为每个颜色阈值设置一个位,这是色块的一部分。
#如果您通过 image.find_blobs 来寻找三个颜色阈值,这个色块可以设置为0/1/2位。 注意:除非以 merge=True 调用 image.find_blobs ,否则每个色块只能设置一位。
if blob.code() == code:
#blob.rect()返回一个矩形元组(x, y, w, h) ,用于如色块边界框的 image.draw_rectangle 等 其他的 image 方法。
#blob.cx()返回色块(int)的中心x位置,可以通过索引 [5] 取得这个值
# blob.cy()返回色块(int)的中心y位置。您也可以通过索引 [6] 取得这个值。
img.draw_rectangle(blob.rect())#在图像上绘制一个矩形
img.draw_cross(blob.cx(), blob.cy())#在图像上绘制一个十字
#print(blob.cx(), blob.cy(),blob.w())
#make sure the detected object is stable and print the coordinates
#first it detect if the coordinates of blob is available
#second compared with the last position to make sure if the object is not moving
#third reduce the affect of anbience
if blob.cx()!=None and (
abs(object_x_old - int(blob.cx())) < 8 and
abs(object_y_old - int(blob.cy())) < 8) and (
blob.w()>35 and
blob.h()>35):
#just detect the objects. turn on the blue only
blue_led.on()
red_led.off()
green_led.off()
#print("stable!")
#print (buf)
#check if the uart got any command and response
#if uart.any()>0 :
#buf=uart.read(1)
if buf[1]==ord('S') :
#print("command\n")
#detect both the objects and the vision command from mega2560. turn on the red only
blue_led.off()
red_led.on()
green_led.off()
uart.write('x'+str(blob.cx())+'y'+str(blob.cy())+'\n')
#finish the sending. turn on the green only
blue_led.off()
red_led.off()
green_led.on()
#clear the flag
buf = "00"
object_x_old = int(blob.cx())
object_y_old = int(blob.cy())