学习目标分类

Overview

还是OpenCV By Example, 承接上文的目标分割和检测,这里用SVM进行了分类的学习。由于从PACKT下载的代码是基于QT的,而我。。开始编译OpenCV没有选QT支持,重新编译也一致出错,就暂时没去用QT的按钮那些功能,也就没QT进行编译了。。扯多了,回到正题。代码应该是有一些bug的,折腾了一下午才调试好…

原理还是比较简单的,处理的流程也较为清晰。对图片主要是进行了中值滤波,去除光纹,和灰度二值化提取轮廓的操作。抽取螺丝环,螺母,螺丝钉的面积和长宽比这两个特征进行训练,测试误差在2%以下,看书上的截图,原作的误差在7%左右…可能和OpenCV的改进有关吧。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

//
// Created by shensir on 17-5-17.
//

#include <iostream>
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/ml.hpp"

using namespace std;
using namespace cv;
using namespace cv::ml;

// 一些全局变量
Mat light_pattern;
Ptr<SVM> svm;
Scalar green(0,255,0), blue (255,0,0), red (0,0,255);

// 光纹去除
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= 255*(1-(img32/pattern32));
// Convert 8 bits format
aux.convertTo(aux, CV_8U);

return aux;
}



// 特征抽取,这里抽取面积及宽高比
vector<vector<float> > ExtractFeatures(Mat img, vector<int>*left=NULL, vector<int>*top=NULL)
{
// count_num +=1;
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;

for(int 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){// 如果area大于面积最小阀值
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));
}
// 显示特征抽取的结果
imshow("Extract Features", mask*255);
waitKey(10);
}
}
return output;
}



// 图片预处理
Mat preprocessImage(Mat input)
{
// 灰度化
if(input.channels()!=1)
cvtColor(input, input, COLOR_BGR2GRAY);

Mat result;
// Remove noise
Mat img_noise;
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;
}


// 从文件批量读取图片,并进行特征抽取
bool readFolderAndExtractFeatures(string folder, int label, int num_for_test,
vector<float>&trainingData, vector<int>&responeseData,
vector<float>&testData, vector<float>&testResponsesData)
{
VideoCapture images(folder);
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)){

// 预处理图像
images>>frame; // 书上参考代码没有这句,貌似bug...
if(!frame.empty()){

// 预处理
Mat pre = preprocessImage(frame);
// 提取特征
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]);
responeseData.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;

};


// 做图
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);
}
imshow("plot", plot);
// waitKey(0);

}


// 训练并测试误差
void trainAndTest() {
vector<float> trainingData;
vector<int> responsesData;
vector<float> testData;
vector<float> testResponsesData;

int num_for_test = 20;


// 获取螺母图像
readFolderAndExtractFeatures("/home/shensir/Documents/MyPrograming/Cpp/books/CV/2-ing/9781785280948_Code/"
"B04283_06_code/data/nut/tuerca_%04d.pgm", 0,
num_for_test, trainingData, responsesData, testData, testResponsesData);

// 获取螺丝圈图像
readFolderAndExtractFeatures("/home/shensir/Documents/MyPrograming/Cpp/books/CV/2-ing/9781785280948_Code/"
"B04283_06_code/data/ring/arandela_%04d.pgm", 1,
num_for_test, trainingData, responsesData, testData, testResponsesData);


// 获取并处理螺丝
readFolderAndExtractFeatures("/home/shensir/Documents/MyPrograming/Cpp/books/CV/2-ing/"
"9781785280948_Code/B04283_06_code/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;

// 合并
// Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

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 testRespones(testResponsesData.size(), 1, CV_32FC1, &testResponsesData[0]);

svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::CHI2);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));

svm->train(trainingDataMat, ROW_SAMPLE, responses);

if(testResponsesData.size() >0){
cout<<"Evaluation"<<endl;
cout<<"============"<<endl;
// test the ML model
Mat testPredict;
svm->predict(testDataMat, testPredict);
cout<<"Prediction Done"<<endl;
// error handle
Mat errorMat = testPredict != testRespones;
float error = 100.0f * countNonZero(errorMat) / testResponsesData.size();
cout<<"Error: "<<error<<"%"<<endl;
//plot data
plotTrainData(trainingDataMat, responses, &error);

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



int main( int argc, const char** argv )
{

// Load image to process
Mat img= imread("/home/shensir/Documents/MyPrograming/Cpp/books/CV/2-ing/9781785280948_Code/B04283_06_code/data/test.pgm", IMREAD_GRAYSCALE);
if(img.data==NULL){
cout << "Error loading image "<< endl;
return 0;
}
imshow("img", img);


// Load image to process
light_pattern= imread("/home/shensir/Documents/MyPrograming/Cpp/Clions/data/light.pgm", 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;

Mat img_output= img.clone();
cvtColor(img_output, img_output, COLOR_GRAY2BGR);

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

}
//vector<int> results= evaluate(features);

// Show images
imshow("Binary image", pre);
imshow("Result", img_output);
waitKey(0);
return 0;

}


此处的测试结果还是不错的,5个部件全部识别成功了。

输出:

Num of train samples: 146
Num of test samples: 67
Evaluation
============
Prediction Done
Error: 1.49254%
Num objects extracted features 5
Data Area AR: 2084 1.54013
Features to predict: [2084, 1.54013]
2
Data Area AR: 1171 1.12195
Features to predict: [1171, 1.1219512]
0
Data Area AR: 1045 1.97279
Features to predict: [1045, 1.9727893]
0
Data Area AR: 1486 1.12069
Features to predict: [1486, 1.1206896]
2
Data Area AR: 1620 1
Features to predict: [1620, 1]
1

本文标题:学习目标分类

文章作者:不秩稚童

发布时间:2017年05月18日 - 17:58:12

最后更新:2017年05月18日 - 18:12:06

原始链接:http://datahonor.com/2017/05/18/学习目标分类/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

击蒙御寇