├── LICENSE ├── README.md ├── annotate-folder.py ├── result └── process.gif └── sample-dataset ├── IMG-20160915-WA0000.jpg ├── IMG-20171218-WA0019.jpg ├── IMG-20181228-WA0003.jpg ├── IMG-20190128-WA0011.jpg └── IMG_20171101_125839.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sai Prajwal Kotamraju 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # annotate-to-KITTI 2 | #### Author: Sai Prajwal Kotamraju 3 | This repository hosts a python script that can be used to draw ground-truth bounding boxes for a given folder of images and generate corresponding annotations in KITTI Vision data format. Detailed information of KITTI Vision benchmark suite 4 | can be found [here](http://www.cvlibs.net/datasets/kitti/). Discussion on the data format of KITTI has been addressed in this [issue](https://github.com/NVIDIA/DIGITS/issues/992). KITTI data format can be used for training an object detection model using Nvidia's [DIGITS](https://devblogs.nvidia.com/deep-learning-object-detection-digits/), one of the most popular tools for developing deep learning models for object classification/detection/segmentation tasks. 5 | 6 | Annotations play an important role in training an object detection model. It is often a tough task to find annotations/labels corresponding to the dataset we would want to train on. Most of the times, we would have to rely on a third party software to annotate our dataset which might end up being costly and a little too overdone for the task we are trying to accomplish. For this reason, this simple, yet effective, annotation script has been developed in order to annotate small datasets with a few hundred images. 7 | 8 | ## Dependencies 9 | * Python 2.7 10 | * OpenCV 2.4 11 | * Numpy 2.12 12 | 13 | ## Running the script 14 | First, clone the repository to a desired location using the following command. 15 | ``` 16 | git clone https://github.com/SaiPrajwal95/annotate-to-KITTI.git 17 | ``` 18 | Then migrate to the local repository and type the following command to get started with the annotation process. 19 | ``` 20 | cd annotate-to-KITTI 21 | python annotate-folder.py 22 | ``` 23 | 24 | ## Usage 25 | * The script asks for the location of the image dataset initially. It can be entered as: '/path/to/image/dataset'. Notice that there is no tailing '/' after the dataset folder's name. 26 | * It also asks for the default label for your dataset. This is very useful in single object detection task where the label stays the same. Once you enter the label like: 'people' initially, you won't be asked to enter the label for any other image in the dataset. 27 | * To start annotating, maximize the image (if required) and left-click and drag the mouse to create a bounding box to cover the object of interest. 28 | * Press 'l' to change the label and enter the name of the label for the upcoming bounding boxes in that image. Note that for every new image that pops, the label will go back to being the default label. 29 | * Press 'c' to cancel the latest ground-truth annotation if you aren't satisfied with it. 30 | * Press 'n' to skip the image if you don't want an annotation file for that image. 31 | * Press 'q' to save the annotations in KITTI format into a folder in current path. The script also saves the corresponding copy of that image into a folder in current path. 32 | * Press 'Esc' key to exit the annotation process. Good thing about this script is that you could continue from where you left off when you start the script again. So, annotate, relax, and continue whenever you can. 33 | 34 | ## Results 35 | ![img ex](result/process.gif "Image being annotated") 36 | 37 | -------------------------------------------------------------------------------- /annotate-folder.py: -------------------------------------------------------------------------------- 1 | ''' 2 | File: annotate-folder.py 3 | Author: Sai Prajwal Kotamraju 4 | Description: This script generates annotations for images of a given dataset 5 | in KITTI data format. Annotations are saved into a specified 6 | destination folder. 7 | ''' 8 | import cv2 9 | import numpy as np 10 | from os import listdir, mkdir, getcwd 11 | from os.path import isfile, join, exists 12 | import json 13 | from shutil import copyfile 14 | 15 | # Global variables 16 | ix,iy = -1,-1 # Iintial mouse point coordinates 17 | # Prefixes: f -> first, l -> last 18 | fx, fy, lx, ly = -1,-1,-1,-1 # coordinates to keep track of mouse movement 19 | draw = False # Enables only on left mouse click 20 | mask_prev = None # Keeps track of previous renderings 21 | mask = None # Mask for current rendering 22 | kitti_data_cell = None # Info on label and bbox for single object 23 | kitti_data = None # List that holds all the data cells for one image 24 | obj_label = None # Object Label 25 | 26 | # mouse callback function 27 | def draw_annotation(event,x,y,flags,param): 28 | """function generates mask renderings depending upon the left mouse button 29 | position, and mode of operation. 30 | Parameters: 31 | event: Refers to the external events of a mouse click. 32 | x,y: Posiion of mouse pointer 33 | flags: Flags that can be used to enable/disable any features. 34 | params: Parameters/thresholds to vary the functionality. 35 | """ 36 | # Declaring the following variables as global ensures that the changes 37 | # made in this function last out of the function 38 | global ix,iy,draw, fx, fy, lx, ly 39 | global mask_prev, mask, obj_label, kitti_data_cell, kitti_data 40 | # As soon as left mouse button is clicked, store the initial mouse 41 | # pointer coordinates. 42 | if event == cv2.EVENT_LBUTTONDOWN: 43 | ix,iy = x,y 44 | draw = True 45 | fx,fy = ix,iy 46 | elif event == cv2.EVENT_LBUTTONUP: 47 | # mask_prev saves every mask rendering in order to get back 48 | # recursively when 'c' is pressed 49 | mask_prev.append(mask.copy()) 50 | cv2.rectangle(mask,(ix,iy),(x,y),(0,-200,200),-1) 51 | kitti_data_cell['label'] = obj_label 52 | kitti_data_cell['bbox'] = dict() 53 | kitti_data_cell['bbox']['xmin'] = min(ix,x) 54 | kitti_data_cell['bbox']['ymin'] = min(iy,y) 55 | kitti_data_cell['bbox']['xmax'] = max(ix,x) 56 | kitti_data_cell['bbox']['ymax'] = max(iy,y) 57 | kitti_data.append(kitti_data_cell) 58 | draw = False 59 | fx, fy, lx, ly = -1, -1, -1, -1 60 | elif (event == cv2.EVENT_MOUSEMOVE and draw): 61 | lx = x 62 | ly = y 63 | 64 | if __name__ == '__main__': 65 | datasetPath = input('Enter the path to dataset: ') 66 | current_path = getcwd() 67 | destination_images_path = current_path+'/'+datasetPath.split('/')[-1]+'_Images_KITTI' 68 | destination_annotations_path = current_path+'/'+datasetPath.split('/')[-1]+'_Annotations_KITTI' 69 | 70 | obj_label = input('Enter default object label: ') 71 | obj_label_default = obj_label 72 | 73 | if(not exists(destination_images_path)): 74 | mkdir(destination_images_path) 75 | 76 | if(not exists(destination_annotations_path)): 77 | mkdir(destination_annotations_path) 78 | logf = open("logFile.log", "w") 79 | for datasetImgFile in listdir(datasetPath): 80 | if isfile(join(datasetPath, datasetImgFile)): 81 | obj_label = obj_label_default 82 | filepath = datasetPath+'/'+datasetImgFile 83 | img = cv2.imread(filepath,1) 84 | try: 85 | rows, columns, colors = img.shape 86 | except Exception as e: 87 | logf.write("Failed to open {0}: {1}\n".format(filepath, str(e))) 88 | continue 89 | logf.write("\nOpened the image {0} for annotation\n".format(filepath)) 90 | resize_factor = 640.00/max(rows, columns) 91 | resized = False 92 | if (resize_factor < 1): 93 | resized = True 94 | img = cv2.resize(img, None, fx=resize_factor,fy=resize_factor) 95 | rows, columns, colors = img.shape 96 | destFileName = datasetImgFile.split('.')[0] 97 | destAnnFile = destination_annotations_path + '/' + destFileName +'.txt' 98 | destImgFile = destination_images_path + '/' + datasetImgFile 99 | if(exists(destAnnFile)): 100 | logf.write("Annotation already exists for {0}\n".format(filepath)) 101 | continue 102 | kitti_data = list() 103 | mask = np.zeros((rows, columns, colors), dtype=np.uint8) 104 | mask_prev = list() 105 | cv2.namedWindow('image',cv2.WINDOW_NORMAL) 106 | cv2.setMouseCallback('image',draw_annotation) 107 | check = 0 # Flag to stop annotation process 108 | cancel_check = 0 # Flag to skip annotation to next image 109 | while(1): 110 | mask_ref = np.zeros((rows, columns, colors), dtype=np.uint8) 111 | kitti_data_cell = dict() 112 | if(fx != -1 and fy != -1 and lx != -1 and ly != -1): 113 | cv2.rectangle(mask_ref,(fx,fy),(lx,ly),(0,200,0),-1) 114 | cv2.imshow('image',cv2.addWeighted(img+mask+mask_ref, 0.7, img, 0.3, 0)) 115 | k = cv2.waitKey(1) & 0xFF 116 | if k == 27: # Stop annotating the dataset (Esc key) 117 | check = 1 118 | break 119 | elif k == ord('q'): # Finish annotating present image 120 | logf.write("Ending the annotation process for {0}\n".format(filepath)) 121 | break 122 | elif k == ord('c'): # Cancel annotation for most recent bbox 123 | logf.write("Canceling the previous bbox annotation\n") 124 | mask = mask_prev.pop() 125 | kitti_data.pop() 126 | elif k == ord('l'): # Change the label for next object 127 | logf.write("Changing the label from {0} to ".format(obj_label)) 128 | obj_label = input('Enter the object label: ') 129 | logf.write("{0}\n".format(obj_label)) 130 | elif k == ord('n'): # Skip to next image 131 | cancel_check = 1 132 | logf.write("Skipping to next image. Annotation not saved.\n") 133 | break 134 | 135 | cv2.destroyAllWindows() 136 | if(not (len(kitti_data) == 0) and not(cancel_check)): 137 | # Write the contents into file 138 | annotation_file_obj = open(destAnnFile,'w') 139 | for obj in kitti_data: 140 | if (resized): 141 | obj['bbox']['xmin'] /= resize_factor 142 | obj['bbox']['ymin'] /= resize_factor 143 | obj['bbox']['xmax'] /= resize_factor 144 | obj['bbox']['ymax'] /= resize_factor 145 | annotation_str = "%s %.2f %.0f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f\n" \ 146 | %(obj['label'], 0, 0, 0, obj['bbox']['xmin'], obj['bbox']['ymin'], obj['bbox']['xmax'], \ 147 | obj['bbox']['ymax'], 0, 0, 0, 0, 0, 0, 0) 148 | annotation_file_obj.write(annotation_str) 149 | annotation_file_obj.close() 150 | # Copy the image into separate folder 151 | copyfile(filepath,destImgFile) 152 | if(check): # Corresponding to the Esc key 153 | logf.write("Qutting the annotation process\n") 154 | break 155 | logf.close() 156 | -------------------------------------------------------------------------------- /result/process.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/result/process.gif -------------------------------------------------------------------------------- /sample-dataset/IMG-20160915-WA0000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/sample-dataset/IMG-20160915-WA0000.jpg -------------------------------------------------------------------------------- /sample-dataset/IMG-20171218-WA0019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/sample-dataset/IMG-20171218-WA0019.jpg -------------------------------------------------------------------------------- /sample-dataset/IMG-20181228-WA0003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/sample-dataset/IMG-20181228-WA0003.jpg -------------------------------------------------------------------------------- /sample-dataset/IMG-20190128-WA0011.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/sample-dataset/IMG-20190128-WA0011.jpg -------------------------------------------------------------------------------- /sample-dataset/IMG_20171101_125839.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SaiPrajwal95/annotate-to-KITTI/4e0c7a96d56beb1456b3097cc53932afb3f396b5/sample-dataset/IMG_20171101_125839.jpg --------------------------------------------------------------------------------