OpenCV实例精解


华章程序员书库 OpenCV 实例精解 OpenCV By Example [美]  普拉蒂克·乔希(Prateek Joshi),大卫·米兰·埃斯克里瓦    (David Millán Escrivá),维尼修斯·戈多伊(V inicius Godoy)  著 呆萌院长 李风明 李翰阳 译图书在版编目(CIP)数据 OpenCV 实例精解 / (美)普拉蒂克·乔希(Prateek Joshi)等著;呆萌院长,李风明,李 翰阳译 . —北京:机械工业出版社,2016.8 (华章程序员书库) 书名原文:OpenCV By Example ISBN 978-7-111-54741-9 I. O… II. ①普… ②呆… ③李… ④李… III. 图像处理软件-程序设计 IV. TP391.413 中国版本图书馆 CIP 数据核字(2016)第 200466 号 本书版权登记号:图字:01-2016-1584 OpenCV By Example(ISBN: 978-1-78528-094-8). Copyright © 2016 Packt Publishing. First published in the English language under the title“ OpenCV By Example”. All rights reserved. Chinese simplified language edition published by China Machine Press. Copyright © 2016 by China Machine Press. 本书中文简体字版由 Packt Publishing 授权机械工业出版社独家出版。未经出版者书面许可,不得以任何方式复 制或抄袭本书内容。 OpenCV 实例精解 出版发行:机械工业出版社(北京市西城区百万庄大街 22 号 邮政编码:100037) 责任编辑:陈佳媛 责任校对:董纪丽 印  刷: 版  次:2016 年 9 月第 1 版第 1 次印刷 开  本:186mm×240mm 1/16 印  张:14 书  号:ISBN 978-7-111-54741-9 定  价:59.00 元 凡购本书,如有缺页、倒页、脱页,由本社发行部调换 客服热线:(010)88379426 88361066 投稿热线:(010)88379604 购书热线:(010)68326294 88379649 68995259 读者信箱:hzit@hzbook.com 版权所有 • 侵权必究 封底无防伪标均为盗版 本书法律顾问:北京大成律师事务所 韩光 / 邹晓东The Translator’s Words 译 者 序 通俗地讲,计算机视觉就是给计算机安装上眼睛(照相机)和大脑(算法),让其能 够感知周围的环境。它是对生物视觉的一种模拟,通常的做法是通过对采集的图像或者 视频进行处理来获得相应场景的三维信息。计算机视觉不仅应用在计算机科学和工程、 信号处理、物理学、应用数学和统计学中,也广泛应用在神经生理学和认知科学等领 域,发展前景可见一斑。 工欲善其事,必先利其器。作为如今开发计算机视觉应用最流行的库之一, OpenCV 不但能够实时运行许多不同的计算机视觉算法,而且几乎可以兼容所有的 平台。 本书本着学以致用的精神,每章都包含现实世界的例子和示例代码,以帮助读者更 好地了解它们在现实生活中的应用。桃李不言,下自成蹊,对本书最真实的评价,来自 于广大的读者朋友。 本书翻译的过程并不短暂,作为译者,我们尽可能地忠于原著。对于本书中大量的 专业术语尽量遵循已有的标准,并参阅了大量文献,以便于读者理解。 全书由呆萌院长、李风明和李翰阳共同完成翻译。由于水平有限,书中出现的错误 和不妥之处,恳请读者批评指正。 译者 2016 年 6 月前  言 Preface OpenCV 是开发计算机视觉应用最流行的库之一。它使我们能够实时运行许多不同 的计算机视觉算法。它已经存在了很多年,并成为这个领域的标准库。OpenCV 的主要 优点之一是它的高度优化和几乎可以在所有平台上兼容。 本书首先介绍了计算机视觉中的各个领域和在 C++ 中相关的 OpenCV 功能。每章 都包含真实世界的例子和示例代码帮助你轻松地掌握主题,并了解它们在现实生活中的 应用。总之,本书是一部实用指南,会教你如何在 C++ 中使用 OpenCV,并建立各种应 用程序。 本书的主要内容 第 1 章涵盖各种操作系统的安装步骤,介绍了人类视觉系统,以及计算机视觉中的 各种主要内容。 第 2 章讨论如何在 OpenCV 中读 / 写图像和视频,并且介绍如何使用 CMake 建立 一个项目。 第 3 章介绍如何通过创建一个图形用户界面和鼠标事件检测器来实现交互式应用 程序。 第 4 章探讨直方图和滤波器,也演示了如何卡通化图像。 第 5 章描述了各种图像的预处理技术,如去除噪声、阈值化,以及轮廓分析。 第 6 章处理对象识别和机器学习,并学习如何使用支持向量机建立一个对象分类 系统。 第 7 章讨论了人脸检测和 Haar 级联,并解释如何使用这些方法来检测人脸的各个V 部分。 第 8 章探索背景差分、视频监控和形态学图像操作,并描述了它们如何彼此关联。 第 9 章介绍如何使用不同的技术跟踪对象,如基于颜色和基于特征。 第 10 章介绍光学字符识别、文本分割和 Tesseract OCR 引擎。 第 11 章深入研究 Tesseract OCR 引擎,介绍如何将它应用于文本检测、提取和 识别。 你需要准备什么 本书的例子会用到以下技术: ● OpenCV 3.0 或更新的版本 ● CMake 3.3.x 或更新的版本 ● Tesseract ● Leptonica(Tesseract 依赖包) ● QT(可 选) ● OpenGL(可 选) 相关章节提供了详细的安装说明。 本书的读者对象 本书面向 OpenCV 初学者,以及希望在 C++ 中使用 OpenCV 进行计算机视觉应用 开发的开发人员。懂得 C++ 的基础知识将有助于理解本书。本书对于想要开始学习计 算机视觉,并了解基本概念的人来说同样适用。他们应该知道基本的数学概念,如向 量、矩阵、矩阵乘法,等等,这样才能最大限度地利用本书。在阅读本书的过程中,你 将从头学习如何使用 OpenCV 创建各种计算机视觉应用。 下载示例代码 可登录 http://www.hzbook.com,下载本书示例代码。目  录 Contents 译者序 前言 第 1 章 OpenCV 的探险之旅 1 1.1 理解人类视觉系统 1 1.2 人类是怎么理解图像内容的 3 1.3 OpenCV 可以做什么 4 1.4 安装 OpenCV 11 1.5 总结 14 第 2 章 OpenCV 基础知识介绍 15 2.1 CMake 基本配置文件 15 2.2 创建库 16 2.3 管理依赖关系 17 2.4 脚本复杂化 19 2.5 图像和矩阵 21 2.6 读写图像 23 2.7 读取视频和摄像头 27 2.8 其他基本对象类型 30 2.9 矩阵的基本运算 33VII 2.10 基本数据持久性和存储 36 2.11 总结 38 第 3 章 图形用户界面和基本滤波 39 3.1 介绍 OpenCV 的用户界面 39 3.2 使用 OpenCV 实现基本图形用户界面 40 3.3 QT 的图形用户界面 45 3.4 在界面上添加滑动条和鼠标事件 47 3.5 在用户界面上添加按钮 51 3.6 支持 OpenGL 55 3.7 总结 60 第 4 章 深入研究直方图和滤波器 61 4.1 生成 CMake 脚本文件 62 4.2 创建图形用户界面 63 4.3 绘制直方图 65 4.4 图像色彩均衡化 69 4.5 LOMO 效果 71 4.6 卡通效果 76 4.7 总结 80 第 5 章 自动光学检测、目标分割和检测 81 5.1 隔离场景中的目标 82 5.2 创建 AOI 应用程序 84 5.3 输入图像的预处理 86 5.4 分割输入图像 92 5.5 总结 101 第 6 章 学习目标分类 102 6.1 介绍机器学习的概念 103VIII 6.2 计算机视觉和机器学习的工作流程 106 6.3 自动检测目标分类的示例 108 6.4 特征提取 110 6.5 总结 120 第 7 章 识别人脸部分并覆盖面具 121 7.1 理解 Haar 级联 121 7.2 积分图 123 7.3 在实时视频中覆盖上面具 124 7.4 戴上太阳镜 127 7.5 跟踪鼻子、嘴和耳朵 130 7.6 总结 131 第 8 章 视频监控、背景建模和形态学操作 132 8.1 理解背景差分 132 8.2 简单背景差分法 133 8.3 帧差值法 137 8.4 混合高斯方法 141 8.5 形态学图像操作 144 8.6 图像细化 145 8.7 图像加粗 146 8.8 其他形态学运算 147 8.9 总结 152 第 9 章 学习对象跟踪 153 9.1 跟踪特定颜色的对象 153 9.2 建立交互式对象跟踪器 156 9.3 使用 Harris 角点检测器检测点 161 9.4 Shi-Tomasi 角点检测器 163 9.5 基于特征的跟踪 166IX 9.6 总结 175 第 10 章 文本识别中的分割算法 176 10.1 OCR 简介 176 10.2 预处理步骤 178 10.3 在你的操作系统上安装 Tesseract OCR 186 10.4 使用 Tesseract OCR 库 190 10.5 总结 195 第 11 章 使用 Tesseract 识别文本 196 11.1 文本识别 API 工作原理 196 11.2 使用文本识别 API 200 11.3 总结 212第 1 章 OpenCV 的探险之旅 计算机视觉应用是很有趣也很有用的,但是它的基础算法是计算密集型的。伴随 着云计算的到来,我们拥有越来越多处理这种算法能力。在实际情况下,OpenCV 库可 以更有效地运行计算机视觉算法。它已经存在很多年了,并且已经成为这个领域的一 个标准库了。OpenCV 的一个主要优势是它已经被高度优化,并且几乎支持在所有平 台上使用。这本书即将介绍 OpenCV 的方方面面包括:我们使用的算法,为什么使用 OpenCV,以及怎么整合 OpenCV 到各个领域。 本章下面将要介绍如何在多操作系统环境下安装 OpenCV。OpenCV 提供的可以立 即使用的功能有哪些,以及可以使用内置函数做到的事情。 在本章结束时候,你可以回答出以下几个问题: ● 人们怎么处理视觉数据,以及怎么理解图像内容? ● OpenCV 可以做些什么,在 OpenCV 提供的大量模块中哪些可以用来完成这些 事情? ● 如何在 W indows、Linux 以及 Mac OS X 上安装 OpenCV ? 1.1 理解人类视觉系统 在接触 OpenCV 功能之前,我们首先需要了解这些功能为什么要创建。了解人类视 Chapter 12   OpenCV 实例精解 觉系统的工作原理是很重要的,因为只有这样你才能够开发出正确的算法。计算机视觉 算法的目的是理解图像和视频的内容。人类似乎可以毫不费力地做到这一点!那么,如 何使机器也具有同样的准确性呢? 接下来,看看下面的图: Ύቚ 人类的眼睛会同时捕获色彩、形状、亮度等信息。在上图中,人眼捕获了这两个主 体的所有信息,并以某种方式将它们存储起来。一旦明白了人类的视觉系统是如何工作 的,我们就可以利用这个来实现预期结果。举个例子,下面有几件事情需要知道: ● 人类视觉系统对低频内容敏感程度高于高频内容。低频内容是指像素值不迅速 改变的平面区域,高频内容是指像素值波动很大的角落和边缘地区。你可能已 经注意到,如果在平坦的表面有斑点,就可以很容易地被发现,但是如果在质 地不平的表面就很难被发现。 ● 人眼对亮度的变化敏感程度高于颜色的变化。 ● 人类视觉系统对运动的事物很敏感。如果有东西在视野中移动,即使人们没有 直视它,也能很快地意识到。 ● 人们往往会用心记住视野内突出的点。下面来想象一下,有一个白色的桌子, 它的四条腿是黑色的,并且表面的某个角落上有一个红点。当看着这张桌子时, 你会立即记住表面和腿有对立的颜色,并且其中一个角落有一个红点。人类大 脑是很聪明的!它会立即做这些,以便下次遇到的时候能够对其进行快速识别。 为了解人类视野,接下来看看人类看不同事物的角度:。面的不变性层次结构 认出它。在人类视觉系统中,大脑创建了可以帮助我们的稳健有关位置、缩放和角度方 通过形状和一些重要的特征记忆一个对象。不管这个对象是如何摆放的,人们仍然可以 原因,我们可以很轻松地识别出它。机器反而不能轻松处理好这样的情况。人们趋向于 比正常大一倍尺寸并且倾斜 45 度角放着的椅子仍然是一张椅子。因为处理方式的 如方向、尺寸、观点,以及不要紧的光照等因素。 同类别对象的不变性排序。当人们观察某个对象时,他们的大脑提取了一些特征点,例 事物,并且还可以将相似的对象归类成组。之所以可以做到这个,是因为人类开发对相 大脑中一块区域的基本层次结构,它会有助于对象识别。人们可以毫无费劲地认知不同 侧视觉通路。这个腹侧视觉通路涉及与对象识别相关联的人类视觉系统回路。这是人类 为了得到这个问题的答案,首先要理解人们是怎么做到的。视觉数据处理发生在腹 研究者们进行了多年研究才找出为什么计算机不擅长做这种人类相当擅长的事情。 子。对,你会立马意识到那是张椅子。实际上从另一方面说,计算机很难做好这件事。 劲地一眼识别出它们。事实上,当看到一张椅子,你不会等待几分钟才意识到那是张椅 环顾四周,你会看到很多对象。每天可能会遇到各种各样的对象,但你会毫不费 1.2 人类是怎么理解图像内容的 过在互联网上阅读人类视觉系统模型来深入探索它。 人类视觉系统实际上能够做更多的事情,但这样足以开始下面的内容了。你可以通 רӠ᣻ಔ 5e-10e ౿ഝ 5e-30e ਘჲ 30e-60e ᶭᙾє ᶊ᣻ܲ 第 1 章 OpenCV 的探险之旅   34   OpenCV 实例精解 如果你对人类视觉系统有深入研究,就会发现人们有很多细胞在视觉皮层。这些细 胞可以识别出曲线和直线等形状。当深入腹侧通路时,我们会看到更复杂的细胞。这些 细胞被训练去反应更为复杂的事物,例如树和门等。人类腹面通路中的神经元会在感觉 域上显示尺寸增长。这也与加上它们首选的刺激的复杂性地增加的事实相关联。 为什么机器很难理解图像内容 现在,我们理解了视觉数据是怎么进入人类视觉系统,以及人类视觉系统怎么处理 它。目前的问题是还没理解透彻人类大脑如何识别和组织这些视觉数据。人们仅从图像 中提取出一些特征,并且要求计算机通过机器学习算法学习人类。仍有很多变化例如形 状、尺寸、观点、角度、光照、遮挡等。例如,在机器眼里,同样的椅子从侧面看起来 不一样。不管它如何呈现,人们可以很容易地识别出它是一张椅子。但是应该如何跟计 算机解释这个呢? 一种处理方法是将一个对象不同的变化存储起来,包括大小、角度、光照等。但 是这样处理过于麻烦又太耗时。而且事实上,它不能将能遇到的每一种变化数据收集起 来。为了识别出这些对象,计算机会消耗大量内存和时间去构建模型。即使能满足所有 这些,当存在特殊遮挡的,计算机仍不能够识别出它,因为计算机会认为它是一个新事 物。所以,在构建一个计算机视觉库时,我们需要构建基本功能块,那样就可以在各种 各样的情况下结合成复杂的算法。OpenCV 提供了很多功能,并且这些功能得到了很好 的优化。所以,一旦理解 OpenCV 提供的立即使用方法,我们就可以高效地使用它创建 有趣的应用。OpenCV 的方法将在下一章具体介绍。 1.3 OpenCV 可以做什么 使用 OpenCV,你可以做相当多能够想象出的计算机视觉任务。现实生活中的问题 需要使用很多函数块来完成预期结果。所以,还需要理解哪些模块和函数能达到预期的 效果。下面开始介绍 OpenCV 提供的可以立即使用的方法。 1.3.1 内置数据结构和输入 / 输出 OpenCV 中最利好的消息是它提供了大量内置基元去处理涉及图像处理和计算机视 觉的操作。如果从零开始写一些东西,你需要定义一些对象包括图像、点、矩形等。这第 1 章 OpenCV 的探险之旅   5 些几乎是任何计算机视觉算法的基础。OpenCV 提供了这些可以立即使用的基本框架, 并且在核心模块中包含了它们。另一个优势是这些基本框架已经在运行速度和内存使用 上进行了优化,所以不需要担心实现细节。 imgcodecs 模块处理图像文件的读写。当处理写入图像和创建图像文件时,你可以 通过简单的命令将图像保存为 JPG 或者 PNG 格式的文件。当使用摄像机的时候,需要 处理大量的视频文件。videoio 模块可以处理视频文件所有读写相关的操作。你可以很 容易地从摄像头中获取视频,或者读取不同种格式的视频文件。甚至可以通过设置每秒 帧播放速度、帧的大小等属性将一大堆的帧保存为视频文件。 1.3.2 图像处理方法 当编写计算机视觉算法时,会有一堆能反复使用的基本图像处理操作。imgproc 模 块展示了大部分函数。你可以处理例如图像滤波,形态学操作,几何变换,色彩变换, 绘制图像,结构分析,直方图,形状分析,运动分析,特征检测等事情。接下来思考下 面的图: 右图是左图的一个旋转的版本,可以通过 OpenCV 中的一行代码做到这种转换。 OpenCV 有个叫作 ximgproc 的模块,它包含了高级图像处理算法,例如基于结构森林 的边缘检测,域变换滤波,自适应流形滤波等。 1.3.3 构建 GUI OpenCV 提供了一个叫作 highgui 的模块,它是用来处理高级用户交互操作的。在 处理下一步之前,讨论处理的问题和想要检查图像的样子。这个模块包括了创建用于展 示图像或者视频的窗口等一系列函数。它还包括等待功能,那是等到用户触发键盘上按6   OpenCV 实例精解 键才能进行下一步。还有一个函数可以检测鼠标移动,它对开发交互应用很有帮助。使 用这个功能就可以在输入窗口中绘画出长方形,处理被选择的区域。 考虑如下图: 从图中可以看出在图像上绘制了一个长方形,并且提供了一个底片影响那个区域。 一旦有了这个长方形的坐标,我们就可以仅处理这块区域。 1.3.4 视频分析 视频分析包括了如下任务,例如分析视频中的连续帧之间的运动,跟踪视频中的不 同对象,创建视频监控模型等。OpenCV 提供了 video 模型,它可以处理上面种种问题。 还有个 videostab 模型用于视频去抖动。视频去抖动是摄像机中的一个重要组成部分。 当用手举起相机捕捉视频,你很难保持手保持绝对静止。事实上,当观看这个视频时, 会发现它看上去很差、摇摇晃晃的。所有现代设备在最后将视频展现给用户之前均使用 了视频去抖动技术处理视频。 1.3.5 三维重建 三维重建是计算机视觉中的一个重要课题。通过使用相关算法,就可以将一系列的 二维图像重建出三维场景。OpenCV 提供了可以发现二维图像中大量事物的相关联性来第 1 章 OpenCV 的探险之旅   7 计算它们三维位置的算法。calib3d 模块可以处理所有这些。这个模块同样处理摄像机 标定,它是一个相机的必要估计参数。这些参数是基本的内在参数,这些内在参数主要 是将相机拍摄到的捕捉幕转化为图像。需要知道这些参数以便设计算法,否则会得到意 想不到的结果。接下来看下面的图: 上图从不同角度捕捉相同的对象。接下来的任务就是通过 2D 图像重建原始对象。 1.3.6 特征提取 正如之前讨论的,人类视觉系统趋向于从一个给予场景中提取特征点,这样可以方 便以后检索。为了模仿这点,人们开始设计很多特征提取器,目的是为了从已知图像上 提取这些特征点。一些流行的算法包括 SIFT(尺度不变特征变换)、SURF(加速鲁棒特 征) 和 F AST(加速分段测试特征)等。features2d 模块提供了检测和提取这些特征的函数。 xfeatures2d 模块提供了一些更多的特征提取器,其中一些还在实验中。如果想挑战,你 可以试试这些试验中的特征提取器。其中一个叫作 bioinspired 的模块提供了计算机视觉 仿生模型方面的算法。 1.3.7 目标检测 目标检测是指在给定图像中检测目标的位置。这一过程不关心目标的类型。如果设 计一个椅子检测器,它只会告诉你在给定图像中椅子的位置,而不会告知是张红色高背8   OpenCV 实例精解 椅子还是低背蓝色椅子。检测目标的位置是许多计算机视觉系统中非常关键的一步。考 虑下图: 如果在这张图像上运行椅子检测器,它会在所有的椅子周围加上绿框。它不会告知 椅子的种类!目标检测过去常常是计算密集型的任务,因为在不同尺度下执行检测需要 大量的计算。为了解决这个问题,Paul Viola 和 Michael Jones 在他们的 2001 年的开创 性论文中发明了伟大的算法。你可以在 https://www.cs.cmu.edu/~efros/courses/LBMV07/ Papers/viola-cvpr-01.pdf 上阅读到这篇论文。他们提供快速的方法来设计针对任何对象 的目标检测器。OpenCV 中的 objdetect 和 xobjdetect 两个模块已提供了设计目标检测器 的框架。你可以使用它们开发任意物品的检测器,如墨镜、靴子等。 1.3.8 机器学习 计算机视觉使用各种机器学习算法来实现不同的事情。OpenCV 提供了 ml 模块, 它已经捆绑了很多机器学习算法。这些算法包括贝叶斯分类器(Bayes Classifier)、 K 邻 域(K-Nearest Neighbors)、支持向量机(Support Vector Machine)、决策树(Decision Trees) 、神经元网络(Neural Networks),等等。还有一个叫作 flann 的模块,它包含大数 据集的快速最近邻搜索算法。机器学习算法广泛用于目标识别、图像分类、人脸检测、 视觉搜索等系统构建。第 1 章 OpenCV 的探险之旅   9 1.3.9 计算摄影 计算摄影是指使用先进的图像处理技术来优化相机拍摄的图像。计算摄影使用软件 来处理可视化数据,而不是专注于光学处理和图像捕获方法。一些应用程序包括高动态 范围成像、全景图像、图像光照、光场相机,等等。 接下来,看以下图像: 这是动态范围图像的例子,如果使用常规图像捕获技术就不可能得到它。为此,必 须在多重曝光下捕获相同的场景,彼此注册这些图像,然后很好地将它们混合,并创建 这幅图。photo 和 xphoto 模块包含各种有关计算摄影的算法。stitching 模块提供创建全 景图像的算法。 前面的图像可以在 https://pixabay.com/en/ hdr-high-dynamic-range-landscape-806260/ 上找到。 1.3.10 形状分析 形状的概念是计算机视觉的关键。可以通过认识到各种图像不同的形状来分析可视 化数据。实际上,这是许多算法的重要一步。比如,试图识别图像中的特定标志。现在, 你应该清楚它可以以各种形状、方向、大小展现出来。一个好的起点是量化对象形状特 征。shape 模块提供提取不同的形状,衡量它们间的相似点,起点变换目标形状等算法。10   OpenCV 实例精解 1.3.11 光流算法 光流算法用于跟踪在视频的连续帧中的特征。比如,你想要跟踪视频中的特定对 象。在每个帧上运行特征提取将会消耗大量计算资源;因此,这一处理会很慢。所以, 仅需要从当前帧中提取特征,然后在连续帧上跟踪这些特性。光流算法被广泛使用在基 于视频的计算机视觉应用。optflow 模块包含执行光流所需的大量算法。tracking 模块包 含了跟踪特征的很多算法。 1.3.12 人脸识别和目标识别 人脸识别是指识别给定的图像中的人。这和识别给定的图像中人脸的位置的人脸 检测不同。所以,如果你想要建立一种实用的生物特征识别系统可以识别镜头前的人, 你首先需要进行人脸检测来确定脸的位置,然后,进行人脸识别以辨认出这是谁的脸。 face 模块调用处理人脸识别。 如前文所述,计算机视觉试图将基于人类是如何感知可视化数据的算法模型化。因 此,它有助于查找特征区域和图像中的目标。它可以在如目标识别、目标检测和跟踪等 方面帮助不同的应用程序。saliency 模块为此而设计,它提供了能够检测静态图像和视 频中特征区域的算法。 1.3.13 曲面匹配 我们越来越多地需要与捕获周围对象的三维结构的设备进行交互。这些设备基本 上捕获了常规二维彩色图像的深度信息。因此,构建理解和处理三维目标的算法是至关 重要的。Kinect 是设备捕获可视化数据深度信息的好例子。手头的任务是通过三维目标 与在我们的数据库模型的匹配来识别输入这个目标。如果有一个系统可以识别和定位目 标,那么它可以应用于许多不同的应用程序。surface_matching 模块包含三维对象识别 和使用三维特征进行位置估计的算法。 1.3.14 文本检测与识别 给定场景中的文本鉴定和内容识别变得越来越重要。一些应用程序包括铭牌识别、 自动驾驶汽车识别道路标志,图书扫描转化数字内容等。text 模块包含处理文本检测与 识别的各种算法。第 1 章 OpenCV 的探险之旅   11 1.4 安装 OpenCV 接下来介绍下如何在多系统中设置和运行 OpenCV。 1.4.1 Windows 为方便起见,接下来使用预生成的库来安装 OpenCV。先去 http://opencv.org 网站 下载最新版本的 Windows。当前版本是 3.0.0,可以去 OpenCV 首页获得最新的链接来 下载软件包。 在继续之前,需要确保你有管理员权限。下载的文件将是可执行的文件,所以只要 双击它即可开始安装过程。安装内容将会扩展到一个文件夹中。你能够选择安装路径和 通过检查文件来检查安装。 一旦完成上一步,接下来需要设置 OpenCV 环境变量并将其添加到系统路径以完成 安装。下面将设置一个可创建 OpenCV 库的生成目录的环境变量。我们将会在项目中用 到它。打开终端并输入以下命令: 假设已有安装了 64 位的 Visual Studio 2012。如果安装的是 Visual Studio 2010, 仅需要将前面的命令 vc11 替换为 vc10。前面指定的路径是 OpenCV 二进制文件 的存放地,而且 lib 和 bin 文件夹也在里面。如果使用的是 Visual Studio 2015, 你应该能够从头编译 OpenCV。 接下来,将路径添加到系统的 bin 文件夹中。需要这样做的原因是因为将以动态链 接库(DLL)形式使用 OpenCV 库。基本上,所有 OpenCV 算法都存储在这里。我们的 操作系统在运行时才会加载它们。为了做到这一点,我们的操作系统需要知道它们所在 的位置。系统环境变量将包含在哪能找到所有 Dll 文件的文件夹列表。所以,自然地, 我们需要将 OpenCV 库的路径添加到这个列表中。我们为什么要做这一切呢?另一种选 择是将所需的 DLL 复制到与应用程序的可执行文件(.exe 文件)相同的文件夹中。这是 不必要的开销,尤其是当我们正在进行许多不同的项目。 我们需要编辑环境变量,以便将它添加到这个文件夹。你可以使用如 Path Editor 等 软件做到这一点。你可以从 https://patheditor2.codeplex.com 下载它。一旦你安装它,启 动它,并添加以下新条目(你可以通过右键单击来插入一条新的项目的路径):12   OpenCV 实例精解 往前走,将它保存到注册表。就完成了! 1.4.2 Mac OS X 在本节中,即将看到如何在 Mac OS X 上安装 OpenCV。预编译文件不可用于 Mac OS X,所以我们需要从头编译 OpenCV。在我们开始之前,需要安装 CMake。如果你 没有安装 CMake,你可以从 https://cmake.org/files/v3.3/cmake-3.3.2-Darwin-x86_64.dmg 下载。它是 Dmg 文件!所以,一旦你下载它,只要运行安装程序即可完成安装。 从 opencv .org 网站下载最新版本的 OpenCV。当前版本是 3.0.0,你可以从 https:// github.com/Itseez/opencv/archive/3.0.0.zip 下载它。 解压缩到你所选择的文件夹。OpenCV 3.0.0 还有一个叫作 opencv_contrib 的新包, 其中包含尚未稳定的用户贡献。要时刻牢记的一件事是在 opencv_contrib 中的一些算法 不能免费供商业使用。此外,安装这个软件包是可选的。如果你不安装 opencv_contrib, OpenCV 工作良好。如果我们正在安装 OpenCV,正好可以安装这个包,这样,你稍后 可以尝试使用它(而不是再一次经历整个安装过程)。这个包是一个学习和把玩新算法 的绝佳方式。你可以从 https://github.com/Itseez/opencv_contrib/archive/3.0.0.zip 下载它。 将 ZIP 文件的内容解压缩到你所选择的文件夹。为方便起见,如前所述,将其解压 缩到同一文件夹中,opencv-3.0.0 和 opencv_contrib-3.0.0 在相同的主文件夹中。 现在我们已经准备好构建 OpenCV。打开你的终端并跳转到解压缩的 OpenCV 3.0.0 的文件夹。在替换命令中的正确路径后运行以下命令: 需要时间来安装 OpenCV 3.0.0。跳转到 /full/path/to/opencv-3.0.0/build 目录中,并 且在终端上运行以下命令: 在前面的命令中,-j4 标记表示它使用四个内核安装它。这种方式是更快!现在, 让我们设置库路径。在你的终端运行 vi~/.profile 命令,打开你的 ~/.profile 文件并添加第 1 章 OpenCV 的探险之旅   13 以下代码: 我们需要将 pkg-config 中的 opencv.pc 文件复制到 /usr/local/lib/pkgconfig,并将其 命名为 opencv3.pc。如果存在 OpenCV 2.4.x 安装程序,也不会造成冲突。让我们继续: 同时,我们需要更新 PKG_CONFIG_PATH 变量。打开你的~ /.profile 文件,并添 加以下行: 使用以下命令重新加载你的 ~/.profile 文件: 这样就完成了!接下来看看它是否正常工作: 如果看到Welcome to OpenCV 3.0.0 显示在你的终端上,说明OpenCV 正常工 作。接下来将使用 CMake 生成涵盖整本书的 OpenCV 项目。我们将在下一章更详细地 介绍。 1.4.3 Linux 接下来看看如何在 Ubuntu 上安装 OpenCV。在开始之前,需要安装一些依存关系。 通过在终端上运行以下命令使用软件包管理器来安装它们: 现在,已安装依赖关系。接下来,下载、构建并安装 OpenCV:14   OpenCV 实例精解 将 pkg-config 文件中的 opencv.pc 复制到 /usr/local/lib/pkgconfig,并命名为 opencv3. pc: 这样就完成了!现在就可以通过命令行编译我们的 OpenCV 程序。此外,如果已经 存在 OpenCV 2.4.x 安装程序,并不会造成冲突。接下来检查安装程序是否工作正常: 如果看到 Welcome to OpenCV 3.0.0 显示在你的终端上,说明 OpenCV 正常工作。 在下面的章节中,你将学习如何使用 CMake 来生成 OpenCV 项目。 1.5 总结 在本章中,我们学会了如何在各种操作系统中安装 OpenCV。讨论了人类的视觉系 统和人类如何处理可视化数据。还知道为什么计算机做同样事情比较困难,以及设计一 个计算机视觉库时需要考虑什么。接下来学到了 OpenCV 可以做什么和可以被用来做这 些任务的各种模块。 在下一章中,即将讨论如何处理图像和如何使用各种模块处理它们。还将学习如何 为 OpenCV 应用程序构建一个项目。第 2 章 OpenCV 基础知识介绍 在上一章中,我们学习了如何在各种操作系统环境下安装 OpenCV,接下来将介绍 OpenCV 开发的基础知识。 本章中,你将学到如何使用 CMake 创建项目。 同时本章也会介绍项目工程中所需要的图像基本数据结构、矩阵,以及项目中常见 的其他结构。 最后还会学习如何通过 XML/YAML 持久化的 OpenCV 函数在文件中存储变量和数据。 在本章,将学习如下主题: ● 使用 CMake 配置项目工程 ● 从磁盘上读写文件 ● 通过摄像机读取视频数据 ● 主要图像结构(矩阵) ● 其他重要和基本结构(向量、标量等) ● 基础矩阵操作介绍 ● OpenCV API 中 XML/YAML 持久化的文件存储操作 2.1 CMake 基本配置文件 我们需要通过使用 CMake 来配置和检查项目工程所有依赖关系,但是这不是强制 Chapter 216   OpenCV 实例精解 性的,还可以通过使用例如 Makefiles 或者 Visual Studio 等工具或 IDE 来配置项目工程。 但是,CMake 是配置多平台 C++ 项目工程最方便的方式。 CMake 使用一个叫作 CMakeLists.txt 的配置文件,在其中编译和依赖关系已经定义 好了。对于一个基本程序来说,基于源代码文件的执行编译,一个两行的 CMakeLists. txt 文件是必要的。文件内容大体如下: 第一行定义了需要的 CMake 文件的最低版本。这行在 CMakeList.txt 文件中是强制 性要写的,并且允许从在第二行定义的一个已知版本使用 cmake 功能。它定义了项目工 程名称。这个名称被保存在 PROJECT_NAME 变量中。 最后一行在 main.cpp 文件中创建了一个执行命令(add_executable()),给它和项目 一样的名称(${PROJECT_NAME}),并且将源代码编译进 CMakeTest 可执行文件。在 这个可执行文件中设置了和项目一样的名称。 ${} 表 达式允许进入环境,并定义任意变量。然后,可以使用${PROJECT_ NAME} 变量作为一个可执行输出名。 2.2 创建库 CMake 允许创建那些必须被 OpenCV 编译系统使用的库。在软件开发过程中,在 多个应用中分解共享代码是常见并且有用的实践。在大型应用或者在多个应用中共享通 用代码,这个实践是非常有用的。 在这个例子中,并没有创建一个二进制执行文件,取而代之的是,创建包含了所有函 数、类等可编译文件,来用于开发。我们可以共享这个库给其他应用而不用开放源代码。 CMake 中的 add_library 函数可以实现这个目的: 创建 Hello 库 使用这个新库创建应用 使用新库链接可执行文件第 2 章 OpenCV 基础知识介绍   17 上述代码中的注释用 # 开头,注释会被 CMake 忽略。 add_library(Hello hello.cpp hello.h) 命令定义了新库 Hello,其中包含了如下源代码 hello.cpp,hello.h。添加头文件是允许 IDE(例 如 Visual Studio)去链接头文件。 依据操作系统是动态库还是静态库,这行将生成一个共享文件(OS X 和 Unix 中是.so,Windows 中是.dll)或者一个静态库(OS X 和 Unix 中是.a,Windows 中 是 .dll)。 target_link_libraries(executable Hello) 是一个可以将可执行文件链接到需要的库的功 能。在上述例子中,它是 Hello 库。 2.3 管理依赖关系 CMake 有搜索依赖关系和外部库的能力,这给创建依赖项目工程中的外部组件和 添加复杂需求带来便利。 当然,在本书中最重要的依赖是 OpenCV,我们将它添加到所有的项目工程中去: 接下来分析下脚本的工作原理: 第一行定义了 CMake 的最低版本;第二行告诉 CMake 使用新行为,以便它可以正 确识别数字和布尔值常数而无须使用名称解引用变量。CMake2.8.0 版本介绍了这项政 策,在 3.0.2 版本中不设置 CMake 警告。最后一行定义了项目工程的标题: 需要 OpenCV 显示检测到的 OpenCV 版本信息 创建一个 SRC 变量 创建可执行文件 链接库18   OpenCV 实例精解 这是搜索 OpenCV 依赖项的地方。FIND_PACKAGE 是允许发现依赖项,并根据是 必选还是可选得到的最低版本要求的函数。在这个脚本示例中,需要 OpenCV 3.0.0 版 本或更高版本,而且它是一个必选的软件包。 FIND_PACKAGE 命令包括 OpenCV 的所有子模块,但是可以指定想要包括在 其中使应用程序更小、更快的子模块。例如,如果想要仅使用基本 OpenCV 类 型和核心功能,可以使用下面的命令行: 如果 CMake 发现不了它,它会返回一个错误,但不妨碍编译应用程序。 MESSAGE 函数在终端或 CMake GUI 上显示一条消息。在本例中,我们将显示 OpenCV 版本,具体如下: ${OpenCV_VERSION} 是一个 CMake 存储 OpenCV 包版本的变量。 include_directories() 和 link_directories() 在环境中添加指定库的头文件和目录。 OpenCV 的 CMake 模块将这个数据保存到 ${OpenCV_INCLUDE_DIRS} 和 ${OpenCV_ LIB_DIR} 变量。这些行并非在所有平台(如 Linux)上都是必选的,因为这些路径通常 是在环境中,但它通过正确的链接和引用目录提供了多个 OpenCV 版本供用户选择: 最后一行创建可执行文件并将其链接到 OpenCV 库中,正如我们在 2.2 节中所看到的。 在这段代码中,还有一个名为 SET 的新函数。这个函数创建一个新的变量,并向 其添加所需要的任何值。在本例中,设置 SRC 变量的值为 main.cpp。然而,在下面的 脚本中,可以添加更多值到相同的变量: 需要 OpenCV 显示检测到的 OpenCV 版本信息 创建一个 SRC 变量 创建可执行文件 链接库第 2 章 OpenCV 基础知识介绍   19 2.4 脚本复杂化 本节将会介绍更复杂的但是只有两个文件和几行代码的脚本,它包括子文件夹、库 和可执行文件。 因为可以在主要的 CMakeLists.txt 文件中指定一切,所以无须强制性创建多个 CMakeLists.txt 文件。对每个项目工程的子文件夹使用不同的 CMakeLists.txt 文件是常 见的,目的是使它更灵活和便携。 示例包括一个带有 utils 库文件夹的代码结构文件夹,以及含有主可执行文件等其 他文件的 root 文件夹: 然后需要定义两个 CMakeLists.txt 文件:一个在 root 文件夹,另一个在 utils 文件 夹中。root 文件夹中的 CMakeLists.txt 文件内容如下: OpenCV 包所需 添加预编译器的可选日志 添加 OpenCV 头文件20   OpenCV 实例精解 几乎所有的行在前面的章节都介绍过了,余下的将会在后面的章节中分析。 add_subdirectory() 告诉 CMake 分析所需子文件夹中的 CMakeLists.txt 文件。 再继续分析主文件夹中的 CMakeLists.txt 文件前,需要解释下 utils 文件夹中的 CMakeLists.txt 文件。 在 utils 文 件夹里的 CMakeLists.txt 文件中,需要编写一个在主工程文件夹下要引用 的新库: CMake 脚本定义了可以添加库需要的所有源文件的 UTILS_LIB_SRC 变量,使用 add_library 函数生成库,并使用 target_include_directories 函数来允许主项目检测所有头 文件。 抛开 utils 子文件夹,继续学习 root 文件夹中的 cmake 脚本,在例子中可选函数创 建了新变量 WITH_LOG,并附带少量描述。通过 ccmake 命令行或 CMake GUI 界面, 可以修改这个变量,允许用户启用或禁用这一选项的描述,并决定是否将一个复选框显 示在上面。 这个函数非常有用并允许用户决定有关编译时的特征,例如启用或禁用日志,支持 OpenCV 编译 Java 或 Python 等。 在下面的例子中,使用选项开启项目工程的日志功能。使用代码中的预定义开启日 志功能: 生成新的可执行文件 链接项目的依赖 为 utils 库添加新变量 创建新 utils 库 确保编译器可以找到库中的文件第 2 章 OpenCV 基础知识介绍   21 为了告诉编译器需要LOG 编译时间定义,在CMakeLists.txt 中使用add_ definitions(-DLOG) 函数。若要允许用户决定是否要启用它,只需要用一个简单的条件 来验证是否检查 CMake 变量 WITH_LOG: 现在已经准备好在任何操作系统中创建可以编译计算机视觉项目的 CMake 脚本文 件。下面在开始一个样例项目工程前,继续了解下 OpenCV 基础知识。 2.5 图像和矩阵 计算机视觉中最重要的结构毫无疑问就是图像。计算机视觉中的图像是数字设备 捕获到物理世界的表象。下图中的这张照片只是存储在矩阵格式中的数字序列。每个数 字是一个考虑的波长(例如彩色图像中的红色、绿色或蓝色)或波长范围(对全色设备 而言)的光强衡量。图像中的每个点称为像素(对图像元素而言),每个像素可以存储一 个或多个值,这取决于它是否为灰色、黑色或白色图像(也称为二进制图像),这些值 存储只有一个值,例如 0 或者 1。灰度级尺度图像可以存储一个值,彩色图像可以存储 三个值。这些值通常是介于 0 到 255 之间的整数,但是还可以使用另一个范围。例如, HDRI(High Dyhamic Range,高动态范围成像)或热图像,它们是从 0 到 1 的浮点数字。 图像存储在矩阵格式中,在那里每个像素都有个位置,可以通过列数和行数引用到 它。OpenCV 类用 Mat 类来实现。下图显示的是使用一个单一矩阵的灰度图像:22   OpenCV 实例精解                                                                                                                                                         下图所示的是彩色图像,我们使用一个宽度 x 高度 x 颜色数目的矩阵: ᝅ ᒤ ᑍ                                                                                                                                                                                                         第 2 章 OpenCV 基础知识介绍   23 Mat 类不只用于存储图像,而且还可以存储任意大小的不同类型的矩阵。可以使用 它作为代数矩阵,并执行操作。在下一章中,将会描述最重要的矩阵操作,例如加法、 矩阵乘法、创建一个对角矩阵等。 然而在此之前,需要重点掌握在计算机内存中矩阵内部是如何存储的,因为它始终 能更好地、高效地访问内存插槽,而不是使用 OpenCV 函数访问每个像素。 在内存中,矩阵被保存为数组或值按列和行有序排列的序列。下表显示 BGR 图像 格式中的像素序列: 行 0 行 1 行 2 列 0 列 1 列 2 列 0 列 1 列 2 列 0 列 1 列 2 像素 1 像素 2 像素 3 像素 4 像素 5 像素 6 像素 7 像素 8 像素 9 B G R B G R B G R B G R B G R B G R B G R B G R B G R 按此顺序,通过下面的公式就可以访问任何像素: OpenCV 函数对随机存取进行充分优化,但有时直接访问内存(使用指针计算) 更为有效,例如,在一个循环中访问到的所有像素。 2.6 读写图像 介绍完矩阵,接下来就要从 OpenCV 基本代码开始学习了。首先,需要学会如何读 取和写入图像: OpenCV 头文件 读图像24   OpenCV 实例精解 下面来分析这段代码: 首先,引用在例子中需要用到的函数声明。这些函数来自核心(基本图像数据处 理) 和 high-gui(OpenCV 所提供的跨平台 I/O 函数是 core 和 highui。第一行包括了例如 矩阵等基本类,第二行包括读取、写入和使用图形界面显示图像的函数)。 imread 是用于读取图像的主要函数。这个函数打开图像,并以矩阵格式存储图 像。imread 函数接受两个参数:第一个参数是一个包含这个图像路径的字符串,第二 个参数默认情况下是可选的,它把加载图像作为一种彩色图像。第二个参数允许下列 选项: ● CV_LOAD_IMAGE_ANYDEPTH :如果设置为这个常数,当输入具有相应 的深度时返回一个16 位或32 位图像;否则,imread 函数将它转换为8 位 图像。 ● CV_LOAD_IMAGE_COLOR :如果设置为这个常数,总是将图像转换为彩 色的。 写图像 通过 opencv 函数获取相同像素 显示图像 等待按键 OpenCV 头文件 读图像第 2 章 OpenCV 基础知识介绍   25 ● CV_LOAD_IMAGE_GRAYSCALE :如果设置为这个常数,总是将图像转换为 灰度。 在计算机中,可以使用 imwrite 函数存储矩阵图像: 第一个参数是带有所需扩展格式的图像保存路径。第二个参数是想要保存的矩阵图 像。在示例代码中,创建和存储灰度版本的图像,然后加载并将存储在 gray 变量中的 灰度图像保存为 JPG 格式的灰度图像: 使用矩阵的 .cols 和 .rows 属性,就可以访问图像的列行数,或者换句话说,就可 访问宽度和高度: 若要访问图像的一个像素,可以使用 OpenCV 的 Mat 类中的 cv::Mat::at < typename t >(row,col)模板函数。模板参数是要有返回类型。8 位彩色图像中的 typename 是一 个 V ec3b 类,它存储三个无符号字符数据(Vec = 向量,3 = 组件数,以及 b = 1 字 节 )。 在灰度图像中,可以直接使用图像中的无符号的字符或任何其他数字格式,例如 uchar pixel = color.at < uchar > (myRow,myCol): 最后,若要显示图像,可以使用 imshow 函数创建一个窗口,其中第一个参数是标 题,第二个参数是图像矩阵。 如果想允许等待用户按任意键停止应用程序,可以使用 OpenCV 中的 waitKey 函 数,并将参数设置为要等待的毫秒数。如果将这一参数设置为 0,将永远等待。 这段代码的结果显示在下面的图像中。 写图像 通过 opencv 函数获取相同像素 显示图像 等待按键26   OpenCV 实例精解 最后,在下面的示例中,创建 CMakeLists.txt 来编译项目。 下面的代码是 CMakeLists.txt 文件: 若要编译代码,使用 CMakeLists.txt 文件必须执行下面的步骤: 1. 创建一个 build 文件夹。 2. 在 build 文件夹中,执行 cmake 或在 Windows 中打开 CMake gui 应用程序,选择 源文件夹和生成文件夹,点击配置和生成按钮。 3. 在第 2 步之后,如果使用的是 Linux 或 OS,生成 makefile ;然后使用 make 命令 行编译项目。如果在 Windows 中,必须用在步骤 2 选择的编辑器中打开项目工程并对 其进行编译。 4. 在第 3 步之后,将得到可执行文件 app。 OpenCV 所需第 2 章 OpenCV 基础知识介绍   27 2.7 读取视频和摄像头 本节介绍通过简单的例子读取视频和摄像头: OpenCV 头文件 OpenCV 命令行解析器函数 命令行解析器接受的按键 分析 params 的变量,检查 params 是否正确 如果需要,显示帮助文档 打开默认相机 检查是否成功了28   OpenCV 实例精解 在解释如何读取视频或照相机输入之前,需要介绍一个有用的新类,它将帮助我 们管理输入的命令行参数;该类名为 CommandLineParser,在 OpenCV 3.0 版本中介绍 了它: 为一个命令行解析器做得第一件事是定义需要或允许一个在常字符向量中的参数, 每一行都具有这种模式: name_param 前面可以带有 @,这样定义了这个参数为默认值输入。可以使用多个 name_param: 构造函数将获取主函数的输入和之前定义的关键常量: .has 类方法检查参数是否存在。在这个示例中,我们检查用户是否已添加 -help 获取摄像机的帧 释放的摄像机或视频 cap OpenCV 命令行解析器功能 命令行解析器接受的按键 如果需要帮助显示第 2 章 OpenCV 基础知识介绍   29 或 ? 参数,然后,使用 printMessage 类函数显示所有描述参数: 使用 .get(parameterName)函数,可以访问和读取任何输入参数: 在获得所有必需的参数之后,如果其中一个参数不被解析,可以检查这些参数是否 能够正确解析,并且显示错误消息。例如,添加一个字符串来代替数字: 读取视频和摄像机的类是相同的。在OpenCV 新版本中,VideoCapture 类属于 videoio 子模块。创建对象后,检查输入的命令行 videoFile 参数是否具有路径文件名。 如果它是空的,则尝试打开网络摄像机,如果它有文件名,则打开视频文件。要做到这 一点,可以使用开放函数,把想要打开的视频文件名或者索引照相机作为参数。如果是 单一的摄像机,可以使用 0 作为参数。 检查是否可以读取视频文件名或者摄像机,可以使用 isOpened 函数: 最后,用 namedWindow 函数创建一个显示帧的窗口。在非完成循环中,如果正 确地检索帧,通过 >> 操作可以抓住每个帧,并且使用 imshow 函数显示图像。在这 种情况下,如果我们不想停止应用程序,也需要等待 30 毫秒来检查用户是否想使用 分析它的变量是否被正确解析 打开默认相机 检查是否成功了 获取摄像机的帧 释放的摄像机或视频 cap30   OpenCV 实例精解 waitKey(30) 任意键来停止执行应用程序。 使用摄像机访问的时候,选择一个等待下一帧的合适的值,可以通过计算摄 像机速度值进行设置。例如,如果摄像在20 帧 / 秒工作,合适的等待值是 40=1000/20。 当用户想结束应用时,他不得不只按一个键,然后必须使用 release 函数释放所有 的视频资源。 在计算机视觉应用中释放所有资源是非常重要的;如果不这么做,RAM 内存会 被全部消耗掉。release 函数还可以释放矩阵。 在下面的屏幕快照中显示了代码运行结果,一个显示 BGR 格式的视频或 Web 摄像 机的新窗口: 2.8 其他基本对象类型 之前了解了 Mat 和 Vec3b 的类,但是下面还需要了解其他类。 在本章节中,将会了解在大多数项目中所需的最基本对象类型: ● Vec ● Scalar第 2 章 OpenCV 基础知识介绍   31 ● Point ● Size ● Rect ● RotatedRect 2.8.1 vec 对象类型 vec 是一个模板类,主要用于数值向量。我们可以定义任何类型的向量和大量的组件: 或者可以使用任何预定义类型: 所有预期的向量运算也实现了,如下所示: 和其他扩展运算 (欧几里得向量范数)32   OpenCV 实例精解 2.8.2 Scalar 对象类型 Scalar 对象类型是Vec 派生出的具有四个元素的模板类。Scalar 类型广泛用于 OpenCV,它传递并读取像素值。 若要访问 Vec 和 Scalar 值,可以使用 [] 运算符。 2.8.3 Point 对象类型 另一个非常常见的类模板是 Point。此类定义指定由其 x 和 y 坐标构建的 2D 点。 类似 Point 对象类型,还有支持 3D 点的 Point3 模板类。 像 Vec 类一样,OpenCV 定义以下 Point 别名从而提供便利: 下列运算符定义了点: 2.8.4 Size 对象类型 另一个是非常重要的模板类:Size 类,它用于指定图像或矩形的尺寸。这个类添加 两个成员:宽度和高度,以及一个有用的 area() 函数。 2.8.5 Rect 对象类型 Rect 是另一个重要的模板类,通过下面的参数定义 2D 矩形: L2 范数第 2 章 OpenCV 基础知识介绍   33 ● 顶部左上角的坐标 ● 宽度和高度的矩形 Rect 模板类可以使用定义 ROI(region of interest,感兴趣区域)的图像。 2.8.6 RotatedRect 对象类型 最后一个有用的类是特殊的矩形,称为 RotatedRect。这个类表示旋转的矩形通过 指定中心点、矩形的宽度和高度,以及旋转角度来定义: 这个类的一个有趣的功能是 boundingBox;这个函数返回包含旋转的矩形的 Rect: width center Bounding Box height 2.9 矩阵的基本运算 在本节中,将会学习一些基本和重要的矩阵运算,可以将其应用于图像或任何矩阵 数据。 还学会了如何在变量 Mat 中加载图像并存储,我们可以手动创建一个 Mat 变量。 最常见的构造函数提供的矩阵大小和类型如下所示: 使用下列构造函数,可以不复制数据从第三方库的存储缓冲区来创建一个新的 矩阵链接(Matrix link):34   OpenCV 实例精解 支持的类型取决于类型的存储和通道数。最常见的类型如下所示: 可以用 CV_number_typeC(n) 创建任何类型的矩阵, number_type 是从 8U(无符 号的 8 位) 到 64F(64 位浮点数)。( n)是通道的数量。所允许的通道数是从 1 到 CV_CN_MAX。 初始化数据,可能会得到不良值。若要避免不良值,可以使用 0 或 1 函数初始化 0 或 1 值的矩阵: 前面矩阵的输出如下所示: 00000 00000 00000 00000 00000 11111 11111 11111 11111 11111 eye 函数可以初始化特殊矩阵,创建一个指定类型(CV_8UC1,CV_8UC3...)和大 小的恒等矩阵: 输出如下所示: 10000 01000 00100 00010 00001 OpenCV 的 Mat 类允许所有矩阵操作。可以通过 + 和 - 运算符进行两个矩阵的加或 减操作:第 2 章 OpenCV 基础知识介绍   35 先操作的结果如下所示: 100 010 111 111 211 121 0 1 1 101 111 111 100 010    可以使用 mul 函数,它将用 * 运算符进行一个矩阵与标量乘法,或每个矩阵元素乘 法,又或者一个矩阵与矩阵乘法: 操作的结果如下所示: 100 010 100 010 11 11 200 020 211 121 433 343 833 383 * 2 .* * 11 11 11    其他常见的数学矩阵运算是通过 t() 和 inv() 函数实现矩阵转换和矩阵求逆。 OpenCV 为我们提供了其他有用的函数是在一个矩阵中进行数组操作;例如,计算 非零元素。这是有用来计数像素或区域的对象: OpenCV 提供了一些统计的功能。使用 meanStdDev 函数可以计算平均值和标准偏 差的通道: 其他有用的统计函数是 minMaxLoc。minMaxLoc 函数可以发现矩阵或数组的最小 值和最大值,并返回其位置和值: 在这里,src 是输入的矩阵,minVal 和 maxVal 是双精度值检测,minLoc 和 maxLoc 是检测到的点值。 标量矩阵 矩阵元素乘法 矩阵乘法36   OpenCV 实例精解 其他核心和有用的功能可参见 http://docs.opencv.org/modules/core/doc/core.html。 2.10 基本数据持久性和存储 之前完结本章前,还将继续探讨 OpenCV 函数存储和读取数据。校准或机器学习 等许多应用会在完成计算时将结果保存,方便在下一次检索到它们。OpenCV 提供的 XML/YAML 持久化层可以完成这个任务。 文件存储写入 要将一些 OpenCV 数据或其他的数值数据写入文件,可以使用 FileStorage 类中(如 STL 流 的) c 运算符的流: 创建写 保存为 int 创建 mat 文例 打印结果 释放文件第 2 章 OpenCV 基础知识介绍   37 要创建保存数据的存储文件,只需要调用文件路径的扩展格式是 XML 或者 YAML 的构造函数,并将第二个参数设置为 WRITE: 如果想要保存数据,只需对第一阶段识别和想要在稍后阶段中保存的矩阵或值使用 流操作符。例如,若要保存 int,需要编写下面的代码: mat 的示例如下所示: 在 YAML 格式下显示如下: 前面已保存文件的读操作与保存操作非常相似: 首先,通过适当的路径和 FileStorage::READ 参数使用 FileStorage 构造函数打开已 保存文件: 打印结果38   OpenCV 实例精解 若要读取任何存储的变量,我们只需要通过 FileStorage 对象和 [] 运算符,来识别 使用 >> 流运算符: 2.11 总结 在本章中,我们学会了如何访问图像和视频,以及它们在矩阵中的存储方式。 我们还学习了基本矩阵运算和存储像素、向量等的 OpenCV 类。 最后学习了如何将数据保存在文件中,以便在其他应用程序或可执行文件中读取 数据。 在下一章中,我们将通过学习 OpenCV 提供的图形用户界面的基础知识来创建第一 个应用程序,同时还会创建按钮和滑动条,并学习一些有关图像处理的基础知识。第 3 章 图形用户界面和基本滤波 在上一章中,我们学习了 OpenCV 的一些基本的类和结构,Mat 是其中一个重要的类。 我们还学习了如何读取和储存图像、视频,以及存储在内存中图像的内部结构。 现在已经做好开始工作的准备了,但是还需要显示结果,并与图像产生一些基本的 交互。OpenCV 提供了一些基本的用户界面用于使用和帮助建立应用程序和原型。 为了更好地理解用户界面的工作原理,在本章末尾会创建一个名为“图像工具” (PhotoT ool)的小应用程序。在这个应用中,我们将会学习如何使用滤波和色彩变换。 本章将涵盖如下主题: ● OpenCV 的基本用户界面 ● OpenCV 的 QT 界面 ● 滑动条和按钮 ● 高级用户接口——OpenGL ● 色彩变换 ● 基本滤波 3.1 介绍 OpenCV 的用户界面 OpenCV 有自己本身的跨操作系统的用户界面,这使得开发者不需要学习复杂的库 Chapter 340   OpenCV 实例精解 就可以在用户界面上创建自己的应用程序。 OpenCV 的用户界面是很基础的,但它给计算机视觉开发工程师提供了创建和管理 软件开发的基本功能。所有这些都是本地的,并且在实时使用中被优化过。 OpenCV 提供了两个用户界面的选项: ● 基于本地用户界面的基本界面,例如OS X 用户界面中的Cocoa 或 Carbon, Linux 或 W indows 用户界面中的 GTK。当编译 OpenCV 时,它们是默认选中的。 ● 基于QT 库且略微高级的跨平台用户界面。在编译OpenCV 之前,必须在 CMake 中手动开启 QT 选项。 3.2 使用 OpenCV 实现基本图形用户界面 下面将使用 OpenCV 创建一个基本用户界面。这一界面允许创建窗口,在上面添加 图像,移动它,调整它的大小,以及销毁它。 这个界面是在 OpenCV 一个叫 highui 的模块里: OpenCV 头文件第 3 章 图形用户界面和基本滤波   41 下面来学习上述代码。 首先要做的就是引入 OpenCV 的 highui 模块,这样才可以使用图形用户界面: 读取图像 创建窗口 移动窗口 展示图像 调整窗口大小,仅当非自使用模式时 等待有按键按下 销毁窗口 创建 10 个新窗口 销毁所有窗口42   OpenCV 实例精解 接下来准备创建新窗口,并加载一些需要显示的图像: 使用 namedWindow 函数创建窗口。这个函数有两个参数:第一个参数是窗口名的 常量字符串,第二个参数是可选的,表示所需的窗口属性标志: 在上述例子中,创建了名为 Lena 和名为 Photo 的两个窗口。 QT 和本地图形界面默认有三种属性标志: ● WINDOW_NORMAL:这个标志允许用户重设窗口大小 ● WINDOW_AUTOSIZE:设这个标志的窗口会自动调整大小 ● WINDOW_OPENGL:这个标志开启 OpenGL 的支持 QT 还包含以下属性标志: ● WINDOW_FREERATIO 或 WINDOW_KEEPRATIO: 如果设置为WINDOW_ FREERATIO,则图像不按其原有比例调整;如果设置为 WINDOW_KEEPRATIO, 则图像按其原有比例调整。 ● CV_GUI_NORMAL 或 CV_GUI_EXPANDED :设置第一个标志,则不包含状态 栏和工具栏的基本图形界面。设置第二个标志,则包含状态栏和工具栏的高级 图形界面。 如果使用 QT 编译 OpenCV,所有创建的窗口都默认为扩展界面,但是我们可以 添加 CV_GUI_NORMAL 标志来使用原生的基础图形界面。 默认状态下的标志为WINDOW_AUTOSIZE、WINDOW_KEEPRATIO 和 CV_ GUI_EXPANDED。 当创建多个窗口时,它们是依次叠在前一个窗口上的,但仍可以使用 moveWindow 函数把窗口移到桌面的任意位置: 读取图像 移动窗口第 3 章 图形用户界面和基本滤波   43 在上述代码中,把 Lena 窗口移动到距离桌面左边和顶部各 10 像素的位置,把 Photo 这个窗口移动到距离桌面左边 520 像素和顶部 10 像素的位置: 用 imshow 函数显示之前加载的图像后,使用 resizeWindow 函数把 Lena 窗口的大 小变为 512 像素的宽高。这个函数有三个参数分别是 window name(窗 口 名)、width(宽 度) 和 height(高 度)。 这个特定的窗口大小是对图像而言的。但是工具栏不包含在内。只在不设定 WINDOW_AUTOSIZE 这个标志时,调整窗口大小的方法才管用。 在用 waitKey 函数等待键盘输入之后,就可以用 destroyWindow 函数移除并且销毁 窗口,destroyWindow 函数仅需要输入窗口名这一个参数。 OpenCV 的 destroyAllWindows 函数可以一次性移除创建的所有窗口。为了展示这 个函数怎样使用,我们在实例代码中创建了 10 个窗口并且等待键盘按下。当用户按下 键盘任意一个键,所有的窗口将被销毁。无论如何,当应用终止时,OpenCV 会自动销 毁所有的窗口,不需要在应用结束的时候手动调用销毁窗口的函数: 展示图像 调整窗口大小,仅当处于非自使用模式时 销毁窗口 创建 10 个新窗口 销毁所有窗口44   OpenCV 实例精解 代码运行的结果可以分两步在下图中看到。第一个图像显示了两个窗口: 当有按键被按下时,应用继续运行,并且绘制数个窗口,同时移动它们的位置:第 3 章 图形用户界面和基本滤波   45 3.3 QT 的图形用户界面 Qt 的用户界面为编辑图像提供了更多的控制和选项。这个界面分为三个主要区域: ● 工具栏 ● 图像区域 ● 状态栏 工具栏从左到右包含以下按钮: ● 4 个控制平移的按钮 ● 恢复原大小 ● 拉伸扩大 30 倍,并且显示标签 ● 缩小 ● 放大 ● 保存现有图像 ● 显示属性窗口 在下图中可以更清晰地看到这些选项:46   OpenCV 实例精解 图像区域显示了一幅图,当在图上右击鼠标时显示了一个上下文菜单。可以使用 displayOverlay 函数在这个区域的顶部显示一个覆盖消息层。这个函数接受三个参数: 窗口名、想要显示的文字内容,以及以毫秒为单位的展示时间。如果时间被设置为 0, 则文本不会消失: 最后状态栏在屏幕底部显示像素值,以及在图像中的坐标位置: 还可以用状态栏显示信息,比如在上面加上个覆盖层。改变状态栏信息的函数叫作 displayStatusBar。这个函数和 displayoverlay 函数的参数一样,包括:窗口名、所想要 显示的文字,以及以毫秒为单位展示时间。 展示覆盖层第 3 章 图形用户界面和基本滤波   47 3.4 在界面上添加滑动条和鼠标事件 鼠标事件和滑动条在计算机视觉和 OpenCV 中非常有用。通过使用这些控件,用户 可以直接与界面进行交互并改变其输入图像或变量的属性。 本节将介绍如何在基本交互上添加滑动条和鼠标点击事件。为了正确的理解这些操 作,下面将创建一个使用鼠标事件在图像上画绿圈,并通过滑动条设置图像模糊度的小 项目: 创建一个变量来保存滑动条位置值 滑动条的回调函数 读取图像 创建窗口 创建一个滑动条 调用 onChange 来初始化 等待键盘按下以退出 鼠标的回调48   OpenCV 实例精解 接下来开始分析代码! 首先创建一个变量来存储滑动条位置值,然后需要保存滑动条的位置值方便其他函 数访问: 分别通过 OpenCV 的 setMouseCallbac 和 createTrackbar 函数定义滑动条和鼠标事件 的回调: 在主函数中读取图像并创建 Lena 窗口, 是时候创建滑动条了。OpenCV 的 createTrackbar 函数通过如下顺序参数可以创建 滑动条: ● 滑动条名。 ● 窗口名。 ● 一个整型指针值;这个参数是可选的,如果设置这一值,创建滑动条时初始位 置将在这个位置。 ● 滑动条的最大位置值。 ● 滑动条位置变化时的回调方法。 ● 回传给滑动条的用户数据。不使用全局变量就可以使用回传数据。 销毁窗口 创建一个变量来保存滑动条位置值 滑动条的回调函数 鼠标的回调 读取图像 创建窗口第 3 章 图形用户界面和基本滤波   49 创建好滑动条之后,接下来加上了鼠标点击事件,这样用户可以按着鼠标左键绘制 圆圈。OpenCV 的 setMouseCallback 函数可以实现此操作,这个函数有如下三个参数: ● 获取到鼠标事件的窗口名 ● 鼠标交互的回调函数 ● 鼠标操作时回传的任何用户数据。在上述例子中是回传了 Lena 的整张图像: 在主函数的最后,使用和滑动条相同的参数初始化图像。要执行初始化,我们仅需 手动调用回调函数,然后在关闭窗口前等待事件: 滑动条的回调给图像加上了个基本模糊滤波,其中滑动条的值为模糊效果数值: 这个方法通过 pos 变量检查滑动条的值是否为 0 ;本例不支持滤波,因为会产生错 误。我们无法应用 0 像素的模糊。 创建一个滑动条 调用 onChange 以初始化 销毁窗口 等待键盘按下以退出 滑动条回调方法 输出的辅助变量 获取输入图像的指针 应用模糊滤波 展示输出50   OpenCV 实例精解 在检查完滑动条值之后,创建了一个名为 imgBlur 的空矩阵,用来存储模糊结果。 为了检索通过回调函数发送的用户数据里的图像,必须把 void* userData 转换成正 确的图像指针类型 Mat*。 现在已经有了适合模糊滤波的正确变量。模糊方法为输入图像提供了一个基础中值 滤波,在上面的例子中是 * img,即一个输出的图像。最后一个参数是需要模糊内核大 小(内核是用来计算内核和图像之间的卷积的小矩阵),在上面的例子中,使用了一个 pos 大小的方框内核。 最后,只需要用 imshow 函数更新图像界面。 鼠标事件的回调有五个参数:第一个参数定义了事件类型,第二个和第三个参数定义 了鼠标的位置,第四个参数定义了鼠标滚轮的移动,第五个参数定义了用户的输入数据。 鼠标的事件类型见下表: 事件类型 描  述 EVENT_MOUSEMOVE 当用户移动鼠标 EVENT_LBUTTONDOWN 当用户按下左键 EVENT_RBUTTONDOWN 当用户按下右键 EVENT_MBUTTONDOWN 当用户按下中键 EVENT_LBUTTONUP 当用户释放左键 EVENT_RBUTTONUP 当用户释放右键 EVENT_MBUTTONUP 当用户释放中键 EVENT_LBUTTONDBLCLK 当用户双击左键 EVENT_RBUTTONDBLCLK 当用户双击右键 EVENT_MBUTTONDBLCLK 当用户双击中键 EVENTMOUSEWHEEL 当用户沿竖直方向滚动鼠标滚轮 EVENT_MOUSEHWHEEL 当用户沿水平方向滚动鼠标滚轮 下面的例子仅响应鼠标左键的点击事件,非EVENT_LBUTTONDOWN 的响应 事件都会被过滤。过滤掉其他事件后,会得到输入的图像,如滑动条回调,然后使用 OpenCV 函数在图像中画一个圆: 鼠标回调 取得输入图像的指针第 3 章 图形用户界面和基本滤波   51 3.5 在用户界面上添加按钮 上一节我们学习了如何创建基本界面或 QT 界面,并且使用鼠标和滑动条进行交 互,但是也可以通过创建不同类型的按钮来替代。 按钮仅可用于 QT 窗口。 支持的按钮类型有以下几种: ● 点击(push)按键 ● 复选框(checkbox) ● 单选按钮(radiobox) 按钮只出现在控制面板上。每一个程序中的控制面板是独立窗口,在上面就可以添 加按钮和滑动条。 可以通过点击工具栏最后一个按钮,或在 QT 窗口右键选择“显示属性”窗口,抑 或通过 ctrl + P 快捷键来显示控制面板。 接下来创建按钮的一个基本示例。这段代码比较长,首先分析主函数,然后依次单 独分析回调以便理解它们: 绘制圆型 调用模糊图像方法 读取图像 创建窗口52   OpenCV 实例精解 下面将会应用三种类型:模糊滤波、索贝尔滤波、彩色到灰度图转换。这些过滤器 是可选的,用户可以用创建的按钮选择它们中的任意一个。为了获取滤波的状态,我们 创建了三个全局布尔值变量: 在加载完图像和创建窗口之后,在主函数中使用createButton 方法创建每个 按钮。 OpenCV 中有三种按钮类型,具体如下: ● QT_CHECKBOX ● QT_RADIOBOX ● QT_PUSH_BUTTON 每个按钮有 5 个参数,如下所示: ● 按钮名称 ● 回调函数 ● 一个回调用户数据的指针 ● 按钮类型 ● 复选框和单选按钮默认的初始化类型 然后,创建了一个模糊复选框按钮,两个用于色彩变换的单选按钮,以及一个索贝 尔滤波按钮: 创建按钮 等待键盘按下以退出 销毁窗口第 3 章 图形用户界面和基本滤波   53 这是主函数最重要的部分。接下来将探讨回调函数。每一个回调调用 applyFilters 函数改变滤波的状态变量,并添加触发输入图像的滤波效果。 applyFilters 函数检查每个滤波的状态变量: 创建按钮54   OpenCV 实例精解 使用了 cvtColor 函数可以实现彩色到灰度的转换,它包含三个参数:输入图像、输 出图像、色彩空间转换类型。 最常用的色彩空间转换类型如下: ● RGB 或 BGR 转成灰度(COLOR_RGB2GRAY,COLOR_BGR2GRAY) ● RGB 或 BGR 转成YcrCb(或YCC)( COLOR_RGB2YCrCb,COLOR_ BGR2YCrCb) ● RGB 或 BGR 转成 HSV(COLOR_RGB2HSV,COLOR_BGR2HSV) ● RGB 或 BGR 转成 Luv(COLOR_RGB2Luv,COLOR_BGR2Luv) ● 灰度转成 RGB 或 BGR(COLOR_GRAY2RGB,COLOR_GRAY2BGR) 可以发现这些代码极易记忆。 需要牢记 OpenCV 默认的是 BGR 格式,而且在色值转换为灰度时 RGB 和 BGR 的色彩空间转换是不同的。一些开发人员认为,灰度等于 R + G + B/3,但最理 想的灰度值被称为照度,计算公式是 0.21*R + 0.72*G + 0.07*B。 模糊滤波器在上一节已经描述过了。如果 applySobel 的值为真,最终会应用索贝尔 滤波。 索贝尔滤波是一个使用索贝尔算子的图像导数,常用于边缘检测。OpenCV 允许创 建不同内核大小的不同导数,但最常见的是计算 x 或 y 导数的 3×3 内核。 最重要的几个索贝尔参数如下所示: ● 输入图像 ● 输出图像 ● 输出图像的深度(CV_8U,CV_16U,CV_32F,CV_64F) ● x 导数的顺序 ● y 导数的顺序 ● 内核大小(默认为 3) ● 使用以下的参数创建 3×3 的内核和 x 一阶导数: ● 使用了下面的参数创建 y 阶导数:第 3 章 图形用户界面和基本滤波   55 在上面的例子中,使用的 x 和 y 导数同时覆盖输入值: x 和 y 导数的输出值如下图所示: 3.6 支持 OpenGL OpenCV 支持 OpenGL。OpenGL 是一个以显卡为标准的图形库。OpenGL 允许绘 制从二维到复杂三维的场景。 OpenCV 在一些任务中需要展示 3D 空间,所以需要支持 OpenGL。调用 namedWindow 函数创建窗口时,设置 WINDOW_OPENGL 标记就可以支持 OpenGL。 下面的代码创建一个 OpenGL 支持窗口,并且绘制一个显示网络摄像头帧的旋转 平面:56   OpenCV 实例精解 绘制前的旋转平面 加载帧纹理 创建平面并设置纹理坐标 起始点与坐标纹理 第二点与坐标纹理 第三点与坐标纹理 最终点与坐标纹理 打开网络摄像头 创建新窗口 开启纹理第 3 章 图形用户界面和基本滤波   57 接下来分析上述代码: 首要任务是创建所需的全局变量,包括存储视频采集,保存位置,控制角平面动 画,以及 OpenGL 纹理: 在主函数中,必须创建摄像机去拍摄并检索摄像机帧: 如果摄像机开启成功,开始使用 WINDOW_OPENGL 标记来创建支持 OpenGL 的窗口: 在本例中,想要在一个平面上绘制来自摄像机的图像,必须启用 OpenGL 的纹理: 现在,我们已经准备好用 OpenGL 来绘制窗口,但是首先必须像传统 OpenGL 应用 一样设置绘制 OpenGL 的回调函数,可用 OpenCV 的 setOpengLDrawCallback 函数来实 现,它包含窗口名和回调函数两个参数。 在定义了 OpenCV 窗口和回调函数之后,需要创建一个循环去加载纹理,并通过调 创建第一个纹理 销毁窗口 创建新窗口 开启纹理58   OpenCV 实例精解 用 OpenGL 绘制回调函数更新窗口内容;最后,需要更新角度的位置。 使用 OpenCV 函数更新窗口内容,参数为窗口名。 按下 q 键退出循环。 在编译应用实例之前,需要定义 loadTexture 函数和 on_opengl 回调函数。 loadTexture 函数将 Mat 的帧转换为将要加载并会在每个回调都绘制到的 OpenGL 纹理 图。在将图像加载为纹理之前,需要检查数据变量对象是否为空,以确保帧矩阵包含数据: 如果帧矩阵中有数据,就可以创建 OpenGL 纹理绑定,并且设置一个线性值作为 OpenGL 纹理参数: 现在需要定义如何存储矩阵中的像素以及如何用 OpenGL 的 glTexImage2D 函数创 建像素。一个需要着重注意的点是,OpenGL 使用 RGB 格式而 OpenCV 默认使用 BGR 格式,需要在这个函数里正确地设置它们: 现在只需要主线程调用 updateWindow 函数时完成绘制平面的每一个回调。使用常 用的 OpenGL 函数,然后加载 OpenGL 矩阵标识来重置之前所有变化: 将纹理帧写入内存: 在绘制平面之前,将所有的转换应用在场景中,在代码中,我们将会在(1,1,1) 创建第一个纹理 加载帧纹理第 3 章 图形用户界面和基本滤波   59 轴上旋转平面: 现在设置了正确场景绘制平面,所以将使用 glBegin(GL_QUADS) 绘制四边的面: 使用了(0,0)为中心绘制一个平面。然后,使用 glTexCoord2d 和 glVertex2d 函 数定义纹理坐标的顶点位置: 这段 OpenGL 的代码虽然是过时的,但是它对于我们理解 OpenCV 与 OpenGL 如何结合来说是很有益处的,同时为我们免去了阅读复杂代码的麻烦。想了 解最新的OpenGL 相关知识,推荐阅读Packt 出版社出版的《 Introduction to Modern OpenGL》一书。 运行结果如下图所示: 绘制前的旋转平面 创建平面并设置纹理坐标 起始点与坐标纹理 第二点与坐标纹理 第三点与坐标纹理 最终点与坐标纹理60   OpenCV 实例精解 3.7 总结 在本章中,我们学习了如何使用 OpenGL 创建不同类型显示图像或三维界面的用户 界面。学习了如何创建滑动条和按钮,以及如何绘制三维界面。同时还学会了一些基本 图像处理滤波器。 在接下来的章节中,我们将学习如何运用所学到的图形用户界面去构建一个完整的 照片工具应用程序,还将学习如何对输入图像使用多个滤波。
还剩68页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 6 人已下载

下载pdf

pdf贡献者

niull

贡献于2017-08-20

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf