精通 Python OpenCV4:第一部分

2023-04-27 15:47:27 浏览数 (2)

第 1 部分:OpenCV 4 和 Python 简介

在本书的第一部分中,将向您介绍 OpenCV 库。 您将学习如何安装开始使用 Python 和 OpenCV 进行编程所需的一切。 另外,您还将熟悉通用的术语和概念,以根据您所学的内容进行语境化,并为掌握本书的主要概念奠定基础。 此外,您将开始编写第一个脚本以掌握 OpenCV 库,并且还将学习如何处理文件和图像,这是构建计算机视觉应用所必需的。 最后,您将看到如何使用 OpenCV 库绘制基本和高级形状。

本节将介绍以下章节:

  • 第 1 章,“设置 OpenCV”
  • 第 2 章,“OpenCV 中的图像基础”
  • 第 3 章,“处理文件和图像”
  • 第 4 章,“在 OpenCV 中构建基本形状”

一、设置 OpenCV

使用 Python 精通 OpenCV 4 将为您提供有关构建涉及开源计算机视觉库OpenCV)和 Python 的项目的知识。 将介绍这两种技术(第一种是编程语言,第二种是计算机视觉和机器学习库)。 另外,您还将了解为什么将 OpenCV 和 Python 结合使用具有构建各种计算机应用的潜力。 最后,将介绍与本书内容有关的主要概念。

在本章中,将逐步指导您安装开始使用 Python 和 OpenCV 进行编程所需的一切。 第一章很长,但是不用担心,因为它被分为容易理解的部分,从一般的术语和概念开始,假定读者是新手。 在本章的最后,您将能够构建第一个涉及 Python 和 OpenCV 的项目。

本章将涵盖以下主题:

  • OpenCV 库的理论介绍
  • 安装 Python OpenCV 和其他包
  • 运行示例,文档,帮助和更新
  • Python 和 OpenCV 项目结构
  • 第一个 Python 和 OpenCV 项目

技术要求

本章及后续各章重点讨论与计算机视觉,机器学习和深度学习技术(以及其他技术)相关的 Python(一种编程语言)和 OpenCV(一个计算机视觉库)概念。 因此,应该在计算机上安装 Python 和 OpenCV。 此外,还应该安装一些与科学计算和数据科学有关的 Python 包(例如 NumPy 或 Matplotlib。

此外,建议您安装集成开发环境IDE)包,因为它有助于计算机程序员进行软件开发。 从这个意义上讲,建议使用 Python 特定的 IDE。 实际上是 Python IDE 是 PyCharm,可以从这里下载。

最后,为了促进 GitHub 活动(例如,克隆存储库),您应该安装 Git 客户端。 从这个意义上讲,GitHub 提供了包括最常见的存储库操作的桌面客户端。 有关 Git 命令的介绍,请查看这里,其中总结了常用的 Git 命令行说明。 此外,还包括在操作系统上安装 Git 客户端的说明。

可从这里访问本书的 GitHub 存储库,其中包含从本书第一章到最后一章所需的所有支持项目文件。

最后,应该注意的是,使用 Python 精通 OpenCV 的 GitHub 存储库的 README 文件包括以下内容,出于完整性考虑,此文件也附在此处:

  • 代码测试规范
  • 硬件规格
  • 相关书籍和产品

代码测试规范

使用 Python 精通 OpenCV 4 需要一些已安装的包,您可以在这里查看:

  • 第 1 章,“设置 OpenCV”:opencv-contrib-python
  • 第 2 章,“OpenCV 中的图像基础”:opencv-contrib-pythonmatplotlib
  • 第 3 章,“处理文件和图像”:opencv-contrib-pythonmatplotlib
  • 第 4 章,“在 OpenCV 中构建基本形状”:opencv-contrib-pythonmatplotlib
  • 第 5 章,“图像处理技术”:opencv-contrib-pythonmatplotlib
  • 第 6 章,“直方图的构建”:opencv-contrib-pythonmatplotlib
  • 第 7 章,“阈值处理技术”:opencv-contrib-pythonmatplotlibscikit-imagescipy
  • 第 8 章,“轮廓检测,过滤和绘制”:opencv-contrib-pythonmatplotlib
  • 第 9 章,“增强现实”:opencv-contrib-pythonmatplotlib
  • 第 10 章,“使用 OpenCV 的机器学习”:opencv-contrib-pythonmatplotlib
  • 第 11 章,“人脸检测,跟踪和识别”:opencv-contrib-pythonmatplotlibdlibface-recognitioncvlibrequestsprogressbarkerastensorflow
  • 第 12 章,“深度学习简介”:opencv-contrib-pythonmatplotlibtensorflowkeras
  • 第 13 章,“使用 Python 和 OpenCV 的移动和 Web 计算机视觉”:opencv-contrib-pythonmatplotlibflasktensorflowkerasrequestspillow

确保已安装包的版本号等于或大于此处指定的版本,以确保代码示例正确运行。

如果要安装本书经过测试的确切版本,请从pip安装时包括该版本,如下所示。

运行以下命令以安装主要模块和贡献模块:

  • 安装opencv-contrib-python
代码语言:javascript复制
pip install opencv-contrib-python==4.0.0.21

应该注意的是,OpenCV 需要numpy。 安装opencv-contrib-python==4.0.0.21时已安装numpy-1.16.1

运行以下命令以安装 Matplotlib 库:

  • 安装matplotlib
代码语言:javascript复制
pip install matplotlib==3.0.2

应当注意,matplotlib需要kiwisolverpyparsingsixcyclerpython-dateutil

安装matplotlib==3.0.2时,已经安装了cycler-0.10.0kiwisolver-1.0.1pyparsing-2.3.1python-dateutil-2.8.0six-1.12.0

运行以下命令以安装库,该库包含用于图像处理的算法的集合:

  • 安装scikit-image
代码语言:javascript复制
pip install scikit-image==0.14.2

应当注意,scikit-image需要cloudpickledecoratornetworkxnumpytoolzdaskpillowPyWaveletssix

安装scikit-image==0.14.2时,已安装PyWavelets-1.0.1cloudpickle-0.8.0dask-1.1.1decorator-4.3.2networkx-2.2numpy-1.16.1pillow-5.4.1six-1.12.0toolz-0.9.0

如果需要 SciPy,可以使用以下命令进行安装:

  • 安装scipy
代码语言:javascript复制
pip install scipy==1.2.1 

应当注意,scipy需要numpy

安装scipy==1.2.1时已安装numpy-1.16.1

运行以下命令以安装dlib库:

  • 安装dlib
代码语言:javascript复制
pip install dlib==19.8.1

要安装面部识别库,请运行以下命令:

  • 安装face-recognition
代码语言:javascript复制
pip install face-recognition==1.2.3

应当注意,face-recognition需要dlibClicknumpyface-recognition-modelspillow

安装face-recognition==1.2.3时,已经安装了dlib-19.8.1Click-7.0face-recognition-models-0.3.0pillow-5.4.1

运行以下命令以安装开源计算机视觉库:

  • 安装cvlib
代码语言:javascript复制
pip install cvlib==0.1.8

要安装请求库,请运行以下命令:

  • 安装requests
代码语言:javascript复制
pip install requests==2.21.0

应当注意,requests需要urllib3chardetcertifiidna

安装requests==2.21.0时,已经安装了urllib3-1.24.1chardet-3.0.4certifi-2018.11.29idna-2.8

运行以下命令以安装文本进度栏库:

  • 安装progressbar
代码语言:javascript复制
pip install progressbar==2.5 

运行以下命令以安装 Keras 库以进行深度学习:

  • 安装keras
代码语言:javascript复制
pip install keras==2.2.4

应当注意,keras需要numpysixh5pykeras-applicationsscipykeras-preprocessingpyyaml

安装keras==2.2.4时,已经安装了h5py-2.9.0keras-applications-1.0.7keras-preprocessing-1.0.9numpy-1.16.1 pyyaml-3.13scipy-1.2.1 six-1.12.0

运行以下命令以安装 TensorFlow 库:

  • 安装tensorflow
代码语言:javascript复制
pip install tensorflow==1.12.0 

应该注意的是 TensorFlow 需要termcolornumpywheelgastsixsetuptoolsprotobufmarkdowngrpciowerkzeugtensorboardabsl-pyh5pykeras-applicationskeras-preprocessingastor

termcolor-1.1.0numpy-1.16.1wheel-0.33.1gast-0.2.2six-1.12.0, setuptools-40.8.0protobuf-3.6.1markdown-3.0.1grpcio-1.18.0werkzeug-0.14.1tensorboard-1.12.2absl-py-0.7.0h5py-2.9.0keras-applications-1.0.7keras-preprocessing-1.0.9astor-0.7.1已在安装tensorflow==1.12.0时安装。

运行以下命令以安装 Flask 库:

  • 安装flask
代码语言:javascript复制
pip install flask==1.0.2

应当注意,flask需要WerkzeugclickitsdangerousMarkupSafe Jinja2

安装flask==1.0.2时,已经安装了Jinja2-2.10MarkupSafe-1.1.1Werkzeug-0.14.1click-7.0itsdangerous-1.1.0

硬件规格

硬件规格如下:

  • 32 位或 64 位架构
  • 2 GHz CPU
  • 4 GB 内存
  • 至少 10 GB 的可用硬盘空间

了解 Python

Python 是具有动态类型系统和自动内存管理的一种解释型高级通用编程语言。 Python 编程语言的官方主页是这里。 在过去的十年中,Python 的普及率稳步上升。 这是因为 Python 是当今一些最令人兴奋和最具挑战性的技术中非常重要的编程语言。 人工智能AI),机器学习,神经网络,深度学习,物联网IoT)和机器人技术(以及其他)依靠 Python。

这是 Python 的一些优点:

  • Python 被认为是科学计算的理想语言,主要有以下四个原因:
    • 这很容易理解。
    • 它具有(通过包)科学计算的支持。
    • 它消除了其他编程语言所具有的许多复杂性。
    • 它具有简单且一致的语法。
  • Python 可以促进快速原型设计,因为它有助于轻松编写和执行代码。 的确,与其他编程语言相比,Python 可以用最少五分之一的代码来实现相同的逻辑。
  • Python 有许多预建的库(NumPy,SciPy,scikit-learn)可满足您 AI 项目的各种需求。 Python 受益于丰富的科学计算库生态系统。
  • 它是一个独立的平台,使开发人员可以节省在不同平台上进行测试的时间。
  • Python 提供了一些工具,例如 Jupyter 笔记本,可用于以轻松舒适的方式共享脚本。 这在科学计算中是完美的,因为它可以激发交互式计算环境中的协作。

介绍 OpenCV

OpenCV 是具有实时功能的 C 编程库。 由于它是用优化的 C/C 编写的,因此该库可以从多核处理中受益。 下一节将对 OpenCV 库进行理论上的介绍。

与 OpenCV 库有关,以下是其受欢迎的一些原因:

  • 开源计算机视觉库
  • OpenCV(BSD 许可证)是免费的
  • 特定的图像处理库
  • 它拥有 2500 多种优化算法,包括最新的计算机视觉算法
  • 机器学习和深度学习支持
  • 该库针对性能进行了优化
  • 有大量的开发人员使用和支持 OpenCV
  • 它具有 C ,Python,Java 和 MATLAB 接口
  • 该库支持 Windows,Linux,Android 和 macOS
  • 快速定期更新(现在每六个月发布一次正式发布)

使读者具有上下文

为了使读者具有上下文关系,有必要建立和设置与本书主题相关的主要概念的基础。 最近几年,人们对 AI 和机器学习产生了浓厚的兴趣,特别是在深度学习领域。 这些术语可以互换使用,并且经常相互混淆。 为了完整和清楚起见,下面将简要描述这些术语。

人工智能是指使机器(计算机或机器人系统)能够以与人类相同的方式处理信息的一组技术。

术语“AI”通常用作机器技术的保护伞,以提供涵盖多种方法和算法的智能。 机器学习是对计算机进行编程以从历史数据中学习以对新数据进行预测的过程。 机器学习是 AI 的子学科,是指机器根据学习到的相互关系使用的统计技术。 根据收集或收集的数据,计算机可以独立学习算法。 这些算法和方法包括支持向量机,决策树,随机森林,逻辑回归,贝叶斯网络和神经网络。

神经网络是用于机器学习的计算机模型,该模型基于生物大脑的结构和功能。 人工神经元处理多个输入信号,然后,当输入信号的总和超过某个阈值时,将向其他相邻神经元发送信号。 深度学习是机器学习的子集,它对大量非结构化数据(例如人类语音,文本和图像)进行操作。 深度学习模型是一种人工神经网络,其中包括对数据进行的多层数学计算,其中一层的结果作为输入输入到下一层,以对输入数据进行分类和/或进行预测。

因此,这些概念在层次结构上是相互依存的,AI 是最广义的术语,而深度学习是最具体的术语。 下图可以看到这种结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWE2QpIS-1681870288340)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b96f4ca6-5e9a-4e9a-a4dd-4494e1dd5642.png)]

计算机视觉人工智能的一个跨学科领域,旨在使具有计算能力的计算机和其他设备从数字图像和视频中获得高层次的理解,包括获取,处理的功能 ,并分析数字图像。 这就是为什么计算机视觉在某种程度上是人工智能的另一个子领域的原因,该领域严重依赖于机器学习和深度学习算法来构建计算机视觉应用。 此外,计算机视觉由多种技术共同作用-计算机图形学图像处理信号处理传感器技术数学甚至是物理

因此,可以完成前面的图来介绍计算机视觉学科:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0QxawVLK-1681870288341)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/e321f2e3-eef8-4139-aea0-17baeda805d1.png)]

OpenCV 库的理论介绍

OpenCV 是一个具有实时计算机视觉功能的编程库,它对于学术和商业用途都是免费的(BSD 许可证)。 在本节中,将介绍有关 OpenCV 库的信息,包括其主要模块以及与该库有关的其他有用信息。

OpenCV 模块

OpenCV(从版本 2 开始)分为几个模块,每个模块通常可以理解为专用于一组计算机视觉问题。 在下图中可以看到这种划分,其中显示了主要模块:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5tf4zhEI-1681870288342)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/2bf8b1ee-1a38-410e-ba15-b566ccfb19f2.png)]

OpenCV 模块在此处简短描述:

  • core:核心功能。 核心功能是一个定义基本数据结构的模块,也是库中所有其他模块使用的基本功能。
  • imgproc:图像处理。 图像处理模块,包括图像过滤,几何图像转换,色彩空间转换和直方图。
  • imgcodecs:图像编解码器。 图像文件读写。
  • videoio:视频 I/O。 视频捕获和视频编解码器的接口。
  • highgui:高级 GUI。 UI 功能的接口。 它提供了一个界面,可以轻松地执行以下操作:
    • 创建和操作可显示图像的窗口
    • 将跟踪栏添加到窗口,键盘命令并处理鼠标事件
  • video:视频分析。 一个视频分析模块,包括背景扣除,运动估计和对象跟踪算法。
  • calib3d:相机校准和 3D 重建。 相机校准和 3D 重建涵盖基本的多视图几何算法,立体对应算法,对象姿态估计,单相机和立体相机校准以及 3D 重建。
  • features2d:2D 特征框架。 该模块包括特征检测器,描述符和描述符匹配器。
  • objdetect:对象检测。 检测对象和预定义类的实例(例如,面部,眼睛,人和汽车)。
  • dnn深度神经网络DNN)模块。 该模块包含以下内容:
    • 用于创建新层的 API
    • 一组有用的层
    • 从层构建和修改神经网络的 API
    • 从不同的深度学习框架加载序列化网络模型的功能
  • ml:机器学习。 机器学习库MLL)是可用于分类,回归和聚类目的的一组类和方法。
  • flann:在多维空间中进行聚类和搜索。 用于近似最近邻的快速库FLANN)是非常适合于快速最近邻搜索的算法集合。
  • photo:计算摄影。 该模块提供了一些用于计算摄影的功能。
  • stitching:图像拼接。 该模块实现了执行自动全景图像拼接的拼接管线。
  • shape:形状距离和匹配。 形状距离和匹配模块,可用于形状匹配,检索或比较。
  • superres:超分辨率。 此模块包含一组可用于增强分辨率的类和方法。
  • videostab:视频稳定。 此模块包含一组用于视频稳定的类和方法。
  • viz:3D 可视化器。 此模块用于显示小部件,这些小部件提供了几种与场景和小部件进行交互的方法。

OpenCV 用户

无论您是专业的软件开发人员还是新手程序员,OpenCV 库都将对图像处理和计算机视觉领域的研究生,研究人员和计算机程序员很感兴趣。 该库已在科学家和学者中广受欢迎,因为该库提供了许多最新的计算机视觉算法。

此外,它通常用作计算机视觉和机器学习的教学工具。 应该考虑到 OpenCV 足够强大以支持实际应用。 因此,OpenCV 可以用于非商业和商业产品。 例如,它被 Google,Microsoft,Intel,IBM,Sony 和 Honda 等公司使用。 MIT,CMU 或 Stanford 等一流大学的研究所为库提供支持。 OpenCV 已被世界各地采用。 它的下载量超过 1400 万,社区中的人口超过 47,000。

OpenCV 应用

OpenCV 正在广泛的应用中使用:

  • 2D 和 3D 特征工具包
  • 街景图像拼接
  • 自我估计
  • 面部识别系统
  • 手势识别
  • 人机交互
  • 移动机器人
  • 运动理解
  • 对象识别
  • 自动化检查和监视
  • 分割与识别
  • 立体视觉 – 两台摄像机的深度感知
  • 医学图像分析
  • 运动结构
  • 运动追踪
  • 增强现实
  • 视频/图像搜索和检索
  • 机器人和无人驾驶汽车的导航和控制
  • 驾驶员嗜睡和注意力分散检测

为什么在您的研究工作中引用 OpenCV

如果您在研究中使用 OpenCV,建议您引用 OpenCV 库。 这样,其他研究人员可以更好地理解您提出的算法并重现您的结果,从而获得更好的信誉。 此外,OpenCV 将增加反响,从而产生更好的计算机视觉库。 以下代码显示了引用 OpenCV 的 BibTex 条目:

代码语言:javascript复制
@article{opencv_library,
 author = {Bradski, G.},
 citeulike-article-id = {2236121},
 journal = {Dr. Dobb's Journal of Software Tools},
 keywords = {bibtex-import},
 posted-at = {2008-01-15 19:21:54},
 priority = {4},
 title = {{The OpenCV Library}},
 year = {2000}
}

安装 OpenCV,Python 和其他包

OpenCV,Python 和 AI 相关的包可以安装在大多数操作系统上。 我们将看到如何通过不同的方法来安装这些包。

在选择最适合您需要的安装选项之前,请确保检查出不同的安装选项。

另外,由于这些文档的普及,在本章的最后对 Jupyter 笔记本进行了介绍,可以运行 Jupyter 笔记本进行数据分析。

全局安装 Python,OpenCV 和其他包

在本节中,您将看到如何全局安装 Python,OpenCV 和任何其他包。 给出了针对 Linux 和 Windows 操作系统的特定说明。

安装 Python

我们将看到如何在 Linux 和 Windows 操作系统上全局安装 Python。

在 Linux 上安装 Python

在 Debian 衍生产品(例如 Ubuntu)上,使用 APT 安装 Python。 之后,建议升级 pip 版本。 PIP 是 PyPA 推荐的安装 Python 包的工具:

代码语言:javascript复制
$ sudo apt-get install python3.7 $ sudo pip install --upgrade pip

要验证 Python 是否已正确安装,请打开命令提示符或 shell 并运行以下命令:

代码语言:javascript复制
$ python3 --version
 Python 3.7.0

在 Windows 上安装 Python

转到这里。 Python Windows 的默认安装程序是 32 位。 启动安装程序。 选择自定义安装:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hVWPX8H-1681870288342)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/170210b9-d5ec-472d-9d53-f26dfba32821.png)]

在下一个屏幕上,应检查所有可选功能:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-llDeD8ZK-1681870288342)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a0e4c7c7-0643-440e-8ca6-254669dae6fb.png)]

最后,在下一个屏幕上,确保选中将 Python 添加到环境变量和预编译标准库。 (可选)您可以自定义安装位置,例如C:Python37

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3Kzzrhx-1681870288343)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/da2afd7d-791f-4d8c-a0d3-1a98f53fb098.png)]

按下“安装”按钮,几分钟后,安装就准备就绪。 在安装程序的最后一页,您还应该按禁用路径长度限制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9oBGH1eY-1681870288343)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/f11adfb7-146f-40cc-aeea-4c5208712400.png)]

要检查 Python 是否已正确安装,请按住Shift键,然后在桌面上的鼠标右键单击。 在此处选择“打开命令窗口”。 或者,在 Windows 10 上,使用左下方的搜索框搜索cmd。 现在,在命令窗口中写入python,然后按Enter键。 您应该会看到以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wiSGGzIp-1681870288343)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b75e99d3-43ea-42fc-8d8f-a5bfb34918a5.png)]

您还应该升级点子:

代码语言:javascript复制
 $ python -m pip install --upgrade pip

安装 OpenCV

现在,我们将在 Linux 和 Windows 操作系统上安装 OpenCV。 首先,我们将了解如何在 Linux 上安装 OpenCV,然后如何在 Windows 上安装 OpenCV。

在 Linux 上安装 OpenCV

确保已安装 NumPy。 要安装 NumPy,请输入以下内容:

代码语言:javascript复制
$ pip3 install numpy

然后安装 OpenCV:

代码语言:javascript复制
$ pip3 install opencv-contrib-python

此外,我们可以安装 Matplotlib,这是一个生成高质量图形的 Python 图形库:

代码语言:javascript复制
$ pip3 install matplotlib

在 Windows 上安装 OpenCV

确保已安装 NumPy。 要安装 NumPy,请输入以下内容:

代码语言:javascript复制
 $ pip install numpy

然后安装 OpenCV:

代码语言:javascript复制
$ pip install opencv-contrib-python

此外,我们可以安装 Matplotlib:

代码语言:javascript复制
$ pip install matplotlib

测试安装

一种测试安装的方法是执行 OpenCV Python 脚本。 为此,在特定的文件夹中应该有两个文件logo.pngtest_opencv_installation.py

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONRazVFs-1681870288343)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b30cd605-ddf6-427b-a4fc-3bdf69849588.png)]

打开一个 cmd 并转到这两个文件所在的路径。 接下来,我们可以通过键入以下内容来检查安装:

代码语言:javascript复制
python test_opencv_installation.py

您应该同时看到 OpenCV RGB 徽标和 OpenCV 灰度徽标:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mYcLN1M3-1681870288344)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/2a580e04-4b55-4487-92a2-8288fd978754.png)]

在这种情况下,安装成功。

使用 Virtualenv 安装 Python,OpenCV 和其他包

virtualenv是一种非常流行的工具,可为 Python 库创建隔离的 Python 环境。 virtualenv允许多个具有不同(有时是相互冲突)要求的 Python 项目。 从技术上讲,virtualenv通过在目录下安装一些文件来工作(例如env/)。

另外,virtualenv修改PATH环境变量以在其前面添加自定义二进制目录(例如env/bin/)。 此外,Python 或 Python3 二进制文件的精确副本位于此目录中。 激活此虚拟环境后,您可以使用 PIP 在虚拟环境中安装包。 PyPA 也推荐virtualenv。 因此,我们将看到如何使用虚拟环境安装 OpenCV 或任何其他包。

通常,pipvirtualenv是仅需要全局安装的两个包。 这是因为,一旦安装了两个包,就可以在虚拟环境中完成所有工作。 实际上,virtualenv实际上就是您所需要的,因为此包提供了pip的副本,该副本被复制到您创建的每个新环境中。

现在,我们将看到如何安装,激活,使用和停用虚拟环境。 现在为 Linux 和 Windows 操作系统提供了特定的命令。 我们不会为每个操作系统添加一个特定的部分,因为每个过程都非常相似。 让我们开始安装virtualenv

代码语言:javascript复制
$ pip install virtualenv

在此目录(env)中,创建了一些文件和文件夹,其中包含运行 python 应用所需的全部内容。 例如,新的 python 可执行文件将位于/env/scripts/python.exe。 下一步是创建一个新的虚拟环境。 首先,将目录更改为项目目录的根目录。 第二步是使用virtualenv命令行工具创建环境:

代码语言:javascript复制
$ virtualenv env

在这里,env是您要在其中创建虚拟环境的目录的名称。 通常的惯例是在env中调用要创建虚拟环境的目录,并将其放入项目目录中。 这样,如果将代码保留在~/code/myproject/,则环境将在~/code/myproject/env/

下一步是使用命令行工具激活刚刚创建的env环境,以执行activate脚本,该脚本位于以下位置:

  • ~/code/myprojectname/env/bin/activate(Linux)
  • ~/code/myprojectname/env/Scripts/activate(Windows)

例如,在 Windows 下,您应该键入以下内容:

代码语言:javascript复制
$ ~/code/myprojectname/env/Scripts/activate
 (env) $

现在,您只能为此激活的环境安装所需的包。 例如,如果要安装使用 Python 编写的 Django(这是一个免费的开放源 Web 框架),则应输入以下内容:

代码语言:javascript复制
(env)$ pip install Django

请记住,此包仅会为myprojectname项目安装。

您还可以通过执行以下操作来停用环境:

代码语言:javascript复制
$ deactivate $

您应该看到已经返回到正常提示,表明您不再处于任何virtualenv中。 最后,如果要删除环境,只需键入以下内容:

代码语言:javascript复制
$ rmvirtualenv test

使用 Python IDE 和 Virtualenv 创建虚拟环境

在下一节中,我们将使用 PyCharm(一个 Python IDE)创建虚拟环境。 但是在此之前,我们将讨论 IDE。 IDE 是一种软件应用,可帮助计算机程序员进行软件开发。 IDE 提供了一个程序,可以完成所有开发。 与 Python IDE 结合,可以找到两种方法:

  • 具有 Python 支持的常规编辑器和 IDE
  • 特定于 Python 的编辑器和 IDE

在第一类(通用 IDE)中,应突出一些示例:

  • Eclipse PyDev
  • Visual Studio 适用于 Visual Studio 的 Python 工具
  • Atom Python 扩展

在第二类中,这是一些特定于 Python 的 IDE:

  • PyCharm:Python 最好的全功能,专用 IDE 之一。 PyCharm 可在 Windows,MacOS 和 Linux 平台上快速轻松地安装。 它实际上是 Python IDE 环境。
  • Spyder:Anaconda 包管理器发行版附带的 Spyder 是一种开源 Python IDE,非常适合数据科学工作流程。
  • Thonny:Thonny 旨在成为初学者的 IDE。 它适用于所有主要平台(Windows,macOS,Linux),并在网站上提供了安装说明。

在这种情况下,我们将安装 PyCharm(实际上是 Python IDE 环境)社区版。 之后,我们将看到如何使用此 IDE 创建虚拟环境。 可以从这里下载 PyCharm。 PyCharm 可以安装在 Windows,MacOS 和 Linux 上:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTvER3hN-1681870288344)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a18e5b7d-f5fa-4c65-a47d-e3accf417c15.png)]

安装 PyCharm 之后,我们就可以使用它了。 使用 PyCharm,我们可以以非常简单直观的方式创建虚拟环境。

通过 PyCharm,可以使用virtualenv工具创建特定于项目的隔离虚拟环境。 此外,virtualenv工具与 PyCharm 捆绑在一起,因此用户不需要安装它。

打开 Pycharm 后,您可以单击“创建新项目”。 如果要创建新环境,则应单击Project Interpreter: New Virtualenv环境。 然后单击使用 Virtualenv 的新环境。 在下一个屏幕截图中可以看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NI6LuBPj-1681870288344)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/091f1599-cc98-46fc-996e-64ca5c34ba59.png)]

您应注意,虚拟环境的名称(默认为 PyCharm)为venv,位于项目文件夹下。 在这种情况下,项目名为test-env-pycharm,虚拟环境venv位于test-env-pycharm/venv。 此外,您可以看到venv名称可以根据您的喜好进行更改。

当您单击创建按钮时,PyCharm 会加载项目并创建虚拟环境。 您应该会看到以下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWn90Rsm-1681870288345)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7003a901-1807-40bf-9e4b-de69580120ad.png)]

创建项目后,只需单击几下就可以安装包。 单击文件,然后单击设置…(Ctrl Alt S)。 将出现一个新窗口,显示如下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1ygPtFm-1681870288345)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/35365b2b-339a-4542-beb0-a82b988c4561.png)]

现在,单击Project:,然后选择Project Interpreter。 在此屏幕的右侧,显示已安装的包以及所选的项目解释器。 您可以在此屏幕顶部进行更改。 选择适当的解释器(以及项目的环境)后,您可以安装新的包。 为此,您可以在左上角的输入框中搜索。 在下一个屏幕截图中,您可以看到一个搜索numpy包的示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KGncTqP9-1681870288345)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/097f64bb-933a-457e-81f0-7bc21d40f814.png)]

您可以通过单击“安装包”来安装包(默认为最新版本)。 您还可以指定一个具体版本,如上一个屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHuvEPhM-1681870288345)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/af22df55-0645-45c2-8287-03a419e93277.png)]

安装该包之后,我们可以看到我们现在在虚拟环境中已经安装了三个包。 此外,在环境之间进行更改非常容易。 您应该转到运行/调试配置,然后单击 Python 解释器以在环境之间进行切换。 下一个屏幕截图中可以看到此功能:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYtP2xBZ-1681870288346)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/2f32d7bb-194d-4e52-ace5-2fe4d3a7768d.png)]

最后,您可能已经注意到,在第一步中,使用 PyCharm 创建虚拟环境时,可以使用virtualenv以外的其他选项。 PyCharm 使您能够使用 Virtualenv,Pipenv 和 Conda 创建虚拟环境:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ahtSU0sq-1681870288346)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/5b367f4e-9fb2-4981-aaa5-18b578a09c2c.png)]

先前我们介绍了 Virtualenv,以及如何使用此工具为 Python 库创建隔离的 Python 环境。

Pyenv 用于隔离 Python 版本。 例如,您可能想针对 Python 2.6、2.7、3.3、3.4 和 3.5 测试代码,因此您将需要一种在它们之间切换的方法。

Conda 是在 Windows,MacOS 和 Linux 上运行的开源包管理和环境管理系统(提供虚拟环境功能)。 Conda 包含在 Anaconda 和 Miniconda 的所有版本中。

由于读者可能会对与 Anaconda/Miniconda 和 Conda 的合作感兴趣,因此在下一节中将进行快速介绍,但是不必运行本书中包含的代码示例。

Anaconda/Miniconda 发行版和 Conda 包以及环境管理系统

Conda 是一个开源的包管理和环境管理系统(提供虚拟环境功能),可在许多操作系统(例如 Windows,macOS 和 Linux)上运行。 Conda 安装,运行和更新包及其依赖项。 Conda 可以创建,保存,加载和在环境之间切换。

由于 Conda 包含在 Anaconda 和 Miniconda 的所有版本中,因此您应该已经安装了 Anaconda 或 Miniconda。

Anaconda 是可下载,免费,开源的高性能 Python 和 R 发行版。 Anaconda 随附 Conda,Conda 构建,Python 和 100 多个开源科学包及其依赖项。 使用conda install命令,您可以轻松地从 Anaconda 存储库安装用于数据科学的流行开源包。 Miniconda 是 Anaconda 的小型版本,仅包含 Conda,Python,它们依赖的包以及少量其他有用的包。

安装 Anaconda 或 Miniconda 很容易。 为了简单起见,我们将重点放在 Anaconda 上。 要安装 Anaconda,请检查操作系统的 Acadonda 安装程序。 Anaconda 5.2 可以在 Windows,MacOS 和 Linux 上的 Python 3.6 和 Python 2.7 版本中安装:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MIBIvS5i-1681870288346)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/f9a0b962-0080-4ff9-9aba-cd24cd35c8f7.png)]

完成安装后,为了测试安装,请在终端或 Anaconda 提示符中运行以下命令:

代码语言:javascript复制
$ conda list

为了成功安装,将显示已安装包的列表。 如前所述,Anaconda(和 Miniconda)附带了 Conda,它是一个简单的包管理器,类似于 Linux 上的apt-get。 这样,我们可以使用以下命令在终端中安装新包:

代码语言:javascript复制
$ conda install packagename

在这里,packagename是我们要安装的包的实际名称。 可以使用以下命令更新现有包:

代码语言:javascript复制
$ conda update packagename

我们还可以使用以下命令搜索包:

代码语言:javascript复制
$ anaconda search –t conda packagename

这将显示单个用户可以使用的包的完整列表。 然后可以如下安装来自名为username的用户的名为packagename的包:

代码语言:javascript复制
$ conda install -c username packagename

此外,Conda 可用于创建和管理虚拟环境。 例如,创建test环境并安装 NumPy 1.7 版就像输入下一个命令一样简单:

代码语言:javascript复制
$ conda create --name test numpy=1.7

与使用virtualenv的方式类似,可以激活和停用环境。 要在 MacOS 和 Linux 上执行此操作,只需运行以下命令:

代码语言:javascript复制
$ source activate test
 $ python
 ...
 $ source deactivate

在 Windows 上,运行以下命令:

代码语言:javascript复制
$ activate test
 $ python
 ...
 $ deactivate

有关使用 Conda 的最重要信息的单页摘要,请参见 Conda 备忘单 PDF(1 MB)。

最后,应该指出的是,我们可以在 PyCharm IDE 下使用 Conda,就像virtualenv一样创建和管理虚拟环境,因为 PyCharm 可以同时使用这两种工具。

科学计算,数据科学,机器学习,深度学习和计算机视觉的包

到目前为止,我们已经了解了如何从头开始安装 Python,OpenCV 和其他一些包(numpymatplotlib),或使用 Anaconda 发行版,其中包括许多流行的数据科学包。 这样,有关科学计算,数据科学,机器学习和计算机视觉的主要包的一些知识是关键点,因为它们提供了强大的计算工具。 在本书中,将使用许多 Python 包。 并非本节中所有引用的包都将提供,但是为了完整起见,提供了一个完整的列表,以显示 Python 在与本书内容相关的主题中的潜力:

  • NumPy 支持大型多维数组。 NumPy 是计算机视觉中的关键库,因为图像可以表示为多维数组。 将图像表示为 NumPy 数组具有许多优点。
  • OpenCV 是一个开源计算机视觉库。
  • Scikit-Imnage 是图像处理算法的集合。 scikit-image 操纵的图像只是 NumPy 数组。
  • Python 图像库PIL)是一种图像处理库,它提供了强大的图像处理和图形功能。
  • Pillow 是 Alex Clark 及其贡献者友好的 PIL 叉子。 PIL 为您的 Python 解释器添加了图像处理功能。
  • SimpleCV 是计算机视觉的框架,提供了用于处理图像处理的关键功能。
  • Mahotas 是 Python 中用于图像处理和计算机视觉的一组功能。 它最初是为生物图像信息学设计的。 但是,它在其他领域也很有用。 它完全基于 numpy 数组作为其数据类型。
  • Ilastik 是一种用于交互式图像分割,分类和分析的用户友好型简单工具。
  • Scikit-learn)是一种机器学习库,具有各种分类,回归和聚类算法。
  • SciPy 是一个用于科学和技术计算的库。
  • NLTK 是一组用于处理人类语言数据的库和程序。
  • spaCy 是一个用于在 Python 中进行高级自然语言处理的开源软件库。
  • LibROSA 是一个用于音乐和音频处理的库。
  • Pandas 是一个库(基于 NumPy 构建),提供了高级数据计算工具和易于使用的数据结构。
  • Matplotlib 是一个绘图库,可产生多种格式的具有出版物质量的图形。
  • Seaborn 是基于 Matplotlib 构建的图形库。
  • Orange 是面向新手和专家的开源机器学习和数据可视化工具包。
  • PyBrain是一种机器学习库,提供了易于使用的最新机器学习算法。
  • Milk 是一种机器学习工具包,专注于带有多个分类器的监督分类。
  • TensorFlow 是一个开源机器学习和深度学习库。
  • PyTorch 是一个开放源代码的机器学习和深度学习库。
  • Theano 是一个用于快速数学表达式,求值和计算的库,已编译为可在 CPU 和 CPU 上运行。 GPU 架构(深度学习的关键点)。
  • Keras 是一个高级深度学习库,可以在 TensorFlow,CNTK,Theano 或 Microsoft 认知工具包之上运行。
  • Django 是基于 Python 的免费和开源 Web 框架,鼓励快速开发和简洁实用的设计。
  • Flask 是一个基于 Werkzeug 和 Jinja 2 用 Python 编写的微型 Web 框架。

所有这些包都可以根据其主要目的进行组织:

  • 处理图像:NumPy,OpenCV,Scikit-image,PIL 枕头,SimpleCV,Mahotas,ilastik
  • 处理文本:NLTK,spaCy,NumPy,scikit-learn,PyTorch
  • 处理音频:LibROSA
  • 解决机器学习问题:Pandas,Scikit-learn,Orange,PyBrain,牛奶
  • 清楚地查看数据:Matplotlib,Seaborn,scikit-learn,Orange
  • 深度学习:TensorFlow,Pytorch,Theano,Keras
  • 科学计算:SciPy
  • 集成 Web 应用:Django,Flask

可以在这个页面上找到用于 AI 和机器学习的其他 Python 库和包。

Jupyter 笔记本

Jupyter 笔记本是一个开源 Web 应用,允许您通过 Web 浏览器编辑和运行文档。 这些文档称为笔记本文档(或笔记本),包含代码(支持 40 多种编程语言,包括 Python)和富文本元素(段落,方程式,图形)。 Jupyter 笔记本可以在本地计算机上执行,也可以安装在远程服务器上。 您可以从笔记本开始,在线尝试它们,也可以安装 Jupyter 笔记本。

在线尝试 Jupiter 笔记本

首先,转到这里。 您将看到如下内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IzgNKeoc-1681870288346)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/eacecff1-fb20-4832-bd46-f33b05a9a811.png)]

要在线尝试使用 Python 的 Jupyter,请单击 Python 选项,或将此 URL 粘贴到 Web 浏览器中:https://mybinder.org/v2/gh/ipython/ipython-in-depth/master?filepath=binder/Index.ipynb。 页面加载后,即可开始编码/加载笔记本。

安装 Jupyter 笔记本

要安装 Jupyter,您可以按照这个页面上的主要步骤进行操作。 Jupyter 笔记本的安装也可以使用 Anaconda 或使用 Python 的包管理器 PIP 完成。

使用 Anaconda 安装 Jupyter

强烈建议您使用 Anaconda 发行版安装 Python 和 Jupyter,该发行版包括 Python,Jupyter 笔记本和其他用于科学计算和数据科学的常用包。 要使用 Anaconda 安装 Jupyter,请下载 Anaconda 并进行安装。 这样,您已经安装了 Jupyter 笔记本。 要运行笔记本,请在命令提示符(Windows)或终端(macOS/Linux)中运行以下命令:

代码语言:javascript复制
$ jupyter notebook

使用 PIP 安装 Jupyter

您还可以通过运行以下命令,使用 Python 的包管理器 PIP 安装 Jupyter:

代码语言:javascript复制
$ python -m pip install --upgrade pip
$ python -m pip install jupyter

此时,您可以通过运行以下命令来启动笔记本服务器:

代码语言:javascript复制
$ jupyter notebook

上一个命令将向您显示与笔记本服务器有关的一些关键信息,包括 Web 应用的 URL(默认为http://localhost:8888)。 然后它将打开您的默认 Web 浏览器到该 URL。 要启动特定的笔记本,应使用以下命令:

代码语言:javascript复制
$ jupyter notebook notebook.ipynb

这是笔记本的快速介绍。 在下一章中,我们将创建一些笔记本,因此您将有机会使用它们,并充分了解此有用的工具。

OpenCV 和 Python 项目结构

项目结构是组织文件夹中所有文件的方式,以使项目最好地实现目标。 我们将从一个.py脚本(sampleproject.py)开始,该脚本应与其他文件一起使用,以完成有关此脚本的信息-依赖关系,许可证,如何安装或如何对其进行测试。 构建此基本项目的常用方法如下:

代码语言:javascript复制
sampleproject/
│
├── .gitignore
├── sampleproject.py
├── LICENSE
├── README.rst
├── requirements.txt
├── setup.py
└── tests.py

sampleproject.py-如果您的项目只是一个 Python 源文件,则将其放入目录中并为其命名与您的项目相关的名称。

README.rst.md扩展名)用于注册项目的主要属性,至少应包括以下内容:

  • 您的项目做什么
  • 如何安装
  • 用法示例
  • 如何建立开发环境
  • 如何发布 ISSUE
  • 变更记录
  • 许可证和作者信息

可以从以下 GitHub 存储库下载可以使用的模板。 有关更多信息,请参见这里。

LICENSE.md文档包含适用的许可证。 除了源代码本身之外,这可以说是存储库中最重要的部分。 完整的许可证文本和版权声明应存在于此文件中。 如果您要分发代码,最好有一个。 通常, GNU 通用公共许可证GPL)或 MIT 许可证在开源项目中使用。 如果您不确定应将哪个许可证应用于您的项目,可以访问这里。

应将requirements.txt PIP 要求文件放在存储库的根目录中,用于指定项目所需的依赖关系。 可以使用以下方法生成requirements.txt文件:

代码语言:javascript复制
$ pip freeze > requirements.txt

要安装这些要求,可以使用以下命令:

代码语言:javascript复制
$ pip install -r requirements.txt

setup.py文件使您可以创建可以重新分发的包。 该脚本旨在将包安装在最终用户的系统上,而不是像pip install -r < requirements.txt那样准备开发环境。 这是一个关键文件,因为它定义了包的信息(例如版本,包要求和项目描述)。

tests.py脚本包含测试。

.gitignore文件告诉 Git 忽略什么类型的文件,例如 IDE 混乱或本地配置文件。 您可以在这个页面上找到 Python 项目的示例.gitignore文件。

我们的第一个 Python 和 OpenCV 项目

基于上一节中显示的最小项目结构,我们将创建我们的第一个 Python 和 OpenCV 项目。 该项目具有以下结构:

代码语言:javascript复制
helloopencv/
│
├── images/
│
├── .gitignore
├── helloopencv.py
├── LICENSE
├── README.rst
├── requirements.txt
├── setup.py
└── helloopencvtests.py

README.rst.rst扩展名)遵循基本结构,如上一节所示。 Python 和 ReStructuredTextRST)紧密相连-RST 是 docutils 和狮身人面像的格式(实际上是用于记录 python 代码的标准)。 RST 既用于通过文档字符串来记录对象,又用于编写其他文档。 如果您访问 Python 的官方文档,则可以查看每个页面的 RST 源。 对README.rst使用 RST 使其与整个文档设置直接兼容。 实际上,README.rst通常是项目文档的封面。

有一些 RST 编辑器可用来帮助您编写README.rst。 您也可以使用一些在线编辑器。 例如,在线 Sphinx 编辑器是一个不错的选择。

.gitignore文件指定 Git 应该忽略的故意未跟踪的文件。 .gitignore告诉git Git 应该忽略哪些文件(或模式)。 通常用于避免从您的工作目录中提交对其他协作者无用的临时文件,例如 IDE 创建的编译产品和临时文件。 打开这个页面以查看可以包含在 Python 项目中的.gitignore文件。

setup.py(有关详细说明,请参见上一节),它是 Python 文件,通常随库或程序一起提供,也使用 Python 编写。 其目的是正确安装软件。 可以在这个页面上看到此文件的非常完整的示例,其中包含许多注释,可帮助您了解如何适配它来满足您的需求。 此文件由 Python 包装规范PyPa)提出。 一个关键点是与的选项相关,正如我们可以在上述setup.py文件中看到的那样。

如果您的项目很简单,则可以在此处手动指定包目录。 或者,您可以使用find_packages()。 另外,如果您只想分发一个 Python 文件,请改为使用py_modules参数,如下所示,这将期望存在一个名为my_module.py的文件 py_modules=["my_module"]

因此,在我们的情况下,使用py_modules =["helloopencv"]

此外,setup.py允许您轻松安装 Python 包。 通常,编写以下内容就足够了:

代码语言:javascript复制
$ python setup.py install

因此,如果要安装此简单包,可以在helloopencv文件夹中编写上一个命令python setup.py install。 例如,在 Windows 中,运行以下命令:

代码语言:javascript复制
C:...helloopencv>python setup.py install

您应该会看到以下内容:

代码语言:javascript复制
 running install
 ...
 ...
 Installed c:python37libsite-packageshelloopencv-0.1-py3.7.egg
 Processing dependencies for helloopencv==0.1
 ...
 ...
 Finished processing dependencies for helloopencv==0.1

完成后,helloopencv已安装在我们的系统中(与其他任何 Python 包一样)。 您还可以在helloopencv文件夹内使用pip install安装helloopencv。 例如,在 Windows 中,运行以下命令:

代码语言:javascript复制
C:...helloopencv>pip install .

您应该会看到以下内容:

代码语言:javascript复制
 Processing c:...helloopencv
 ...
 ...
 Successfully installed helloopencv-0.1

这表示helloopencv已成功安装。 要使用此包,我们可以编写一个 Python 文件并导入helloopencv包。 另外,我们可以通过直接从 Python 解释器导入来快速使用此包。 按照第二种方法,您可以打开命令提示符,导入包并使用它。 首先,打开命令提示符,然后键入python以运行解释器:

代码语言:javascript复制
C:...helloopencv>python
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:06:47) [MSC v.1914 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

加载解释器后,我们可以导入包:

代码语言:javascript复制
>>> import helloopencv
helloopencv.py is being imported into another module
>>> 

helloopencv.py is being imported into another module输出是来自helloopencv包(特别是来自helloopencv.py文件)的消息,指示已导入此文件。 因此,此消息表明模块已成功导入。 导入后,我们就可以使用它。 例如,我们可以调用show_message方法:

代码语言:javascript复制
>>> helloopencv.show_message()
'this function returns a message'
>>>

我们可以看到,调用此方法的结果是一条消息显示在屏幕上。 此方法是一种简单的方法,它知道所有内容均已正确安装,因为它涉及到安装,导入和使用包中的函数。 此外,我们可以调用helloopencv包中包含的更有用的方法。 例如,您可以调用load_image方法从磁盘加载图像,然后,可以使用show_image方法显示它:

代码语言:javascript复制
>>> image = helloopencv.load_image("C:/.https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/logo.png")
>>> helloopencv.show_image(image)

此处,load_image函数的参数是计算机图像的路径。 在这种情况下,将加载logo.png图像。 调用show_image方法后,应显示图像。 要关闭窗口,必须按下一个键。 然后,您应该能够再次在解释器中编写。 要查看helloopencv包中可用的所有方法,可以使用喜欢的编辑器或 IDE 打开helloopencv.py文件并进行查看。 在此 Python 文件中,您可以看到一些符合我们第一个 Python 项目的方法:

  • show_message():此函数打印this function returns a message消息。
  • load_image():此函数从其路径加载图像。
  • show_image():加载图像后,此函数会显示图像。
  • convert_to_grayscale():此函数在加载图像后将其转换为灰度。
  • write_image_to_disk():此函数将图像保存在磁盘上。

所有这些方法都执行简单且基本的操作。 它们中的大多数都使用 OpenCV 库,该库在此文件(import cv2)的开头导入。 不必担心此文件中包含的 Python 代码,因为仅执行基本操作和对 OpenCV 库的调用。

您无需安装包即可执行helloopencv.py脚本。 要执行此文件,应在打开命令提示符后运行python helloopencv.py命令:

代码语言:javascript复制
 C:...helloopencv>python helloopencv.py
 helloopencv.py is being run directly

执行完该文件后,将显示helloopencv.py is being run directly消息,这意味着该文件将直接执行,而不是从其他模块或包(或 Python 解释器)导入。 您还可以看到已加载并显示图像。 您可以按任意键继续执行。 再次显示徽标的灰度版本,应再次按下任何键以结束执行。 将灰度图像保存到磁盘后,执行结束。

最后,helloopencvtests.py文件可用于单元测试。 测试应用已成为任何合格开发人员的标准技能。 Python 社区支持测试,Python 标准库具有良好的内置工具来支持测试。

在 Python 生态系统中,有很多测试工具。 [用于测试的两个最常见的包是`nose](https://pypi.org/project/nose/)和[`pytest`](https://pypi.org/project/pytest/)。 在第一个 Python 项目中,我们将使用`pytest`进行单元测试。

要执行测试,请在打开命令提示符后运行py.test -s -v helloopencvtests.py命令:

代码语言:javascript复制
C:...helloopencv>py.test -s -v helloopencvtests.py
============================= test session starts =============================
 platform win32 -- Python 3.7.0, pytest-3.8.0, py-1.6.0, pluggy-0.7.1 -- c:python37python.exe
 cachedir: .pytest_cache
 collected 4 items
 helloopencvtests.py::test_show_message testing show_message
 PASSED
 helloopencvtests.py::test_load_image testing load_image
 PASSED
 helloopencvtests.py::test_write_image_to_disk testing
 write_image_to_disk
 PASSED
 helloopencvtests.py::test_convert_to_grayscale testing    test_convert_to_grayscale
 PASSED
========================== 4 passed in 0.57 seconds      ===========================

执行测试后,您可以看到执行了四个测试。 PASSED消息表示测试已成功执行。 这是 Python 单元测试的快速介绍。 不过,完整的pytest文档可在这个页面中找到。

总结

在第一章中,我们介绍了设置 OpenCV 和 Python 以构建您的计算机视觉项目的主要步骤。 在本章开始时,我们快速浏览了本书的主要概念-人工智能,机器学习,神经网络和深度学习。 然后,我们探索了 OpenCV 库,包括该库的历史及其主要模块。 由于 OpenCV 和其他包可以在许多操作系统中以不同的方式安装,因此我们介绍了主要方法。

具体来说,我们看到了如何在全局或虚拟环境中安装 Python,OpenCV 和其他包。 在安装包时,我们介绍了 Anaconda/Miniconda 和 Conda,因为我们还可以创建和管理虚拟环境。 此外,Anaconda/Miniconda 附带了许多开源科学包,包括 SciPy 和 NumPy。

我们探索了用于科学计算,数据科学,机器学习和计算机视觉的主要包,因为它们提供了强大的计算工具。 然后,我们讨论了 Python 特定的 IDE,包括 PyCharm(实际上是 Python IDE 环境)。 PyCharm(和其他 IDE)可以帮助我们以非常直观的方式创建虚拟环境。 我们还研究了 Jupyter 笔记本,因为它可能是本书读者的一个很好的工具。 在下一章中,将创建更多的 Jupyter 笔记本,以使您更好地了解此有用的工具。 最后,我们探索了 OpenCV 和 Python 项目结构,涵盖了应包含的主要文件。 然后,我们构建了第一个 Python 和 OpenCV 示例项目,在其中我们看到了构建,运行和测试该项目的命令。

在下一章中,您将开始熟悉 OpenCV 库,从而开始编写第一个脚本。 您将看到开始对计算机视觉项目进行编码的一些基本概念(例如,了解主要图像概念,OpenCV 中的坐标系以及 OpenCV 中的访问和操纵像素)。

问题

  1. 什么是虚拟环境?
  2. PIP,Virtualenv,Pipenv,Anaconda 和 Conda 之间有什么联系?
  3. 什么是 Jupyter 笔记本?
  4. 在 Python 中使用计算机视觉的主要包是什么?
  5. pip install -r requirements.txt有什么作用?
  6. 什么是 IDE?为什么在开发项目时使用 IDE?
  7. OpenCV 以什么协议发布?

进一步阅读

以下参考资料将帮助您更深入地了解本章中介绍的概念:

  • Python 机器学习:
    • 《Python 机器学习》,作者:塞巴斯蒂安·拉施卡(Sebastian Raschka)
  • Python 深度学习:
    • 《Python 深度学习项目》,作者:Rahul Kumar,Matthew Lamons

查看这些参考资料(主要是书籍),以获取有关概念的更多信息,这些概念将在本书的后续章节中介绍。 保持此清单方便; 这将非常有帮助:

  • 《使用 Python 和 OpenCV 的计算机视觉》
  • 《OpenCV:使用 Python 的计算机视觉项目》
  • 《面向开发人员的增强现实》
  • 《使用 Python 和 OpenCV 的深度学习》
  • 《使用 Keras 的深度学习》
  • 《TensorFlow 入门》
  • 《精通 Flask Web 开发:第二版》

二、OpenCV 中的图像基础

图像是计算机视觉项目中的关键组成部分,因为在许多情况下,它们提供了要使用的输入。 因此,了解主要的图像概念是开始编写计算机视觉项目所需的基本知识。 此外,还将介绍一些 OpenCV 库的特性,例如坐标系或 BGR 顺序(而不是 RGB)。

在本章中,您将学习如何开始编写第一个脚本,这将向您介绍 OpenCV 库。 在本章的最后,您将有足够的知识来开始使用 OpenCV 和 Python 编写您的第一个计算机视觉项目。

在本章中,我们将介绍以下主题:

  • 图像基础的理论介绍
  • 像素,颜色,通道,图像和色彩空间的概念
  • OpenCV 中的坐标系
  • 在 OpenCV 中访问和操作不同颜色空间中的像素(获取和设置)
  • OpenCV 中的 BGR 顺序(而不是 RGB)

技术要求

本章的技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Jupyter 笔记本
  • Git 客户端

有关如何安装这些要求的更多详细信息,请参见第 1 章,“设置 OpenCV”。 可以在这个页面上访问用于通过 Python 精通 OpenCV 的 GitHub 存储库,其中包含从第一章到最后一章都需要完成本书的所有支持项目文件。

图像基础的理论介绍

本部分的主要目的是为图像基础知识提供理论上的介绍-这些将在下一部分中详细说明。 首先,将快速介绍一下在计算机视觉项目中开发图像处理集时遇到的一些困难的重要性,然后再介绍一些与图像有关的简单公式。

图像处理中的主要问题

引入的第一个概念与图像有关,可以将其视为 3D 世界的二维2D)视图。 数字图像是 2D 图像的数字表示形式,通常是二进制的有限数字值集,称为像素(像素的概念将在“像素,颜色,通道,图像和色彩空间概念”部分中详细说明)。 因此,计算机视觉的目标是将 2D 数据转换为以下内容:

  • 新的表示形式(例如,新的图像)
  • 决策(例如,执行具体任务)
  • 新结果(例如,图像的正确分类)
  • 一些有用的信息提取(例如,对象检测)

在处理图像处理技术时,计算机视觉可能会解决常见的问题(或难题):

  • 含糊不清的图像,因为它们会受到透视的影响,这可能会导致图像的视觉外观发生变化。 例如,从不同角度观看的同一对象可能会产生不同的图像。
  • 通常受许多因素影响的图像,例如照明,天气,反射和运动。
  • 图像中的物体也可能被其他物体遮挡,从而难以检测或分类被遮挡的物体。 根据遮挡的级别,所需的任务(例如,将图像分类为一些预定义的类别)可能确实具有挑战性。

为了将所有这些困难放在一起,假设您想开发一个面部检测系统。 该系统应足够坚固以应对照明或天气条件的变化。 另外,该系统应该处理头部的运动,甚至可以处理用户可以离相机更远或更近的事实。 它应该能够在每个轴(偏航,横摇和俯仰)上旋转一定程度来检测用户的头部。 例如,当头部靠近额头时,许多面部检测算法都具有良好的表现。 但是,如果面部不是正面的话,他们将无法检测到(例如,个人资料中的面部)。 此外,即使用户戴着眼镜或太阳镜,也可能希望检测到脸部,这会在眼睛区域产生遮挡。 在开发计算机视觉项目时,必须考虑所有这些因素。 一个很好的近似值是通过合并一些困难来使用许多测试图像来验证您的算法。 您还可以根据要轻松检测算法弱点的主要困难对测试图像进​​行分类。

图像处理步骤

图像处理包括以下三个步骤:

  1. 获取要使用的图像。 此过程通常涉及一些函数,以便您可以从不同的来源(摄像机,视频流,磁盘,在线资源)读取图像。
  2. 通过应用图像处理技术来处理图像以实现所需的功能(例如,检测图像中的猫)。
  3. 显示处理步骤的结果(例如,在图像中绘制边框,然后将其保存到磁盘)。

此外,第二步可以分为三个处理级别:

  • 低级流程
  • 中级流程
  • 高级流程

低级过程通常将图像作为输入,然后输出另一个图像。 可以在此步骤中应用的示例过程包括:

  • 噪音消除
  • 图像锐化
  • 光照归一化
  • 透视校正

结合面部检测示例,输出图像可以是光照归一化图像以处理由太阳反射引起的变化。

中级过程提取预处理后的图像,以输出该图像的某种表示形式。 将其视为数字的集合(例如,包含 100 个数字的向量),该集合汇总了要用于进一步处理的图像的主要信息。 关于面部检测示例,输出可以是由点(x, y),包含检测到的面部的宽度和高度定义的矩形。

高级过程提取此数字向量(通常称为属性)并输出最终结果。 例如,输入可以是检测到的面部,输出可以是以下内容:

  • 人脸识别
  • 情感识别
  • 睡意和注意力分散
  • 面部远程心率测量

图像创建

图像可以描述为 2D 函数f(x, y),其中(x, y)是空间坐标和f的值。 在任何时候,(x, y)与图像的亮度或灰度级成正比。 另外,当(x, y)f的亮度值都是有限离散量时,该图像被称为数字图像。 因此,f(x, y)采用以下值:

  • x ∈ [0, h-1],其中h是图像的高度
  • y ∈ [0, w-1],其中w是图像的宽度
  • f(x, y) ∈ [0, L-1],其中L = 256(对于 8 位图像)

彩色图像可以用相同的方式表示,但是我们需要定义三个函数分别表示红色,绿色和蓝色值。 这三个函数的每一个都遵循与为灰度图像定义的f(x, y)函数相同的公式。 我们将针对三种秘籍(对于彩色图像)将这三个函数的子索引 R,G 和 B 表示为fR(x, y)fG(x, y)fB(x, y)

黑白图像遵循相同的近似方式,只需要一个函数即可表示图像。 但是,一个关键点是f(x, y)只能取两个值。 通常,这些值为0(黑色)和255(白色)。

这三种类型的图像通常用于计算机视觉,因此请记住它们的格式。

以下屏幕截图显示了三种类型的图像(彩色图像,灰度图像和黑白图像):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nip1Q4wi-1681870288347)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c2208747-9c1e-4f0d-b4e5-2fcc7d63540f.png)]

请记住,由于f(x, y)值是有限的离散量,因此可以将数字图像视为真实场景的近似值。 此外,灰度图像和黑白图像每点只有一个样本(仅需要一个函数),而彩色图像每点只有三个样本(需要三项函数,对应于图像的红色,绿色和蓝色分量) 。

像素,颜色,通道,图像和色彩空间的概念

有几种不同的颜色模型,但最常见的一种是红色,绿色,蓝色RGB)模型,这些模型将用于解释有关数字图像的一些关键概念。

在第 5 章,“图像处理技术”中,将详细说明主要颜色模型(也称为颜色空间)。

RGB 模型是一种加色模型,其中将原色(R, G, B)*混合在一起以再现各种颜色。 如前所述,在 RGB 模型中,原色是红色,绿色和蓝色。

每个原色(R, G, B),通常称为通道,通常表示为[0, 255]范围内的整数。 因此,每个通道总共产生 256 个离散值,这些离散值对应于用于表示颜色通道值的总位数(2^8 = 256)。 此外,由于存在三个不同的通道,因此称为 24 位色深

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WrBMjC2i-1681870288347)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3f3c4033-6d4c-4e92-a8b6-c813b2c6f2c6.png)]

在上图中,您可以看到 RGB 颜色空间的加色属性:

  • 将红色添加到绿色将获得黄色
  • 将红色添加到蓝色会产生洋红色
  • 将绿色添加到蓝色会生成青色
  • 将所有三种原色相加会产生白色

如前所述,结合 RGB 颜色模型,特定颜色由红色,绿色和蓝色值表示,将像素值表示为 RGB 三元组(R, G, B)。 如下图所示,是一个图形软件中典型的 RGB 颜色选择器。 可以想象,每个滑块的范围从0255

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZiRbYJsO-1681870288347)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/feb71c65-7bc5-49fd-baec-43a9779debe9.png)]

您还可以看到,将纯红色添加到纯蓝色会产生完美的洋红色。 您可以在这个页面上使用 RGB 颜色图表。

分辨率为800×1200的图像是具有 800 列和 1200 行的网格,包含800×1200 = 960,000像素。 应该注意的是,知道图像中有多少像素并不表示其物理尺寸(一个像素不等于一毫米)。 取而代之的是,一个像素的大小(因此图像的大小)将取决于已设置的每英寸像素PPI) 该图像。 一般的经验法则是 PPI 在[200 - 400]范围内。

计算 PPI 的基本公式如下:

代码语言:javascript复制
PPI = 图像的宽度(像素)/ 宽度(英寸)
PPI = 图像的高度(像素)/ 高度(英寸)

因此,例如,如果要打印4×6英寸图像,并且图像为800×1200 ,则 PPI 为 200。

现在,我们将研究文件扩展名。

文件扩展名

尽管我们将在 OpenCV 中处理的图像可以看作是 RGB 三元组的矩形数组(在 RGB 图像的情况下),但不一定必须以该格式创建,存储或传输它们。 从这个意义上讲,某些文件格式(例如 GIF,PNG,位图或 JPEG)使用不同形式的压缩(无损或有损)来更有效地表示图像。

这样,出于完整性考虑,此处简要介绍了这些图像文件,特别着重于 OpenCV 支持的文件格式。 OpenCV 支持以下文件格式(带有关联的文件扩展名):

  • Windows 位图*.bmp*.dib
  • JPEG 文件*.jpeg*.jpg*.jpe
  • JPEG 2000 文件*.jp2
  • 便携式网络图形*.png
  • 便携式图像格式*.pbm*.pgm*.ppm
  • TI​​FF 文件*.tiff*.tif

位图图像文件BMP)或设备独立位图DIB)文件格式是用于存储的光栅图像文件格式。 位图数字图像。 BMP 文件格式可以处理各种颜色深度的 2D 数字图像,还可以处理数据压缩,alpha 通道或颜色配置文件。

联合图像专家组JPEG)是一种光栅图像文件格式,用于存储已压缩以在小文件中存储大量信息的图像。

JPEG 2000 是图像压缩标准和编码系统,它使用基于小波的压缩技术来提供高级别的可伸缩性和可访问性。 以此方式,JPEG 2000 压缩的图像比常规 JPEG 少。

便携式网络图形(PNG)是一种压缩的光栅图形文件格式,于 1994 年引入,作为图形交换格式GIF)的改进替代。

便携式像素图格式PPM),便携式位图格式PBM)和便携式灰度图格式PGM)指定交换图形文件的规则。 几个应用将这些文件格式统称为可移植任意图格式PNM)。 这些文件是保存图像数据的便捷方法。 此外,它们易于阅读。 从这个意义上讲,PPM,PBM 和 PGM 格式都设计得尽可能简单。

标记图像文件格式TI​​FF)是一种可调整的文件格式,用于处理单个文件中的图像和数据。

将无损和有损类型的压缩算法应用于图像,从而导致图像小于未压缩的图像。 一方面,在无损压缩算法中,生成的图像与原始图像等效,这意味着在反转压缩过程之后,生成的图像与原始图像等效(等于)。 另一方面,在有损压缩算法中,生成的图像与原始图像不相等,这意味着图像中的某些细节会丢失。 从这个意义上讲,在许多有损压缩算法中,可以调整压缩级别。

OpenCV 中的坐标系

为了向您展示 OpenCV 中的坐标系以及如何访问单个像素,我们将向您展示 OpenCV 徽标的低分辨率图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDuPz1zY-1681870288348)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/05fc4aae-b9fd-469d-ad2a-355e805e5c07.png)]

该徽标的尺寸为20 x 18像素,即,该图像具有 360 像素。 因此,我们可以在每个轴上添加像素数,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jt6XssqX-1681870288348)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/104f4697-cba0-4e4c-9191-13137ebd6039.png)]

现在,我们来看一下(x, y)形式的像素索引。 请注意,像素是零索引的,这意味着左上角位于(0, 0),而不是(1, 1)。 看一下下图,该图索引了三个单独的像素。 如您所见,图像的左上角是原点的坐标。 此外,坐标随着其下降而变大:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qHykLw9H-1681870288348)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/d3c85bc1-1983-4dee-9c18-613cd70729eb.png)]

可以使用与在 Python 中引用数组的单个元素相同的方式从图像中提取单个像素的信息。 在下一节中,我们将看到如何做到这一点。

在 OpenCV 中访问和操作像素

在本节中,您将学习如何使用 OpenCV 访问和读取像素值以及如何对其进行修改。 此外,您将学习如何访问图像属性。 如果要一次处理多个像素,则需要创建图像的兴趣区域ROI)。 在本节中,您将学习如何执行此操作。 最后,您将学习如何拆分和合并图像。

请记住,在 Python 中,图像表示为 NumPy 数组。 因此,这些示例中包含的大多数操作都与 NumPy 有关,因此需要对 NumPy 包有很好的了解,才能理解这些示例中包含的代码并使用 OpenCV 编写优化的代码。

在 OpenCV 中访问和操纵 BGR 图像的像素

现在,我们将了解如何在 OpenCV 中处理 BGR 图像。 OpenCV 加载彩色图像,因此蓝色通道是第一个,绿色通道是第二个,红色通道是第三个。 请参阅“使用灰度图像访问和操作 OpenCV 中的像素”,以全面了解此概念。

首先,使用cv2.imread()函数读取要使用的图像。 该图像应位于工作目录中,或者应提供该图像的完整路径。 在这种情况下,我们将读取logo.png图像并将其存储在img变量中:

代码语言:javascript复制
# The function cv2.imread() is used to read an image from the the working directory
# Alternatively, you should provide a full path of the image: 
# Load OpenCV logo image (in this case from the working directoy):
img = cv2.imread('logo.png')

将图像加载到img后,我们将可以访问图像的某些属性。 我们将从加载的图像中提取的第一个属性是shape,它将告诉我们行,列和通道的数量(如果图像是彩色的)。 我们会将这些信息存储在dimensions变量中,以备将来使用:

代码语言:javascript复制
# To get the dimensions of the image use img.shape
# img.shape returns a tuple of number of rows, columns and channels (if a colour image)
# If image is grayscale, img.shape returns a tuple of number of rows and columns.
# So,it can be used to check if loaded image is grayscale or color image.
# Get the shape of the image:
dimensions = img.shape

另一个属性是图像的大小(img.size等于高度×宽度×通道的乘积):

代码语言:javascript复制
# Total number of elements is obtained by img.size:
total_number_of_elements= img.size

属性图像数据类型是通过img.dtype获得的。 在这种情况下,图像数据类型为uint8(无符号字符),因为值在[0 - 255]范围内:

代码语言:javascript复制
# Image datatype is obtained by img.dtype.
# img.dtype is very important because a large number of errors is caused by invalid datatype.
# Get the image datatype:
image_dtype = img.dtype

要显示图像,我们将使用cv2.imshow()函数在窗口中显示图像。 窗口自动适合图像尺寸。 此函数的第一个参数是窗口名称,第二个参数是要显示的图像。 在这种情况下,由于加载的图像已存储在img变量中,因此我们将使用此变量作为第二个参数:

代码语言:javascript复制
# The function cv2.imshow() is used to display an image in a window
# The first argument of this function is the window name
# The second argument of this function is the image to be shown.
# Each created window should have different window names.
# Show original image:
cv2.imshow("original image", img)

显示图像后,cv2.waitKey()函数(一种键盘绑定函数)将为任何键盘事件等待指定的毫秒数。 参数是时间(以毫秒为单位)。 如果此时按任何键,程序将继续。 如果毫秒数是0cv2.waitKey(0)),它将无限期地等待击键。 因此,此函数将使我们能够看到显示的窗口中等待按键输入:

代码语言:javascript复制
# The function cv2.waitKey(), which is a keyboard binding function, waits for any keyboard event.
# This function waits the value indicated by the argument (in milliseconds). 
# If any keyboard event is produced in this period of time, the program continues its execution
# If the value of the argument is 0, the program waits indefinitely until a keyboard event is produced:
cv2.waitKey(0)

要访问(读取)像素值,我们需要将所需像素的行和列提供给img变量,该变量包含加载的图像。 例如,要获取像素值[x=40y=6),我们将使用以下代码:

代码语言:javascript复制
# A pixel value can be accessed by row and column coordinates.
# In case of BGR image, it returns an array of (Blue, Green, Red) values.
# Get the value of the pixel (x=40, y=6):
(b, g, r) = img[6, 40]

我们已将三个像素值加载到三个变量(b,g,r)中。 您可以在此处看到 OpenCV 对彩色图像使用 BGR 格式。 此外,我们一次只能访问一个通道。 在这种情况下,我们将使用行,列和所需通道的索引进行索引。 例如,要仅获取像素的蓝色值(x=40y=6),我们将使用以下代码:

代码语言:javascript复制
# We can only  access one channel at a time.
# In this case, we will use row, column and the index of the desired channel for indexing.
# Get only blue value of the pixel (x=40, y=6):
b = img[6, 40, 0]

像素值也可以以相同的方式修改。 请记住,它是(b, g, r)格式。 例如,要将像素(x=40y=6)设置为红色,请执行以下操作:

代码语言:javascript复制
# The pixel values can be also modified in the same way - (b, g, r) format:
img[6, 40] = (0, 0, 255)

有时,您将不得不处理某个区域而不是一个像素。 在这种情况下,应提供值的范围而不是各个值。 例如,要到达图像的左上角,请输入以下内容:

代码语言:javascript复制
# In this case, we get the top left corner of the image:
top_left_corner = img[0:50, 0:50]

top_left_corner变量是另一张图像(小于img),但是我们可以用相同的方式来播放它。

在 OpenCV 中访问和操作灰度图像的像素

灰度图像只有一个通道。 因此,在处理这些图像时会引入一些差异。 我们将在这里重点介绍这些差异。

同样,我们将使用cv2.imread()函数读取图像。 在这种情况下,需要第二个参数,因为我们要以灰度加载图像。 第二个参数是一个标志,指定应读取图像的方式。 加载灰度图像所需的值为cv2.IMREAD_GRAYSCALE

代码语言:javascript复制
# The function cv2.imshow() is used to display an image in a window
# The first argument of this function is the window name
# The second argument of this function is the image to be shown.
# In this case, the second argument is needed because we want to load the image in grayscale.
# Second argument is a flag specifying the way the image should be read.
# Value needed for loading an image in grayscale: 'cv2.IMREAD_GRAYSCALE'.
# load OpenCV logo image:
gray_img = cv2.imread('logo.png', cv2.IMREAD_GRAYSCALE)

在这种情况下,我们将图像存储在gray_img变量中。 如果获得图像的尺寸(使用gray_img.shape),则将仅获得两个值,即行和列。 在灰度图像中,不提供通道信息:

代码语言:javascript复制
# To get the dimensions of the image use img.shape
# If color image, img.shape returns returns a tuple of number of rows, columns and channels
# If grayscale, returns a tuple of number of rows and columns.
# So, it can be used to check if the loaded image is grayscale or color image.
# Get the shape of the image (in this case only two components!):
dimensions = gray_img.shape

img.shape将以元组的形式返回图像的尺寸,例如(99, 82)

像素值可以通过行和列坐标进行访问。 在灰度图像中,仅获得一个值(通常称为像素的强度)。 例如,如果要获取像素强度[x=40y=6),则可以使用以下代码:

代码语言:javascript复制
# You can access a pixel value by row and column coordinates.
# For BGR image, it returns an array of (Blue, Green, Red) values.
# Get the value of the pixel (x=40, y=6):
i = gray_img[6, 40]

图像的像素值也可以用相同的方式修改。 例如,如果我们要将像素(x=40y=6)的值更改为黑色(强度等于0),则可以使用以下代码:

代码语言:javascript复制
# You can modify the pixel values of the image in the same way.
# Set the pixel to black:
gray_img[6, 40] = 0

OpenCV 中的 BGR 顺序

我们已经提到过 OpenCV 使用 BGR 颜色格式而不是 RGB 颜色格式。 可以在下图中看到,您可以在其中看到三个通道的顺序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHjvXsr4-1681870288348)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/ef884cf4-46f9-4db1-b473-ed80ecbc2593.png)]

下图可以看到 BGR 图像的像素结构。 特别是,为了说明目的,我们详细介绍了如何访问像素y = n, x = 1):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8TJeqivH-1681870288349)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a4bb6560-4c40-4de2-b88f-8e08b69cf7ab.png)]

OpenCV 的最初开发人员选择了 BGR 色彩格式(而不是 RGB 格式),因为当时 BGR 色彩格式在软件提供商和相机制造商中非常受欢迎。 例如,在 Windows 中,使用 COLORREF 指定颜色值时,他们使用 BGR 格式0x00bbggrr。 总而言之,选择 BGR 是出于历史原因。

此外,其他 Python 包使用 RGB 颜色格式。 因此,我们需要知道如何将图像从一种格式转换为另一种格式。 例如,Matplotlib 使用 RGB 颜色格式。 Matplotlib 是最受欢迎的 2D Python 绘图库,可为您提供多种绘图方法。 您可以与绘制的图像进行交互(例如,放大并保存图像)。 Matplotlib 可以在 Python 脚本或 Jupyter 笔记本中使用。 您可以查看 Matplotlib 文档以获取更多详细信息。

因此,对于您的项目而言,一个不错的选择是使用 Matplotlib 包而不是 OpenCV 提供的函数来显示图像。 现在,我们将看到如何处理两个库中的不同颜色格式。

首先,我们使用cv2.imread()函数加载图像:

代码语言:javascript复制
# Load image using cv2.imread:
img_OpenCV = cv2.imread('logo.png')

图像存储在img_OpenCV变量中,因为cv2.imread()函数以 BGR 顺序加载图像。 然后,我们使用cv2.split()函数将加载的图像分为三个通道(b, g, r)。 该函数的参数是我们要分割的图像:

代码语言:javascript复制
# Split the loaded image into its three channels (b, g, r):
b, g, r = cv2.split(img_OpenCV)

下一步是再次合并通道(以基于通道提供的信息构建新图像),但顺序不同。 我们更改br通道的顺序以遵循 RGB 格式,即 Matplotlib 需要的格式:

代码语言:javascript复制
# Merge again the three channels but in the RGB format:
img_matplotlib = cv2.merge([r, g, b])

此时,我们有两个图像(img_OpenCVimg_matplotlib),将使用 OpenCV 和 Matplotlib 对其进行绘制,以便可以看到结果。 首先,我们将使用 Matplotlib 显示这两个图像。

为了在同一窗口中显示带有 Matplotlib 的两个图像,我们将使用subplot,它将在同一窗口中放置多个图像。 您可以在subplot中使用三个参数,例如subplot(m,n,p)。 在这种情况下,subplot处理xn网格中的图,其中m建立行数,n建立列数,p确定要在网格中放置绘图的位置。 为了显示 Matplotlib 的图像,我们将使用imshow

在这种情况下,由于我们水平显示两个图像m = 1n = 2。 我们将在第一个子图img_OpenCV中使用p = 1,在第二个子图img_matplotlib中使用p = 2

代码语言:javascript复制
# Show both images (img_OpenCV and img_matplotlib) using matplotlib
# This will show the image in wrong color:
plt.subplot(121)
plt.imshow(img_OpenCV)
# This will show the image in true color:
plt.subplot(122)
plt.imshow(img_matplotlib)
plt.show()

因此,您将获得的输出应与下图所示的输出非常相似:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xs1Q5kUp-1681870288349)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/fda6dbe2-2417-49b0-872b-15fa220ff69b.png)]

如您所见,第一个子图以错误的颜色(BGR 顺序)显示图像,而第二个子图以真实的颜色(RGB 顺序)显示图像。 以相同的方式,我们将使用cv2.imshow()显示两个图像:

代码语言:javascript复制
# Show both images (img_OpenCV and img_matplotlib) using cv2.imshow()
# This will show the image in true color:
cv2.imshow('bgr image', img_OpenCV)
# This will show the image in wrong color:
cv2.imshow('rgb image', img_matplotlib)
cv2.waitKey(0)
cv2.destroyAllWindows()

以下屏幕截图显示了执行前面的代码将获得的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iLn4OO5Y-1681870288349)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c5f167a3-cfec-46fb-b9b7-01c2e71ef485.png)]

正如预期的那样,屏幕截图以真实的颜色显示图像,而第二个图以错误的颜色显示图像。

此外,如果要在同一窗口中显示两个图像,则可以构建一个完整的图像,其中包含两个图像,并将它们水平连接。 为此,我们将使用 NumPy 的concatenate()方法。 此方法的参数是要连接的两个图像和轴。 在这种情况下,axis = 1(水平堆叠):

代码语言:javascript复制
# To stack horizontally (img_OpenCV to the left of img_matplotlib):
img_concats = np.concatenate((img_OpenCV, img_matplotlib), axis=1)
# Now, we show the concatenated image:
cv2.imshow('bgr image and rgb image', img_concats)
cv2.waitKey(0)
cv2.destroyAllWindows()

查看以下屏幕截图以查看连接的图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mU02y25-1681870288349)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/94fc9f36-4076-4ae1-b541-dd559fa5c63f.png)]

要考虑的一个因素是cv2.split()是一项耗时的操作。 根据您的需求,考虑使用 NumPy 索引。 例如,如果要获取图像的一个通道,而不是使用cv2.split()来获取所需的通道,则可以使用 NumPy 索引。 请参阅以下示例,以使用 NumPy 索引获取通道:

代码语言:javascript复制
# Using numpy capabilities to get the channels and to build the RGB image
# Get the three channels (instead of using cv2.split):
B = img_OpenCV[:, :, 0]
G = img_OpenCV[:, :, 1]
R = img_OpenCV[:, :, 2]

另一个考虑因素是,您可以使用 NumPy 在单个指令中将图像从 BGR 转换为 RGB:

代码语言:javascript复制
# Transform the image BGR to RGB using Numpy capabilities:
img_matplotlib = img_OpenCV[:, :, ::-1]

为了总结本章的所有内容,我们创建了两个 Jupyter 笔记本。 在这些笔记本中,您可以使用到目前为止介绍的所有概念:

  • Getting-And-Setting-BGR.ipynb
  • Getting-And-Setting-GrayScale.ipynb

利用福利笔记本(以及本章中包括的所有信息)的优势,无需其他信息即可使用它们。 因此,继续尝试一下。 请记住(请参阅第 1 章,“设置 OpenCV”),要运行笔记本,您需要在终端机(Mac/Linux)或命令提示符(Windows)上运行以下命令:

代码语言:javascript复制
$ jupyter notebook

此命令将打印与笔记本服务器有关的信息,包括 Web 应用的 URL(默认情况下,此 URL 为http://localhost:8888)。 此外,此命令还将打开指向该 URL 的 Web 浏览器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5IndCQ2I-1681870288349)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/bab8bebf-d6cd-4a62-a459-652a1061e8b7.png)]

此时,您可以通过单击“上载”按钮来上载Getting-And-Setting-BGR.ipynbGetting-And-Setting-GrayScale.ipynb文件(请参见上一个屏幕截图)。 这些文件使用logo.png图像。 因此,您应该以相同的方式上传此图像。 加载这三个文件之后,您应该看到已加载以下文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GTsjc3Fy-1681870288350)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/2997dcb4-210c-498f-b327-e0305875d606.png)]

此时,您可以通过单击打开这些笔记本。 您应该看到笔记本的内容,如以下屏幕快照所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJp5xg8W-1681870288350)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/a852680f-1d98-4b29-b11b-d0d6b59e02fb.png)]

最后,您可以开始执行加载的笔记本文档。 您可以通过按Shift Enter来逐步执行笔记本(一次一个单元)。 另外,您可以通过单击“单元格 | 步骤”一步来执行整个笔记本。 运行全部菜单。 此外,您还可以通过单击“内核 | 重新启动内核”菜单(计算引擎)。

有关编辑笔记本的更多信息,请查看这里,它也是笔记本!

总结

在本章中,我们研究了与图像有关的关键概念。 图像构成了构建计算机视觉项目所必需的丰富信息。 OpenCV 使用 BGR 颜色格式而不是 RGB,但是某些 Python 包(例如 Matplotlib)使用后者。 因此,我们介绍了如何将图像从一种颜色格式转换为另一种颜色格式。

此外,我们总结了使用图像的主要函数和选项:

  • 访问图像属性
  • 一些 OpenCV 函数,例如cv2.imread()cv2.split()cv2.merge()cv2.imshow()cv2.waitKey()cv2.destroyAllWindows()
  • 如何在 BGR 和灰度图像中获取和设置图像像素

最后,我们包括了两个笔记本,可让您使用所有这些概念。 请记住,一旦加载了笔记本,就可以通过按Shift Enter来逐步运行它,或者单击“单元格” |“一步”来运行笔记本。 运行全部菜单。

在下一章中,您将学习如何处理文件和图像,这是构建计算机视觉应用所必需的。

问题

  1. 主要的图像处理步骤是什么?
  2. 三种处理级别是什么?
  3. 灰度图像和黑白图像有什么区别?
  4. 什么是像素?
  5. 什么是图像分辨率?
  6. 您使用哪些 OpenCV 函数执行以下操作?
    • 加载(读取)图像
    • 显示图像
    • 等待按键
    • 拆分通道
    • 合并通道
  7. 您使用什么命令来运行 Jupyter 笔记本?
  8. 以下三元组会得到什么颜色?
    • B = 0G = 255R = 255
    • B = 255G = 255R = 0
    • B = 255G = 0R = 255
    • B = 255G = 255R = 255
  9. 假设您已在img中加载了图像。 如何检查img是彩色还是灰度?

进一步阅读

以下参考文献将帮助您更深入地了解本章中介绍的概念:

  • 有关 Git 的更多信息,请看这本书:

《精通 Git》,作者 JakubNarębski

  • 有关 Jupyter 笔记本的更多信息:

《Jupyter 笔记本:第一部分》,作者 Dan Toomey

三、处理文件和图像

在任何类型的项目中,处理文件和图像都是关键。 从这个意义上讲,许多项目都应将文件作为数据输入形式来使用。 此外,项目可以在完成任何类型的处理后生成一些数据,这些数据可以以文件或图像的形式输出。 在计算机视觉中,由于这些类型的项目的固有特征(例如,要处理的图像和由机器学习算法生成的模型),这种信息流(输入-处理-输出)具有特殊的意义。

在本章中,我们将看到如何处理文件和图像。 您将学习如何处理构建计算机视觉应用所必需的文件和图像。

更具体地说,我们将涵盖以下主题:

  • 有关处理文件和图像的理论介绍
  • 读/写图像
  • 读取相机帧和视频文件
  • 写入视频文件
  • 玩转视频捕获属性

技术要求

本章的技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib Python 包
  • Git 客户端

可以在这个页面上访问使用 Python 精通 OpenCV 的 GitHub 存储库。

处理文件和图像的简介

在深入处理文件和图像之前,我们将为您提供本章将要介绍的内容的概述。 下图概述了此概述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MuMM1boA-1681870288356)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/5f416e00-1a15-4ad1-bbae-6c73a70d0fa0.png)]

在上图中,您可以看到计算机视觉项目(例如 OpenCV 和 Python 项目)应处理一些输入文件(例如文件图片)。 另外,经过一些处理后,项目可以输出一些文件(例如图像文件)。 因此,在本章中,我们将了解如何满足这些要求以及如何正确实现此流程(输入-处理-输出)。

执行程序的首要步骤是正确处理命令行参数,命令行参数是提供给包含某种参数化信息的程序或脚本的参数。 例如,如果编写脚本以将两个数字相加,则通常的方法是具有两个参数,这是执行加法所必需的两个数字。 在计算机视觉项目中,图像和不同类型的文件通常作为命令行参数传递给脚本。

命令行参数是参数化程序执行的常用且简单的方法。

sys.argv

为了处理命令行参数,Python 使用sys.argv。 从这种意义上讲,执行程序时,Python 从命令行获取所有值并将其设置在sys.argv列表中。 列表的第一个元素是脚本的完整路径(或脚本名称-取决于操作系统),该路径始终为sys.argv[0]。 列表的第二个元素是脚本的第一个参数,即sys.argv[1],依此类推。 可以在下图中看到,其中sysargv_python.py脚本使用两个参数执行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9Fms9Pp-1681870288356)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/1703c8e7-9874-4d73-b6da-df4dab856cdc.png)]

要查看sys.argv的工作方式,我们将使用sysargv_python.py脚本:

代码语言:javascript复制
# Import the required packages
import sys

# We will print some information in connection with sys.argv to see how it works:
print("The name of the script being processed is: '{}'".format(sys.argv[0]))
print("The number of arguments of the script is: '{}'".format(len(sys.argv)))
print("The arguments of the script are: '{}'".format(str(sys.argv)))

如果执行此脚本时不带任何参数,则将看到以下信息:

代码语言:javascript复制
The name of the script being processed is: 'sysargv_python.py'
The number of arguments of the script is: '1'
The arguments of the script are: '['sysargv_python.py']'

另外,如果我们使用一个参数(例如sysargv_python.py OpenCV)执行此脚本,我们将获得以下信息:

代码语言:javascript复制
The name of the script being processed is: 'sysargv_python.py'
The number of arguments of the script is: '2'
The arguments of the script are: '['sysargv_python.py', 'OpenCV']'

如您所见,列表的第一个元素sysargv_python.pysys.argv[0])是脚本名称。 列表(sys.argv[1])中的第二个元素OpenCV是脚本的第一个参数。

argv[0]是脚本名,如果它不是完整路径名,则取决于操作系统。 请参阅这里了解更多信息。

argparse – 命令行选项和参数解析

应该考虑到,我们不应该直接处理sys.argv,主要是当我们的程序采用复杂的参数或多个文件名时。 另外,我们应该使用 Python 的argparse库,该库以系统的方式处理命令行参数,从而使其可以编写用户友好的命令行程序。 换句话说,Python 在标准库中有一个名为argparse的模块,用于解析命令行参数。 首先,程序确定所需的参数。 然后,argparse将研究如何将这些参数解析为sys.argv。 同样,argparse会生成帮助和使用消息,并在提供无效参数时发出错误。

此处介绍此模块的最小示例为argparse_minimal.py,如下所示:

代码语言:javascript复制
# Import the required packages
import argparse

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# The information about program arguments is stored in 'parser' and used when parse_args() is called.
# ArgumentParser parses arguments through the parse_args() method:
parser.parse_args()

不带参数运行此脚本将不会显示任何内容给stdout。 但是,如果包含--help(或-h)选项,我们将获得脚本的用法消息:

代码语言:javascript复制
usage: argparse_minimal.py [-h] 
optional arguments:
-h, --help show this help message and exit

指定其他任何参数都会导致错误,例如:

代码语言:javascript复制
argparse_minimal.py 6
usage: argparse_minimal.py [-h]
argparse_minimal.py: error: unrecognized arguments: 6

因此,我们必须使用-h参数调用此脚本。 这样,将显示使用消息信息。 由于未定义任何参数,因此不允许其他可能性。 这样,引入argparse的第二个示例是添加一个参数,可以在argparse_positional_arguments.py的示例中看到:

代码语言:javascript复制
# Import the required packages
import argparse

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add a positional argument using add_argument() including a help
parser.add_argument("first_argument", help="this is the string text in connection with first_argument")

# The information about program arguments is stored in 'parser'
# Then, it is used when the parser calls parse_args().
# ArgumentParser parses arguments through the parse_args() method:
args = parser.parse_args()

# We get and print the first argument of this script:
print(args.first_argument)

我们添加了add_argument()方法。 此方法用于指定程序将接受的命令行选项。 在这种情况下,需要first_argument参数。 另外,argparse模块存储所有参数,使其名称与每个添加的参数的名称(在本例中为first_argument)匹配。 因此,为了获得我们的参数,我们执行args.first_argument

如果此脚本以argparse_positional_arguments.py 5的身份执行,则输出将为5。 但是,如果脚本不带参数argparse_positional_arguments.py来执行,则输出如下:

代码语言:javascript复制
usage: argparse_positional_arguments.py [-h] first_argument
argparse_positional_arguments.py: error: the following arguments are required: first_argument

最后,如果我们使用-h选项执行脚本,输出将如下所示:

代码语言:javascript复制
usage: argparse_positional_arguments.py [-h] first_argument
positional arguments:
 first_argument this is the string text in connection with first_argument
optional arguments:
 -h, --help show this help message and exit

默认情况下,argparse将我们提供的选项视为字符串。 因此,如果参数不是字符串,则应建立type选项。 我们将看到argparse_sum_two_numbers.py脚本添加了两个参数,因此,这两个参数属于int类型:

代码语言:javascript复制
# Import the required packages
import argparse

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'first_number' argument using add_argument() including a help. The type of this argument is int
parser.add_argument("first_number", help="first number to be added", type=int)

# We add 'second_number' argument using add_argument() including a help The type of this argument is int
parser.add_argument("second_number", help="second number to be added", type=int)

# The information about program arguments is stored in 'parser'
# Then, it is used when the parser calls parse_args().
# ArgumentParser parses arguments through the parse_args() method:
args = parser.parse_args()
print("args: '{}'".format(args))

print("the sum is: '{}'".format(args.first_number   args.second_number))

# Additionally, the arguments can be stored in a dictionary calling vars() function:
args_dict = vars(parser.parse_args())

# We print this dictionary:
print("args_dict dictionary: '{}'".format(args_dict))

# For example, to get the first argument using this dictionary:
print("first argument from the dictionary: '{}'".format(args_dict["first_number"]))

如果在不带参数的情况下执行脚本,则输出将如下所示:

代码语言:javascript复制
argparse_sum_two_numbers.py
usage: argparse_sum_two_numbers.py [-h] first_number second_number
argparse_sum_two_numbers.py: error: the following arguments are required: first_number, second_number

另外,如果我们使用-h选项执行脚本,则输出将如下所示:

代码语言:javascript复制
argparse_sum_two_numbers.py --help
usage: argparse_sum_two_numbers.py [-h] first_number second_number

positional arguments:
 first_number first number to be added
 second_number second number to be added

optional arguments:
 -h, --help show this help message and exit

应该考虑到,在前面的示例中,我们通过调用vars()函数引入了将参数存储在字典中的可能性:

代码语言:javascript复制
# Additionally, the arguments can be stored in a dictionary calling vars() function:
args_dict = vars(parser.parse_args())

# We print this dictionary:
print("args_dict dictionary: '{}'".format(args_dict))

# For example, to get the first argument using this dictionary:
print("first argument from the dictionary: '{}'".format(args_dict["first_number"]))

例如,如果此脚本以argparse_sum_two_numbers.py 5 10执行,则输出将如下所示:

代码语言:javascript复制
args: 'Namespace(first_number=5, second_number=10)'
the sum is: '15'
args_dict dictionary: '{'first_number': 5, 'second_number': 10}'
first argument from the dictionary: '5'

这是对sys.argvargparse的快速介绍。 可以在这个页面上看到argparse的高级介绍。 此外,其文档非常详细,细致,并涵盖了许多示例。 此时,您现在可以在 OpenCV 和 Python 程序中学习如何使用argparse读取和写入图像,这将在“读取和写入图像”部分中显示。

读写图像

在计算机视觉项目中,图像通常在脚本中用作命令行参数。 在以下各节中,我们将看到如何读取和写入图像。

在 OpenCV 中读取图像

以下示例argparse_load_image.py展示了如何加载图像:

代码语言:javascript复制
# Import the required packages
import argparse
import cv2

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'path_image' argument using add_argument() including a help. The type of this argument is string (by default)
parser.add_argument("path_image", help="path to input image to be displayed")

# The information about program arguments is stored in 'parser'
# Then, it is used when the parser calls parse_args().
# ArgumentParser parses arguments through the parse_args() method:
args = parser.parse_args()

# We can now load the input image from disk:
image = cv2.imread(args.path_image)

# Parse the argument and store it in a dictionary:
args = vars(parser.parse_args())

# Now, we can also load the input image from disk using args:
image2 = cv2.imread(args["path_image"])

# Show the loaded image:
cv2.imshow("loaded image", image)
cv2.imshow("loaded image2", image2)

# Wait until a key is pressed:
cv2.waitKey(0)

# Destroy all windows:
cv2.destroyAllWindows()

在此示例中,必需的参数为path_image,其中包含我们要加载的图像的路径。 图像的路径是一个字符串。 因此,位置参数中不应包含任何类型,因为默认情况下它是字符串。 args.path_imageargs["path_image"]都将包含图像的路径(从参数获取值的两种不同方式),因此我们将它们用作cv2.imread()函数的参数。

在 OpenCV 中读取和写入图像

一种常见的方法是加载图像,执行某种处理,然后最终输出处理后的图像(请参阅第 2 章, OpenCV 中的图像基础知识,这三部分的详细说明) 脚步)。 从这个意义上讲,可以将处理后的图像保存到磁盘。 在以下示例中,介绍了这三个步骤(加载,处理和保存)。 在这种情况下,处理步骤非常简单(将图像转换为灰度)。 在以下示例argparse_load_processing_save_image.py中可以看到:

代码语言:javascript复制
# Import the required packages
import argparse
import cv2

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# Add 'path_image_input' argument using add_argument() including a help. The type is string (by default):
parser.add_argument("path_image_input", help="path to input image to be displayed")

# Add 'path_image_output' argument using add_argument() including a help. The type is string (by default):
parser.add_argument("path_image_output", help="path of the processed image to be saved")

# Parse the argument and store it in a dictionary:
args = vars(parser.parse_args())

# We can load the input image from disk:
image_input = cv2.imread(args["path_image_input"])

# Show the loaded image:
cv2.imshow("loaded image", image_input)

# Process the input image (convert it to grayscale):
gray_image = cv2.cvtColor(image_input, cv2.COLOR_BGR2GRAY)

# Show the processed image:
cv2.imshow("gray image", gray_image)

# Save the processed image to disk:
cv2.imwrite(args["path_image_output"], gray_image)

# Wait until a key is pressed:
cv2.waitKey(0)

# Destroy all windows:
cv2.destroyAllWindows()

在前面的示例中,有两个必需的参数。 第一个是path_image_input,它包含我们要加载的图像的路径。 图像的路径是一个字符串。 因此,位置参数中不应包含任何类型,因为默认情况下它是字符串。 第二个是path_image_output,它包含我们要保存的结果图像的路径。 在此示例中,处理步骤包括将加载的图像转换为灰度:

代码语言:javascript复制
# Process the input image (convert it to grayscale)
gray_image = cv2.cvtColor(image_input, cv2.COLOR_BGR2GRAY)

应当注意,第二个参数cv2.COLOR_BGR2GRAY假定加载的图像是 BGR 彩色图像。 如果您已加载 RGB 彩色图像,并且想要将其转换为灰度,则应使用cv2.COLOR_RGB2GRAY

这是一个非常简单的处理步骤,但为简单起见将其包括在内。 在以后的章节中,将显示更详细的处理算法。

读取相机帧和视频文件

在某些项目中,您必须捕获相机帧(例如,使用笔记本电脑的网络摄像头捕获的帧)。 在 OpenCV 中,我们具有cv2.VideoCapture,该类用于从不同来源(例如图像序列,视频文件和相机)捕获视频。 在本节中,我们将看到一些示例,向我们介绍此类用于捕获相机帧的类。

读取相机帧

第一个示例read_camera.py向您展示如何从连接到计算机的相机读取帧。 必需的参数为index_camera,它指示要读取的摄像机的索引。 如果已将网络摄像头连接到计算机,则其索引为0。 另外,如果您有第二台摄像机,则可以通过1进行选择。 如您所见,此参数的类型为int

使用cv2.VideoCapture的第一步是创建一个要使用的对象。 在这种情况下,对象是capture,我们这样调用构造器:

代码语言:javascript复制
 # We create a VideoCapture object to read from the camera (pass 0):
capture = cv2.VideoCapture(args.index_camera)

如果index_camera0(您的第一个连接的摄像机),则它等效于cv2.VideoCapture(0)。 为了检查连接是否正确建立,我们使用capture.isOpened()方法,如果无法建立连接,则返回False。 同样,如果捕获已正确初始化,则此方法返回True

要从摄像机逐帧捕获素材,我们调用capture.read()方法,该方法从摄像机返回帧。 该框架与 OpenCV 中的图像具有相同的结构,因此我们可以以相同的方式使用它。 例如,要将帧转换为灰度,请执行以下操作:

代码语言:javascript复制
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

此外,capture.read()返回布尔值。 该布尔值指示是否已从捕获对象正确读取帧。

访问捕获对象的某些属性

最后,您可以使用capture.get(property_identifier)访问捕获对象的某些属性。 在这种情况下,我们获得一些属性,例如帧宽度,帧高度每秒帧fps)。 如果我们调用不支持的属性,则返回值为0

代码语言:javascript复制
# Import the required packages
import cv2
import argparse

# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'index_camera' argument using add_argument() including a help.
parser.add_argument("index_camera", help="index of the camera to read from", type=int)
args = parser.parse_args()

# We create a VideoCapture object to read from the camera (pass 0):
capture = cv2.VideoCapture(args.index_camera)

# Get some properties of VideoCapture (frame width, frame height and frames per second (fps)):
frame_width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
frame_height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = capture.get(cv2.CAP_PROP_FPS)

# Print these values:
print("CV_CAP_PROP_FRAME_WIDTH: '{}'".format(frame_width))
print("CV_CAP_PROP_FRAME_HEIGHT : '{}'".format(frame_height))
print("CAP_PROP_FPS : '{}'".format(fps))

# Check if camera opened successfully
if capture.isOpened()is False:
    print("Error opening the camera")

# Read until video is completed
while capture.isOpened():
    # Capture frame-by-frame from the camera
    ret, frame = capture.read()

    if ret is True:
        # Display the captured frame:
        cv2.imshow('Input frame from the camera', frame)

        # Convert the frame captured from the camera to grayscale: 
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # Display the grayscale frame:
        cv2.imshow('Grayscale input camera', gray_frame)

        # Press q on keyboard to exit the program
        if cv2.waitKey(20) & 0xFF == ord('q'):
            break
    # Break the loop
    else:
        break

# Release everything:
capture.release()
cv2.destroyAllWindows()

保存相机帧

可以很容易地修改前面的示例,以添加有用的功能。 假设您想在发生一些有趣的事情时将一些帧保存到磁盘。 在下面的示例read_camera_capture.py中,我们将添加此功能。 当按下键盘上的C键时,我们将当前帧保存到磁盘。 我们同时保存了 BGR 和灰度帧。 执行此功能的代码如下所示:

代码语言:javascript复制
 # Press c on keyboard to save current frame
 if cv2.waitKey(20) & 0xFF == ord('c'):
     frame_name = "camera_frame_{}.png".format(frame_index)
     gray_frame_name = "grayscale_camera_frame_{}.png".format(frame_index)
     cv2.imwrite(frame_name, frame)
     cv2.imwrite(gray_frame_name, gray_frame)
     frame_index  = 1

ord('c')使用八位返回代表c字符的值。 此外,cv2.waitKey()值是按位的,并且将&运算符与0xFF一起使用只能得到其最后八位。 因此,我们可以在这两个 8 位值之间进行比较。 当按下C键时,我们为两个帧建立名称。 然后,我们将两个图像保存到磁盘。 最后,增加frame_index,以便为保存下一帧做好准备。 请查看read_camera_capture.py以查看此脚本的完整代码。

读取视频文件

cv2.VideoCapture也允许我们阅读视频文件。 因此,要读取视频文件,在创建cv2.VideoCapture对象时应提供视频文件的路径:

代码语言:javascript复制
# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'video_path' argument using add_argument() including a help.
parser.add_argument("video_path", help="path to the video file")
args = parser.parse_args()

# Create a VideoCapture object. In this case, the argument is the video file name:
capture = cv2.VideoCapture(args.video_path)

请查看read_video_file.py,以查看如何使用cv2.VideoCapture读取和显示视频文件的完整示例。

从 IP 摄像机读取

为了完成cv2.VideoCapture,我们将看看如何从 IP 摄像机读取数据。 从 OpenCV 中的 IP 摄像机读取与从文件读取非常相似。 从这个意义上讲,仅应更改cv2.VideoCapture构造器的参数。 这样做的好处是,您无需在本地网络中使用 IP 摄像机即可尝试此功能。 您可以尝试连接许多公共 IP 摄像机。 例如,我们要连接到公共 IP 摄像机,该摄像机位于俱乐部 Näutic 德拉塞尔瓦港–布拉瓦海岸–克雷乌斯角(西班牙赫罗纳)。 该端口的网页位于这个页面上。 您可以导航到网络摄像头部分以找到一些要连接的网络摄像头。

因此,您唯一需要修改的就是cv2.VideCapture的参数。 在这种情况下,它是http://217.126.89.102:8010/axis-cgi/mjpg/video.cgi。 如果执行此示例(read_ip_camera.py),应该会看到类似于以下屏幕截图的内容,其中显示了从 IP 摄像机获得的 BGR 和灰度图像:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJD10ckr-1681870288356)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b7f728c1-fd89-4a05-abd6-3567fecc2405.png)]

写入视频文件

在本节中,我们将看到如何使用cv2.VideoWriter写入视频文件。 但是,应首先介绍一些概念(例如 fps,编解码器和视频文件格式)。

计算每秒帧

在“读取摄像机帧和视频文件”部分中,我们看到了如何从cv2.VideoCapture对象获得一些属性。 fps 是计算机视觉项目中的重要指标。 该度量指示每秒处理多少帧。 可以肯定地说,较高的 fps 更好。 但是,算法每秒应处理的帧数取决于您要解决的特定问题。 例如,如果您的算法应跟踪并检测沿着街道行走的人,则 15 fps 可能就足够了。 但是,如果您的目标是检测和跟踪高速公路上快速行驶的汽车,则可能需要 20-25 fps。

因此,了解如何在计算机视觉项目中计算 fps 指标很重要。 在下面的示例read_camera_fps.py中,我们将修改read_camera.py以输出 fps 数。 关键点显示在以下代码中:

代码语言:javascript复制
# Read until the video is completed, or 'q' is pressed
while capture.isOpened():
    # Capture frame-by-frame from the camera
    ret, frame = capture.read()

    if ret is True:
        # Calculate time before processing the frame:
        processing_start = time.time()

        # All the processing should be included here
        # ...
        # ...
        # End of processing

        # Calculate time after processing the frame
        processing_end = time.time()

        # Calculate the difference
        processing_time_frame = processing_end - processing_start

        # FPS = 1 / time_per_frame
        # Show the number of frames per second
        print("fps: {}".format(1.0 / processing_time_frame))

    # Break the loop
    else:
        break

首先,我们花点时间进行处理:

代码语言:javascript复制
processing_start = time.time()

然后,我们在所有处理完成之后花时间:

代码语言:javascript复制
processing_end = time.time()

然后,我们计算出差异:

代码语言:javascript复制
processing_time_frame = processing_end - processing_start

最后,我们计算并打印 fps 数:

代码语言:javascript复制
print("fps: {}".format(1.0 / processing_time_frame))

写入视频文件的注意事项

视频代码是一种用于压缩和解压缩数字视频的软件。 因此,编解码器可以用于将未压缩的视频转换为压缩的视频,也可以用于将压缩的视频转换为未压缩的视频。 压缩视频格式通常遵循称为视频压缩规范视频编码格式的标准规范。 从这个意义上讲,OpenCV 提供了 FOURCC,这是一个 4 字节的代码,用于指定视频编解码器。 FOURCC 代表四个字符的代码。 可以在这个页面上找到所有可用代码的列表。 应该考虑到受支持的编解码器是平台相关的。 这意味着,如果要使用特定的编解码器,则该编解码器应该已经安装在系统上。 典型的编解码器是 DIVX,XVID,X264 和 MJPG。

此外,视频文件格式是一种用于存储数字视频数据的文件格式。 典型的视频文件格式为 AVI(*.avi),MP4(*.mp4),QuickTime(*.mov)和 Windows Media Video(*.wmv)。

最后,应考虑到视频文件格式(例如*.avi)和 FOURCC(例如 DIVX)之间的正确组合并不是简单明了的。 您可能需要尝试并使用这些值。 因此,在 OpenCV 中创建视频文件时,您必须考虑所有这些因素。

下图试图对其进行总结:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEPZ6CF7-1681870288357)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/212ae939-7668-4144-9fd4-ae72654d5312.png)]

此图总结了在 OpenCV 中使用cv2.VideoWriter()创建视频文件时应考虑的主要注意事项。 在此图中,已创建video_demo.avi视频。 在这种情况下,FOURCC 值为XVID,视频文件格式为AVI*.avi)。 最后,应确定 fps 和视频每一帧的尺寸。

此外,下面的示例write_video_file.py编写了一个视频文件,使用这些概念也可能会有所帮助。 此示例的一些关键点在此处进行了注解。 在此示例中,必需的参数是视频文件名(例如video_demo.avi):

代码语言:javascript复制
# We first create the ArgumentParser object 
# The created object 'parser' will have the necessary information
# to parse the command-line arguments into data types.
parser = argparse.ArgumentParser()

# We add 'output_video_path' argument using add_argument() including a help.
parser.add_argument("output_video_path", help="path to the video file to write")
args = parser.parse_args()

我们将从连接到计算机的第一台相机拍摄帧。 因此,我们相应地创建对象:

代码语言:javascript复制
# Create a VideoCapture object and pass 0 as argument to read from the camera
capture = cv2.VideoCapture(0)

接下来,我们将从捕获对象中获取一些属性(帧宽度,帧高度和 fps)。 我们将使用它们来创建我们的视频文件:

代码语言:javascript复制
# Get some properties of VideoCapture (frame width, frame height and frames per second (fps)):
frame_width = capture.get(cv2.CAP_PROP_FRAME_WIDTH)
frame_height = capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = capture.get(cv2.CAP_PROP_FPS)

现在,我们使用 FOURCC(四字节代码)指定视频编解码器。 请记住,它是依赖于平台的。 在这种情况下,我们将编解码器定义为XVID

代码语言:javascript复制
# FourCC is a 4-byte code used to specify the video codec and it is platform dependent!
# In this case, define the codec XVID
fourcc = cv2.VideoWriter_fourcc('X', 'V', 'I', 'D')

以下行也适用:

代码语言:javascript复制
# FourCC is a 4-byte code used to specify the video codec and it is platform dependent!
# In this case, define the codec XVID
fourcc = cv2.VideoWriter_fourcc(*'XVID')

然后,我们创建cv2.VideoWriter对象out_gray。 我们使用与输入相机相同的属性。 最后一个参数是False,以便我们可以以灰度级编写视频。 如果我们要创建彩色视频,则最后一个参数应为True

代码语言:javascript复制
# Create VideoWriter object. We use the same properties as the input camera.
# Last argument is False to write the video in grayscale. True otherwise (write the video in color)
out_gray = cv2.VideoWriter(args.output_video_path, fourcc, int(fps), (int(frame_width), int(frame_height)), False)

我们使用capture.read()从捕获对象逐帧输出。 每帧都将转换为灰度并写入视频文件。 我们可以显示框架,但这不是编写视频所必需的。 如果按q,程序结束:

代码语言:javascript复制
# Read until video is completed or 'q' is pressed
while capture.isOpened():
    # Read the frame from the camera
    ret, frame = capture.read()
    if ret is True:

        # Convert the frame to grayscale
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # Write the grayscale frame to the video
        out_gray.write(gray_frame)

        # We show the frame (this is not necessary to write the video)
        # But we show it until 'q' is pressed
        cv2.imshow('gray', gray_frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break

最后,我们释放所有内容(cv2.VideoCapturecv2.VideWriter对象,并销毁创建的窗口):

代码语言:javascript复制
# Release everything:
capture.release()
out_gray.release()
cv2.destroyAllWindows()

该示例的完整代码可以在write_video_file.py文件中看到。

玩转视频捕获属性

在前面的一些示例中,我们看到了如何从cv2.VideoCapture对象获取某些属性。 在本节中,我们将看到如何获取所有属性并了解它们如何工作。 最后,我们将使用这些属性来加载视频文件并向后输出(首先显示视频的最后一帧,依此类推)。

从视频捕获对象获取所有属性

首先,我们创建read_video_file_all_properties.py脚本以显示所有属性。 其中一些属性仅在我们使用相机(不适用于视频文件)时才起作用。 在这些情况下,将返回0值。 此外,我们创建了decode_fourcc()函数,该函数将capture.get(cv2.CAP_PROP_FOURCC)返回的值转换为包含编解码器的int表示形式的字符串值。 从这个意义上讲,此值应转换为四字节的char表示形式,以正确输出编解码器。 因此,decode_fourcc()函数可以解决此问题。

该函数的代码如下:

代码语言:javascript复制
def decode_fourcc(fourcc):
    """Decodes the fourcc value to get the four chars identifying it

    """
    fourcc_int = int(fourcc)

    # We print the int value of fourcc
    print("int value of fourcc: '{}'".format(fourcc_int))

    # We can also perform this in one line:
    # return "".join([chr((fourcc_int >> 8 * i) & 0xFF) for i in range(4)])

    fourcc_decode = ""
    for i in range(4):
        int_value = fourcc_int >> 8 * i & 0xFF
        print("int_value: '{}'".format(int_value))
        fourcc_decode  = chr(int_value)
    return fourcc_decode

为了说明其工作原理,下图总结了主要步骤:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bieG2VIZ-1681870288357)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c8b2bfee-b5fb-4e7d-8c0e-e13ee0ffdc40.png)]

如您所见,第一步是获取capture.get(cv2.CAP_PROP_FOURCC)返回的值的int表示形式,该值是一个字符串。 然后,我们迭代四次以获取每八位,并将这八位转换为int。 最后,使用chr()函数将这些int值转换为char。 应该注意的是,我们只能在一行代码中执行此功能,如下所示:

代码语言:javascript复制
return "".join([chr((fourcc_int >> 8 * i) & 0xFF) for i in range(4)])

CAP_PROP_POS_FRAMES属性为您提供视频文件的当前帧,CAP_PROP_POS_MSEC属性为您提供当前帧的时间戳。 我们还可以使用CAP_PROP_FPS属性获取 fps 的数量。 CAP_PROP_FRAME_COUNT属性为您提供视频文件的总帧数。

要获取并打印所有属性,请使用以下代码:

代码语言:javascript复制
# Get and print these values:
print("CV_CAP_PROP_FRAME_WIDTH: '{}'".format(capture.get(cv2.CAP_PROP_FRAME_WIDTH)))
print("CV_CAP_PROP_FRAME_HEIGHT : '{}'".format(capture.get(cv2.CAP_PROP_FRAME_HEIGHT)))
print("CAP_PROP_FPS : '{}'".format(capture.get(cv2.CAP_PROP_FPS)))
print("CAP_PROP_POS_MSEC : '{}'".format(capture.get(cv2.CAP_PROP_POS_MSEC)))
print("CAP_PROP_POS_FRAMES : '{}'".format(capture.get(cv2.CAP_PROP_POS_FRAMES)))
print("CAP_PROP_FOURCC  : '{}'".format(decode_fourcc(capture.get(cv2.CAP_PROP_FOURCC))))
print("CAP_PROP_FRAME_COUNT  : '{}'".format(capture.get(cv2.CAP_PROP_FRAME_COUNT)))
print("CAP_PROP_MODE : '{}'".format(capture.get(cv2.CAP_PROP_MODE)))
print("CAP_PROP_BRIGHTNESS : '{}'".format(capture.get(cv2.CAP_PROP_BRIGHTNESS)))
print("CAP_PROP_CONTRAST : '{}'".format(capture.get(cv2.CAP_PROP_CONTRAST)))
print("CAP_PROP_SATURATION : '{}'".format(capture.get(cv2.CAP_PROP_SATURATION)))
print("CAP_PROP_HUE : '{}'".format(capture.get(cv2.CAP_PROP_HUE)))
print("CAP_PROP_GAIN  : '{}'".format(capture.get(cv2.CAP_PROP_GAIN)))
print("CAP_PROP_EXPOSURE : '{}'".format(capture.get(cv2.CAP_PROP_EXPOSURE)))
print("CAP_PROP_CONVERT_RGB : '{}'".format(capture.get(cv2.CAP_PROP_CONVERT_RGB)))
print("CAP_PROP_RECTIFICATION : '{}'".format(capture.get(cv2.CAP_PROP_RECTIFICATION)))
print("CAP_PROP_ISO_SPEED : '{}'".format(capture.get(cv2.CAP_PROP_ISO_SPEED)))
print("CAP_PROP_BUFFERSIZE : '{}'".format(capture.get(cv2.CAP_PROP_BUFFERSIZE)))

您可以在read_video_file_all_properties.py文件中查看此脚本的完整代码。

使用属性来反向播放视频

为了了解如何使用上述属性,我们将了解read_video_file_backwards.py脚本,该脚本使用其中的某些属性加载视频并向后输出,首先显示视频的最后一帧,依此类推。 我们将使用以下属性:

  • cv2.CAP_PROP_FRAME_COUNT:此属性提供帧的总数
  • cv2.CAP_PROP_POS_FRAMES:此属性提供当前帧

第一步是获取最后一帧的索引:

代码语言:javascript复制
# We get the index of the last frame of the video file
frame_index = capture.get(cv2.CAP_PROP_FRAME_COUNT) - 1

因此,我们将当前帧设置为读取到以下位置:

代码语言:javascript复制
 # We set the current frame position
 capture.set(cv2.CAP_PROP_POS_FRAMES, frame_index)

这样,我们可以照常阅读此框架:

代码语言:javascript复制
 # Capture frame-by-frame from the video file
 ret, frame = capture.read()

最后,我们递减索引以从视频文件中读取下一帧:

代码语言:javascript复制
 # Decrement the index to read next frame
 frame_index = frame_index - 1

完整代码在read_video_file_backwards.py脚本中提供。 可以轻松修改此脚本,以保存生成的视频向后播放(不仅显示该视频)。 在“问题”部分中提出了此脚本。

总结

在本章中,我们看到使用图像和文件是计算机视觉项目的关键元素。 这种项目中的常见方法是先加载一些图像,执行一些处理,然后输出处理后的图像。 在本章中,我们回顾了该流程。 另外,关于视频流,cv2.VideoCapturecv2.VideoWriter均被覆盖。 我们还研究了用于视频写作的cv2.VideoWriter类。 编写视频文件时,审查了两个关键方面-视频编解码器(例如 DIVX)和视频文件格式(例如 AVI)。 要使用视频编解码器,OpenCV 提供了四字节代码 FOURCC。 典型的编解码器是 DIVX,XVID,X264 和 MJPG,而典型的视频文件格式是 AVI(*.avi),MP4(*.mp4),QuickTime(*.mov)和 Windows Media Video(*.wmv)。

我们还回顾了 fps 的概念以及如何在程序中进行计算。 此外,我们研究了如何获取cv2.VideoCapture对象的所有属性,以及如何使用它们加载视频并向后输出,从而首先显示了视频的最后一帧。 最后,我们看到了如何应对命令行参数。 Python 使用sys.argv处理命令行参数。 当我们的程序采用复杂的参数或多个文件名时,我们应该使用 Python 的argparse库。

在下一章中,我们将学习如何使用 OpenCV 库绘制基本的和更高级的形状。 OpenCV 提供绘制线,圆,矩形,椭圆,文本和折线的函数。 与计算机视觉项目有关,这是在图像中绘制基本形状以执行以下操作的常用方法:

  • 显示算法的一些中间结果(例如,检测到的对象的边界框)
  • 显示算法的最终结果(例如,检测到的对象的类别,例如汽车,猫或狗)
  • 显示一些调试信息(例如,执行时间)

因此,下一章将对您的计算机视觉算法有很大帮助。

问题

  1. 什么是sys.argv[1]
  2. 编写一段代码以添加int类型的first_number参数,并包括要使用parser.add_argument()添加的帮助优先编号。
  3. 编写一段代码,将想象中的img保存到名称为image.png的磁盘上。
  4. 使用cv2.VideoCapture()创建capture对象,以从连接到计算机的第一台摄像机读取。
  5. 使用cv2.VideoCapture()创建对象捕获,以从连接到计算机的第一台摄像机读取并打印CAP_PROP_FRAME_WIDTH属性。
  6. 读取图像并将其保存到名称相同但以_copy.png结尾的磁盘(例如logo_copy.png)。
  7. 创建一个脚本(read_video_file_backwards_save_video.py),以加载视频文件并创建另一个向后播放的脚本(首先包含视频的最后一帧,依此类推)。

进一步阅读

以下参考资料将帮助您深入研究argparse,这是您的计算机视觉项目的关键点:

  • 《使用argparse解析命令行》
  • 《使用argparse获取命令行输入》

四、在 OpenCV 中构造基本形状

OpenCV 提供的一种基本功能是绘制基本形状。 OpenCV 提供绘制线,圆,矩形,椭圆等的函数。 在构建计算机视觉项目时,通常需要通过绘制一些形状来修改图像。 例如,如果开发人脸检测算法,则应绘制一个矩形以突出显示在计算图像中检测到的人脸。 此外,如果您开发了面部识别算法,则应绘制一个矩形突出显示检测到的面部,并编写一些文本来显示检测到的面部的身份。 最后,写一些调试信息是一种常见的方法。 例如,您可以显示检测到的脸部数量(以便查看脸部检测算法的表现)或处理时间。 在本章中,您将了解如何使用 OpenCV 库绘制基本的和更高级的形状。

将涵盖以下主题:

  • OpenCV 中绘图的理论介绍
  • 基本形状 - 直线,矩形和圆形
  • 基本形状(2)- 直线和箭头线,椭圆和折线
  • 绘制文字
  • 带有鼠标事件的动态绘图
  • 高级绘图

技术要求

技术要求如下:

  • Python 和 OpenCV
  • 特定于 Python 的 IDE
  • NumPy 和 Matplotlib 包
  • Git 客户端

有关如何安装它们的更多详细信息,请参见第 1 章,“设置 OpenCV”。 可以在此处访问 GitHub 存储库,该存储库包含从第一章到最后一章都需要完成本书的所有支持项目文件。

OpenCV 中绘图的理论介绍

OpenCV 提供了许多绘制基本形状的函数。 常见的基本形状包括直线,矩形和圆形。 但是,使用 OpenCV,我们可以绘制更多基本形状。 如引言中简要提到的,这是在图像上绘制基本形状以执行以下操作的常用方法:

  • 显示算法的一些中间结果
  • 显示算法的最终结果
  • 显示一些调试信息

在下一个屏幕截图中,您可以看到一张经过修改的图像,其中包含与导言中提到的两种算法(面部检测和面部识别)有关的一些有用信息。 这样,您可以处理目录中的所有图像,然后,您可以查看算法检测到错误人脸(假正例)甚至丢失人脸(假负例)的位置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGOJbUiC-1681870288357)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/37386eca-3b9d-44db-aa74-23e2d7bbe24a.png)]

假正例是一个错误,其中结果指示存在条件,而实际上不满足条件(例如,椅子被分类为人脸)。 假负例是一个错误,其中结果指示不存在条件,而实际上应满足条件(例如,未检测到脸部)。

在本章中,我们将看到如何用不同的颜色绘制一些基本形状和文本。 为了对此进行介绍并回顾先前各章中的一些概念,我们将向您展示我们将在本章的大多数示例中使用的两个基本功能。 第一个功能是构建colors字典,该字典定义了要使用的主要颜色。 在下一个屏幕截图中,您可以看到它是如何工作的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ojKQ342C-1681870288357)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/91a8a43c-3a25-4908-881c-c69372185ccf.png)]

应该指出的是,这本词典仅用于训练和实践目的。 为了其他目的,您可以使用其他选项。 一种常见的方法是创建一个constant.py文件来定义颜色。 每种颜色由一个常量定义:

代码语言:javascript复制
"""
Common colors triplets (BGR space) to use in OpenCV
"""

BLUE = (255, 0, 0)
GREEN = (0, 255, 0)
RED = (0, 0, 255)
YELLOW = (0, 255, 255)
MAGENTA = (255, 0, 255)
CYAN = (255, 255, 0)
DARK_GRAY = (50, 50, 50)
...

以下代码将使您能够使用这些常量:

代码语言:javascript复制
import constant

# Getting red color:
print("red: '{}'".format(constant.RED))

常量通常以大写字母(例如BLUE)指定,单词之间带有下划线(例如DARK_GRAY)。

另外,当我们要使用 Matplotlib 绘制图形时,我们创建了带有两个参数的show_with_matplotlib()函数。 第一个是我们要显示的图像,第二个是要绘制的图形的标题。 因此,此函数的第一步是将BGR图像转换为RGB,因为您必须使用 Matplotlib 显示彩色图像。 此函数的第二个也是最后一个步骤是使用 Matplotlib 功能显示图像。 为了将这些片段放在一起,已对testing_colors.py脚本进行了编码。 在此脚本中,我们绘制了几行,每行以字典的颜色显示。

创建字典的代码如下所示:

代码语言:javascript复制
# Dictionary containing some colors
colors = {'blue': (255, 0, 0), 'green': (0, 255, 0), 'red': (0, 0, 255), 'yellow': (0, 255, 255), 'magenta': (255, 0, 255), 'cyan': (255, 255, 0), 'white': (255, 255, 255), 'black': (0, 0, 0), 'gray': (125, 125, 125), 'rand': np.random.randint(0, high=256, size=(3,)).tolist(), 'dark_gray': (50, 50, 50), 'light_gray': (220, 220, 220)}

您会看到此词典中包含一些预定义的颜色-bluegreenredyellowmagentacyanwhiteblackgray, 随机数graydark_graylight_gray。 如果要使用特定的颜色(例如magenta),则应执行以下操作:

代码语言:javascript复制
colors['magenta']

或者,您可以使用(255, 0, 255)获得magenta颜色。 但是,使用此字典比写数字三元组更容易,因为您不需要记住 RGB 颜色空间的相加属性(将blue-(255,0,0)red相加-(0,0,255)会得出magenta(255, 0, 255))。 请记住,您可以使用constant.py执行此功能。

如果您不知道这些数字是什么或代表什么,则应阅读第 2 章,“OpenCV 中的图像基础知识”,在此介绍了这些概念。

为了了解如何使用这两个函数,我们在本章的大多数示例中都使用了这些函数(colors函数和show_with_matplotlib()函数),我们创建了testing_colors.py 脚本。 如果执行它,您将看到下一个屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYPt0wd1-1681870288357)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/89fcd117-3725-4abb-9426-422bfa99124b.png)]

在此示例中,我们创建了大小为500x500的图像,具有3通道(我们需要彩色图像)和uint8类型(8 位无符号整数)。 我们用黑色背景创建了它:

代码语言:javascript复制
# We create the canvas to draw: 400 x 400 pixels, 3 channels, uint8 (8-bit unsigned integers)
# We set background to black using np.zeros()
image = np.zeros((500, 500, 3), dtype="uint8")

在这种情况下,我们想将背景设置为浅灰色,而不是黑色。 如果要更改背景,可以执行以下操作:

代码语言:javascript复制
# If you want another background color, you can do the following:
image[:] = colors['light_gray']

接下来,我们添加了绘制一些线的功能,每条线都使用字典的颜色。 应当注意,在下一节中,我们将看到如何创建一些基本形状,因此,如果您不了解创建线条的代码,请不要担心:

代码语言:javascript复制
# We draw all the colors to test the dictionary
# We draw some lines, each one in a color. To get the color, use 'colors[key]'
separation = 40
for key in colors:
cv2.line(image, (0, separation), (500, separation), colors[key], 10)
separation  = 40

最后,我们使用创建的show_with_matplotlib()函数绘制图像:

代码语言:javascript复制
# Show image:
show_with_matplotlib(image, 'Dictionary with some predefined colors')

show_with_matplotlib()的两个参数是要绘制的图像和要显示的标题。 因此,现在我们准备开始使用 OpenCV 和 Python 创建一些基本形状。

绘制形状

在本节中,我们将看到如何使用 OpenCV 功能绘制形状。 首先,我们将研究如何绘制基本形状,然后将重点放在更高级的形状上。

基本形状 – 直线,矩形和圆形

在下一个示例中,我们将看到如何在 OpenCV 中绘制基本形状。 这些基本形状包括线条,矩形和圆形,它们是最常见且最简单的绘制形状。 第一步是创建将在其中绘制形状的图像。 为此,将创建带有3通道的400x400图像(以正确显示 BGR 图像)和uint8类型(8 位无符号整数):

代码语言:javascript复制
# We create the canvas to draw: 400 x 400 pixels, 3 channels, uint8 (8-bit unsigned integers)
# We set the background to black using np.zeros()
image = np.zeros((400, 400, 3), dtype="uint8")

我们使用colors 字典将背景设置为浅灰色:

代码语言:javascript复制
# If you want another background color, you can do the following:
image[:] = colors['light_gray']

下一个屏幕截图显示了此画布(或图像):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnEQqCgs-1681870288358)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7a09ff70-a777-4030-aa4b-8e69b8e83ca5.png)]

现在,我们准备绘制基本形状。 应该注意的是,OpenCV 提供的大多数绘图函数都有共同的参数。 为了简单起见,在此简要介绍这些参数:

  • img:是要绘制形状的图像。
  • color:它是用来绘制形状的颜色(BGR 三元组)。
  • thickness:如果该值为正,则是形状轮廓的厚度。 否则,将绘制填充形状。
  • lineType:这是形状边界的类型。 OpenCV 提供三种类型的线:
    • cv2.LINE_4:这表示四线连接
    • cv2.LINE_8:这表示八连接线
    • cv2.LINE_AA:这表示抗锯齿线
  • shift:这表示与定义形状的某些点的坐标有关的小数位数。

结合上述参数,lineTypecv2.LINE_AA选项可产生质量更好的绘图(例如,在绘制文本时),但绘制速度较慢。 因此,应考虑这一因素。 八连接线和四连接线(均为非抗锯齿线)均使用 Bresenham 算法绘制。 对于抗锯齿线类型,使用高斯滤波算法。 另外,shift参数是必需的,因为许多绘图函数无法处理亚像素精度。 为了简单起见,我们将使用整数坐标。 因此,该值将设置为0shift = 0)。 但是,为了让您有一个完整的了解,还将提供一个有关如何使用shift参数的示例。

请记住,对于本节中包含的所有示例,已经创建了一个画布来绘制所有形状。 此画布是400 x 400像素图像,背景为浅灰色。 请参阅前面的屏幕快照,其中显示了此画布。

绘制直线

我们将要看到的第一个函数是cv2.line()。 签名如下:

代码语言:javascript复制
img = line(img, pt1, pt2, color, thickness=1, lineType=8, shift=0)

此函数在img图像上画一条连接pt1pt2的线:

代码语言:javascript复制
cv2.line(image, (0, 0), (400, 400), colors['green'], 3)
cv2.line(image, (0, 400), (400, 0), colors['blue'], 10)
cv2.line(image, (200, 0), (200, 400), colors['red'], 3)
cv2.line(image, (0, 200), (400, 200), colors['yellow'], 10)

对这些行进行编码后,我们调用show_with_matplotlib(image, 'cv2.line()')函数。 结果显示在下一个屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zneFYJEI-1681870288358)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/235f90b0-c9f9-4343-8f52-6f696a3e3e23.png)]

绘制矩形

cv2.rectangle()函数的签名如下:

代码语言:javascript复制
img = rectangle(img, pt1, pt2, color, thickness=1, lineType=8, shift=0)

给定两个相对的角pt1pt2,此函数绘制一个矩形:

代码语言:javascript复制
cv2.rectangle(image, (10, 50), (60, 300), colors['green'], 3)
cv2.rectangle(image, (80, 50), (130, 300), colors['blue'], -1)
cv2.rectangle(image, (150, 50), (350, 100), colors['red'], -1)
cv2.rectangle(image, (150, 150), (350, 300), colors['cyan'], 10)

绘制这些矩形后,我们调用show_with_matplotlib(image, 'cv2.rectangle()')函数。 结果显示在下一个屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQ2jI8Cw-1681870288358)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/481682d2-4a6b-4d40-ad30-b1435338768f.png)]

请记住,thickness参数的负值(例如-1)表示将绘制填充的形状。

绘制圆形

cv2.circle()函数的签名如下:

代码语言:javascript复制
img = circle(img, center, radius, color, thickness=1, lineType=8, shift=0)

此函数绘制一个以center位置为中心的radius半径的圆。 以下代码定义了一些圈子:

代码语言:javascript复制
cv2.circle(image, (50, 50), 20, colors['green'], 3)
cv2.circle(image, (100, 100), 30, colors['blue'], -1)
cv2.circle(image, (200, 200), 40, colors['magenta'], 10)
cv2.circle(image, (300, 300), 40, colors['cyan'], -1)

绘制这些圆之后,我们调用show_with_matplotlib(image, 'cv2.circle()')函数。 结果显示在下一个屏幕截图中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQt8unt4-1681870288358)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/9149d991-8922-4c24-86f0-3bdbb0a6949d.png)]

本节示例的完整代码可以在basic_drawing.py中看到。

了解高级形状

在本节中,我们将看到如何绘制剪切线,箭头线,椭圆和折线。 这些形状的绘制不像我们在上一节中看到的那样简单,但是它们很容易理解。 第一步是创建将在其中绘制形状的图像。 为此,使用3通道(以正确显示 BGR 图像)和uint8类型的300 x 300图像(8 位无符号整数)将被创建:

代码语言:javascript复制
# We create the canvas to draw: 300 x 300 pixels, 3 channels, uint8 (8-bit unsigned integers)
# We set the background to black using np.zeros()
image = np.zeros((300, 300, 3), dtype="uint8")

我们使用colors字典将背景设置为浅灰色:

代码语言:javascript复制
# If you want another background color, you can do the following:
image[:] = colors['light_gray']

此时,我们可以开始绘制新形状了。

绘制剪切线

cv2.clipLine()函数的签名如下:

代码语言:javascript复制
retval, pt1, pt2 = clipLine(imgRect, pt1, pt2)

cv2.clipLine()函数返回矩形内的段(由pt1pt2输出点定义)(函数根据定义的矩形剪切片段)。 从这个意义上讲,如果两个原始pt1pt2点都在矩形外部,则retvalFalse。 否则(两个pt1pt2点中的一些在矩形内)此函数返回True。 在下一段代码中可以更清楚地看到这一点:

代码语言:javascript复制
cv2.line(image, (0, 0), (300, 300), colors['green'], 3)
cv2.rectangle(image, (0, 0), (100, 100), colors['blue'], 3)
ret, p1, p2 = cv2.clipLine((0, 0, 100, 100), (0, 0), (300, 300))
if ret:
    cv2.line(image, p1, p2, colors['yellow'], 3)

在下一个屏幕截图中,执行以下代码后,您可以看到结果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsEGH0xW-1681870288359)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7089fc28-b4df-4b2d-bdb1-2e139d4d8f71.png)]

如您所见,由p1p2点定义的线段显示为黄色,根据矩形剪切原始线段。 在这种情况下,retTrue,因为至少有一个点在矩形内,这就是绘制由pt1pt2定义的黄色部分的原因。

绘制箭头

该函数的签名如下:

代码语言:javascript复制
cv.arrowedLine(img, pt1, pt2, color, thickness=1, lineType=8, shift=0, tipLength=0.1)

此函数允许您创建一个箭头,该箭头从pt1定义的第一个点指向pt2定义的第二个点。 箭头尖端的长度可以由tipLength参数控制,该参数相对于段长度(pt1pt2之间的距离)定义:

代码语言:javascript复制
cv2.arrowedLine(image, (50, 50), (200, 50), colors['red'], 3, 8, 0, 0.1)
cv2.arrowedLine(image, (50, 120), (200, 120), colors['green'], 3, cv2.LINE_AA, 0, 0.3)
cv2.arrowedLine(image, (50, 200), (200, 200), colors['blue'], 3, 8, 0, 0.3)

如您所见,定义了三个箭头。 请参见下一个屏幕截图,其中绘制了这些箭头。 另外,请查看cv2.LINE_AA(您也可以写16)和8(您也可以写cv2.LINE_8)之间的区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZyYXxAZI-1681870288359)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/bb3d3a68-98a1-4e7f-8d54-08d27c588e63.png)]

在此示例中,我们结合了两个枚举(例如,cv2.LINE_AA)(或引起您的注意),或将lineType参数直接写入值(例如,8)。 这绝对不是一个好主意,因为它可能会使您感到困惑。 应该在您的所有代码中建立并维护一个标准。

绘制椭圆

该函数的签名如下:

代码语言:javascript复制
cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness=1, lineType=8, shift=0)

此函数使您可以创建不同类型的椭圆。 angle参数(以度为单位)允许您旋转椭圆。 axes参数控制对应于轴尺寸一半的椭圆尺寸。 如果需要完整的椭圆,请startAngle = 0endAngle = 360。 否则,应将这些参数调整为所需的椭圆弧(以度为单位)。 您还可以看到,通过为轴传递相同的值,您可以绘制一个圆:

代码语言:javascript复制
cv2.ellipse(image, (80, 80), (60, 40), 0, 0, 360, colors['red'], -1)
cv2.ellipse(image, (80, 200), (80, 40), 0, 0, 360, colors['green'], 3)
cv2.ellipse(image, (80, 200), (10, 40), 0, 0, 360, colors['blue'], 3)
cv2.ellipse(image, (200, 200), (10, 40), 0, 0, 180, colors['yellow'], 3)
cv2.ellipse(image, (200, 100), (10, 40), 0, 0, 270, colors['cyan'], 3)
cv2.ellipse(image, (250, 250), (30, 30), 0, 0, 360, colors['magenta'], 3)
cv2.ellipse(image, (250, 100), (20, 40), 45, 0, 360, colors['gray'], 3)

这些省略号可以在下一个屏幕截图中看到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cb5VNBiY-1681870288359)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/008aaed5-df0f-4235-bbad-5d5375392410.png)]

绘制多边形

该函数的签名如下:

代码语言:javascript复制
cv2.polylines(img, pts, isClosed, color, thickness=1, lineType=8, shift=0)

使用此函数可以创建多边形曲线。 此处的关键参数是pts,应在其中提供定义多边形曲线的数组。 该参数的形状应为(number_vertex, 1, 2)。 因此,一种常见的方法是通过使用np.array创建(np.int32类型的坐标)来定义它,然后对其进行重塑以匹配上述形状。 例如,要创建一个三角形,代码将如下所示:

代码语言:javascript复制
# These points define a triangle
pts = np.array([[250, 5], [220, 80], [280, 80]], np.int32)
# Reshape to shape (number_vertex, 1, 2)
pts = pts.reshape((-1, 1, 2))
# Print the shapes: this line is not necessary, only for visualization
print("shape of pts '{}'".format(pts.shape))
# this gives: shape of pts '(3, 1, 2)'

另一个重要参数是isClosed。 如果此参数为True,则多边形将被绘制为封闭状态。 否则,将不会绘制第一个顶点与最后一个顶点之间的线段,从而形成一个开放的多边形。 为了完整说明,为了绘制一个闭合的三角形,下面给出代码:

代码语言:javascript复制
# These points define a triangle
pts = np.array([[250, 5], [220, 80], [280, 80]], np.int32)
# Reshape to shape (number_vertex, 1, 2)
pts = pts.reshape((-1, 1, 2))
# Print the shapes: this line is not necessary, only for visualization
print("shape of pts '{}'".format(pts.shape))
# Draw this poligon with True option
cv2.polylines(image, [pts], True, colors['green'], 3)

同样,我们对五边形和矩形进行了编码,可以在下一个屏幕截图中看到它们:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-303kQqPF-1681870288359)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7431648d-2d4c-4e0d-8645-11e6010d8f0b.png)]

要查看本节的完整代码,您可以查看basic_drawing_2.py脚本。

绘图函数中的shift参数

某些以前的函数(带有shift参数的函数)可以与像素坐标结合使用,达到亚像素精度。 为了解决这个问题,您应该将坐标作为定点数字传递,并以整数编码。

定点数表示为整数(在小数点左边)和小数部分(在小数点右边)都保留了特定(固定)位数(位)。

因此,shift参数允许您指定小数位数(在小数点右边)。 最后,实点坐标计算如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0OuU2CJX-1681870288359)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/8960400a-b88f-47b2-b5ce-1befe824f839.png)]

例如,这段代码绘制了两个半径为300的圆。 其中之一使用shift = 2值提供子像素精度。 在这种情况下,应将原点和半径乘以42^(shift = 2))的倍数:

代码语言:javascript复制
shift = 2
factor = 2 ** shift
print("factor: '{}'".format(factor))
cv2.circle(image, (int(round(299.99 * factor)), int(round(299.99 * factor))), 300 * factor, colors['red'], 1, shift=shift)
cv2.circle(image, (299, 299), 300, colors['green'], 1)

如果shift = 3,则因子的值为82^(shift = 3)),依此类推。 乘以2的幂与将对应于整数二进制表示的位左移一相同。 这样您可以绘制浮动坐标。 总结一下,我们还可以为cv2.circle()创建一个包装函数,该函数可以使用shift参数属性来处理浮点坐标draw_float_circle()。 接下来显示此示例的关键代码。 完整代码在shift_parameter.py脚本中定义:

代码语言:javascript复制
def draw_float_circle(img, center, radius, color, thickness=1, lineType=8, shift=4):
    """Wrapper function to draw float-coordinate circles

    """
    factor = 2 ** shift
    center = (int(round(center[0] * factor)), int(round(center[1] * factor)))
    radius = int(round(radius * factor))
    cv2.circle(img, center, radius, color, thickness, lineType, shift)

draw_float_circle(image, (299, 299), 300, colors['red'], 1, 8, 0)
draw_float_circle(image, (299.9, 299.9), 300, colors['green'], 1, 8, 1)
draw_float_circle(image, (299.99, 299.99), 300, colors['blue'], 1, 8, 2)
draw_float_circle(image, (299.999, 299.999), 300, colors['yellow'], 1, 8, 3)

绘图函数中的lineType参数

另一个常用参数是lineType,它可以采用三个不同的值。 我们之前曾评论过这三种类型之间的区别。 为了更清楚地看到它,请看下一个屏幕截图,其中我们绘制了三条具有相同粗细和倾斜度的线:yellow = cv2.LINE_4red = cv2.LINE_AAgreen = cv2.LINE_8。 要查看此示例的完整代码,可以检查basic_line_types.py脚本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4nPwVhl-1681870288360)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/3f69219e-da87-4022-827d-1ffd9d842159.png)]

在上一个屏幕截图中,您可以清楚地看到使用三种不同的线型绘制一条线的区别。

绘制文字

OpenCV 还可以用于在图像中呈现文本。 在本节中,我们将看到如何使用cv2.putText()函数绘制文本。 此外,我们将看到您可以使用的所有可用字体。 最后,我们将看到一些与文本绘制有关的 OpenCV 函数。

绘制文字

cv2.putText()函数具有以下签名:

代码语言:javascript复制
img = cv.putText( img, text, org, fontFace, fontScale, color, thickness=1, lineType= 8, bottomLeftOrigin=False)

此函数使用由fontFacefontScale因素提供的字体类型,从org坐标(如果是bottomLeftOrigin = False则为左上角,否则为左下角)开始绘制所提供的文本字符串。 结合此示例,您可以看到最后提供的参数lineType带有 OpenCV 中可用的三个不同值(cv2.LINE_4cv2.LINE_8cv2.LINE_AA)。 这样,在绘制这些类型时,您可以更好地看到差异。 请记住,cv2.LINE_AA的质量更好(抗锯齿线型),但是绘制速度比其他两种类型慢。 接下来给出绘制一些文本的关键代码。 该示例的完整代码可以在text_drawing.py脚本中看到:

代码语言:javascript复制
# We draw some text on the image:
cv2.putText(image, 'Mastering OpenCV4 with Python', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_4)
cv2.putText(image, 'Mastering OpenCV4 with Python', (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_8)
cv2.putText(image, 'Mastering OpenCV4 with Python', (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.9, colors['red'], 2, cv2.LINE_AA)

# Show image:
show_with_matplotlib(image, 'cv2.putText()')

在下一个屏幕截图中,您可以看到结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNV7A12P-1681870288360)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/efe34abf-8583-43eb-9fe1-8247e97fe3bc.png)]

在此示例中,背景色设置为白色。 要执行此函数,您可以执行以下操作:

代码语言:javascript复制
image.fill(255)

使用所有 OpenCV 文本字体

OpenCV 中所有可用的字体如下:

  • FONT_HERSHEY_SIMPLEX = 0
  • FONT_HERSHEY_PLAIN = 1
  • FONT_HERSHEY_DUPLEX = 2
  • FONT_HERSHEY_COMPLEX = 3
  • FONT_HERSHEY_TRIPLEX = 4
  • FONT_HERSHEY_COMPLEX_SMALL = 5
  • FONT_HERSHEY_SCRIPT_SIMPLEX = 6
  • FONT_HERSHEY_SCRIPT_COMPLEX = 7

与此相关,我们对text_drawing_fonts.py脚本进行了编码,该脚本绘制了所有可用字体。 由于所有这些字体都在(0-7)范围内,因此我们可以迭代并调用cv2.putText()函数,从而更改colorfontFaceorg参数。 我们还绘制了这些字体的小写和大写版本。 执行此函数的关键代码如下:

代码语言:javascript复制
position = (10, 30)
for i in range(0, 8):
    cv2.putText(image, fonts[i], position, i, 1.1, colors[index_colors[i]], 2, cv2.LINE_4)
    position = (position[0], position[1]   40)
    cv2.putText(image, fonts[i].lower(), position, i, 1.1, colors[index_colors[i]], 2, cv2.LINE_4)
    position = (position[0], position[1]   40)

生成的屏幕截图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fc9NU6sj-1681870288360)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/b2c0c223-2e0a-4c25-b777-6151ce1a7ee6.png)]

在上一个屏幕截图中,您可以看到 OpenCV 中所有可用的字体(小写和大写版本)。 因此,您可以将此屏幕截图用作参考,以轻松检查要在项目中使用的字体。

与文字相关的更多函数

OpenCV 提供了更多与文本绘制有关的函数。 应当注意,这些函数不是用于绘制文本,而是可以用于对上述cv2.putText()函数进行补充,它们的评论如下。 我们要看到的第一个函数是cv2.getFontScaleFromHeight()。 该函数的签名如下:

代码语言:javascript复制
retval = cv2.getFontScaleFromHeight(fontFace, pixelHeight, thickness=1)

此函数返回字体比例(fontScale),这是在cv2.putText()函数中使用的参数,以达到所提供的高度(以像素为单位),并且同时考虑了字体类型(fontFace)和thickness

第二个函数是cv2.getTextSize()

代码语言:javascript复制
retval, baseLine = cv2.getTextSize(text, fontFace, fontScale, thickness)

此函数可用于基于以下参数获取文字大小(宽度和高度):text,字体类型fontFacescalethickness。 此函数返回sizebaseLine,它们对应于基线相对于文本底部的y坐标。 下一段代码向您展示了查看此函数的关键方面。 完整代码可在text_drawing_bounding_box.py脚本中找到:

代码语言:javascript复制
# assign parameters to use in the drawing functions
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 2.5
thickness = 5
text = 'abcdefghijklmnopqrstuvwxyz'
circle_radius = 10

# We get the size of the text
ret, baseline = cv2.getTextSize(text, font, font_scale, thickness)

# We get the text width and text height from ret
text_width, text_height = ret

# We center the text in the image
text_x = int(round((image.shape[1] - text_width) / 2))
text_y = int(round((image.shape[0]   text_height) / 2))

# Draw this point for reference:
cv2.circle(image, (text_x, text_y), circle_radius, colors['green'], -1)

# Draw the rectangle (bounding box of the text)
cv2.rectangle(image, (text_x, text_y   baseline), (text_x   text_width - thickness, text_y - text_height),
              colors['blue'], thickness)

# Draw the circles defining the rectangle
cv2.circle(image, (text_x, text_y   baseline), circle_radius, colors['red'], -1)
cv2.circle(image, (text_x   text_width - thickness, text_y - text_height), circle_radius, colors['cyan'], -1)

# Draw the baseline line
cv2.line(image, (text_x, text_y   int(round(thickness/2))), (text_x   text_width - thickness, text_y  
                                                             int(round(thickness/2))), colors['yellow'], thickness)
# Write the text centered in the image
cv2.putText(image, text, (text_x, text_y), font, font_scale, colors['magenta'], thickness)

下一个屏幕截图给出了此示例的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbakeFhT-1681870288360)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/c983ac3d-aef4-4fcb-860f-1905be659998.png)]

注意如何绘制三个小点(redcyangreen),以及如何显示黄色基线。

使用鼠标事件的动态绘图

在本节中,您将学习如何使用鼠标事件执行动态绘图。 我们将以复杂度递增的顺序来查看一些示例。

绘制动态形状

下一个示例向您介绍如何使用 OpenCV 处理鼠标事件。 cv2.setMouseCallback()函数执行此功能。 此方法的签名如下:

代码语言:javascript复制
cv2.setMouseCallback(windowName, onMouse, param=None)

此函数为名为windowName的窗口建立鼠标处理器。 onMouse函数是回调函数,当执行鼠标事件时(例如,双击,按下鼠标左键,按下鼠标左键等),将调用该函数。 可选的param参数用于将其他信息传递给callback函数。

因此,第一步是创建callback函数:

代码语言:javascript复制
# This is the mouse callback function:
def draw_circle(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDBLCLK:
        print("event: EVENT_LBUTTONDBLCLK")
        cv2.circle(image, (x, y), 10, colors['magenta'], -1)

    if event == cv2.EVENT_MOUSEMOVE:
        print("event: EVENT_MOUSEMOVE")

    if event == cv2.EVENT_LBUTTONUP:
        print("event: EVENT_LBUTTONUP")

    if event == cv2.EVENT_LBUTTONDOWN:
        print("event: EVENT_LBUTTONDOWN")

draw_circle()函数接收特定事件以及每个鼠标事件的坐标(x, y)。 在这种情况下,当执行左键双击(cv2.EVENT_LBUTTONDBLCLK)时,我们在事件的相应(x, y)坐标中绘制一个圆。

此外,我们还打印了一些消息以查看其他产生的事件,但是我们不使用它们执行任何其他操作。

下一步是创建一个命名窗口。 在这种情况下,我们将其命名为Image mouse。 此命名窗口是鼠标回调函数将与之关联的位置:

代码语言:javascript复制
# We create a named window where the mouse callback will be established
cv2.namedWindow('Image mouse')

最后,我们将鼠标回调函数设置(或激活)为之前创建的函数:

代码语言:javascript复制
# We set the mouse callback function to 'draw_circle'
cv2.setMouseCallback('Image mouse', draw_circle)

总之,当执行左双击时,将以执行的双击的(x, y)位置为中心绘制一个填充的洋红色圆圈。 该示例的完整代码可以在mouse_drawing.py脚本中看到。

绘制文字和形状

在此示例中,我们将鼠标事件和图形文本结合在一起。 从这种意义上讲,将渲染一些文本以显示如何使用鼠标事件来执行特定操作。 为了更好地理解此示例,在下一个屏幕截图中,您可以看到渲染的文本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JguFXHOQ-1681870288360)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/f6f2ab83-c6a4-4d23-a7f0-1e13081b3fb8.png)]

您可以执行以下操作:

  • 使用鼠标左键双击添加一个圆
  • 只需单击鼠标左键即可删除最后添加的圆圈
  • 双击右键删除所有圆圈

为了执行此功能,我们创建了一个名为circles的列表,其中维护着用户选择的当前圈子。 此外,我们还使用渲染的文本创建了一个备份图像。 产生鼠标事件时,我们从circles列表中添加或删除圆圈。 然后,在绘制时,我们仅绘制列表中的当前圆。 因此,例如,当用户执行简单的右键单击时,最后一个添加的圆圈将从列表中删除。 完整代码在mouse_drawing_circles_and_text.py脚本中提供。

Matplotlib 事件处理

您可以在前面的示例中看到我们没有使用 Matplotlib 来显示图像。 这是因为 Matplotlib 也可以处理事件处理和选择。 因此,您可以使用 Matplotlib 功能捕获鼠标事件。 我们可以使用 Matplotlib 连接更多事件。 例如,关于鼠标,我们可以连接以下事件-button_press_eventbutton_release_eventmotion_notify_eventscroll_event

我们将展示一个简单的示例,以便在与button_press_event事件相关的单击鼠标时渲染一个圆形:

代码语言:javascript复制
# 'button_press_event' is a MouseEvent where a mouse botton is click (pressed)
# When this event happens the function 'click_mouse_event' is called:
figure.canvas.mpl_connect('button_press_event', click_mouse_event)

我们还必须为button_press_event事件定义事件监听器:

代码语言:javascript复制
# We define the event listener for the 'button_press_event':
def click_mouse_event(event):
    # (event.xdata, event.ydata) contains the float coordinates of the mouse click event:
    cv2.circle(image, (int(round(event.xdata)), int(round(event.ydata))), 30, colors['blue'], cv2.FILLED)
    # Call 'update_image()' method to update the Figure:
    update_img_with_matplotlib()

因此,执行鼠标单击时,将显示blue圆圈。 应当指出,我们已经对update_img_with_matplotlib()函数进行了编码。 在前面的示例中,我们使用了show_with_matplotlib()show_with_matplotlib()函数用于使用 Matplotlib 显示图像,而update_img_with_matplotlib()用于更新现有图形。 该示例的完整代码可以在matplotlib_mouse_events.py脚本中看到。

高级绘图

在本节中,我们将看到如何结合上述某些函数以在 OpenCV 中绘制基本形状(例如,线,圆,矩形和文本等)以呈现更高级的绘图。 为了将所有这些部分放在一起,我们构建了一个模拟时钟来向您显示当前时间(小时,分钟和秒)。 为此,编写了两个脚本:

  • analog_clock_values.py
  • analog_clock_opencv.py

analog_clock_opencv.py脚本使用cv.line()cv.circle()cv.rectangle()cv2.putText()绘制模拟时钟。 在此脚本中,我们首先绘制静态图形。 从这个意义上讲,您可以看到有两个数组包含固定的坐标:

代码语言:javascript复制
hours_orig = np.array(
    [(620, 320), (580, 470), (470, 580), (320, 620), (170, 580), (60, 470), (20, 320), (60, 170), (169, 61), (319, 20),
     (469, 60), (579, 169)])

hours_dest = np.array(
    [(600, 320), (563, 460), (460, 562), (320, 600), (180, 563), (78, 460), (40, 320), (77, 180), (179, 78), (319, 40),
     (459, 77), (562, 179)])

这些数组是绘制小时标记所必需的,因为它们定义了时钟每一小时的线条的起点和终点。 因此,这些标记绘制如下:

代码语言:javascript复制
for i in range(0, 12):
    cv2.line(image, array_to_tuple(hours_orig[i]), array_to_tuple(hours_dest[i]), colors['black'], 3)

此外,绘制了一个大圆圈,对应于模拟时钟的形状:

代码语言:javascript复制
cv2.circle(image, (320, 320), 310, colors['dark_gray'], 8)

最后,我们绘制包含Mastering OpenCV 4 with Python 文本的矩形,该文本将在时钟内渲染:

代码语言:javascript复制
cv2.rectangle(image, (150, 175), (490, 270), colors['dark_gray'], -1)
cv2.putText(image, "Mastering OpenCV 4", (150, 200), 1, 2, colors['light_gray'], 1, cv2.LINE_AA)
cv2.putText(image, "with Python", (210, 250), 1, 2, colors['light_gray'], 1, cv2.LINE_AA)

在图像中绘制出此静态信息后,我们将其复制到image_original图像中:

代码语言:javascript复制
image_original = image.copy()

要绘制动态信息,执行几个步骤:

  1. 获取当前时间的小时,分​​钟和秒:
代码语言:javascript复制
# Get current date:
date_time_now = datetime.datetime.now()
# Get current time from the date:
time_now = date_time_now.time()
# Get current hour-minute-second from the time:
hour = math.fmod(time_now.hour, 12)
minute = time_now.minute
second = time_now.second
  1. 将这些值(小时,分钟和秒)转换为角度:
代码语言:javascript复制
# Get the hour, minute and second angles:
second_angle = math.fmod(second * 6   270, 360)
minute_angle = math.fmod(minute * 6   270, 360)
hour_angle = math.fmod((hour*30)   (minute/2)   270, 360)
  1. 绘制与时针,分针和秒针相对应的线:
代码语言:javascript复制
# Draw the lines corresponding to the hour, minute and second needles:
second_x = round(320   310 * math.cos(second_angle * 3.14 / 180))
second_y = round(320   310 * math.sin(second_angle * 3.14 / 180))
cv2.line(image, (320, 320), (second_x, second_y), colors['blue'], 2)

minute_x = round(320   260 * math.cos(minute_angle * 3.14 / 180))
minute_y = round(320   260 * math.sin(minute_angle * 3.14 / 180))
cv2.line(image, (320, 320), (minute_x, minute_y), colors['blue'], 8)

hour_x = round(320   220 * math.cos(hour_angle * 3.14 / 180))
hour_y = round(320   220 * math.sin(hour_angle * 3.14 / 180))
cv2.line(image, (320, 320), (hour_x, hour_y), colors['blue'], 10)
  1. 最后,绘制一个小圆圈,对应于三个针的连接点:
代码语言:javascript复制
cv2.circle(image, (320, 320), 10, colors['dark_gray'], -1)

在下一个屏幕截图中,您可以看到模拟时钟的外观:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fohpZhTs-1681870288361)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/01aac5bd-2bdb-47b5-a458-47faf6b586e6.png)]

script analog_clock_values.py脚本计算hours_orighours_dest数组的固定坐标。 要计算小时标记的(x, y)坐标,我们使用圆的参数方程式,如下面的屏幕截图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2gP6bI2v-1681870288361)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/41cde822-aefe-4a8d-a970-15d8ce511614.png)]

我们已按照上一个屏幕快照中的公式计算了每30度并从0度开始的 12 个点P(x, y)的坐标(0306090120150180210240270300330)具有两个不同的半径。 这样,我们可以为定义小时标记的线定义坐标。 计算这些坐标的代码如下:

代码语言:javascript复制
radius = 300
center = (320, 320)

for x in (0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330):
    x_coordinate = center[0]   radius * math.cos(x * 3.14/180)
    y_coordinate = center[1]   radius * math.sin(x * 3.14/180)
    print("x: {} y: {}".format(round(x_coordinate), round(y_coordinate)))

for x in (0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330):
    x_coordinate = center[0]   (radius - 20) * math.cos(x * 3.14/180)
    y_coordinate = center[1]   (radius - 20) * math.sin(x * 3.14/180)
    print("x: {} y: {}".format(round(x_coordinate), round(y_coordinate)))

该脚本的完整代码可以在analog_clock_values.py中看到。 应该注意的是,我们可能已经在其他脚本中包含了用于计算这些坐标的代码,但这对您来说是一个很好的练习。

总结

在本章中,我们回顾了 OpenCV 提供的与图形形状和文本有关的功能。 关于形状,我们已经看到了如何绘制非常基本的形状(直线,矩形和圆形),以及更高级的形状(直线,箭头,椭圆和多边形)。 在文本方面,我们已经看到了如何绘制文本以及如何在 OpenCV 库中呈现所有可用字体。 此外,我们还介绍了如何捕获鼠标事件并使用它们执行特定的操作(例如,绘制与执行的鼠标事件的(x, y)坐标关联的点)。 最后,我们绘制了一个模拟时钟,试图总结本章的所有先前概念。

在下一章中,我们将看到有关图像处理技术的主要概念。 我们还将解决如何执行基本的图像转换(例如,平移,旋转,调整大小,翻转和裁剪)。 另一个关键方面是如何对图像执行基本算术,例如按位运算(AND,OR,XOR 和 NOT)。 最后,我们将介绍主要的颜色空间和颜色图。

问题

  1. 您应该正确配置哪个参数以绘制填充形状(例如,圆形或矩形)?
  2. 您应该正确配置哪个参数以绘制抗锯齿线型?
  3. 创建一条从(0, 0)开始到(512, 512)结束的对角线。
  4. 使用所需的参数呈现文本Hello OpenCV
  5. 使用 12 个点绘制一个多边形(圆形)。
  6. 双击时使用鼠标事件和 Matplotlib 事件绘制一个矩形。
  7. 尝试使用lenna.png图像作为背景绘制这个非常简单的模因生成器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4NEb6YAC-1681870288361)(https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/master-opencv4-py/img/7cc8c802-14ef-46b8-ad18-6bef75365775.png)]

0 人点赞