标签归档:图像处理

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

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

视频