OpenCV 中基本数据结构 Mat、Vec、Scalar、Point、Size、Rect
opencv 常见的数据结构,包括标量、点、向量、区域、Mat
OpenCV 几种常见的基本数据结构?
- Cv: : Vec 一个模板类,主要用于存储数值向量
- Cv: : Scalar 从 Vec 类引出的模板类,是一个可存放 4 个元素的向量,广泛用于传递和读取图像中的像素值
- Cv: : Point 表示 2 维坐标 (x, y)
- Cv: : Size 表示一幅图像或一个矩形的大小。它包含宽、高 2 个成员:width , height,还有一个有用的面积函数 area ()
- Cv: : Rect 用于定义 2 维矩形的模板类,包括:矩形左上角坐标: (x, y),矩形的宽和高: width, height
- Cv: : RotatedRect 一种特殊的矩形称为 RotatedRect。这个类通过中心点,宽度和高度和旋转角度来表示一个旋转的矩形
什么是 OpenCV 的 Mat 类?
- Mat 类是存储图像的数据结构,在 core 模块中定义
- Mat 本质上是由两个数据部分组成的类: 矩阵头和一个指针,指向包含了像素值的矩阵。 矩阵头部的大小是恒定的。然而,矩阵本身的大小因图像的不同而不同,通常是较大的数量级
1
2
3
4Mat A, C; //仅创建了头部
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); //在此我们知道使用的方法(分配矩阵)
Mat B(A); //使用拷贝构造函数
C = A; //赋值运算符
OpenCV 的 Mat 类有哪些常见属性?
- data uchar 型的指针,Mat 类分为了两个部分:矩阵头和指向矩阵数据部分的指针,data 就是指向矩阵数据的指针
- dims 矩阵的维度,例如 5x6 矩阵是二维矩阵,则 dims=2,三维矩阵 dims=3.
- rows 矩阵的行数
- cols 矩阵的列数
- size 矩阵的大小,size (cols,rows), 如果矩阵的维数大于 2,则是 size (-1,-1)
- channels 矩阵元素拥有的通道数,例如常见的彩色图像,每一个像素由 RGB 三部分组成,则 channels = 3
- OpenCV Mat 类的 type 表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为 CV_(位数)+(数据类型)+(通道数)
- depth 矩阵中元素的一个通道的数据类型,这个值和 type 是相关的, 将 type 的预定义值去掉通道信息就是 depth 值
- elemSize 矩阵一个元素占用的字节数,例如:type 是 CV_16SC3,那么 elemSize = 3 * 16 / 8 = 6 bytes
- elemSize1 矩阵元素一个通道占用的字节数,例如:type 是 CV_16CS3,那么 elemSize1 = 16 / 8 = 2 bytes = elemSize /channels
OpenCV 中 Mat 数据在内存的组织方式?
- 单通道数据按排列顺序存储;多通道数据按通道顺序 - 排列顺序存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43// 2x2x3Mat
cv::Mat listMat[3];
cv::Mat channeMatl = (cv::Mat_(2, 2, 1) << 1.1, 2.1, 3.1, 4.1);
cv::Mat channeMat2 = (cv::Mat_(2, 2, 1) << 1.2, 2.2, 3.2, 4.2);
cv::Mat channeMat3 = (cv::Mat_(2, 2, 1) << 1.3, 2.3, 3.3, 4.3);
listMat[0] = channeMatl.clone();
listMat[1] = channeMat2.clone();
listMat[2] = channeMat3.clone();
// 多通道数据按像素位置打印
cv::Mat mergeMat;
cv::merge(listMat, 3, mergeMat);
std::vector chann{ "通道1","通道2","通道3" };
for (int c = 0; c < mergeMat.channels(); c++) {
std::cout << "-----------"<(h, w)).val[c]) << ",";
}
std::cout << "" << std::endl;
}
std::cout << "---------------------------" << std::endl;
}
// 按存储顺序打印
float* matData = (float*)mergeMat.data;
for (int i = 0; i < mergeMat.cols * mergeMat.rows * mergeMat.channels(); i++) {
std::cout << matData[i] << ",";
}
//-----------------输出-------------------------
-----------通道1-----------
1.1,2.1,
3.1,4.1,
---------------------------
-----------通道2-----------
1.2,2.2,
3.2,4.2,
---------------------------
-----------通道3-----------
1.3,2.3,
3.3,4.3,
---------------------------
1.1,1.2,1.3,2.1,2.2,2.3,3.1,3.2,3.3,4.1,4.2,4.3,
opencv 的 ones 生成 Mat 对象的注意事项?
- ones 生成多通道的 Mat 对象时,只有第一个通道的数据为 1,其他通道为 0
1
2
3cv::Mat m = cv::Mat::ones(2,2CV_8UC3);
# [1,0,0] [1,0,0]
# [1,0,0] [1,0,0]
认识 OpenCV 的 Mat 类的 type 值?
- Mat 的 type 命名规则为:CV_(位数)+(数据类型)+(通道数)
C++ 上 OpenCV 的 convertTo 函数?
- 用于转换 Mat 的数据类型,需要提供 3 个参数:数据类型、alpha、beta,计算时先进行以下公式计算,然后再转换数据类型 $$DstMat(x,y)=SrcMat(x,y)\times \alpha + \beta$$
- 转换前需要观察前者的数据范围,比如同样是 float 32 转 uint 8,如果其被归一化了,需要设置 alpha=255.0,否则使用默认值
1
2
3
4
5
6
7// CV32F转CV8U
cv::Mat matTemp = cv::Mat::zeros(100,100,CV_32F); //得到一个浮点型的100*100的矩阵
cv::Mat MatTemp2;
//把矩阵matTemp转为unsing char类型的矩阵,注在转换过程中有可能数值上会出现一些变化,这个要注意
matTemp.convertTo(MatTemp2, CV_8U);
//将[0,1]范围内的浮点表示的图像转换成8bit整数表示的图像
float_image.convertTo(integer_image, CV_8U, 255.0); - 注意:这函数转换时,会进行四舍五入,假设 Python 使用 numpy 的 astype 转换数据类型,会发现和使用 C++ 使用 opencv 的 convertTo 差 1,将 astype 改为 round 即可
C++ 上 OpenCV 进行数据转换前遵循的原则?
- 在从小的数据范围转向大的数据范围时,必须先改变类型
1
2
3// CV_8U转为CV_16U
mat.convertTo(mat,CV16U); //代表的是16U,注意,转类型的时候不需要考虑通道
mat = mat * 257; //8bit的数据乘以257就转为了16bit - 整数类型转小数类型时,先转类型再转数据
1
2
3
4//先转为32F
img.convertTo(img, CV32F);
//再转为小数
img = img / 65535;
C++ 上 OpenCV 的 Mat 数据与 byte 互转?
- Mat 转 byte
1
2
3
4
5
6byte * matToBytes(Mat image)
{
int size = image.total() * image.elemSize();
byte * bytes = new byte[size]; // you will have to delete[] that later
std::memcpy(bytes,image.data,size * sizeof(byte));
} - byte 转 Mat
1
2
3
4
5Mat bytesToMat(byte * bytes,int width,int height)
{
Mat image = Mat(height,width,CV_8UC3,bytes).clone(); // make a copy
return image;
}
Python 上如何将 float 的 Mat 转 unit8 的 Mat?
- float 转为 unit8,有可能会造成数据的损失,因此会有警告提醒
1
2
3
4
5
6from skimage import img_as_ubyte
import numpy as np
img = np.array([0, 0.5, 1], dtype=float)
print(img.dtype.name)
dst=img_as_ubyte(img)
print(dst.dtype.name)
如何在 cv: :Mat 中找到最大值、最小值?
- 方法 1: std 中 algorithm
1
2
3
4
5
6
7
8
cv::Mat img = cv::imread("path-to-image/juice.tiff");
// 假设图片数据类型位float
float maxValue = *max_element(img.begin(), img.end());
float minValue = *min_element(img.begin(), img.end()); - 方法 2: cv 中 minMaxLoc
1
2
3
4
5
6
7
8
9
10int main()
{
// std::cout << "Hello World!\n";
cv::Mat image = cv::imread("path-to-image/juice.png");
cv::Mat image_re = image.reshape(1);
double minValue, maxValue; // 最大值,最小值
cv::Point minIdx, maxIdx; // 最小值坐标,最大值坐标
cv::minMaxLoc(image_re, &minValue, &maxValue, &minIdx, &maxIdx);
std::cout << "最大值:" << maxValue <<"最小值:"<
} - 方法 3: 遍历 Mat
1
2
3
4
5
6
7
8
9
10
11
12
13int main(int argc, char* argv[])
{
float Tval = 0.0;
float RawData[2][3] = {{4.0,1.0,3.0},{8.0,7.0,9.0}};
Mat RawDataMat(2,3,CV_32FC1,RawData);
double minv = 0.0, maxv = 0.0;
double* minp = &minv;
double* maxp = &maxv;
minMaxIdx(RawDataMat,minp,maxp);
cout << "Mat minv = " << minv << endl;
cout << "Mat maxv = " << maxv << endl;
return 0;
}
访问 OpenCV 中 Mat 数据的方法?
- 利用 at 访问 Mat 中的元素:at 方法后面跟 <数据类型>,此处数据类型需与矩阵定义时一致,否则报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 单通道
std::cout<(nrow,ncol);
std::cout<<(int)val<<",";
}
std::cout << std::endl;
}
# 3通道
for( size_t nrow = 0; nrow < demoImageC3.rows; nrow++)
{
for(size_t ncol = 0; ncol < demoImageC3.cols; ncol++)
{
Vec3i bgr = demoImageC3.at(nrow,ncol);//用Vec3b也行
}
}- 使用指针读取
1
2
3
4
5
6
7for(size_t nrow=0;nrow<demoImageC3.rows;nrow++){
uchar* data=demoImageC3.ptr<uchar>(nrow);
for(size_t ncol=0;ncol<demoImageC3.cols*demoImageC3.channels();ncol++){
std::cout<<int(data[ncol])<<",";
}
std::cout<<std::endl;
}
- 使用指针读取
- 使用迭代器
1
2
3
4
5
6
7
8MatIterator_<uchar> it=demoImageC3.begin<uchar>();
MatIterator_<uchar> it_end=demoImageC3.end<uchar>();
for(int cnt=1;it!=it_end;++it){
std::cout<<(int(*it))<<",";
if((cnt++%demoImageC3.cols)==0){
std::cout<<std::endl;
}
} - 使用矩阵地址
1
2
3
4
5
6for(size_t nrow=0;nrow<demoImageC3.rows;nrow++){
for(size_t ncol=0;ncol<demoImageC3.cols;ncol++){
std::cout<<(int)(*(demoImageC3.data+demoImageC3.step[0]*nrow+demoImageC3.step[1]*ncol))<<",";
}
std::cout<<std::endl;
}
如何快速扫描 Mat,并进行值替换?
- 在图像处理中,很常见的是要将所有给定的图像值修改为其他值。OpenCV 提供了修改图像值的功能,无需编写图像的扫描逻辑。
- 使用 core 模块的 cv: :LUT 函数完成快速转换
如何将一个 CV32F 的 Mat 保存到文本文件中?
- 直接遍历存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15ofstream fileWrite("afternormalizeData.txt");
for (size_t nrow = 0; nrow < outputMat.rows; nrow++)
{
for (size_t ncol = 0; ncol < outputMat.cols; ncol++)
{
// 使用cv::Vec3f读取
cv::Vec3f bgr = outputMat.at(nrow, ncol);
// 默认输出某一行
if (nrow == 250) {
string saveString = std::to_string(nrow) + "-" + std::to_string(ncol) + ":" + (" + std::to_string(bgr.val[0]) + "," + std::to_string(bgr.val[1]) + "," + std::to_string(bgr.val[2]) + ")" + "\n";
fileWrite.write(saveString.c_str(), saveString.size());
}
}
}
fileWrite.close();
如何将 Mat 类转为数组 Vector?
- Mat 类为 uchar
1
2
3
4
5
6
7
8
9std::vector array;
if (mat.isContinuous()) {
// array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
array.assign(mat.data, mat.data + mat.total());
} else {
for (int i = 0; i < mat.rows; ++i) {
array.insert(array.end(), mat.ptr(i), mat.ptr(i)+mat.cols);
}
} - Mat 类为 float
1
2
3
4
5
6
7
8
9std::vector array;
if (mat.isContinuous()) {
// array.assign((float*)mat.datastart, (float*)mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
array.assign((float*)mat.data, (float*)mat.data + mat.total());
} else {
for (int i = 0; i < mat.rows; ++i) {
array.insert(array.end(), mat.ptr(i), mat.ptr(i)+mat.cols);
}
}
如何分通道处理 Mat 中的数据?
- for 循环遍历
1
2
3
4
5
6
7
8
9std::vector meanValues= { 0.4,0.3,0.2 };
std::vector stdValues = { 0.2,0.3,0.4 };
for (int c = 0; c < inputMat.channels(); c++) {
for (int n = 0; n < inputMat.size().height; n++) {
for (int m = 0; m < inputMat.size().width; m++) {
cv::Vec3i(outputMat.at(n, m)).val[c] = (float(cv::Vec3f(inputMat.at(n, m)).val[c]) - meanValues.at(c)) / stdValues.at(c);
}
}
} - 拆分通道后处理
1
2
3
4
5
6
7// 处理更快
cv::Mat splitMat[3],outputMat;
cv::split(inputMat, splitMat);
for (int c = 0; c < inputMat.channels(); c++) {
splitMat[c] = (splitMat[c] - meanValues.at(c)) / stdValues.at(c);
}
cv::merge(splitMat, 3, outputMat);
OpenCV 如何定义 ROI 区域?
- 第一种,使用 cv: :Rect
1
2Mat imageROI;
imageROI=image(Rect(500,250,logo.cols,logo.rows)); - 第二种,使用 cv: :Range
1
imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));