diff --git a/AUTHORS b/AUTHORS index 0e73ea66..28e15c8b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,5 +6,6 @@ Main Developers Contributors ============ +* Abdulhadi Mohamed @AbdulTheProgrammer * Dimitri Racordon @kyouko-taiga * Guillaume Martres @smarter diff --git a/README.rst b/README.rst index 5a01febf..e4f8b0ac 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ ========= -pytoolbox +Pytoolbox ========= .. image:: https://badge.fury.io/py/pytoolbox.png diff --git a/pytoolbox/ai/vision/face/detect/dlib.py b/pytoolbox/ai/vision/face/detect/dlib.py index 4543152f..93aef7d5 100644 --- a/pytoolbox/ai/vision/face/detect/dlib.py +++ b/pytoolbox/ai/vision/face/detect/dlib.py @@ -16,10 +16,12 @@ # Original: https://github.com/krasserm/face-recognition/blob/master/align.py -import bz2, os, tempfile +import bz2, tempfile import cv2, dlib, numpy as np +from ... import utils + __all__ = ['DlibFaceDetector'] TEMPLATE = np.float32([ @@ -78,8 +80,11 @@ class DlibFaceDetector(object): """ # Downloaded from http://dlib.net/files/ - DEFAULT_PREDICTOR = os.path.join( - os.path.dirname(__file__), 'data', 'shape_predictor_68_face_landmarks.dat.bz2') + DEFAULT_PREDICTOR = ( + 'https://s3-eu-west-1.amazonaws.com/pytoolbox/ai/vision/face/detect/' + 'shape_predictor_68_face_landmarks.dat.bz2') + + PREDICTORS_DOWNLOAD_DIRECTORY = tempfile.gettempdir() #: Landmark indices. INNER_EYES_AND_BOTTOM_LIP = [39, 42, 57] @@ -92,7 +97,9 @@ def __init__(self, predictor=None): :param predictor: The path to dlib's landmarks file. :type predictor: str """ - predictor = predictor or self.DEFAULT_PREDICTOR + if predictor is None: + predictor = self.DEFAULT_PREDICTOR + predictor = utils.load_to_file(predictor) if predictor.endswith('.bz2'): decompressor = bz2.BZ2Decompressor() with open(predictor, 'rb') as f, tempfile.NamedTemporaryFile('wb') as g: @@ -102,7 +109,7 @@ def __init__(self, predictor=None): self.predictor = dlib.shape_predictor(predictor) self.detector = dlib.get_frontal_face_detector() - def align(self, image, box, dimension=96, landmark_indices=OUTER_EYES_AND_NOSE, landmarks=None): + def align(self, image, box, dimension=96, landmark_indices=None, landmarks=None): """ Transform and align a face in an image. @@ -120,6 +127,8 @@ def align(self, image, box, dimension=96, landmark_indices=OUTER_EYES_AND_NOSE, :return: The aligned RGB image. Shape: (dimension, dimension, 3) :rtype: numpy.ndarray """ + if landmark_indices is None: + landmark_indices = self.OUTER_EYES_AND_NOSE if landmarks is None: landmarks = self.find_landmarks(image, box) @@ -132,12 +141,11 @@ def align(self, image, box, dimension=96, landmark_indices=OUTER_EYES_AND_NOSE, return cv2.warpAffine(image, H, (dimension, dimension)) - def extract_all_faces(self, image, dimension=96, landmark_indices=OUTER_EYES_AND_NOSE): + def extract_all_faces(self, image, dimension=96, landmark_indices=None): for box in self.get_all_faces_bounding_boxes(image): yield box, self.align(image, box, dimension, landmark_indices) - def extract_largest_face(self, image, dimension=96, landmark_indices=OUTER_EYES_AND_NOSE, - skip_multi=False): + def extract_largest_face(self, image, dimension=96, landmark_indices=None, skip_multi=False): box = self.get_largest_face_bounding_box(image, skip_multi=skip_multi) return box, (self.align(image, box, dimension, landmark_indices) if box else None) diff --git a/pytoolbox/ai/vision/face/recognize/nn4_small2.py b/pytoolbox/ai/vision/face/recognize/nn4_small2.py index e853e1d5..3824f1ad 100644 --- a/pytoolbox/ai/vision/face/recognize/nn4_small2.py +++ b/pytoolbox/ai/vision/face/recognize/nn4_small2.py @@ -2,8 +2,6 @@ # Code taken from https://github.com/iwantooxxoox/Keras-OpenFace (with minor modifications) # -------------------------------------------------------------------------------------------------- -import os - import tensorflow as tf from keras import backend as K from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate @@ -12,7 +10,11 @@ from keras.layers.pooling import MaxPooling2D, AveragePooling2D from keras.models import Model -WEIGHTS_FILENAME = os.path.join(os.path.dirname(__file__), 'data', 'nn4.small2.v1.h5') +from ... import utils + +DEFAULT_WEIGHTS = ( + 'https://s3-eu-west-1.amazonaws.com/pytoolbox/ai/vision/face/recognize/' + 'nn4.small2.v1.h5') E = 0.00001 # Epsilon @@ -324,8 +326,8 @@ def create_model(): return Model(inputs=[inputs], outputs=outputs) -def load_model(weights_filename=WEIGHTS_FILENAME): +def load_model(weights=DEFAULT_WEIGHTS): model = create_model() - if weights_filename: - model.load_weights(weights_filename) + if weights: + model.load_weights(utils.load_to_file(weights)) return model diff --git a/pytoolbox/ai/vision/utils.py b/pytoolbox/ai/vision/utils.py index 5de537d9..86a93a3d 100644 --- a/pytoolbox/ai/vision/utils.py +++ b/pytoolbox/ai/vision/utils.py @@ -1,11 +1,23 @@ +import os, tempfile + import cv2, numpy as np +from pytoolbox.network.http import download_ext + def load_image(path): """Reverse channels because OpenCV loads images in BGR mode.""" return cv2.imread(path, 1)[..., ::-1] +def load_to_file(uri): + if uri.startswith('http'): + path = os.path.join(tempfile.gettempdir(), os.path.basename(uri)) + download_ext(uri, path, force=False) + return path + return uri + + def normalize_rgb(image): """Scale integer RGB values [0,255] to float32 [0.0,1.0].""" return (image / 255).astype(np.float32) diff --git a/setup.py b/setup.py index 64aad2f0..957dacac 100755 --- a/setup.py +++ b/setup.py @@ -2,10 +2,10 @@ # -*- encoding: utf-8 -*- # ************************************************************************************************** -# PYTOOLBOX - TOOLBOX FOR PYTHON SCRIPTS +# PYTOOLBOX - TOOLBOX FOR PYTHON SCRIPTS # # Main Developer : David Fischer (david.fischer.ch@gmail.com) -# Copyright : Copyright (c) 2012-2015 David Fischer. All rights reserved. +# Copyright : Copyright (c) 2012-2018 David Fischer. All rights reserved. # # ************************************************************************************************** # @@ -193,6 +193,7 @@ def run(self): # https://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', + 'Framework :: Django', 'Framework :: Flask', 'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)', 'Natural Language :: English', diff --git a/tests/pytoolbox_runtests.py b/tests/pytoolbox_runtests.py index 81e5fd1e..5fe7f269 100755 --- a/tests/pytoolbox_runtests.py +++ b/tests/pytoolbox_runtests.py @@ -45,5 +45,6 @@ def main(): print('Run the tests with nose') return runtests(__file__, cover_packages=['pytoolbox'], packages=['pytoolbox', 'tests']) + if __name__ == '__main__': main()