1.1 openCv简介
-
官方文档地址:https://docs.opencv.org/master/index.html
其中包含C++,python等多种语言版本的文档
-
opencv+visual studio环境配置:https://docs.opencv.org/master/dd/d6e/tutorial_windows_visual_studio_opencv.html(官方)
1.2 装载、显示和存储图像
- 实现过程
//引入所需类和函数的头文件
#include <opencv2/core.hpp> //core.hpp包含图像数据结构的核心头文件
#include <opencv2/highgui.hpp> //highgui.hpp包含了所有图形接口函数头文件
#include<iostream> //标准输入输出头文件
int main() {
cv::Mat image; // 定义一个Mat类对象
std::cout << "This image is " << image.rows << " x "
<< image.cols << std::endl; //访问Mat类对象的属性
image = cv::imread("壁纸1.jpg"); //读取输入图像
//判断图像读取是否正确
if (image.empty()) {
return -1;
}
cv::namedWindow("original image"); //定义一个窗口
cv::imshow("original image", image); //让图像在指定的窗口中显示出来
cv::Mat result; //创建另外一个空的图像
cv::flip(image, result, 0); //对图像进行翻转// 正数表示水平
// 0 表示垂直
// 负数表示水平和垂直
cv::namedWindow("Output Image"); // 输出窗口
cv::imshow("Output Image", result);
cv::waitKey(0); //0 表示永远地等待按键, 键入的正数表示等待的毫秒数
cv::imwrite("output.bmp", result); // 保存结果
return 0;
}
- 实现原理
命名空间 cv:
在OpenCV 的C++ API 中,所有类和函数都在命名空间cv 内定义。
访问它们的方法共有两种,第一种是在定义main 函数前使用如下声明:using namespace cv;
第二种方法是根据命名空间规范给所有OpenCV 的类和函数加上前缀:cv::
图像读入:
// 读入一个图像文件并将其转换为灰度图像
image= cv::imread("puppy.bmp", CV::IMREAD_GRAYSCALE);
// 读取图像,并将其转换为三通道彩色图像
image= cv::imread("puppy.bmp", CV::IMREAD_COLOR);
std::cout << "This image has "
<< image.channels() << " channel(s)"; //可用channels 方法检查图像的通道数
- 在图像上点击、在图像上绘图(具体查阅官方文档)
通过编程,你可以让鼠标在置于图像窗口上时运行特定的指令。要实现这个功能,需定义一个合适的回调函数??
OpenCV 还提供了几个用于在图像上绘制形状和写入文本的函数。基本的形状绘制函数有circle、ellipse、line 、rectangle??
1.3 cv::Mat数据结构
- cv::Mat数据结构的不同属性
.cols
、 .rows
- cv::Mat 类的组成
矩阵头:包括矩阵尺寸、存储方法、存储地址等信息
指针:指向存储所有像素值的矩阵,矩阵的维度取决于选择的存储方法
矩阵头的大小是恒定的,但是矩阵本身的大小可能因图像而异,并且通常会大几个数量级。
- 引用计数机制
每个 Mat 对象都有自己的信息头,但是通过让矩阵指针指向相同的地址,可以在两个Mat对象之间共享矩阵。此外,拷贝构造函数和赋值运算符只会复制信息头和指向大矩阵的指针,而不是数据本身。
cv::Mat A, C; //只创建信息头部分
A = cv::imread("壁纸1.jpg", cv::IMREAD_COLOR); //分配矩阵
cv::Mat B(A); //使用拷贝构造函数
C = A; //使用赋值运算符
以上代码所有对象都最终指向同一个数据矩阵,使用它们中的任何一个进行修改也会影响所有其他对象。 实际上,不同的对象只是为相同的底层数据提供了不同的访问方法,尽管它们的信息头是不同的。真正有趣的部分是您可以创建仅引用完整数据的一部分的信息头,比如想要创建一个感兴趣区域(ROI),只需要创建包含边界信息的信息头:
Mat D (A, Rect(10, 10, 100, 100) ); // 使用矩阵来界定 using a rectangle
Mat E = A(Range::all(), Range(1,3)); //使用行和列来界定 using row and column boundarie
**引用计数机制概念:**无论什么时候复制一个Mat对象的信息头,计数器将会加1;当一个信息头被释放时,计数器将会减1;当计数值为0时,矩阵将会被清理。
有时你也想复制矩阵本身,所以 OpenCV 提供了 **cv::Mat::clone() **和 **cv::Mat::copyTo() **函数,如下所示:
cv::Mat F = A.clone(); //复制A给F,包含矩阵本身
cv::Mat G;
A.copyTo(G);//复制A给G,包含矩阵本身
现在修改 F 或 G 不会影响 A信息头指向的矩阵。 你需要记住的是:
-
OpenCV 函数中输出图像的内存分配是自动的(除非另有说明)。
-
您无需考虑使用 OpenCV 的 C++ 接口进行内存管理。
-
赋值运算符和复制构造函数只复制信息头和矩阵指针。
-
可以使用 cv::Mat::clone() 和 cv::Mat::copyTo() 函数复制图像的底层矩阵。
1.4 cv::Mat的创建
- cv::Mat的显式创建
#include <opencv2/core.hpp> //core.hpp包含图像数据结构的核心头文件
#include <opencv2/highgui.hpp> //highgui.hpp包含了所有图形接口函数头文件
//引入所需类和函数的头文件
int main() {
cv::Mat image1(240, 320, CV_8U, 100);
//创建一个240行x320列的新图像
// CV_8U指定每个矩阵元素的类型
// 100表示灰度值
cv::Mat image2(240, 320, CV_8UC3, cv::Scalar(0, 0, 255));
// 创建一个红色图像,通道次序是BGR
// CV_8UC3,表示8位,无符号,通道数为3
//初始化灰度图像可这样使用这个数据结构:cv::Scalar(100)
cv::Mat image3(cv::Size(320, 240), CV_8UC3);
// 创建一个未初始化的彩色图像
// cv::Size()结构包含了矩阵的高度和宽度
image1.create(200, 200, CV_8U);//使用create方法分配或重新分配图像的数据
//(仅在大小或类型不同时)重新分配
cv::Mat image4(image3); //拷贝构造函数
image1 = image3; //赋值运算符
// 所有图像都指向同一个数据块
image3.copyTo(image2);
cv::Mat image5 = image3.clone();
// 这些图像是原始图像的新副本
image1.convertTo(image2, CV_32F, 1 / 255.0, 0.0);
//将image1转换为浮点型图像[0,1],image2为转换的结果
return 0;
}
- OpenCV中还有几个与cv::Mat相关的类
输入和输出数组:
在OpenCV 的文档中,很多方法和函数都使用cv::InputArray 类型作为输入参数。因为它是一个输入数组,所以你必须确保函数不会修改这个数据结构。
此外还有一个代理类cv::OutputArray,用来指定某些方法或函数的返回数组。
处理小矩阵:
开发应用程序时,你可能会遇到需要处理小矩阵的情况,这时就可以使用模板类cv::Matx和它的子类
// // 3×3 双精度型矩阵
cv::Matx33d matrix(3.0, 2.0, 1.0,
2.0, 1.0, 3.0,
1.0, 2.0, 3.0);
// 3×1 矩阵(即向量)
cv::Matx31d vector(5.0, 1.0, 3.0);
// 相乘
cv::Matx31d result = matrix*vector;