1 | //////////////////////////////////////////////////////////////////////////////////////// |
2 | // |
3 | // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. |
4 | // |
5 | // By downloading, copying, installing or using the software you agree to this license. |
6 | // If you do not agree to this license, do not download, install, |
7 | // copy or use the software. |
8 | // |
9 | // |
10 | // License Agreement |
11 | // For Open Source Computer Vision Library |
12 | // |
13 | // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. |
14 | // Copyright (C) 2009, Willow Garage Inc., all rights reserved. |
15 | // Copyright (C) 2013, OpenCV Foundation, all rights reserved. |
16 | // Third party copyrights are property of their respective owners. |
17 | // |
18 | // Redistribution and use in source and binary forms, with or without modification, |
19 | // are permitted provided that the following conditions are met: |
20 | // |
21 | // * Redistribution's of source code must retain the above copyright notice, |
22 | // this list of conditions and the following disclaimer. |
23 | // |
24 | // * Redistribution's in binary form must reproduce the above copyright notice, |
25 | // this list of conditions and the following disclaimer in the documentation |
26 | // and/or other materials provided with the distribution. |
27 | // |
28 | // * The name of the copyright holders may not be used to endorse or promote products |
29 | // derived from this software without specific prior written permission. |
30 | // |
31 | // This software is provided by the copyright holders and contributors "as is" and |
32 | // any express or implied warranties, including, but not limited to, the implied |
33 | // warranties of merchantability and fitness for a particular purpose are disclaimed. |
34 | // In no event shall the Intel Corporation or contributors be liable for any direct, |
35 | // indirect, incidental, special, exemplary, or consequential damages |
36 | // (including, but not limited to, procurement of substitute goods or services; |
37 | // loss of use, data, or profits; or business interruption) however caused |
38 | // and on any theory of liability, whether in contract, strict liability, |
39 | // or tort (including negligence or otherwise) arising in any way out of |
40 | // the use of this software, even if advised of the possibility of such damage. |
41 | // |
42 | //////////////////////////////////////////////////////////////////////////////////////// |
43 | |
44 | /***************************************************************************************************** |
45 | USAGE: |
46 | ./opencv_annotation -images <folder location> -annotations <output file> |
47 | |
48 | Created by: Puttemans Steven - February 2015 |
49 | Adapted by: Puttemans Steven - April 2016 - Vectorize the process to enable better processing |
50 | + early leave and store by pressing an ESC key |
51 | + enable delete `d` button, to remove last annotation |
52 | *****************************************************************************************************/ |
53 | |
54 | #include <opencv2/core.hpp> |
55 | #include <opencv2/highgui.hpp> |
56 | #include <opencv2/imgcodecs.hpp> |
57 | #include <opencv2/videoio.hpp> |
58 | #include <opencv2/imgproc.hpp> |
59 | |
60 | #include <fstream> |
61 | #include <iostream> |
62 | #include <map> |
63 | |
64 | using namespace std; |
65 | using namespace cv; |
66 | |
67 | // Function prototypes |
68 | void on_mouse(int, int, int, int, void*); |
69 | vector<Rect> get_annotations(Mat); |
70 | |
71 | // Public parameters |
72 | Mat image; |
73 | int roi_x0 = 0, roi_y0 = 0, roi_x1 = 0, roi_y1 = 0, num_of_rec = 0; |
74 | bool start_draw = false, stop = false; |
75 | |
76 | // Window name for visualisation purposes |
77 | const string window_name = "OpenCV Based Annotation Tool" ; |
78 | |
79 | // FUNCTION : Mouse response for selecting objects in images |
80 | // If left button is clicked, start drawing a rectangle as long as mouse moves |
81 | // Stop drawing once a new left click is detected by the on_mouse function |
82 | void on_mouse(int event, int x, int y, int , void * ) |
83 | { |
84 | // Action when left button is clicked |
85 | if(event == EVENT_LBUTTONDOWN) |
86 | { |
87 | if(!start_draw) |
88 | { |
89 | roi_x0 = x; |
90 | roi_y0 = y; |
91 | start_draw = true; |
92 | } else { |
93 | roi_x1 = x; |
94 | roi_y1 = y; |
95 | start_draw = false; |
96 | } |
97 | } |
98 | |
99 | // Action when mouse is moving and drawing is enabled |
100 | if((event == EVENT_MOUSEMOVE) && start_draw) |
101 | { |
102 | // Redraw bounding box for annotation |
103 | Mat current_view; |
104 | image.copyTo(m: current_view); |
105 | rectangle(img: current_view, pt1: Point(roi_x0,roi_y0), pt2: Point(x,y), color: Scalar(0,0,255)); |
106 | imshow(winname: window_name, mat: current_view); |
107 | } |
108 | } |
109 | |
110 | // FUNCTION : returns a vector of Rect objects given an image containing positive object instances |
111 | vector<Rect> get_annotations(Mat input_image) |
112 | { |
113 | vector<Rect> current_annotations; |
114 | |
115 | // Make it possible to exit the annotation process |
116 | stop = false; |
117 | |
118 | // Init window interface and couple mouse actions |
119 | namedWindow(winname: window_name, flags: WINDOW_AUTOSIZE); |
120 | setMouseCallback(winname: window_name, onMouse: on_mouse); |
121 | |
122 | image = input_image; |
123 | imshow(winname: window_name, mat: image); |
124 | int key_pressed = 0; |
125 | |
126 | do |
127 | { |
128 | // Get a temporary image clone |
129 | Mat temp_image = input_image.clone(); |
130 | Rect currentRect(0, 0, 0, 0); |
131 | |
132 | // Keys for processing |
133 | // You need to select one for confirming a selection and one to continue to the next image |
134 | // Based on the universal ASCII code of the keystroke: http://www.asciitable.com/ |
135 | // c = 99 add rectangle to current image |
136 | // n = 110 save added rectangles and show next image |
137 | // d = 100 delete the last annotation made |
138 | // <ESC> = 27 exit program |
139 | key_pressed = 0xFF & waitKey(delay: 0); |
140 | switch( key_pressed ) |
141 | { |
142 | case 27: |
143 | stop = true; |
144 | break; |
145 | case 99: |
146 | // Draw initiated from top left corner |
147 | if(roi_x0<roi_x1 && roi_y0<roi_y1) |
148 | { |
149 | currentRect.x = roi_x0; |
150 | currentRect.y = roi_y0; |
151 | currentRect.width = roi_x1-roi_x0; |
152 | currentRect.height = roi_y1-roi_y0; |
153 | } |
154 | // Draw initiated from bottom right corner |
155 | if(roi_x0>roi_x1 && roi_y0>roi_y1) |
156 | { |
157 | currentRect.x = roi_x1; |
158 | currentRect.y = roi_y1; |
159 | currentRect.width = roi_x0-roi_x1; |
160 | currentRect.height = roi_y0-roi_y1; |
161 | } |
162 | // Draw initiated from top right corner |
163 | if(roi_x0>roi_x1 && roi_y0<roi_y1) |
164 | { |
165 | currentRect.x = roi_x1; |
166 | currentRect.y = roi_y0; |
167 | currentRect.width = roi_x0-roi_x1; |
168 | currentRect.height = roi_y1-roi_y0; |
169 | } |
170 | // Draw initiated from bottom left corner |
171 | if(roi_x0<roi_x1 && roi_y0>roi_y1) |
172 | { |
173 | currentRect.x = roi_x0; |
174 | currentRect.y = roi_y1; |
175 | currentRect.width = roi_x1-roi_x0; |
176 | currentRect.height = roi_y0-roi_y1; |
177 | } |
178 | // Draw the rectangle on the canvas |
179 | // Add the rectangle to the vector of annotations |
180 | current_annotations.push_back(x: currentRect); |
181 | break; |
182 | case 100: |
183 | // Remove the last annotation |
184 | if(current_annotations.size() > 0){ |
185 | current_annotations.pop_back(); |
186 | } |
187 | break; |
188 | default: |
189 | // Default case --> do nothing at all |
190 | // Other keystrokes can simply be ignored |
191 | break; |
192 | } |
193 | |
194 | // Check if escape has been pressed |
195 | if(stop) |
196 | { |
197 | break; |
198 | } |
199 | |
200 | // Draw all the current rectangles onto the top image and make sure that the global image is linked |
201 | for(int i=0; i < (int)current_annotations.size(); i++){ |
202 | rectangle(img: temp_image, rec: current_annotations[i], color: Scalar(0,255,0), thickness: 1); |
203 | } |
204 | image = temp_image; |
205 | |
206 | // Force an explicit redraw of the canvas --> necessary to visualize delete correctly |
207 | imshow(winname: window_name, mat: image); |
208 | } |
209 | // Continue as long as the next image key has not been pressed |
210 | while(key_pressed != 110); |
211 | |
212 | // Close down the window |
213 | destroyWindow(winname: window_name); |
214 | |
215 | // Return the data |
216 | return current_annotations; |
217 | } |
218 | |
219 | int main( int argc, const char** argv ) |
220 | { |
221 | // Use the cmdlineparser to process input arguments |
222 | CommandLineParser parser(argc, argv, |
223 | "{ help h usage ? | | show this message }" |
224 | "{ images i | | (required) path to image folder [example - /data/testimages/] }" |
225 | "{ annotations a | | (required) path to annotations txt file [example - /data/annotations.txt] }" |
226 | "{ maxWindowHeight m | -1 | (optional) images larger in height than this value will be scaled down }" |
227 | "{ resizeFactor r | 2 | (optional) factor for scaling down [default = half the size] }" |
228 | ); |
229 | // Read in the input arguments |
230 | if (parser.has(name: "help" )){ |
231 | parser.printMessage(); |
232 | cerr << "TIP: Use absolute paths to avoid any problems with the software!" << endl; |
233 | return 0; |
234 | } |
235 | string image_folder(parser.get<string>(name: "images" )); |
236 | string annotations_file(parser.get<string>(name: "annotations" )); |
237 | if (image_folder.empty() || annotations_file.empty()){ |
238 | parser.printMessage(); |
239 | cerr << "TIP: Use absolute paths to avoid any problems with the software!" << endl; |
240 | return -1; |
241 | } |
242 | |
243 | int resizeFactor = parser.get<int>(name: "resizeFactor" ); |
244 | int const maxWindowHeight = parser.get<int>(name: "maxWindowHeight" ) > 0 ? parser.get<int>(name: "maxWindowHeight" ) : -1; |
245 | |
246 | // Start by processing the data |
247 | // Return the image filenames inside the image folder |
248 | map< String, vector<Rect> > annotations; |
249 | vector<String> filenames; |
250 | String folder(image_folder); |
251 | glob(pattern: folder, result&: filenames); |
252 | |
253 | // Add key tips on how to use the software when running it |
254 | cout << "* mark rectangles with the left mouse button," << endl; |
255 | cout << "* press 'c' to accept a selection," << endl; |
256 | cout << "* press 'd' to delete the latest selection," << endl; |
257 | cout << "* press 'n' to proceed with next image," << endl; |
258 | cout << "* press 'esc' to stop." << endl; |
259 | |
260 | // Loop through each image stored in the images folder |
261 | // Create and temporarily store the annotations |
262 | // At the end write everything to the annotations file |
263 | for (size_t i = 0; i < filenames.size(); i++){ |
264 | // Read in an image |
265 | Mat current_image = imread(filename: filenames[i]); |
266 | bool const resize_bool = (maxWindowHeight > 0) && (current_image.rows > maxWindowHeight); |
267 | |
268 | // Check if the image is actually read - avoid other files in the folder, because glob() takes them all |
269 | // If not then simply skip this iteration |
270 | if(current_image.empty()){ |
271 | continue; |
272 | } |
273 | |
274 | if(resize_bool){ |
275 | resize(src: current_image, dst: current_image, dsize: Size(current_image.cols/resizeFactor, current_image.rows/resizeFactor), fx: 0, fy: 0, interpolation: INTER_LINEAR_EXACT); |
276 | } |
277 | |
278 | // Perform annotations & store the result inside the vectorized structure |
279 | // If the image was resized before, then resize the found annotations back to original dimensions |
280 | vector<Rect> current_annotations = get_annotations(input_image: current_image); |
281 | if(resize_bool){ |
282 | for(int j =0; j < (int)current_annotations.size(); j++){ |
283 | current_annotations[j].x = current_annotations[j].x * resizeFactor; |
284 | current_annotations[j].y = current_annotations[j].y * resizeFactor; |
285 | current_annotations[j].width = current_annotations[j].width * resizeFactor; |
286 | current_annotations[j].height = current_annotations[j].height * resizeFactor; |
287 | } |
288 | } |
289 | annotations[filenames[i]] = current_annotations; |
290 | |
291 | // Check if the ESC key was hit, then exit earlier then expected |
292 | if(stop){ |
293 | break; |
294 | } |
295 | } |
296 | |
297 | // When all data is processed, store the data gathered inside the proper file |
298 | // This now even gets called when the ESC button was hit to store preliminary results |
299 | ofstream output(annotations_file.c_str()); |
300 | if ( !output.is_open() ){ |
301 | cerr << "The path for the output file contains an error and could not be opened. Please check again!" << endl; |
302 | return 0; |
303 | } |
304 | |
305 | // Store the annotations, write to the output file |
306 | for(map<String, vector<Rect> >::iterator it = annotations.begin(); it != annotations.end(); it++){ |
307 | vector<Rect> &anno = it->second; |
308 | output << it->first << " " << anno.size(); |
309 | for(size_t j=0; j < anno.size(); j++){ |
310 | Rect temp = anno[j]; |
311 | output << " " << temp.x << " " << temp.y << " " << temp.width << " " << temp.height; |
312 | } |
313 | output << endl; |
314 | } |
315 | |
316 | return 0; |
317 | } |
318 | |