那些年在Opencv遇到过的Mat坑
来源:互联网 发布:电脑拍照软件 清晰 编辑:程序博客网 时间:2024/06/10 06:05
本文记录一些遇到过的Mat坑,以及相应知识点。
1.Mat成员:
a. Mat.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));
3.矩阵之间运算有:
A*B;//普通矩阵乘法
note
矩阵乘法需要注意的是,矩阵之间的类型必须一致,否则会报错:
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转换)要一致
4.图像之间的运算有(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);
5.opencv 的mat运算速度较快,不知其加速机制,但测试发现同为图像相加,c风格数组相加,时间是opencv的图像相加的10倍。
#include <opencv2/opencv.hpp>//the total headerusing 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>pointscv::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());}
- 那些年在Opencv遇到过的Mat坑
- 那些年遇到过的坑之 Android
- 那些年遇到过的坑之 iOS
- 那些年使用Maven遇到过的坑
- 那些年,我们遇到过的“S”
- 那些年,在nodejs上踩过的坑
- 那些年在Android之路上踩过的坑
- 那些年在WebView上踩过的坑
- 那些年在WebView上踩过的坑
- 那些年在WebView上踩过的坑
- 那些年在WebView上踩过的坑
- 【那些年遇到过的面试题】关键字volatile
- 【那些年遇到过的面试题】 rpm包
- 【那些年遇到过的面试题】 explicit关键字
- 【那些年遇到过的面试题】switch case default
- 【那些年遇到过的面试题】并发服务器模型
- 【那些年遇到过的面试题】gdb调试多线程
- 【那些年遇到过的面试题】tcpdump
- 6.3
- HDFS文件
- 摧毁数组
- 摩尔定律对IT未来发展的影响
- ArrayList 的实现分析
- 那些年在Opencv遇到过的Mat坑
- CSS中上下左右提示框主要代码
- linux中的系统恢复
- JDBC学习笔记(1)—JDBC概述
- Effective C++ 49. Understand the behavior of the new-handler
- 机器学习(3)-逻辑回归
- 数学之美,判断两个线段相交
- Linux系统学习之系统启动的5个过程
- AMD和CMD规范