2020电子设计大赛——坡道行驶小车

简要

这里接触到了openmv,就先分享一下openmv的使用心得

openmv处理图像方法

感光元件
sensor模块,用于设置感光元件的参数。
举个例子:

import sensor#引入感光元件的模块

# 设置摄像头
sensor.reset()#初始化感光元件
sensor.set_pixformat(sensor.RGB565)#设置为彩色
sensor.set_framesize(sensor.QVGA)#设置图像的大小
sensor.skip_frames()#跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定。

# 一直拍照
while(True):
    img = sensor.snapshot()#拍摄一张照片,img为一个image对象
    

初始化

  • sensor.reset() 初始化感光元件

设置彩色/黑白

  • sensor.set_pixformat() 设置像素模式。
    • sensor.GRAYSCALE: 灰度,每个像素8bit。
    • sensor.RGB565: 彩色,每个像素16bit。

设置图像大小

  • sensor.set_framesize() 设置图像的大小

    sensor.QQCIF: 88x72
    sensor.QCIF: 176x144
    sensor.CIF: 352x288
    sensor.QQSIF: 88x60
    sensor.QSIF: 176x120
    sensor.SIF: 352x240
    sensor.QQQQVGA: 40x30
    sensor.QQQVGA: 80x60
    sensor.QQVGA: 160x120
    sensor.QVGA: 320x240
    sensor.VGA: 640x480
    sensor.HQQQVGA: 80x40
    sensor.HQQVGA: 160x80
    sensor.HQVGA: 240x160
    sensor.B64X32: 64x32 (用于帧差异 image.find_displacement())
    sensor.B64X64: 64x64 用于帧差异 image.find_displacement())
    sensor.B128X64: 128x64 (用于帧差异 image.find_displacement())
    sensor.B128X128: 128x128 (用于帧差异 image.find_displacement())
    sensor.LCD: 128x160 (用于LCD扩展板)
    sensor.QQVGA2: 128x160 (用于LCD扩展板)
    sensor.WVGA: 720x480 (用于 MT9V034)
    sensor.WVGA2:752x480 (用于 MT9V034)
    sensor.SVGA: 800x600 (仅用于 OV5640 感光元件)
    sensor.XGA: 1024x768 (仅用于 OV5640 感光元件)
    sensor.SXGA: 1280x1024 (仅用于 OV5640 感光元件)
    sensor.UXGA: 1600x1200 (仅用于 OV5640 感光元件)
    sensor.HD: 1280x720 (仅用于 OV5640 感光元件)
    sensor.FHD: 1920x1080 (仅用于 OV5640 感光元件)
    sensor.QHD: 2560x1440 (仅用于 OV5640 感光元件)
    sensor.QXGA: 2048x1536 (仅用于 OV5640 感光元件)
    sensor.WQXGA: 2560x1600 (仅用于 OV5640 感光元件)
    sensor.WQXGA2: 2592x1944 (仅用于 OV5640 感光元件)

跳过一些帧

  • sensor.skip_frames(n=10) 跳过n张照片,在更改设置后,跳过一些帧,等待感光元件变稳定

获取一张图像

  • sensor.snapshot() 拍摄一张照片,返回一个image对象

自动增益/白平衡/曝光

  • sensor.set_auto_gain() 自动增益开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动增益。

  • sensor.set_auto_whitebal() 自动白平衡开启(True)或者关闭(False)。在使用颜色追踪时,需要关闭自动白平衡。

  • sensor.set_auto_exposure(enable[, exposure_us]

    • enable 打开(True)或关闭(False)自动曝光。默认打开。
    • 如果 enable 为False, 则可以用 exposure_us 设置一个固定的曝光时间(以微秒为单位)。

python的类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

以Student类为例,在Python中,定义类是通过class关键字,定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过 类名+() 实现的:

class Student(object):#定义一个Student类
    pass

>>> bart = Student()#创建实例是通过 类名+()实现的!!!
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去:( 注意:特殊方法“init”前后分别有两个下划线!!! )

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

注意到__init__方法的第一个参数永远是self表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

接下来说说类的方法,面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入

>>> bart.print_score()
Bart Simpson: 59

这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

小结:这里看一个pid的例子

from pyb import millis
from math import pi, isnan

class PID:
    _kp = _ki = _kd = _integrator = _imax = 0
    _last_error = _last_derivative = _last_t = 0
    _RC = 1/(2 * pi * 20)
    def __init__(self, p=0, i=0, d=0, imax=0):
        self._kp = float(p)
        self._ki = float(i)
        self._kd = float(d)
        self._imax = abs(imax)
        self._last_derivative = float('nan')

    def get_pid(self, error, scaler):
        tnow = millis()
        dt = tnow - self._last_t
        output = 0
        if self._last_t == 0 or dt > 1000:
            dt = 0
            self.reset_I()
        self._last_t = tnow
        delta_time = float(dt) / float(1000)
        output += error * self._kp
        if abs(self._kd) > 0 and dt > 0:
            if isnan(self._last_derivative):
                derivative = 0
                self._last_derivative = 0
            else:
                derivative = (error - self._last_error) / delta_time
            derivative = self._last_derivative + \
                                     ((delta_time / (self._RC + delta_time)) * \
                                        (derivative - self._last_derivative))
            self._last_error = error
            self._last_derivative = derivative
            output += self._kd * derivative
        output *= scaler
        if abs(self._ki) > 0 and dt > 0:
            self._integrator += (error * self._ki) * scaler * delta_time
            if self._integrator < -self._imax: self._integrator = -self._imax
            elif self._integrator > self._imax: self._integrator = self._imax
            output += self._integrator
        return output
    def reset_I(self):
        self._integrator = 0
        self._last_derivative = float('nan')

然后在main.py中

from pid import PID   #从pid.py中引入PID类

。。。。。。

x_pid = PID(p=0.5, i=1, imax=100)#创建在水平方向上的pid实例
h_pid = PID(p=0.05, i=0.1, imax=50)#创建在垂直方向上的pid实例

。。。。。。

 x_output=x_pid.get_pid(x_error,1)#通过点·使用类的方法,得到水平方向上的pid控制量
 h_output=h_pid.get_pid(h_error,1)#通过点·使用类的方法,得到垂直方向上的pid控制量

注意:
import pyb就是引入pyb这个模块。通过import语句,就可以引入模块。

import pyb

red_led = pyb.LED(1) #不知道LED从哪来的,所以要用pyb.的形式

red_led.on()

还有from xxx import ooo 的语句,意思是通过xxx模块引入ooo类,或者通过xxx模块引入ooo函数。比如上面的程序可以写成:

from pyb import LED	#LED来自pyb,所以下面就不要pyb.

red_led = LED(1)

red_led.on()

mport pyb red_led = pyb.LED(1)

from pyb import LED =red_led = LED(1)

实例讲解


# Hello World 示例
#
# 欢迎使用OpenMV IDE!点击下面绿色的运行箭头按钮运行脚本!
#用usb线与电脑连接后,打开 文件——examples——01Basic——helloworld.py例程,点击上面的齿轮按钮运行。

import sensor, image, time
#引入此例程依赖的模块,sensor是与摄像头参数设置相关的模块,image是图像处理相关的模块,
#time时钟控制相关的模块。import相当于c语言的#include <>,模块相当于c语言的库。

sensor.reset()                      # 复位并初始化传感器。
#初始化摄像头,reset()是sensor模块里面的函数

sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE)
#设置图像色彩格式,有RGB565色彩图和GRAYSCALE灰度图两种

sensor.set_framesize(sensor.QVGA)   # 将图像大小设置为QVGA (320x240)
#设置图像像素大小,sensor.QQVGA: 160x120,sensor.QQVGA2: 128x160 (一般用于LCD
#扩展板),sensor.QVGA: 320x240,sensor.VGA: 640x480, sensor.QQCIF: 88x72,sensor.QCIF: 176x144,sensor.CIF: 352x288

sensor.skip_frames(time = 2000)     # 等待设置生效。
clock = time.clock()                # 创建一个时钟对象来跟踪FPS帧率。
#初始化时钟

while(True):
    #python while循环,一定不要忘记加冒号“:”
    clock.tick()                    # 更新FPS帧率时钟。
    img = sensor.snapshot()         # 拍一张照片并返回图像。
     #截取当前图像,存放于变量img中。注意python中的变量是动态类型,不需要声明定义,直接用即可。

    print(clock.fps())              
    # 注意: 当连接电脑后,OpenMV会变成一半的速度。当不连接电脑,帧率会增加。
    #打印当前的帧率。

  1. sensor摄像头模块:包含了感光芯片与图像预处理的各项操作.
  • sensor.reset() #重置并初始化OpenMV
  • sensor.set_pixformat(sensor.RGB565)#选择颜色空间与像素格式RGB565/GRAYSCALE,设定选择颜色空间为RGB, 选择像素格式为RGB565.颜色存储红色(8位)绿色(8位)蓝色(8位).各通道的取值范围0-255, RGB565就是R(5位)G(6位)B(5位)相对于普通的RGB进行压缩
  • sensor.set_framesize(sensor.QVGA)#选择窗口分辨率为320*240,VGA640×480,QQVGA160×120
  • sensor.skip_frames([n, time])# skip_frame() 一共有两种传参方式一种是设定跳过的帧数n:sensor.skip_frame(20),一种是指定等待的时间time单位为ms,sensor.skip_frames(time = 2000)
  • img = sensor.snapshot() #截取当前sensor读取的画面,返回一个Image对象赋值给img变量
  1. time计时模块,micropython的time模块与python标准库的time模块不是一个包, 这里的time模块主要起计时的作用,用于追踪过去的时间。
  • clock = time.clock()#创建一个 clock实例。
  • clock.tick()#开始计时,是开始计时追踪过去的时间。此操作就等同于摁一下手里的秒表,开始计时。
  • clock.fps()#这个函数做的事情是记录从clock.tick()开始。 中间做了一些其他操作, 然后到执行到clock.fps()这个函数之间一共花费了多久的时间, 我们记为t.然后再将这个时间t转换为帧率fps.

2020坡道行驶小车

这个礼拜把2020年的电赛坡道行驶小车回顾了一下,先看下题目
在这里插入图片描述
首先最麻烦的可能是430了,这点应该就劝退了很多人了,现在我也还没研究过msp430,但是感觉上手很快,这里我现在使用stm32做的。

要求:
在这里插入图片描述
看到这里,两个主要问题,首先是小车的循迹,其次是时间的控制。循迹方面可以采用红外对管或者摄像头,红外对管分为输出数字量和模拟量的,摄像头最容易上手的就是openmv了,时间上用pid控制编码器电机达到指定速度,从而满足任意指定时间的要求。

这里我最开始的是用舵机转向的车模,用的是模拟量输出的红外三路循迹,由于我的车子不是很宽,所以舵机在圆弧拐弯处拐不过来,会压线,后来改用openmv,使用了例程的linear_regression_fast 快速线性回归,图像二值化后,使用快速线性回归,用一条回归直线距离屏幕中央的距离和角度来表征小车偏离黑线的程度,然后进行控制,但是这里哪怕舵机提前已经打到底了,拐弯还会压线,后来我把后轮的两个电机往外扩展了,后面车宽了些,但是前轮舵机不好扩,打算放弃这辆车模了,重新搭一个车,说了这么多,硬件结构很重要,首先得把车子搭起来,其中最重要的一点就是车子足够宽,但是不能超过题目要求,其次是车子尽量重,也是不能超过题目要求,重心也要分布好,保证上坡时上的坡度能够尽量大而轮胎不打滑。

我的想法是尽量宽的地盘,后面装上两个编码器直流电机,前轮用一个万向轮代替,用数字式红外对管就好了,当检测车子偏右了,右边的轮子停止,左边的轮子接着前进,使得车子转向

还有最重要的是时间的控制,这里就得需要编码器了,我测了一下轮胎的周长,用定时器5ms测量编码器脉冲数,通过脉冲数知道轮子转了多少圈,通过周长可知在5ms内轮子走了多远,从而算出速度。这里我用了p控制,就基本可以控制速度在预期之内,比如,赛道长300cm,5ms内轮胎转10圈,轮子周长是20cm,那么可以算出速度V来,300cm➗V,可以知道时间,反过来,已知所需要的时间,反算出速度V’,再反算出5ms内轮胎转的圈数,用pid控制该转的圈数,使其能在任何坡度下都可以达到预期的转速,我用的蓝牙来控制预期转速,这里转速以m/s会有小数,可以乘以1000来变成整型,在数据传输时比较方便,这里pid用增量式pid,光是p控制就可以满足要求了,即便实在很陡的坡下,只要摩擦力足够,设置好预期转速,则速度始终不变

在这里插入图片描述
这里摄像头不好固定就这能这样了。。。
————————————————————————

在这里插入图片描述这里用黑线代替了题目中黑白相间的线,如果是黑白相间的线,在拐弯处小车可能会直接开出去,可能存在一定偶然性,也可以改用openmv,只是用舵机的话得把车地盘扩

————————————————————————

在这里插入图片描述
在这里插入图片描述
再分享一下我们小队再2020电赛A题无限运动传感器的作品
————————————————————————

在这里插入图片描述

在测试中,很荣幸所有指标都能够达到,最后也是取得了湖南省一等奖的成绩,最近没什么精力忙着写这些以前的记录了,这些分享好早之前就想发出来了,一直在草稿箱了,没什么时间完善,感觉这次也有点潦草赶时间了,今天就想着把以前的草稿箱中的内容完善分享出来,有些作品的照片当时也没拍照记录,然后又被我拆了。。。日后再慢慢完善吧,到时候把关于电赛的一些作品再分享给大家。

  • 8
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值