build_voc_dataset.cpp

Build custom VOC2010 dataset for opencv 2.4 - Joel Mckay, 2012-05-04 07:28 pm

Download (16.7 kB)

 
1
/*****************************************************************************************
2
        This program reads in a generic directory tree of cropped sample images, and is
3
        currently only compatible with *nix systems due to file check macros etc.
4
        It quickly produces a PASCAL VOC (Visual Object Challenge) compatible output,
5
        and was built to feed OpenCV's sample code like  bagofwords_classification.cpp. 
6
        
7
        2012  Joel Mckay        
8
        [email protected]
9
10
        Disclaimer:
11
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12
        "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
13
        LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14
        FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
15
        COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
16
        INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17
        BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
18
        LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
19
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
20
        LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
21
        WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
22
        OF SUCH DAMAGE. 
23
 *****************************************************************************************/
24
25
26
#include "global_headers.hpp"
27
28
29
/* How many of the positive set to train on, or comment out to test every file against the set */
30
//#define POS_TRAIN_SET_RATIO_OF_POSITIVE_SAMPLES 1
31
//#define NEG_TRAIN_SET_RATIO_OF_NEGAITIVE_SAMPLES 1
32
 
33
/* Notes:  additional negative training samples can be kept in another "negative" folder */
34
#define NEG_TRAIN_SET_GENERIC_PATH  "negative"
35
36
/* + all imported items will be listed as valid for their class */
37
/* + the file handles are freed with each scan as there may be a great number of classes */
38
/* + all names must be  [A-Za-Z0-9\.] to avoid VOC import format problems later */
39
40
/*****************************************************************************************/
41
42
/*
43
------------------------------------------------------------------------------------------------------------------------------
44
For classification and detection there are four sets for VOC:
45
------------------------------------------------------------------------------------------------------------------------------
46
"train": 
47
Training data
48
49
"val": 
50
Validation data (suggested). 
51
The validation data may be used as addi-tional training data (see below).
52
53
"trainval": 
54
The union of train and val.
55
56
"test": 
57
Test data. 
58
The test set is not provided in the development kit. 
59
It will be released in good time before the deadline for submission of results.
60
61
62
OpenCV BoG default file suffix used when the new VOC class loads with VocData vocData( vocPath, false ):
63
        m_train_set = "train";
64
        m_test_set = "val";
65
    
66
These file suffix used when the new VOC class loads with VocData vocData( vocPath, true ):
67
        m_train_set = "trainval";
68
        m_test_set = "test";
69
70
---------------------------------------------------------------
71
There are three ground truth labels:
72
---------------------------------------------------------------
73
-1: Negative: 
74
The image contains no objects of the class of interest. 
75
A classifier should give a 'negative' output.
76
77
1: Positive: 
78
The image contains at least one object of the class of interest.
79
A classifier should give a 'positive' output.
80
81
0: "Difficult": 
82
The image contains only objects of the class of interest marked as 'difficult'.
83
84
------------------------------------------------------------------------------------------------------------------------------
85
*/
86
87
88
/********************************************************************************************************/
89
//This function scans the class dir to push the specific file names one at a time to a vector list
90
void loadListFromDir( string dir , vector<string>* m_object_filenames)        
91
{
92
string filepath;
93
string filename;
94
string filebasename;
95
string fileext; 
96
int arrTemplatesCount;
97
DIR *dp;
98
struct dirent *dirp;
99
struct stat filestat; 
100
101
arrTemplatesCount=0;
102
dp = opendir( dir.c_str() );        //try to open the directory
103
if (dp == NULL)
104
{
105
    cout << "Error opening " << dir << endl;
106
    return;
107
}
108
109
while ((dirp = readdir( dp )) && (arrTemplatesCount < 100000)) //scan the dir
110
{
111
        filename=dirp->d_name;
112
        filepath = dir + "/" + filename; 
113
114
        // file invalid? we'll skip it... 
115
        if (stat( filepath.c_str(), &filestat )) continue;
116
            
117
        //is a file? 
118
        if (S_ISREG( filestat.st_mode ))  //is a real file?
119
        {
120
                fileext = filename.substr(filename.find_last_of(".") + 1);
121
                std::transform(fileext.begin(), fileext.end(),fileext.begin(), ::tolower); //str ::tolower   ::toupper 
122
                    
123
                if(( fileext == "png") || ( fileext == "jpg") || ( fileext == "bmp"))        //image?
124
                {
125
                        filebasename = filename.substr(0, filename.find_first_of(".") );
126
                        
127
                        #if defined(DEBUG_MODE)
128
                                cout << "Loaded: " << filebasename << "  " << filepath.c_str() << endl;
129
                        #endif
130
                        
131
                        (*m_object_filenames).push_back(filepath);
132
                        
133
                }
134
        }        
135
}
136
137
closedir( dp );        //done loading list
138
139
}
140
 
141
142
/********************************************************************************************************/
143
//This function scans the class dir to push the specific DIR names one at a time to a vector list
144
void loadClassListFromDir( string dir , vector<string>* m_object_classes,  vector<string>* m_object_classes_paths)        
145
{
146
string filepath;
147
string filename;
148
string filebasename;
149
string fileext; 
150
int arrTemplatesCount;
151
DIR *dp;
152
struct dirent *dirp;
153
struct stat filestat; 
154
155
arrTemplatesCount=0;
156
dp = opendir( dir.c_str() );        //try to open the directory
157
if (dp == NULL)
158
{
159
    cout << "Error opening " << dir << endl;
160
    return;
161
}
162
163
while ((dirp = readdir( dp )) && (arrTemplatesCount < 100000)) //scan the dir
164
{
165
        filename=dirp->d_name;
166
        filepath = dir + "/" + filename; 
167
168
        // file invalid? we'll skip it... 
169
        if (stat( filepath.c_str(), &filestat )) continue;
170
            
171
        //is a dir class name? 
172
        if (S_ISDIR( filestat.st_mode ))  //is a real dir?
173
        {
174
                fileext = filename;
175
                std::transform(fileext.begin(), fileext.end(),fileext.begin(), ::tolower); //str ::tolower   ::toupper 
176
                    
177
                if(( fileext != ".") && ( fileext != ".."))        //real dir?
178
                { 
179
                        (*m_object_classes).push_back(fileext);
180
                        (*m_object_classes_paths).push_back(filepath);
181
                        
182
                }
183
        }        
184
}
185
186
closedir( dp );        //done loading list
187
188
} 
189
190
191
192
193
/********************************************************************************************************/
194
//This function exports a specific image if it passes QC 
195
//note: filename is the name without extension
196
//format string to reduce stack hammering
197
const string FASTXMLOUTPUTFORMAT="<annotation>\n        <folder>VOC2010</folder>\n        <filename>%s.jpg</filename>\n        <source>\n                <database>The VOC2007 Database</database>\n                <annotation>PASCAL VOC2007</annotation>\n                <image>flickr</image>\n        </source>\n        <size>\n                <width>%i</width>\n                <height>%i</height>\n                <depth>%i</depth>\n        </size>\n        <segmented>1</segmented>\n        <object>\n                <name>%s</name>\n                <pose>Frontal</pose>\n                <truncated>0</truncated>\n                <difficult>0</difficult>\n                <bndbox>\n                        <xmin>%i</xmin>\n                        <ymin>%i</ymin>\n                        <xmax>%i</xmax>\n                        <ymax>%i</ymax>\n                </bndbox>\n        </object>\n</annotation>\n";
198
int  createImageXMLfile( Mat* img, string* image_path, string* annotation_path, string* filename, string* classname)
199
{
200
//create file contents
201
Size size = (*img).size();
202
int width=size.width;
203
int height=size.height;
204
int colorChans=(*img).channels();
205
int colordepth=(*img).depth();        //CV_8U ?
206
int boxXmin=0;
207
int boxYmin=0;
208
int boxXmax=size.width;                //assume item is pre-cropped
209
int boxYmax=size.height;
210
211
        //check if BGR or Gray
212
        if(!((colorChans==3) || (colorChans == 1)))
213
        {
214
                cout << "\nError: Bad color channel in: " << (*filename) << endl;
215
                return -1;
216
        }
217
        
218
        //check if image too small
219
        if((width < 40) || (height < 40))
220
        {
221
                cout << "\nError: Image too small: " << (*filename) << endl;
222
                return -1;
223
        }
224
        
225
        //create new copy of image data
226
        string jpgFileName= (*image_path) + "/" + (*filename) + ".jpg";
227
        if(imwrite(jpgFileName, (*img)) == false)
228
        {
229
                cout << "\nError: Image write fail: " << (*filename) << endl;
230
                return -1;  
231
        }
232
        
233
        
234
        //Dump cropped image info to xml file
235
        FILE * filePointer;
236
        string xmlFileName= (*annotation_path) + "/" + (*filename) + ".xml"; 
237
        filePointer = fopen(xmlFileName.c_str(),"w");        //imgfilename.xml
238
239
        if( fprintf(filePointer, FASTXMLOUTPUTFORMAT.c_str() ,(*filename).c_str(), width, height, colorChans,(*classname).c_str(),boxXmin,boxYmin,boxXmax,boxYmax) < 0 )
240
        {
241
                    cout << "\nError: Could not write xml file: " << xmlFileName << endl;
242
                    return -1;
243
        }
244
        fclose(filePointer);
245
    
246
   cout << "Wrote file: " << xmlFileName << "\r";
247
   return 0;
248
}
249
250
251
/********************************************************************************************************/
252
253
int main(int argc, char** argv)
254
{
255
        if( argc != 3 )
256
        { 
257
        cout << "\nUsage" << endl
258
                << "./build_classification  </path/to/all/cropped/image/classdirs> </path/to/create/new/VOC2010/>" << endl;        
259
        exit(-1);
260
        }
261
262
        
263
        //Mimic minimal VOC2010 dir structure
264
        string r_vocPath = argv[2];
265
        if( mkdir(r_vocPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)  != 0 )
266
        {
267
                cout << "\nError: Could not create the path: \n" << r_vocPath << endl;        
268
        }
269
270
        string r_annotation_path = r_vocPath + "/Annotations";
271
        if( mkdir(r_annotation_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)  != 0 )
272
        {
273
                cout << "\nError: Could not create the path: \n" << r_annotation_path << endl;        
274
                exit(-1);
275
        }
276
277
        string r_image_path = r_vocPath + "/JPEGImages";
278
        if( mkdir(r_image_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)  != 0 )
279
        {
280
                cout << "\nCould not create JPEGImages path: \n" << r_image_path << endl;        
281
                exit(-1);
282
        }
283
284
        string r_imageset_path = r_vocPath + "/ImageSets";
285
        if( mkdir(r_imageset_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)  != 0 )
286
        {
287
                cout << "\nCould not create ImageSets path: \n" << r_imageset_path << endl;        
288
                exit(-1);
289
        }
290
        string r_class_imageset_path = r_vocPath + "/ImageSets/Main";
291
        if( mkdir(r_class_imageset_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)  != 0 )
292
        {
293
                cout << "\nCould not create ImageSets/Main path: \n" << r_class_imageset_path << endl;        
294
                exit(-1);
295
        }
296
 
297
298
        // initialize main classification/detection template paths
299
        string template_imageset_path = r_vocPath + "/ImageSets/Main/%s.txt";
300
        string template_class_imageset_path = r_vocPath + "/ImageSets/Main/%s_%s.txt";
301
        char trainFileName[4096]="\0";
302
        char valFileName[4096]="\0"; 
303
        char trainvalFileName[4096]="\0";
304
        char testFileName[4096]="\0";
305
        
306
        //Scan all of the class dirs
307
        string classesSrcBaseDir=argv[1];  // classdirs 
308
        vector<string> my_classes;  
309
        vector<string> my_classes_dir;   
310
        vector<vector<string> > my_dataset_files;  
311
        loadClassListFromDir(classesSrcBaseDir, &my_classes, &my_classes_dir);
312
        
313
        unsigned int fileCounter=0;
314
315
        //check all dirs/classnames in path for positive samples 
316
        for( size_t classIdx = 0; (classIdx < my_classes.size()); ++classIdx )
317
        {
318
                  
319
                /* first check for file list for the current class has been saved to file */
320
                vector<string> my_files;  
321
                vector<string> img_data_names;  
322
                loadListFromDir(my_classes_dir[classIdx], &my_files);
323
                
324
                //parse out each dir as a new class name
325
                string dirclassname=my_classes[classIdx];//new class to read?
326
                cout << "\n---------------------------- Create pos set: " << dirclassname << "-----------------------"<<endl;
327
                
328
                //create file index for each scanned dir class
329
                sprintf(trainFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "train");
330
                sprintf(valFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "val");
331
                sprintf(trainvalFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "trainval");
332
                sprintf(testFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "test");
333
                 
334
                FILE * trainFilePointer;
335
                FILE * trainvalFilePointer;
336
                FILE * valFilePointer;
337
                FILE * testFilePointer;
338
                
339
                trainFilePointer = fopen(trainFileName,"a");
340
                trainvalFilePointer = fopen(trainvalFileName,"a");
341
                valFilePointer = fopen(valFileName,"a");
342
                testFilePointer = fopen(testFileName,"a");
343
344
                
345
                //check all files in specific class dir
346
                for(size_t fileIdx = 0; (fileIdx < my_files.size()); ++fileIdx)
347
                { 
348
                        Mat img = imread(my_files[fileIdx] .c_str());                //read each file into the buffer
349
350
                        //create new file name
351
                        std::ostringstream namerender;
352
                        namerender << "0000_" << fileCounter;                        //bogus release prefix to prevent overlap
353
                        string imgname =  namerender.str();
354
355
                        
356
                        #if defined(DEBUG_MODE)
357
                        cout << "\nREAD: " << my_files[fileIdx] << endl
358
                                << "\nr_image_path:" << r_image_path << endl
359
                                << "\nr_annotation_path:" << r_annotation_path <<endl 
360
                                << "\nimgname:" << imgname <<endl
361
                                << "\ndirclassname:" << dirclassname << endl;
362
                        #endif
363
364
                        
365
                        //if item imported write to the log list format
366
                        if( createImageXMLfile( &img, &r_image_path, &r_annotation_path, &imgname, &dirclassname) == 0 )
367
                        {
368
                                        //Sample valid image name to index files only if not in negative set???
369
                                        if( imgname.compare( NEG_TRAIN_SET_GENERIC_PATH ) != 0)
370
                                        {
371
                                
372
                                        #if defined(POS_TRAIN_SET_RATIO_OF_POSITIVE_SAMPLES)
373
                                        if((fileCounter%POS_TRAIN_SET_RATIO_OF_POSITIVE_SAMPLES)==0)
374
                                        #endif
375
                                        {
376
                                                if( fprintf(trainFilePointer, "%s  1\n" ,imgname.c_str() ) < 0 )
377
                                                {
378
                                                    cout << "\nError: Could not write file: " << trainFilePointer << endl;
379
                                                    exit(-1);
380
                                                }
381
                                        }
382
                                        #if defined(POS_TRAIN_SET_RATIO_OF_POSITIVE_SAMPLES)
383
                                        else
384
                                        #endif
385
                                        {
386
                                                if( fprintf(valFilePointer, "%s  1\n" ,imgname.c_str() ) < 0 )
387
                                                {
388
                                                    cout << "\nError: Could not write file: " << trainFilePointer << endl;
389
                                                    exit(-1);
390
                                                }
391
                                        }
392
                                        
393
                                        
394
                                        
395
                                        if( fprintf(trainvalFilePointer, "%s  1\n" ,imgname.c_str() ) < 0 )
396
                                        {
397
                                                    cout << "\nError: Could not write file: " << trainvalFilePointer << endl;
398
                                                    exit(-1);
399
                                        }
400
                                        
401
                                        }
402
403
                                        //create data name list
404
                                        img_data_names.push_back(imgname);
405
                                        
406
                                        //show new file name for user
407
                                        cout << imgname   <<".jpg OK!                                                \r";
408
                                        fileCounter++; 
409
                        } 
410
                } 
411
                
412
                //store output marker for negative training data later
413
                my_dataset_files.push_back(img_data_names);
414
                
415
                fclose(trainFilePointer);
416
                fclose(trainvalFilePointer);
417
                fclose(valFilePointer);
418
                fclose(testFilePointer);
419
        
420
        }
421
422
        
423
        fileCounter=0;         //reset counter
424
        
425
        //append all classnames negative samples to disjoint sets
426
        for( size_t classIdx = 0; (classIdx < my_classes.size()); ++classIdx )
427
        {
428
                   
429
                //parse out each dir as a new class name
430
                string dirclassname=my_classes[classIdx];//new class to read?
431
                cout << "\n---------------------------- Create neg set: " << dirclassname << " -----------------------"<<endl;
432
                
433
                //Re-open file index for each scanned dir class
434
                sprintf(trainFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "train");
435
                sprintf(trainvalFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "trainval");
436
                sprintf(valFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "val");
437
                sprintf(testFileName, template_class_imageset_path.c_str(), dirclassname.c_str(), "test");
438
                 
439
                FILE * trainFilePointer;
440
                FILE * trainvalFilePointer;
441
                FILE * valFilePointer;
442
                FILE * testFilePointer;
443
                
444
                trainFilePointer = fopen(trainFileName,"a");
445
                trainvalFilePointer = fopen(trainvalFileName,"a");
446
                valFilePointer = fopen(valFileName,"a");
447
                testFilePointer = fopen(testFileName,"a"); 
448
                
449
                
450
                //append all file names if in disjoint class
451
                for( size_t classIter = 0; (classIter < my_classes.size()); ++classIter )
452
                {
453
                        if(classIdx != classIter)
454
                        {
455
                                for(size_t fileIdx = 0; (fileIdx < my_dataset_files[classIter].size()) ; ++fileIdx)
456
                                {   
457
                                  
458
                                        //if item imported write to the log list format
459
460
                                        //Sample valid neg index files
461
                                                #if defined(NEG_TRAIN_SET_RATIO_OF_NEGAITIVE_SAMPLES)
462
                                                if((fileCounter%NEG_TRAIN_SET_RATIO_OF_NEGAITIVE_SAMPLES)==0)
463
                                                #endif
464
                                                {
465
                                                        if( fprintf(trainFilePointer, "%s -1\n" ,my_dataset_files[classIter][fileIdx].c_str() ) < 0 )
466
                                                        {
467
                                                                cout << "\nError: Could not write file: " << trainFilePointer << endl;
468
                                                                exit(-1);
469
                                                        }
470
                                                }
471
                                                #if defined(NEG_TRAIN_SET_RATIO_OF_NEGAITIVE_SAMPLES)
472
                                                else
473
                                                #endif
474
                                                {
475
                                                        if( fprintf(valFilePointer, "%s -1\n" ,my_dataset_files[classIter][fileIdx].c_str() ) < 0 )
476
                                                        {
477
                                                            cout << "\nError: Could not write file: " << trainFilePointer << endl;
478
                                                            exit(-1);
479
                                                        }
480
                                                 }
481
                                                        
482
                                                if( fprintf(trainvalFilePointer, "%s -1\n" ,my_dataset_files[classIter][fileIdx].c_str() ) < 0 )
483
                                                {
484
                                                        cout << "\nError: Could not write file: " << trainvalFilePointer << endl;
485
                                                        exit(-1);
486
                                                }
487
                                                
488
                                                fileCounter++; 
489
         
490
                                } 
491
                                cout << ".";         //progress bar nibbles
492
                        }
493
                        
494
                } 
495
                
496
                fclose(trainFilePointer);
497
                fclose(trainvalFilePointer);
498
                fclose(valFilePointer);
499
                fclose(testFilePointer);
500
        
501
        }
502
        
503
        cout << "\n Done!" << endl;
504
        return 0;
505
}
506
507