那些年在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());}
原创粉丝点击