《opencv4快速入门》
D:\opencv\build\include\opencv2 路径下
Mat类用于存储矩阵数据,可以自动管理内存,解决内存释放的问题。用来保存矩阵类型的数据,包括向量,矩阵,图像数据等。分为矩阵头和指向数据的指针两部分,矩阵头包括矩阵的尺寸,存储方法,地址,引用次数等。大小是个常数,opencv中复制和传递图像,就只是复制了矩阵头和指向数据的地址。 创建Mat时可以先创建矩阵头再赋值。
using namespace cv;
Mat a; // 名为a的矩阵头
a = imread("3.jpg"); // 向a中赋值图像数据,矩阵指针指向像素数据
Mat b=a; // 复制矩阵头,命名b,虽然有各自的矩阵头,但数据指针指向的时一个数据
Mat c = Mat_<double>(3,3) // 创建3*3 的矩阵存放double类型的数据。
// 同样的有 flaot,uchar,char 和模板,CV_64F
通过其中一个矩阵头改变数据,另一个矩阵头指向的数据同样变化。
但是当a 变量删除时,b不会指向一个i空数据,只有两个变量都没了才会删除数据。
这就是矩阵头的引用次数标记了引用矩阵数据的次数, 只有值为0才会释放数据。
为了避免在不同环境下因变量位数长度不同而造成程序执行问题,OpenCV 根据数值变量存储位数长度定义了数据类型,后加一个C表示通道
Mat a(640,640,CV_8UC3); // 创建一个640*640 8位三通道的矩阵
Mat a(3,3,CV_8U); // 单通道,C1可以省略
Mat ()
Mat (int rows, int cols, int type)
Mat (Size size, int type) size:二维数组尺寸 Size(cols,rows),注意行列顺序
Mat a(Size(480,640), CV_8UC1); 行640,列480
Mat (int rows, int cols, int type, const Scalar &s) 构造的同时赋值(相同的值)
Mat a(2,2,CV_8UC3, Scalar(0,0,255)); 创建三通道矩阵,通道值位0,0,255。
Mat (Size size, int type, const Scalar &s)
Mat (int ndims, const int *sizes, int type)
Mat (const std::vector< int > &sizes, int type)
Mat (int ndims, const int *sizes, int type, const Scalar &s)
Mat (const std::vector< int > &sizes, int type, const Scalar &s)
Mat (const Mat &m) 利用已有矩阵构建,只是复制了矩阵头,这辆指向同一数据,m=a.clone() 复制数据
Mat (int rows, int cols, int type, void *data, size_t step=AUTO_STEP)
Mat (Size size, int type, void *data, size_t step=AUTO_STEP)
Mat (int ndims, const int *sizes, int type, void *data, const size_t *steps=0)
Mat (const std::vector< int > &sizes, int type, void *data, const size_t *steps=0)
Mat (const Mat &m, const Range &rowRange, const Range &colRange=Range::all())
再已有矩阵中截取一个范围,rowRange是一个Range变量 Range(2,5) 截取2至5行,colRange 截取列,默认全截
这个截取的矩阵仍然与原Mat 共享数据,改一个另一个也会改变
Mat (const Mat &m, const Rect &roi)
Mat (const Mat &m, const Range *ranges)
Mat (const Mat &m, const std::vector< Range > &ranges)
Mat a = (Mat_(3, 3) << 1 , 2 , 3 , 4 , 5, 6 , 7 , 8 , 9) 枚举赋值,用数据流赋值
Mat c=Mat_<int>(3,3); // 循环赋值
for (int i =0;i<c.rows;i++)
{
for(int j=0; j<c.cols;j++)
{
c.at<int>(i,j)= i+j; // 类型一样
}
}
Mat a = Mat::eye(3,3, CV_8UC1); // 单位矩阵
Mat b = (Mat_<int>(1,3)<<1,2,3);
Mat c = Mat::diag(b); // 对角矩阵,参数是Mat类型的一维变量,用来存放堆笑元素的值
Mat d = Mat::ones(3,3, CV_8UC1); // 全为1
Mat e = Mat::zeros(3,3, CV_8UC3); // 全为0
利用数组进行赋值
float a[] = {1,2,3,4,5,1,2,3};
Mat b = Mat(2,2,CV_32FC2,a); // 拆分方式根据矩阵的尺寸和通道数,数组不够填充别的值。多了就省略
Mat类的运算,看作普通的矩阵就行
e = a+b 矩阵加减法,两个矩阵类型相同
f = 2*a
h = d/2. 与常数的运算,保留矩阵的类型
g = a-1 逐元素减1
j = a*b 矩阵的乘法
double k = a.dot(b) 矩阵的内积,一个行向量和列向量的点乘
m = a.mul(b) 逐元素对应相乘
比如之前的 at方法对有每一位的读取,多通道的Mat矩阵存储的形式类似于三维数组,先存储第一个元素的每个通道的数据,然后第二个元素每个通道的数据,每一行就如此存储。找到每个元素的起始位置,就可以找到这个元素中每个通道的数据
Mat a = (Mat_<uchar>(3,3) << 1,2,3,4,5,6,7,8,9);
int Value = (int) a.at<uchar>(0,0);
Mat b(3,4,CV_8UC3,Scalar(0,0,1);
Vec3b vc3 = b.at<Vec3b>(0,0); // 与矩阵类型对应
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
Mat b(3,4,CV_8UC3,Scalar(0,0,1);
for (int i=0; i<b.rows;i++) // 每一行
{
uchar* ptr = b.ptr<uchar>(i); // 定义指针,声明指向哪一行
for (int j =0;j<b.clos*b.channels();j++) // 每一行存储的数据数量为列数与通道的乘积。
{
cout<<(int)ptr[j]<<endl;
}
}
// 读取第2行数据中第三个数据,a.ptr(1)[2];
MatIterator_<uchar> it = a.begin<uchar>(); // 迭代器遍历类型
MatIterator_<uchar> it_end = a.end<uchar>();
for (int i=0; it != it_end; it++)
{
cout<<(int)(*it)<< " "; // 解引用,输出每一个元素的每个通道
if((++i% a.cols) == 0){cout<<endl;}
}
(int)(*(b.data+b.step[0] * row + b.step[1] * col + channel));
row 某个数据的行数,col列数,channel某个数据所在元素的通道。就是第row行,第col列,第channel通道的数据。也是通过首地址移动若干位读取数据。
Mat cv::imread ( const String & filename,
int flags = IMREAD_COLOR
)
读取图片,返回一个Mat,读取失败返回空矩阵,可以使用Mat.empty() 判断。Win中默认使用自带的编码器 libjpeg,libpng等来读取图像文件,linux就需要自己下载,能否读取文件看文件的内容而不是后缀,.jpg 改成.exe 一样可以读取,相反就不行。
flags参数设置读取样式,原样读取,灰度,彩图,多位数,缩小等。功能不冲突的前提下可以同时声明多个,用 | 隔开
void cv::namedWindow ( const String & winname,
int flags = WINDOW_AUTOSIZE
)
显示图像时没有主动定义窗口,会自动生成。需要添加滑动条这类的需要手动创建。创建一个窗口变量,用于显示图像和滑动条。创建窗口时已经存在同名的,就不会再执行任何操作,创建的窗口使用 cv::destroyWindow() and cv::destroyAllWindows() 来释放资源,一个指定名字,一个释放全部。但实际上程序的退出会自动关闭应用程序的所有资源,可以不用主动释放。
flags声明窗口属性,设置是否可调大小,显示图像是否填充+
void cv::imshow ( const String & winname,
InputArray mat
)
再指定窗口中显示图像,第二个参数是InputArray,是一个类型声明引用,用于输出参数的标记,就当作Mat就行。
VideoCapture ()
VideoCapture (const String &filename, int apiPreference=CAP_ANY)
apiPreference 读取视频时设置的属性,编码格式,是否使用OpenNI等。
可以读取处理视频流,也可以是图片序列或者视频的URL,图片序列将多个图像的名字统一为 "前缀+数字“ 的形式,比如 img_%02d.jpg ,可以读取文件夹下 img_00.jpg, img_01.jpg…就可以自动搜索合适的标志。 isOpened() 函数进行判断是否读取成。
通过VideoCapture 将视频文件加载到了这个类变量里,使用视频中的图片使用,使用>> 将图片导入Mat中,当VideoCapture里所有图像赋值给了Mat,后再次赋值就变成空矩阵了。使用 empty() 判断是否读取完毕。
还提供了查看视频属性的get() 函数,通过输入指定的标记获取视频属性。
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
system("color F0"); // 摄像头就 video(0)
VideoCapture video("D:\\opencv\\sources\\samples\\data\\Megamind.avi");
if (video.isOpened())
{
cout << video.get(3) << video.get(4) << video.get(5) << video.get(7);
}
while (1)
{
Mat frame;
video >> frame;
if (frame.empty())
{
break;
}
imshow("img", frame);
waitKey(1000 / video.get(CAP_PROP_FPS));
}
waitKey();
return 0;
}
bool cv::imwrite ( const String & filename,
InputArray img,
const std::vector< int > & params = std::vector< int >()
)
将Mat保存为图片,
函数第三个参数在一般情况下不需要填写 ,保存成指定的文件格式只需要直接在第一个参数
后面更改文件后缀,但是当需要保存的 Mat 类矩阵中数据比较特殊 〈如 16 位深度数据 )就需要
要设置第 3个参数.
// 保存图像
#include
#include
using namespace std;
using namespace cv;
void AlphaMat(Mat &mat) // 引用作参数 就是改改值吧。。
{
CV_Assert(mat.channels() == 4);
/*进行条件检查,失败就报出异常*/
for (int i = 0; i < mat.rows; i++)
{
for (int j = 0; j < mat.cols; ++j)
{
Vec4b& bgra = mat.at<Vec4b>(i, j); // 多通道就返回一个元素多个数据,使用这种类型承载
bgra[0] = UCHAR_MAX; // maximum unsigned char value 蓝色通道,255
/*
saturate_cast 是为了防止颜色溢出
原理大致如下
if(data<0)
data=0;
elseif(data>255)
data=255;
*/
bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols)); //绿色
bgra[2] = saturate_cast<uchar>((float(mat.rows - j)) / ((float)mat.rows)); //red
bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); //alpha 通道
}
}
}
int main(int argc, char *argv[])
{
system("color E");
Mat mat(480, 640, CV_8UC4); // 创建个四通道的mat
AlphaMat(mat);
vector<int> compression_params;
compression_params.push_back(IMWRITE_PNG_COMPRESSION); // imwrite 第三个参数设置的方式
compression_params.push_back(9);
bool result = imwrite("alpha.png", mat, compression_params);
if (!result)
{
cout << "error";
return -1;
}
cout << "okk";
return 0;
}
cv::VideoWriter::VideoWriter ( const String & filename,
int fourcc,
double fps,
Size frameSize,
bool isColor = true
)
fourcc视频编码器,-1 自动搜索合适的。fps保存的视频帧率,可以实现二倍速,慢速等。frameSize保存视频的尺寸,但是要与图像的尺寸相同。。。isColor 是否保存彩色视频。保存视频就使用 << or write() 。isOpened() 判断是否创建成功,release() 关闭视频流。
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
Mat img;
VideoCapture video(0);
if (!video.isOpened())
{
cout << "error";
return -1;
}
video >> img; // 读取视频
if (img.empty())
{
cout << "error";
return -1;
}
bool isColor = (img.type() == CV_8UC3); // 判断相机类型是否为彩色
VideoWriter writer;
int codec = VideoWriter::fourcc('M', 'J', 'P', 'G'); // 选择编码格式
double fps = 25.;
string filename = "a.avi";
writer.open(filename, codec, fps, img.size(), isColor); // 创建保存视频文件的视频流
if (!writer.isOpened()) // 视频流是否创建成功
{
cout << "error";
return -1;
}
while (1)
{
if (!video.read(img)) // 摄像头断开,或视频读取完成,执行完毕
{
cout << "error";
break;
}
writer.write(img); // 写入视频流
// writer<
imshow("img", img);
char c = waitKey(50); // ESC退出视频程序
if (c == 27)
{
break;
}
}
video.release(); // 程序退出自动释放资源,可以不用
writer.release();
return 0;
}
程序中尺寸较小的Mat类矩阵,字符串,数组等数据也需要保存,这种数据通常保存为XML or YAML文件。
YAML文件通过”变量:数值“的方式表示每个数据,通过缩进表示数据之间的结构和隶属关系。扩展就是 .ymal .yml
cv::FileStorage::FileStorage ( const String & filename,
int flags,
const String & encoding = String()
)
flags对文件进行的操作类型标志,encoding,编码格式 UTF-8 XML编码。声明打开的文件名和操作的类型。但是文件的操作需要已经存在的文件。
默认的FileSotrage的构造函数没有参数,一般需要open() 函数单独声明,传入参数。isOpened() 判断是否打开文件,使用 << 写入数据(file<<“age”<<24;)(write() 变量名,变量值),表示某个变量是个数组使用”[] " 来表示,比如“ file<<“age”<<"["<<21<<22<<"]" “。隶属关系使用{} 表示,例如” file<<“age”<<”{"<<“qbx”<<20<<“lh”<<22<<"}" ".
>> 读取数据。通过变量名读取变量值 file[“x”] >> xRead ,读取名为x的变量。当某个变量有多个数据或自变量时,需要通过FileNode 节点类型和迭代器 FileNodeIterator 读取。
virtual bool cv::FileStorage::open ( const String & filename,
int flags,
const String & encoding = String()
)
void cv::FileStorage::write ( const String & name,
int val // 经过函数重载,可以是很多其他格式
)
#include
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
system("color E");
string filename = "data.xml";
// string filename = "data.yaml";
FileStorage fwrite(filename, FileStorage::WRITE); // 写入模式,需要加前缀的
Mat mat = Mat::eye(3, 3, CV_8U);
fwrite.write("mat", mat); // 写入数据。使用write方法
float x = 100;
fwrite << "x" << x; // 跟python的字典一样,使用流方法
String str = "hi, hhh";
fwrite << "str" << str;
fwrite << "number_array" << "[" << 4 << 5 << 6 << "]"; // 表示一个数组 []
fwrite << "multi_nodes" << "{" << "month" << 8 << "day" << 28 << "year" //{} 表示隶属关系
<< 2020 << "time" << "[" << 0 << 1 << 2 << 3 << "]" << "}";
fwrite.release(); // 关闭文件
FileStorage fread(filename, FileStorage::READ); // 读取文件数据
if (!fread.isOpened())
{
cout << "error";
return -1;
}
float xRead;
fread["x"] >> xRead; // 通过变量名读取
cout << "x=" << xRead << endl;
string strRead;
fread["str"] >> strRead;
cout << "str=" << strRead << endl;
FileNode fileNode = fread["number_array"]; // 含有多个数据或自变量的,通过节点类型和迭代器进行读取
cout << "number_array = [";
for (FileNodeIterator i = fileNode.begin(); i != fileNode.end(); i++)
{
float a;
*i >> a;
cout << a << " ";
}
cout << "]" << endl;
Mat matRead;
fread["mat"] >> matRead;
cout << "mat = " << mat << endl;
FileNode fileNodel = fread["multi_nodes"];
int mouth = (int)fileNodel["mouth"]; // 从节点中读取自变量
int day = (int)fileNodel["day"];
int year = (int)fileNodel["year"];
cout << "multi_nodes:" << endl
<< " mouth=" << mouth << " day=" << day << " year=" << year;
cout << " time = [";
for (int i = 0; i < 4; i++)
{
int a = (int)fileNodel["time"][i]; // 另一种方法不用迭代器,就一级一级的找
cout << a << " ";
}
cout << "]" << endl;
fread.release();
return 0;
}