OpenCV3学习笔记
一、OpenCV数据类型
- 第一类是直接从C++中继承的基础数据类型(如int和float等)。这些类型包括简单的数组和矩阵,一些简单的几何概念,比如点、矩形、大小等;
- 第二类是辅助对象。这同时也代表些对象代表更抽象的概念, 比如垃圾收集指针类、用于数据切片的范围对象 (range objects) 以及抽象的终止条件类等;
- 第三类可以称为大型数组类型。这些对象原本目的是涵盖数组或一些其他的原语、 程序集或更常见的基础数据类型。这一类的典型代表是cv::Mat类,该类用来代表任意维度的包含任意基础元素的数组。存储图片对象是cv::Mat类的特殊用途。
为快速上手,先从第三类的Mat类开始。
1. 大型数组类型
比较具有代表性的是 cv::Mat和cv::SparseMat 类型。
-
cv::Mat针对的是密集连续性的存储,大多数的图像数据被存储为这种类,即使数据为空,预留的存储空间仍然存在;
-
cv::SparseMat针对的是稀疏的存储方式,只有数据不为0才保留空间,否则不会预留。显然cv::SparseMat存储更为节省空间,典型使用cv::SparseMat的例如直方图数据的存储。
1.1 Mat类
Mat 是一个类,由两个数据部分组成:
-
矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)
-
一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。
1.2 SparseMat类
二、Mat类基础操作
1. 图像的读写
1.1 读取
imread功能是加载图像文件成为一个Mat对象,其中:
第一个参数为图像文件的路径;
第二个参数,表示加载的图像是什么类型,支持常见的三个参数值:
- IMREAD_UNCHANGED (<0) 表示加载原图,不做任何改变
- IMREAD_GRAYSCALE (=0)表示把原图作为灰度图像加载进来
- IMREAD_COLOR (>0) 表示把原图作为RGB图像加载进来
注意:OpenCV支持JPG、PNG、TIFF等常见格式图像文件加载。
同时用到的函数还有:
- namedWindow:功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放,你无需取销毁它。常见用法是:
namedWindow(“Window Title”, WINDOW_AUTOSIZE)。
— WINDOW_AUTOSIZE,会自动根据图像大小,显示窗口大小,不能人为改变窗口大小;
— WINDOW_NORMAL,跟QT集成的时候会使用,允许修改窗口大小。 - destroyWindow:功能是销毁指定的窗口。
- imshow:根据窗口名称显示图像到指定的窗口上去,第一个参数是窗口名称,第二参数是Mat对象
//加载图像
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://1.jpg"); //加载图像
if (image.empty())
{
return -1;
}
namedWindow("Example1", WINDOW_AUTOSIZE); //创建窗口
imshow("Example1", image); //显示图像
waitKey(0);
destroyWindow("Example1"); //销毁窗口
return 0;
}
//加载为灰度图
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://1.jpg", 0); //IMREAD_GRAYSCALE (=0)表示把原图作为灰度图像加载进来
if (image.empty())
{
return -1;
}
namedWindow("Example1", WINDOW_AUTOSIZE);
imshow("Example1", image);
waitKey(0);
destroyWindow("Example1");
return 0;
}
1.2 写入
imwrite功能是保存图像文件到指定目录路径,有三个参数:
-
第一个参数表示需要写入的路径&文件名,必须要加上后缀,比如“123.png”;
-
第二个参数表示Mat类型的图像数据,是你要保存的对象。
-
第三个参数表示为特定格式保存的参数编码,它有一个默认值std::vector< int >(),所以一般情况下不用写。
注意:只有8位、16位的PNG、JPG、Tiff文件格式而且是单通道或者三通道的BGR的图像才可以通过这种方式保存,保存PNG格式的时候可以保存透明通道的图片
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://1.jpg");
if (image.empty())
{
return -1;
}
Mat image_out;
cvtColor(image, image_out, COLOR_BGR2HLS); //转换为灰度图
imwrite("D://1_out.png", image_out); //将转换后image_out的写入到"D://1_out.png"
namedWindow("Output Window", WINDOW_AUTOSIZE);
imshow("Output Window", image_out);
waitKey(0);
destroyWindow("Output Window");
return 0;
}
2. 创建一个Mat类
- Mat对象构造函数:Mat()
- Mat对象构造方法:A.create()
赋值一个全为0的Mat:img = Scalar(0)。
//三种建立Mat类的方式
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** args) {
Mat M;
M.create(4, 3, CV_32FC3);
M = Scalar((126.3928), (12.3334), (246.12));
cout << "M = " << endl << M << endl << endl;
Mat A = Mat(3, 2, CV_16SC3);
A = Scalar(1, 2, 3);
cout << endl << "A = " << endl << A << endl << endl;
Mat D = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "D = " << endl << D << endl << endl;
waitKey(0);
return 0;
}
/*
M =
[126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12]
A =
[1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3]
D =
[0, -1, 0;
-1, 5, -1;
0, -1, 0]
C:\Users\CK Yang\source\repos\LXCV01\x64\Debug\LXCV01.exe (进程 18808)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
*/
}
创建Mat类型的函数十分灵活:
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** args) {
Mat M;
M.create(4, 3, CV_32FC3);
M = Scalar((126.3928), (12.3334), (246.12));
cout << "M = " << endl << M << endl << endl;
Mat A = Mat(3, 2, CV_16SC3);
A = Scalar(1, 2, 3);
cout << endl << "A = " << endl << A << endl << endl;
Mat B = Mat(Size(3, 4), CV_8SC3);
B = Scalar(2, 3, 4);
cout << "B = " << endl << B << endl << endl;
Mat C = Mat(Size(2, 3), CV_16SC2, Scalar(2, 3, 4));
cout << "C = " << endl << C << endl << endl;
Mat D = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "D = " << endl << D << endl << endl;
waitKey(0);
return 0;
}
/*
A =
[1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3;
1, 2, 3, 1, 2, 3]
B =
[ 2, 3, 4, 2, 3, 4, 2, 3, 4;
2, 3, 4, 2, 3, 4, 2, 3, 4;
2, 3, 4, 2, 3, 4, 2, 3, 4;
2, 3, 4, 2, 3, 4, 2, 3, 4]
C =
[2, 3, 2, 3;
2, 3, 2, 3;
2, 3, 2, 3]
D =
[0, -1, 0;
-1, 5, -1;
0, -1, 0]
*/
还可通过imread(“C:/2.jpg”);的方式建立图像的Mat
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** args)
{
Mat image = imread("C:/2.jpg");
if (image.empty())
{
return -1;
}
namedWindow("My Image", WINDOW_AUTOSIZE);
imshow("My Image", image);
Mat M;
M.create(4, 3, CV_32FC3);
M = Scalar((126.3928), (12.3334), (246.12));
cout << "M = " << endl << " " << M << endl << endl;
uchar* firstRow = M.ptr<uchar>(0);
printf("%d\n", *firstRow);
Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
waitKey(0);
cvDestroyAllWindows();
return 0;
}
Mat的type和depth:
例:CV_32FC3——表示的是元素类型是一个32位的浮点数,通道为3。
- 数据类型中:
U(unsigned integer)表示的是无符号整数,
S(signed integer)是有符号整数,
F(float)是浮点数。 - 通道数中:C1,C2,C3,C4则表示通道数是1,2,3,4。
type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用depth:
depth是矩阵中元素的一个通道的数据类型,这个值和type是相关的。
例如: type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。
- depth:将type的值去掉通道信息就是depth值:CV_8U、CV_16S、CV_32F、CV_64F。
- elemSize:矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6
bytes - elemSize1:矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2
bytes = elemSize / channels
常用方法:
- void copyTo(Mat):完全复制;
- Mat clone():完全复制;
- void convertTo(Mat, int type):转换Mat为指定type;
- int type():查看Mat的type;
- size():查看Mat的size;
- int channels():查看Mat的channels;
- int depth():查看Mat的depth;
- bool empty():查看Mat是否为empty;
- uchar* ptr(i=0):像素指针。
注:
部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分
- Mat A= imread(imgFilePath);
- Mat B(A)
完全复制:如果想把Mat对象的头部和数据部分一起复制,可以通过如下两个API实现
- Mat F = A.clone();
- Mat G;
A.copyTo(G);
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** args) {
Mat M;
M.create(4, 3, CV_32FC3);
M = Scalar((126.3928), (12.3334), (246.12));
cout << "M = " << endl << M << endl << endl;
Mat A = Mat(3, 2, CV_16SC3);
A = Scalar(1, 2, 3);
cout << endl << "A = " << endl << A << endl << endl;
Mat B = Mat(Size(3, 4), CV_8SC3);
B = Scalar(2, 3, 4);
cout << "B = " << endl << B << endl << endl;
Mat E;
A.copyTo(E);
cout << "E = " << endl << E << endl << endl;
Mat F;
A.convertTo(F, CV_32FC3);
cout << "F = " << endl << F << endl << endl;
Mat G = B.clone();
cout << "G = " << endl << G << endl << endl;
3. 色彩空间转换
cvtColor( image, gray_image, COLOR_BGR2GRAY )
cvtColor功能是把图像从一个彩色空间转换到另外一个色彩空间,有三个参数:
-
第一个参数表示源图像;
-
第二参数表示色彩空间转换之后的图像;
-
第三个参数表示源和目标色彩空间如:COLOR_BGR2HLS 、COLOR_BGR2GRAY 等。
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://1.jpg");
if (image.empty())
{
return -1;
}
Mat image_out;
cvtColor(image, image_out, COLOR_BGR2HLS); //转换色彩空间为COLOR_BGR2HLS
namedWindow("Output Window", WINDOW_AUTOSIZE);
imshow("Output Window", image_out);
waitKey(0);
destroyWindow("Output Window");
return 0;
}
4. 图像像素指针&掩膜操作(Mask)的实现
Mat.ptr(int i=0) 功能是获取像素矩阵的指针,索引 i 表示行数,从0开始计数。
- 获得当前行指针const uchar* current= myImage.ptr< uchar>(row);
- 获取当前像素点P(row, col)的像素值 p(row, col) = current[col]。
注意:掩膜Mask也被称之为Kernel。
实现一个下图的掩膜操作,功能是提高图像的对比度:
这能干嘛?我下载的某个PDF文档看着比较瞎眼,就可以用这个来让它看着没那么瞎眼。
saturate_cast< uchar>()是像素范围处理函数,功能是确保RGB值的范围在0~255之间。
- saturate_cast(-100),返回 0;
- saturate_cast(288),返回255;
- saturate_cast(100),返回100。
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://2.png");
Mat image_output = Mat::zeros(image.size(), image.type()); //创立一个元素全为0的Mat类型作为输出图像,size和type与输入图像保持一致。
if (image.empty())
{
return -1;
}
namedWindow("Input",WINDOW_AUTOSIZE);
imshow("Input", image);
int cols = image.cols * image.channels();
int rows = image.rows;
for (int row = 1; row < (rows - 1); row++)
{
const uchar* current = image.ptr<uchar>(row); //获取当前行指针
const uchar* next = image.ptr<uchar>(row + 1); //获取下一行指针
const uchar* previous = image.ptr<uchar>(row - 1); //获取上一行指针
uchar* output = image_output.ptr<uchar>(row); //获取想要输出的图像的行指针
for (int col = 1 * image.channels(); col < cols; col++) //为什么要* image.channels()?思考下彩色图像显示的原理。
{
output[col] = saturate_cast<uchar> ( current[col] * 5 - (current[col] - image.channels()) - (current[col] + image.channels()) - next[col] - previous[col] );
//掩膜操作
}
}
namedWindow("Output", WINDOW_AUTOSIZE);
imshow("Output", image_output);
waitKey(0);
destroyWindow("Output");
destroyWindow("INput");
return 0;
}
除了利用图像像素指针进行掩膜操作,还能用OpenCV的api完成同样的操作,操作步骤:
- 定义掩膜:Mat kernel = (Mat_(3,3) << 0, -1, 0, -1, 5, -1, 0, -1,
0); - 调用filter2D( image, image_output, src.depth(), kernel):
其中image和 image_output是Mat类型变量;
src.depth表示位图深度,有32、24、8等,根据图像属性变化;
kernel是刚才定义的掩膜。
#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("C://2.png");
Mat image_output = Mat::zeros(image.size(), image.type());
if (image.empty())
{
return -1;
}
namedWindow("Input", WINDOW_AUTOSIZE);
imshow("Input", image);
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); //定义掩膜
filter2D(image, image_output, image.depth(), kernel); //调用filter2D
namedWindow("Output", WINDOW_AUTOSIZE);