SVM模型训练与分类的OpenCV实现
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录数据准备一、pandas是什么?二、使用步骤1.引入库2.读入数据总结数据准备在OpenCV的安装路径下,搜索digits,可以得到一张图片,图片大小为10002000,有0-9的10个数字,每5行为一个数字,总共50行,共有5000个手写数字,每个数字块大小为2020。 下面将把这些数字中的0和1作为二分类的准备数据。其中0
文章目录
- 一、数据准备
- 二、模型训练
- 三、加载模型实现分类
- 四、OpenCV 应用读取文件路径与文件名批量处理图片
- 五、逻辑运算符与位运算符
- 六、getchar()的作用
- 六、严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4996 ‘strcat‘: This function or variable may be unsafe. Consider using
- 七、OpenCV3:通道和位深的理解含义整理
- 八、Mat之通道的理解
- 九、opencv3将文件夹中的图像路径自动生成txt文件
- 十、opencv读取txt文件
- 十一、c++读取文本文件中矩阵,得到矩阵的行列数
- 十二、opencv多分类
- 十三、opencv trainAuto用法
- 十四、mat文件与txt文件的相互转换
- 十五、OpenCv中计算图像像素最大值、最小值、均值和方差
一、数据准备
在OpenCV的安装路径下,搜索digits,可以得到一张图片,图片大小为10002000,有0-9的10个数字,每5行为一个数字,总共50行,共有5000个手写数字,每个数字块大小为2020。 下面将把这些数字中的0和1作为二分类的准备数据。其中0有500张,1有500张。
用下面的代码将图片准备好,在写入路径提前建立好文件夹:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
char ad[128] = { 0 };
int filename = 0, filenum = 0;
Mat img = imread("C:\\Users\\Administrator\\Desktop\\小李文献\\digits.png");
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
int b = 20;
int m = gray.rows / b; //原图为1000*2000
int n = gray.cols / b; //裁剪为5000个20*20的小图块
for (int i = 0; i < m; i++)
{
int offsetRow = i * b; //行上的偏移量
if (i % 5 == 0 && i != 0)
{
filename++;
filenum = 0;
}
for (int j = 0; j < n; j++)
{
int offsetCol = j * b; //列上的偏移量
sprintf_s(ad, "C:\\Users\\Administrator\\Desktop\\小李文献\\data\\%d\\%d.jpg", filename, filenum++);
//截取20*20的小块
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
imwrite(ad, tmp);
}
}
return 0;
}
最后可以得到这样的结果:
组织的二分类数据形式为:
–D:
–data
–train_image
–0(400张)
–1(400张)
–test_image
–0(100张)
–1(100张)
同时也适合截取多个文件夹,代码一样,多点cout而已。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
char ad[128] = { 0 };
int filename = 0, filenum = 0;
Mat img = imread("H:\\opencv\\screen\\digits.png");
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
int b = 20;
int m = gray.rows / b; //原图为1000*2000
int n = gray.cols / b; //裁剪为5000个20*20的小图块
cout << "m:" << m << endl;
cout << "m:" << n << endl;
cout << "截图中....." << endl;
for (int i = 0; i < m; i++)
{
int offsetRow = i * b; //行上的偏移量
if (i % 5 == 0 && i != 0)
{
filename++;
filenum = 0;
}
for (int j = 0; j < n; j++)
{
int offsetCol = j * b; //列上的偏移量
sprintf_s(ad, "H:\\opencv\\screen\\data\\%d\\%d.jpg", filename, filenum++);
//截取20*20的小块
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
imwrite(ad, tmp);
}
}
cout << "截图完毕" << endl;
system("pause");
return 0;
}
里面有图片数据
二、模型训练
数据准备完成之后,就可以用下面的代码训练了:
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h>
using namespace std;
using namespace cv;
using namespace cv::ml;
void getFiles(string path, vector<string>& files);
void get_1(Mat& trainingImages, vector<int>& trainingLabels);
void get_0(Mat& trainingImages, vector<int>& trainingLabels);
int main()
{
//获取训练数据
Mat classes;
Mat trainingData;
Mat trainingImages;
vector<int> trainingLabels;
get_1(trainingImages, trainingLabels);
get_0(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
//配置SVM训练器参数
/*CvSVMParams SVM_params;
SVM_params.svm_type = CvSVM::C_SVC;
SVM_params.kernel_type = CvSVM::LINEAR;
SVM_params.degree = 0;
SVM_params.gamma = 1;
SVM_params.coef0 = 0;
SVM_params.C = 1;
SVM_params.nu = 0;
SVM_params.p = 0;
SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);*/
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setGamma(1);
svm->setDegree(0);
svm->setCoef0(0);
svm->setC(1);
svm->setNu(0);
svm->setP(0);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-2));//设置SVM训练时迭代终止条件 10的12次方
//训练
/*Ptr<TrainData> tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
SVM_params->train(tData);*/ //这两行代码和下面一行代码等效
cout << "开始进行训练..." << endl;
svm->train(trainingData, cv::ml::SampleTypes::ROW_SAMPLE, classes);
//保存模型
cout << "保存模型..." << endl;
svm->save("C:\\Users\\Administrator\\Desktop\\小李文献\\data\\svm.xml");
cout << "训练好了!!!" << endl;
getchar();
return 0;
}
void getFiles(string path, vector<string>& files)
{
long long hFile = 0;
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
void get_1(Mat& trainingImages, vector<int>& trainingLabels)
{
char * filePath = (char *) "C:\\Users\\Administrator\\Desktop\\小李文献\\data\\train_image\\1";
vector<string> files;
getFiles(filePath, files);
int number = (int)files.size();
for (int i = 0; i < number; i++)
{
Mat SrcImage = imread(files[i].c_str());
SrcImage = SrcImage.reshape(1, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(1);
}
}
void get_0(Mat& trainingImages, vector<int>& trainingLabels)
{
char * filePath = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\train_image\\0";
vector<string> files;
getFiles(filePath, files);
int number = (int)files.size();
for (int i = 0; i < number; i++)
{
Mat SrcImage = imread(files[i].c_str());
SrcImage = SrcImage.reshape(1, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(0);
}
}
整个训练过程可以分为一下几个部分:
2.1数据准备
该例程中一个定义了三个子程序用来实现数据准备工作:
getFiles()用来遍历文件夹下所有文件,可以参考:
http://blog.csdn.net/chaipp0607/article/details/53914954
getBubble()用来获取有气泡的图片和与其对应的Labels,该例程将Labels定为1。
getNoBubble()用来获取没有气泡的图片与其对应的Labels,该例程将Labels定为0。
getBubble()与getNoBubble()将获取一张图片后会将图片(特征)写入到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征和标签是一一对应的关系push_back(0)或者push_back(1)其实就是我们贴标签的过程。
trainingImages.push_back(SrcImage);
trainingLabels.push_back(0);
在主函数中,将getBubble()与getNoBubble()写好的包含特征的矩阵拷贝给trainingData,将包含标签的vector容器进行类型转换后拷贝到trainingLabels里,至此,数据准备工作完成,trainingData与trainingLabels就是我们要训练的数据。
Mat classes;
Mat trainingData;
Mat trainingImages;
vector<int> trainingLabels;
getBubble(trainingImages, trainingLabels);
getNoBubble(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
2.2特征提取
其实特征提取和数据的准备是同步完成的,我们最后要训练的也是正负样本的特征。本例程中同样在getBubble()与getNoBubble()函数中完成特征提取工作,只是我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程,所以选择了最简单的方式完成特征提取工作,除此中外,特征提取的方式有很多,比如LBP,HOG等等。
SrcImage= SrcImage.reshape(1, 1);
我们利用reshape()函数完成特征提取,原型如下:
Mat reshape(int cn, int rows=0) const;
可以看到该函数的参数非常简单,cn为新的通道数,如果cn = 0,表示通道数不会改变。参数rows为新的行数,如果rows = 0,表示行数不会改变。我们将参数定义为reshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
2.3参数配置
参数配置是SVM的核心部分,在Opencv中它被定义成一个结构体类型,如下:
struct CV_EXPORTS_W_MAP CvSVMParams
{
CvSVMParams();
CvSVMParams(
int svm_type,
int kernel_type,
double degree,
double coef0,
double Cvalue,
double p,
CvMat* class_weights,
CvTermCriteria term_crit );
CV_PROP_RW int svm_type;
CV_PROP_RW int kernel_type;
CV_PROP_RW double degree; // for poly
CV_PROP_RW double gamma; // for poly/rbf/sigmoid
CV_PROP_RW double coef0; // for poly/sigmoid
CV_PROP_RW double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR
CV_PROP_RW double nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR
CV_PROP_RW double p; // for CV_SVM_EPS_SVR
CvMat* class_weights; // for CV_SVM_C_SVC
CV_PROP_RW CvTermCriteria term_crit; // termination criteria
};
所以在例程中我们定义了一个结构体变量用来配置这些参数,而这个变量也就是CVSVM类中train函数的第五个参数,下面对参数进行说明。
SVM_params.svm_type :SVM的类型:
C_SVC表示SVM分类器,C_SVR表示SVM回归
SVM_params.kernel_type:核函数类型
线性核LINEAR:
d(x,y)=(x,y)
多项式核POLY:
d(x,y)=(gamma*(x’y)+coef0)degree
径向基核RBF:
d(x,y)=exp(-gamma*|x-y|^2)
sigmoid核SIGMOID:
d(x,y)= tanh(gamma*(x’y)+ coef0)
SVM_params.degree:核函数中的参数degree,针对多项式核函数;
SVM_params.gama:核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params.coef0:核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params.c:SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params.nu:SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params.p:SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
2.4训练模型
CvSVM svm;
svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
通过上面的过程,我们准备好了待训练的数据和训练需要的参数,**其实可以理解为这个准备工作就是在为svm.train()函数准备实参的过程。**来看一下svm.train()函数,Opencv将SVM封装成CvSVM库,这个库是基于台湾大学林智仁(Lin Chih-Jen)教授等人开发的LIBSVM封装的,由于篇幅限制,不再全部粘贴库的定义,所以一下代码只是CvSVM库中的一部分数据和函数:
class CV_EXPORTS_W CvSVM : public CvStatModel
{
public:
virtual bool train(
const CvMat* trainData,
const CvMat* responses,
const CvMat* varIdx=0,
const CvMat* sampleIdx=0,
CvSVMParams params=CvSVMParams() );
virtual float predict(
const CvMat* sample,
bool returnDFVal=false ) const;
我们就是应用类中定义的train函数完成模型训练工作。
2.5保存模型
svm.save("svm.xml");
保存模型只有一行代码,利用save()函数,我们看下它的定义:
CV_WRAP virtual void save( const char* filename, const char* name=0 ) const;
该函数被定义在CvStatModel类中,CvStatModel是ML库中的统计模型基类,其他 ML 类都是从这个类中继承。
总结:到这里我们就完成了模型训练工作,可以看到真正用于训练的代码其实很少,OpenCV最支持向量机的封装极大地降低了我们的编程工作。
三、加载模型实现分类
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h>
using namespace std;
using namespace cv;
using namespace cv::ml;
void getFiles(string path, vector<string>& files);
int main()
{
int result0 = 0;
int result1 = 0;
char * filePath = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\0";
vector<string> files;
getFiles(filePath, files);
int number = (int)files.size();
cout << number << endl;
/*Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->clear();
string modelpath = "C:\\Users\\Administrator\\Desktop\\小李文献\\data\\svm.xml";
FileStorage svm_fs(modelpath, FileStorage::READ);
if (svm_fs.isOpened())
{
svm->load(modelpath.c_str());
}*/ //这一段代码有点错误
cv::Ptr<cv::ml::SVM> svm = cv::ml::StatModel::load<cv::ml::SVM>("C:\\Users\\Administrator\\Desktop\\小李文献\\data\\svm.xml");
for (int i = 0; i < number; i++)
{
Mat inMat = imread(files[i].c_str());
Mat p = inMat.reshape(1, 1);
p.convertTo(p, CV_32FC1);
int response = (int)svm->predict(p);
if (response == 0)
{
result0++;
}
/* if (response == 1)
{
result1++;
}*/
}
cout << result0 << endl;
//cout << result1 << endl;
getchar();//有system("pause")的作用
return 0;
}
void getFiles(string path, vector<string>& files)
{
long long hFile = 0; //long long = intptr_t
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
用到predict()函数用来预测分类结果,predict()被定义在CVSVM类中。
升级版,可以分别识别2个
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h>
using namespace std;
using namespace cv;
using namespace cv::ml;
void getFiles(string path, vector<string>& files);
int main()
{
int result0_0 = 0;
int result0_1 = 0;
int result1_0 = 0;
int result1_1 = 0;
char * filePath = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\0";
vector<string> files;
getFiles(filePath, files);
char * filePath1 = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\1";
vector<string> files1;
getFiles(filePath1, files1);
int number = (int)files.size();
int number1 = (int)files1.size();
cout << number << endl;
cout << number1 << endl;
cv::Ptr<cv::ml::SVM> svm = cv::ml::StatModel::load<cv::ml::SVM>("C:\\Users\\Administrator\\Desktop\\小李文献\\data\\svm.xml");
//测试样本0
for (int i = 0; i < number; i++)
{
Mat inMat = imread(files[i].c_str());
Mat p = inMat.reshape(1, 1);
p.convertTo(p, CV_32FC1);
int response = (int)svm->predict(p);
if (response == 0)
{
result0_0++;
}
if (response == 1)
{
result0_1++;
}
}
cout << "样本0-result0:" << result0_0 << endl;
cout << "样本0-result1:" << result0_1 << endl;
//测试样本11
for (int i = 0; i < number1; i++)
{
Mat inMat1 = imread(files1[i].c_str());
Mat p1 = inMat1.reshape(1, 1);
p1.convertTo(p1, CV_32FC1);
int response1 = (int)svm->predict(p1);
if (response1 == 0)
{
result1_0++;
}
if (response1 == 1)
{
result1_1++;
}
}
cout << "样本1-result0:" << result1_0 << endl;
cout << "样本1-result1:" << result1_1 << endl;
getchar();//有system("pause")的作用
return 0;
}
void getFiles(string path, vector<string>& files)
{
long long hFile = 0; //long long = intptr_t
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
注意:
1.为什么要建立三个独立的工程呢?
主要是考虑写在一起话,代码量会比较大,逻辑没有分开清晰,当跑通上面的代码之后,就可以随意的改了。
2.为什么加上数据准备?
之前有评论说道数据的问题,提供数据后实验能更顺利一些,因为本身代码没有什么含金量,这样可以更顺利的运行起来工程,并修改它。
3.一些容易引起异常的情况:
(1):注意生成的.xml记得拷贝到预测工程下;
(2):注意准备好数据路径和代码是不是一致;
(3):注意训练的特征要和测试的特征一致;
四、OpenCV 应用读取文件路径与文件名批量处理图片
在应用OpenCV大量测试图片时,需要对图片批量的读入并进行处理。之前处理这个问题时是使用这种方法:把待处理的图片放到一个文件夹内,全选它们然后重命名1,这样系统会自动给他们全部重命名为1(1),1(2),1(3)等等等
然后用下面的代码把图片读进来:
for ( i=1;i<=624;i++)
{
sprintf_s(adr, "C:\Users\Administrator\Desktop\第二组截图\1 (%d).jpg",i);
Mat g_SrcImage;
g_SrcImage=imread(adr);
printf("i=%d",i);
}
这种方法很麻烦,需要手动重命名一遍,然后根据文件夹下的图片个数确定循环中的值。有一种更简便并且灵活性更高的方法,就是遍历文件夹内所有图片的路径,名称和总个数。
下面这种实现方式其实和OpenCV本身没什么关系了,是一种应用C++提供的io.h头文件中定义的函数实现。
先给出函数的定义:
void listFiles(const char * dir, vector<string>& files);
可以看到函数没有返回值,而是将遍历到的文件信息存储到vector中,完整的代码实现如下:
#include <iostream>
#include <io.h>
#include <vector>
#include <string>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
void listFiles(const char * dir, vector<string>& files);
int main()
{
string path = "C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\0";
vector<string> files;
listFiles(path.c_str(), files);
for (int i = 0; i < files.size(); i++)
{
cout << files[i] << endl;
Mat SrcImage = imread(files[i]);
namedWindow("show", 0);
imshow("show", SrcImage);
waitKey(10);
}
waitKey(0);
return 0;
}
//目录中的所有图片(到每一级目录)
void listFiles(const char * dir, vector<string>& files)
{
char dirNew[200];
strcpy_s(dirNew, dir);
strcat_s(dirNew, "\\*.*"); // 在目录后面加上"\\*.*"进行第一次搜索
intptr_t handle;
_finddata_t findData;
handle = _findfirst(dirNew, &findData);
if (handle == -1) // 检查是否成功
return;
do
{
if (findData.attrib & _A_SUBDIR)
{
if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
continue;
cout << findData.name << "\t<dir>\n";
// 在目录后面加上"\\"和搜索到的目录名进行下一次搜索
strcpy_s(dirNew, dir);
strcat_s(dirNew, "\\");
strcat_s(dirNew, findData.name);
listFiles(dirNew, files);
}
else
files.push_back(string(dir).append("\\").append(findData.name));
cout << findData.name << "\t" << findData.size << " bytes.\n";
} while (_findnext(handle, &findData) == 0);
_findclose(handle); // 关闭搜索句柄
}
运行结果
备注:
1.在上面的代码中可以看到,listFiles函数其实在利用递归,这意味着,这个函数不仅仅可以找目录中的文件,还可以找到目录下每一层的文件,在大多数情况下并不需要区分是遍历目录下还是遍历目录中,因为目录是我们自己创建的,要遍历的路径也是自己输入,所以我们完全可以把这个当做遍历目录中文件的函数来用。
2.上述代码在x64,x86平台上都测试通过,之所以出现x86平台运行正常,x64编译通过,运行出现异常,是因为_findfirst()返回类型为intptr_t而非long型,从“intptr_t”转换到“long”丢失了数据,所以创建句柄时需要:intptr_t handle;
3.顺便提醒一下,在使用这个函数的时候,有一个后缀参数,例如希望访问到所有的 .txt
文件,那么在使用的时候,可以把后缀参数设置为: *txt
这个也有输入的path有关系,如果path不是以\结尾,那么后缀参数记得加上,当然修改这个函数也行。
五、逻辑运算符与位运算符
逻辑与(&&)、逻辑或(||)、按位与(&)、按位或(|)、按位异或(^)、按位取反(~)
【1】逻辑与(&&)
运算符两边的表达式的值都为true运算结果为true, 其余情况为false。
【2】逻辑或(||)
运算符两边的表达式的值都为false运算结果为false, 其余情况为true。
【3】按位与(&)
计算方法:
参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当 相应位上全部为1时取1, 存在0时为0。
011 & 110
011
110
---
010
【4】按位或(|)
计算方法:
参加运算的两个数,换算为二进制(0、1)后,进行或运算。只要当 相应位上存在1时取1, 全部为0时为0。
011 | 110
011
110
---
111
【5】按位同或(⊙)
计算方法:
参加运算的两个数,换算为二进制(0、1)后,进行异或运算。只有当 相应位上的数字相同时取1, 不相同为0。
011 ⊙ 110
011
110
---
010
【6】按位异或(^) xor
计算方法:
参加运算的两个数,换算为二进制(0、1)后,进行异或运算。只有当 相应位上的数字不相同时取1, 相同为0。
011 ^ 110
011
110
---
101
【7】按位取反(~)
计算方法:
参加运算的两个数,换算为二进制(0、1)后, 0变1, 1变0。
~(010) = 101
【8】优先级
not>and>xor>or
六、getchar()的作用
【1】让程序停留在这一步,直到它从键盘接收到消息.
在程序末尾加getchar(),用来让程序不会立即退出,跟system(“pause”);是一样的功能.可能你在写完代码后用ctrl + F5运行时,不加getchar();程序也不会立即退出,这是当然的,编译器有这个功能.不过如果你从debug文件夹下用.exe文件打开代码,没有getchar()或system(“pause”);程序会闪一下就消失,可能就零点几秒.getchar();让程序停留在这一步,直到它从键盘接收到消息。
【2】缓冲区的作用
在两次连续从键盘输入语句中间.这个就有点意思了,下面我用一段代码演示
这段代码里getchar()被我注释掉了,看一下运行结果
看出什么了吗?下面那句gets(str2)直接执行了,我还没有输入字符,它就执行结束了.
看一下加了getchar()的程序
看到差异了吗?这里gets(str2)直接没有执行,下面我键入命令
这才是我想要的运行结果.
那么为什么会出现这种情况呢,我自己也上网查了些许资料,以下为本人总结:
当从键盘输入时,键盘输入的字符会保存在缓冲区,当键盘按下enter建时,缓冲区被清空,缓冲区的内容被写入目标内,比如我这段代码的目标就是str,即我从键盘输入的list被写入str数组里,这个时候缓冲区还有什么呢?准确的说,这时缓冲区里还有一个字符’enter’,
如果不加getchar(),缓冲区会把’enter’这个字符写进gets(str2),这时程序就会像上面那样,直接结束.而加了getchar();它会吃了缓冲区里的’enter’字符,这时候缓冲区才是真的什么都没有,gets(str2)等待缓冲区写入内容,这时程序才会像下面那样执行
六、严重性 代码 说明 项目 文件 行 禁止显示状态 错误 C4996 ‘strcat‘: This function or variable may be unsafe. Consider using
解决方法: 把这句话strcat(des, src);改为strcat_s(des, src);
原因: 因为strcat(des, src);这样写不安全,如果这个程序动态的去执行的话,程序不确定des这个字符数组够不够大,如果真的疏忽了,把第一个字符数组定义的比第二个字符数组小,这样程序运行起来,就会发生缓冲区溢出,一旦溢出,就可能把本来有用的数据给覆盖了,这是一种非常危险的行为。
所以编译器非常智能的告诉我们,这样写不安全,要用strcat_s(des, src)这个函数来代替,这个函数是微软提供的,是不支持跨平台的,-s的这种都是微软提供的,他会让你指定缓冲区的大小,他会判断第二个字符数组如果真的拼上去,第一个字符数组的空间够不够,不够的话就直接报警了,他不会发生缓冲区溢出这种情况。
七、OpenCV3:通道和位深的理解含义整理
7.1矩阵数据类型
– CV_<bit_depth>(S|U|F)C<number_of_channels>
S = 符号整型 U = 无符号整型 F = 浮点型
CV_8UC1 是指一个8位无符号整型单通道矩阵,
CV_32FC2是指一个32位浮点型双通道矩阵
CV_8UC1 CV_8SC1 CV_16U C1 CV_16SC1
CV_8UC2 CV_8SC2 CV_16UC2 CV_16SC2
CV_8UC3 CV_8SC3 CV_16UC3 CV_16SC3
CV_8UC4 CV_8SC4 CV_16UC4 CV_16SC4
CV_32SC1 CV_32FC1 CV_64FC1
CV_32SC2 CV_32FC2 CV_64FC2
CV_32SC3 CV_32FC3 CV_64FC3
CV_32SC4 CV_32FC4 CV_64FC4
其中,通道表示每个点能存放多少个数,类似于RGB彩色图中的每个像素点有三个值,即三通道的。
图片中的深度表示每个值由多少位来存储,是一个精度问题,一般图片是8bit(位)的,则深度是8.
1–bit_depth—比特数—代表8bite,16bites,32bites,64bites—举个例子吧–比如说,如
如果你现在创建了一个存储–灰度图片的Mat对象,这个图像的大小为宽100,高100,那么,现在这张
灰度图片中有10000个像素点,它每一个像素点在内存空间所占的空间大小是8bite,8位–所以它对
应的就是CV_8
2–S|U|F–S–代表—signed int—有符号整形
U–代表–unsigned int–无符号整形
F–代表–float---------单精度浮点型
3–C<number_of_channels>----代表—一张图片的通道数,比如:
1–灰度图片–grayImg—是–单通道图像
2–RGB彩色图像---------是–3通道图像
3–带Alph通道的RGB图像–是–4通道图像
7.2opencv cv::Mat数据类型总结
在以下两个场景中使用OpenCV时,我们必须事先知道矩阵元素的数据类型:
使用 at 方法访问数据元素的时候要指明数据类型
做数值运算的时候,比如究竟是整数除法还是浮点数除法。
cv::Mat 类的对象有一个成员函数type()用来返回矩阵元素的数据类型,
返回值是 int 类型,不同的返回值代表不同的类型,具体对应关系如下所示:
表头的 C1, C2, C3, C4 指的是通道(Channel)数,例如:
灰度图像只有 1 个通道,是 C1;
JPEG格式 的 RGB 彩色图像就是 3 个通道,是 C3
PNG 格式的彩色图像除了 RGB 3个通道外,还有一个透明度通道,所以是 C4。
如果仅仅是为了在数值计算前明确数据类型,那么看到这里就可以了
如果是要使用 at 方法访问数据元素,那么还需要下面一步
因为以单通道为例,at 方法接受的是 uchar 这样的数据类型,而非 CV_8U。
在已知通道数和每个通道数据类型的情况下,指定给 at 方法的数据类型如下表所示:
现在,就可以使用at来访问图像的像素了:
cv::Vec3b vec3b = img.at<cv::Vec3b>(0,0);
uchar vec3b0 = img.at<cv::Vec3b>(0,0)[0];
uchar vec3b1 = img.at<cv::Vec3b>(0,0)[1];
uchar vec3b2 = img.at<cv::Vec3b>(0,0)[2];
std::cout<<"vec3b = "<<vec3b<<std::endl;
std::cout<<"vec3b0 = "<<(int)vec3b0<<std::endl;
std::cout<<"vec3b1 = "<<(int)vec3b1<<std::endl;
std::cout<<"vec3b2 = "<<(int)vec3b2<<std::endl;
上述数据类型以及取值范围
Vec类的定义:
template<typename _Tp, int n> class Vec : public Matx<_Tp, n, 1> {...};
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
八、Mat之通道的理解
- 知识点
tips1: 一个图像的通道数是N,就表明每个像素点处有N个数,一个a×b的N通道图像,其图像矩阵实际上是b行N×a列的数字矩阵。
OpenCV中图像的通道可以是1、2、3和4。其中常见的是1通道和3通道,2通道和4通道不常见。
1通道的是灰度图。
3通道的是彩色图像,比如RGB图像。
4通道的图像是RGBA,是RGB加上一个A通道,也叫alpha通道,表示透明度。PNG图像是一种典型的4通道图像。alpha通道可以赋值0到1,或者0到255,表示透明到不透明。
2通道的图像是RGB555和RGB565。2通道图在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。RGB555是16位的,2个字节,5+6+5,第一字节的前5位是R,后三位+第二字节是G,第二字节后5位是B,可见对原图像进行压缩了。
tips2: OpenCV中用imshow( )来显示图像,只要Mat的数据矩阵符合图像的要求,就可以用imshow来显示。二通道好像不可以。。。超过了4通道,就不是图像了,imshow( )也显示不了。
tips3: imshow( )显示单通道图像时一定是灰度图,如果我们想显示红色的R分量,还是应该按三通道图像显示,只不过G和B通道要赋值成0或255.
tips4: 通道分解用split( ),通道合成用merg( ),这俩函数都是mixchannel( )的特例。
九、opencv3将文件夹中的图像路径自动生成txt文件
便于opencv遍历处理图像
#include<iostream>
#include<vector>
#include<io.h>
#include<fstream>
using namespace std;
ofstream ofs("C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\img_pow_sta2.txt", ios::out); //ofstream ofs ofs可以随便起名
vector<int> number;
int num = 0;
void getFiles(string path, vector<string>& files)
{
//文件句柄
long long hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) {
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
num++;
}
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
number.push_back(num);
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
int main() {
char* filepath = (char*)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\0";
vector<string> files;
getFiles(filepath, files);
//char str[30];
int size = (int)files.size();
for (int i = 1; i < size; i++) {
ofs << files[i].c_str();
ofs << " ";
//off << number[i];
ofs << "\n";
}
ofs.close();
return 0;
}
9.1opencv3.x遍历文件夹读取图片
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(){
//用void glob(String pattern, std::vector<String>& result, bool recursive = false);当recursive为false时,仅仅遍历指定文件夹内符合模式的文件,当recursive为true时,会同时遍历指定文件夹的子文件夹
//pattern要绝对路径 其它测试有问题
string pattern = "D:/Acodes/data/*.jpg";
//cout << pattern << endl;
vector<Mat> images;
// 必须cv的String
vector<String> fn;
glob(pattern, fn, false);
size_t count = fn.size();
cout << count << endl;
for (int i = 0; i < count; i++){
images.push_back(imread(fn[i]));
imshow("jaj", images[i]);
waitKey(10);
}
return 1;
}
9.2 OpenCV:glob遍历文件夹下的所有图片
程序如下:
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
//要绝对路径
string path = "D:\\data\\*.bmp";
cout << path << endl;
vector<Mat> images;
// 必须cv的String
vector<String> fn;
glob(path, fn, false);
size_t count = fn.size();
cout << count << endl;
for (int i = 0; i < count; i++)
{
images.push_back(imread(fn[i]));
imshow("pic", images[i]);
waitKey(10);
}
return 0;
}
【注】:
//用void glob(String pattern, std::vector& result, bool recursive = false);
//当recursive为false时,仅仅遍历指定文件夹内符合模式的文件,当recursive为true时,会同时遍历指定文件夹的子文件夹
由于glob遍历图像名称不是按顺序进行遍历的;
在读取图像序列的时候经常要按顺序读取,如在多目标跟踪中;
这时可以sort进行排序;
//获取文件夹下所有图像名称,
// 图像名称按升序排列
int imageNameLists(string filePath, vector<string>& nameArr)
{
vector<cv::String> fn;
cv::glob(filePath, fn, false);
size_t count = fn.size();
if (count==0)
{
cout << "file " << filePath << " not exits"<<endl;
return -1;
}
for (int i = 0; i < count; ++i)
{
//1.获取不带路径的文件名,000001.jpg
string::size_type iPos = fn[i].find_last_of('/') + 1;
string filename = fn[i].substr(iPos, fn[i].length() - iPos);
//cout << filename << endl;
//2.获取不带后缀的文件名,000001
string name = filename.substr(0, filename.rfind("."));
//cout << name << endl;
nameArr.emplace_back(name);
}
sort(nameArr.begin(), nameArr.end(),
[](string a, string b) {return stoi(a) < stoi(b); });
return 0;
}
9.3C++ OpenCV 读取文件夹下的所有图片并重命名(format(), glob())
文中所利用知识点:
1. cv::String cv::format(const char* fmg,...) 用于给要存储的照片指定路径。
2. void glob(String pattern, std::vector<String>& result, bool recursive = false); 用于遍历指定路径下的文件。
代码演示
此代码的目的如下:从一个文件夹中读取后缀为 .jpg的图片,将其存入到另一个指定文件夹,并按顺序命名。
剩余判断,或者输出其他信息,自己可以在代码上进行添加即可。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
string yuanPath = "E:/test/*.jpg";
string mudiPath = "E:/target";
vector<string> result;
glob(yuanPath, result, false); //从文件夹中读取图片路径名
for (int i = 0; i < result.size(); i++) {
string tarpath = mudiPath + format("/img%03d.jpg", i);//设置新的路径
Mat img = imread(result[i], -1);//从路径读取图片,以不变的格式读取
imwrite(tarpath, img);//以新路径存储图片
}
cout << "finish" << endl;
return 0;
}
函数讲解
void glob(String pattern, std::vector<String>& result, bool recursive = false);
参数: pattern 是要遍历的路径; result 是要存放的结果,
recursive 这个参数默认为false,此时glob函数只会查找pattern路径下的的匹配类型的文件,不会匹配其他类型及子文件夹。如果为true的话,会遍历子文件。举个例子如下:
string path2 = "E:/test/*.jpg"
vector<String> result;
glob(path2, result, false);
//只能输出path2路径下的图片名,并且不会遍历子文件
vector<String> result2;
glob(path2, result2, true);
//输出path2路径下的图片名,并且会遍历子文件。
result2 为{”E:/test\3.jpg“,”E:/test\311.jpg”,”E:/test\31111.jpg”,”E:/test\3111111.jpg”,”E:/test\311111111.jpg”,”E:/test\31111111111.jpg”,”E:/test\3111111111111.jpg”,”E:/test\target\img000.jpg”,”E:/test\target\img001.jpg”,”E:/test\target\img002.jpg”,”E:/test\target\img003.jpg”,”E:/test\target\img004.jpg”,”E:/test\target\img005.jpg”,”E:/test\target\img006.jpg”}
result 为{”E:/test\3.jpg“,”E:/test\311.jpg”,”E:/test\31111.jpg”,”E:/test\3111111.jpg”,”E:/test\311111111.jpg”,”E:/test\31111111111.jpg”,”E:/test\3111111111111.jpg”}
cv::String cv::format(const char* fmg,...)
format 函数类似于 sprintf 函数。都是用来处理字符串的格式化。
但注意,%s 并不能处理 string类型,因为读入的地址 可能是对象的地址,并不是字符串的首地址,在vs调试是这个原因。 因此在处理字符串格式的时候,只能处理 c 拥有的格式。( string str = “hanhan”; printf("%s",str);这样是不对的。可以这样输出:printf("%s\n",str.c_str());)
int sprintf(char* str, const char* format,...);
成功后,将返回写入的字符总数。此计数不包括自动附加在字符串末尾的其他空字符。
失败时,将返回负数。
#include <cstdio>
int main ()
{
char buffer [50];
int n, a=5, b=3;
n=sprintf (buffer, "%d plus %d is %d", a, b, a+b);
printf ("[%s] is a string %d chars long\n",buffer,n);
return 0;
}
//[5 plus 3 is 8] is a string 13 chars long
十、opencv读取txt文件
10.1opencv读取txt文件,并赋值为Mat矩阵
目的:opencv读取txt文件,并将txt文件赋值给Mat矩阵
方法:利用fstream类来完成
// vv.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include <stdio.h>
#include <cv.h>
#include "cvaux.h" //必须引此头文件
#include "cxcore.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
fstream file1, file2;//创建文件流对象
file1.open("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
file2.open("H:\\code-practice\\file_practice\\opencv读txt\\2222.txt");
Mat Ky1_Data = Mat::zeros(100, 6, CV_32FC1);//创建Mat类矩阵,定义初始化值全部是0,矩阵大小和txt一致
Mat Ky2_Data = Mat::zeros(100, 6, CV_32FC1);//同理
//将txt文件数据写入到Data矩阵中
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 6; j++)
{
file1 >> Ky1_Data.at<float>(i, j);
//file2 >> Ky2_Data.at<float>(i, j);
}
}
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 6; j++)
{
//file1 >> Ky1_Data.at<float>(i, j);
file2 >> Ky2_Data.at<float>(i, j);
}
}
cout << "矩阵1的数据输出为:" << endl;
cout << Ky1_Data << endl;
cout << endl;
cout << "矩阵2的数据输出为:" << endl;
cout << Ky2_Data << endl;
waitKey(0);
return 0;
}
collet001.txt文件显示
2222.txt显示
【注意】:1.空格辨认数据
2.mat数据数据根据设置补全,例如Mat Ky2_Data = Mat::zeros(100, 6, CV_32FC1);这行代码代表是生成100X6的矩阵,里面填充0,所以即使文件是4列,它会读取第二行
升级
//将txt文件数据写入到Data矩阵中
for (int i = 0; i < Ky1_Data.rows; i++)
{
for (int j = 0; j < Ky1_Data.cols; j++)
{
file1 >> Ky1_Data.at<float>(i, j);
//file2 >> Ky2_Data.at<float>(i, j);
}
}
10.2将mat图片数据写进txt文件里
//#include <iterator>
//#include <vector>
#include<opencv2\opencv.hpp>
#include<core/core.hpp>
#include<highgui/highgui.hpp>
#include<cv.h>
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
/*
* 功能 : 将 Mat 数据写入到 .txt 文件
* 函数 : WriteData
* 访问 : public
* 返回 : -1:打开文件失败;0:写入数据成功;1:矩阵为空
*
* 参数 : fileName [in] 文件名
* 参数 : matData [in] 矩阵数据
*/
int WriteData(string fileName, Mat& matData)
{
int retVal = 0;
// 检查矩阵是否为空
if (matData.empty())
{
cout << "矩阵为空" << endl;
retVal = 1;
return (retVal);
}
// 打开文件
ofstream outFile(fileName.c_str(), ios_base::out); //按新建或覆盖方式写入
if (!outFile.is_open())
{
cout << "打开文件失败" << endl;
retVal = -1;
return (retVal);
}
// 写入数据方法1
// for (int i = 0; i < matData.rows; i++)
// {
// uchar* pixelPtr = matData.ptr<uchar>(i); //获取矩阵每行首地址指针
// for (int j = 0; j < matData.cols*matData.channels(); j++)
// {
// int data = pixelPtr[j];
// outFile << data << "\t"; //每列数据用 tab 隔开
// }
// outFile << endl; //换行
// }
// return (retVal);
//}
// 写入数据方法2
for (int r = 0; r < matData.rows; r++)
{
for (int c = 0; c < matData.cols; c++)
{
int data = matData.at<uchar>(r, c); //读取数据,at<type> - type 是矩阵元素的具体数据格式
outFile << data << "\t"; //每列数据用 tab 隔开
}
outFile << endl; //换行
}
return (retVal);
}
int main(int argc, char* argv[])
{
Mat scr = imread("H:\\code-practice\\file_practice\\opencv读txt\\1111.png");
WriteData("H:\\code-practice\\file_practice\\opencv读txt\\4333.txt", scr);
}
10.3将mat矩阵数据写进txt文件里
//#include <iterator>
//#include <vector>
#include<opencv2\opencv.hpp>
#include<core/core.hpp>
#include<highgui/highgui.hpp>
#include<cv.h>
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
/*
* 功能 : 将 Mat 数据写入到 .txt 文件
* 函数 : WriteData
* 访问 : public
* 返回 : -1:打开文件失败;0:写入数据成功;1:矩阵为空
*
* 参数 : fileName [in] 文件名
* 参数 : matData [in] 矩阵数据
*/
int WriteData(string fileName, Mat& matData)
{
int retVal = 0;
// 检查矩阵是否为空
if (matData.empty())
{
cout << "矩阵为空" << endl;
retVal = 1;
return (retVal);
}
// 打开文件
ofstream outFile(fileName.c_str(), ios_base::out); //按新建或覆盖方式写入
if (!outFile.is_open())
{
cout << "打开文件失败" << endl;
retVal = -1;
return (retVal);
}
// 写入数据方法1
// for (int i = 0; i < matData.rows; i++)
// {
// uchar* pixelPtr = matData.ptr<uchar>(i); //获取矩阵每行首地址指针
// for (int j = 0; j < matData.cols*matData.channels(); j++)
// {
// int data = pixelPtr[j];
// outFile << data << "\t"; //每列数据用 tab 隔开
// }
// outFile << endl; //换行
// }
// return (retVal);
//}
// 写入数据方法2
for (int r = 0; r < matData.rows; r++)
{
for (int c = 0; c < matData.cols; c++)//这里只考虑单通道
{
int data = matData.at<uchar>(r, c); //读取数据,at<type> - type 是矩阵元素的具体数据格式
outFile << data << "\t"; //每列数据用 tab 隔开
}
outFile << endl; //换行
}
return (retVal);
}
int main(int argc, char* argv[])
{
Mat scr = Mat::ones(4, 4, CV_8UC1);
WriteData("H:\\code-practice\\file_practice\\opencv读txt\\63331.txt", scr);
Mat scr1 = Mat::ones(4, 4, CV_8UC2);
WriteData("H:\\code-practice\\file_practice\\opencv读txt\\63332.txt", scr1);
Mat scr2 = Mat::ones(4, 4, CV_8UC3);
WriteData("H:\\code-practice\\file_practice\\opencv读txt\\63333.txt", scr2);
cout << scr << endl;
cout << scr1 << endl;
cout << scr2 << endl;
getchar();
return 0;
}
【说明】1.写入数据方法1打印结果(考虑了通道没问题)
2.写入数据方法2打印结果(未考虑了通道有问题)
写到文本会出现问题,修改如下
// 写入数据方法2
for (int r = 0; r < matData.rows; r++)
{
for (int c = 0; c < matData.cols*matData.channels(); c++)
{
int data = matData.at<uchar>(r, c); //读取数据,at<type> - type 是矩阵元素的具体数据格式
outFile << data<< "\t";
//outFile << data << "\t"; //每列数据用 tab 隔开
}
outFile << endl; //换行
}
return (retVal);
}
10.4 VS打印输出opencv的版本信息
#include<iostream>
#include<opencv2/opencv.hpp>
int main()
{
std::cout << "OpenCV version : " << CV_VERSION << std::endl;
std::cout << "Major version : " << CV_MAJOR_VERSION << std::endl;
std::cout << "Minor version : " << CV_MINOR_VERSION << std::endl;
std::cout << "Subminor version : " << CV_SUBMINOR_VERSION << std::endl;
system("pause");
return 0;
}
10.5 SVM小试牛刀
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
#include<iostream>
#include <cv.h>
#include "cvaux.h" //必须引此头文件
#include "cxcore.h"
#include <iostream>
#include <fstream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
//视觉表示的数据
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//设置训练数据
int labels[4] = { 1, -1, -1, -1 };
//float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
fstream file1;
file1.open("E:\\code\\c++\\Project10\\Project10\\111.txt");
Mat Ky1_Data = Mat::zeros(4, 2, CV_32FC1);//创建Mat类矩阵,定义初始化值全部是0,矩阵大小和txt一致
//将txt文件数据写入到Data矩阵中
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
file1 >> Ky1_Data.at<float>(i, j);
//file2 >> Ky2_Data.at<float>(i, j);
}
}
//Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
//cout << trainingDataMat << endl;
//Mat trainingDataMat(4, 2, CV_32FC1, Ky1_Data);
Mat trainingDataMat = Ky1_Data;
cout << Ky1_Data << endl;
Mat labelsMat(4, 1, CV_32SC1, labels);//4行1列
//训练SVM
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
svm->train(train_data); //参数默认
//svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
//显示SVM的决策区域
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);//蓝绿赋值
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
//显示训练数据
int thickness = -1;//-1表示实心
int lineType = 8;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness, lineType);//半径为5
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
//显示支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
//cout << sv << endl;//输出结果:[501,10; 255,10; 501,255]为什么???
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);//指向矩阵sv的第i行
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);//灰色,半径为6
}
imwrite("result.png", image); //保存图像
imshow("SVM Simple Example", image); //显示图像
waitKey(0);
}
十一、c++读取文本文件中矩阵,得到矩阵的行列数
对于存储一个矩阵的文本文件中的行数,我们可以很轻易的用一个getline函数得到,代码在下面,但是如何得到列数了,是把它一行一行的读进来,然后再写代码分解吗?太麻烦了把,现在想到了一个简便的方法,我们知道文本文件的末尾有一个\n作为换行符,而元素与元素之间有一个空格相隔,所以我们每读取一个数字, 就判断后面一个符号是不是换行符,这样我们就能得到一行的列数. peek 和 get 函数都可以实现, peek只是检查,不会让流前进,而get会让流前进.
#include <stdio.h>
#include <cv.h>
#include "cvaux.h" //必须引此头文件
#include "cxcore.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int getFileColumns(const char * fileName) {
ifstream fileStream;
fileStream.open(fileName, std::ios::_Nocreate);
double tmp;
char c;
int count = 0;
for (int i = 0; i < 10000; i++) {
fileStream >> tmp;
++count;
c = fileStream.peek();
if ('\n' == c)
{
break;
}
}
fileStream.close();
return count;
}
int getFileRows(const char *fileName) {
ifstream fileStream;
string tmp;
int count = 0;
fileStream.open(fileName);
if (fileStream) {
while (getline(fileStream, tmp, '\n')) {
count++;
}
fileStream.close();
}
return count;
}
int main(int argc, char** argv)
{
cout << getFileColumns("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt") << endl;
cout << getFileRows("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt") << endl;
system("pause");
return 0;
}
【2】方法2的txt文件的行数
#include <stdio.h>
#include <cv.h>
#include "cvaux.h" //必须引此头文件
#include "cxcore.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
fstream file1;//创建文件流对象
file1.open("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
char c;
int T0 = 0;//T0是txt文件的行数
while (file1.get(c))
{
if (c == '\n')
T0++;
}
cout << T0 << endl;
file1.close();
file1.open("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
Mat Ky1_Data = Mat::zeros(T0, 6, CV_32FC1);//创建Mat类矩阵,定义初始化值全部是0,矩阵大小和txt一致
//将txt文件数据写入到Data矩阵中
for (int i = 0; i < T0; i++)
{
for (int j = 0; j < Ky1_Data.cols; j++)
{
file1 >> Ky1_Data.at<float>(i, j);
//file2 >> Ky2_Data.at<float>(i, j);
}
}
cout << "矩阵1的数据输出为:" << endl;
cout << Ky1_Data << endl;
cout << endl;
file1.close();
waitKey(0);
return 0;
}
【注意】:将第一种获取方法与opencv与Mat矩阵结合,就可以自适应知道txt文件的行列数。
#include <stdio.h>
#include <cv.h>
#include "cvaux.h" //必须引此头文件
#include "cxcore.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int getFileColumns(const char * fileName);
int getFileRows(const char * fileName);
int main(int argc, char** argv)
{
int gCol = getFileColumns("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
int gRow = getFileRows("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
cout << gCol << endl;
cout << gRow << endl;
fstream file1, file2;//创建文件流对象
file1.open("H:\\code-practice\\file_practice\\特征提取加分类\\action1\\collect001.txt");
Mat Ky1_Data = Mat::zeros(gRow, gCol, CV_32FC1);//创建Mat类矩阵,定义初始化值全部是0,矩阵大小和txt一致
//将txt文件数据写入到Data矩阵中
for (int i = 0; i < Ky1_Data.rows; i++)
{
for (int j = 0; j < Ky1_Data.cols; j++)
{
file1 >> Ky1_Data.at<float>(i, j);
//file2 >> Ky2_Data.at<float>(i, j);
}
}
cout << "矩阵1的数据输出为:" << endl;
cout << Ky1_Data << endl;
system("pause");
//waitKey(0);
return 0;
}
//获取txt文件的列数
int getFileColumns(const char * fileName) {
ifstream fileStream;
fileStream.open(fileName, std::ios::_Nocreate);
double tmp;
char c;
int count = 0;
for (int i = 0; i < 10000; i++) {
fileStream >> tmp;
++count;
c = fileStream.peek();
if ('\n' == c)
{
break;
}
}
fileStream.close();
return count;
}
//获取txt文件的行数
int getFileRows(const char *fileName) {
ifstream fileStream;
string tmp;
int count = 0;
fileStream.open(fileName);
if (fileStream) {
while (getline(fileStream, tmp, '\n')) {
count++;
}
fileStream.close();
}
return count;
}
十二、opencv多分类
12.1 opencv简单多分类
#include<opencv2\opencv.hpp>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main()
{
//训练需要用到的数据
int 标签[4] = { 1, 2, 3, 4 };
float 训练数据[4][2] = { { 31, 12 },{ 65, 220 },{ 440, 350 },{ 400, 400 } };
//转为Mat以调用
Mat 训练Mat(4, 2, CV_32FC1, 训练数据);
Mat 标签label(4, 1, CV_32SC1, 标签);
//训练的初始化
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
//开始训练
svm->train(训练Mat, ROW_SAMPLE, 标签label);
//-----------无关紧要的美工的部分-----------------------
//----其实对每个像素点的坐标也进行了分类----------------
int 宽 = 512, 高 = 512;
Mat 演示图片 = Mat::zeros(高, 宽, CV_8UC3);
Vec3b green(0, 255, 0), blue(255, 0, 0), red(0, 0, 255), black(0, 0, 0);
for (int i = 0; i < 演示图片.rows; ++i)
for (int j = 0; j < 演示图片.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);
float response = svm->predict(sampleMat);
if (response == 1)
演示图片.at<Vec3b>(i, j) = green;
else if (response == 2)
演示图片.at<Vec3b>(i, j) = blue;
else if (response == 3)
演示图片.at<Vec3b>(i, j) = red;
else if (response == 4)
演示图片.at<Vec3b>(i, j) = black;
}
//--------把初始化训练的点画进图片------------
int thickness = -1;
int lineType = 8;
for (int 画点 = 0; 画点 < sizeof(标签) / sizeof(int); 画点++) {
circle(演示图片, Point(训练数据[画点][0], 训练数据[画点][1]), 10, Scalar(255, 255, 255), thickness, -1);
}
// 把 support vectors cout粗来看看……
Mat sv = svm->getSupportVectors();
cout << "Support Vectors为:" << endl;
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);
cout << v[0] << " " << v[1] << endl;
}
//测试测试
Mat 结果;
float teatData[2][2] = { { 20, 11 },{ 310, 411 } };
Mat query(2, 2, CV_32FC1, teatData);
svm->predict(query, 结果);
cout << "分类结果为:" << endl;
cout << 结果;
imshow("SVM显示", 演示图片);
waitKey(-1);
}
结果
12.2 opencv复杂多分类
【注意1】:这个需要改编
【注意2】:2048表示维度,代码的背景是:一张人脸可以用2048维特征来表示
// svm_test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
using namespace cv;
using namespace std;
#define skyface_API extern __declspec(dllexport)
skyface_API int sex_detect(vector<float> &feats, const char* modpth);
Mat traindata(string path, int num)
{
vector<vector<float>> data(num, vector<float>(2048, 0));
ifstream ifs;
ifs.open(path);
for (int i = 0; i < num; i++)
{
for (int j = 0; j < 2048; j++)
{
ifs >> data[i][j];
}
}
ifs.close();
Mat class_n_data(data.size(), data.at(0).size(), CV_32FC1);
for (int i = 0; i < data.size(); i++)
for (int j = 0; j < data.at(0).size(); j++)
class_n_data.at<float>(i, j) = data.at(i).at(j);
return class_n_data;
}
Mat get_traindata3(Mat class1, Mat class2, Mat class3)
{
Mat traindata(class1.rows + class2.rows + class3.rows , 2048, CV_32FC1);
Mat tmp = traindata.rowRange(0, class1.rows);
class1.copyTo(tmp);
tmp = traindata.rowRange(class1.rows, class1.rows + class2.rows);
class2.copyTo(tmp);
tmp = traindata.rowRange(class1.rows + class2.rows, class1.rows + class2.rows + class3.rows);
class3.copyTo(tmp);
cout << "获取到训练数据!" << endl;
return traindata;
}
Mat get_labels3(Mat class1, Mat class2, Mat class3)
{
Mat labels(class1.rows + class2.rows + class3.rows , 1, CV_32FC1);
labels.rowRange(0, class1.rows).setTo(1);
labels.rowRange(class1.rows, class1.rows + class2.rows).setTo(2);
labels.rowRange(class1.rows + class2.rows, class1.rows + class2.rows + class3.rows).setTo(3);
return labels;
}
void trainSVM(Mat traindata, Mat labels, string modelpth)
{
//------------------------ 2. Set up the support vector machines parameters --------------------
CvSVMParams params;
params.svm_type = SVM::C_SVC;
params.C = 0.1;
params.kernel_type = SVM::LINEAR;
params.term_crit = TermCriteria(CV_TERMCRIT_ITER, (int)1e7, 1e-6);
//------------------------ 3. Train the svm ----------------------------------------------------
cout << "Starting training process" << endl;
CvSVM svm;
svm.train(traindata, labels, Mat(), Mat(), params);
cout << "Finished training process" << endl;
svm.save("../data/model_AGE.txt");
}
int sex_detect(vector<float> &feats, const char* modpth)
{
CvSVM SVM;
SVM.load(modpth);
int i;
float* testdata = new float[2048];
for (int i = 0; i < 2048; i++)
{
testdata[i] = feats[i];
}
Mat test = Mat(1, 2048, CV_32FC1, testdata);
float result = SVM.predict(test);
delete[] testdata;
return result;
}
int main()
{
//int labels[3]=[class1,class2,class3];
Mat class1 = traindata("../data/feats_left.txt",40);
Mat class2 = traindata("../data/feats_right.txt",36);
Mat class3 = traindata("../data/feats_pos.txt",48);
//Mat traindata = get_traindata(class1, class2);
//Mat labels = get_labels(class1, class2);
Mat traindata = get_traindata3(class1, class2, class3);
Mat labels = get_labels3(class1, class2, class3);
trainSVM(traindata, labels, "*");
CvSVM SVM;
SVM.load("../data/model_AGE.txt");
ifstream ifs;
float testdata[2048];
ifs.open("../data/feats_test.txt");
for (int i = 0; i < 2048; i++)
{
ifs >> testdata[i];
}
Mat test = Mat(1, 2048, CV_32FC1, testdata);
float result = SVM.predict(test);
if (result == 1)
cout << "左偏30度" << endl;
else if (result == 2)
cout<< "右偏30度" <<endl;
else if (result == 3)
cout<< "正脸" <<endl;
ifs.close();
system("pause");
}
十三、opencv trainAuto用法
13.1 train_auto的函数原型
C++: bool CvSVM::train_auto(const Mat& trainData,
const Mat& responses,
const Mat& varIdx,
const Mat& sampleIdx,
CvSVMParams params,
int k_fold=10,
CvParamGrid Cgrid=CvSVM::get_default_grid(CvSVM::C), CvParamGrid gammaGrid=CvSVM::get_default_grid(CvSVM::GAMMA), CvParamGrid pGrid=CvSVM::get_default_grid(CvSVM::P), CvParamGrid nuGrid=CvSVM::get_default_grid(CvSVM::NU), CvParamGrid coeffGrid=CvSVM::get_default_grid(CvSVM::COEF), CvParamGrid degreeGrid=CvSVM::get_default_grid(CvSVM::DEGREE),
bool balanced=false
)
自动训练函数的参数注释(13个)
前5个参数参考构造函数的参数注释。 k_fold:
交叉验证参数。训练集被分成k_fold的自子集。其中一个子集是用来测试模型,其他子集则成为训练集。所以,SVM算法复杂度是执行k_fold的次数。
*Grid: (6个)对应的SVM迭代网格参数。
balanced: 如果是true则这是一个2类分类问题。这将会创建更多的平衡交叉验证子集。
自动训练函数的使用说明
这个方法根据CvSVMParams中的最佳参数C, gamma, p, nu, coef0, degree自动训练SVM模型。
参数被认为是最佳的交叉验证,其测试集预估错误最小。
如果没有需要优化的参数,相应的网格步骤应该被设置为小于或等于1的值。例如,为了避免gamma的优化,设置gamma_grid.step =
0,gamma_grid.min_val, gamma_grid.max_val 为任意数值。所以params.gamma
由gamma得出。
最后,如果参数优化是必需的,但是相应的网格却不确定,你可能需要调用函数CvSVM::get_default_grid(),创建一个网格。例如,对于gamma,调用CvSVM::get_default_grid(CvSVM::GAMMA)。
该函数为分类运行 (params.svm_type=CvSVM::C_SVC 或者
params.svm_type=CvSVM::NU_SVC) 和为回归运行 (params.svm_type=CvSVM::EPS_SVR
或者
params.svm_type=CvSVM::NU_SVR)效果一样好。如果params.svm_type=CvSVM::ONE_CLASS,没有优化,并指定执行一般的SVM。
这里需要注意的是,对于需要的优化的参数虽然train_auto可以自动选择最优值,但在代码中也要先赋初始值,要不然编译能通过,但运行时会报错。
下面是示例代码
CvSVMParams param;
param.svm_type = CvSVM::EPS_SVR;
param.kernel_type = CvSVM::RBF;
param.C = 1; //给参数赋初始值
param.p = 5e-3; //给参数赋初始值
param.gamma = 0.01; //给参数赋初始值
param.term_crit = cvTermCriteria(CV_TERMCRIT_EPS, 100, 5e-3);
//对不用的参数step设为0
CvParamGrid nuGrid = CvParamGrid(1,1,0.0);
CvParamGrid coeffGrid = CvParamGrid(1,1,0.0);
CvParamGrid degreeGrid = CvParamGrid(1,1,0.0);
CvSVM regressor;
regressor.train_auto(PCA_training,tr_label,NULL,NULL,param,
10,
regressor.get_default_grid(CvSVM::C),
regressor.get_default_grid(CvSVM::GAMMA),
regressor.get_default_grid(CvSVM::P),
nuGrid,
coeffGrid,
degreeGrid);
用上面的代码的就可以自动训练并优化参数。最后,若想查看优化后的参数值,可以使用CvSVM::get_params()函数来获得优化后的CvSVMParams。下面是示例代码:
CvSVMParams params_re = regressor.get_params();
regressor.save("training_srv.xml");
float C = params_re.C;
float P = params_re.p;
float gamma = params_re.gamma;
printf("\nParms: C = %f, P = %f,gamma = %f \n",C,P,gamma);
十四、mat文件与txt文件的相互转换
14.1xx.txt转换为xx.mat
Load(‘路径\xx.txt’)
%加载txt文件,加载成功后,在Workspace中出现与该txt文件同名的变量。
%注意:若txt文件名中有“-”字符,则Workspace中变量名中相应字符变为“_”
Save(‘路径\xx.mat’,‘变量名’)
例:
load('D:\matlabprogram\test-1.txt')
save('D:\matlabprogram\test-1.mat','test_1')
14.2xx.mat转换为xx.txt
(1)不考虑转换后txt文件中数据格式
Load(‘路径\xx.mat’)
Save(‘路径\xx.txt’,‘变量名’,’-ASCII’)
Save函数可用到的文件格式选项如下:
load('路径\collect001.mat')
save('路径\collect001.txt','collect001','-ASCII')
【注意】:上下.mat文件和.txt文件名应该相同
(2)考虑转换后txt文件中数据格式
【方法1】
当前路径下
raw=load('collect001.mat');%将.mat文件的数据读入raw中
%%将IMU的陀螺和加计数据存储到变量imu中(北-东-地 转为 东-北-天)
imu(:,1) = raw.collect001(:,1);
imu(:,2) = raw.collect001(:,2);
imu(:,3) = raw.collect001(:,3);
imu(:,4) = raw.collect001(:,4);
imu(:,5) = raw.collect001(:,5);
imu(:,6) = raw.collect001(:,6);
fid=fopen('imu.txt','w'); %打开txt文件,如果没有会自动创建
len = length(imu);
for k=1:1:len %开始逐行存储
fprintf(fid,' %f %f %f %f %f %f\n',imu(k,1),imu(k,2),imu(k,3),imu(k,4),imu(k,5),imu(k,6));
end
fclose(fid);
打开imu.txt文件,存储效果如下图所示,
【方法2】还没成功
//把矩阵 matrix 保存成任意后缀的文件
//转换成 .txt 举例:mat2txt( 'filename.txt', data );
//转换成 .corr 举例:mat2txt( 'filename.corr',data );
function back = mat2txt( file_Name, matrix )
fop = fopen( file_Name, 'wt' );
[M,N] = size(matrix);
for m = 1:M
for n = 1:N
fprintf( fop, ' %s', mat2str( matrix(m,n) ) );
end
fprintf(fop, '\n' );
end
back = fclose( fop ) ;
【方法3】还没成功
改成用load函数和fprintf函数相结合的方法,一行一行的读进来,然后写到对应的TXT文件中去。(因为我最终要的TXT文件需要与之前的文件名一致,所以会多一些比较细节的操作)
%topFolder表示.mat文件的上层目录
%outputFolder表示最终输出TXT文件的上层目录
function flag = mat2txt(topFolder, outputFolder)
%获取所有的.mat文件
AllFile = strcat(topFolder,'\*.','mat');
files = dir(AllFile);
len =length(files);
for i = 1:len
fileName = files(i).name;
%载入相应的mat文件
loadFile = load(strcat(topFolder,fileName));
%创建输出的TXT文件,windows中如果想用‘\n’来表示换行,需要使用'wt'读写模式
outputFile = fopen(strcat(outputFolder,strrep(fileName,'.mat',''),'.txt'),'wt');
%向txt文件中写数据
%dataVariable表示.mat文件中含有的字段名
%由于字段不同数据格式可能不同,所以一次只支持一个字段,根据自己的需要进行修改
[m,n] = size(loadFile.dataVariable);
for j = 1:m
for k =1:n
fprintf(outputFile, '%d ',loadFile.dataVariable(j,k));
end
fprintf(outputFile,'\n');
end
flag = fclose(outputFile);
end
end
十五、OpenCv中计算图像像素最大值、最小值、均值和方差
15.1寻找图像像素的最大值最小值
寻找图像最大值最小值的函数 minMaxLoc() 函数
minMaxLoc() 函数原型
void cv::minMaxLoc(InputArray src, double * minVal, double * maxVal=0,
Point * minLoc=0,Point * maxLoc=0,InputArray mask = noArray())
其中,src为需要寻找最大值和最小值的图像或者矩阵,要求必须是单通道;minVal:图像或矩阵的最小值;maxVal:图像或矩阵的最大值;minLoc:图像或矩阵的最小值在矩阵中的坐标;maxLoc:图像或矩阵的最大值在矩阵中的坐标;mask:掩膜,用于设置在图像或矩阵中的指定区域寻找最值。
minMaxLoc() 函数输出最值的位置为按行扫描从左到右第一次检测到最值的位置,同时输入参数时一定
Point 数据类型:该数据类型用于表示图像的像素坐标,水平方向为x轴,垂直方向为y轴,Point(x,y)。针对二维坐标数据类型,定义了整型坐标 cv::Point2i(或者cv::Point)、double类型坐标cv::Point2d、浮点型坐标cv::Point2f。对于三维坐标类型定义与二维坐标数据类型相似,只需要将 2 改成 3 即可。对于坐标中 x、y 轴具体数据,可以通过变量属性进行访问,例如:Point.x 可以读取坐标的 x 轴数据。
src为需要寻找最大值和最小值的图像或者矩阵,要求必须是单通道,对于多通道矩阵数据,需要用 cv::Mat::reshape() 将多通道变成单通道,或者分别寻找每个通道的最值,然后进行比较。
cv::Mat::reshape() 函数原型
Mat cv::Mat::reshape(int cn, int rows = 0)
其中 cn:转换后矩阵的通道数;rows:转换后矩阵的行数。如果参数为0,则转换后行数与转化前行数相同。
列还是原来的
综合示例:
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
system("color F0"); //更改输出界面颜色
float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
Mat img = Mat(3, 4, CV_32FC1, a); //单通道矩阵
Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩阵
double minVal, maxVal; //用于存放矩阵中的最大值和最小值
Point minIdx, maxIdx; // 用于存放矩阵中的最大值和最小值在矩阵中的位置
cout << img << endl;
cout << imgs << endl;
/*寻找单通道矩阵中的最值*/
minMaxLoc(img, &minVal, &maxVal, &minIdx, &maxIdx);
cout << "img中最大值是:" << maxVal << " " << "在矩阵中的位置:" << maxIdx << endl;
cout << "img中最小值是:" << minVal << " " << "在矩阵中的位置:" << minIdx << endl;
/*寻找多通道矩阵中的最值*/
Mat imgs_re = imgs.reshape(1, 4); //将多通道矩阵变成单通道矩阵
cout << imgs_re << endl;
minMaxLoc(imgs_re, &minVal, &maxVal, &minIdx, &maxIdx);
cout << "imgs中最大值是:" << maxVal << " " << "在矩阵中的位置:" << maxIdx << endl;
cout << "imgs中最小值是:" << minVal << " " << "在矩阵中的位置:" << minIdx << endl;
return 0;
}
运行结果:mat矩阵是从0开始记录的
15.2计算图像的平均值和标准差
图像的平均值表示图像整体的亮暗程度,平均值越大,则图像整体越亮。标准差表示图中明暗变化的对比程度,标准差越大,表示图像中明暗变化越明显。
mean()函数原型
cv::Scalar cv::mean(InputArray src, InputArray mask= noArray())
其中,src:待求平均值的图像矩阵(通道数可以位1~4)。mask:掩模,用于标记求取那些区域的平均值。
该函数求取每个通道的平均值。该函数返回值是一个cv::Scalar 类型的变量,函数的返回值有 4 位,分别表示输入图像的4 个通道的平均值,如果输入图像只有一个通道,那么返回值的后 3 位都是0.可以通过 cv::Scalar[n] 查看第 n 个通道的平均值。
meanStdDev()函数原型
void cv::meanStdDev(InputArray src, OutputArray mean, OutputArray stddev,
InputArray mask =noArray())
其中,src:待求平均值的图像矩阵;mean:图像每个通道的平均值,参数为Mat类型变量;
stddev:图像每个通道的标准差,参数为Mat类型变量;mask:掩模,用于标记求取那些区域的平均值和标准差。该函数没有返回值。图像的均值和标准差输出在函数第二个参数和第三个参数中。
综合示例:包括提取某一列
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
system("color F0"); //更改输出界面颜色
float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
Mat img = Mat(3, 4, CV_32FC1, a); //单通道矩阵
Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩阵
Mat A = Mat::zeros(3, 1, CV_32FC1);
img.col(0).copyTo(A.col(0));//提取某一列
cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
Scalar myMean;
Scalar myMean1;
myMean = mean(imgs);
myMean1 = mean(A);
cout << "imgs均值=" << myMean << endl;
cout << "img第一列均值=" << myMean1 << endl; //提取某一列平均值
cout << "imgs第一个通道的均值=" << myMean[0] << " "
<< "imgs第二个通道的均值=" << myMean[1] << endl << endl;
cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
Mat myMeanMat, myStddevMat;
meanStdDev(img, myMeanMat, myStddevMat);//可以同时输出2个参数
cout << "img均值=" << myMeanMat << " " << endl;
cout << "img标准差=" << myStddevMat << endl << endl;
meanStdDev(imgs, myMeanMat, myStddevMat);
cout << "imgs均值=" << myMeanMat << " " << endl << endl;
cout << "imgs标准差=" << myStddevMat << endl;
return 0;
}
运行结果
升级,循环提取某一列
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
system("color F0"); //更改输出界面颜色
float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
Mat img = Mat(3, 4, CV_32FC1, a); //单通道矩阵
Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩阵
Mat A = Mat::zeros(3, 1, CV_32FC1);
//img.col(0).copyTo(A.col(0));//提取某一列
cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
Scalar myMean;
Scalar myMean1;
myMean = mean(imgs);
//循环提取img Mat矩阵每一列
for (int i = 0; i < 4; i++)
{
img.col(i).copyTo(A.col(0));//提取某一列
myMean1 = mean(A);
cout << "img每一列均值=" << myMean1 << endl; //提取某一列平均值
}
cout << "imgs均值=" << myMean << endl;
//cout << "img第一列均值=" << myMean1 << endl; //提取某一列平均值
cout << "imgs第一个通道的均值=" << myMean[0] << " "
<< "imgs第二个通道的均值=" << myMean[1] << endl << endl;
cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
Mat myMeanMat, myStddevMat;
meanStdDev(img, myMeanMat, myStddevMat);//可以同时输出2个参数
cout << "img均值=" << myMeanMat << " " << endl;
cout << "img标准差=" << myStddevMat << endl << endl;
meanStdDev(imgs, myMeanMat, myStddevMat);
cout << "imgs均值=" << myMeanMat << " " << endl << endl;
cout << "imgs标准差=" << myStddevMat << endl;
return 0;
}
输出结果:
15.3opencv提取Mat中的某些行和列
原始Mat格式数据:
cv::Mat A = Mat::zeros(4, 5, CV_32F);【4行5列,高4宽5】
1、提取行
函数:Mat::rowRange(int startrow, int endrow)
例:提取第0~2行(包括第2行)
cv::Mat B = A.rowRange(0, 3).clone() ;
2、提取列
函数:Mat::colRange(int startcol, int endcol)
例:提取第2~4列(包括第4列)
cv::Mat C = A.colRange(2, 5).clone() ;
注意,rowRange(start,end)与colRange(start,end)均包括左边界,不包括右边界。
3、copyTo()函数
Mat c = Mat::zeros(3, 5, CV_32F);
Mat a = Mat::ones(3, 6, CV_32F);
1)将c的第1列赋值给a
c.col(0).copyTo(a.col(0));
2)将c的1-5列赋值给a
c.copyTo(a.colRange(1, 6));
更多推荐
所有评论(0)