分类目录归档:机器人

解魔方机器人[二]-基本动作(上)

要想让机器人完成解魔方的各种动作,我们先来分解每个马达的最基本的动作,然后通过这些基本的动作组合实现机器人更复杂的动作指令。根据马达的不同位置和作用,我们将EV3 MindCub3r的三个马达命名为:底座马达、手臂马达和探头马达。

底座马达基本动作

底座马达负责转动底座,自身可以完成魔方的旋转、配合手臂可以完成拧魔方最底层、配合探头可以完成魔方各个色块的颜色扫描。因此,底座马达的基本动作可以总结为:

  • 顺时针旋转90度
  • 逆时针旋转90度
  • 顺时针旋转180度(也可以采用逆时针,这里使用顺时针)
  • 顺时针旋转45度(也可以采用逆时针,区别在于色块扫描的顺序,这里使用顺时针)

第一个问题:如何让底座转90度?

仔细观察搭建图不难发现,动力系统是通过安装在大型马达上的一个小齿轮,带动底座下面的一个大齿轮完成的,并不很复杂。两个齿轮的传动比为1:3,即小齿轮转动3圈相当于大齿轮转动1圈。因此如果要底座转动90度,马达应转动270度,LEGO EV3的大型马达转1圈相应的走360步、即1步等于1度。

齿轮传动比1:3

齿轮传动比1:3

第二个问题:如何表示马达的运行位置?

EV3的马达属于伺服马达,这种马达的优势就是可以通过转动的步数精确计算马达的位置(position),无论马达处在停止还是运行状态都可以读取当前位置。对于位置可以这样理解:设想马达刚连接到EV3端口上,此时的位置为0;当顺时针转动一圈之后,此时的位置是360;再逆时针转一圈,位置又回到0;如果继续逆时针转动,位置就会变为相应的负数。如果持续的转动,位置的值也会持续的增加(或减少)。

对于马达运行位置的表示,EV3提供了两种方式:绝对位置、相对位置:

  • 绝对位置:采用这种表示方式,无论马达此时的位置值是多少,都会运行到设置的位置值。比如:此时马达的位置值是360,设置运行到绝对位置90,马达不会顺时针转动90步,而是逆时针转动270步,之后位置值变为90。
  • 相对位置:采用这种表示方式,运行的位置只是相对马达当前位置的位移。比如:此时马达的位置值是360,设置运行到绝对位置90,马达将会顺时针转动90步,之后位置值变为450。

对于底座马达,我们需要采用相对位置的运行方式。

第三个问题:马达每次停止都有一些位移误差?

显然,这种位移误差对于需要精准控制底座角度的需求是无法容忍的。要想解决这个问题,需要先了解EV3马达的停止模式。EV3马达有三种停止模式:coast、brake、hold:

  • coast模式:马达得到停止指令时,停止供电,随惯性再转会停止。
  • brake模式:马达得到停止指令时,停止供电,立刻停止转动,制动距离较coast模式要短得多。
  • hold模式:马达得到停止指令时,马达并不会停止供电,而是将马达稳稳的保持在停止时的位置,此时如果你用外力强制改变他的位置,马达会强有力的跟你较劲,还是会转回它的位置。

综上,使用hold模式能够更精准的控制马达所转动的角度,也不会使底座发生“变形”。

最后:马达的速度设置

EV3马达的速度有两种表达方式:速度值、占空比:

  • 速度值:马达的速度值单位为:步/秒,由于EV3马达一圈正好360步,因此等同于:角度/秒。
  • 占空比:使用占空比表达马达的速度,取值范围-100%到100%,100%为顺时针最大速度。

这两种方式对于底座马达都可以,通常如果对速度的精度需求不那么大的情况下,可以采用占空比的方式。

程序设计

import ev3dev

# 底座的EV3大型马达接入主控制器A口
base = ev3dev.large_motor('outA')

if base.connected == True:
  # 设置速度控制模式为占空比
  base.speed_regulation_enabled = 'off'
  # 设置马达停止模式为hold
  base.stop_command = 'hold'

# 底座顺时针旋转90度,占空比75%
def base_clockwise():
   base.run_to_rel_pos(position_sp=270, duty_cycle_sp=75)
   while 'holding' not in base.state: time.sleep(0.2)

# 底座逆时针旋转90度,占空比75%
def base_anticlockwise():
  base.run_to_rel_pos(position_sp=-270, duty_cycle_sp=75)
  while 'holding' not in base.state: time.sleep(0.2)

# 底座顺时针旋转45度,占空比30%
def base_step():
  base.run_to_rel_pos(position_sp=135, duty_cycle_sp=30)
  while 'holding' not in base.state: time.sleep(0.2)

解魔方机器人[一]-方案设计

目标

机器人解魔方的例子在网上已经有过很多,对于LEGO MINDSTORMS来说,从NXT开始就已经有了,EV3推出后许多玩家更是将解魔方机器人的搭建当成玩EV3的代表作品。那么机器人究竟是如何完成解魔方这件事情的,这是导致我这次选题来做的原因。

可惜我对工业设计并不了解,魔方的整体搭建基于David Gilday的结构,搭建图纸可以到他的官方网站下载:http://mindcuber.com

mindcub3r

本系列文章主要介绍程序设计方面的内容,一起探索如何从0开始设计解魔方机器人的程序实现。

设计思路

初次接触解魔方机器人可能会觉得无从下手,因此我们需要进行任务分解,将复杂的大任务逐步分解为简单的小任务。这种“解决问题”的系统都理解为三部分组成:输入、计算、输出,按照这种思路整理如下:

Input System Output
Scan
魔方扫描
Compute
算法计算
Solve
转动解决

动作分解

在分解动作之前,先来看看机器人所拥有的“能力”。EV3连接的主要设备包括:颜色传感器1个、大型马达2个、中型马达1个。各个设备的作用:

  • 颜色传感器:扫描魔方
  • 大型马达A:转动底座
  • 大型马达B:转动手臂
  • 中型马达:转动扫描头

而通过这些设备的组合,可以实现机器人所需要的基本动作,比如:拧魔方、翻转魔方、扫描每个色块。

接下来按照之前讨论的设计思路,对系统的三个部分分别进行动作分解。

输入-魔方扫描

先来想象一下扫描魔方的过程:扫描第1个面、翻转、扫描第2个面、翻转……扫描第6个面。

再想象扫描单面的过程:扫描中心块1、旋转45度、扫描角块2、宣传45度、扫描边块3……扫描边块9。

而关于翻转魔方,由于EV3机器人只能从一个方向翻转魔方,当翻转到第4个面后,需要将魔方旋转90度后才能继续将第5、第6个面翻转上来。

如果理解了扫描魔方的动作过程,那么每个马达所需要做的动作也就容易得出:

  • 大型马达(底座):顺时针转90度、逆时针转90度、顺时针转45度、逆时针转45度
  • 大型马达(手臂):抬起、扣住、后拉
  • 中型马达:定位中心块、定位角块、定位边块

计算-算法计算

通常算法是一些理论的实现,而工程设计是要将算法与实际相结合。就拿解魔方为例,解魔方的算法早已经得出,而计算机程序设计也已经实现,但EV3机器人若想应用这些算法和程序,就要做相应的兼容接口,为算法和机器人充当翻译的角色。

实现解魔方算法的程序往往已经有特定的输入与输出,因此EV3要做的第一件事就是要将扫描到的结果转化为与算法相同的表示方式;而计算之后还要将算法的输出转化为EV3能够识别的机器人动作。

不难想象,在这一步所需的动作分解:

  • 魔方表示方法的转化
  • 算法实现
  • 算法嵌入EV3程序

输出-转动解决

最后就是将已计算出的解魔方步骤转化为机器人动作,EV3的动作无法像人一样十指齐发多维度拧魔方,只能通过两个大型马达的配合每次拧最下面的一层,如果需要拧其他面就得先通过旋转、翻转把要拧的那一面挪到最下面才行。

魔方一共6个面,所以输出的动作分解可以理解为:

  • 拧正面:顺时针90度、逆时针90度、180度
  • 拧背面:顺时针90度、逆时针90度、180度
  • 拧上面:顺时针90度、逆时针90度、180度
  • 拧下面:顺时针90度、逆时针90度、180度
  • 拧左面:顺时针90度、逆时针90度、180度
  • 拧右面:顺时针90度、逆时针90度、180度

小结

通过本文可以从整体上对EV3解魔方的流程、环节、动作有一定了解,这些对下一步的任务实现具有非常重要的作用,尤其是对输入-计算-输出的理解,能够帮助我们在程序设计时能更加清晰的找到关键问题。

ev3dev手册[四]-专用传感器

Special sensor classes/专用传感器类

The classes derive from Sensor and provide helper functions specific to the corresponding sensor type. Each of the functions makes sure the sensor is in the required mode and then returns the specified value.
这些类继承了Sensor,针对相应的传感器类型提供了专门的函数便于使用。所有函数均能够保证所使用的传感器在正确模式下工作,并返回符合规定的值。

Touch Sensor/触碰传感器

class Touch_Sensor/Touch_Sensor

Touch Sensor/触碰传感器

inherits from/父类: sensor

Target driver(s)/目标驱动: lego-ev3-touch, lego-nxt-touch

Special properties/参数

Is_Pressed
boolean, read

A boolean indicating whether the current touch sensor is being pressed.
用于识别触碰传感器是否被按下。

Required mode/模式: TOUCH

Value index/索引值: 0

Color Sensor/颜色传感器

class Color_Sensor/Color_Sensor

LEGO EV3 color sensor.
LEGO EV3 颜色传感器。

inherits from/父类: sensor

Target driver(s)/驱动: lego-ev3-color

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-ev3-color-sensor/

Special properties/参数

Reflected_Light_Intensity
int, read

Reflected light intensity as a percentage. Light on sensor is red.
通过百分比表示反射光强度,此时传感器发出红色光。

Required mode/模式: COL-REFLECT

Value index/索引值: 0

Ambient_Light_Intensity
int, read

Ambient light intensity. Light on sensor is dimly lit blue.
环境光强度。此时传感器发出蓝光。

Required mode/模式: COL-AMBIENT

Value index/索引值: 0

Color
int, read

Color detected by the sensor, categorized by overall value.
传感器所识别的颜色,不同数字表示不同的颜色。

  • 0: No color/无颜色(未识别)
  • 1: Black/黑
  • 2: Blue/蓝
  • 3: Green/绿
  • 4: Yellow/黄
  • 5: Red/红
  • 6: White/白
  • 7: Brown/棕

Required mode/模式: COL-COLOR

Value index/索引值: 0

Red
int, read

Red component of the detected color, in the range 0-1020.
所采集颜色的红色分量,取值范围:0-1020。

Required mode/模式: RGB-RAW

Value index/索引值: 0

Green
int, read

Green component of the detected color, in the range 0-1020.
所采集颜色的绿色分量,取值范围:0-1020。

Required mode/模式: RGB-RAW

Value index/索引值: 1

Blue
int, read

Blue component of the detected color, in the range 0-1020.
所采集颜色的蓝色分量,取值范围:0-1020。

Required mode/模式: RGB-RAW

Value index/索引值: 2

Ultrasonic Sensor/超声波传感器

class Ultrasonic_Sensor/Ultrasonic_Sensor

LEGO EV3 ultrasonic sensor.
LEGO EV3超声波传感器。

inherits from/父类: sensor

Target driver(s)/驱动: lego-ev3-us, lego-nxt-us

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-ev3-ultrasonic-sensor/

Special properties/参数

Distance_Centimeters
float, read

Measurement of the distance detected by the sensor, in centimeters.
传感器的距离测量值,单位:厘米。

Required mode/模式: US-DIST-CM

Value index/索引值: 0

Distance_Inches
float, read

Measurement of the distance detected by the sensor, in inches.
传感器的距离测量值,单位:英寸。

Required mode/模式: US-DIST-IN

Value index/索引值: 0

Other_Sensor_Present
boolean, read

Value indicating whether another ultrasonic sensor could be heard nearby.
用于识别附近是否有其他的超声波传感器。

Required mode/模式: US-LISTEN

Value index/索引值: 0

Gyro Sensor/陀螺仪传感器

class Gyro_Sensor/Gyro_Sensor

LEGO EV3 gyro sensor.
LEGO EV3 陀螺仪传感器。

inherits from/父类: sensor

Target driver(s)/驱动: lego-ev3-gyro

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-ev3-gyro-sensor/

Special properties/参数

Angle
int, read

The number of degrees that the sensor has been rotated since it was put into this mode.
返回在此模式下,传感器转过的角度。

Required mode/模式: GYRO-ANG

Value index/索引值: 0

Rate
int, read

The rate at which the sensor is rotating, in degrees/second.
传感器旋转的速度,单位:角度/秒。

Required mode/模式: GYRO-RATE

Value index/索引值: 0

Infrared Sensor/红外传感器

class Infrared_Sensor/Infrared_Sensor

LEGO EV3 infrared sensor.
LEGO EV3红外传感器。

inherits from/父类: sensor

Target driver(s)/驱动: lego-ev3-ir

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-ev3-infrared-sensor/

Special properties/参数

Proximity
int, read

A measurement of the distance between the sensor and the remote, as a percentage. 100% is approximately 70cm/27in.
返回传感器和信标之间的距离,单位:百分比,当值为100%时距离大约为70cm/27in。

Required mode/模式: IR-PROX

Value index/索引值: 0

Sound Sensor/声波传感器

class Sound_Sensor/Sound_Sensor

LEGO NXT Sound Sensor
LEGO NXT声波传感器

inherits from/父类: sensor

Target driver(s)/驱动: lego-nxt-sound

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-nxt-sound-sensor/

Special properties/参数

Sound_Pressure
float, read

A measurement of the measured sound pressure level, as a percent. Uses a flat weighting.
返回声音强度的测量值,单位:百分比。使用的加权系数保持平衡。

Required mode/模式: DB

Value index/索引值: 0

Sound_Pressure_Low
float, read

A measurement of the measured sound pressure level, as a percent. Uses A-weighting, which focuses on levels up to 55 dB.
返回声音强度的测量值,单位:百分比。使用的加权系数侧重55dB信号范围。

Required mode/模式: DBA

Value index/索引值: 0

Light Sensor/光传感器

class Light_Sensor/Light_Sensor

LEGO NXT Light Sensor
LEGO NXT光传感器

inherits from/父类: sensor

Target driver(s)/驱动: lego-nxt-light

ev3dev docs link/ev3dev文档链接: http://www.ev3dev.org/docs/sensors/lego-nxt-light-sensor/

Special properties/参数

Reflected_Light_Intensity
float, read

A measurement of the reflected light intensity, as a percentage.
反射光强度测量值,单位:百分比。

Required mode/模式: REFLECT

Value index/索引值: 0

Ambient_Light_Intensity
float, read

A measurement of the ambient light intensity, as a percentage.
环境光强度测量值,单位:百分比。

Required mode/模式: AMBIENT

Value index/索引值: 0

HSL和HSV色彩空间

基本概念

HSL和HSV都是一种将RGB色彩模型中的点在圆柱坐标系中的表示法。这两种表示法试图做到比RGB基于笛卡尔坐标系的几何结构更加直观。HSL即色相、饱和度、亮度(英语:Hue, Saturation, Lightness),又称HSL。HSV即色相、饱和度、明度(英语:Hue, Saturation, Value),又称HSB,其中B即英语:Brightness。

  • H:色相是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
  • S:饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取0-100%的数值。
  • V:明度(V),亮度(L),取0-100%。

HSL和HSV二者都把颜色描述在圆柱坐标系内的点,这个圆柱的中心轴取值为自底部的黑色到顶部的白色而在它们中间的是灰色,绕这个轴的角度对应于“色相”,到这个轴的距离对应于“饱和度”,而沿着这个轴的高度对应于“亮度”,“色调”或“明度”。

这两种表示在目的上类似,但在方法上有区别。二者在数学上都是圆柱,但HSV(色相,饱和度,明度)在概念上可以被认为是颜色的倒圆锥体(黑点在下顶点,白色在上底面圆心),HSL在概念上表示了一个双圆锥体和圆球体(白色在上顶点,黑色在下顶点,最大横切面的圆心是半程灰色)。注意:尽管在HSL和HSV中“色相”指称相同的性质,它们的“饱和度”的定义是明显不同的。

HSL和HSV的色彩空间

HSL和HSV的色彩空间

为什么考虑这种色彩空间

大多数电视机、显示器、投影仪通过将不同强度的红、绿、蓝色光混合来生成不同的颜色,这就是RGB三原色的加色法。通过这种方法可以在RGB色彩空间生成大量不同的颜色,然而,这三种颜色分量的取值与所生成的颜色之间的联系并不直观。

设计者们有时偏好使用HSL或HSV而不选择三原色光模式(即RGB模型)或 印刷四分色模式(即CMYK模型),因为它类似于人类感觉颜色的方式,具有较强的感知度。RGB和CMYK分别是加法原色和减法原色模型,以原色组合的方式定义颜色,而HSV以人类更熟悉的方式封装了关于颜色的信息:“这是什么颜色?深浅如何?明暗如何?”。

主要用途

HSV模型通常用于计算机图形应用中。在用户必须选择一个颜色应用于特定图形元素各种应用环境中,经常使用HSV 色轮。在其中,色相表示为圆环;可以使用一个独立的三角形来表示饱和度和明度。典型的,这个三角形的垂直轴指示饱和度,而水平轴表示明度。在这种方式下,选择颜色可以首先在圆环中选择色相,在从三角形中选择想要的饱和度和明度。

HSV的平面表示

HSV的平面表示

HSV模型的另一种可视方法是圆锥体。在这种表示中,色相被表示为绕圆锥中心轴的角度,饱和度被表示为从圆锥的横截面的圆心到这个点的距离,明度被表示为从圆锥的横截面的圆心到顶点的距离。某些表示使用了六棱锥体。这种方法更适合在一个单一物体中展示这个HSV色彩空间;但是由于它的三维本质,它不适合在二维计算机界面中选择颜色。

HSV的表示2

HSV的立体表示

由于HSL、HSV关于色相的划分与人眼识别是连续的,而RGB对人眼而言没那么只管,通常我们可以使用HSL、HSV进行计算机图像处理中的颜色分割,比如识别某个区域内“蓝色”系的物体等。

蓝色球体监测跟踪

蓝色球体监测跟踪

转换方式

RGB到HSL、HSV的转换

设 (r, g, b)分别是一个颜色的红、绿和蓝坐标,它们的值是在0到1之间的实数。设max等价于r, g和b中的最大者。设min等于这些值中的最小者。要找到在HSL空间中的 (h, s, l)值,这里的h ∈ [0, 360)度是角度的色相角,而s, l ∈ [0,1]是饱和度和亮度,计算为:

RGB转HSL

RGB转HSL

h的值通常规范化到位于0到360°之间。而h = 0用于max = min的(就是灰色)时候而不是留下h未定义。HSL和HSV有同样的色相定义,但是其他分量不同。HSV颜色的s和v的值定义如下:

RGB转HSV

RGB转HSV

同样,HSL、HSV也可以转换回RGB,在此不做介绍,可参考:维基百科:HSL和HSV色彩空间

程序实现

Python程序实现

程序的实现很简单,不考虑程序优化,根据公式直译即可:

# 函数名:rgb2hsv(rgb)
# 功能:RGB转换为HSV
# 输入:像素点的RGB值
# 输出:像素点的HSV值
def rgb2hsv(rgb): 
  rgbmax = max(rgb[:])
  rgbmin = min(rgb[:])
  r, g, b = rgb[:]
  h, s, v = 0

  if rgbmax == rgbmin:
    h = 0
  elif (rgbmax==r) and (g>=b):
    h = 60*(g-b)/(rgbmax-rgbmin)+0
  elif (rgbmax==r) and (g<b):
    h = 60*(g-b)/(rgbmax-rgbmin)+360
  elif rgbmax == g:
    h = 60*(b-r)/(rgbmax-rgbmin)+120
  elif rgbmax == b:
    h = 60*(r-g)/(rgbmax-rgbmin)+240

  v = rgbmax/255

  if rgbmax == 0:
    s = 0
  else:
    s = (rgbmax-rgbmin)/rgbmax
 
  hsv = [h, s, v]
  return hsv

OpenCV实现

OpenCV函数:cvtColor(src, flag)

输入:src为要进行转换的RGB图像;flag为转换参数,RGB转换HSV的参数是COLOR_RGB2HSV,也就是说通过不同的flag属性,这个函数还支持其他色彩格式的转换,详见OpenCV手册

输出:色彩格式转换后的图像。

程序示例:

# Filename: rgb2hsvbycv2.py
# 载入OpenCV模块
import cv2
# 读取一副图像
img = cv2.imread('motor.png')
# 将图像色彩格式转换为HSV
img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)

我们在进行程序设计的时候,通常尽量去使用已经创建好的函数库,而不是自己动手来写。原因很简单,通用的函数库已经充分做了程序优化,效率更高。

OpenCV介绍

OpenCV(Open Source Computer Vision) 是一个开源的计算机视觉函数库,于1999年由Intel建立,如今由Willow Garage提供支持,可以运行在Linux、Windows和Mac OS等桌面操作系统,及Android、iOS、Maemo、BlackBerry10等移动操作系统上。

 

OpenCV

OpenCV

OpenCV的应用领域包括:

  • 人机互动
  • 物体识别
  • 图象分割
  • 人脸识别
  • 动作识别
  • 运动跟踪
  • 机器学习
  • 运动分析
  • 机器视觉
  • 结构分析

OpenCV用C++语言编写,它的主要接口也是C++,但是依然保留了大量的C接口,函数库轻量级且高效。该库也有大量的Python, Java、MATLAB/OCTAVE接口,如今又增加了对C#、Perl、Ch、Ruby的支持,实现了图像处理和计算机视觉方面丰富的通用算法。

官方网站:http://opencv.org

中文网站:http://www.opencv.org.cn

 

数字识别机器人

项目描述

机器人通过色彩传感器对写在纸上的数字进行逐行扫描,之后通过图像处理识别出所扫描的数字,并显示在屏幕上。其实就是扫描仪+字符识别啦!本项目中使用了OpenCV模块强大的图像处理功能。

机器人搭建

数字识别机器人

如果需要搭建图,可以看这里

流程设计

第一步 第二步 第三步 第四步 第五步 第六步
扫描 二值化 滤波 切割 标准化 模板匹配
  1. 扫描:色彩传感器(反射光模式)每次只能读取一个点的亮度信息,利用横向的马达装置可以完成一行线的扫描,再利用纵向的马达装置就完成一个面的扫描,也就是完成了整幅图片的扫描。扫描的信息存储在一个178×128的二维数组中(EV3屏幕分辨率),每个像素的亮度信息是0-100(反射光传感器的读值范围)。
  2. 二值化:利用OTSU算法将0-100的“灰度图像”转换为二值图像,便于后期的处理。
  3. 滤波:二值化后的图像存在许多“噪声”,利用3×3中值滤波,将这些噪声滤除,留下的才是扫描到要识别的真正图像。
  4. 切割:将扫描图像中,要识别的数字部分单独切割出来,便于图像识别。
  5. 标准化:为了与识别模板进行匹配,需要对切割后的图像按照指定尺寸进行标准化转换。
  6. 模板匹配:将标准化后的图像,与0-9的数字模板图像一一进行比对,对两幅图像每个相同位置的像素值做异或运算,计算累计的差异值,差异值最小的被认为是匹配度最高的,也就是识别到的数字结果。

程序设计

用到的模块

from ev3dev import *    #ev3dev主模块
import cv2              #OpenCV模块,用于图像处理
import numpy as np      #NumPy模块,用于数组操作

1. 图像扫描

我们可以使用光传感器(Color Sensor)作为机器人的“眼睛”,读取所看到的反射光强度从而知道图像的模样。

#连接光传感器并设置为反射光模式
cs = color_sensor()
cs.mode = 'COL-REFLECT'

可惜反射光传感器每次只能看到一个“点”的亮度,而我们需要的是一个“面”的图像,因此采用逐行扫描的原理通过“点-线-面”依次把图像扫描出来。这样就需要横向、纵向两个马达配合,实现逐行扫描。我们使用中型马达进行横向扫描、使用两个大型马达控制纵向移动。

#变量定义:大型马达lmotor、rmotor,中型马达mmotor
#EV3的LCD尺寸:178*128
#横向扫描函数,返回x方向的数组
def scan_x():
  x_label = np.zeros(178, np.uint8)
  #马达移动到最边
  while mmotor.position != 90:
    mmotor.run_to_abs_pos(position_sp=90)
  #扫描反射光读值
  while mmotor.position != -90:
    mmotor.run_to_abs_pos(position_sp=-90)
    width = 90-mmotor.position
    #扫描范围符合LCD的宽度
    if width>=0 and width<=177:
      x_label[width] = cs.value()
  #马达位置归零
  while mmotor.position !=0:
    mmotor.run_to_abs_pos(position_sp=0)
  #返回扫描值
  return x_label

#纵向移动,扫描整幅图像
def scan_xy():
  move_step = 0
  xy_result = np.zeros([128,178], np.uint8)
  while lmotor.position < 120:
    #扫描每一行
    scan_result = scan_x()
    #由于采用的是自下而上扫描,因此在数组操作上从最后一行开始
    for i in range(128-move_step-1, 128-(move_step+8)-1,-1):
      xy_result[i,:] = scan_result
    move_step += 8
    #由于机器人通过两个马达控制移动,纵向马达向上同时走一步
    lmotor.run_to_abs_pos(position_sp=move_step)
    rmotor.run_to_abs_pos(position_sp=move_step)
  #扫描完毕,机器人位置归零
  while lmotor.position !=0:
    lmotor.run_to_abs_pos(position_sp=0)
    rmotor.run_to_abs_pos(position_sp=0)
  #返回扫描结果
  return xy_result

2. 二值化

反射光传感器读取的亮度值范围(0~100),为了进行图像识别,首先就是要做二值化,把图像改成“非黑即白”的纯黑白图像。二值化的算法很多,在此我们使用全局亮度均值作为二值化阈值,并利用OpenCV的threshold函数实现图像二值化。

#扫描图像
img = scan_xy()

#计算整幅图像所有点的亮度平均值
for y in range(128):
  for x in range(178):
    total += img[y,x]
mean = total / (178*128)

#将平均值作为二值化的阈值,img_bin为二值化后的图像
ret, img_bin = cv2.threshold(img, mean, 255, cv2.THRESH_BINARY_INV)

3. 滤波

此时的图像除了我们扫描到的数字以外还存在许多噪点,即进行识别的图像并不“干净”,因此需要通过“滤波器”将这些噪点过滤掉。我们使用图像腐蚀算法进行滤波。

#图像滤波,使用3x3滤波器进行一次腐蚀,img_ers为滤波后的图像
kernel = np.ones((3,3), np.uint8)
img_ers = cv2.erode(img_bin, kernel, iterations=1)

4. 图像切割

此时的图像,在我们要识别的数字周围还存在许多空白,因此我们要将最有用的信息提取出来,即只保留中间的数字而扔掉没用的空白。这就涉及到图像切割技术,OpenCV的findContours函数可以方便的实现轮廓定位。

#由于findContours函数会改变原图像,因此做一份图像拷贝用于轮廓定位
img_ers_c = img_ers.copy()

#提取最外面的轮廓,并使用方形框定位
contours, hierarchy = cv2.findContours(img_ers_c, cv2.CV_RETR_EXTERNAL, cv2.CV_CHAIN_APPROX_SIMPLE)
cnt = contours[0]
#读取轮廓定位的起始位置(x,y),及宽w和高h
x,y,w,h = cv2.boundingRect(cnt)

#切割图像
img_cut = img_ers[y:(y+h), x:(x+w)]

5. 标准化

进行数字识别的思路是,将标准的0-9每个数字分别做成一个模板,然后将图像与各个数字模板分别进行比对,差异最小的那个认为就是识别到的数字。所以我们需要对切割图像的尺寸进行调整,使之与模板的大小一致,也就是所谓的标准化。OpenCV的resize函数用于改变图像尺寸。

#标准化模板尺寸为20*40
img_norm = cv2.resize(img_cut, (20,40), cv2.INTER_CUBIC)

6. 模板匹配

刚刚说了数字识别的思路就是用图像与每个数字模板做比对看差异化,实际可以把每个像素点的值做与运算,最终累加为整幅图像比对的差异值,差异值越小说明图像与模板的匹配度越高。在这里需要提前分别制作0-9十个数字的标准模板,可以自己用画图软件画一个,保证为二值黑白图像,长款为:40:20。

0到9

error = np.zeros(10)
for numcode in range(0,10):
  #读取各个数字的标准模板
  fname = './'+str(numcode)+'.jpg'
  img_mod = cv2.imread(fname, 0)
  #将图像与模板做按位与运算
  pix_and = cv2.bitwise_and(img_norm, img_mod)

  #记录每个模板匹配后的差异值
  pix_err = 0
  for h in range(0,40):
    for w in range(0,20):
      if pix_and[h, w]:
        pix_err += 1
  error[numcode] = pix_err

#差异值最小的模板所对应的数字,即为识别的数字
error_min = min(error)
num_recognize = 0
for i in range(10):
  if error[i] == error_min:
  num_recognize = i
  break

print "THE NUMBER IS %d:", num_recognize

程序完整代码,变量名称可能会跟上文有些差异。

视频