Mat 基础
图片在计算机中的本质就是一个数组。其中 Mat 就是在 OpenCV 中图像的表示形式,因此简单介绍 Mat 中一些常用的基础知识。
其中 Mat 类中有一些基本属性:
cols :矩阵列数
rows:矩阵行数
channels:通道数
type:数据类型
total:矩阵总元素数
data:指向矩阵数据块的指针
其中 Mat 排列方式如下:
通道顺序为 BGR
参考:https://blog.csdn.net/x199699/article/details/82424596
1、Mat 类型
在访问图片像素点时,了解 Mat 的类型至关重要。我们可以通过 Mat.type() 函数返回类型,通过下表的数值对应可知预定义名,如
“22" 表示 CV_64FC3,Mat 有三个通道,每个像素点是一个 64位double 类型的数值。
命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值:
C1 | C2 | C3 | C4 | |
---|---|---|---|---|
CV_8U | 0 | 8 | 16 | 24 |
CV_8S | 1 | 9 | 17 | 25 |
CV_16U | 2 | 10 | 18 | 26 |
CV_16S | 3 | 11 | 19 | 27 |
CV_32S | 4 | 12 | 20 | 28 |
CV_32F | 5 | 13 | 21 | 29 |
CV_64F | 6 | 14 | 22 | 30 |
用户定义 | 7 |
图像深度 | 枚举数值 | 空间大小 | 范围 | 等同C++变量 |
---|---|---|---|---|
CV_8U | 0 | 8bits | 0~255 | unsigned char |
CV_8S | 1 | 8bits | -128~127 | char |
CV_16U | 2 | 16bits | 0~65535 | ushort,unsigned short int,unsigned short |
CV_16S | 3 | 16bits | -32768~32767 | short,short int |
CV32S | 4 | 32bits | -2147483648~2147483647 | int,long |
CV32F | 5 | 32bits | 1.18e-38~3.40e38 | float |
CV_64F | 6 | 64bits | 2.23e-308~1.79e308 | double |
CV_USRTYPE1 | 7 | - |
参考:<https://segmentfault.com/a/1190000015653101>
2、Mat 元素访问
先行后列
图片的 高(height)==> Mat 的 行(row)==> 坐标系的 y
图片的 宽(width) ==> Mat 的 列(col)==> 坐标系的 x
2.1 at访问
img.at<type>(row, col)[channel];
// 三通道
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols ; ++ w)
{
std::cout << image.at<Vec3b>(h , w)[0] << std::endl;
std::cout << image.at<Vec3b>(h , w)[1] << std::endl;
std::cout << image.at<Vec3b>(h , w)[2] << std::endl;
}
}
// 单通道
std::cout << image.at<uchar>(h , w) << std::endl;
2.2 ptr访问
// 三通道
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols ; ++ w)
{
// 输出 [1.1, 1.2, 1.3] 的形式,输出都一样
Vec3b*ptr = image.ptr<Vec3b>(h , w) ;
std::cout << ptr[0] << std::endl;
std::cout << ptr[1] << std::endl;
std::cout << ptr[2] << std::endl;
// 输出 每个通道的值,将上边形式的数值分开输出
//Vec3b *ptr = image.ptr<Vec3b>(h , w) ;
//std::cout << ptr->val[0] << std::endl;
//std::cout << ptr->val[1] << std::endl;
//std::cout << ptr->val[2] << std::endl;
}
}
// 单通道
double* ptr = image.ptr<double>(h , w) ;
std::cout << *ptr << std::endl;
2.3 迭代器(安全)
// 三通道
Mat_<Vec3b>::iterator it = image.begin<Vec3b>() ;
Mat_<Vec3b>::iterator itend = image.end<Vec3b>() ;
for(;it != itend ; ++ it)
{
std::cout << (*it)[0] << std::endl;
std::cout << (*it)[1] << std::endl;
std::cout << (*it)[2] << std::endl;
}
// 单通道
std::cout << (*it1) << std::endl;
2.4 data 访问
uchar *data = image.data ;
// 三通道
for(int h = 0 ; h < image.rows ; ++ h)
{
for(int w = 0 ; w < image.cols ; ++ w)
{
std::cout << *data ++ << std::endl;
std::cout << *data ++ << std::endl;
std::cout << *data ++ << std::endl;
}
}
// 单通道
for(int h = 0 ; h < image.rows ; ++ h)
for(int w = 0 ; w < image.cols; ++ w)
std::cout << *data ++ << std::endl ;
2.5 continuous+channels 访问(高效)
isContinuous()
判断 image 存储是否为连续,连续为 true,不连续为 false。
可以将简单元素操作的性能提高10-20%,特别是如果图像相当小并且操作非常简单。
一般读取图片都为连续,但是裁剪过后的图片返回为不连续
int nRows = image.rows ;
int nCols = image.cols * image.channels() ;
if(image.isContinuous())
{
nCols = nRows * nCols ;
nRows = 1 ;
}
for(int h = 0 ; h < nRows ; ++ h)
{
uchar *ptr = image.ptr<double>(h) ;
for(int w = 0 ; w < nCols ; ++ w)
std::cout << *ptr++ << std::endl;
}
2.6 step 访问
改变 “0”,“1”,“2” 为 “i”,“j”,“k” 即可以访问任意位置像素值(不要越界)
for (int row = 0; row < img.rows; row++)
{
for (int col = 0; col < img.cols; col++)
{
//[row,col]像素的第1通道地址解析为Blue通道
*(img.data + img.step[0] * row + img.step[1] * col) += 15;
//[row,col]像素的第2通道地址解析为Green通道
*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) += 16;
//[row,col]像素的第3通道地址解析为Red通道
*(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) += 17;
}
}
参考:https://blog.csdn.net/BoaHock/article/details/80790323
2.7 单个像素点类型
种类 | C1 | C2 | C3 | C4 | C6 |
---|---|---|---|---|---|
uchar 8U | uchar | cv::Vec2b | cv::Vec3b | cv::Vec4b | |
char 8S | |||||
ushort 16U | |||||
short 16S | short | cv::Vec2s | cv::Vec3s | cv::Vec4s | |
int 32S | int | cv::Vec2i | cv::Vec3i | cv::Vec4i | |
float 32F | float | cv::Vec2f | cv::Vec3f | cv::Vec4f | cv::Vec6f |
double 64F | double | cv::Vec2d | cv::Vec3d | cv::Vec4d | cv::Vec6d |
当需要访问 Mat 类型为 CV_64FC3 的图片像素时
cv::Vec3d vec3d = img.at<cv::Vec3d>(0,0);
uchar vec3d0 = img.at<cv::Vec3d>(0,0)[0];
uchar vec3d1 = img.at<cv::Vec3d>(0,0)[1];
uchar vec3d2 = img.at<cv::Vec3d>(0,0)[2];
std::cout<<"vec3d = "<<vec3d<<std::endl;
std::cout<<"vec3d0 = "<<(double)vec3d0<<std::endl;
std::cout<<"vec3d1 = "<<(double)vec3d1<<std::endl;
std::cout<<"vec3d2 = "<<(double)vec3d2<<std::endl;
2.5 convertTo()
当我们了解 Mat 数据类型时,我们可以方便的进行 像素运算:
1、将整型类型的 Mat 转化为 double 类型:利用 convertTo()
函数
src.convertTo(dst, CV_64FC3);
2、将 Mat 中元素进行统一运算,如 matlab 中点除
比如:dst = a* src + b
src.convertTo(dst, CV_32F, a, b); //CV_32F为 B 中 Mat 类型
当然,也可以利用遍历进行运算
3、Mat 初始化
(1) Mat::Mat()
(2) Mat::Mat(int rows, int cols, int type)
(3) Mat::Mat(Size size, int type)
(4) Mat::Mat(int rows, int cols, int type, const Scalar& s)
(5) Mat::Mat(Size size, int type, const Scalar& s)
(6) Mat::Mat(const Mat& m)
(7) Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
(8) Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
(9) Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
(10) Mat::Mat(const Mat& m, const Rect& roi)
(11) Mat::Mat(const CvMat* m, bool copyData=false)
(12) Mat::Mat(const IplImage* img, bool copyData=false)
(13) template<typename T, int n> explicit Mat::Mat(const Vec<T, n>& vec, bool copyData=true)
(14) template<typename T, int m, int n> explicit Mat::Mat(const Matx<T, m, n>& vec, bool copyData=true)
(15) template explicit Mat::Mat(const vector& vec, bool copyData=false)
(16) Mat::Mat(const MatExpr& expr)
(17) Mat::Mat(int ndims, const int* sizes, int type)
(18) Mat::Mat(int ndims, const int* sizes, int type, const Scalar& s)
(19) Mat::Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0)
其中 clone()
也可以实现初始化
Mat img(640, 480, CV_8UC3);
Mat roi(img, Rect(10,10,100,100));
*深拷贝浅拷贝
OpenCV 中 ”=“ 是浅拷贝,只赋值图片的指针头,所以在修改 Mat 元素时,会影响另一个。
如果想实现图片复制为两个图片,应当选择 clone()
或copyto()
。
dst = src.clone();
src.copyto(dst);
*特殊矩阵初始化
Mat eye = Mat::eye(4,4,CV_8U);
Mat ones = Mat::ones(4,4,CV_8U);
Mat zeros = Mat::zeros(4,4,CV_8U);
Mat img = (Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
// 随机初始化像素值
Mat r = Mat(3,3,CV_8UC3);
randu(r, Scalar::all(0), Scalar::all(255));
4、显示图片
#include <opencv2/opencv.hpp>//opencv的头文件
using namespace cv;
int main()
{
Mat img = imread("C:/daima practice/opencv/mat3/mat3/image4.jpg");
imshow("显示灰度图",img);
waitKey(0);
return 0;
}
5、播放视频
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
// VideoCapture capture(0); 摄像头
VideoCapture cap("test.avi");
if (!cap_PD_depth.isOpened())
return -1;
double rate = capture.get(CV_CAP_PROP_FPS);//获取视频文件的帧率
int delay = cvRound(1000.000 / rate); // 设置帧率
Mat Frame;
while(1)
{
cap >> Frame;
if (frame.empty()) break;
imshow("视频", frame);
waitKey(delay);
}
return 0;
}
6、waitKey
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat Frame;
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
return 0;
while (1)
{
cap >> Frame;
if (Frame.empty())
return 0;
imshow("camera", Frame);
if (waitKey(10) == 'b') // 设置为按下 b 键退出程序
{
break;
}
}
return 0;
}