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/*****************************************************************************************************
45USAGE:
46./opencv_annotation -images <folder location> -annotations <output file>
47
48Created by: Puttemans Steven - February 2015
49Adapted 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
64using namespace std;
65using namespace cv;
66
67// Function prototypes
68void on_mouse(int, int, int, int, void*);
69vector<Rect> get_annotations(Mat);
70
71// Public parameters
72Mat image;
73int roi_x0 = 0, roi_y0 = 0, roi_x1 = 0, roi_y1 = 0, num_of_rec = 0;
74bool start_draw = false, stop = false;
75
76// Window name for visualisation purposes
77const 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
82void 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
111vector<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
219int 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

source code of opencv/apps/annotation/opencv_annotation.cpp