├── .gitignore ├── app.py ├── darknet.py ├── exifutil.py ├── readme.md ├── requirements.txt ├── ssd_detect.py ├── static └── js │ └── template.js └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.pyc 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import os 3 | import time 4 | # import cPickle 5 | import datetime 6 | import logging 7 | import flask 8 | import werkzeug 9 | import optparse 10 | import tornado.wsgi 11 | import tornado.httpserver 12 | import numpy as np 13 | import pandas as pd 14 | from PIL import Image, ImageDraw 15 | # import cStringIO as StringIO 16 | try: 17 | import cStringIO as StringIO 18 | except ImportError: 19 | from io import StringIO 20 | 21 | import urllib 22 | import exifutil 23 | import sys 24 | reload(sys) 25 | sys.setdefaultencoding('utf-8') # add this to support Chinese in python2 26 | 27 | # import caffe 28 | import darknet 29 | 30 | REPO_DIRNAME = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + '/../..') 31 | UPLOAD_FOLDER = '/tmp/objdet_demos_uploads' 32 | ALLOWED_IMAGE_EXTENSIONS = set(['png', 'bmp', 'jpg', 'jpe', 'jpeg', 'gif', 'tif', 'tiff']) 33 | 34 | # Obtain the flask app object 35 | app = flask.Flask(__name__) 36 | 37 | 38 | @app.route('/') 39 | def index(): 40 | return flask.render_template('index.html', has_result=False) 41 | 42 | # fyk 43 | def load_img(img_buffer): 44 | # image = caffe.io.load_image(string_buffer) 45 | pass 46 | def disp_wait_msg(imagesrc): 47 | flask.render_template( 48 | 'index.html', has_result=True, 49 | result=(False, '处理图片中...'), 50 | imagesrc=imagesrc 51 | ) 52 | 53 | 54 | def draw_rectangle(draw, coordinates, color, width=1, draw_ellipse=False): 55 | for i in range(width): 56 | rect_start = (coordinates[0] - i, coordinates[1] - i) 57 | rect_end = (coordinates[2] + i, coordinates[3] + i) 58 | if draw_ellipse: 59 | draw.ellipse((rect_start, rect_end), outline=color) 60 | else: 61 | draw.rectangle((rect_start, rect_end), outline=color) 62 | 63 | 64 | def draw_rectangles(image_pil,det_result): 65 | # draw rectangles 66 | draw = ImageDraw.Draw(image_pil) 67 | for idx, item in enumerate(det_result): 68 | x, y, w, h = item[2] 69 | half_w = w / 2 70 | half_h = h / 2 71 | box = (int(x - half_w+1), int(y - half_h+1), int(x + half_w+1), int(y + half_h+1)) 72 | # draw.rectangle(box, outline=(0, 255, 0)) 73 | draw_rectangle(draw,box,(0, 255, 0),width=2,draw_ellipse=True) 74 | # draw.ellipse(box, outline=(255, 0, 0)) 75 | draw.text((x - half_w + 5, y - half_h + 5), str(idx + 1)+" : "+item[0], fill=(0, 0, 150)) 76 | del draw 77 | 78 | 79 | @app.route('/classify_url', methods=['GET']) 80 | def classify_url(): 81 | imageurl = flask.request.args.get('imageurl', '') 82 | try: 83 | # download 84 | raw_data = urllib.urlopen(imageurl).read() 85 | string_buffer = StringIO.StringIO(raw_data) 86 | # image = load_img(string_buffer) 87 | image_pil = Image.open(string_buffer) 88 | filename = os.path.join(UPLOAD_FOLDER, 'tmp.jpg') 89 | with open(filename,'wb') as f: 90 | f.write(raw_data) 91 | 92 | except Exception as err: 93 | # For any exception we encounter in reading the image, we will just 94 | # not continue. 95 | logging.info('URL Image open error: %s', err) 96 | return flask.render_template( 97 | 'index.html', has_result=True, 98 | result=(False, 'Cannot open image from URL.') 99 | ) 100 | 101 | logging.info('Image: %s', imageurl) 102 | # img_base64 = embed_image_html(filename) 103 | # disp_wait_msg(img_base64) 104 | results = app.clf.classify_image(filename) 105 | draw_rectangles(image_pil, results[1]) 106 | new_img_base64 = embed_image_html(image_pil) 107 | return flask.render_template( 108 | 'index.html', has_result=True, result=results, imagesrc=new_img_base64) 109 | # 'index.html', has_result=True, result=result, imagesrc=imageurl) 110 | 111 | 112 | @app.route('/classify_upload', methods=['POST']) 113 | def classify_upload(): 114 | try: 115 | # We will save the file to disk for possible data collection. 116 | imagefile = flask.request.files['imagefile'] 117 | filename_ = str(datetime.datetime.now()).replace(' ', '_') + \ 118 | werkzeug.secure_filename(imagefile.filename) 119 | filename = os.path.join(UPLOAD_FOLDER, filename_) 120 | imagefile.save(filename) 121 | logging.info('Saving to %s.', filename) 122 | image_pil = exifutil.open_oriented_pil(filename) 123 | 124 | except Exception as err: 125 | logging.info('Uploaded image open error: %s', err) 126 | return flask.render_template( 127 | 'index.html', has_result=True, 128 | result=(False, 'Cannot open uploaded image.') 129 | ) 130 | # img_base64 = embed_image_html(image_pil) 131 | # disp_wait_msg(img_base64) 132 | results = app.clf.classify_image(filename) 133 | # [('F22', 0.9006772637367249, (338.6946105957031, 431.28515625, 608.9721069335938, 220.40663146972656)), 134 | # ('F22', 0.890718400478363, (545.9476318359375, 294.4508361816406, 509.1690979003906, 177.72409057617188)), 135 | # ('F22', 0.8847938179969788, (642.2884521484375, 193.6743927001953, 401.5226745605469, 135.20948791503906))] 136 | 137 | draw_rectangles(image_pil, results[1]) 138 | new_img_base64 = embed_image_html(image_pil) 139 | # import time 140 | # time.sleep(5) # test 141 | return flask.render_template( 142 | 'index.html', has_result=True, result=results, 143 | imagesrc=new_img_base64 144 | ) 145 | 146 | 147 | def embed_image_html(image_pil): 148 | """Creates an image embedded in HTML base64 format.""" 149 | # image_pil = Image.fromarray((255 * image).astype('uint8')) 150 | # image_pil = Image.open(image) 151 | size = (512, 512) # (256, 256) 152 | resized = image_pil.resize(size) 153 | string_buf = StringIO.StringIO() 154 | resized.save(string_buf, format='png') 155 | data = string_buf.getvalue().encode('base64').replace('\n', '') 156 | return 'data:image/png;base64,' + data 157 | 158 | 159 | def allowed_file(filename): 160 | return ( 161 | '.' in filename and 162 | filename.rsplit('.', 1)[1] in ALLOWED_IMAGE_EXTENSIONS 163 | ) 164 | 165 | 166 | class ImagenetClassifier(object): 167 | default_args = { 168 | 'model_def_file': ( 169 | '{}/models/bvlc_reference_caffenet/deploy.prototxt'.format(REPO_DIRNAME)), 170 | 'pretrained_model_file': ( 171 | '{}/models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel'.format(REPO_DIRNAME)), 172 | 'mean_file': ( 173 | '{}/python/caffe/imagenet/ilsvrc_2012_mean.npy'.format(REPO_DIRNAME)), 174 | 'class_labels_file': ( 175 | '{}/data/ilsvrc12/synset_words.txt'.format(REPO_DIRNAME)), 176 | 'bet_file': ( 177 | '{}/data/ilsvrc12/imagenet.bet.pickle'.format(REPO_DIRNAME)), 178 | } 179 | # for key, val in default_args.iteritems(): 180 | # if not os.path.exists(val): 181 | # raise Exception( 182 | # "File for {} is missing. Should be at: {}".format(key, val)) 183 | default_args['image_dim'] = 256 184 | default_args['raw_scale'] = 255. 185 | 186 | # fyk 预先加载模型 187 | def __init__(self, model_def_file, pretrained_model_file, mean_file, 188 | raw_scale, class_labels_file, bet_file, image_dim, gpu_mode): 189 | logging.info('Loading net and associated files...') 190 | base_dir = "/home/s05/fyk/darknet-modify/" 191 | self.net = darknet.load_net(base_dir + "PLANE/yolo-voc.2.0.cfg", base_dir + "backup/yolo-voc_26000.weights", 0) 192 | self.meta = darknet.load_meta(base_dir + "PLANE/voc.data") 193 | 194 | # if gpu_mode: 195 | # caffe.set_mode_gpu() 196 | # else: 197 | # caffe.set_mode_cpu() 198 | # self.net = caffe.Classifier( 199 | # model_def_file, pretrained_model_file, 200 | # image_dims=(image_dim, image_dim), raw_scale=raw_scale, 201 | # mean=np.load(mean_file).mean(1).mean(1), channel_swap=(2, 1, 0) 202 | # ) 203 | # with open(class_labels_file) as f: 204 | # labels_df = pd.DataFrame([ 205 | # { 206 | # 'synset_id': l.strip().split(' ')[0], 207 | # 'name': ' '.join(l.strip().split(' ')[1:]).split(',')[0] 208 | # } 209 | # for l in f.readlines() 210 | # ]) 211 | # self.labels = labels_df.sort('synset_id')['name'].values 212 | # self.bet = cPickle.load(open(bet_file)) 213 | # A bias to prefer children nodes in single-chain paths 214 | # I am setting the value to 0.1 as a quick, simple model. 215 | # We could use better psychological models here... 216 | # self.bet['infogain'] -= np.array(self.bet['preferences']) * 0.1 217 | 218 | def classify_image(self, image_filename): 219 | try: 220 | starttime = time.time() 221 | # scores = self.net.predict([image], oversample=True).flatten() 222 | results = darknet.detect(self.net, self.meta, image_filename) 223 | # [('F22', 0.9006772637367249, (338.6946105957031, 431.28515625, 608.9721069335938, 220.40663146972656)), 224 | # ('F22', 0.890718400478363, (545.9476318359375, 294.4508361816406, 509.1690979003906, 177.72409057617188)), 225 | # ('F22', 0.8847938179969788, (642.2884521484375, 193.6743927001953, 401.5226745605469, 135.20948791503906))] 226 | endtime = time.time() 227 | bet_result = [(str(idx+1)+' : '+v[0], '%.5f' % v[1]) 228 | for idx, v in enumerate(results)] 229 | # logging.info('bet result: %s', str(bet_result)) 230 | rtn = (True, results, bet_result, '%.3f' % (endtime - starttime)) 231 | return rtn 232 | 233 | # indices = (-scores).argsort()[:5] 234 | # predictions = self.labels[indices] 235 | 236 | # In addition to the prediction text, we will also produce 237 | # the length for the progress bar visualization. 238 | # meta = [ 239 | # (p, '%.5f' % scores[i]) 240 | # for i, p in zip(indices, predictions) 241 | # ] 242 | # logging.info('result: %s', str(meta)) 243 | 244 | # Compute expected information gain 245 | # expected_infogain = np.dot( 246 | # self.bet['probmat'], scores[self.bet['idmapping']]) 247 | # expected_infogain *= self.bet['infogain'] 248 | 249 | # sort the scores 250 | # infogain_sort = expected_infogain.argsort()[::-1] 251 | # bet_result = [(self.bet['words'][v], '%.5f' % expected_infogain[v]) 252 | # for v in infogain_sort[:5]] 253 | # logging.info('bet result: %s', str(bet_result)) 254 | 255 | # return (True, meta, bet_result, '%.3f' % (endtime - starttime)) 256 | 257 | except Exception as err: 258 | logging.info('Classification error: %s', err) 259 | return (False, 'Something went wrong when classifying the ' 260 | 'image. Maybe try another one?') 261 | 262 | 263 | def start_tornado(app, port=5000): 264 | http_server = tornado.httpserver.HTTPServer( 265 | tornado.wsgi.WSGIContainer(app)) 266 | http_server.listen(port) 267 | print("Tornado server starting on port {}".format(port)) 268 | tornado.ioloop.IOLoop.instance().start() 269 | 270 | 271 | def start_from_terminal(app): 272 | """ 273 | Parse command line options and start the server. 274 | """ 275 | parser = optparse.OptionParser() 276 | parser.add_option( 277 | '-d', '--debug', 278 | help="enable debug mode", 279 | action="store_true", default=False) 280 | parser.add_option( 281 | '-p', '--port', 282 | help="which port to serve content on", 283 | type='int', default=5000) 284 | parser.add_option( 285 | '-g', '--gpu', 286 | help="use gpu mode", 287 | action='store_true', default=True) 288 | 289 | opts, args = parser.parse_args() 290 | ImagenetClassifier.default_args.update({'gpu_mode': opts.gpu}) 291 | 292 | # Initialize classifier + warm start by forward for allocation 293 | app.clf = ImagenetClassifier(**ImagenetClassifier.default_args) 294 | #app.clf.net.forward() 295 | 296 | if opts.debug: 297 | app.run(debug=True, host='0.0.0.0', port=opts.port) 298 | else: 299 | start_tornado(app, opts.port) 300 | 301 | 302 | if __name__ == '__main__': 303 | logging.getLogger().setLevel(logging.INFO) 304 | if not os.path.exists(UPLOAD_FOLDER): 305 | os.makedirs(UPLOAD_FOLDER) 306 | start_from_terminal(app) 307 | -------------------------------------------------------------------------------- /darknet.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import math 3 | import random 4 | 5 | 6 | def sample(probs): 7 | s = sum(probs) 8 | probs = [a / s for a in probs] 9 | r = random.uniform(0, 1) 10 | for i in range(len(probs)): 11 | r = r - probs[i] 12 | if r <= 0: 13 | return i 14 | return len(probs) - 1 15 | 16 | 17 | def c_array(ctype, values): 18 | return (ctype * len(values))(*values) 19 | 20 | 21 | class BOX(Structure): 22 | _fields_ = [("x", c_float), 23 | ("y", c_float), 24 | ("w", c_float), 25 | ("h", c_float)] 26 | 27 | 28 | class IMAGE(Structure): 29 | _fields_ = [("w", c_int), 30 | ("h", c_int), 31 | ("c", c_int), 32 | ("data", POINTER(c_float))] 33 | 34 | 35 | class METADATA(Structure): 36 | _fields_ = [("classes", c_int), 37 | ("names", POINTER(c_char_p))] 38 | 39 | 40 | # lib = CDLL("/home/pjreddie/documents/darknet/libdarknet.so", RTLD_GLOBAL) 41 | lib = CDLL("/home/s05/fyk/darknet-modify/libdarknet-gpu.so", RTLD_GLOBAL) 42 | #lib = CDLL("/home/s05/fyk/darknet-modify/libdarknet-cpu.so", RTLD_GLOBAL) 43 | lib.network_width.argtypes = [c_void_p] 44 | lib.network_width.restype = c_int 45 | lib.network_height.argtypes = [c_void_p] 46 | lib.network_height.restype = c_int 47 | 48 | predict = lib.network_predict_p 49 | predict.argtypes = [c_void_p, POINTER(c_float)] 50 | predict.restype = POINTER(c_float) 51 | 52 | make_boxes = lib.make_boxes 53 | make_boxes.argtypes = [c_void_p] 54 | make_boxes.restype = POINTER(BOX) 55 | 56 | free_ptrs = lib.free_ptrs 57 | free_ptrs.argtypes = [POINTER(c_void_p), c_int] 58 | 59 | num_boxes = lib.num_boxes 60 | num_boxes.argtypes = [c_void_p] 61 | num_boxes.restype = c_int 62 | 63 | make_probs = lib.make_probs 64 | make_probs.argtypes = [c_void_p] 65 | make_probs.restype = POINTER(POINTER(c_float)) 66 | 67 | detect = lib.network_predict_p 68 | detect.argtypes = [c_void_p, IMAGE, c_float, c_float, c_float, POINTER(BOX), POINTER(POINTER(c_float))] 69 | 70 | reset_rnn = lib.reset_rnn 71 | reset_rnn.argtypes = [c_void_p] 72 | 73 | load_net = lib.load_network_p 74 | load_net.argtypes = [c_char_p, c_char_p, c_int] 75 | load_net.restype = c_void_p 76 | 77 | free_image = lib.free_image 78 | free_image.argtypes = [IMAGE] 79 | 80 | letterbox_image = lib.letterbox_image 81 | letterbox_image.argtypes = [IMAGE, c_int, c_int] 82 | letterbox_image.restype = IMAGE 83 | 84 | load_meta = lib.get_metadata 85 | lib.get_metadata.argtypes = [c_char_p] 86 | lib.get_metadata.restype = METADATA 87 | 88 | load_image = lib.load_image_color 89 | load_image.argtypes = [c_char_p, c_int, c_int] 90 | load_image.restype = IMAGE 91 | 92 | predict_image = lib.network_predict_image 93 | predict_image.argtypes = [c_void_p, IMAGE] 94 | predict_image.restype = POINTER(c_float) 95 | 96 | network_detect = lib.network_detect 97 | network_detect.argtypes = [c_void_p, IMAGE, c_float, c_float, c_float, POINTER(BOX), POINTER(POINTER(c_float))] 98 | 99 | 100 | def classify(net, meta, im): 101 | out = predict_image(net, im) 102 | res = [] 103 | for i in range(meta.classes): 104 | res.append((meta.names[i], out[i])) 105 | res = sorted(res, key=lambda x: -x[1]) 106 | return res 107 | 108 | 109 | def detect(net, meta, image, thresh=.5, hier_thresh=.5, nms=.45): 110 | im = load_image(image, 0, 0) 111 | boxes = make_boxes(net) 112 | probs = make_probs(net) 113 | num = num_boxes(net) 114 | network_detect(net, im, thresh, hier_thresh, nms, boxes, probs) 115 | res = [] 116 | for j in range(num): 117 | for i in range(meta.classes): 118 | if probs[j][i] > 0: 119 | res.append((meta.names[i], probs[j][i], (boxes[j].x, boxes[j].y, boxes[j].w, boxes[j].h))) 120 | res = sorted(res, key=lambda x: -x[1]) 121 | free_image(im) 122 | free_ptrs(cast(probs, POINTER(c_void_p)), num) 123 | return res 124 | 125 | 126 | if __name__ == "__main__": 127 | # net = load_net("cfg/densenet201.cfg", "/home/pjreddie/trained/densenet201.weights", 0) 128 | # im = load_image("data/wolf.jpg", 0, 0) 129 | # meta = load_meta("cfg/imagenet1k.data") 130 | # r = classify(net, meta, im) 131 | # print r[:10] 132 | net = load_net("cfg/tiny-yolo.cfg", "tiny-yolo.weights", 0) 133 | meta = load_meta("cfg/coco.data") 134 | r = detect(net, meta, "data/dog.jpg") 135 | # print r 136 | -------------------------------------------------------------------------------- /exifutil.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script handles the skimage exif problem. 3 | """ 4 | 5 | from PIL import Image 6 | import numpy as np 7 | 8 | ORIENTATIONS = { # used in apply_orientation 9 | 2: (Image.FLIP_LEFT_RIGHT,), 10 | 3: (Image.ROTATE_180,), 11 | 4: (Image.FLIP_TOP_BOTTOM,), 12 | 5: (Image.FLIP_LEFT_RIGHT, Image.ROTATE_90), 13 | 6: (Image.ROTATE_270,), 14 | 7: (Image.FLIP_LEFT_RIGHT, Image.ROTATE_270), 15 | 8: (Image.ROTATE_90,) 16 | } 17 | 18 | def open_oriented_pil(im_path): 19 | im = Image.open(im_path) 20 | if hasattr(im, '_getexif'): 21 | exif = im._getexif() 22 | if exif is not None and 274 in exif: 23 | orientation = exif[274] 24 | im = apply_orientation(im, orientation) 25 | 26 | return im 27 | 28 | def open_oriented_im(im_path): 29 | im = open_oriented_pil(im_path) 30 | img = np.asarray(im) 31 | # img = np.asarray(im).astype(np.float32) / 255. # for caffe 32 | if img.ndim == 2: 33 | img = img[:, :, np.newaxis] 34 | img = np.tile(img, (1, 1, 3)) 35 | elif img.shape[2] == 4: 36 | img = img[:, :, :3] 37 | return img 38 | 39 | 40 | def apply_orientation(im, orientation): 41 | if orientation in ORIENTATIONS: 42 | for method in ORIENTATIONS[orientation]: 43 | im = im.transpose(method) 44 | return im 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Object Detection Web Demo 3 | 4 | 5 | 6 | 7 | 8 | Image object detection demo(YOLO,SSD, Faster rcnn, etc.) running as a Flask web server. 9 | 10 | > ***Notice*** 11 | This repo is not a turnkey project for object detection web system but an easy template for those who are not familiar with Web development just like me. You need to understand and modify the code little or much to use. 12 | 13 | ## Requirements 14 | 15 | The demo server requires Python with some dependencies. 16 | To make sure you have the dependencies, please run `pip install -r examples/web_demo/requirements.txt`. 17 | 18 | ## Run 19 | 20 | Running `python app.py` will bring up the demo server, accessible at `http://0.0.0.0:5000`. 21 | You can enable debug mode of the web server, or switch to a different port: 22 | 23 | % python app.py -h 24 | Usage: app.py [options] 25 | 26 | Options: 27 | -h, --help show this help message and exit 28 | -d, --debug enable debug mode 29 | -p PORT, --port=PORT which port to serve content on 30 | 31 | ## More 32 | 33 | The Javascript code `static/js/template.js` includes several part: 34 | 35 | - use of `Dropzone.js`(Drag and drop file plugin) to upload image and get result with AJAX. 36 | - parse JSON result and draw rectangles on image in browser canvas. 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | werkzeug 2 | flask 3 | tornado 4 | numpy 5 | pandas 6 | pillow 7 | pyyaml 8 | -------------------------------------------------------------------------------- /ssd_detect.py: -------------------------------------------------------------------------------- 1 | #encoding=utf8 2 | ''' 3 | Detection with SSD 4 | In this example, we will load a SSD model and use it to detect objects. 5 | ''' 6 | 7 | import os 8 | import sys 9 | import argparse 10 | import numpy as np 11 | from PIL import Image, ImageDraw 12 | # Make sure that caffe is on the python path: 13 | caffe_root = '/home/s05/caffe_ssd/caffe/' 14 | os.chdir(caffe_root) 15 | sys.path.insert(0, os.path.join(caffe_root, 'python')) 16 | import caffe 17 | 18 | from google.protobuf import text_format 19 | from caffe.proto import caffe_pb2 20 | 21 | 22 | def get_labelname(labelmap, labels): 23 | num_labels = len(labelmap.item) 24 | labelnames = [] 25 | if type(labels) is not list: 26 | labels = [labels] 27 | for label in labels: 28 | found = False 29 | for i in xrange(0, num_labels): 30 | if label == labelmap.item[i].label: 31 | found = True 32 | labelnames.append(labelmap.item[i].display_name) 33 | break 34 | assert found == True 35 | return labelnames 36 | 37 | class CaffeDetection: 38 | def __init__(self, gpu_id, model_def, model_weights, image_resize, labelmap_file): 39 | if gpu_id == -1: 40 | caffe.set_mode_cpu() 41 | else: 42 | caffe.set_device(gpu_id) 43 | caffe.set_mode_gpu() 44 | 45 | self.image_resize = image_resize 46 | # Load the net in the test phase for inference, and configure input preprocessing. 47 | self.net = caffe.Net(model_def, # defines the structure of the model 48 | model_weights, # contains the trained weights 49 | caffe.TEST) # use test mode (e.g., don't perform dropout) 50 | # input preprocessing: 'data' is the name of the input blob == net.inputs[0] 51 | self.transformer = caffe.io.Transformer({'data': self.net.blobs['data'].data.shape}) 52 | self.transformer.set_transpose('data', (2, 0, 1)) 53 | self.transformer.set_mean('data', np.array([104, 117, 123])) # mean pixel 54 | # the reference model operates on images in [0,255] range instead of [0,1] 55 | self.transformer.set_raw_scale('data', 255) 56 | # the reference model has channels in BGR order instead of RGB 57 | self.transformer.set_channel_swap('data', (2, 1, 0)) 58 | 59 | # load PASCAL VOC labels 60 | file = open(labelmap_file, 'r') 61 | self.labelmap = caffe_pb2.LabelMap() 62 | text_format.Merge(str(file.read()), self.labelmap) 63 | 64 | def detect(self, image_file, conf_thresh=0.5, topn=5): 65 | ''' 66 | SSD detection 67 | ''' 68 | # set net to batch size of 1 69 | # image_resize = 300 70 | self.net.blobs['data'].reshape(1, 3, self.image_resize, self.image_resize) 71 | image = caffe.io.load_image(image_file) 72 | 73 | #Run the net and examine the top_k results 74 | transformed_image = self.transformer.preprocess('data', image) 75 | self.net.blobs['data'].data[...] = transformed_image 76 | 77 | # Forward pass. 78 | detections = self.net.forward()['detection_out'] 79 | 80 | # Parse the outputs. 81 | det_label = detections[0,0,:,1] 82 | det_conf = detections[0,0,:,2] 83 | det_xmin = detections[0,0,:,3] 84 | det_ymin = detections[0,0,:,4] 85 | det_xmax = detections[0,0,:,5] 86 | det_ymax = detections[0,0,:,6] 87 | #conf_thresh=0.8 88 | # Get detections with confidence higher than 0.6. 89 | top_indices = [i for i, conf in enumerate(det_conf) if conf >= conf_thresh] 90 | 91 | top_conf = det_conf[top_indices] 92 | top_label_indices = det_label[top_indices].tolist() 93 | top_labels = get_labelname(self.labelmap, top_label_indices) 94 | top_xmin = det_xmin[top_indices] 95 | top_ymin = det_ymin[top_indices] 96 | top_xmax = det_xmax[top_indices] 97 | top_ymax = det_ymax[top_indices] 98 | 99 | result = [] 100 | for i in xrange(min(topn, top_conf.shape[0])): 101 | xmin = top_xmin[i] # xmin = int(round(top_xmin[i] * image.shape[1])) 102 | ymin = top_ymin[i] # ymin = int(round(top_ymin[i] * image.shape[0])) 103 | xmax = top_xmax[i] # xmax = int(round(top_xmax[i] * image.shape[1])) 104 | ymax = top_ymax[i] # ymax = int(round(top_ymax[i] * image.shape[0])) 105 | score = top_conf[i] 106 | label = int(top_label_indices[i]) 107 | label_name = top_labels[i] 108 | result.append([xmin, ymin, xmax, ymax, label, score, label_name]) 109 | return result 110 | 111 | def main(args): 112 | '''main ''' 113 | detection = CaffeDetection(args.gpu_id, 114 | args.model_def, args.model_weights, 115 | args.image_resize, args.labelmap_file) 116 | result = detection.detect(args.image_file) 117 | print result 118 | 119 | img = Image.open(args.image_file) 120 | draw = ImageDraw.Draw(img) 121 | width, height = img.size 122 | print width, height 123 | for item in result: 124 | xmin = int(round(item[0] * width)) 125 | ymin = int(round(item[1] * height)) 126 | xmax = int(round(item[2] * width)) 127 | ymax = int(round(item[3] * height)) 128 | draw.rectangle([xmin, ymin, xmax, ymax], outline=(255, 0, 0)) 129 | draw.text([xmin, ymin], item[-1] + str(item[-2]), (0, 0, 255)) 130 | print item 131 | print [xmin, ymin, xmax, ymax] 132 | print [xmin, ymin], item[-1] 133 | img.save('detect_result.jpg') 134 | 135 | 136 | def parse_args(): 137 | '''parse args''' 138 | parser = argparse.ArgumentParser() 139 | parser.add_argument('--gpu_id', type=int, default=0, help='gpu id') 140 | parser.add_argument('--labelmap_file', 141 | default='/home/s05/caffe_ssd/caffe/data/VOC0712/labelmap_voc.prototxt') 142 | parser.add_argument('--model_def', 143 | default='/home/s05/caffe_ssd/caffe/models/VGGNet/VOC0712/SSD_300x300/deploy.prototxt') 144 | parser.add_argument('--image_resize', default=300, type=int) 145 | parser.add_argument('--model_weights', 146 | default='/home/s05/caffe_ssd/caffe/models/VGGNet/VOC0712/SSD_300x300/' 147 | 'VGG_VOC0712_SSD_300x300_iter_6000.caffemodel') 148 | # parser.add_argument('--image_file', default='/home/s05/caffe_ssd/caffe/examples/images/plane (25).jpg') 149 | return parser.parse_args() 150 | def load_net(): 151 | args = parse_args() 152 | detection = CaffeDetection(args.gpu_id, 153 | args.model_def, args.model_weights, 154 | args.image_resize, args.labelmap_file) 155 | return detection 156 | def detect(net,image_file): 157 | result = net.detect(image_file) 158 | return result 159 | if __name__ == '__main__': 160 | main(parse_args()) -------------------------------------------------------------------------------- /static/js/template.js: -------------------------------------------------------------------------------- 1 | function require(script) { 2 | $.ajax({ 3 | url: script, 4 | dataType: "script", 5 | async: false, // <-- This is the key, however this has been deprecated, for more solutions, see https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file 6 | success: function () { 7 | // all good... 8 | }, 9 | error: function () { 10 | throw new Error("Could not load script " + script); 11 | } 12 | }); 13 | } 14 | // load the script of decompressing tiff image, for tiff/tif is not normal images supported by browser 15 | //require("/static/js/tiff.min.js"); 16 | // load the script of dropzone(drag and drop file utils). 17 | //require("/static/js/dropzone.js"); 18 | 19 | var currentFile = null; 20 | var resizeRatio = 1; 21 | var dx = 0; 22 | var dy = 0; 23 | Dropzone.autoDiscover = false; 24 | var myDropzone = new Dropzone("#dropz", { //url: "/upload-det-rsi"}); 25 | url: "/upload-det-rsi", 26 | timeout: 600000, /*milliseconds, default is 30 sec*/ 27 | maxFiles: 100, 28 | maxFilesize: 1024, 29 | // acceptedFiles: ".jpg,.jpeg,.doc,.docx,.ppt,.pptx,.txt,.avi,.pdf,.mp3,.zip", 30 | // autoProcessQueue: false, 31 | // paramName: "file", 32 | createImageThumbnails: false,//不显示缩略图 33 | previewsContainer: false,//不显示preview 34 | // dictDefaultMessage: "拖入需要上传的文件", 35 | init: function () { 36 | var myDropzone = this;//, submitButton = document.querySelector("#qr"), 37 | //cancelButton = document.querySelector("#cancel"); 38 | var picshow = document.querySelector("#dropz"); 39 | myDropzone.on('addedfile', function (file) { 40 | //添加上传文件的过程 41 | if (currentFile) { 42 | this.removeFile(currentFile); 43 | } 44 | currentFile = file; 45 | picshow.setAttribute('plus_sign', 'none'); 46 | $(".dz-message").html(null); 47 | var subregions = document.getElementById('rightpic'); 48 | $(subregions).empty(); 49 | subregions.setAttribute("class", "rightpic_hide"); 50 | var FR= new FileReader(); 51 | // handle special type of images 52 | if (file.type != "image/tif" && file.type != "image/tiff") { 53 | FR.onload = function(e) { 54 | // console.log( e.target.result); //This is the base64 data of file(gif) dropped 55 | //if you want to display it somewhere in your previewTemplate 56 | // var temp = file.previewTemplate; 57 | // picshow.innerHTML = ""; 58 | // var imgTag = picshow.querySelector("img"); 59 | // imgTag.setAttribute('src', e.target.result); //setting as src of some img tag with class 'my-preview' 60 | 61 | var img = new Image(); 62 | img.onload = imageLoaded; 63 | img.src = e.target.result; 64 | }; 65 | FR.readAsDataURL(file); 66 | 67 | } else { 68 | FR.onload = function (event) { 69 | var buffer = event.target.result; 70 | var tiff = new Tiff({ buffer: buffer }); 71 | var tif_canvas = tiff.toCanvas(); 72 | //var width = tiff.width(); 73 | //var height = tiff.height(); 74 | var dataURL = tif_canvas.toDataURL(); 75 | var img = new Image(); 76 | img.onload = imageLoaded; 77 | img.src = dataURL; 78 | }; 79 | FR.onerror = function (event) { 80 | //console.error("File could not be read! Code " + event.target.error.code); 81 | $("#status").text("File could not be read! Code " + event.target.error.code); 82 | }; 83 | FR.readAsArrayBuffer(file); 84 | } 85 | }); 86 | myDropzone.on('sending', function (data, xhr, formData) { 87 | /*Called just before each file is sent*/ 88 | xhr.ontimeout = (() => { 89 | /*Execute on case of timeout only*/ 90 | $("#status").text('Server Timeout') 91 | }); 92 | //向后台发送该文件前添加参数、表单 93 | var thresholdValue = $("#thresholdValue").text(); 94 | formData.append('threshold', thresholdValue); 95 | // formData.append('watermark', jQuery('#info').val()); 96 | }); 97 | myDropzone.on("complete", function(file) { 98 | // console.log("结束"); 99 | }); 100 | myDropzone.on('success', function (files, response) { 101 | // 得到返回结果 102 | // console.log(response); 103 | 104 | showResult(response); 105 | }); 106 | myDropzone.on('error', function (files, response) { 107 | //文件上传失败后的操作 108 | $("#status").text("上传失败"); 109 | }); 110 | myDropzone.on('totaluploadprogress', function (progress, byte, bytes) { 111 | //progress为进度百分比 112 | if (progress == 100){ 113 | $("#status").text("上传成功,等待结果..."); 114 | }else{ 115 | $("#status").text("上传进度:" + parseInt(progress) + "%"); 116 | } 117 | //计算上传速度和剩余时间 118 | /*var mm = 0; 119 | var byte = 0; 120 | var tt = setInterval(function () { 121 | mm++; 122 | var byte2 = bytes; 123 | var remain; 124 | var speed; 125 | var byteKb = byte/1024; 126 | var bytesKb = bytes/1024; 127 | var byteMb = byte/1024/1024; 128 | var bytesMb = bytes/1024/1024; 129 | if(byteKb <= 1024){ 130 | speed = (parseFloat(byte2 - byte)/(1024)/mm).toFixed(2) + " KB/s"; 131 | remain = (byteKb - bytesKb)/parseFloat(speed); 132 | }else{ 133 | speed = (parseFloat(byte2 - byte)/(1024*1024)/mm).toFixed(2) + " MB/s"; 134 | remain = (byteMb - bytesMb)/parseFloat(speed); 135 | } 136 | $("#dropz #speed").text("上传速率:" + speed); 137 | $("#dropz #time").text("剩余时间"+arrive_timer_format(parseInt(remain))); 138 | if(bytes >= byte){ 139 | clearInterval(tt); 140 | if(byteKb <= 1024){ 141 | $("#dropz #speed").text("上传速率:0 KB/s"); 142 | }else{ 143 | $("#dropz #speed").text("上传速率:0 MB/s"); 144 | } 145 | $("#dropz #time").text("剩余时间:0:00:00"); 146 | } 147 | },1000);*/ 148 | }); 149 | /*submitButton.addEventListener('click', function () { 150 | //点击上传文件 151 | myDropzone.processQueue(); 152 | }); 153 | cancelButton.addEventListener('click', function () { 154 | //取消上传 155 | myDropzone.removeAllFiles(); 156 | });*/ 157 | } 158 | }); 159 | function imageLoaded() { 160 | var img = this; 161 | showImageOnCanvas(img); 162 | } 163 | function showImageOnCanvas(img) { 164 | var canvas = document.getElementById('canvas'); 165 | canvas.style.display = 'block'; 166 | canvas.width = 700; 167 | canvas.height = 700; 168 | var max_side = img.width > img.height?img.width:img.height; 169 | resizeRatio = 700.0 / max_side; 170 | var dispWidth = img.width * resizeRatio; 171 | var dispHeight = img.height * resizeRatio; 172 | dx = (canvas.width - dispWidth)/2; 173 | dy = (canvas.height - dispHeight)/2; 174 | var ctx = canvas.getContext("2d"); 175 | ctx.drawImage(img,0,0,img.width,img.height,dx,dy,dispWidth,dispHeight); 176 | } 177 | // loaded from our server 178 | function urlImgLoaded() { 179 | var img = this; 180 | showImageOnCanvas(img); 181 | showResult(img.det_result); 182 | } 183 | function showResult(jsonObj) { 184 | var subregions = document.getElementById('rightpic'); 185 | subregions.setAttribute("class", "rightpic"); 186 | var html = ""; 232 | // subregions.innerHTML = html; 233 | } 234 | 235 | function uploadUrlAndShowResult(url) { 236 | // clear display 237 | var picshow = document.querySelector("#dropz"); 238 | picshow.setAttribute('plus_sign', 'none'); 239 | $(".dz-message").html(null); 240 | // load image 241 | var img = new Image(); 242 | //img.setAttribute("crossOrigin",'Anonymous'); 243 | //img.src = url + '?' + new Date().getTime();//受限于 CORS 策略,会存在跨域问题,虽然可以绘制,但是会污染画布,一旦一个画布被污染,就无法提取画布的数据,比如无法使用使用画布toBlob(),toDataURL(),或getImageData()方法;会抛出一个安全错误 244 | // 另外,add a timestamp to the image URL to avoid the storage server from responding with 304 without the Access-Control-Allow-Origin header.但是对于严格的服务器没有仍然不管用 245 | //img.onload = imageLoaded; 246 | var formData = new FormData(); 247 | formData.append("image_url", url); 248 | var thresholdValue = $("#thresholdValue").text(); 249 | formData.append('threshold', thresholdValue); 250 | 251 | $.ajax({ 252 | type: "POST", 253 | timeout: 600000, 254 | url: "/upload-det-rsi", 255 | data: formData, 256 | processData: false, 257 | contentType: false, 258 | error: function (resp) { 259 | $("#status").text("Unable to perform detection."); 260 | }, 261 | success: function (resp) { 262 | img.src = resp["image_base64"]; 263 | img.det_result = resp; 264 | img.onload = urlImgLoaded; 265 | } 266 | }); 267 | 268 | } 269 | 270 | //剩余时间格式转换: 271 | function arrive_timer_format(s) { 272 | var t; 273 | if(s > -1){ 274 | var hour = Math.floor(s/3600); 275 | var min = Math.floor(s/60) % 60; 276 | var sec = s % 60; 277 | var day = parseInt(hour / 24); 278 | if (day > 0) { 279 | hour = hour - 24 * day; 280 | t = day + "day " + hour + ":"; 281 | } 282 | else t = hour + ":"; 283 | if(min < 10){t += "0";} 284 | t += min + ":"; 285 | if(sec < 10){t += "0";} 286 | t += sec; 287 | } 288 | return t; 289 | } 290 | 291 | function uploadAndShowResult() { 292 | var subregions = document.getElementById('subregions'); 293 | 294 | fileChangesInput(fileInput); // Reset 295 | 296 | $(subregions).empty() 297 | 298 | thresholdValue = $("#thresholdValue").text(); 299 | var formData = new FormData(); 300 | formData.append("image", document.getElementById("inputImage").files[0]); 301 | $.ajax({ 302 | type: "POST", 303 | timeout: 50000, 304 | url: "http://IP/or/host/detectObject?threshold=" + thresholdValue, 305 | data: formData, 306 | processData: false, 307 | contentType: false, 308 | error: function (resp) { 309 | $("#status").text("Unable to perform detection."); 310 | }, 311 | success: function (resp) { 312 | console.log(resp); 313 | // fileChangesInput(fileInput); 314 | 315 | if(resp.length == 0){ 316 | $("#status").text("No objects found."); 317 | } else { 318 | if(resp.length == 1){ 319 | $("#status").text("1 object found."); 320 | } else { 321 | $("#status").text(resp.length + " objects found."); 322 | } 323 | } 324 | 325 | $(subregions).empty() 326 | 327 | for (x = 0; x < resp.length; x++) { 328 | var bbox = resp[x]["bbox"]; 329 | var cur_class = resp[x]["class"]; 330 | var b0 = bbox[0]; 331 | var b1 = bbox[1]; 332 | var b2 = bbox[2]; 333 | var b3 = bbox[3]; 334 | var img_x = b0; 335 | var img_y = b1; 336 | var img_w = b2 - b0; 337 | var img_h = b3 - b1; 338 | var canvas = document.getElementById('canvas'); 339 | var ctx = canvas.getContext("2d"); 340 | var subimage = copyCanvasRegionToBuffer(canvas, img_x,img_y,img_w,img_h); 341 | 342 | subregions.appendChild(subimage); 343 | } 344 | 345 | for (x = 0; x < resp.length; x++) { 346 | var bbox = resp[x]["bbox"]; 347 | var cur_class = resp[x]["class"]; 348 | var b0 = bbox[0]; 349 | var b1 = bbox[1]; 350 | var b2 = bbox[2]; 351 | var b3 = bbox[3]; 352 | var img_x = b0; 353 | var img_y = b1; 354 | var img_w = b2 - b0; 355 | var img_h = b3 - b1; 356 | var canvas = document.getElementById('canvas'); 357 | var ctx = canvas.getContext("2d"); 358 | var pxsz = Math.floor(0.03 * Math.max(canvas.width, canvas.height)) 359 | 360 | ctx.strokeStyle="#FF0000"; 361 | ctx.lineWidth=5; 362 | ctx.strokeRect(img_x,img_y,img_w,img_h); 363 | 364 | ctx.font = "" + pxsz + "px verdana"; 365 | console.log(ctx.font); 366 | ctx.fillStyle = 'red'; 367 | ctx.fillText("Class:" + cur_class, img_x, img_y - 15); 368 | } 369 | } 370 | }); 371 | 372 | $("#status").text("Waiting for response..."); 373 | } 374 | // Thanks SO: http://stackoverflow.com/questions/4532166/how-to-capture-a-section-of-a-canvas-to-a-bitmap 375 | function copyCanvasRegionToBuffer( canvas, x, y, w, h ){ 376 | var bufferCanvas = document.createElement('canvas'); 377 | bufferCanvas.width = w; 378 | bufferCanvas.height = h; 379 | bufferCanvas.getContext('2d').drawImage( canvas, x, y, w, h, 0, 0, w, h ); 380 | return bufferCanvas; 381 | } 382 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | F22 Det 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 31 | 32 | 46 | 47 | 48 | 49 | 50 |
51 | 60 | 61 |
62 |

检 测

63 | 64 | 65 | 66 | 点击这里快速示例 67 | 68 |
69 | 70 | {% if has_result %} 71 | {% if not result[0] %} 72 | 73 |
{{ result[1] }}
74 |
75 | 76 |
77 |
78 | 82 |
83 | 84 |
85 |
86 | {% else %} 87 |
88 | 89 |
90 |
91 | 95 |
96 |
97 |
    98 | {% for single_pred in result[2] %} 99 |
  • 100 | {{ single_pred[1] }} 101 |

    102 | 103 | {{ single_pred[0] }} 104 |

    105 |
  • 106 | {% endfor %} 107 |
108 |
109 | 123 |
124 |
125 |

耗时 {{ result[3] }} 秒.

126 |
127 |
128 | 129 | {% endif %} 130 | 131 | {% endif %} 132 |
133 |
134 |
135 |
136 | 137 | 138 | 139 | 140 |
141 |
142 |
143 | 144 |
145 |
146 | 147 | 148 |
149 | 150 |
151 |
152 | 153 |
154 | 159 | 160 | 161 | --------------------------------------------------------------------------------