| import os |
| import time |
| import cPickle |
| import datetime |
| import logging |
| import flask |
| import werkzeug |
| import optparse |
| import tornado.wsgi |
| import tornado.httpserver |
| import numpy as np |
| import pandas as pd |
| from PIL import Image |
| import cStringIO as StringIO |
| import urllib |
| import exifutil |
|
|
| import caffe |
|
|
| REPO_DIRNAME = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + '/../..') |
| UPLOAD_FOLDER = '/tmp/caffe_demos_uploads' |
| ALLOWED_IMAGE_EXTENSIONS = set(['png', 'bmp', 'jpg', 'jpe', 'jpeg', 'gif']) |
|
|
| |
| app = flask.Flask(__name__) |
|
|
|
|
| @app.route('/') |
| def index(): |
| return flask.render_template('index.html', has_result=False) |
|
|
|
|
| @app.route('/classify_url', methods=['GET']) |
| def classify_url(): |
| imageurl = flask.request.args.get('imageurl', '') |
| try: |
| string_buffer = StringIO.StringIO( |
| urllib.urlopen(imageurl).read()) |
| image = caffe.io.load_image(string_buffer) |
|
|
| except Exception as err: |
| |
| |
| logging.info('URL Image open error: %s', err) |
| return flask.render_template( |
| 'index.html', has_result=True, |
| result=(False, 'Cannot open image from URL.') |
| ) |
|
|
| logging.info('Image: %s', imageurl) |
| result = app.clf.classify_image(image) |
| return flask.render_template( |
| 'index.html', has_result=True, result=result, imagesrc=imageurl) |
|
|
|
|
| @app.route('/classify_upload', methods=['POST']) |
| def classify_upload(): |
| try: |
| |
| imagefile = flask.request.files['imagefile'] |
| filename_ = str(datetime.datetime.now()).replace(' ', '_') + \ |
| werkzeug.secure_filename(imagefile.filename) |
| filename = os.path.join(UPLOAD_FOLDER, filename_) |
| imagefile.save(filename) |
| logging.info('Saving to %s.', filename) |
| image = exifutil.open_oriented_im(filename) |
|
|
| except Exception as err: |
| logging.info('Uploaded image open error: %s', err) |
| return flask.render_template( |
| 'index.html', has_result=True, |
| result=(False, 'Cannot open uploaded image.') |
| ) |
|
|
| result = app.clf.classify_image(image) |
| return flask.render_template( |
| 'index.html', has_result=True, result=result, |
| imagesrc=embed_image_html(image) |
| ) |
|
|
|
|
| def embed_image_html(image): |
| """Creates an image embedded in HTML base64 format.""" |
| image_pil = Image.fromarray((255 * image).astype('uint8')) |
| image_pil = image_pil.resize((256, 256)) |
| string_buf = StringIO.StringIO() |
| image_pil.save(string_buf, format='png') |
| data = string_buf.getvalue().encode('base64').replace('\n', '') |
| return 'data:image/png;base64,' + data |
|
|
|
|
| def allowed_file(filename): |
| return ( |
| '.' in filename and |
| filename.rsplit('.', 1)[1] in ALLOWED_IMAGE_EXTENSIONS |
| ) |
|
|
|
|
| class ImagenetClassifier(object): |
| default_args = { |
| 'model_def_file': ( |
| '{}/models/bvlc_reference_caffenet/deploy.prototxt'.format(REPO_DIRNAME)), |
| 'pretrained_model_file': ( |
| '{}/models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'.format(REPO_DIRNAME)), |
| 'mean_file': ( |
| '{}/python/caffe/imagenet/ilsvrc_2012_mean.npy'.format(REPO_DIRNAME)), |
| 'class_labels_file': ( |
| '{}/data/ilsvrc12/synset_words.txt'.format(REPO_DIRNAME)), |
| 'bet_file': ( |
| '{}/data/ilsvrc12/imagenet.bet.pickle'.format(REPO_DIRNAME)), |
| } |
| for key, val in default_args.iteritems(): |
| if not os.path.exists(val): |
| raise Exception( |
| "File for {} is missing. Should be at: {}".format(key, val)) |
| default_args['image_dim'] = 256 |
| default_args['raw_scale'] = 255. |
|
|
| def __init__(self, model_def_file, pretrained_model_file, mean_file, |
| raw_scale, class_labels_file, bet_file, image_dim, gpu_mode): |
| logging.info('Loading net and associated files...') |
| if gpu_mode: |
| caffe.set_mode_gpu() |
| else: |
| caffe.set_mode_cpu() |
| self.net = caffe.Classifier( |
| model_def_file, pretrained_model_file, |
| image_dims=(image_dim, image_dim), raw_scale=raw_scale, |
| mean=np.load(mean_file).mean(1).mean(1), channel_swap=(2, 1, 0) |
| ) |
|
|
| with open(class_labels_file) as f: |
| labels_df = pd.DataFrame([ |
| { |
| 'synset_id': l.strip().split(' ')[0], |
| 'name': ' '.join(l.strip().split(' ')[1:]).split(',')[0] |
| } |
| for l in f.readlines() |
| ]) |
| self.labels = labels_df.sort('synset_id')['name'].values |
|
|
| self.bet = cPickle.load(open(bet_file)) |
| |
| |
| |
| self.bet['infogain'] -= np.array(self.bet['preferences']) * 0.1 |
|
|
| def classify_image(self, image): |
| try: |
| starttime = time.time() |
| scores = self.net.predict([image], oversample=True).flatten() |
| endtime = time.time() |
|
|
| indices = (-scores).argsort()[:5] |
| predictions = self.labels[indices] |
|
|
| |
| |
| meta = [ |
| (p, '%.5f' % scores[i]) |
| for i, p in zip(indices, predictions) |
| ] |
| logging.info('result: %s', str(meta)) |
|
|
| |
| expected_infogain = np.dot( |
| self.bet['probmat'], scores[self.bet['idmapping']]) |
| expected_infogain *= self.bet['infogain'] |
|
|
| |
| infogain_sort = expected_infogain.argsort()[::-1] |
| bet_result = [(self.bet['words'][v], '%.5f' % expected_infogain[v]) |
| for v in infogain_sort[:5]] |
| logging.info('bet result: %s', str(bet_result)) |
|
|
| return (True, meta, bet_result, '%.3f' % (endtime - starttime)) |
|
|
| except Exception as err: |
| logging.info('Classification error: %s', err) |
| return (False, 'Something went wrong when classifying the ' |
| 'image. Maybe try another one?') |
|
|
|
|
| def start_tornado(app, port=5000): |
| http_server = tornado.httpserver.HTTPServer( |
| tornado.wsgi.WSGIContainer(app)) |
| http_server.listen(port) |
| print("Tornado server starting on port {}".format(port)) |
| tornado.ioloop.IOLoop.instance().start() |
|
|
|
|
| def start_from_terminal(app): |
| """ |
| Parse command line options and start the server. |
| """ |
| parser = optparse.OptionParser() |
| parser.add_option( |
| '-d', '--debug', |
| help="enable debug mode", |
| action="store_true", default=False) |
| parser.add_option( |
| '-p', '--port', |
| help="which port to serve content on", |
| type='int', default=5000) |
| parser.add_option( |
| '-g', '--gpu', |
| help="use gpu mode", |
| action='store_true', default=False) |
|
|
| opts, args = parser.parse_args() |
| ImagenetClassifier.default_args.update({'gpu_mode': opts.gpu}) |
|
|
| |
| app.clf = ImagenetClassifier(**ImagenetClassifier.default_args) |
| app.clf.net.forward() |
|
|
| if opts.debug: |
| app.run(debug=True, host='0.0.0.0', port=opts.port) |
| else: |
| start_tornado(app, opts.port) |
|
|
|
|
| if __name__ == '__main__': |
| logging.getLogger().setLevel(logging.INFO) |
| if not os.path.exists(UPLOAD_FOLDER): |
| os.makedirs(UPLOAD_FOLDER) |
| start_from_terminal(app) |
|
|