diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4625326d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/Images +!/Images/001 diff --git a/Examples/001/Cute-Dog-Wallpaper.JPEG b/Examples/demo/Cute-Dog-Wallpaper.JPG similarity index 100% rename from Examples/001/Cute-Dog-Wallpaper.JPEG rename to Examples/demo/Cute-Dog-Wallpaper.JPG diff --git a/Examples/001/dog.JPEG b/Examples/demo/dog.JPG similarity index 100% rename from Examples/001/dog.JPEG rename to Examples/demo/dog.JPG diff --git a/Examples/001/dogs-wallpaper.JPEG b/Examples/demo/dogs-wallpaper.JPG similarity index 100% rename from Examples/001/dogs-wallpaper.JPEG rename to Examples/demo/dogs-wallpaper.JPG diff --git a/Images/001/test.JPEG b/Images/001/test.JPEG deleted file mode 100644 index 581e403f..00000000 Binary files a/Images/001/test.JPEG and /dev/null differ diff --git a/Images/001/test2.JPEG b/Images/001/test2.JPEG deleted file mode 100644 index f0778d4b..00000000 Binary files a/Images/001/test2.JPEG and /dev/null differ diff --git a/Images/001/test3.JPEG b/Images/001/test3.JPEG deleted file mode 100644 index bc412890..00000000 Binary files a/Images/001/test3.JPEG and /dev/null differ diff --git a/Labels/001/test.txt b/Labels/001/test.txt deleted file mode 100644 index 808509e1..00000000 --- a/Labels/001/test.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -80 8 172 111 diff --git a/Labels/001/test2.txt b/Labels/001/test2.txt deleted file mode 100644 index 22e8a683..00000000 --- a/Labels/001/test2.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -129 42 389 235 diff --git a/Labels/001/test3.txt b/Labels/001/test3.txt deleted file mode 100644 index e51f2880..00000000 --- a/Labels/001/test3.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -74 73 171 170 diff --git a/README.md b/README.md index 5c823f37..f333ff61 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ +### Forked from [puzzledqs/BBox-Label-Tool](https://github.com/puzzledqs/BBox-Label-Tool) +## Improvements +1. Add multi-class support +2. Change some of the color-candidates for better display +3. Fix the 'Example' filepath for convenience +4. Change the image format from '.JPEG' to '.JPG' + +## New Usage +### For multi-class task, modify 'class.txt' with your own class-candidates and before labeling bbox, choose the 'Current Class' in the Combobox and make sure you click 'ComfirmClass' button. + +### The remaining usage is the same as the origin one. + +------------------------------------ + +**Contact info**: jxgu1016@gmail.com + +------------------------------------ + BBox-Label-Tool =============== diff --git a/class.txt b/class.txt new file mode 100644 index 00000000..1a2ddc0f --- /dev/null +++ b/class.txt @@ -0,0 +1,133 @@ +Pedestrian +Truck +Car +Cyclist +DontCare +Misc +Van +Tram +Person_sitting +Cat +Dog +Horse +Pram +Fence +Pole +Lamppost +Door +Window +Home/Facade +Max_speed_A01-5 +Max_speed_A01-10 +Max_speed_A01-15 +Max_speed_A01-30 +Max_speed_A01-50 +Zone_max_speed_A01-30 +Zone_max_speed_A01-60 +End_max_speed_A02-30 +End_max_speed_A02-50 +End_zone_speed_A02-30 +Priority_road +End_priority_road +Priority_crossing_point +Priority_crossing_point_side_left +Priority_crossing_point_side_right +Give_way_sign +Stop_sign +Deflective_priority_road_OB701-1 +Deflective_priority_road_OB701-2 +Deflective_priority_road_OB701-3 +Deflective_priority_road_OB702-1 +Deflective_priority_road_OB702-2 +Deflective_priority_road_OB703-1 +Deflective_priority_road_OB703-2 +Closed_both_directions +One_way_street +One_way_road +One_way_road_left +One_way_road_right +One_way_road_left +Drive_in_allowed +Closed_for_motor_vehicles_on_two_plus_wheels +Closed_for_all_motor_vehicles +Zone_closed_for_motor_vehicles_on_two_plus_wheels +End_zone_closed_for_motor_vehicles +Zone_closed_for_all_motor_vehicles +End_zone_closed_for_all_motor_vehicles +Roundabout +Commandment_to_pass_sign_on_arrow_indication_D02-LO +Commandment_to_pass_sign_on_arrow_indication_D02-RO +Commandment_to_pass_sign_on_arrow_indication_D02-LB +Commandment_to_pass_sign_on_arrow_indication_D02-RB +Commandment_to_pass_sign_on_both_sides_of_arrow +Commandment_to_follow_sign_arrow_D04 +Commandment_to_follow_sign_arrow_D05-L +Commandment_to_follow_sign_arrow_D05-R +Commandment_to_follow_sign_one_of_arrow_indication_D06-L +Commandment_to_follow_sign_one_of_arrow_indication_D06-R +Commandment_to_follow_sign_one_of_arrow_indication_D07 +Scare_fence +Arch_left +Arch_right +Forbidden_to_park +Forbidden_to_stand_still +Zone_forbidden_to_park +End_zone_forbidden_to_park +Zone_forbidden_to_stand_still +End_zone_forbidden_to_stand_still +Prohibition_to_approach_traffic_out_of_opposite_direction +Drivers_from_counter-direction_need_to_give_priority_to_other_direction_drivers +Forbidden_to_turn +End_of_all_previous_indications +Stop_for_specified +Property +End_of_property +Sidewalk +End_of_sidewalk +Optional_bicycle_path +End_of_optional_bicycle_path +Obligated_bicycle_path +End_of_obligated_bicycle_path +Warning_for_bad_pavement +Warning_for_turn_right +Warning_for_turn_left +Warning_for_dangerous_intersection +Warning_for_roundabout +Warning_for_constructions +Warning_for_road_narrowing +Warning_for_road_narrowing_right +Warning_for_road_narrowing_left +Warning_for_children +Warning_for_pedestrian_crossing +Warning_for_pedestrians +Warning_for_bikers_and_mopeds +Warning_for_quay_or_river_banks +Warning_for_oncoming_traffic +Warning_for_traffic_lights +Warning_for_given_reason_under_sign +Warning_for_electric_and_extandable_pole_in_road_track +Pedestrian_crossing +Bus_stop_and_tram_stop +Tram_stop_and_bus_stop +Bus_stop +Tram_stop +Dead_end +Dead_end_with_left_way +Dead_end_with_right_way +Dead_end_left_with_straight_ahead_way +Dead_end_right_with_straight_ahead_way +Watch_out_bus_lane +Watch_out_speed_bumps +Bike_street_cars_allowed +Orange_pawn +Red_white_pawn +Guidance_beacon +Guidance_beacon_left +Guidance_beacon_right +Right_of_way_road_markings +Pedestrian_crossing_marking +Bicycle_lane +Bus_lane +Continious_lane +Parking_lots +Traffic_lights \ No newline at end of file diff --git a/convert.py b/convert.py new file mode 100644 index 00000000..fdeb97ed --- /dev/null +++ b/convert.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 9 14:55:43 2015 + +This script is to convert the txt annotation files to appropriate format needed by YOLO + +@author: Guanghan Ning +Email: gnxr9@mail.missouri.edu +""" + +import os +import time +from os import walk, getcwd +from PIL import Image + +classes = [] + +def convert(size, box): + dw = 1./size[0] + dh = 1./size[1] + x = (box[0] + box[1])/2.0 + y = (box[2] + box[3])/2.0 + w = box[1] - box[0] + h = box[3] - box[2] + x = x*dw + w = w*dw + y = y*dh + h = h*dh + return (x,y,w,h) + + +"""-------------------------------------------------------------------""" + +""" Configure Paths """ +mypath = "Labels/002/" +outpath = "Labels/converted/{}/".format(int(time.time())) +class_loc = "class.txt" +images_path = 'Images/002/%s.JPG' +os.makedirs(outpath) + +""" Load classes """ +with open(class_loc) as classes_f: + for c in classes_f: + classes.append(c.replace('\n', '')) + print("Loaded classes: ", classes) +list_file = open('list.txt', 'w+') + +""" Get input text file list """ +txt_name_list = [] +for (dirpath, dirnames, filenames) in walk(mypath): + txt_name_list.extend(filenames) + break +print(txt_name_list) + +""" Process """ +for txt_name in txt_name_list: + # txt_file = open("Labels/stop_sign/001.txt", "r") + + """ Open input text files """ + txt_path = mypath + txt_name + print("Input:" + txt_path) + txt_file = open(txt_path, "r") + lines = txt_file.read().split('\n') #for ubuntu, use "\r\n" instead of "\n" + + """ Open output text files """ + txt_outpath = outpath + txt_name + print("Output:" + txt_outpath) + txt_outfile = open(txt_outpath, "w") + + + """ Convert the data to YOLO format """ + ct = 0 + for line in lines: + #print('lenth of line is: ') + #print(len(line)) + #print('\n') + if(len(line) >= 2): + ct = ct + 1 + print(line + "\n") + elems = line.split(' ') + print(elems) + xmin = elems[0] + xmax = elems[2] + ymin = elems[1] + ymax = elems[3] + cls = elems[4] + # + img_path = str(images_path % (os.path.splitext(txt_name)[0])) + #t = magic.from_file(img_path) + #wh= re.search('(\d+) x (\d+)', t).groups() + im=Image.open(img_path) + w = int(im.size[0]) + h = int(im.size[1]) + #w = int(xmax) - int(xmin) + #h = int(ymax) - int(ymin) + # print(xmin) + print(w, h) + b = (float(xmin), float(xmax), float(ymin), float(ymax)) + bb = convert((w,h), b) + print(bb) + cls_id = classes.index(cls) + txt_outfile.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') + + """ Save those images with bb into list""" + if(ct != 0): + list_file.write('Images/%s.JPG\n'%(os.path.splitext(txt_name)[0])) + +list_file.close() diff --git a/list.txt b/list.txt new file mode 100644 index 00000000..09ebfd98 --- /dev/null +++ b/list.txt @@ -0,0 +1,3 @@ +Images/000008.JPG +Images/000000.JPG +Images/007480.JPG diff --git a/main.py b/main.py index e19a0888..bb2c5206 100644 --- a/main.py +++ b/main.py @@ -10,12 +10,13 @@ from Tkinter import * import tkMessageBox from PIL import Image, ImageTk +import ttk import os import glob import random # colors for the bboxes -COLORS = ['red', 'blue', 'yellow', 'pink', 'cyan', 'green', 'black'] +COLORS = ['red', 'blue', 'yellow', 'olive drab', 'purple', 'cyan', 'green', 'gold', 'orange', 'deep pink',] # image sizes for the examples SIZE = 256, 256 @@ -40,6 +41,9 @@ def __init__(self, master): self.imagename = '' self.labelfilename = '' self.tkimg = None + self.currentLabelclass = '' + self.cla_can_temp = [] + self.classcandidate_filename = 'class.txt' # initialize mouse state self.STATE = {} @@ -58,9 +62,10 @@ def __init__(self, master): self.label = Label(self.frame, text = "Image Dir:") self.label.grid(row = 0, column = 0, sticky = E) self.entry = Entry(self.frame) + self.entry.insert(END, '1') self.entry.grid(row = 0, column = 1, sticky = W+E) self.ldBtn = Button(self.frame, text = "Load", command = self.loadDir) - self.ldBtn.grid(row = 0, column = 2, sticky = W+E) + self.ldBtn.grid(row = 0, column = 2,sticky = W+E) # main panel for labeling self.mainPanel = Canvas(self.frame, cursor='tcross') @@ -68,23 +73,40 @@ def __init__(self, master): self.mainPanel.bind("", self.mouseMove) self.parent.bind("", self.cancelBBox) # press to cancel current bbox self.parent.bind("s", self.cancelBBox) - self.parent.bind("a", self.prevImage) # press 'a' to go backforward + self.parent.bind("a", self.prevImage) # press 'a' to go backwards self.parent.bind("d", self.nextImage) # press 'd' to go forward self.mainPanel.grid(row = 1, column = 1, rowspan = 4, sticky = W+N) + # choose class + self.classname = StringVar() + self.classcandidate = ttk.Combobox(self.frame,state='readonly',textvariable=self.classname) + self.classcandidate.grid(row=1,column=2) + if os.path.exists(self.classcandidate_filename): + with open(self.classcandidate_filename) as cf: + for line in cf.readlines(): + # print line + self.cla_can_temp.append(line.strip('\n')) + #print self.cla_can_temp + self.classcandidate['values'] = self.cla_can_temp + self.classcandidate.current(0) + self.currentLabelclass = self.classcandidate.get() #init + self.classcandidate.bind("<>", self.setClass) + #self.btnclass = Button(self.frame, text = 'ComfirmClass', command = self.setClass) + #self.btnclass.grid(row=2,column=2,sticky = W+E) + # showing bbox info & delete bbox self.lb1 = Label(self.frame, text = 'Bounding boxes:') - self.lb1.grid(row = 1, column = 2, sticky = W+N) + self.lb1.grid(row = 3, column = 2, sticky = W+N) self.listbox = Listbox(self.frame, width = 22, height = 12) - self.listbox.grid(row = 2, column = 2, sticky = N) + self.listbox.grid(row = 4, column = 2, sticky = N+S) self.btnDel = Button(self.frame, text = 'Delete', command = self.delBBox) - self.btnDel.grid(row = 3, column = 2, sticky = W+E+N) + self.btnDel.grid(row = 5, column = 2, sticky = W+E+N) self.btnClear = Button(self.frame, text = 'ClearAll', command = self.clearBBox) - self.btnClear.grid(row = 4, column = 2, sticky = W+E+N) + self.btnClear.grid(row = 6, column = 2, sticky = W+E+N) # control panel for image navigation self.ctrPanel = Frame(self.frame) - self.ctrPanel.grid(row = 5, column = 1, columnspan = 2, sticky = W+E) + self.ctrPanel.grid(row = 7, column = 1, columnspan = 2, sticky = W+E) self.prevBtn = Button(self.ctrPanel, text='<< Prev', width = 10, command = self.prevImage) self.prevBtn.pack(side = LEFT, padx = 5, pady = 3) self.nextBtn = Button(self.ctrPanel, text='Next >>', width = 10, command = self.nextImage) @@ -98,15 +120,16 @@ def __init__(self, master): self.goBtn = Button(self.ctrPanel, text = 'Go', command = self.gotoImage) self.goBtn.pack(side = LEFT) + # example pannel for illustration - self.egPanel = Frame(self.frame, border = 10) - self.egPanel.grid(row = 1, column = 0, rowspan = 5, sticky = N) - self.tmpLabel2 = Label(self.egPanel, text = "Examples:") - self.tmpLabel2.pack(side = TOP, pady = 5) - self.egLabels = [] - for i in range(3): - self.egLabels.append(Label(self.egPanel)) - self.egLabels[-1].pack(side = TOP) + #self.egPanel = Frame(self.frame, border = 10) + #self.egPanel.grid(row = 1, column = 0, rowspan = 5, sticky = N) + #self.tmpLabel2 = Label(self.egPanel, text = "Examples:") + #self.tmpLabel2.pack(side = TOP, pady = 5) + #self.egLabels = [] + #for i in range(3): + # self.egLabels.append(Label(self.egPanel)) + # self.egLabels[-1].pack(side = TOP) # display mouse position self.disp = Label(self.ctrPanel, text='') @@ -114,10 +137,11 @@ def __init__(self, master): self.frame.columnconfigure(1, weight = 1) self.frame.rowconfigure(4, weight = 1) + self.loadDir() # for debugging ## self.setImage() -## self.loadDir() +## self.loadDir() def loadDir(self, dbg = False): if not dbg: @@ -131,9 +155,12 @@ def loadDir(self, dbg = False): ## return # get image list self.imageDir = os.path.join(r'./Images', '%03d' %(self.category)) - self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPEG')) + #print self.imageDir + #print self.category + self.imageList = glob.glob(os.path.join(self.imageDir, '*.JPG')) + #print self.imageList if len(self.imageList) == 0: - print 'No .JPEG images found in the specified dir!' + print 'No .JPG images found in the specified dir!' return # default to the 1st image in the collection @@ -146,22 +173,24 @@ def loadDir(self, dbg = False): os.mkdir(self.outDir) # load example bboxes - self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) + #self.egDir = os.path.join(r'./Examples', '%03d' %(self.category)) + self.egDir = os.path.join(r'./Examples/demo') + print os.path.exists(self.egDir) if not os.path.exists(self.egDir): return - filelist = glob.glob(os.path.join(self.egDir, '*.JPEG')) + filelist = glob.glob(os.path.join(self.egDir, '*.JPG')) self.tmp = [] self.egList = [] random.shuffle(filelist) - for (i, f) in enumerate(filelist): - if i == 3: - break - im = Image.open(f) - r = min(SIZE[0] / im.size[0], SIZE[1] / im.size[1]) - new_size = int(r * im.size[0]), int(r * im.size[1]) - self.tmp.append(im.resize(new_size, Image.ANTIALIAS)) - self.egList.append(ImageTk.PhotoImage(self.tmp[-1])) - self.egLabels[i].config(image = self.egList[-1], width = SIZE[0], height = SIZE[1]) + #for (i, f) in enumerate(filelist): + # if i == 3: + # break + # im = Image.open(f) + # r = min(SIZE[0] / im.size[0], SIZE[1] / im.size[1]) + # new_size = int(r * im.size[0]), int(r * im.size[1]) + # self.tmp.append(im.resize(new_size, Image.ANTIALIAS)) + # self.egList.append(ImageTk.PhotoImage(self.tmp[-1])) + # self.egLabels[i].config(image = self.egList[-1], width = SIZE[0], height = SIZE[1]) self.loadImage() print '%d images loaded from %s' %(self.total, s) @@ -180,30 +209,25 @@ def loadImage(self): self.imagename = os.path.split(imagepath)[-1].split('.')[0] labelname = self.imagename + '.txt' self.labelfilename = os.path.join(self.outDir, labelname) - bbox_cnt = 0 + # bbox_cnt = 0 if os.path.exists(self.labelfilename): with open(self.labelfilename) as f: for (i, line) in enumerate(f): - if i == 0: - bbox_cnt = int(line.strip()) - continue - tmp = [int(t.strip()) for t in line.split()] -## print tmp + tmp = line.split() self.bboxList.append(tuple(tmp)) - tmpId = self.mainPanel.create_rectangle(tmp[0], tmp[1], \ - tmp[2], tmp[3], \ + tmpId = self.mainPanel.create_rectangle(int(tmp[0]), int(tmp[1]), \ + int(tmp[2]), int(tmp[3]), \ width = 2, \ outline = COLORS[(len(self.bboxList)-1) % len(COLORS)]) self.bboxIdList.append(tmpId) - self.listbox.insert(END, '(%d, %d) -> (%d, %d)' %(tmp[0], tmp[1], tmp[2], tmp[3])) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(tmp[4],int(tmp[0]), int(tmp[1]), \ + int(tmp[2]), int(tmp[3]))) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) def saveImage(self): with open(self.labelfilename, 'w') as f: - f.write('%d\n' %len(self.bboxList)) for bbox in self.bboxList: f.write(' '.join(map(str, bbox)) + '\n') - print 'Image No. %d saved' %(self.cur) def mouseClick(self, event): @@ -212,10 +236,10 @@ def mouseClick(self, event): else: x1, x2 = min(self.STATE['x'], event.x), max(self.STATE['x'], event.x) y1, y2 = min(self.STATE['y'], event.y), max(self.STATE['y'], event.y) - self.bboxList.append((x1, y1, x2, y2)) + self.bboxList.append((x1, y1, x2, y2, self.currentLabelclass)) self.bboxIdList.append(self.bboxId) self.bboxId = None - self.listbox.insert(END, '(%d, %d) -> (%d, %d)' %(x1, y1, x2, y2)) + self.listbox.insert(END, '%s : (%d, %d) -> (%d, %d)' %(self.currentLabelclass,x1, y1, x2, y2)) self.listbox.itemconfig(len(self.bboxIdList) - 1, fg = COLORS[(len(self.bboxIdList) - 1) % len(COLORS)]) self.STATE['click'] = 1 - self.STATE['click'] @@ -279,6 +303,10 @@ def gotoImage(self): self.cur = idx self.loadImage() + def setClass(self, *args, **kwargs): + self.currentLabelclass = self.classcandidate.get() + print 'set label class to :',self.currentLabelclass + ## def setImage(self, imagepath = r'test2.png'): ## self.img = Image.open(imagepath) ## self.tkimg = ImageTk.PhotoImage(self.img)