首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「学习笔记」OpenMV 与 MicroPython

「学习笔记」OpenMV 与 MicroPython

作者头像
悠风
发布2019-08-28 16:26:03
3.9K0
发布2019-08-28 16:26:03
举报

说 明

大一时参加2017年全国大学生电子设计竞赛时,当时第一次接触 Python 和 OpenMV,这是当时写下的学习笔记。后来随着硬盘损坏,这份笔记文档也消失了,幸得队友替我收藏了这份笔记。现在把这份笔记放到公众号这里,方便日后查阅,同时也分享给所有有兴趣的人。

该笔记内容对应的原资料为星瞳科技的 OpenMV 上手教程文档(阅读原文)。

Python基本数据类型

  • 列表list boys = ['Bob', 'Jack', 'Tom'] printf(boys[0], boys[1], boys[2]) # 列表是动态的,可以添加和删除。
  • 元组tupple boys = ('Bob', 'Jack', 'Tom') print(boys[0], boys[1], boys[2]) # 元组是不能变化的
  • 对象 from pyb import LED red_led = LED(1) red_lcd.on()

python循环语句

  • for循环 boys = ['Bob', 'Jack', 'Tom'] for name in boys: print(name)

python模块

  • 在Python中,一个.py文件就称之为一个模块(Module)。
  • 模块的引入: # 引入方法一: import pyb # 引入pyb这个模块 red_led = pyb.LED(1) red_LED.on() # 引入方法二: from pyb import LED # 通过pyb模块引入LED类或LED函数 red_LED = LED(1) red_LED.on()

sensor模块

import sensor
sensor.set_windowing(roi)    # 设置窗口ROI,roi的格式是(x, y, w, h)。
sensor.set_hmirror(True)     # 水平方向翻转
sensor.set_vflip(True)       # 垂直方向翻转

获取/设置像素点

# 获取一个像素点的值
img = sensor.snapshot()

# 获取坐标为(10, 10)的像素点的颜色值(灰度值/(R, G, B)值)
img.get_pixel(10, 10)

# 设置坐标为(10, 10)的像素点的颜色为红色
img.set_pixel(10, 10, (255, 0, 0))

获取图像的宽度和高度

image.width()    # 返回图像的宽度(像素)
image.height()   # 返回图像的高度(像素)
image.format()   # 灰度图会返回sensor.GRAYSCALE,彩色图会返回sensor.RGB565
image.size()     # 返回图像的大小(byte)

图像的运算

image.invert()            # 取反,对于二值化的图像,0(黑)变成(白);图像可以是另一个image对象,或者是从(bmp/pgm/ppm)文件读入的image对象,两个图像都必须是相同的尺寸和类型(灰度图/彩色图)。
image.nand(image)         # 与另一个图片进行与非运算
image.nor(image)          # 与另一个图片进行或非运算
image.xor(image)          # 与另一个图片进行异或运算
image.xnor(image)         # 与另一个图片进行异或非运算
image.difference(image)   # 从这张图片减去另一个图片。比如,对于每个通道的每个像素点,取相减绝对值操作。这个函数,经常用来做移动检测。

使用图片的统计信息

import sensor

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_auto_whitebal()

ROI = (80, 30 ,15, 15)    # 设置统计区域

while(True):
    img = sensor.snapshot()
    statistics = img.get_statistics(roi = ROI)   # 获取统计信息
    color_l = statistics.l_mean()                # 获取L通道的平均数
    color_a = statistics.a_mean()                # 获取A通道的平均数
    color_b = statistics.b_mean()                # 获取B通道的平均数
    print(color_l, color_a, color_b)
    img.draw_rectangle(ROI)
    
    color_mean   = statistics.mean()             # 返回灰度的平均数
    color_median = statistics.median()           # 返回灰度的中位数
    color_mode   = statistics.mode()             # 返回灰度的众数
    color_stdev  = statistics.stdev()             # 返回灰度的标准差
    color_min    = statistics.min()               # 返回灰度的最小值
    color_man    = statistics.max()               # 返回灰度的最大值
    color_lq     = statistics.lq()               # 返回灰度的第一四分数
    color_uq     = statistics.uq()               # 返回灰度的第三四分数

画图

# 画线
# image.draw_line(line_tuple, color = White)
# 在图像中画一条直线
# line_tuple的格式是(x0, y0, x1, y1),指从(x0, y0)到(x1, y1)的直线
# color可以使灰度值(0~255)或彩色值(r, g, b)的tupple,默认为White

# 画框
# image.draw_rectangle(rect_tuple, color = White)
# 在图像中画一个矩形框
# rect_tuple的格式是(x, y, w, h)

# 画圆
# image.draw_circle(x, y, radius, color = White)
# 在图像中画一个圆
# x, y是坐标
# radius是圆的半径

# 画十字
# image.draw_cross(x, y, size = 5, color = White)
# 在图像中画一个十字
# x, y是坐标
# size是两侧的尺寸

# 写字
# image.draw_string(x, y, text, color = White)
# 在图像中写字,每个字占用8×10像素
# x, y是坐标,使用\n, \r, and \r\n会使光标移动到下一行
# text是要写的字符串

# 例子:
import sensor, image, time

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frams(10)

while(True):
    img = sensor.snapshot()
    img.draw_line((20, 30, 40, 50))
    img.draw_line((80, 50, 100, 100), color = (255, 0, 0))
    img.draw_rectangle((20, 30, 41, 51), color = (255, 0, 0))
    img.draw_circle(50, 50, 30)
    img.draw_cross(90, 60, size = 10)
    img.draw_string(10, 10, "Hello MicroPython!")

寻找色块

  • 阈值:一个颜色的阈值是这样的: red = (minL, maxL, minA, maxA, minB, maxB) # 元组里面的数值分别是LAB的最大值和最小值
  • blobs:img.find_blobs()返回的色块对象,是一个列表,包含多个blob对象。 blobs = img.find_blobs([red]) for blob in blobs: print(blob.cx())
  • blob色块对象: blob.rect() # 返回这个色块的外框——矩形元组 blob.x() # 返回色块外框的x坐标 blob.y() # 返回色块外框的y坐标 blob.w() # 返回色块外框的宽度 blob.h() # 返回色块外框的高度 blob.pixels() # 返回色块的像素数量 blob.cx() # 返回色块的外框的中心x坐标 blob.cy() # 返回色块的外框的中心y坐标 blob.rotation() # 返回色块的旋转角度(弧度值,0~2π,浮点型),色块为直线时值为0~π,色块为圆时此值无效 blob.code() # 返回一个16bit数字,每一个bit会对应一个阈值,可用来查找颜色代码 blob.count() # 返回被合并(merge=True)的blob个数 blob.area() # 返回色块的外框面积,应该等于(W×h) blob.density() # 返回色块的密度,等于色块的像素数除以外框的区域。如果密度较低则说明目标锁定得不是很好。
  • img.fimd_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方法来判断是什么颜色的色块。 # roi是感兴趣区。 # x_stride是查找的色块的x方向上最小宽度的像素,默认为2。 # y_stride是查找的色块的y方向上最小宽度的像素,默认为1。 # invert为反转阈值,把阈值以外的颜色作为阈值进行查找。 # area_threshold为面积阈值,如果色块被框起来的面积小于这个值,会被过滤掉 # pixels_threshold为像素个数阈值,如果色块像素数量小于这个值,会被过滤掉 # merge为合并,设置为True时合并所有重叠的blob为一个,无论是什么颜色的blob。 # margin 为边界,如果设置为1,那么两个blobs如果间距小于一个像素点,也会被合并。 # 例子: import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) sensor.skip_frames(10) red = (0, 10, 10, 22, 56, 63) blue = (4, 18, 10, 26, 80, 66) yellow = (45, 66, 2, 17, 35, 57) left_roi = [0, 0, 160, 240] while(True): img = sensor.snapshot() red_blobs = img.find_blobs([red]) color_blobs = img.find_blobs([red, blue, yellow]) roi_blobs = img.find_blobs([red], roi = left_roi) stride_bolbs = img.find_blobs([red], x_stride = 10, y_stride = 5) all_blobs = img.find_blobs([red, blue, yellow], merge = True) blue_blobs = img.find_blobs([blue], merge = True)

AprilTag标记追踪

  • AprilTag是一个视觉基准系统,可用于各种任务,包括AR,机器人和相机校准。这个tag可以直接用打印机打印出来,而AprilTag检测程序可以计算相对于相机的精确3D位置、方向和ID。
  • AprilTag的种类: # TAG16H5->0 to 29 # TAG25H7->0 to 241 # TAG25H9->0 to 34 # TAG36H10->0 to 2319 # TAG36H11->0 to 586 # ARTOOLKIT->0 to 511 # TAG16H5的有效区域是4x4=16的方块,共有30个家族(family),每一个都有对应的ID,从0~29。 # TAG16H5比TAG36H11看的更远(因为它有6x6个方块)。但是TAG16H5的错误率比TAG36H11高很多,因为TAG36H11的校验信息多,所以,如果没有别的理由,推荐用TAG36H11。
  • AprilTag标记追踪程序: import sensor, image, time, math sensor.reset() sensor.set_pxiformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) sensor.frames(30) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) clock = time.clock while(True): clock.tick() img = sensor.snapshot() for tag in img.find_apriltags():#default to TAG36H11 without "families" img.draw_rectangle(tag.rect(), color = (255, 0, 0)) img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0)) degrees = 180 * tag.rotation() / math.pi print(tag.id(), degrees)#print id and degrees of the find tag
  • AprilTag的3D定位: import sensor, image, time, math sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) sensor.frames(30) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) clock = time.clock() # 注意:与find_qrcodes不同,find_apriltags不需要软件矫正畸变就可以工作 # 注意:输出的姿态的单位是弧度,可以转换成角度,但是位置的单位是和大小有关,需要等比例换算 # f_x是x的像素为单位的焦距。对于OpenMV,应该等于2.8/3.984*656,这个值是用毫米为单位的焦距除以x方向的感光元件的长度,乘以x方向的感光元件的像素(OV7725)。f_y同理。 # c_x和c_y是图像的x, y中心位置 f_x = (2.8/3.984)*160 f_y = (2.8/3.952)*120 c_x = 160*0.5#image.w × 0.5 c_y = 120*0.5#image.h × 0.5 def degrees(radians): return (180 * redians) / math.pi while(True): clock.tick() img = sensor.snapshot() for tag in img.find_apriltags(fx = f_x, fy = f_y, cx = c_x, cy = c_y): img.draw_rectangle(tag.rect(), color = (255, 0, 0)) img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0)) print_args = (tag.x_translation(), tag.y_translation(), tag.z_translation(),degrees(tag.x_ratation()), degrees(tag.y_ratation()), degrees(tag.z_ratation())) #位置的单位是未知的,旋转的单位是角度 print("Tx:%f, Ty:%f, Tz:%f, Rx:%f, Ry:%f, Rz:%f" % print_args) #串口输出为六个变量:Tx,Ty,Tz为空间的三个位置量;Rx,Ry,Rz为三个旋转量 print(clock.fps())

OpenMV的文件系统

  • OpenMV中路径都是以根目录为起点。当插入SD卡后,根目录就是SD卡;不插入SD卡,根目录就是内置的Flash。
  • 如果需要,可以在SD卡上新建一个空文件:/flash/SKIPSD,这会避免挂载SD卡,也可以使用os.mount来手动挂载SD卡。

OpenMV的默认文件

  • 默认情况下,OpenMV的磁盘有三个文件: # main.py上电自动运行这个文件的代码 # openmv.infWindows驱动文件 # README.txt备注说明文件

OS模块

  • 在代码中,可以使用os库来进行新建目录、新建文件之类的操作: os.listdir([dir]) # 如果没有参数,列出当前目录;如果给了参数,就列出参数所代表的目录 os.chdir(path) # 改变当前目录 os.getcwd() # 获得当前目录 os.mkdir(path) # 新建一个新目录 os.remove(path) # 删除文件 os.rmdir(path) # 删除目录 os.rename(old_path, new_path) # 重命名文件 os.stat(path) # 获得文件或者路径的状态

pyb各种外设

  • OpenMV作为一个单片机,控制IO口、IIC、SPI、CAN、PWM、定时器当然都是可以的。 而且,使用python语言,可以非常简单的调用它们,而不用考虑寄存器。
  • OpenMV Cam M7 - OV7725 参数配置: #Pin10 #ADC/DAC1 #SPI1 #I2C2 #UART1 #Servo3 #CAN bus1 #ICSTM32F765 #RAM512K #Flash2MB #Max_Freq216MHz
  • 注意:因为MicroPython可以在很多平台上运行。最开始在pyb模块,pyboard,是基于STM32的,但是后来又加入了esp8266和esp32,以及nrf系列,他们的架构和STM32不同。所以官方统一制定了machine模块,所以通用性更高一些。最终pyb会被淘汰,但是目前pyb比machine功能要多。 本教程中,只有I2C使用了machine库。

pyb模块

  • 常用的函数: import pyb pyb.delay(50)#延时50毫秒 pyb.millis()#获取从启动开始计时的毫秒数
  • LED: from pyb import LED led_red = LED(1)#红色LED led_green = LED(2)#绿色LED led_blue = LED(3)#蓝色LED led_infrared = LED(4)#红外LED led_red.toggle() led_red.on()#点亮 led_red.off()#熄灭
  • I/O: from pyb import Pin p_out = Pin('P7', Pin.OUT_PP)#设置p_out为输出引脚 p_out.high()#设置p_out引脚为高电平 p_out.low()#设置p_out引脚为低电平 p_in = Pin('P7', Pin.IN, Pin.PULL_UP)#设置p_in为输入引脚,并开启上拉电阻 value = p_in.value()#读入p_in引脚的值(0/1)
  • Servo: from pyb import Servo s1 = Servo(1)#Servo on position 1 (P7:PD12) s2 = Servo(2)#Servo on position 2 (P8:PD13) s3 = Servo(3)#Servo on position 3 (P9:PD14) s1.angle(45)#move to 45 degrees s1.angle(-60, 1500)#move to -60 degrees in 1500ms s1.speed(50)#for continuous ratation servos
  • I/O中断: from pyb import Pin, ExtInt callback = lambda e: #中断事件 print("intr") ext = ExtInt(Pin('P7'), ExtInt.IRQ_RISING, Pin.PULL_NONE, callback)#注册中断事件
  • 定时器: from pyb import Timer tim = Timer(4, freq = 1000) tim.counter()#get counter value tim.freq(0.5)#0.5Hz tim.callback(lambda t: pyb.LED(1).toggle())#Timer Event #Timer 1 Channel 3 Negative -> P0 #Timer 1 Channel 2 Negative -> P1 #Timer 1 Channel 1 Negative -> P2 #Timer 2 Channel 3 Positive -> P4 #Timer 2 Channel 4 Positive -> P5 #Timer 2 Channel 1 Positive -> P6 #Timer 4 Channel 1 Negative -> P7 #Timer 4 Channel 3 Positive -> P8
  • PWM: from pyb import Pin, Timer p = Pin('P7')#P7 has TIM4, CH1 tim = Timer(4, freq = 1000) ch = tim.channel(1, Timer.PWM, pin = p) ch.pulse_width_percent(50)
  • ADC: from pyb import Pin, ADC adc = ADC('P6') adc.read()#read value, 0~4095
  • DAC: from pyb import Pin, DAC dac = DAC('P6') dac.write(120)#output between 0 and 255
  • UART: from pyb import UART uart = UART(3, 9600) uart.write('hello') uart.read(5)#read up to 5 bytes #UART 3 RX -> P5(PB11) #UART 3 TX -> P4(PB10) #UART 1 RX -> P0(PB15) #UART 1 TX -> P1(PB14)
  • SPI: from pyb import SPI spi = SPI(2, SPI.MASTER, baudrate = 200000, polarity = 1, phase = 0) spi.send('hello') spi.recv(5)#receive 5 bytes on the bus spi.send_recv('hello')#send a receive 5 bytes
  • I2C: from machine import I2C, Pin i2c = I2C(sda = Pin('P5'), scl = Pin('P4')) i2c.scan() i2c.writeto(0x42, b'123')#write 3 bytes to slave with 7-bit address 42 i2c.readfrom(0x42, 4)#read 4 bytes from slave with 7-bit address 42 i2c.readfrom_mem(0x42, 8, 3)#read 3 bytes from memory of slave 42, #starting at memory-address 8 in the slave i2c.writeto_mem(0x42, 2, b'\x10')#write 1 byte to memory of slave 42, #starting at address 2 in the slave #I2C 2 SCL(Serial Clock) -> P4(PB10) #I2C 2 SDA(Serial Data) -> P5(PB11) #I2C 4 SCL(Serial Clock) -> P7(PD13) #I2C 4 SDA(Serial Data) -> P8(PD12) #machine库是软件模拟的I2C协议,所以使用任何引脚都可以,但还是推荐使用上面所说的引脚。

json模块

  • 生成json字符串和解析json字符串 #生成json字符串:json.dumps(obj) #解析json字符串:json.loads(str) import json obj = [[12, 0], [10, 12], [22, 10], [99, 11]] print(json.dumps(obj))#生成json字符串 obj = { "number": 10, "color":[255, 0, 0], "rate":0.65 } print(json.dumps(obj)) #输出效果: '[[12, 0], [10, 12], [22, 10], [99, 11]]' '{"color": [255, 0, 0], "number": 10, "rate": 0.65}'

串口通信

  • TTL串口至少需要3根线:TXD,RXD,GND。TXD是发送端,RXD是接收端,GND是地线。 连线的时候,需要把OpenMV的RXD连到另一个MCU的TXD,TXD连到RXD。
  • 实例化一个19200波特率的串口3: import time from pyb import UART uart = UART(3, 19200) while(True): uart.write("Hello World!\r") time.sleep(1000) #注意:必须是串口3,因为OpenMV2只引出了这个串口,pyb的串口有好多个的。OpenMV3又增加了串口1。
  • 传输复杂的json数据: import sensor, image, time, json from pyb import UART sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QQVGA) sensor.frames(10) sensor.set_auto_whitebal(False) clock = time.clock() yellow_threshold = (65, 100, -10, 6, 24, 51) uart = UART(3, 115200) while(True): img = sensor.snapshot() blobs = img.find_blobs([yellow_threshold]) if blobs: print('sum:', len(blobs)) output_str = json.dumps(blobs) for b in blobs: img.draw_rectangle(b.rect()) img.draw_cross(b.cx(), b.cy()) print('you send:', output_str) uart.write(output_str + '\n') else: print('not found!')
  • 传输精简数据: # …… def find_max(blobs): max_size = 0 for blob in blobs: if blob.pixels() > max_size: max_size = blob.pixels() max_blob = blob return max_blob while(True): img = sensor.snapshot() blobs = img.find_blobs([yellow_threshold]) if blobs: max_blob = find_max(blobs) print('sum:', len(blobs)) img.draw_rectangle(max_blob.rect()) img.draw_cross(max_blob.cx(), max_blob.cy()) output_str = "[%d, %d]" % (max_blob.cx(), max_blob.cy())#方式一 #output_str = json.dumps([max_blob.cx(), max_blob.cy()])#方式二 print('you send:', output_str) uart.write(output_str + '\r\n') else: print('not found!')

各种电机

  • 步进电机控制程序: # ……
  • 直流电机 # ……
  • 舵机控制程序: import time from pyb import Servo s1 = Servo(1)#P7 s2 = Servo(2)#P8 while(True): for i in range(-90, 90): s1.angle(i) s2.angle(i) time.sleep(10) for i in range(90, -90): s1.angle(i) s2.angle(i) time.sleep(10)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 悠风的采坑日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说 明
  • Python基本数据类型
  • python循环语句
  • python模块
  • sensor模块
  • 获取/设置像素点
  • 获取图像的宽度和高度
  • 图像的运算
  • 使用图片的统计信息
  • 画图
  • 寻找色块
  • AprilTag标记追踪
  • OpenMV的文件系统
  • OpenMV的默认文件
  • OS模块
  • pyb各种外设
  • pyb模块
  • json模块
  • 串口通信
  • 各种电机
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档