OpenCV实战(2)——OpenCV核心数据结构

  • 0. 前言
  • 1. cv::Mat 数据结构
    • 1.1 cv::Mat 简介
    • 1.2 cv::Mat 属性
    • 1.3 完整代码示例
  • 2. 探索 cv::Mat 数据结构
    • 2.1 cv::Mat 对象的创建
    • 2.2 OpenCV 输入和输出数组
  • 小结
  • 系列链接

0. 前言

cv::Mat 类是用于保存图像(以及其他矩阵数据)的数据结构,该数据结构是所有 OpenCV 类和函数的核心,这是 OpenCV 库的一个关键元素,用于处理图像和矩阵(从计算和数学的角度来看,图像本质上是一个矩阵),同时 cv::Mat 数据结构结合了优雅的内存管理机制,因此,使用起来十分高效。此数据结构在应用程序开发中广泛使用,因此再进一步学习前我们必须熟悉 cv::Mat 数据结构。

1. cv::Mat 数据结构

1.1 cv::Mat 简介

cv::Mat 数据结构基本上由两部分组成:标头 (header) 和数据块 (data block)。标头包含与矩阵相关的所有信息(尺寸大小、通道数、数据类型等)。我们已经介绍了如何访问包含在 cv::Mat 标头中的一些属性,例如,通过使用 colsrowschannels 访问矩阵的列数、行数或通道数;数据块包含图像的所有像素值,标头中包含一个指向这个数据块的指针变量,即 data 属性。cv::Mat 数据结构的一个重要特性是内存块仅在明确请求时才被复制,大多数操作只是简单地复制 cv::Mat 标头,这样多个对象本质上同时指向同一个数据块,这种内存管理模型使应用程序更高效,同时可以避免内存泄漏。

1.2 cv::Mat 属性

接下来,我们通过编写测试程序 (mat.cpp) 来测试 cv::Mat 数据结构的不同属性。

1. 首先,声明 opencv 头文件和 c++ i/o 流文件:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

2. 创建函数来生成新的灰度图像,其所有像素具有相同的默认值:

cv::Mat function() {// 创建新图像并返回cv::Mat ima(500, 500, CV_8U, 50);return ima;
}

默认情况下, cv::Mat 对象在创建时的大小为零,但也可以指定初始大小:

cv::Mat image1(240,320,CV_8U,100);

如果指定图像初始大小,就需要指定每个矩阵元素的类型,以上代码使用 CV_8U 类型,对应创建的图像像素为 1 字节 (8 位),字母 U 表示它是无符号的;还可以使用字母 S 声明有符号整数类型。对于彩色图像,需要指定三个通道 (CV_8UC3),也可以声明大小为 1632 的整数(同样可以为有符号或无符号)图像,例如,CV_16SC3 表示 16 位有符号整数类型。除了整数类型,还可以使用 32 位或 64 位浮点数,例如,CV_32F 表示 32 位浮点数。

3. 在主函数中,创建六个窗口来显示图像处理结果:

cv::namedWindow("Image 1");
cv::namedWindow("Image 2");
cv::namedWindow("Image 3");
cv::namedWindow("Image 4");
cv::namedWindow("Image 5");
cv::namedWindow("Image");

4. 创建多个不同的图像矩阵(具有不同的尺寸、通道和默认值),并等待按键被按下:

// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值
cv::Mat image1(240,320,CV_8U,100);
cv::imshow("Image", image1); // 显示图像
cv::waitKey(0);
// 当尺寸或数据类型不同时,需要重新分配内存
image1.create(200,200,CV_8U);
image1 = 200;
cv::imshow("Image", image1); // 显示图像
cv::waitKey(0);
// 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGR
cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));
// 也可以使用以下方法定义
// cv::Mat image2(cv::Size(320,240),CV_8UC3);
// image2= cv::Scalar(0,0,255);
cv::imshow("Image", image2); // 显示图像
cv::waitKey(0);

图像(或矩阵)的每个元素可以由多个值组成(例如,彩色图像具有三个通道,因此每个坐标点都有三个像素值),因此,OpenCV 引入了一种简单的数据结构,用于将像素值传递给函数,即 cv::Scalar 结构体,一般用来保存一个值或者三个值。例如,要创建一个用红色像素初始化的彩色图像:

cv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));

同样,灰度图像的初始化也可以通过使用此结构完成 (cv::Scalar(100))。
图像尺寸大小通常也需要作为参数传递给函数,我们已经知道 colsrows 属性可用于获取 cv::Mat 实例的维度。尺寸大小信息也可以通过 cv::Size 结构提供,它只包含矩阵的高度和宽度,size() 方法可以获取当前矩阵大小。cv::Size 结构是许多必须指定矩阵大小的方法中常用的格式。例如,使用以下方式创建图像:

cv::Mat image2(cv::Size(320,240),CV_8UC3);

5. 使用 imread 函数读取图像并将其复制到另一个图像矩阵:

// 读取图像
cv::Mat image3 =  cv::imread("1.png");
// 令 image4 和 image1 指向同一个数据块 image3
cv::Mat image4(image3);
image1 = image3;
// image2 和 image5 是 image3 的副本
image3.copyTo(image2);
cv::Mat image5 = image3.clone();

6. 对复制后的图像应用图像转换函数 (cv::flip()),显示创建的所有图像,然后等待按键:

// 水平翻转图像
cv::flip(image3,image3,1);
// 检查图像
cv::imshow("Image 3", image3);
cv::imshow("Image 1", image1);
cv::imshow("Image 2", image2);
cv::imshow("Image 4", image4);
cv::imshow("Image 5", image5);
cv::waitKey(0);

7. 使用创建的函数来生成一个新的灰色图像:

// 使用 function 函数创建灰度图像
cv::Mat gray = function();
cv::imshow("Image", gray); // 显示图像
cv::waitKey(0);

8. 加载一个彩色图像,在加载过程中将其转换为灰度图像,然后,将其值转换为浮点矩阵:

// 将图像读取为灰度图像
image1=  cv::imread("1.png", cv::IMREAD_GRAYSCALE);
// 将图像转换为值在 [0, 1] 区间内的浮点图像
image1.convertTo(image2,CV_32F,1/255.0,0.0);
cv::imshow("Image", image2); // 显示图像

1.3 完整代码示例

完整代码 (mat.cpp) 如下所示:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>cv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;
}int main() {// 创建一个尺寸为 240x320 的图像,创建时直接定义像素值cv::Mat image1(240,320,CV_8U,100);cv::imshow("Image", image1); // 显示图像cv::waitKey(0); // 当尺寸或数据类型不同时,需要重新分配内存image1.create(200,200,CV_8U);image1 = 200;cv::imshow("Image", image1); // 显示图像cv::waitKey(0); // 创建一个由红色填充的图像,OpenCV 中色彩通道顺序为 BGRcv::Mat image2(240,320,CV_8UC3,cv::Scalar(0,0,255));// 也可以使用以下方法定义// cv::Mat image2(cv::Size(320,240),CV_8UC3);// image2= cv::Scalar(0,0,255);cv::imshow("Image", image2); // 显示图像cv::waitKey(0); // 读取图像cv::Mat image3 =  cv::imread("1.png"); // 令 image4 和 image1 指向同一个数据块 image3cv::Mat image4(image3);image1 = image3;// image2 和 image5 是 image3 的副本image3.copyTo(image2);cv::Mat image5 = image3.clone();// 水平翻转图像cv::flip(image3,image3,1); // 检查图像cv::imshow("Image 3", image3); cv::imshow("Image 1", image1); cv::imshow("Image 2", image2); cv::imshow("Image 4", image4); cv::imshow("Image 5", image5); cv::waitKey(0); // 使用 function 函数创建灰度图像cv::Mat gray = function();cv::imshow("Image", gray); // 显示图像cv::waitKey(0); // 将图像读取为灰度图像image1=  cv::imread("1.png", cv::IMREAD_GRAYSCALE); // 将图像转换为值在 [0, 1] 区间内的浮点图像image1.convertTo(image2,CV_32F,1/255.0,0.0);cv::imshow("Image", image2); // 显示图像// 使用 cv::Matx 创建一个 3x3 的双精度 (double-precision) 矩阵cv::Matx33d matrix(3.0, 2.0, 1.0,2.0, 1.0, 3.0,1.0, 2.0, 3.0);// 使用 cv::Matx 创建一个 3x1 的双精度矩阵 (向量)cv::Matx31d vector(5.0, 1.0, 3.0);// 矩阵相乘cv::Matx31d result = matrix*vector;std::cout << result;cv::waitKey(0); return 0;
}

编译后,运行此程序查看生成图像(生成的纯色图像未列出):

$ g++ mat.cpp -o mat `pkg-config --cflags --libs opencv4`
$ ./mat

2. 探索 cv::Mat 数据结构

2.1 cv::Mat 对象的创建

图像的数据块可以使用 create() 方法分配或重新分配。当图像先前已被分配时,它的旧内容首先被释放,出于效率考虑,如果新分配的尺寸大小和类型与现有的尺寸大小和类型匹配,则不会执行新的内存分配:

image1.create(200,200,CV_8U);

当没有引用指向给定的 cv::Mat 对象时,分配的内存会自动释放,这样可以避免在 C++ 中经常与动态内存分配相关的常见内存泄漏问题,这是 OpenCV 中的一个关键机制,通过让 cv::Mat 类实现引用计数和浅复制来实现。因此,当一个图像分配给另一个图像时,图像数据(即像素)不会被复制,两个图像都将指向同一个内存块,按值传递或按值返回的图像同样也是如此。保留引用计数,以便仅当对图像的所有引用都被销毁或分配给另一个图像时才会释放内存:

cv::Mat image4(image3);
image1= image3;

应用于以上图像 (image3image4image1) 之一的任何变换也将影响其他图像;如果希望创建图像内容的深层副本,需要使用 copyTo() 方法,在这种情况下,将在目标图像上调用 create() 方法;另一种生成图像副本的方法是 clone() 方法,它创建一个相同的新图像:

image3.copyTo(image2);
cv::Mat image5= image3.clone();

如果需要将一个图像复制到另一个不一定具有相同数据类型的图像中,则必须使用 convertTo() 方法:

image1.convertTo(image2,CV_32F,1/255.0,0.0);

以上代码中,源图像 image3 被复制到浮点图像 image2 中。convertTo() 方法包括两个可选参数——比例因子和偏移量。需要注意的是,两个图像的通道数必须相同。
cv::Mat 对象的分配模型还允许我们安全地编写返回图像的函数(或类方法):

cv::Mat function() {// 创建新图像并返回cv::Mat ima(500,500,CV_8U,50);return ima;
}

可以从主函数 main() 中调用这个函数 function()

cv::Mat gray= function();

如果我们调用函数 function(),那么变量 gray 将保存由函数 function() 创建的图像,而无需分配额外的内存。事实上,从 funtion() 返回的 cv::Mat 实例只是 ima 图像的浅拷贝。当 ima 局部变量超出范围时,该变量被释放,但由于关联的引用计数器指示其内部图像数据正在被另一个实例(即变量 gray )引用,因此不会释放其内存块。
需要注意的是,在创建类实例时,不要返回图像的类属性。接下来,我们介绍一个容易出错的实现示例:

class ErrorExample {// 图像属性cv::Mat ima;public:ErrorExample(): ima(240, 320, CV_8U, cv::Scalar(100)){}cv::Mat method() {return ima;}
}

在以上代码中,如果一个函数调用这个类的方法,它会获得一个图像属性的浅拷贝。如果以后这个副本被修改,类属性也会被修改,这会影响类的后续行为(反之亦然),为了避免这类错误,我们应该返回属性的副本。

2.2 OpenCV 输入和输出数组

查看 OpenCV 文档,可以看到许多方法和函数都接受 cv::InputArray 类型的参数作为输入。该类型是一个简单的代理类,引入此类型用于概括 OpenCV 中数组的概念,从而避免重复实现具有不同输入参数类型的相同方法或函数的多个版本,这意味着可以通过提供 cv::Mat 对象或其他兼容类型作为参数,该类只是一个接口,所以不应该在代码中显式地声明它。
cv::InputArray 也可以使用 std::vector 类构造,这意味着这类对象可以用作 OpenCV 方法和函数的输入。其他兼容类型包括 cv::Scalarcv::Vec;相应的,还存在一个 cv::OutputArray 代理类,用于指定某些方法或函数返回的数组。

小结

cv::Mat 数据结构是 OpenCV 中最基础也是最核心的数据结构,通过此数据结构构成了 OpenCV 图像处理所用的复杂类和函数。本节中,我们介绍了 cv::Mat 结构的基本用法,包括如何创建空白/非空白图像、修改图像数据类型以及复制图像内容等操作,并且深入探索了创建 cv::Mat 数据结构时的内存分配方式,最后介绍了 OpenCV 输入和输出数组的两个代理类,包括 cv::InputArraycv::OutputArray

系列链接

OpenCV实战(1)——OpenCV与图像处理基础
OpenCV实战(3)——图像感兴趣区域
OpenCV实战(4)——像素操作
OpenCV实战(5)——图像运算详解
OpenCV实战(6)——OpenCV策略设计模式
OpenCV实战(7)——OpenCV色彩空间转换
OpenCV实战(8)——直方图详解
OpenCV实战(9)——基于反向投影直方图检测图像内容
OpenCV实战(10)——积分图像详解

OpenCV实战(2)——OpenCV核心数据结构相关推荐

  1. 【OpenCV实战】OpenCV实现人脸检测详解(含代码)

    OpenCV中有许多可以进行人脸.人眼检测的特征文件,今天我们利用OpenCV中自带的特征文件haarcascade_frontalface_default.xml来进行人脸检测. [OpenCV实战 ...

  2. OpenCV实战(4)——像素操作

    OpenCV实战(4)--像素操作 0. 前言 1. 图像的基本组成 2. 访问像素值 2.1 修改图像像素 2.2 cv::Mat_ 模板类 2.3 完整代码示例 3. 用指针扫描图像 3.1 图像 ...

  3. OpenCV实战(7)——OpenCV色彩空间转换

    OpenCV实战(7)--OpenCV色彩空间转换 0. 前言 1. RGB 色彩空间 2. 色彩空间转换 2.1 CIE L*a*b* 色彩空间 2.2 其它色彩空间 3. 用色调.饱和度和亮度表示 ...

  4. OpenCV实战(3)——图像感兴趣区域

    OpenCV实战(3)--图像感兴趣区域 0. 前言 1. 感兴趣区域 1.1 ROI 实例 1.2 定义 ROI 2. 使用图像掩码 3. 完整代码示例 小结 系列链接 0. 前言 在实际应用场景下 ...

  5. OpenCV实战(1)——OpenCV与图像处理基础

    OpenCV实战(1)--OpenCV与图像处理基础 0. 前言 1. OpenCV 基础 1.1 安装 OpenCV 1.2 OpenCV 主要模块 1.3 使用 Qt 进行 OpenCV 开发 2 ...

  6. Opencv实战 | 用摄像头自动化跟踪特定颜色物体

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:新机器视觉 1. 导语 在之前的某个教程里,我们探讨了如 ...

  7. 再次升级,985博士整理的71个OpenCV实战项目教程开放下载!

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 近期小白学视觉公众号推出了多篇Python+OpenCV实战项目的 ...

  8. 基于OpenCV实战:3步实现图像降噪

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 在本文中,我们将展示如何通过三个简单的步骤来实现降噪.我们将使用机 ...

  9. 基于OpenCV实战:绘制图像轮廓(附代码)

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 山区和地形图中海拔高的区域划出的线称为地形轮廓,它们提供了地形的高 ...

最新文章

  1. Jackson——来自官网的翻译
  2. 如何解析字符串类型的xml
  3. iOS微博项目(一)
  4. 4qam、16qam、64qam、256qam理论仿真曲线
  5. curl模拟自动登陆采集网页数据
  6. 第一个DFS,第一个递归 HDU1515
  7. Spring MVC中静态资源加载
  8. 微信台配置那服务器,微信配置
  9. 用iPhone打造个人的GTD(Get Things Done)实践
  10. 免费车型车系品牌api
  11. word2016 图片去底灰_Word2016中为图片去除背景的方法
  12. 高通6350:adb抓取Camera RAW/YUV数据
  13. MyBatis学习(一)-- 实现简单查询
  14. 补间动画tween.js
  15. 暗影精灵电脑 开机速度慢的解决方法
  16. 爆火的Java面试题-易语言线程池用法
  17. Linux下的常用编程工具初探
  18. 关于特征值特征向量和矩阵分解的理解总结
  19. Fractal解题笔记
  20. 追MM与Java的23种设计模式(转)_三木_新浪博客

热门文章

  1. 多消费者(多线程)对MNS的使用
  2. C#如何解析UTF-8编码
  3. 关于加减运算时能否使用等价无穷小的问题
  4. C语言——实例005 输入三个整数x,y,z,请把这三个数由小到大输出。
  5. 基于物联网的环境监控系统设计与实现
  6. 百度编辑器(ueditor)魔改:3、高亮提示、搜索(广告词、违禁词等)
  7. ubuntu系统gcc版本切换指导
  8. koa2如何使用https
  9. 批量比对 mysql 字段_MS SQL Server数据库两个库之间相同数据表名内容批量对比方法...
  10. 思科路由器IOS系统和配置文件的备份、删除及还原