OpenCV轻松入门:面向Python(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-05 09:06:39

点击下载

作者:李立宗

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

OpenCV轻松入门:面向Python

OpenCV轻松入门:面向Python试读:

前言

目前,计算机视觉技术的应用越来越广泛。伴随着硬件设备的不断升级,构造复杂的计算机视觉应用变得越来越容易了。有非常多的软件工具和库可以用来构造计算机视觉应用,而面向 Python 的 OpenCV(OpenCV for Python)就是一个很好的选择,本书正是基于面向 Python的OpenCV来讲解的。

本书的主要内容和特点

OpenCV本身是一个“黑盒”,它为我们提供了接口(参数、返回值)。我们只需要掌握接口的正确使用方法,就可以在完全不了解其内部工作原理(算法)的情况下,方便地进行各种复杂的图像处理。在这一点上,它和Photoshop等工具是相似的,只要掌握了正确的使用方法,就能够得到正确的处理结果。它们都尝试让我们专注于图像处理本身,而不用去考虑算法实现的细节。

在学习Photoshop时,我们学习的是如何使用它的功能,而不需要系统地学习每个功能所采用的算法原理。但是很明显,我们在使用OpenCV进行图像处理时,是不能完全忽略算法实现的,否则是不可能用好OpenCV的,更不能设计出好的计算机视觉应用系统。

从上述角度讲,我们可以从两个角度学习OpenCV:

● 将OpenCV作为“白盒”学习:深入学习OpenCV每个函数所使用算法的基本原理、每个函数的具体实现细节,进一步加深对图像处理的理解。

● 将OpenCV作为“黑盒”学习:仅仅将OpenCV作为一个工具来使用,学习的是每个函数内参数的含义和使用方式,学习的目的是更好地使用OpenCV函数。

本书尽量帮助读者在“黑盒”学习和“白盒”学习之间取得平衡。在介绍具体的算法原理时,尽量使用通俗易懂的语言和贴近生活的示例来说明问题,避免使用过多复杂抽象的公式。希望这样的安排能够帮助读者更好地掌握计算机视觉的相关知识,更透彻地理解计算机视觉的相关算法。在介绍OpenCV函数的使用方法时,我们为读者提供了大量的程序示例。而且在介绍函数对图像的处理前,往往先展示函数对数值、数组的处理,方便读者从数值的角度观察和理解函数的处理过程和结果。希望这些例题能够帮助读者更好地理解OpenCV处理图像的方式,快速地掌握OpenCV的使用方法,更好地使用OpenCV进行图像处理。需要说明的一点是,本书为黑白印刷,无法很好地呈现某些程序的运行效果,请读者自行运行程序并观察结果。

在内容的设置上,本书以OpenCV官方文档的知识脉络为主线,在此基础上对细节进行补充和说明。

为了方便读者学习,本书力求将每一个知识点作为一个独立的点来介绍和说明。在介绍知识点时,尽量采用从零开始的方式,以避免读者在学习过程中需要不断地离开当前知识点,去查阅相关背景资料。但是由于篇幅有限,如果某一函数已经在前面介绍过,在后面用到该函数时,就没有对其进行重复介绍,而是给出介绍该函数语法的章节位置,方便读者参考阅读。

本书适合计算机视觉领域的初学者阅读,包括在校学生、教师、专业技术人员、图像处理爱好者。

感谢

首先,我要感谢我的老师高铁杠教授,感谢高老师带我走进了计算机视觉这一领域,让我对计算机视觉产生了浓厚的兴趣,更要感谢高老师一直以来对我的关心和帮助。

感谢本书的策划编辑符隆美老师,符老师的专业精神给我留下了非常深刻的印象。感谢本书的责任编辑王中英和许艳老师,她们对本书内容做出了细致修改,不仅修改了很多不通顺的语句和错别字,还对书中存在的技术问题进行了确认和修正。还要感谢为本书出版而付出辛苦工作的电子工业出版社的其他老师们。

感谢OpenCV开源库的所有贡献者。

感谢合作单位天津拨云咨询服务有限公司为本书提供的支持。

感谢我的家人,感谢你们一直以来对我的理解、支持和付出。

互动方式

限于本人水平,书中肯定存在很多不足之处,欢迎大家提出问题和建议,也非常欢迎大家和我交流关于OpenCV的各种问题,我的邮箱是lilizong@gmail.com。李立宗2019年3月

读者服务

轻松注册成为博文视点社区用户(www.broadview.com.cn),扫码直达本书页面。

● 提交勘误:您对书中内容的修改意见可在 提交勘误 处提交,若被采纳,将获赠博文视点社区积分(在您购买电子书时,积分可用来抵扣相应金额)。

● 交流互动:在页面下方 读者评论 处留下您的疑问或观点,与我们和其他读者一同学习交流。

页面入口:http://www.broadview.com.cn/36290

第1章 OpenCV入门

OpenCV是一个开源的计算机视觉库,1999年由英特尔的Gary Bradski启动。Bradski在访学过程中注意到,在很多优秀大学的实验室中,都有非常完备的内部公开的计算机视觉接口。这些接口从一届学生传到另一届学生,对于刚入门的新人来说,使用这些接口比重复造轮子方便多了。这些接口可以让他们在之前的基础上更有效地开展工作。OpenCV正是基于为计算机视觉提供通用接口这一目标而被策划的。

由于要使用计算机视觉库,用户对处理器(CPU)的要求提升了,他们希望购买更快的处理器,这无疑会增加英特尔的产品销量和收入。这也许就解释了为什么OpenCV是由硬件厂商而非软件厂商开发的。当然,随着OpenCV项目的开源,目前其已经得到了基金会的支持,很大一部分研究主力也转移到了英特尔之外,越来越多的用户为OpenCV做出了贡献。

OpenCV库由C和C++语言编写,涵盖计算机视觉各个领域内的500多个函数,可以在多种操作系统上运行。它旨在提供一个简洁而又高效的接口,从而帮助开发人员快速地构建视觉应用。

OpenCV更像一个黑盒,让我们专注于视觉应用的开发,而不必过多关注基础图像处理的具体细节。就像PhotoShop一样,我们可以方便地使用它进行图像处理,我们只需要专注于图像处理本身,而不需要掌握复杂的图像处理算法的具体实现细节。

本章将介绍OpenCV的具体配置过程及基础使用方法。

1.1 如何使用

Python的开发环境有很多种,在实际开发时我们可以根据需要选择一种适合自己的。在本书中,我们选择使用Anaconda作为开发环境。本节简单介绍如何配置环境,来实现在Anaconda下使用基于Python语言的OpenCV库。

1.Python的配置

在本书中,我们使用的是Python 3版本。虽然Python 2和Python 3有很多相同之处,以至于Python 2的读者也可以使用本书,但还是要说明一下,本书直接面向的版本是Python 3。

可以在Python的官网上(https://www.python.org/downloads/)下载Python 3的解释器。在该网页顶部已经指出了最新的版本,例如“Download Python 3.7.0”就表示当前(2018年10月9日)的最新版本是Python 3.7.0,单击“Download Python 3.7.0”,就会开始下载解释器软件。

如果想安装其他版本,可以向下拖动鼠标,你将看到一个滚动页面,其中显示了一个列表集,其中列出了不同的安装包,具体选择哪种安装包依赖于两个因素:

● 操作系统,例如Windows、macOS、Linux等。

● 处理器位数,例如32位或者64位。

根据自己的计算机配置,在列表集中选择对应的安装包下载。

下载完成后,按照提示步骤完成安装即可。

2.Anaconda的配置

可以在Anaconda的官网上(https://www.anaconda.com/download/)下载Anaconda。在下载页顶部指出了当前的最新版本。

如果想安装其他版本,可以在下载页内根据实际情况选择。具体选择哪种安装包依赖于三个因素:

● Python的版本,例如Python 2.7或者Python 3.7等。

● 操作系统,例如Windows、macOS、Linux等。

● 处理器位数,例如32位或者64位。

根据自己的环境配置,在下载页中选择对应的安装包下载。

下载完成后,按照提示步骤完成安装即可。

3.OpenCV的配置

可以从官网下载OpenCV的安装包,编译后使用;也可以直接使用第三方提供的预编译包安装。

在本书中,我们选择由 PyPI 提供的 OpenCV 安装包,可以在 https://pypi.org/project/opencv-python/上面下载最新的基于Python的OpenCV库。同样,下载页顶部指出了当前的最新版本。

如果想安装其他版本,可以在Download files栏目内根据实际情况选择。具体选择哪种安装包依赖于三个因素:

● Python的版本,例如Python 2.7或者Python 3.7等。

● 操作系统,例如Windows、macOS、Linux等。

● 处理器位数,例如32位或者64位。

完成下载后,在Anaconda Prompt内使用pip install完整路径文件名完成安装。例如,假设文件存储在D:\anaconda\Lib目录下面,则要使用的语句为:

>>pip install D:\anaconda\Lib\opencv_python-3.4.3.18-cp37-cp37m-win_amd64.whl

安装完成后,可以在Anaconda Prompt内使用conda list语句查看安装是否成功。如果安装成功,就会显示安装成功的OpenCV库及对应的版本等信息。需要注意,不同安装包的名称及版本号可能略有差异。例如,图1-1中的包列表显示了系统内配置了OpenCV其他版本的情况,该图说明在当前系统内配置的是OpenCV 3.4.2版本。图1-1 包列表

这里主要介绍了如何在Windows系统下对环境进行配置,如果需要配置其他操作系统下的环境,请参考官网的具体介绍。

1.2 图像处理基本操作

在图像处理过程中,读取图像、显示图像、保存图像是最基本的操作。本节将简单介绍这几项基本操作。1.2.1 读取图像

OpenCV提供了函数cv2.imread()来读取图像,该函数支持各种静态图像格式。该函数的语法格式为:

retval=cv2.imread(filename[,flags])

式中:

● retval是返回值,其值是读取到的图像。如果未读取到图像,则返回“None”。

● filename表示要读取的图像的完整文件名。

● flags是读取标记。该标记用来控制读取文件的类型,具体如表1-1所示。表1-1中的第一列参数与第三列数值是等价的。例如 cv2.IMREAD_UNCHANGED=−1,在设置参数时,既可以使用第一列的参数值,也可以采用第三列的数值。表1-1 flags标记值续表

函数cv2.imread()能够读取多种不同类型的图像,具体如表1-2所示。表1-2 cv2.imread()函数支持的图像格式

例如,想要读取当前目录下文件名为lena.bmp的图像,并保持按照原有格式读入,则使用的语句为:

lena=cv2.imread("lena.bmp",-1)

需要注意,上述程序要想正确运行,首先需要导入cv2模块,大多数常用的OpenCV函数都在cv2模块内。与cv2模块所对应的cv模块代表传统版本的模块。这里的cv2模块并不代表该模块是专门针对OpenCV 2版本的,而是指该模块引入了一个改善的API接口。在cv2模块内部采用了面向对象的编程方式,而在cv模块内更多采用的是面向过程的编程方式。

本书中所使用的模块函数都是cv2模块函数,为了方便理解,在函数名前面加了“cv2.”。但是如果函数名出现在标题中,那么希望突出的是该函数本身,所以未加“cv2.”。【例1.1】使用cv2.imread()函数读取一幅图像。

根据题目要求,编写代码如下:

import cv2

lena=cv2.imread("lenacolor.png")

print(lena)

上述程序首先会读取当前目录下的图像 lena.bmp,然后使用 print 语句打印读取的图像数据。运行上述程序后,会输出图像的部分像素值,如图1-2所示。图1-2 图像部分像素值1.2.2 显示图像

OpenCV提供了多个与显示有关的函数,下面对常用的几个进行简单介绍。

1.namedWindow函数

函数cv2.namedWindow()用来创建指定名称的窗口,其语法格式为:

None=cv2.namedWindow(winname)

式中,winname是要创建的窗口的名称。

例如,下列语句会创建一个名为lesson的窗口:

cv2.namedWindow(“lesson”)

2.imshow函数

函数cv2.imshow()用来显示图像,其语法格式为:

None=cv2.imshow(winname,mat)

式中:

● winname是窗口名称。

● mat是要显示的图像。【例1.2】

在一个窗口内显示读取的图像。

根据题目要求,编写代码如下:

import cv2

lena=cv2.imread("lena.bmp")

cv2.namedWindow("lesson")

cv2.imshow("lesson",lena)

在本程序中,首先通过cv2.imread()函数读取图像lena.bmp,接下来通过cv2.namedWindow()函数创建一个名为lesson的窗口,最后通过cv2.imshow()函数在窗口lesson内显示图像lena.bmp。

运行上述程序,得到的运行结果如图1-3所示。图1-3【例1.2】程序的运行结果

在实际使用中,可以先通过函数 cv2.namedWindow()来创建一个窗口,再让函数cv2.imshow()引用该窗口来显示图像。也可以不创建窗口,直接使用函数cv2.imshow()引用一个并不存在的窗口,并在其中显示指定图像,这样函数cv2.imshow()实际上会完成如下两步操作。

第1步:函数cv2.imshow()创建一个指定名称的新窗口。

第2步:函数cv2.imshow()将图像显示在刚创建的窗口内。

例如,在下面的语句中,函数cv2.imshow()完成了创建demo窗口和显示image图像的操作,该语句的功能与例1.2相同。

import cv2

lena=cv2.imread("lena.bmp")

cv2.imshow("demo",lena)

在显示图像时,初学者最经常遇到的一个错误是“error:(-215:Assertion failed)size.width>0&& size.height>0 in function 'cv::imshow'”,说明当前要显示的图像是空的(None),这通常是由于在读取文件时没有找到图像文件造成的。一般来说,没有找到要读取的图像文件,可能是因为文件名错误;如果确认要读取的图像的完整文件名(路径名和文件名)没有错,那么往往是工作路径配置错误造成的。

例如,当前程序所在路径为 e:\lesson,要读取当前路径下存在的图像 image\lena.bmp。那么:

● 如果设置的当前工作路径是 e:\lesson,程序就会读取当前工作路径下的文件image\lena.bmp,该文件的完整文件名就是e:\lesson\ image\lena.bmp。正是我们想要读取的指定文件。

● 如果设置的当前工作路径不是e:\lesson,例如,当前的工作路径是e:\python,程序仍然会读取当前工作路径下的文件 image\lena.bmp(而不是读取当前程序所在路径下的image\lena.bmp),该文件的完整文件名就是e:\python\ image\lena.bmp。显然,这样是无法读取到指定图像的。

为了避免上述错误,可以在读取图像前判断图像文件是否存在,并在显示图像前判断图像是否存在。

3.waitKey函数

函数 cv2.waitKey()用来等待按键,当用户按下键盘后,该语句会被执行,并获取返回值。其语法格式为:

retval=cv2.waitKey([delay])

式中:

● retval表示返回值。如果没有按键被按下,则返回−1;如果有按键被按下,则返回该按键的ASCII码。

● delay 表示等待键盘触发的时间,单位是 ms。当该值是负数或者零时,表示无限等待。该值默认为0。

在实际使用中,可以通过函数cv2.waitKey()获取按下的按键,并针对不同的键做出不同的反应,从而实现交互功能。例如,如果按下 A 键,则关闭窗口;如果按下 B 键,则生成一个窗口副本。

下面通过一个示例演示如何通过函数cv2.waitKey()实现交互功能。【例1.3】在一个窗口内显示图像,并针对按下的不同按键做出不同的反应。

函数 cv2.waitKey()能够获取按键的 ASCII 码。例如,如果该函数的返回值为97,表示按下了键盘上的字母a键。通过将返回值与ASCII码值进行比较,就可以确定是否按下了某个特定的键。例如,通过语句“返回值==97”就可以判断是否按下了字母a键。

Python提供了函数ord(),用来获取字符的ASCII码值。因此,在判断是否按下了某个特定的按键时,可以先使用 ord()函数获取该特定字符的 ASCII 码值,再将该值与 cv2.waitKey()函数的返回值进行比较,从而确定是否按下了某个特定的键。这样,在程序设计中就不需要ASCII值的直接参与了,从而避免了使用ASCII进行比较可能带来的不便。例如,要判断是否按下了字母A键,可以直接使用“返回值==ord('A')”语句来完成。

根据题目要求及以上分析,编写代码如下:

运行上述程序,按下键盘上的A 键或者B键,会在一个新的窗口内显示图像lena.bmp,它们的不同之处在于:

● 如果按下的是键盘上的A键,则新出现的窗口名称为PressA,如图1-4的左图所示。

● 如果按下的是键盘上的B键,则新出现的窗口名称为PressB,如图1-4的右图所示。图1-4【例1.3】程序的运行结果

从另外一个角度理解,该函数还能够让程序实现暂停功能。当程序运行到该语句时,会按照参数delay的设定等待特定时长。根据该值的不同,可能有不同的情况:

● 如果参数delay的值为0,则程序会一直等待。直到有按下键盘按键的事件发生时,才会执行后续程序。

● 如果参数delay的值为一个正数,则在这段时间内,程序等待按下键盘按键。当有按下键盘按键的事件发生时,就继续执行后续程序语句;如果在delay参数所指定的时间内一直没有这样的事件发生,则超过等待时间后,继续执行后续的程序语句。【例1.4】在一个窗口内显示图像,用函数cv2.waitKey()实现程序暂停,在按下键盘的按键后让程序继续运行。

根据题目要求,编写代码如下:

运行上述程序,首先会在一个名为demo的窗口内显示lena.bmp图像。在未按下键盘上的按键时,程序没有新的状态出现;当按下键盘上的任意一个按键后,会在控制台打印“触发了按键”。

在本例中,由于cv2.waitKey()函数的参数值是默认值0,所以在未按下键盘上的按键时,程序会一直处于暂停状态。当按下键盘上的任意一个按键时,程序中key=cv2.waitKey()下方的语句便得以执行,程序输出“触发了按键”。

4.destroyWindow函数

函数cv2.destroyWindow()用来释放(销毁)指定窗口,其语法格式为:

None=cv2.destroyWindow(winname)

其中,winname是窗口的名称。

在实际使用中,该函数通常与函数cv2.waitKey()组合实现窗口的释放。【例1.5】编写一个程序,演示如何使用函数cv2.destroyWindow()释放窗口。

根据题目要求,编写代码如下:

import cv2

lena=cv2.imread("lena.bmp")

cv2.imshow("demo",lena)

cv2.waitKey()

cv2.destroyWindow("demo")

运行上述程序,首先会在一个名为demo的窗口内显示lena.bmp图像。在程序运行的过程中,当未按下键盘上的按键时,程序没有新的状态出现;当按下键盘上的任意一个按键后,窗口demo会被释放。

5.destroyAllWindows函数

函数cv2.destroyAllWindows()用来释放(销毁)所有窗口,其语法格式为:

None=cv2.destroyAllWindows()【例1.6】编写一个程序,演示如何使用函数cv2.destroyAllWindows()释放所有窗口。

根据题目要求,编写代码如下:

import cv2

lena=cv2.imread("lena.bmp")

cv2.imshow("demo1",lena)

cv2.imshow("demo2",lena)

cv2.waitKey()

cv2.destroyAllWindows()

运行上述程序,会分别出现名称为 demo1和 demo2的窗口,在两个窗口中显示的都是lena.bmp图像。在未按下键盘上的按键时,程序没有新的状态出现;当按下键盘上的任意一个按键后,两个窗口都会被释放。1.2.3 保存图像

OpenCV提供了函数cv2.imwrite(),用来保存图像,该函数的语法格式为:

retval=cv2.imwrite(filename,img[,params])

式中:

● retval是返回值。如果保存成功,则返回逻辑值真(True);如果保存不成功,则返回逻辑值假(False)。

● filename是要保存的目标文件的完整路径名,包含文件扩展名。

● img是被保存图像的名称。

● params是保存类型参数,是可选的。【例1.7】编写一个程序,将读取的图像保存到当前目录下。

根据题目要求,编写代码如下:

import cv2

lena=cv2.imread("lena.bmp")

r=cv2.imwrite("result.bmp",lena)

上述程序会先读取当前目录下的图像lena.bmp,生成它的一个副本图像,然后将该图像以名称result.bmp存储到当前目录下。

1.3 OpenCV贡献库

目前,OpenCV库包含如下两部分。

● OpenCV主库:即通常安装的OpenCV库,该库是成熟稳定的,由核心的OpenCV团队维护。

● OpenCV 贡献库:该扩展库的名称为 opencv_contrib,主要由社区开发和维护,其包含的视觉应用比OpenCV主库更全面。需要注意的是,OpenCV贡献库中包含非OpenCV许可的部分,并且包含受专利保护的算法。因此,在使用该模块前需要特别注意。

OpenCV贡献库中包含了非常多的扩展模块,举例如下。

● bioinspired:生物视觉模块。

● datasets:数据集读取模块。

● dnn:深度神经网络模块。

● face:人脸识别模块。

● matlab:MATLAB接口模块。

● stereo:双目立体匹配模块。

● text:视觉文本匹配模块。

● tracking:基于视觉的目标跟踪模块。

● ximgpro:图像处理扩展模块。

● xobjdetect:增强2D目标检测模块。

● xphoto:计算摄影扩展模块。

可以通过以下两种方式使用贡献库:

● 下载OpenCV贡献库,使用cmake手动编译。

● 通过语句 pip install opencv-contrib-python 直接安装编译好的 OpenCV 贡献库。网页https://pypi.org/project/opencv-contrib-python/上提供了该方案的常见问题列表 FAQ(Frequently Asked Questions),而且该FAQ是不断更新的。

第2章 图像处理基础

本章主要介绍图像的基本表示方法、像素的访问和操作、感兴趣区域处理、通道处理等知识点。需要强调的是,使用面向Python的OpenCV(OpenCV for Python)必须熟练掌握Numpy库,尤其是Numpy.array库,Numpy.array库是Python处理图像的基础。

2.1 图像的基本表示方法

本节主要讨论二值图像、灰度图像、彩色图像的基本表示方法。

1.二值图像

二值图像是指仅仅包含黑色和白色两种颜色的图像。

在计算机中,通过一个栅格状排列的数据集(矩阵)来表示和处理图像。例如,图2-1是一个字母A的图像,计算机在处理该图像时,会首先将其划分为一个个的小方块,每一个小方块就是一个独立的处理单位,称为像素点。接下来,计算机会将其中的白色像素点(白色小方块区域)处理为“1”,将黑色像素点(黑色小方块区域)处理为“0”,以方便进行后续的存储和处理等操作。图2-1 字母A的图像

按照上述处理方式,图2-1中的字母A在计算机内的存储形式如图2-2所示。图2-2 计算机内字母A的存储形式

上述图像比较简单,图像内只有黑色和白色两种不同的颜色,因此只使用一个比特位(0或者1)就能表示。

2.灰度图像

二值图像表示起来简单方便,但是因为其仅有黑白两种颜色,所表示的图像不够细腻。如果想要表现更多的细节,就需要使用更多的颜色。例如,图2-3中的lena图像是一幅灰度图像,它采用了更多的数值以体现不同的颜色,因此该图像的细节信息更丰富。图2-3 lena图像

通常,计算机会将灰度处理为256个灰度级,用数值区间[0,255]来表示。其中,数值“255”表示纯白色,数值“0”表示纯黑色,其余的数值表示从纯白到纯黑之间不同级别的灰度。

用于表示256个灰度级的数值0~255,正好可以用一个字节(8位二进制值)来表示。表2-1所示的是部分二进制值所对应的十进制值及灰度颜色。表2-1 部分灰度级及所对应的值续表

按照上述方法,图2-3中的图像需要使用一个各行各列的数值都在[0,255]之间的矩阵来表示。例如,图2-4就是图2-3的lena图像中部分区域的数值表示形式。图2-4 部分lena图像的数值表示

有些情况下,也会使用8位二进制值来表示一幅二值图像。这种情况下,使用灰度值255表示白色、灰度值0表示黑色。此时,该二值图像内仅有数值0和数值255两种类型的灰度值(灰度级),不存在其他灰度值的像素点。

3.彩色图像

相比二值图像和灰度图像,彩色图像是更常见的一类图像,它能表现更丰富的细节信息。

神经生理学实验发现,在视网膜上存在三种不同的颜色感受器,能够感受三种不同的颜色:红色、绿色和蓝色,即三基色。自然界中常见的各种色光都可以通过将三基色按照一定的比例混合构成。除此以外,从光学角度出发,可以将颜色解析为主波长、纯度、明度等。从心理学和视觉角度出发,可以将颜色解析为色调、饱和度、亮度等。通常,我们将上述采用不同的方式表述颜色的模式称为色彩空间,或者颜色空间、颜色模式等。

虽然不同的色彩空间具有不同的表示方式,但是各种色彩空间之间可以根据需要按照公式进行转换。这里仅仅介绍较为常用的RGB色彩空间。

在 RGB 色彩空间中,存在 R(red,红色)通道、G(green,绿色)通道和 B(blue,蓝色)通道,共三个通道。每个色彩通道值的范围都在[0,255]之间,我们用这三个色彩通道的组合表示颜色。

以比较通俗的方式来解释就是,有三个油漆桶,分别装了红色、绿色、蓝色的油漆,我们分别从每个油漆桶中取容量为0~255个单位的不等量的油漆,将三种油漆混合就可以调配出一种新的颜色。三种油漆经过不同的组合,共可以调配出所有常见的256×256×256=16 777 216种颜色。

表2-2展示了不同的RGB值所对应的颜色。表2-2 RGB值及颜色示例

例如,对于图2-5左侧的彩色图像,可以理解为由右侧的R通道、G通道、B通道三个通道构成。其中,每一个通道都可以理解为一个独立的灰度图像。左侧彩色图像中的白色方块内的区域对应右侧三个通道的三个矩阵,白色方块左上角顶点的RGB值为(205,89,68)。图2-5 图像数据展示

因此,通常用一个三维数组来表示一幅RGB色彩空间的彩色图像。

一般情况下,在RGB色彩空间中,图像通道的顺序是R→G→B,即第1个通道是R通道,第2个通道是G通道,第3个通道是B通道。需要特别注意的是,在OpenCV中,通道的顺序是B→G→R,即:

● 第1个通道保存B通道的信息。

● 第2个通道保存G通道的信息。

● 第3个通道保存R通道的信息。

在图像处理过程中,可以根据需要对图像的通道顺序进行转换。除此以外,还可以根据需要对不同色彩空间的图像进行类型转换,例如,将灰度图像处理为二值图像,将彩色图像处理为灰度图像等。

2.2 像素处理

像素是图像构成的基本单位,像素处理是图像处理的基本操作,可以通过位置索引的形式对图像内的元素进行访问、处理。

1.二值图像及灰度图像

需要说明的是,在OpenCV中,最小的数据类型是无符号的8位数。因此,在OpenCV中实际上并没有二值图像这种数据类型,二值图像经常是通过处理得到的,然后使用0表示黑色,使用255表示白色。

可以将二值图像理解为特殊的灰度图像,这里仅以灰度图像为例讨论像素的读取和修改。通过2.1节的分析可知,可以将图像理解为一个矩阵,在面向Python的OpenCV(OpenCV for Python)中,图像就是 Numpy 库中的数组。一个 OpenCV 灰度图像是一个二维数组,可以使用表达式访问其中的像素值。例如,可以使用image[0,0]访问图像image第0行第0列位置上的像素点。第0行第0列位于图像的左上角,其中第1个索引表示第0行,第2个索引表示第0列。

为了方便理解,我们首先使用Numpy库来生成一个8×8大小的数组,用来模拟一个黑色图像,并对其进行简单处理。【例2.1】使用Numpy库生成一个元素值都是0的二维数组,用来模拟一幅黑色图像,并对其进行访问、修改。

分析:使用Numpy库中的函数zeros()可以生成一个元素值都是0的数组,并可以直接使用数组的索引对其进行访问、修改。

根据题目要求及分析,编写代码如下:

import cv2

import numpy as np

img=np.zeros((8,8),dtype=np.uint8)

print("img=\n",img)

cv2.imshow("one",img)

print("读取像素点img[0,3]=",img[0,3])

img[0,3]=255

print("修改后img=\n",img)

print("读取修改后像素点img[0,3]=",img[0,3])

cv2.imshow("two",img)

cv2.waitKey()

cv2.destroyAllWindows()

代码分析如下。

● 使用函数 zeros()生成了一个8×8大小的二维数组,其中所有的值都是0,数值类型是np.uint8。根据该数组的属性,可以将其看成一个黑色的图像。

● 语句img[0,3]访问的是img第0行第3列的像素点,需要注意的是,行序号、列序号都是从0开始的。

● 语句img[0,3]=255将img中第0行第3列的像素点的像素值设置为“255”。

运行上述程序,会出现名为one和two的两个非常小的窗口,其中:

● 名为one的窗口是一个纯黑色的图像。

● 名为 two 的窗口在顶部靠近中间位置有一个白点(对应修改后的值255),其他地方也都是纯黑的图像。

同时,在控制台会输出如下内容:

通过本例中两个窗口显示的图像可知,二维数组与图像之间存在对应关系。因为窗口太小,在纸质图书上可能无法直接观察效果,请大家在计算机上运行上述程序,观察效果。【例2.2】读取一个灰度图像,并对其像素进行访问、修改。

根据题目要求,编写代码如下:

在本例中,使用了一个嵌套循环语句,将图像img中“第10行到99行”与“第80列到99列”交叉区域内的像素值设置为255。从图像img上来看,该交叉区域被设置为白色。

运行程序,结果如图2-6所示,其中:

● 左图是读取的原始图像。

● 右图是经过修改后的图像。图2-6 像素修改示例

2.彩色图像

RGB模式的彩色图像在读入OpenCV内进行处理时,会按照行方向依次读取该RGB图像的B通道、G通道、R通道的像素点,并将像素点以行为单位存储在ndarray的列中。例如,有一幅大小为R行×C列的原始RGB图像,其在OpenCV内以BGR模式的三维数组形式存储,如图2-7所示。图2-7 RGB图像以三维数组形式存储的情况说明

可以使用表达式访问数组内的值。例如,可以使用image[0,0,0]访问图像image的B通道内的第0行第0列上的像素点,式中:

● 第1个索引表示第0行。

● 第2个索引表示第0列。

● 第3个索引表示第0个颜色通道。

根据上述分析可知,假设有一个红色(其R通道值为255,G通道值为0,B通道值为0)图像,不同的访问方式得到的值如下。

● img[0,0]:访问图像img第0行第0列像素点的BGR值。图像是BGR格式的,得到的数值为[0,0,255]。

● img[0,0,0]:访问图像img第0行第0列第0个通道的像素值。图像是BGR格式的,所以第0个通道是B通道,会得到B通道内第0行第0列的位置所对应的值0。

● img[0,0,1]:访问图像img第0行第0列第1个通道的像素值。图像是BGR格式的,所以第1个通道是G通道,会得到G通道内第0行第0列的位置所对应的值0。

● img[0,0,2]:访问图像img第0行第0列第2个通道的像素值。图像是BGR格式的,所以第2个通道是R通道,会得到R通道内第0行第0列的位置所对应的值255。

为了方便理解,我们首先使用Numpy库来生成一个2×4×3大小的数组,用它模拟一幅黑色图像,并对其进行简单处理。【例2.3】使用Numpy生成三维数组,用来观察三个通道值的变化情况。

根据题目要求,编写代码如下:

在本例中,分别生成了 blue、green、red 三个数组,其初始值都是0。接下来,分别改变各通道值。

● 针对数组blue,将其第0个通道的值设置为255。从图像角度来看,图像blue的B通道值为255,其余两个通道值为0,因此图像blue为蓝色图像。

● 针对数组 green,将其第1个通道的值设置为255。从图像角度来看,图像green 的 G通道值为255,其余两个通道值为0,因此图像green为绿色图像。

● 针对数组red,将其第2个通道的值设置为255。从图像角度来看,图像red的R通道值为255,其余两个通道值为0,因此图像red为红色图像。

运行上述程序,会显示颜色为蓝色、绿色、红色的三幅图像,分别对应数组blue、数组green、数组red。因为黑白印刷无法显示彩色图像,所以请读者运行程序后观察结果。

除了显示图像,还会显示每个数组的输出值,部分输出结果如图2-8所示。图2-8【例2.3】程序的部分输出结果【例2.4】使用Numpy生成一个三维数组,用来观察三个通道值的变化情况。

根据题目要求,编写代码如下:

import numpy as np

import cv2

img=np.zeros((300,300,3),dtype=np.uint8)

img[:,0:100,0]=255

img[:,100:200,1]=255

img[:,200:300,2]=255

print("img=\n",img)

cv2.imshow("img",img)

cv2.waitKey()

cv2.destroyAllWindows()

运行上述程序,会显示如图2-9所示的图像。由于本书为黑白印刷,所以为了更好地观察运行效果,请大家亲自上机运行程序。图2-9【例2.4】程序的运行结果

除了显示图像,还会显示img值的情况,部分输出结果如图2-10所示。图2-10【例2.4】程序的部分输出结果【例2.5】使用Numpy生成一个三维数组,用来模拟一幅BGR模式的彩色图像,并对其进行访问、修改。

分析:使用Numpy中的zeros()函数可以生成一个元素值都是0的数组。可以直接使用数组的索引形式对其进行访问、修改。

根据题目要求及分析,编写代码如下:

1.import numpy as np

2.img=np.zeros((2,4,3),dtype=np.uint8)

3.print("img=\n",img)

4.print("读取像素点img[0,3]=",img[0,3])

5.print("读取像素点img[1,2,2]=",img[1,2,2])

6.img[0,3]=255

7.img[0,0]=[66,77,88]

8.img[1,1,1]=3

9.img[1,2,2]=4

10.img[0,2,0]=5

11.print("修改后img\n",img)

12.print("读取修改后像素点img[1,2,2]=",img[1,2,2])

本程序进行了如下操作。

● 第2行使用zeros()生成一个2×4×3大小的数组,其对应一个“2行4列3个通道”的BGR图像。

● 第3行使用print语句显示(打印)当前图像(数组)的值。

● 第4行中的img[0,3]语句会访问第0行第3列位置上的B通道、G通道、R通道三个像素点。

● 第5行的img[1,2,2]语句会访问第1行第2列第2个通道位置上的像素点。

● 第6行的img[0,3]=255语句会修改img中第0行第3列位置上的像素值,该位置上的B通道、G通道、R通道三个像素点的值都会被修改为255。

● 第7行的img[0,0]=[66,77,88]语句会修改img中第0行第0列位置上的B通道、G通道、R通道上三个像素点的值,将它们修改为[66,77,88]。

● 第8行的img[1,1,1]=3语句会修改img中第1行第1列第1个通道(G通道)位置上的像素值,将其修改为3。

● 第9行的img[1,2,2]=4语句会修改img中第1行第2列第2个通道(R通道)位置上的像素值,将其修改为4。

● 第10行的img[0,2,0]=5语句会修改img中第0行第2列第0个通道(B通道)位置上的像素值,将其修改为5。

● 最后两行使用print语句观察img和img[1,2,2]的值。

运行上述程序,会在控制台输出如下结果:

在本例中,为了方便说明问题,设置的数组比较小。在实际中可以定义稍大的数组,并使用cv2.imshow()将其显示出来,进一步观察处理结果,加深理解。【例2.6】读取一幅彩色图像,并对其像素进行访问、修改。

根据题目要求,编写代码如下:

1.import cv2

2.img=cv2.imread("lenacolor.png")

3.cv2.imshow("before",img)

4.print("访问img[0,0]=",img[0,0])

5.print("访问img[0,0,0]=",img[0,0,0])

6.print("访问img[0,0,1]=",img[0,0,1])

7.print("访问img[0,0,2]=",img[0,0,2])

8.print("访问img[50,0]=",img[50,0])

9.print("访问img[100,0]=",img[100,0])

10.#区域1

11.for i in range(0,50):

12.  for j in range(0,100):

13.    for k in range(0,3):

14.      img[i,j,k]=255 #白色

15.#区域2

16.for i in range(50,100):

17.  for j in range(0,100):

18.    img[i,j]=[128,128,128] #灰色

19.#区域3

20.for i in range(100,150):

21.  for j in range(0,100):

22.    img[i,j]=0     #黑色

23.cv2.imshow("after",img)

24.print("修改后img[0,0]=",img[0,0])

25.print("修改后img[0,0,0]=",img[0,0,0])

26.print("修改后img[0,0,1]=",img[0,0,1])

27.print("修改后img[0,0,2]=",img[0,0,2])

28.print("修改后img[50,0]=",img[50,0])

29.print("修改后img[100,0]=",img[100,0])

30.cv2.waitKey()

31.cv2.destroyAllWindows()

上述程序进行了如下操作。

● 第2行使用imread()函数读取当前目录下的一幅彩色RGB图像。

● 第4行的img[0,0]语句会访问img中第0行第0列位置上的B通道、G通道、R通道三个像素点。

● 第5~7行分别会访问img中第0行第0列位置上的B通道、G通道、R通道三个像素点。

● 第8行的img[50,0]语句会访问第50行第0列位置上的B通道、G通道、R通道三个像素点。

● 第9行的img[100,0]语句会访问第100行第0列位置上的B通道、G通道、R通道三个像素点。

● 第10~14行使用三个for语句的嵌套循环,对图像左上角区域(即“第0行到第49行”与“第0列到第99列”的行列交叉区域,我们称之为区域1)内的像素值进行设定。借助img[i,j,k]=255语句将该区域内的三个通道的像素值都设置为255,让该区域变为白色。

● 第15~18行使用两个for语句的嵌套循环,对图像左上角位于区域1正下方的区域(即“第50行到第99行”与“第0列到第99列”的行列交叉区域,我们称之为区域2)内的像素值进行设定。借助img[i,j]=[128,128,128]语句将该区域内的三个通道的像素值都设置为128,让该区域变为灰色。

● 第19~21行使用两个for语句的嵌套循环,对图像左上角位于区域2正下方的区域(即“第100行到第149行”与“第0列到第99列”的行列交叉区域,我们称之为区域3)内的像素值进行设定。借助img[i,j]=0语句将该区域内的三个通道的像素值都设置为0,让该区域变为黑色。

运行程序,结果如图2-11所示,其中左图是读取的原始图像,右图是经过修改后的图像。图2-11【例2.6】程序的运行结果

同时,在控制台会输出如下内容:

访问img[0,0]=[125 137 226]

访问img[0,0,0]=125

访问img[0,0,1]=137

访问img[0,0,2]=226

访问img[50,0]=[114 136 230]

访问img[100,0]=[75 55 155]

修改后img[0,0]=[255 255 255]

修改后img[0,0,0]=255

修改后img[0,0,1]=255

修改后img[0,0,2]=255

修改后img[50,0]=[128 128 128]

修改后img[100,0]=[0 0 0]

2.3 使用numpy.array访问像素

numpy.array提供了item()和itemset()函数来访问和修改像素值,而且这两个函数都是经过优化处理的,能够更大幅度地提高处理效率。在访问及修改像素点的值时,利用 numpy.array提供的函数比直接使用索引要快得多,同时,这两个函数的可读性也更好。

1.二值图像及灰度图像

可以将二值图像理解为特殊的灰度图像,所以这里仅以灰度图像为例讨论像素点值的读取和修改。

函数item()能够更加高效地访问图像的像素点,该函数的语法格式为:

item(行,列)

函数itemset()可以用来修改像素值,其语法格式为:

itemset(索引值,新值)

为了便于理解,我们首先使用Numpy库生成一个5×5大小的随机数组,用来模拟一幅灰度图像,并对其进行简单的处理。【例2.7】使用 Numpy 生成一个二维随机数组,用来模拟一幅灰度图像,并对其像素进行访问、修改。

分析:使用Numpy中的random.randint可以生成一个随机数组,该随机数组对应一幅灰度图像。然后分别使用函数item()及函数itemset()对其像素进行访问、修改。

根据题目要求及分析,编写代码如下:

import numpy as np

img=np.random.randint(10,99,size=[5,5],dtype=np.uint8)

print("img=\n",img)

print("读取像素点img.item(3,2)=",img.item(3,2))

img.itemset((3,2),255)

print("修改后img=\n",img)

print("修改后像素点img.item(3,2)=",img.item(3,2))

运行程序,控制台输出结果如下:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载