我是靠谱客的博主 闪闪咖啡豆,最近开发中收集的这篇文章主要介绍那些年在Opencv遇到过的Mat坑,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

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

a. Mat.depth()

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

+--------+----+----+----+----+------+------+------+------+
|
| 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 |
+--------+----+----+----+----+------+------+------+------+

其中:

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()

[3,4]

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

c.其他常见常用函数
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;//普通矩阵乘法

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

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

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倍。


#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时要特别注意
二是如本文开头提到的,模板中的类型精确匹配十分重要。

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;
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.点序列转换成图像矩阵

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在执行拷贝构造时也未拷贝内存,因此存在:

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:

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遇到过的Mat坑所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部