QQ: 282397369

6. 学习对象分类

对每个分离的对象进行分类
训练系统,以便能够学习必需的参数,从而决定将哪个特定标签分配给检测到的对象
机器学习的基本概念,以便对具有不同标签的图像进行分类

6.1 技术要求

6.2 机器学习概念介绍

机器学习:计算机无须明确编程就能够开展学习的能力
机器学习涉及到人工智能中的模式识别和学习理论,并且与计算统计学有关
应用:OCR、垃圾邮件过滤、搜索引擎以及数以千计的计算机视觉应用程序。
机器学习算法从输入数据中进行学习的方式:

  • 监督学习:从一组有标签的数据中学习。其目标是学习模型的参数以及能使计算机对数据和输出标签结果之间的关系进行映射的规则
  • 无监督学习
  • 强化学习
    根据结果划分:
  • 分类
  • 回归
  • 聚类
  • 密度估计
    比较流行的方法:
  • 支持向量机
  • 人工神经网络
  • 聚类
  • k-最近邻
  • 决策树
  • 深度学习

OpenCV机器学习算法

继承自StatModel类的8种算法:

  • 人工神经网络
  • 随机树
  • 期望最大化
  • k-最近邻
  • 逻辑回归
  • 朴素贝叶斯分类器
  • 支持向量机
  • 随机梯度下降SVM
    StatModel类是所有机器学习算法的基类,它提供预测和所有读写功能,对于保存和读取机器学习参数和训练数据非常重要。
    训练方法:耗时、耗资源
    基类最重要的两个函数:
  • train
bool train(const Ptr<TrainData>& trainData, int falgs = 0);
bool train(InputArray samples, int layout, INputArray responses);
Ptr<_Tp> train(const Ptr<TrainData>& data, int falgs = 0);

参数:

  • TrainData:训练数据可以从TrainData类加载或创建,该类可帮助开发人员从机器学习算法中创建和提取训练数据。

  • samples:一系列的训练阵列样本,例如采用机器学习算法所需格式的训练数据

  • layout:ROW_SAMPLE(训练样本是矩阵行)或COL_SAMPLE(训练样本是矩阵列)。

  • responses:与样本数据相关的响应向量。

  • flags:由每个方法定义的可选标志。

  • predict
    float StatModel::predict(InputArray samples, OutputArray results=noArray(), int flags = 0);
    参数:

  • samples:用于预测模型结果的输入样本可以包含任意数量的数据,无论是单个还
    是多个。

  • results:每个输入行样本的结果(由先前训练的模型的算法计算)。

  • flags:这些可选标志与模型有关。某些模型(如Boost)由SVM StatModel::RAW
    _OUTPUT标志识别,这使得该方法可以返回原始结果(总和),而不是类标签。

StatModel类为其他非常有用的方法提供接口:

  • isTrained(),如果模型是训练过的,则返回true
  • isClassifier(),如果模型是分类器,则返回true,如果是回归,则返回false
  • getVarCount()返回训练样本中的变量数
  • save(const string&filename)将模型保存在指定的文件中
  • Ptr<_Tp>load(const string&filename)从指定的文件中加载<indexentry conte
    nt=“StatModel class: Ptr load (const string & filename)”>模型,例如-Ptr
    svm=StatModel::load(“my_svm_model.xml”)
  • calcError(const Ptr&data,bool test,OutputArray resp)从测试数据计算错误,其中数据是训练数据。如果test参数为true,则该方法从测试数据子集计算错误;如果为false,则该方法计算所有训练数据的错误。resp是可选的输出结果。

6.3 计算机视觉和机器学习工作流程

具备机器学习的计算机视觉应用具有共同的基本结构,这种结构分为不同的步骤

  1. 预处理:去除光和噪声、滤波、模糊。。。

  2. 分割:提取图像中的感兴趣区域,并将每个区域隔离为感兴趣的唯一对象

  3. 特征提取:特征用于描述对象,可以是对象区域、轮廓、纹理图案、像素等

  4. 分类结果:对象的描述符[特征向量或特征集],是用于描述对象的特征,用它们来训练或预测模型。须创建一个大型的特征数据集,其中预先处理了数千个图像,在选择的训练模型函数中使用提取的特征(图像/对象的特征),如面积、大小和纵横比。在这里插入图片描述

  5. 后处理:针对输出数据进行后处理
    在这里插入图片描述

6.4 自动对象检查分类示例

步骤

  1. 对于每个图像
  • 预处理图像
  • 分割图像
  1. 对于图像中的每个对象
  • 提取特征
  • 使用相应的标签(螺母、螺钉、垫圈)将这些提取出的特征添加到训练特征向量中
  1. 创建SVM模型
  2. 使用训练特征向量训练SVM模型
  3. 预处理输入图像以便对每个被分割的对象进行分类
  4. 分割输入图像
  5. 对于检测到的每个对象
  • 提取特征
  • 用SVM预测它
  • 建模
  • 在输出图像中绘制结果

6.4.1 特征提取

不同对象可能的特征:

  • 对象的面积
  • 纵横比,即宽度除以边界矩形的高度
  • 孔的数量
  • 轮廓边数
vector<vector<float>> ExtractFeatures(Mat img, vector<int>* left = NULL, vector<int>* top = NULL) {
	vector<vector<float>> output;
	vector<vector<Point>> contours;
	Mat input = img.clone();
	vector<Vec4i> hierarchy;
	findContours(input, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
	if(contours.size() == 0)
		return output;
	RNG rng(0xFFFFFFFF);
	for(auto i = 0; i < contours.size(); ++i) {
		Mat mask = Mat::zeros(img.rows, img.cols, CV_8UC1);
		drawContours(mask, contours, i, Scalar(1), FILLED, LIN8, hierarchy, 1);
		Scalar areas_s = sum(mask);
		float area = area_s[0];
		if(area > 500) {
			RotatedRect r = minAreaRect(contours[i]);
			float width  r.size.width;
			float heigh = r.size.height;
			float ar = (width < height) ? height / width : width / height;
			vector<float> row;
			row.push_back(area, ar);
			output.push_back(row);
			if(left != NULL)	
				left->push_back((int)(r.center.x));
			if(top != NULL)
				top->push_back((int)(r.center.y));
			miw->addImage("ExtractF Features", mask * 255);
			miw->render();
			waitKey(10);
		}
	}
	return output;
}

6.4.2 训练SVM模型

用监督学习,为每个对象及其相应的标签获取一组图像。
创建模型。OpenCV使用Ptr模板类进行指针管理 Ptr svm;

void trainAndTest()
{
  vector< float > trainingData;
  vector< int > responsesData;
  vector< float > testData;
  vector< float > testResponsesData;

  int num_for_test= 20;

  // Get the nut images
  readFolderAndExtractFeatures("../data/nut/tuerca_%04d.pgm", 0, num_for_test, trainingData, responsesData, testData, testResponsesData);
  // Get and process the ring images
  readFolderAndExtractFeatures("../data/ring/arandela_%04d.pgm", 1, num_for_test, trainingData, responsesData, testData, testResponsesData);
  // get and process the screw images
  readFolderAndExtractFeatures("../data/screw/tornillo_%04d.pgm", 2, num_for_test, trainingData, responsesData, testData, testResponsesData);
  
  cout << "Num of train samples: " << responsesData.size() << endl;

  cout << "Num of test samples: " << testResponsesData.size() << endl;
  
  // Merge all data 
  Mat trainingDataMat(trainingData.size()/2, 2, CV_32FC1, &trainingData[0]);
  Mat responses(responsesData.size(), 1, CV_32SC1, &responsesData[0]);

  Mat testDataMat(testData.size()/2, 2, CV_32FC1, &testData[0]);
  Mat testResponses(testResponsesData.size(), 1, CV_32FC1, &testResponsesData[0]);
  
	Ptr<TrainData> tdata= TrainData::create(trainingDataMat, ROW_SAMPLE, responses);
	
	svm = cv::ml::SVM::create();
	svm->setType(cv::ml::SVM::C_SVC);
	svm->setNu(0.05);
	svm->setKernel(cv::ml::SVM::CHI2);
	svm->setDegree(1.0);
	svm->setGamma(2.0);
	svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
	svm->train(tdata);

  if(testResponsesData.size()>0){
    cout << "Evaluation" << endl;
    cout << "==========" << endl;
    // Test the ML Model
    Mat testPredict;
    svm->predict(testDataMat, testPredict);
    cout << "Prediction Done" << endl;
    // Error calculation
    Mat errorMat= testPredict!=testResponses;
    float error= 100.0f * countNonZero(errorMat) / testResponsesData.size();
    cout << "Error: " << error << "\%" << endl;
    // Plot training data with error label
    plotTrainData(trainingDataMat, responses, &error);

  }else{
    plotTrainData(trainingDataMat, responses);
  }
}

bool readFolderAndExtractFeatures(string folder, int label, int num_for_test, 
  vector<float> &trainingData, vector<int> &responsesData,  
  vector<float> &testData, vector<float> &testResponsesData)
{
  VideoCapture images;
  if(images.open(folder)==false){
    cout << "Can not open the folder images" << endl;
    return false;
  }
  Mat frame;
  int img_index=0;
  while( images.read(frame) ){
	 Preprocess image
    Mat pre= preprocessImage(frame);
    // Extract features
    vector< vector<float> > features= ExtractFeatures(pre);
    for(int i=0; i< features.size(); i++){
      if(img_index >= num_for_test){
        trainingData.push_back(features[i][0]);
        trainingData.push_back(features[i][1]);
        responsesData.push_back(label);    
      }else{
        testData.push_back(features[i][0]);
        testData.push_back(features[i][1]);
        testResponsesData.push_back((float)label);    
      }
    }
    img_index++;
  }
  return true;  
}

bool readFolderAndExtractFeatures(string folder, int label, int num_for_test, 
  vector<float> &trainingData, vector<int> &responsesData,  
  vector<float> &testData, vector<float> &testResponsesData)
{
  VideoCapture images;
  if(images.open(folder)==false){
    cout << "Can not open the folder images" << endl;
    return false;
  }
  Mat frame;
  int img_index=0;
  while( images.read(frame) ){
	 Preprocess image
    Mat pre= preprocessImage(frame);
    // Extract features
    vector< vector<float> > features= ExtractFeatures(pre);
    for(int i=0; i< features.size(); i++){
      if(img_index >= num_for_test){
        trainingData.push_back(features[i][0]);
        trainingData.push_back(features[i][1]);
        responsesData.push_back(label);    
      }else{
        testData.push_back(features[i][0]);
        testData.push_back(features[i][1]);
        testResponsesData.push_back((float)label);    
      }
    }
    img_index++;
  }
  return true;  
}

6.4.3 输入图像预测

  • 加载图像并将其转换为灰度值
  • 通过preprocessImage函数应用预处理任务
  • ExtractFeatures提取图像中出现的所有对象的向量特征和每
    个对象的左上角位置
  • 将检测到的每个对象存储为特征行,然后把每行转换为一行的Mat和两个特征
  • 用StatModel SVM的predict函数预测单个对象,预测结果是被检测的对象的标签。
  • 用stringstream存储文本,并用Scalar存储每个不同标签的颜色
  • 用在ExtractFeatures函数中检测到的位置在每个对象上绘制标签文本

6.5 总结

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐