diff --git a/README.md b/README.md index 1adf19b..bd80467 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Simple Image Annotator ## Description -All image annotators I found either didn't work or had some overhead in the setup. So, I tried to make this one simple to run and simple to use. +I modified a great simple image annotator developed by [Sebastian Perez](https://github.com/sgp715) to facilitate browsing forward and backward and to output data in per-image files in KITTI format for easy consumption for object detection using detectnet. ![action](./actionshot.png) ## Install @@ -16,22 +16,24 @@ $ pip install Flask ``` $ python app.py /images/directory ``` -* you can also specify the file you would like the annotations output to (out.csv is the default) +* you can also specify the directory you would like the annotations output to (image dir is the default) ``` -$ python app.py /images/directory --out test.csv +$ python app.py /images/directory --out /labels ``` -* open http://127.0.0.1:5000/tagger in your browser +* specify port if you want (5555 is default) +``` +$ python app.py /images/directory --out /labels --port 5556 +``` +* open http://127.0.0.1:5555/tagger in your browser * only tested on Chrome ## Output -* in keeping with simplicity, the output is to a csv file with the following fields - * *id* - id of the bounding box within the image - * *name* - name of the bounding box within the image - * *image* - image the bounding box is associated with - * *xMin* - min x value of the bounding box - * *xMax* - max x value of the bounding box - * *yMin* - min y value of the bounding box - * *yMax* - max y value of the bounding box +* This branch outputs data in KITTI format for easy consumption in DIGITS using detectnet + * filename is the same as the input image file prefix changed to .txt + * pertinent columns for detectnet are 0: label; 1: truncated; 4: xmin; 5: ymin; 6: xmax; 7: ymax +``` +person 0.2 0 0.0 114.0 650.0 227.0 796.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 +``` ## HOWTOs * draw a bounding box @@ -43,8 +45,10 @@ $ python app.py /images/directory --out test.csv * enter the name you want in the input field * press enter * move to next image - * click the blue arrow button at the bottom of the page (depending on the size of the image you may have to scroll down) + * click the blue right arrow button at the top of the page (depending on the size of the image you may have to scroll down) +* move to the previous image + * click the left arrow button at the top of the page * remove label click the red button on the label you would like to remove * check generated data - * at the top level of the directory where the program was run, there should be a file called out.csv that contains the generate data + * the output directory should contain one .txt file per image with KITTI format data diff --git a/actionshot.png b/actionshot.png index facddab..8a30e3a 100644 Binary files a/actionshot.png and b/actionshot.png differ diff --git a/app.py b/app.py index 7067695..26198e0 100644 --- a/app.py +++ b/app.py @@ -1,43 +1,66 @@ import sys +import os.path from os import walk import imghdr import csv import argparse +import re from flask import Flask, redirect, url_for, request from flask import render_template from flask import send_file - app = Flask(__name__) app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 +app.config['CURFIL'] = "" + +def writeLabels(): + image = app.config["FILES"][app.config["HEAD"]] + with open(app.config["OUTDIR"]+re.sub('\.(jpg|jpeg|png)','.txt',image),'w') as f: + for label in app.config["LABELS"]: + if label["name"] == "":continue + trunc = "0.0" + if "-trunc-" in label["name"]: + trunc = re.sub('.*-','',label["name"]) + label["name"] = re.sub('-.*','',label["name"]) + f.write(label["name"] + " " + + trunc + " " + "0 0.0 " + + str(round(float(label["xMin"]))) + " " + + str(round(float(label["yMin"]))) + " " + + str(round(float(label["xMax"]))) + " " + + str(round(float(label["yMax"]))) + " " + + "0.0 0.0 0.0 0.0 0.0 0.0 0.0\n") + f.close() @app.route('/tagger') def tagger(): if (app.config["HEAD"] == len(app.config["FILES"])): return redirect(url_for('bye')) + if (app.config["HEAD"] < 0): + app.config["HEAD"] = 0 + return redirect(url_for('tagger')) directory = app.config['IMAGES'] image = app.config["FILES"][app.config["HEAD"]] labels = app.config["LABELS"] - # [{"id":"1", "name":None, "ymin":0, "ymax":2, "xmin":0, "ymax":5}, - # {"id":"2", "name":"image", "ymin":0, "ymax":20, "xmin":6, "ymax":50}] + if not image == app.config["CURFIL"]: + app.config["CURFIL"] = image + current_file = app.config["OUTDIR"]+re.sub('\.(jpg|jpeg|png)','.txt',app.config["CURFIL"]) + if os.path.isfile(current_file): + for idx,line in enumerate(open(current_file,'r').readlines()): + larr = line.strip().split(" ") + lname = larr[0] + if not larr[1] == "0.0": + lname = lname + "-trunc-" + larr[1] + app.config["LABELS"].append({"id":idx+1, "name":lname, "xMin":float(larr[4]), "yMin":float(larr[5]), "xMax":float(larr[6]), "yMax":float(larr[7])}) not_end = not(app.config["HEAD"] == len(app.config["FILES"]) - 1) - print(not_end) - return render_template('tagger.html', not_end=not_end, directory=directory, image=image, labels=labels, head=app.config["HEAD"] + 1, len=len(app.config["FILES"])) + not_begin = app.config["HEAD"] > 0 + return render_template('tagger.html', not_end=not_end, not_begin=not_begin, directory=directory, image=image, labels=labels, head=app.config["HEAD"] + 1, len=len(app.config["FILES"]), filename=image) @app.route('/next') def next(): - image = app.config["FILES"][app.config["HEAD"]] + writeLabels() app.config["HEAD"] = app.config["HEAD"] + 1 - with open(app.config["OUT"],'a') as f: - for label in app.config["LABELS"]: - f.write(image + "," + - label["id"] + "," + - label["name"] + "," + - str(round(float(label["xMin"]))) + "," + - str(round(float(label["xMax"]))) + "," + - str(round(float(label["yMin"]))) + "," + - str(round(float(label["yMax"]))) + "\n") app.config["LABELS"] = [] return redirect(url_for('tagger')) @@ -68,21 +91,23 @@ def label(id): app.config["LABELS"][int(id) - 1]["name"] = name return redirect(url_for('tagger')) -# @app.route('/prev') -# def prev(): -# app.config["HEAD"] = app.config["HEAD"] - 1 -# return redirect(url_for('tagger')) +@app.route('/prev') +def prev(): + writeLabels() + app.config["HEAD"] = app.config["HEAD"] - 1 + app.config["LABELS"] = [] + return redirect(url_for('tagger')) @app.route('/image/') def images(f): images = app.config['IMAGES'] return send_file(images + f) - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('dir', type=str, help='specify the images directory') - parser.add_argument("--out") + parser.add_argument("--out", help='specify labels director') + parser.add_argument("--port", help='specify port to run on') args = parser.parse_args() directory = args.dir if directory[len(directory) - 1] != "/": @@ -91,7 +116,7 @@ def images(f): app.config["LABELS"] = [] files = None for (dirpath, dirnames, filenames) in walk(app.config["IMAGES"]): - files = filenames + files = sorted(filenames) break if files == None: print("No files") @@ -99,10 +124,16 @@ def images(f): app.config["FILES"] = files app.config["HEAD"] = 0 if args.out == None: - app.config["OUT"] = "out.csv" + app.config["OUTDIR"] = args.dir + else: + app.config["OUTDIR"] = args.out + if not app.config["OUTDIR"].endswith("/"): + app.config["OUTDIR"] += "/" + if args.port == None: + app.config["PORT"] = 5555 else: - app.config["OUT"] = args.out + app.config["PORT"] = args.port print(files) with open("out.csv",'w') as f: f.write("image,id,name,xMin,xMax,yMin,yMax\n") - app.run(debug="True") + app.run(host='0.0.0.0',debug="True",port=int(app.config["PORT"])) diff --git a/tagger.html b/tagger.html new file mode 100644 index 0000000..b33fa34 --- /dev/null +++ b/tagger.html @@ -0,0 +1,167 @@ + + + + Tagger + + + + + + +
+
+ {{ head }} / {{ len }} [{{ filename }}] + {% if not_end %} + + + + {% else %} + + + + {% endif %} + {% if not_begin %} + + + + {% endif %} +
+
+ +
+ + +
+ + diff --git a/templates/tagger.html b/templates/tagger.html index ce3da0f..317c5a8 100644 --- a/templates/tagger.html +++ b/templates/tagger.html @@ -64,6 +64,11 @@

Labels

{% endif %} + {% if not_begin %} + + + + {% endif %}
@@ -81,7 +86,7 @@

Labels

ctx.rect(xMin, yMin, xMax - xMin, yMax - yMin); ctx.lineWidth="3"; ctx.stroke(); - ctx.font = "10px Arial"; + ctx.font = "20px Arial"; ctx.fillText("id: " + id, xMin,yMin); }; var image = new Image();