SVM模型训练与分类的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。

011110

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之通道的理解

  1. 知识点
    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);45列,高45

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));

猜你喜欢

转载自blog.csdn.net/weixin_50624597/article/details/124259032