From 16ebe0d16916fc603142d0174a49f017048e5e43 Mon Sep 17 00:00:00 2001 From: Pramay <31489611+QuickLearner171998@users.noreply.github.com> Date: Mon, 30 Mar 2020 00:57:59 +0530 Subject: [PATCH 1/9] features added Following features are added: 1) Threshold can now set by the user. 2) Pascal VOC saving format 3) Add all classes at once without selecting each manually --- main.py | 82 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/main.py b/main.py index 493dbd9..5c397f5 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,8 @@ """ Copyright {2018} {Viraj Mavani} - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 """ @@ -23,6 +21,8 @@ import tensorflow as tf import config import math +# for saving to xml files +from pascal_voc_writer import Writer def get_session(): @@ -41,7 +41,7 @@ def get_session(): class MainGUI: def __init__(self, master): self.parent = master - self.parent.title("Semi Automatic Image Annotation Tool") + self.parent.title("Auto Label") self.frame = Frame(self.parent) self.frame.pack(fill=BOTH, expand=1) self.parent.resizable(width=False, height=False) @@ -77,7 +77,9 @@ def __init__(self, master): self.filenameBuffer = None self.objectLabelList = [] self.EDIT = False - + self.writer = None + # default thresh can be modified by the user later + self.thresh = 0.5 # initialize mouse state self.STATE = {'x': 0, 'y': 0} self.STATE_COCO = {'click': 0} @@ -94,29 +96,33 @@ def __init__(self, master): self.ctrlPanel = Frame(self.frame) self.ctrlPanel.grid(row=0, column=0, sticky=W + N) self.openBtn = Button(self.ctrlPanel, text='Open', command=self.open_image) - self.openBtn.pack(fill=X, side=TOP) + self.openBtn.grid(columnspan=2, sticky=W + E) self.openDirBtn = Button(self.ctrlPanel, text='Open Dir', command=self.open_image_dir) - self.openDirBtn.pack(fill=X, side=TOP) + self.openDirBtn.grid(columnspan=2, sticky = W + E) self.nextBtn = Button(self.ctrlPanel, text='Next -->', command=self.open_next) - self.nextBtn.pack(fill=X, side=TOP) + self.nextBtn.grid(columnspan=2, sticky=W + E) self.previousBtn = Button(self.ctrlPanel, text='<-- Previous', command=self.open_previous) - self.previousBtn.pack(fill=X, side=TOP) + self.previousBtn.grid(columnspan=2, sticky=W + E) self.saveBtn = Button(self.ctrlPanel, text='Save', command=self.save) - self.saveBtn.pack(fill=X, side=TOP) + self.saveBtn.grid(columnspan=2, sticky=W + E) self.semiAutoBtn = Button(self.ctrlPanel, text="Show Suggestions", command=self.automate) - self.semiAutoBtn.pack(fill=X, side=TOP) + self.semiAutoBtn.grid(columnspan=2, sticky=W + E) self.disp = Label(self.ctrlPanel, text='Coordinates:') - self.disp.pack(fill=X, side=TOP) + self.disp.grid(columnspan=2, sticky=W + E) self.mb = Menubutton(self.ctrlPanel, text="COCO Classes for Suggestions", relief=RAISED) - self.mb.pack(fill=X, side=TOP) + self.mb.grid(columnspan=2, sticky=W + E) self.mb.menu = Menu(self.mb, tearoff=0) self.mb["menu"] = self.mb.menu self.addCocoBtn = Button(self.ctrlPanel, text="+", command=self.add_labels_coco) - self.addCocoBtn.pack(fill=X, side=TOP) + self.addCocoBtn.grid(columnspan=2, sticky=W + E) + # option to add all classes to list + self.addCocoBtnAllClasses = Button(self.ctrlPanel, text="Add All Classes", command=self.add_all_classes) + self.addCocoBtnAllClasses.grid(columnspan=2, sticky=W + E) + self.zoomPanelLabel = Label(self.ctrlPanel, text="Precision View Panel") - self.zoomPanelLabel.pack(fill=X, side=TOP) + self.zoomPanelLabel.grid(columnspan=2, sticky=W + E) self.zoomcanvas = Canvas(self.ctrlPanel, width=150, height=150) - self.zoomcanvas.pack(fill=X, side=TOP, anchor='center') + self.zoomcanvas.grid(columnspan=2, sticky=W + E) # Image Editing Region self.canvas = Canvas(self.frame, width=500, height=500) @@ -142,9 +148,13 @@ def __init__(self, master): self.classesNameLabel = Label(self.listPanel, text="Classes").pack(fill=X, side=TOP) self.textBox = Entry(self.listPanel, text="Enter label") self.textBox.pack(fill=X, side=TOP) - self.addLabelBtn = Button(self.listPanel, text="+", command=self.add_label).pack(fill=X, side=TOP) self.delLabelBtn = Button(self.listPanel, text="-", command=self.del_label).pack(fill=X, side=TOP) + # add option to set threshold + self.addThresh = Label(self.listPanel, text="Threshold").pack(fill=X, side=TOP) + self.textBoxTh = Entry(self.listPanel, text="Enter threshold value") + self.textBoxTh.pack(fill=X, side=TOP) + self.enterthresh = Button(self.listPanel, text="Set", command=self.changeThresh).pack(fill=X, side=TOP) self.labelListBox = Listbox(self.listPanel) self.labelListBox.pack(fill=X, side=TOP) @@ -165,6 +175,15 @@ def __init__(self, master): self.imageIdxLabel = Label(self.statusBar, text=" ") self.imageIdxLabel.pack(side="right", fill=X) + + + def changeThresh(self): + # to update the threshold + if(float(self.textBoxTh.get()) >0 and float(self.textBoxTh.get()) <1): + self.thresh = float(self.textBoxTh.get()) + + + def open_image(self): self.filename = filedialog.askopenfilename(title="Select Image", filetypes=(("jpeg files", "*.jpg"), ("all files", "*.*"))) @@ -223,18 +242,37 @@ def open_previous(self, event=None): def save(self): if self.filenameBuffer is None: + w, h = self.img.size + self.writer = Writer(os.path.join(self.imageDirPathBuffer , self.imageList[self.cur]), w, h) self.annotation_file = open('annotations/' + self.anno_filename, 'a') for idx, item in enumerate(self.bboxList): + x1, y1, x2, y2 = self.bboxList[idx] + self.writer.addObject(str(self.objectLabelList[idx]), x1, y1, x2, y2) self.annotation_file.write(self.imageDirPathBuffer + '/' + self.imageList[self.cur] + ',' + ','.join(map(str, self.bboxList[idx])) + ',' + str(self.objectLabelList[idx]) + '\n') self.annotation_file.close() + baseName = os.path.splitext(self.imageList[self.cur])[0] + save_dir = 'annotations/annotations_voc/' + save_path = save_dir + baseName + '.xml' + if(not os.path.exists(save_dir)): + os.mkdir(save_dir) + + self.writer.save(save_path) + self.writer = None else: + w, h = self.img.size + self.writer = Writer(self.filenameBuffer, w, h) self.annotation_file = open('annotations/' + self.anno_filename, 'a') for idx, item in enumerate(self.bboxList): + x1, y1, x2, y2 = self.bboxList[idx] + self.writer.addObject(str(self.objectLabelList[idx]), x1, y1, x2, y2) self.annotation_file.write(self.filenameBuffer + ',' + ','.join(map(str, self.bboxList[idx])) + ',' + str(self.objectLabelList[idx]) + '\n') self.annotation_file.close() + baseName = os.path.splitext(self.imageList[self.cur])[0] + self.writer.save('annotations/annotations_voc/' + baseName + '.xml') + self.writer = None def mouse_click(self, event): # Check if Updating BBox @@ -418,6 +456,13 @@ def add_labels_coco(self): if list_label_coco not in curr_label_list: self.labelListBox.insert(END, str(list_label_coco)) + def add_all_classes(self): + for listidxcoco, list_label_coco in enumerate(self.cocoLabels): + curr_label_list = self.labelListBox.get(0, END) + curr_label_list = list(curr_label_list) + if list_label_coco not in curr_label_list: + self.labelListBox.insert(END, str(list_label_coco)) + def automate(self): self.processingLabel.config(text="Processing ") self.processingLabel.update_idletasks() @@ -425,12 +470,15 @@ def automate(self): # Convert RGB to BGR opencvImage= open_cv_image[:, :, ::-1].copy() # opencvImage = cv2.cvtColor(np.array(self.img), cv2.COLOR_RGB2BGR) + # self.thresh = self.thresh + + image = preprocess_image(opencvImage) boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0)) for idx, (box, label, score) in enumerate(zip(boxes[0], labels[0], scores[0])): curr_label_list = self.labelListBox.get(0, END) curr_label_list = list(curr_label_list) - if score < 0.5: + if score < self.thresh: continue if config.labels_to_names[label] not in curr_label_list: From 9773ba049308a17bb4c29a89caffe5cbd13aa1f3 Mon Sep 17 00:00:00 2001 From: Pramay <31489611+QuickLearner171998@users.noreply.github.com> Date: Mon, 30 Mar 2020 01:00:35 +0530 Subject: [PATCH 2/9] Update main.py --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 5c397f5..191ec47 100644 --- a/main.py +++ b/main.py @@ -41,7 +41,7 @@ def get_session(): class MainGUI: def __init__(self, master): self.parent = master - self.parent.title("Auto Label") + self.parent.title("Semi Automatic Image Annotation Tool") self.frame = Frame(self.parent) self.frame.pack(fill=BOTH, expand=1) self.parent.resizable(width=False, height=False) From 2f76818d8381dfa09feabb867d652185431778f6 Mon Sep 17 00:00:00 2001 From: Pramay Date: Tue, 31 Mar 2020 13:25:50 +0530 Subject: [PATCH 3/9] added option to switch between models --- main.py | 165 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 35 deletions(-) diff --git a/main.py b/main.py index 191ec47..5a761f0 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,7 @@ from tkinter import * from tkinter import filedialog from PIL import Image, ImageTk - import keras - from keras_retinanet import models from keras_retinanet.utils.image import preprocess_image @@ -20,26 +18,26 @@ import numpy as np import tensorflow as tf import config +import tf_config import math -# for saving to xml files from pascal_voc_writer import Writer - -def get_session(): - config = tf.ConfigProto() - config.gpu_options.allow_growth = True - return tf.Session(config=config) - - -keras.backend.tensorflow_backend.set_session(get_session()) - -model_path = os.path.join('.', 'snapshots', 'resnet50_coco_best_v2.1.0.h5') - -model = models.load_model(model_path, backbone_name='resnet50') +# make sure the file is inside semi-auto-image-annotation-tool-master +import pathlib +cur_path = pathlib.Path(__file__).parent.absolute() +# cur_path = r"semi-auto-image-annotation-tool-master" +sys.path.append(cur_path) +os.chdir(cur_path) class MainGUI: def __init__(self, master): + + # to choose between keras or tensorflow models + self.kera = 1 # default + self.tensorf = 0 + self.models_dir = '' # gets updated as per user choice + self.model_path = '' self.parent = master self.parent.title("Semi Automatic Image Annotation Tool") self.frame = Frame(self.parent) @@ -77,9 +75,11 @@ def __init__(self, master): self.filenameBuffer = None self.objectLabelList = [] self.EDIT = False + self.autoSuggest = StringVar() self.writer = None - # default thresh can be modified by the user later self.thresh = 0.5 + self.org_h = 0 + self.org_w = 0 # initialize mouse state self.STATE = {'x': 0, 'y': 0} self.STATE_COCO = {'click': 0} @@ -99,26 +99,43 @@ def __init__(self, master): self.openBtn.grid(columnspan=2, sticky=W + E) self.openDirBtn = Button(self.ctrlPanel, text='Open Dir', command=self.open_image_dir) self.openDirBtn.grid(columnspan=2, sticky = W + E) + self.nextBtn = Button(self.ctrlPanel, text='Next -->', command=self.open_next) self.nextBtn.grid(columnspan=2, sticky=W + E) self.previousBtn = Button(self.ctrlPanel, text='<-- Previous', command=self.open_previous) self.previousBtn.grid(columnspan=2, sticky=W + E) self.saveBtn = Button(self.ctrlPanel, text='Save', command=self.save) self.saveBtn.grid(columnspan=2, sticky=W + E) - self.semiAutoBtn = Button(self.ctrlPanel, text="Show Suggestions", command=self.automate) + self.autoManualLabel = Label(self.ctrlPanel, text="Suggestion Mode") + self.autoManualLabel.grid(columnspan=2, sticky=W + E) + self.radioBtnAuto = Radiobutton(self.ctrlPanel, text="Auto", variable=self.autoSuggest, value=1) + self.radioBtnAuto.grid(row=7, column=0, sticky=W + E) + self.radioBtnManual = Radiobutton(self.ctrlPanel, text="Manual", variable=self.autoSuggest, value=2) + self.radioBtnManual.grid(row=7, column=1, sticky=W + E) + self.semiAutoBtn = Button(self.ctrlPanel, text="Detect", command=self.automate) self.semiAutoBtn.grid(columnspan=2, sticky=W + E) self.disp = Label(self.ctrlPanel, text='Coordinates:') self.disp.grid(columnspan=2, sticky=W + E) + self.mb = Menubutton(self.ctrlPanel, text="COCO Classes for Suggestions", relief=RAISED) self.mb.grid(columnspan=2, sticky=W + E) self.mb.menu = Menu(self.mb, tearoff=0) self.mb["menu"] = self.mb.menu + self.addCocoBtn = Button(self.ctrlPanel, text="+", command=self.add_labels_coco) self.addCocoBtn.grid(columnspan=2, sticky=W + E) - # option to add all classes to list self.addCocoBtnAllClasses = Button(self.ctrlPanel, text="Add All Classes", command=self.add_all_classes) self.addCocoBtnAllClasses.grid(columnspan=2, sticky=W + E) + # options to add different models + self.mb1 = Menubutton(self.ctrlPanel, text="Select models from here", relief=RAISED) + self.mb1.grid(columnspan=2, sticky=W + E) + self.mb1.menu = Menu(self.mb1, tearoff=0) + self.mb1["menu"] = self.mb1.menu + + self.addModelBtn = Button(self.ctrlPanel, text="Add model", command=self.add_model) + self.addModelBtn.grid(columnspan=2, sticky=W + E) + self.zoomPanelLabel = Label(self.ctrlPanel, text="Precision View Panel") self.zoomPanelLabel.grid(columnspan=2, sticky=W + E) self.zoomcanvas = Canvas(self.ctrlPanel, width=150, height=150) @@ -150,7 +167,7 @@ def __init__(self, master): self.textBox.pack(fill=X, side=TOP) self.addLabelBtn = Button(self.listPanel, text="+", command=self.add_label).pack(fill=X, side=TOP) self.delLabelBtn = Button(self.listPanel, text="-", command=self.del_label).pack(fill=X, side=TOP) - # add option to set threshold + self.addThresh = Label(self.listPanel, text="Threshold").pack(fill=X, side=TOP) self.textBoxTh = Entry(self.listPanel, text="Enter threshold value") self.textBoxTh.pack(fill=X, side=TOP) @@ -158,15 +175,22 @@ def __init__(self, master): self.labelListBox = Listbox(self.listPanel) self.labelListBox.pack(fill=X, side=TOP) + if self.kera: + self.cocoLabels = config.labels_to_names.values() + else: + self.cocoLabels = tf_config.labels_to_names.values() - self.cocoLabels = config.labels_to_names.values() self.cocoIntVars = [] - for idxcoco, label_coco in enumerate(self.cocoLabels): self.cocoIntVars.append(IntVar()) self.mb.menu.add_checkbutton(label=label_coco, variable=self.cocoIntVars[idxcoco]) # print(self.cocoIntVars) + self.modelIntVars = [] + for idxmodel, modelname in enumerate(self.available_models()): + self.modelIntVars.append(IntVar()) + self.mb1.menu.add_checkbutton(label=modelname, variable=self.modelIntVars[idxmodel]) + # STATUS BAR self.statusBar = Frame(self.frame, width=500) self.statusBar.grid(row=1, column=1, sticky=W + N) @@ -175,15 +199,27 @@ def __init__(self, master): self.imageIdxLabel = Label(self.statusBar, text=" ") self.imageIdxLabel.pack(side="right", fill=X) + def get_session(self): + config = tf.ConfigProto() + config.gpu_options.allow_growth = True + return tf.Session(config=config) + + def available_models(self): + self.models_dir = os.path.join(cur_path, 'snapshots') + # only for keras and tf + model_categ = os.listdir(self.models_dir) + # creating all model options list + model_names = [] + for categ in model_categ: + for name in os.listdir(os.path.join(self.models_dir , categ)): + model_names.append(os.path.join(categ,name)) + return model_names def changeThresh(self): - # to update the threshold if(float(self.textBoxTh.get()) >0 and float(self.textBoxTh.get()) <1): self.thresh = float(self.textBoxTh.get()) - - def open_image(self): self.filename = filedialog.askopenfilename(title="Select Image", filetypes=(("jpeg files", "*.jpg"), ("all files", "*.*"))) @@ -203,12 +239,16 @@ def open_image_dir(self): self.imageDirPathBuffer = self.imageDir self.load_image(self.imageDirPathBuffer + '/' + self.imageList[self.cur]) + def open_video_file(self): + pass + def load_image(self, file): self.img = Image.open(file) self.imageCur = self.cur + 1 self.imageIdxLabel.config(text=' || Image Number: %d / %d' % (self.imageCur, self.imageTotal)) # Resize to Pascal VOC format w, h = self.img.size + self.org_w, self.org_h = self.img.size if w >= h: baseW = 500 wpercent = (baseW / float(w)) @@ -231,6 +271,8 @@ def open_next(self, event=None): self.load_image(self.imageDirPathBuffer + '/' + self.imageList[self.cur]) self.processingLabel.config(text=" ") self.processingLabel.update_idletasks() + if self.autoSuggest.get() == str(1): + self.automate() def open_previous(self, event=None): self.save() @@ -239,6 +281,8 @@ def open_previous(self, event=None): self.load_image(self.imageDirPathBuffer + '/' + self.imageList[self.cur]) self.processingLabel.config(text=" ") self.processingLabel.update_idletasks() + if self.autoSuggest.get() == str(1): + self.automate() def save(self): if self.filenameBuffer is None: @@ -448,6 +492,24 @@ def del_label(self): labelidx = self.labelListBox.curselection() self.labelListBox.delete(labelidx) + def add_model(self): + for listidxmodel, list_model_name in enumerate(self.available_models()): + if(self.modelIntVars[listidxmodel].get()): + # check which model is it keras or tensorflow + self.model_path = os.path.join(self.models_dir,list_model_name) + # if its Tensorflow model then modify path + if('keras' in list_model_name): + self.kera = 1 + self.tensorf = 0 + elif('tensorflow' in list_model_name): + self.model_path = os.path.join(self.model_path,'frozen_inference_graph.pb') + self.kera = 0 + self.tensorf = 1 + # change cocoLabels corresponding to tensorflow + self.cocoLabels = tf_config.labels_to_names.values() + break + + def add_labels_coco(self): for listidxcoco, list_label_coco in enumerate(self.cocoLabels): if self.cocoIntVars[listidxcoco].get(): @@ -458,6 +520,7 @@ def add_labels_coco(self): def add_all_classes(self): for listidxcoco, list_label_coco in enumerate(self.cocoLabels): + # if self.cocoIntVars[listidxcoco].get(): curr_label_list = self.labelListBox.get(0, END) curr_label_list = list(curr_label_list) if list_label_coco not in curr_label_list: @@ -468,24 +531,55 @@ def automate(self): self.processingLabel.update_idletasks() open_cv_image = np.array(self.img) # Convert RGB to BGR - opencvImage= open_cv_image[:, :, ::-1].copy() - # opencvImage = cv2.cvtColor(np.array(self.img), cv2.COLOR_RGB2BGR) - # self.thresh = self.thresh + opencvImage = open_cv_image[:, :, ::-1].copy() + # if tensorflow + if self.tensorf : + detection_graph = tf.Graph() + with detection_graph.as_default(): + od_graph_def = tf.GraphDef() + with tf.gfile.GFile(self.model_path, 'rb') as fid: + serialized_graph = fid.read() + od_graph_def.ParseFromString(serialized_graph) + tf.import_graph_def(od_graph_def, name='') + + sess = tf.Session(graph=detection_graph) + + image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') + detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0') + detection_scores = detection_graph.get_tensor_by_name('detection_scores:0') + detection_classes = detection_graph.get_tensor_by_name('detection_classes:0') + num_detections = detection_graph.get_tensor_by_name('num_detections:0') + + image_expanded = np.expand_dims(opencvImage, axis=0) + (boxes, scores, labels, num) = sess.run( + [detection_boxes, detection_scores, detection_classes, num_detections], + feed_dict={image_tensor: image_expanded}) + config_labels = tf_config.labels_to_names + m_name = os.path.split((os.path.split(self.model_path)[0]))[1] - - image = preprocess_image(opencvImage) - boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0)) + else: + keras.backend.tensorflow_backend.set_session(self.get_session()) + model_path = self.model_path + model = models.load_model(model_path, backbone_name='resnet50') + image = preprocess_image(opencvImage) + boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0)) + config_labels = config.labels_to_names + m_name = os.path.split(self.model_path)[1] for idx, (box, label, score) in enumerate(zip(boxes[0], labels[0], scores[0])): curr_label_list = self.labelListBox.get(0, END) curr_label_list = list(curr_label_list) if score < self.thresh: continue - if config.labels_to_names[label] not in curr_label_list: + if config_labels[label] not in curr_label_list: continue - b = box.astype(int) - + b = box + # only if using tf models as keras and tensorflow have different coordinate order + if(self.tensorf): + w, h = self.img.size + (b[0],b[1],b[2],b[3]) = (b[1]*w, b[0]*h, b[3]*w, b[2]*h) + b = b.astype(int) self.bboxId = self.canvas.create_rectangle(b[0], b[1], b[2], b[3], width=2, @@ -501,9 +595,10 @@ def automate(self): self.bboxPointList.append(o4) self.bboxIdList.append(self.bboxId) self.bboxId = None - self.objectLabelList.append(str(config.labels_to_names[label])) + self.objectLabelList.append(str(config_labels[label])) self.objectListBox.insert(END, '(%d, %d) -> (%d, %d)' % (b[0], b[1], b[2], b[3]) + ': ' + - str(config.labels_to_names[label])) + str(config_labels[label])+' '+str(int(score*100))+'%' + +' '+ m_name) self.objectListBox.itemconfig(len(self.bboxIdList) - 1, fg=config.COLORS[(len(self.bboxIdList) - 1) % len(config.COLORS)]) self.processingLabel.config(text="Done ") From 140931f837f21ee41206a13b73bf51f097ab9f91 Mon Sep 17 00:00:00 2001 From: Pramay Date: Tue, 31 Mar 2020 13:28:06 +0530 Subject: [PATCH 4/9] added separate file for tensorflow labels --- tf_config.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tf_config.py diff --git a/tf_config.py b/tf_config.py new file mode 100644 index 0000000..b7e1768 --- /dev/null +++ b/tf_config.py @@ -0,0 +1,85 @@ +labels_to_names = {1: 'person', + 2: 'bicycle', + 3: 'car', + 4: 'motorcycle', + 5: 'airplane', + 6: 'bus', + 7: 'train', + 8: 'truck', + 9: 'boat', + 10: 'traffic light', + 11: 'fire hydrant', + 13: 'stop sign', + 14: 'parking meter', + 15: 'bench', + 16: 'bird', + 17: 'cat', + 18: 'dog', + 19: 'horse', + 20: 'sheep', + 21: 'cow', + 22: 'elephant', + 23: 'bear', + 24: 'zebra', + 25: 'giraffe', + 27: 'backpack', + 28: 'umbrella', + 31: 'handbag', + 32: 'tie', + 33: 'suitcase', + 34: 'frisbee', + 35: 'skis', + 36: 'snowboard', + 37: 'sports ball', + 38: 'kite', + 39: 'baseball bat', + 40: 'baseball glove', + 41: 'skateboard', + 42: 'surfboard', + 43: 'tennis racket', + 44: 'bottle', + 46: 'wine glass', + 47: 'cup', + 48: 'fork', + 49: 'knife', + 50: 'spoon', + 51: 'bowl', + 52: 'banana', + 53: 'apple', + 54: 'sandwich', + 55: 'orange', + 56: 'broccoli', + 57: 'carrot', + 58: 'hot dog', + 59: 'pizza', + 60: 'donut', + 61: 'cake', + 62: 'chair', + 63: 'couch', + 64: 'potted plant', + 65: 'bed', + 67: 'dining table', + 70: 'toilet', + 72: 'tv', + 73: 'laptop', + 74: 'mouse', + 75: 'remote', + 76: 'keyboard', + 77: 'cell phone', + 78: 'microwave', + 79: 'oven', + 80: 'toaster', + 81: 'sink', + 82: 'refrigerator', + 84: 'book', + 85: 'clock', + 86: 'vase', + 87: 'scissors', + 88: 'teddy bear', + 89: 'hair drier', + 90: 'toothbrush'} + + + + + From 52ed5f436068c6e1f340dc38f58ee69a3b38ec1a Mon Sep 17 00:00:00 2001 From: Pramay Date: Tue, 31 Mar 2020 13:29:44 +0530 Subject: [PATCH 5/9] Create README --- snapshots/keras/README | 1 + 1 file changed, 1 insertion(+) create mode 100644 snapshots/keras/README diff --git a/snapshots/keras/README b/snapshots/keras/README new file mode 100644 index 0000000..40bb525 --- /dev/null +++ b/snapshots/keras/README @@ -0,0 +1 @@ +add keras models here From c5d3bd162b9668c64a9810d015929a0a2719535a Mon Sep 17 00:00:00 2001 From: Pramay Date: Tue, 31 Mar 2020 13:30:34 +0530 Subject: [PATCH 6/9] Create readme --- snapshots/tensorflow/readme | 1 + 1 file changed, 1 insertion(+) create mode 100644 snapshots/tensorflow/readme diff --git a/snapshots/tensorflow/readme b/snapshots/tensorflow/readme new file mode 100644 index 0000000..9fa3c96 --- /dev/null +++ b/snapshots/tensorflow/readme @@ -0,0 +1 @@ +add tensorflow models here From 273c7f28b58d4e0baa03b762f607f97c124023aa Mon Sep 17 00:00:00 2001 From: Pramay Date: Tue, 31 Mar 2020 14:22:56 +0530 Subject: [PATCH 7/9] Update README.md --- README.md | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 20ab591..1f759e5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![alt text](https://raw.githubusercontent.com/virajmavani/semi-auto-image-annotation-tool/master/demo.gif) -Semi Automatic Image Annotation Toolbox with RetinaNet as the suggesting algorithm. The toolbox suggests 80 class objects from the MS COCO dataset using a pretrained RetinaNet model. +Semi Automatic Image Annotation Toolbox with tensorflow and keras object detection models. ## Installation @@ -14,7 +14,13 @@ Semi Automatic Image Annotation Toolbox with RetinaNet as the suggesting algorit Please make sure `tensorflow` is installed as per your systems requirements. Also, make sure Keras 2.1.3 or higher and OpenCV 3.x is installed. -3) Download the [pretrained weights](https://github.com/fizyr/keras-retinanet/releases/download/0.3.1/resnet50_coco_best_v2.1.0.h5) and save it in /snapshots. +3) a) For Keras model - Download the [pretrained weights](https://github.com/fizyr/keras-retinanet/releases/download/0.3.1/resnet50_coco_best_v2.1.0.h5) and save it in /snapshots/keras. + + b) For tensorflow model get the desired model from [here](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) and extract it in /sanpshots/tensorfow + + c) You can even save custom pre trained model in the respective directory. + + ### Dependencies @@ -28,19 +34,30 @@ For, Python >= 3.5 ### Instructions -1) Select the COCO object classes for which you need suggestions from the drop-down menu and add them. +1) Select the COCO object classes for which you need suggestions from the drop-down menu and add them. Or simply click on ```Add all classes``` . + +2) Select the desired model and click on ```Add model```. + +3) Click on ```detect``` button. -2) When annotating manually, select the object class from the List and while keep it selected, select the BBox. +4) When annotating manually, select the object class from the List and while keep it selected, select the BBox. -3) The final annotations can be found in the file `annotations.csv` in ./annotations/ +5) The final annotations can be found in the file `annotations.csv` in ./annotations/ . Also a xml file will saved. ### Usage + +For MSCOCO dataset ``` python main.py ``` +For any other dataset- -Tested on: +First change the labels in config.py (for keras model) or in tf_config.py( for tensorflow model). +Then run: +```python main.py +``` +#### Tested on: 1. Windows 10 2. Linux 16.04 From 20759ce7921b24160b7608ad6ebbb338c19853fb Mon Sep 17 00:00:00 2001 From: Pramay <31489611+QuickLearner171998@users.noreply.github.com> Date: Sun, 12 Apr 2020 03:25:46 +0530 Subject: [PATCH 8/9] minor bugs fixed --- main.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 5a761f0..5f99759 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,9 @@ # make sure the file is inside semi-auto-image-annotation-tool-master import pathlib -cur_path = pathlib.Path(__file__).parent.absolute() +# cur_path = pathlib.Path(__file__).parent.absolute() +cur_path = pathlib.Path(__file__).parent.absolute().as_posix() +print(cur_path) # cur_path = r"semi-auto-image-annotation-tool-master" sys.path.append(cur_path) os.chdir(cur_path) @@ -34,8 +36,8 @@ class MainGUI: def __init__(self, master): # to choose between keras or tensorflow models - self.kera = 1 # default - self.tensorf = 0 + self.keras_ = 1 # default + self.tensorflow_ = 0 self.models_dir = '' # gets updated as per user choice self.model_path = '' self.parent = master @@ -175,7 +177,7 @@ def __init__(self, master): self.labelListBox = Listbox(self.listPanel) self.labelListBox.pack(fill=X, side=TOP) - if self.kera: + if self.keras_: self.cocoLabels = config.labels_to_names.values() else: self.cocoLabels = tf_config.labels_to_names.values() @@ -207,7 +209,7 @@ def get_session(self): def available_models(self): self.models_dir = os.path.join(cur_path, 'snapshots') # only for keras and tf - model_categ = os.listdir(self.models_dir) + model_categ = [dir_ for dir_ in os.listdir(self.models_dir) if os.path.isdir(os.path.join(self.models_dir, dir_))] # creating all model options list model_names = [] for categ in model_categ: @@ -499,12 +501,12 @@ def add_model(self): self.model_path = os.path.join(self.models_dir,list_model_name) # if its Tensorflow model then modify path if('keras' in list_model_name): - self.kera = 1 - self.tensorf = 0 + self.keras_ = 1 + self.tensorflow_ = 0 elif('tensorflow' in list_model_name): self.model_path = os.path.join(self.model_path,'frozen_inference_graph.pb') - self.kera = 0 - self.tensorf = 1 + self.keras_ = 0 + self.tensorflow_ = 1 # change cocoLabels corresponding to tensorflow self.cocoLabels = tf_config.labels_to_names.values() break @@ -533,7 +535,7 @@ def automate(self): # Convert RGB to BGR opencvImage = open_cv_image[:, :, ::-1].copy() # if tensorflow - if self.tensorf : + if self.tensorflow_ : detection_graph = tf.Graph() with detection_graph.as_default(): od_graph_def = tf.GraphDef() @@ -576,7 +578,7 @@ def automate(self): b = box # only if using tf models as keras and tensorflow have different coordinate order - if(self.tensorf): + if(self.tensorflow_): w, h = self.img.size (b[0],b[1],b[2],b[3]) = (b[1]*w, b[0]*h, b[3]*w, b[2]*h) b = b.astype(int) From f3ae901b28f17cce81cfd9b10b18fc62b9e544db Mon Sep 17 00:00:00 2001 From: Pramay <31489611+QuickLearner171998@users.noreply.github.com> Date: Sun, 12 Apr 2020 03:47:23 +0530 Subject: [PATCH 9/9] remove print --- main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.py b/main.py index 5f99759..c2adb69 100644 --- a/main.py +++ b/main.py @@ -26,8 +26,6 @@ import pathlib # cur_path = pathlib.Path(__file__).parent.absolute() cur_path = pathlib.Path(__file__).parent.absolute().as_posix() -print(cur_path) -# cur_path = r"semi-auto-image-annotation-tool-master" sys.path.append(cur_path) os.chdir(cur_path)