第二章 初识机器视觉

2.1 简介

【知识目标】

【技能目标】 - 熟悉视觉环境运行程序 - 熟悉视觉的基础接口

【项目描述】 学习视觉的基本概念与原理,掌握视觉的基本操作和实现。可以完成本地图片的显示与处理,以及摄像头视频的显示与处理。

2.2 知识点

2.2.1 像素

什么是像素呢?首先要说的是每个图像都是由像素组成的,像素是图像的原始构建块,没有比像素更精细的粒度了。 通常情况下,我们认为像素是出现在图像中的某一个特定位置的光的“颜色”或“强度”。 如果我们把一个图像看做是一个网格,网络中的每个方块就是一个独立的像素。 比如我们一个张500x300的图片,这就意味着这个像素的网格是500行和300列的,总共有500×300=150,000像素在这个图片里。 大多数像素使用两种方式来表示:灰度和颜色。 在灰度图像中,每个像素都是介于0和255之间,0代表黑色,255代表白色。其中越接近0就越深,越接近255就越浅。 颜色像素通常使用RGB表示。R表示红色,G表示绿色,B表示蓝色,也存在其它颜色空间。这三个颜色值都是0和255之间。白色为(255,255,255),黑色为(0,0,0),纯色红色就是(255,0,0),等等。

2.2.2 坐标系

正如前面所说,图像可以表示为像素的网格,(0,0)对应于图像的左上角,当我们向下和向右移动时,x和y值都会分别增加。我们下面的一张图就可以清晰理解什么是坐标系。 这个是一个9×9的网格,总共有81个像素,(0,0)代表着左上角,然而(9,9) 代表着右下角,(4,3)表示第5列,第4行的像素点,所以我们要记住索引是从0开始的而不是1。这就意味着以后我们要从0开始数,否则我们会算错位置。

在这里插入图片描述

2.2 图片的显示

首先通过PyCharm创建工程,点击菜单“File”选择“CreateProject”,在弹出的对话框中输入工程的名字比如OPenCV。 然后创建成功后在工程文件夹右键点击选择弹出菜单中的“New”再选择“PythonFile”,最后输入文件名字即可,比如cv_bridge_hello.py。 成功创建Python文件后我们就可以开始编程了。

关键方法介绍

读取图像OpenCV提供了方法cv2.imread来读取图像,该方法支持各种静态图像格式。该方法的语法格式为: retval=cv2.imread(filename[,flags]) 式中:

显示图像使用cv2.imshow()方法,该方法的语法格式为: None = cv2.imshow(winname,mat) 式中:

等待按键使用cv2.waitKey()方法,当用户按下键盘后,该语句会被执行,并获取返回值。该方法的语法格式为: retval = cv2.waitKey([delay]) 式中:

释放资源使用cv2.destroyAllWindows()方法用来释放指定窗口,该方法的语法格式为:None = cv2.destroyAllWindows(winname) 式中:

全部代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2

image = cv2.imread("/home/yangzl/pic/pic1.png")
cv2.imshow("Image window", image)

cv2.waitKey()

cv2.destroyAllWindows()

第4行引用OpenCV的库cv2。所有视觉相关的方法(不是函数)都需要使用cv2库。

函数是封装了一些独立的功能,可以直接调用,能将一些数据(参数)传递进去进行处理,然后返回一些数据(返回值),也可以没有返回值。可以直接在模块中进行定义使用。所有传递给函数的数据都是显式传递的。 方法和函数类似,同样封装了独立的功能,但是方法是只能依靠类或者对象来调用的,表示针对性的操作。方法中的数据self(表示一个具体的实例本身)和cls(表示这个类本身)是隐式传递的,即方法的调用者;方法可以操作类内部的数据 所以类实例化出来的去调用,叫做方法,直接使用类名去调用或直接调用,叫做函数

第6行定义变量image,并调用imread接口加载本地的图片。 第7行调用imshow方法给我们展示图片。 第9行调用waitkey方法阻塞等待按键。 第10行调用destroyAllWindows方法销毁所有资源,结束程序。 在执行完这个程序后会弹出一个窗口显示出我们指定的图片,如下图所示。

在这里插入图片描述

2.3 像素的操作

经过前面例子,我们已经加载了一个图片,那么我们怎么访问实际的像素数据呢?我们首先要记住的一点就是OpenCV所代表的图像就是一个网格,一个矩阵,它可以用NumPy数组来表示。NumPy(Numerical Python)是 Python 中的一个线性代数库。对数组执行数学运算和逻辑运算时,NumPy 是非常有用的。在用 Python 对 n 维数组和矩阵进行运算时,NumPy 提供了大量有用的操作方法。下面我们通过例子让大家理解如何获取像素值。

完整代码如下所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2

image = cv2.imread("/home/yangzl/pic/pic1.png")
(b,g,r) = image[0,0]
print("Red:{}, Green:{}, Blue:{}".format(r,g,b))
image[0:10,0:10] = (0,0,255)
(b,g,r) = image[0,0]
print("Red:{}, Green:{}, Blue:{}".format(r,g,b))
cv2.imshow("Image window", image)
cv2.waitKey()
cv2.destroyAllWindows()

在原有代码的基础上增加了7-11行的代码,用意是获取左上角的像素值打印出来,在把一个左上角10*10的矩阵位置里重新赋值并打印和显示。

第7行定义一个元祖变量代表红黄蓝数值并把图片左上角的像素值赋予它,我们需要注意的是OpenCV存储RGB通道数据是倒序的也就是BGR,这点需要牢牢记住,所以赋值是变量写的是b,g,r,而不是r,g,b。并在第8行把每个数据打印出来显示在Console中。目前我们就通过一行代码非常简单的获取到了我想要的像素值,而这个image实际就是一个NumPy数组,它让我们不用考虑内部复杂的数据转换,只要通过索引值就可以轻易的获取到我们想要的像素值。

第9行我们操作左上角10*10的矩阵里数据并赋值为(0,0,255),根据OpenCV的特性我们知道这个是红色。0:10表示起始索引为0,终点索引为9,即0到9的10个点的数据。 第10行获取修改后的左上角像素值,并在第11行打印出来查看结果。 第12行通过窗口显示查看最终的效果图,会在左上角看到一个红色的正方形。

运行程序后显示效果如下图所示。

在这里插入图片描述

通过这个例子,我们可以看到,我们可以通过NumPy数组访问像素值,我们也可以通过NumPy数组给指定的位置赋值,修改像素值。并在左上角的10*10区域里画出红色正方形。然而通过这方法画矩形的并不是很快速,我们通过下一节的例子给大家展示如何画线,矩形,圆形等。

2.4 绘图

OpenCV提供了很多方法方便绘图,使用其中的绘图函数可以绘制直线、矩形、圆、椭圆等多种几何图形,还能在图像中添加文字说明。

画线完整代码如下所示。

画线OpenCV提供了方法cv2.line()用来绘制直线,该方法的语法格式为: img = cv2.line(img,pt1,pt2,color[,thickness[,lineType]]) 式中:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2

image = cv2.imread("/home/yangzl/pic/pic1.png")
cv2.line(image,(0,0),(680,480),(0,0,255),3)
cv2.imshow("Image window", image)
cv2.waitKey()
cv2.destroyAllWindows()

我们基于第一个例子在第7行增加一行代码就可以完成直线的绘制,第7行就是我们增加的画线功能代码使用的OpenCV的line方法,颜色为RGB格式所以0,0,255为红色。就如前面提到的OpenCV实际存储时是BGR,也就是说如果要显示红色为0,0,255,蓝色为255,0,0。

运行程序后效果如下图所示。

在这里插入图片描述

如果我们要绘制其他形状可以使用如下方法。 参考代码如下: 画一个矩形:cv2.rectangle(image,(340,20),(400,100),(0,255,0),-1) 该方法的语法格式为:cv2.rectange(画布,起点,终点,颜色,宽度),若宽度大于0,标识边线宽度;如果小于0,表示画实心矩形

画一个圆形:cv2.circle(image,(200,300),40,(0,0,255),-1) 该方法的语法格式为:cv2.circle(画布,圆心坐标,半径,颜色,宽度)若宽度大于0,标识边线宽度;如果小于0,表示画实心圆行

写一个文本:cv2.putText(image,'Hello OpenCV',(200,200),cv2.FONT_HERSHEY_SIMPLEX,1,(255,0,0),2) 该方法的语法格式为:cv2.putText(画布,文字,位置,字体,大小,颜色,文字粗细)

字体:   cv2.FONT_HERSHEY_SIMPLEX 正常尺寸的sans-serif字体   cv2.FONT_HERSHEY_SPLAIN 小尺寸的sans-serif字体   cv2.FONT_HERSHEY_COMPLEX 正常尺寸的serif字体   cv2.FONT_HERSHEY_SCREIPT_SIMPLEX 手写字体风格

2.5 驱动摄像头

以上我们已经学会了通过读取图片的方式显示,但是如何使用摄像头显示图像呢?这我们就需要使用OpenCV的方法VideoCapture来完成对摄像头数据的读取。

摄像头操作OpenCV提供了方法cv2.VideoCapture(0)用,该方法的语法格式为: cap = cv2.VideoCapture(device) 式中:

OpenCV提供了方法capture.read()读取摄像头数据。该方法的语法格式为: img = capture.read()

OpenCV提供了方法capture.release()释放摄像头。该方法的语法格式为: None = capture.release()

完整代码如下图所示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2

capture = cv2.VideoCapture(0)
while(True):
    ret, frame = capture.read()
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) == ord('q'):
        break;
capture.release()
cv2.destroyAllWindows()

第6行调用VideoCapture()方法获取VideoCapture对象.为了显示摄像头的图像,需要不停的获取摄像头里每一帧的图像,所以我们需要使用循环不停的通过VideoCapture的对象获取摄像头的图像数据即每一帧的图像。 第7行到第11行的循环目的是获取每一帧图像并在窗口中显示,同时监听用户按键。 第8行调用read方法获取摄像头的图像数据,ret,frame是read()方法的两个返回值。其中ret是布尔值,如果读取帧是正确的则返回True,如果文件读取到结尾,它的返回值就为False。frame就是每一帧的图像。 第9行使用imshow方法显示每一帧的图像。 第10行调用waitKey方法等待。参数是1,表示延时1ms切换到下一帧图像,参数过大如cv2.waitKey(1000),会因为延时过久而感觉到卡顿。参数为0,代表无限等待,如cv2.waitKey(0)就只显示当前帧图像而不会进入下一次循环,相当于视频暂停。 如果在等待期间用户按了键盘的q就结束循环,退出程序。 最后退出循环后在第12行使用capture.release()释放摄像头。第13行释放图像窗口。

2.6 Numpy库的使用

NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。 NumPy 是一个运行速度非常快的数学库,主要用于数组计算,包含:

我们在后面的程序中要使用到np.random.randint,该方法的语法格式为:numpy.random.randint(low, high=None, size=None, dtype='l')。

我们使用numpy来完成一个有趣的实验,完整的代码如下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy

image = cv2.imread("/home/yangzl/pic/pic1.png")
for i in range(0, 25):
    radius = np.random.randint(5, high=200)
    color = np.random.randint(0, high=256, size=(3,)).tolist()
    pt = np.random.randint(0, high=500, size=(2,))
    cv2.circle(image, tuple(pt), radius, color, -1)

cv2.imshow("Image window", image)
cv2.waitKey()
cv2.destroyAllWindows()

第5行增加numpy库的引用。 第8行 定义了一个循环语句,循环25次,我们要随机画25个圆。 第9行定义了一个变量radius使用保存半径的值,半径的取值范围是5到200。 第10行定义了一个变量color保存颜色数组,每个值的取值范围是0到256,产生三个随机数并输出一个数组。比如(255,0,0)。 第11行定义了一个变量pt保存坐标位置,取值范围是0到500,产生二个随机数。 第12行使用画圆方法circle把每个变量添加进去完成整个方法的使用。 这样通过这个调用会在画面上画出25个不同位置不同颜色的圆。效果如下。

在这里插入图片描述

2.7 挑战任务

  1. 画德国国旗。
  2. 画日本国旗。
  3. 滚动的文字。