基于uArm 的视觉分拣方案--代码分析--2021-02-24

以下代码为了做笔记方便,希望有错误的地方大家能给予修改意见,感谢!
代码均来自https://github.com/uArm-Developer/Vision-Pick-and-Place
Arduino参考链接:https://www.arduino.cc/reference/en/
使用 ArduinoMega
1. Arduino 函数简要介绍

  • Serial相关函数
    Arduino串行端口(也称为UART或USART),ArduinoMega有多个:
    基于uArm 的视觉分拣方案--代码分析--2021-02-24
//指示指定的串行端口是否已就绪。如果指定的串行端口可用,则返回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())
上一篇:manjaro:vmware workstation 16新建虚拟机提示:“Could not open /dev/vmmon: No such file or directory. Please m


下一篇:Manjaro软件推荐