OpenCV之机器学习对象分类(二)

计算机视觉和机器学习工作流程
预处理/分割/特征提取/分类结果/后处理
1,输入图像的预处理,包括去除光和噪声、滤波、模糊等,参考上一篇文章。

Mat removeLight(Mat img, Mat pattern)
{
 	Mat aux;
  	// Require change our image to 32 float for division
  	Mat img32, pattern32;
  	img.convertTo(img32, CV_32F);
  	pattern.convertTo(pattern32, CV_32F);
  	// Divide the imabe by the pattern
  	aux= 1-(img32/pattern32);
  	// Scale it to convert o 8bit format
  	aux=aux*255;
  	// Convert 8 bits format
  	aux.convertTo(aux, CV_8U);

  	//equalizeHist( aux, aux );
  	return aux;
}

Mat light_pattern;
Mat preprocessImage(Mat input)
{
	if (input.channels() == 3)
		cvtColor(input, input, COLOR_RGB2GRAY);
	Mat result;
	// Remove noise
	Mat img_noise, img_box_smooth;
	medianBlur(input, img_noise, 3);
	//Apply the light pattern
	Mat img_no_light;
	img_noise.copyTo(img_no_light);
	img_no_light = removeLight(img_noise, light_pattern);

	// Binarize image for segment
	threshold(img_no_light, result, 30, 255, THRESH_BINARY);

	return result;
}

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

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

3,特征提取,这些特征是对象特征的向量,特征用于描述对象,可以是对象区域、轮廓、纹理图案、像素等。
提高机器学习算法准确性的特征或特性,不同对象(螺母、螺钉和垫圈)的这些可能特征如下:
对象的面积
纵横比,即宽度除以边界矩形的高度
孔的数量
轮廓边数
本例子使用前两个特征,即面积和纵横比。为了提取这些特征,将用黑/白ROI图像作为输入,上一篇文章中分割的结果作为输入,采用findContours算法分割对象。
图像中检测到的每个对象的左侧和顶部位置两个向量作为参数返回。函数的输出是浮点向量的向量,即矩阵,每行包含检测到的每个对象的特征。
OpenCV的findContours函数会修改输入图像,创建一个输入图像的副本,该函数检索图像中的每个对象,如果没有检测到任何轮廓返回一个空的输出矩阵。
如果检测到对象,每个轮廓将在黑色图像上绘制白色图像,用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);
	//Check the number of objects detected
	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, LINE_8, hierarchy, 1);
		Scalar area_s = sum(mask);
		float area = area_s[0];
		if (area > 500)
		{
			RotatedRect r = minAreaRect(contours[i]);
			float width = r.size.width;
			float height = r.size.height;
			float ar = (width < height) ? height / width : width / height;

			vector<float> row;
			row.push_back(area);
			row.push_back(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);
			}
			//Add image to the multiple image window class, See the class on full github code
			waitKey(0);
		}
	}
	return output;
}

4,训练SVM模型,描述符是用于描述对象的特征,我们用它们来训练或预测模型。
使用带有标签的训练数据集来训练模型,并且模型的预测结果是可能的标签之一
StateModel类是所有机器学习算法的基类,它提供预测和所有读写功能。在机器学习中,最耗时和最耗费计算资源的部分是训练方法,建议保存训练好的模型和已经学习过的所有参数,在后续操作中只需加载/读取保存的模型即可。

bool train(const Ptr<TrainData>& data, int flags=0)
bool train(InputArray sample, int layout, InputArray responses)
template<typename _Tp> static Ptr<_Tp> train(const Ptr<TrainData>& data, int flags=0)

TrainData:训练数据可以从TrainData类加载或创建,从机器学习算法中创建和提取训练数据。
samples:一系列训练阵列样本
layout:ROW_SAMPLE(训练样本是矩阵行)或COL_SAMPLE(矩阵列)
responses:与样本数据相关的响应向量
flags:由每个方法定义的可选标志
最后一个训练方法创建并训练一个_TP类类型的模型。

float predict( InputArray samples, OutputArray results=noArray(), int flags=0 )

samples:用于预测模型结果的输入样本
result:每个输入行样本的结果(由先前训练的模型的算法计算)
flags:可选标志与模型有关
StateModel类为其他非常有用的方法提供接口:
isTrained()
isClassifier()
getVarCount()
save(const string& filename)将模型保存在指定文件中
Ptr<_TP> load(const string& filename)从指定文件中加载模型,例如Ptr svm = StateModel::load(“my_svm_model.xml”)
CalcError(const Ptr& data, bool test, OutputArray resp)从测试数据计算错误,test为true从训练数据子集计算错误,为false计算所有训练数据的错误。

Scalar green(0, 255, 0), blue(255, 0, 0), red(0, 0, 255);
void plotTrainData(Mat trainData, Mat labels, float *error = NULL)
{
	float area_max, ar_max, area_min, ar_min;
	area_max = ar_max = 0;
	area_min = ar_min = 99999999;
	// Get the min and max of each feature for normalize plot image
	for (int i = 0; i < trainData.rows; i++) {
		float area = trainData.at<float>(i, 0);
		float ar = trainData.at<float>(i, 1);
		if (area > area_max)
			area_max = area;
		if (ar > ar_max)
			ar_max = ar;
		if (area < area_min)
			area_min = area;
		if (ar < ar_min)
			ar_min = ar;
	}
	
	//Create image for plot
	Mat plot = Mat::zeros(512, 512, CV_8UC3);
	//Plot each of two features in a 2D graph using an image
	//where x is area and y is aspect ratio
	for (int i = 0; i < trainData.rows; i++) {
		//Set the X Y pos for each data
		float area = trainData.at<float>(i, 0);
		float ar = trainData.at<float>(i, 1);
		int x = (int)(512.0f*((area - area_min) / (area_max - area_min)));
		int y = (int)(512.0f*((ar - ar_min) / (ar_max - ar_min)));

		//Get label
		int label = labels.at<int>(i);
		//Set color depend of label
		Scalar color;
		if (label == 0)
			color = green;//NUT
		else if (label == 1)
			color = blue;//RING
		else if (label == 2)
			color = red;//SCREW
		circle(plot, Point(x, y), 3, color, -1, 8);
	}
	if (error != NULL) {
		stringstream ss;
		ss << "Error:" << *error << "\%";
		putText(plot, ss.str().c_str(), Point(20, 512 - 40), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(200, 200, 200), 1, LINE_AA);
	}
}

#include <opencv2/ml.hpp>
using namespace cv::ml;
Ptr<SVM> 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/nut_%4d.pgm", 0, num_for_test, trainingData, responsesData, testData, testResponsesData);
	//Get and process the ring images
	readFolderAndExtractFeatures("../data/ring/ring_%4d.pgm", 1, num_for_test, trainingData, responsesData, testData, testResponsesData);
	//Get and process the screw images
	readFolderAndExtractFeatures("../data/screw/screw_%4d.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);
	}
}

在预测之后,只需计算testPredict和testResponses(原始标签)的差异,如果存在差异计算差异有多少,并将其除以测试总数以计算误差。
main调用

const char* keys =
{
   "{help h usage ? | | print this message}"
   "{@image || Image to classify}"
};
int main(int argc, const char** argv)
{
	CommandLineParser parser(argc, argv, keys);
	parser.about("Chapter 6. Classification v1.0.0");
	//If requires help show
	if (parser.has("help"))
	{
		parser.printMessage();
		return 0;
	}

	String img_file = parser.get<String>(0);
	String light_pattern_file = "../data/pattern.pgm";

	// Check if params are correctly parsed in his variables
	if (!parser.check())
	{
		parser.printErrors();
		return 0;
	}

	// Load image to process
	Mat img = imread(img_file, 0);
	if (img.data == NULL) {
		cout << "Error loading image " << img_file << endl;
		return 0;
	}
	Mat img_output = img.clone();
	cvtColor(img_output, img_output, COLOR_GRAY2BGR);

	// Load image to process
	light_pattern = imread(light_pattern_file, 0);
	if (light_pattern.data == NULL) {
		// Calculate light pattern
		cout << "ERROR: Not light patter loaded" << endl;
		return 0;
	}
	medianBlur(light_pattern, light_pattern, 3);

	trainAndTest();

	 Preprocess image
	Mat pre = preprocessImage(img);
	End preprocess

	// Extract features
	vector<int> pos_top, pos_left;
	vector< vector<float> > features = ExtractFeatures(pre, &pos_left, &pos_top);

	cout << "Num objects extracted features " << features.size() << endl;

	for (int i = 0; i < features.size(); i++) {

		cout << "Data Area AR: " << features[i][0] << " " << features[i][1] << endl;

		Mat trainingDataMat(1, 2, CV_32FC1, &features[i][0]);
		cout << "Features to predict: " << trainingDataMat << endl;
		float result = svm->predict(trainingDataMat);
		cout << result << endl;


		stringstream ss;
		Scalar color;
		if (result == 0) {
			color = green; // NUT
			ss << "NUT";
		}
		else if (result == 1) {
			color = blue; // RING
			ss << "RING";
		}
		else if (result == 2) {
			color = red; // SCREW
			ss << "SCREW";
		}

		putText(img_output,
			ss.str(),
			Point2d(pos_left[i], pos_top[i]),
			FONT_HERSHEY_SIMPLEX,
			0.4,
			color);

	}
	// Show images
	imshow("output", img_output);
	waitKey(0);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值