我是靠谱客的博主 闪闪咖啡豆,这篇文章主要介绍那些年在Opencv遇到过的Mat坑,现在分享给大家,希望可以做个参考。

本文记录一些遇到过的Mat坑,以及易淆的知识点。

1.热身:Mat成员之易淆:

a. Mat.depth()

depth()得到的是一个0~6的数字,分别代表单个图不同的深度,对应关系如下:

复制代码
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
44
45
46
47
48
49
50
51
+--------+----+----+----+----+------+------+------+------+ | | C1 | C2 | C3 | C4 | C(5) | C(6) | C(7) | C(8) | +--------+----+----+----+----+------+------+------+------+ | CV_8U | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | | CV_8S | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 | | CV_16U | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 | | CV_16S | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 | | CV_32S | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 | | CV_32F | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 | | CV_64F | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 | +--------+----+----+----+----+------+------+------+------+

其中:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CV_8U A.at <unsigned char> CV_8S A.at <char> CV_16S A.at <short> CV_16U A.at <unsigned short> CV_32S A.at<int> CV_32F A.at<float>(0,1) CV_64F A.at<double>

不可用A.at(0,0)来取深度CV_64F的矩阵值,会报错
**note:
之前写findFundamental时,调用opencv 的库,计算极线误差总是不对,最终才发现cv::findFundamentalMat返回的是CV_64F的矩阵,但我采用F.at(1,0)来获取其中的值,结果无穷大,所以得到的结果是错的。**
可以通过type()来获取矩阵类型(对应上面0-6)。
用channels()来获取通道数,如CV_32FC1的通道数为1,则返回1.

convertTo 只能转换type,不能转换通道
也就是如果一个CV_U8C1的image;
image.convertTo(newImage, CV_U8C3);
那么newImage还是CV_U8C1(CV_U8C1和CV_U8C3)
将1通道的转换成3通道的用:
cvtColor(src,dst,CV_GRAY2RGB);
将彩色图转换成灰度图用:
cvtColor(src,dst,CV_RGB2GRAY);
cvtColor用于在彩色、灰度、HSV、YCrCb等色彩空间相互转换

b.矩阵行列数目

打印 matA.size()

复制代码
1
[3,4]

matA行为4,列为3
可以通过matA.size().width,matA.size().height得到正确行列数

c.其他常见常用函数
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. clone() 创建一个图像的深拷贝 Mat image; image = cv::imread("boldt.jpg"); Mat cloneimage = image.clone(); 2. create()函数 result.create(image.rows,image.cols,image.type()); create函数创建的图像的内存是连续的,不会对图像的行进行填补,分配的内存大小为total()*elemSize() 3. total()函数 返回Mat矩阵的像素个数 4. elemSize()函数 返回Mat矩阵每个像素的字节数 5. isContinuous() 判断Mat所表示的那副图像是否连续,即是否进行了行填补;如果返回为真的话,没有进行行填补,反之就进行了行填补 6. data成员变量 data是一个unsigned char的指针,代表Mat内存的首地址 uchar *data = image.data; 7.step成员变量 step代表Mat矩阵的行款,包括填补像素 8.setTo函数 设置矩阵的值 image.row(0).setTo(cv::Scalar(0))或 image.row(0).setTo(cv::Scalar(0,0,0));

2.矩阵之间运算

A*B;//普通矩阵乘法

矩阵乘法需要注意的是,矩阵之间的类型必须一致,否则会报错:

复制代码
1
OpenCV Error: Assertion failed (type == B.type() && (type == CV_32FC1 || type == CV_64FC1 || type == CV_32FC2 || type == CV_64FC2)) in gemm, file /home/kevin/DevLib/opencv-3.2.0/modules/core/src/matmul.cpp, line 1530

需要使用A.covertTo(A,CV_32F)等方式来使得AB两者的类型一致。
CV_32F等价于CV_32FC1
同时需要注意,opencv构造基础矩阵等函数默认形成CV_64FC1的类型。
A.dot(B)//叉积:先将矩阵转换成一个向量,再将向量vA.dot(vB);得到一个浮点数

A.mul(B)//点乘./,得到矩阵
做矩阵乘法时,除了行列要满足相应要求外,数据的位数(可以通过convertTo转换)要一致

3.图像之间进行运算

opencv 图像与一般矩阵不同,额外含有通道等信息,因此不能对图像矩阵直接使用 *,+等,需要如下操作:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
void add(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1); void subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1); void multiply(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1); void divide(InputArray src1, InputArray src2, OutputArray dst, double scale=1, int dtype=-1); void divide(double scale, InputArray src2, OutputArray dst, int dtype=-1); void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst); void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);

4.opencv的mat运算效率

Mat运算速度较快,不知其加速机制,但测试发现同为图像相加,c风格数组相加,时间是opencv的图像相加的10倍。

复制代码
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
#include <opencv2/opencv.hpp>//the total header using namespace std; using namespace cv; class Timer{ public: Timer(){} void Start(){ t1 = std::chrono::steady_clock::now(); }; void End(){ t2 = std::chrono::steady_clock::now(); std::cout<<"Time used: "<<std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count()<<" microseconds."<<std::endl; } private: std::chrono::steady_clock::time_point t1, t2; }; int main(){ cv::Mat img2 = imread("2.png"); cv::Mat img1 = cv::imread("1.png"); cvtColor(img1, img1, COLOR_BGR2GRAY); cvtColor(img2, img2, COLOR_BGR2GRAY); std::cout<<img1.depth()<<" "<<img1.channels()<<std::endl; Timer timer; //img1.resize(480,480); int rows = img1.rows, cols = img1.cols; int length = rows * cols; uchar * uarray = new uchar[length]; for(int i = 0; i < img1.rows; i++) for(int j = 0; j < img1.cols; j++) uarray[i*img1.cols+j] = img1.at<uchar>(i,j); timer.Start(); multiply(img1,img2,img1); timer.End(); timer.Start(); for(int i = 0; i< length; i++) uarray [i] = uarray[i]+uarray[i]; timer.End(); return 0;

6.获取/设置图像像素值

不考虑速度的话,用方法一即可,如果涉及到大量/重复获取全部图像的全部像素,则可以考虑方法二
方式一:动态获取:.at
比如对于4x1的矩阵,即使可以写作.at(3,1),也可以写作.at(3)
写在例程前面:获取矩阵值必须十分小心*

一是.at<>(i,j)取值不会检查i,j的值,即使i,j已经出了边界,也不会报错,只是按照内存取其他区域的值。
对此opencv文档写得十分清楚:For the sake of higher performance, the index range checks are only performed in the Debug configuration.
此处不知它的debug configuration是我自己工程的设置,而实际使用中为了速度都是使用release编译的opencv版本。而根据自己测试,在CmakeLists设置关闭CMAKE_CXX_FLAGS并开启debug模式,还是不会启动它的index检查。因此使用.at时要特别注意
二是如本文开头提到的,模板中的类型精确匹配十分重要。

复制代码
1
2
3
4
5
6
7
Vec3b intensity = img.at<Vec3b>(y, x); Vec3b intensity = img.at<Vec3b>(Point2(x,y)) uchar blue = intensity.val[0]; uchar green = intensity.val[1]; uchar red = intensity.val[2]; //设置像素 img.at<float>(320,240) = 255;
复制代码
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
const int channels = I.channels(); switch(channels) { case 1: { MatIterator_<uchar> it, end; for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it) *it = table[*it]; break; } case 3: { MatIterator_<Vec3b> it, end; for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it) { (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } } } return I;

7.点序列转换成图像矩阵

复制代码
1
2
3
4
5
6
std::vector<cv::Point2f>points cv::Mat img(points);//img的行列数目是N,2 (32FC1) std::vector<Point3f> points; Mat pointsMat = Mat(points).reshape(1);//pointsMat:N x 3 (32FC1)

8.Mat中符号重载:

operator = :是指传递引用,非值传递,值传递需要用A.copyTo(B)。这与标准库的vector等容器对 = 的重载表示硬拷贝(值传递)不一样

附录 c++知识:
当涉及以对象为函数实参时,以下情形会调用拷贝构造函数:
注意拷贝构造函数有:Class A(){};Class A(const Class& a); Class A (Class& a);
a.当将对象作为实参传入函数时,如果是值传递,则会调用类的拷贝构造函数,当函数结束时,调用析构函数.
b.当g_Fun()函数执行到return时,会产生以下几个重要步骤:
(1). 先会产生一个临时变量,temp。
(2). 然后调用拷贝构造函数把retVal的值给temp。这两个步骤有点像:Cclass temp(retVal);
(3). 在函数执行到最后先析构retVal局部变量。
(4). 等g_Fun()执行完后再析构temp对象。

当调用拷贝构造函数时,如果没有重写构造函数,则默认会进行深度拷贝成员变量(静态成员除外);
若重写了拷贝构造函数,拷贝构造函数将会在初始化形参时被调用,不再直接进行内存拷贝工作。

当传入参数为引用时,不会调用构造函数。
当返回值为引用时,不会调用构造函数,也不会调用析构函数。

9.再谈Mat的浅拷贝由于Mat拷贝构造函数Mat(const Mat &m)没有拷贝内存,只是将新的对象的指针指向m的内存区(浅拷贝),所以初始化和返回时都应该用.clone()确保进行了深度拷贝

Mat 的各种运算,包括加减乘除、.t(),.inv()等都会创造一个临时结果变量,但仍然存在大量的成员函数返回的只是一个Mat头指针比如A.rowrange(0,3)。因此初次使用时需要注意甄别,对后者使用clone()深度拷贝一份。

正因如此,对于Mat 的 vector,push_back在执行拷贝构造时也未拷贝内存,因此存在:

复制代码
1
2
3
4
5
6
Mat m(1,1); vector<Mat>vecMatrix; for(int i = 0; i < 10; i++){ m.at<float>(0) = float(i); vecMatrix.push_back(m); }

这样处理后会发现vecMatrix所有成员的数据都是9。
因此当push_back()时,一定要push_back新创建的Mat或使用深度拷贝后的mat:

复制代码
1
2
3
4
5
6
Mat m(1,1); vector<Mat>vecMatrix; for(int i = 0; i < N; i++){ m.at<float>(0) = float(i); vecMatrix.push_back(m.clone()); }

10.其他

经Berwin1973同学评论:
opencv2.x时候,有直接从IPLImage结构转为Mat图像结构的构造函数 Mat(IplImage*),而在3.x中此构造函数被移除

最后

这是之前写的博客,最近评论时重看了下,感觉有经常遇到的问题,也可以抽空去opencv下边issue下,助力opencv社区成长。
anyway,我用OpenCV,我快乐:)

最后

以上就是闪闪咖啡豆最近收集整理的关于那些年在Opencv遇到过的Mat坑的全部内容,更多相关那些年在Opencv遇到过内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(117)

评论列表共有 0 条评论

立即
投稿
返回
顶部