uarm视觉分拣代码解析、openmv颜色识别

以下代码为了做笔记方便,希望有错误的地方大家能给予修改意见,感谢!
代码均来自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();

// 读取uarm串口发送的消息
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;
        //buf[counters++] - 48意思是:比如字符0,转成数字0,相差了十进制数值48,因为x_openmv就是int类型
        x_openmv += buf[counters++] - 48;  
      }while((buf[counters]>=0x30)&&(buf[counters]<=0x39));
      // 十六进制0x30~0x39代表字符 0~9
      // 上面do{}地内容意思是:
      // 假设openmv传递地数据是字符串123
      // 0  0  0+字符1地ASCII-48 = 0+数字1 = 1
      // 然后:
      // 1 = 1*10 = 10
      // 10 + 字符2-48 = 10+数字2 = 12
      // 然后:
      // 12 = 12*10 = 120
      // 120 + 字符3 - 48 = 120+数字3 = 123,也就是将坐标字符串123转换成数字123

      
      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代码解析
(1)openMV相关函数使用

classtime.clock #返回一个时钟对象。
参见链接:https://docs.singtown.com/micropython/zh/latest/openmvcam/library/index.html#openmv-cam

(2)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() 开始追踪运行时间。
    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])
            # 下面的buf[0]数据是在vision.ino的void loop()的switch(color_sel)里面发送过来的
            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: # 如果检测到的色块代码和初始给的色块代码(初始给的是主程序发过来的code(主程序代码是color_sel))一致
#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())

三、openMV颜色识别
文档链接:寻找色块文档说明
在这里插入图片描述

import sensor, image, time, math
# 设置阈值列表
threshold_index = 0 # 0 for red, 1 for green, 2 for blue
thresholds = [(30, 100, 15, 127, 15, 127), # generic_red_thresholds
              (30, 100, -64, -8, -32, 32), # generic_green_thresholds
              (0, 30, 0, 64, -128, 0)] # generic_blue_thresholds
sensor.reset()  # 重置感光元件
sensor.set_pixformat(sensor.RGB565)  # 设置RGB格式为565
sensor.set_framesize(sensor.QVGA)  # 图像大小是QVGA
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking,关闭白平衡
sensor.set_auto_whitebal(False) # must be turned off for color ,tracking 关闭自动增益
clock = time.clock()
while(True):
    clock.tick()
    img = sensor.snapshot() # 截取感光原件的一张图片
    # find_blobs函数会返回一个列表
    for blob in img.find_blobs([thresholds[threshold_index]], pixels_threshold=200, area_threshold=200, merge=True):
    	img.draw_rectangle(blob.rect()) # 绘制矩形
        img.draw_cross(blob.cx(), blob.cy()) # 绘制十字架
		print('x:'+str(blob.cx())+' y:'+str(blob.cy())+'\n')
	print(clock.fps())

find_blobs函数

image.find_blobs(thresholds, roi=Auto, x_stride=2, y_stride=1, invert=False, area_threshold=10, pixels_threshold=10, merge=False, margin=0, threshold_cb=None, merge_cb=None)

  • thresholds是颜色的阈值,注意:这个参数是一个列表,可以包含多个颜色。如果你只需要一个颜色,那么在这个列表中只需要有一个颜色值,如果你想要多个颜色阈值,那这个列表就需要多个颜色阈值。注意:在返回的色块对象blob可以调用code方法,来判断是什么颜色的色块。
red = (xxx,xxx,xxx,xxx,xxx,xxx)
blue = (xxx,xxx,xxx,xxx,xxx,xxx)
yellow = (xxx,xxx,xxx,xxx,xxx,xxx)

img=sensor.snapshot()
red_blobs = img.find_blobs([red])

color_blobs = img.find_blobs([red,blue, yellow])
  • roi是“感兴趣区”。
    参见:https://book.openmv.cc/image/statistics.html
  • x_stride 就是查找的色块的x方向上最小宽度的像素,默认为2,如果你只想查找宽度10个像素以上的色块,那么就设置这个参数为10:blobs = img.find_blobs([red],x_stride=10)
  • y_stride 就是查找的色块的y方向上最小宽度的像素,默认为1,如果你只想查找宽度5个像素以上的色块,那么就设置这个参数为5:
    blobs = img.find_blobs([red],y_stride=5)
  • invert 反转阈值,把阈值以外的颜色作为阈值进行查找
  • area_threshold 面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉,是矩形面积而不是色块面积
  • pixels_threshold 像素个数阈值,如果色块像素数量小于这个值,会被过滤掉,是色块面积
  • merge 合并,如果设置为True,那么合并所有重叠的blob为一个。
    注意:这会合并所有的blob,无论是什么颜色的。如果你想混淆多种颜色的blob,只需要分别调用不同颜色阈值的find_blobs。
all_blobs = img.find_blobs([red,blue,yellow],merge=True)

red_blobs = img.find_blobs([red],merge=True)
blue_blobs = img.find_blobs([blue],merge=True)
yellow_blobs = img.find_blobs([yellow],merge=True)
  • margin 边界,如果设置为1,那么两个blobs如果间距1一个像素点,也会被合并。

阈值
一个颜色阈值的结构是这样的:

red = (minL, maxL, minA, maxA, minB, maxB)

元组里面的数值分别是L A B 的最大值和最小值。

blobs是一个列表
find_blobs对象返回的是多个blob的列表。(注意区分blobs和blob,这只是一个名字,用来区分多个色块,和一个色块)。
列表类似与C语言的数组,一个blobs列表里包含很多blob对象,blobs对象就是色块,每个blobs对象包含一个色块的信息。

blobs = img.find_blobs([red])

blobs就是很多色块。

可以用for循环把所有的色块找一遍。

for blob in blobs:
    print(blob.cx())

blob色块对象
blob有多个方法:

blob.rect() 返回这个色块的外框——矩形元组(x, y, w, h),可以直接在image.draw_rectangle中使用。

blob.x() 返回色块的外框的x坐标(int),也可以通过blob[0]来获取。

blob.y() 返回色块的外框的y坐标(int),也可以通过blob[1]来获取。

blob.w() 返回色块的外框的宽度w(int),也可以通过blob[2]来获取。

blob.h() 返回色块的外框的高度h(int),也可以通过blob[3]来获取。

blob.pixels() 返回色块的像素数量(int),也可以通过blob[4]来获取。

blob.cx() 返回色块的外框的中心x坐标(int),也可以通过blob[5]来获取。

blob.cy() 返回色块的外框的中心y坐标(int),也可以通过blob[6]来获取。

blob.rotation() 返回色块的旋转角度(单位为弧度)(float)。如果色块类似一个铅笔,那么这个值为0180°。如果色块是一个圆,那么这个值是无用的。如果色块完全没有对称性,那么你会得到0360°,也可以通过blob[7]来获取。

blob.code() 返回一个16bit数字,每一个bit会对应每一个阈值。举个例子:

blobs = img.find_blobs([red, blue, yellow], merge=True)

如果这个色块是红色,那么它的code就是0001,如果是蓝色,那么它的code就是0010。注意:一个blob可能是合并的,如果是红色和蓝色的blob,那么这个blob就是0011。这个功能可以用于查找颜色代码。也可以通过blob[8]来获取。

blob.count() 如果merge=True,那么就会有多个blob被合并到一个blob,这个函数返回的就是这个的数量。如果merge=False,那么返回值总是1。也可以通过blob[9]来获取。

blob.area() 返回色块的外框的面积。应该等于(w * h)

blob.density() 返回色块的密度。这等于色块的像素数除以外框的区域。如果密度较低,那么说明目标锁定的不是很好。
比如,识别一个红色的圆,返回的blob.pixels()是目标圆的像素点数,blob.area()是圆的外接正方形的面积。

具体参见链接:
https://docs.singtown.com/micropython/zh/latest/openmvcam/library/omv.image.html?highlight=find_blobs#id38
和链接
https://docs.singtown.com/micropython/zh/latest/openmvcam/library/omv.image.html?highlight=find_blobs#blob

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习的代码小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值