如题,问题很简单,也很基础,但是却是高频使用的,有时候,莫名地就忘了格式,或者忘了还有这个操作,所以有必要写一篇博文把OpenCV的Mat类对象的初始化、再初始化、属性获取、基本操作”的示例代码进行个积累汇总,以便自己Coding时取用。
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//OpenCV版本 OpenCV3.0
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
// 方法1:创建无初始化矩阵
cv::Mat image1;
// 方法2:创建大小为2x3,数据类型为8位无符号整数的单通道矩阵
cv::Mat image2( 2, 3, CV_8UC1 );
// 方法3:创建大小为3x2,数据类型为8位无符号整数的三通道矩阵
cv::Mat image3( cv::Size(2,3), CV_8UC1 );
// 方法4:创建大小为2x3,数据类型为32位浮点型的两通道矩阵,两个通道的数据初始值分别为1和2
cv::Mat image4( 2, 3, CV_32FC2, cv::Scalar(1,2) );
// 方法5:创建大小为3x2,数据类型为8位无符号的三通道矩阵,三个通道的数据初始值分别为1、2、3
cv::Mat image5( cv::Size(2,3), CV_8UC3, cv::Scalar(1,2,3) );
//方法6:创建Mat对象image6,由于是引用传递,所以image6和image2共用内存空间。
cv::Mat image6( image2 );
// 输出矩阵结果
std::cout <<"imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
std::cout <<"imag2的数据如下:\n"<<image2 << std::endl<< std::endl;
std::cout <<"imag3的数据如下:\n"<<image3 << std::endl<< std::endl;
std::cout <<"imag4的数据如下:\n"<<image4 << std::endl<< std::endl;
std::cout <<"imag5的数据如下:\n"<<image5 << std::endl<< std::endl;
std::cout <<"imag6的数据如下:\n"<<image6 << std::endl<< std::endl;
return 0;
}
运行结果如下图所示:
从上面的运行结果我们可以看出,MAT对象的数据如果没有指定初始值,那么其值为205,注意不是255。
要特别注意的地方是:使用cv::Size()设定大小时,第一个参数是指矩阵有多少列,第二个参数是指矩阵有多少行,即cv::Size(width,height),比如上面代码中的方法3和方法5。
示例代码如下:
#include
#include
using namespace std;
int main()
{
cv::Mat A1 = (cv::Mat_<uchar>(3, 3) << 1, 2, 3,
4, 5, 6,
7, 8, 9);
cv::Mat B1 = (cv::Mat_<float>(3, 3) << 0.1, 0.2, 0.3,
0.4, 0.5, 0.6,
0.7, 0.8, 0.9);
cv::Mat C1 = (cv::Mat_<double>(3, 3) << 0.1, 0.2, 0.3,
0.4, 0.5, 0.6,
0.7, 0.8, 0.9);
cout << "A1中的数据为:\n" << A1 << endl << endl;
cout << "B1中的数据为:\n" << B1 << endl << endl;
cout << "C1中的数据为:\n" << C1 << endl << endl;
return(0);
}
运行结果如下:
注意B1和C1输出的不同,所以当数据为浮点型时能用double型就用double型。
示例代码如下:
//OpenCV版本:3.0.0
//VS版本:2013
#include
#include
using namespace std;
int main()
{
cv::Mat A1 = (cv::Mat_<float>(2, 3) << 1, 200, 3, 4, 5, 6);
cout << "A1中的数据为:\n" << A1 << endl << endl;
cv::Mat B1(A1.size(), A1.type(), cv::Scalar(1));
cout << "B1中的数据为:\n" << B1 << endl << endl;
return(0);
}
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat image1( 2, 2, CV_8UC1,cv::Scalar(9));
std::cout <<"原imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
image1.create(3, 3, CV_32FC2); //注意,函数create()不允许设置调整后矩阵元素的值
// 输出矩阵结果
std::cout <<"经调整后imag1的数据如下:\n"<<image1 << std::endl<< std::endl;
return 0;
}
运行结果如下图所示:
这里要特别注意:函数create()不允许设置调整后矩阵元素的值,这也是为什么它不经常被大家使用的原因。
函数create()的常用原型如下:
void cv::Mat::create( int rows,
int cols,
int type
)
可见,确实是没有设置调整后矩阵元素的值的参数。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat image1( 2, 2, CV_8UC1,cv::Scalar(9));
cv::Mat image2( 2, 2, CV_8SC1,cv::Scalar(9));
cv::Mat image3( 2, 2, CV_16UC1,cv::Scalar(9));
cv::Mat image4( 2, 2, CV_16SC1,cv::Scalar(9));
cv::Mat image5( 2, 2, CV_32SC1,cv::Scalar(9));
cv::Mat image6( 2, 2, CV_32FC1,cv::Scalar(9));
cv::Mat image7( 2, 2, CV_64FC1,cv::Scalar(9));
std::cout <<"image1 depth:" <<image1.depth() << std::endl<< std::endl;
std::cout <<"image2 depth:" <<image2.depth() << std::endl<< std::endl;
std::cout <<"image3 depth:" <<image3.depth() << std::endl<< std::endl;
std::cout <<"image4 depth:" <<image4.depth() << std::endl<< std::endl;
std::cout <<"image5 depth:" <<image5.depth() << std::endl<< std::endl;
std::cout <<"image6 depth:" <<image6.depth() << std::endl<< std::endl;
std::cout <<"image7 depth:" <<image7.depth() << std::endl<< std::endl;
return 0;
}
运行结果如下图所示:
从代码和运行结果我们可以得出下面的对应关系:
0代表CV_8U - 8-bit unsigned integers ( 0..255 )
1代表CV_8S - 8-bit signed integers ( -128..127 )
2代表CV_16U - 16-bit unsigned integers ( 0..65535 )
3代表CV_16S - 16-bit signed integers ( -32768..32767 )
4代表CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
5代表CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
6代表CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
这个成员函数的使用和上一个示例一样,只是把上一个示例中的depth()换成channels()。示例代码如下:
std::cout <<"Image2 channels:" << Image2.channels() << std::endl;
示例代码如下:
#include
#include
#include
#include
#include
using namespace cv;
int main()
{
//源图像
Mat img_input = imread("F:/material/images/P0005-BaoXiaofeng.jpg");
Mat img_output(img_input.size(), img_input.type());
std::cout<<"img_input channels:" << img_input.channels() << std::endl<< std::endl;
std::cout<<"img_input depth:" << img_input.depth() << std::endl<< std::endl;
std::cout<<"img_input type:" << img_input.type() << std::endl<< std::endl;
std::cout<<"img_output channels:" << img_output.channels() << std::endl<< std::endl;
std::cout<<"img_output depth:" << img_output.depth() << std::endl<< std::endl;
waitKey(0);
return 0;
}
运行结果如下:
从上面的运行结果我们可以看出,img_output的depth和channels和img_input的depth和channels相同,即矩阵的depth和channels都包含于type中。
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat image1( 2, 2, CV_8UC1,cv::Scalar(9));
std::cout <<"原imag1的数据类型为:"<<image1.depth()<< std::endl<< std::endl;
image1.convertTo(image1, CV_16UC1);
std::cout <<"经转换后的image1的数据类型为:"<<image1.depth() << std::endl<< std::endl;
return 0;
}
运行结果如下:
可见实现了数据类型的转换,运行结果中的0和2具体代表的类型可查看上上一个示例代码(即本文中的3-1)。
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 10, 8, CV_8UC1, cv::Scalar(5) );
// 矩阵的行数和列数获取
std::cout << "Image1 row: " << Image1.rows << std::endl<< std::endl;
std::cout << "Image1 col: " << Image1.cols << std::endl<< std::endl;
return 0;
}
代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 10, 8, CV_8UC1, cv::Scalar(5) );
// 矩阵的尺寸(size)
std::cout << "Image1的尺寸为: " << Image1.size() << std::endl<< std::endl;
return 0;
}
从上面的截图中我们可以看出,函数size()只是获取矩阵的宽度和高度(宽度值在前,高度值在后),而不会获取矩阵更高维度的值,比如通道数是不会获取的。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1 = cv::imread("F:/material/images/P0005-BaoXiaofeng.jpg");
cv::Mat Image2(Image1.size(), Image1.type());
// 矩阵的尺寸(size)
std::cout << "Image1的尺寸为: " << Image1.size() << std::endl<< std::endl;
std::cout << "Image2 row: " << Image2.rows << std::endl<< std::endl;
std::cout << "Image2 col: " << Image2.cols << std::endl<< std::endl;
return 0;
}
这个的详细介绍见博文https://www.hhai.cc/thread-75-1-1.html 【打开页面后搜索“成员函数Mat:at()”】
要注意at的两个参数,第一个为行号,第二个为列号,即并不是按横坐标,纵坐标的顺序,而是按行号和列号的顺序。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 3,4, CV_8UC1, cv::Scalar(0) );
uchar value = 1;
for (int i = 0; i < Image1.rows; i++)
{
for (int j = 0; j < Image1.cols; j++)
{
Image1.at<uchar>(i, j) = value;
value++;
}
}
std::cout <<"Image1的数据如下:\n"<<Image1 << std::endl<< std::endl;
std::cout <<"Image1的第0行的数据如下:\n"<<Image1.row(0) << std::endl<< std::endl;
cv::Mat Image2 = Image1.row(0);
std::cout <<"Image2的数据如下:\n"<<Image2 << std::endl<< std::endl;
Image1.at<uchar>(0, 0) = 100;
std::cout <<"修改Image1相应位置的数据后再看Image2的数据:\n"<<Image2 << std::endl<< std::endl;
return 0;
}
运行结果如下图所示:
从运行结果我们可以看,Image2和Image1是共用内存空间的,修改Image2的值会影响Image1第0行的值,而修改Image1中第0行的值也会影响到Image2。
这个和上一个示例其实是一样的,只是把上一个示例中的row()改成col(),其它完全一样,也是与原矩阵共用内存空间。
示例代码如下:
cv::Mat Image2 = Image1.col(0);
这个和第5、第6是一样的使用方法,只是这个可以选取多行或多列,也是与原矩阵共用内存空间。
函数rowRange()的原型如下:
Mat cv::Mat::rowRange( int startrow,
int endrow
) const
参数startrow和参数endrow设置要选取的连续行区域的起始行和结束行。注意由这两个参数
由参数startrow和参数endrow设置选取的行区间[startrow, endrow),注意是左开右闭区间。举个例子,[0, 2)选取的是第0行和第1行,没有第二行。
示例代码如下:
std::cout << Image1.rowRange(1,3) << std::endl;
这个和上一个示例其实是一样的,只是把上一个示例中的rowRange()改成colRange(),其它完全一样,也是与原矩阵共用内存空间。
示例代码如下:
std::cout << Image1.colRange(2,4) << std::endl;
注意:上面标题中的operator()表示Mat类对符号()的运算符重载,关于运算符重载,可以参见我的另一篇博文,链接 https://www.hhai.cc/thread-81-1-1.html
关于上面标题中const的含义,可以参见我的另一篇博文,链接:https://www.hhai.cc/thread-82-1-1.html
深拷贝ROI区域的示例代码如下:
//利用Rect选择区域(100, 180, 150, 50)
int xRoi = 80;
int yRoi = 180;
int widthRoi = 150;
int heightRoi = 100;
srcImage(cv::Rect(xRoi,yRoi,widthRoi,heightRoi)).copyTo(roiImage);
上面的代码实现了把srcImage中的区域(100, 180, 150, 50)深拷贝到给了roiImage。
区域(100, 180, 150, 50)代表的矩形区域是:
矩形区域左上角顶点的坐标为(100, 180)
矩形区域在x方向上的长度为150;
矩形区域在y方向上长度为180;
浅拷贝ROI区域的示例代码如下:
#include
#include
using namespace cv;
using namespace std;
int main()
{
cv::Mat A1(7, 7, CV_8UC1, cv::Scalar(0));
cout << "A1的数据为:\n" << A1 << endl << endl;
int xRoi = 1;
int yRoi = 1;
int widthRoi = 3;
int heightRoi = 4;
Mat A1_roi = A1(cv::Rect(xRoi, yRoi, widthRoi, heightRoi));
cout << "A1_roi的数据为:\n" << A1_roi << endl << endl;
A1_roi.at<uchar>(1, 1) = 1;
cout << "对A1_roi进行修改后A1_roi的数据为:\n" << A1_roi << endl << endl;
cout << "对A1_roi进行修改后A1的数据为:\n" << A1 << endl << endl;
return(0);
}
运行结果如下:
从上面的运行结果来看,对浅拷贝对象A1_roi的修改也会影响到A1中相应元素的值。
示例代码如下:
#include
#include
using namespace cv;
using namespace std;
int main()
{
cv::Mat A1(7, 7, CV_8UC1, cv::Scalar(0));
cv::Mat B1(3, 3, CV_8UC1, cv::Scalar(1));
int xRoi = 1;
int yRoi = 1;
int widthRoi = 3;
int heightRoi = 3;
cv::Rect A1_roi = cv::Rect(xRoi, yRoi, widthRoi, heightRoi);
B1.copyTo(A1(A1_roi)); //这是关键代码
B1.at<uchar>(1, 1) = 5; //这是为了测试看是不是深拷贝
cout << "A1的数据为:\n" << A1 << endl << endl;
return(0);
}
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 3,4, CV_8UC1, cv::Scalar(0) );
uchar value = 1;
for (int i = 0; i < Image1.rows; i++)
{
for (int j = 0; j < Image1.cols; j++)
{
Image1.at<uchar>(i, j) = value;
value++;
}
}
std::cout <<"原Image1的数据如下:\n"<<Image1 << std::endl<< std::endl;
//Image1的第0行元素变换成Image1的第2行元素乘以2
Image1.row(0) = Image1.row(2) * 2;
std::cout <<"进行行间运算后Image1的数据如下:\n"<<Image1 << std::endl<< std::endl;
return 0;
}
这个和上一个示例其实是一样的,只是把上一个示例中的row()改成col(),其它完全一样。
示例代码如下:
Image1.col(0) = Image1.col(2) * 2;
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 3,4, CV_8UC1, cv::Scalar(0) );
uchar value = 1;
for (int i = 0; i < Image1.rows; i++)
{
for (int j = 0; j < Image1.cols; j++)
{
Image1.at<uchar>(i, j) = value;
value++;
}
}
cv::Mat Image2( 2, 2, CV_8UC1, cv::Scalar(5) );
cv::Mat Image3;
Image2 = Image1.rowRange(0,2).clone();
Image3 = Image1.clone();
std::cout <<"Image1的数据如下:\n"<<Image1 << std::endl<< std::endl;
std::cout <<"Image2的数据如下:\n"<<Image2<< std::endl<< std::endl;
std::cout <<"Image3的数据如下:\n"<<Image3<< std::endl<< std::endl;
return 0;
}
示例代码如下:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include
int main( )
{
cv::Mat Image1( 3,4, CV_8UC1, cv::Scalar(0) );
uchar value = 1;
for (int i = 0; i < Image1.rows; i++)
{
for (int j = 0; j < Image1.cols; j++)
{
Image1.at<uchar>(i, j) = value;
value++;
}
}
cv::Mat Image2( 2, 2, CV_8UC1, cv::Scalar(5) );
cv::Mat Image3;
Image1.rowRange(0,2).copyTo(Image2);
Image1.copyTo(Image3);
std::cout <<"Image1的数据如下:\n"<<Image1 << std::endl<< std::endl;
std::cout <<"Image2的数据如下:\n"<<Image2<< std::endl<< std::endl;
std::cout <<"Image3的数据如下:\n"<<Image3<< std::endl<< std::endl;
return 0;
}
运行结果如下图所示:
从上面的运行结果我们可以看出函数clone()和函数copyTo()在使用上及效果上几乎是一样的,事实上,这两个函数在使用效果上几乎也是没有差别的。它们都不与原矩阵共用内存空间。
它们俩的细微差别如下:
clone 是完全的深拷贝,在内存中申请新的空间。
copyTo 也是深拷贝,但是否申请新的内存空间,取决于dst矩阵头中的大小信息是否与src一至,若一致则只深拷贝并不申请新的空间,否则先申请空间后再进行拷贝。
类成员函数at()的详细介绍请参看我的另一篇博文,链接如下:
https://www.hhai.cc/thread-75-1-1.html【进入页面后搜索“成员函数Mat:at()”】