From 9b6c843131fd89b35f8de20dab3ba7554ffbc6a2 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Mon, 13 Apr 2020 18:42:35 +0100 Subject: [PATCH 01/12] TF 2.O porting: ImageNet ImageClassication, Custom Training and Inference --- .vscode/settings.json | 3 + imageai/Classification/Custom/__init__.py | 661 +++++++++++ imageai/Classification/__init__.py | 300 +++++ imageai/Prediction/Custom/__init__.py | 1225 +-------------------- imageai/Prediction/__init__.py | 649 +---------- 5 files changed, 983 insertions(+), 1855 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 imageai/Classification/Custom/__init__.py create mode 100644 imageai/Classification/__init__.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3a268214 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "C:\\Users\\John Olafenwa\\AppData\\Local\\Programs\\Python\\Python36\\python.exe" +} \ No newline at end of file diff --git a/imageai/Classification/Custom/__init__.py b/imageai/Classification/Custom/__init__.py new file mode 100644 index 00000000..bb1038fe --- /dev/null +++ b/imageai/Classification/Custom/__init__.py @@ -0,0 +1,661 @@ +import tensorflow as tf +from PIL import Image +import time +import numpy as np +import os +import warnings +from matplotlib.cbook import deprecated +import json + +class ClassificationModelTrainer: + """ + This is the Classification Model training class, that allows you to define a deep learning network + from the 4 available networks types supported by ImageAI which are MobileNetv2, ResNet50, + InceptionV3 and DenseNet121. Once you instantiate this class, you must call: + + * + """ + + def __init__(self): + self.__modelType = "" + self.__use_pretrained_model = False + self.__data_dir = "" + self.__train_dir = "" + self.__test_dir = "" + self.__logs_dir = "" + self.__num_epochs = 10 + self.__trained_model_dir = "" + self.__model_class_dir = "" + self.__initial_learning_rate = 1e-3 + self.__model_collection = [] + + + + + def setModelTypeAsMobileNetV2(self): + """ + 'setModelTypeAsMobileNetV2()' is used to set the model type to the MobileNetV2 model + for the training instance object . + :return: + """ + self.__modelType = "mobilenetv2" + + def setModelTypeAsResNet50(self): + """ + 'setModelTypeAsResNet()' is used to set the model type to the ResNet model + for the training instance object . + :return: + """ + self.__modelType = "resnet50" + + def setModelTypeAsDenseNet121(self): + """ + 'setModelTypeAsDenseNet()' is used to set the model type to the DenseNet model + for the training instance object . + :return: + """ + self.__modelType = "densenet121" + + def setModelTypeAsInceptionV3(self): + """ + 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model + for the training instance object . + :return: + """ + self.__modelType = "inceptionv3" + + def setDataDirectory(self, data_directory="", train_subdirectory="train", test_subdirectory="test", + models_subdirectory="models", json_subdirectory="json"): + """ + 'setDataDirectory()' + + - data_directory , is required to set the path to which the data/dataset to be used for + training is kept. The directory can have any name, but it must have 'train' and 'test' + sub-directory. In the 'train' and 'test' sub-directories, there must be sub-directories + with each having it's name corresponds to the name/label of the object whose images are + to be kept. The structure of the 'test' and 'train' folder must be as follows: + + >> train >> class1 >> class1_train_images + >> class2 >> class2_train_images + >> class3 >> class3_train_images + >> class4 >> class4_train_images + >> class5 >> class5_train_images + + >> test >> class1 >> class1_test_images + >> class2 >> class2_test_images + >> class3 >> class3_test_images + >> class4 >> class4_test_images + >> class5 >> class5_test_images + + - train_subdirectory (optional), subdirectory within 'data_directory' where the training set is. Defaults to 'train'. + - test_subdirectory (optional), subdirectory within 'data_directory' where the testing set is. Defaults to 'test'. + - models_subdirectory (optional), subdirectory within 'data_directory' where the output models will be saved. Defaults to 'models'. + - json_subdirectory (optional), subdirectory within 'data_directory' where the model classes json file will be saved. Defaults to 'json'. + + :param data_directory: + :param train_subdirectory: + :param test_subdirectory: + :param models_subdirectory: + :param json_subdirectory: + :return: + """ + + self.__data_dir = data_directory + + self.__train_dir = os.path.join(self.__data_dir, train_subdirectory) + self.__test_dir = os.path.join(self.__data_dir, test_subdirectory) + self.__trained_model_dir = os.path.join(self.__data_dir, models_subdirectory) + self.__model_class_dir = os.path.join(self.__data_dir, json_subdirectory) + self.__logs_dir = os.path.join(self.__data_dir, "logs") + + def lr_schedule(self, epoch): + + # Learning Rate Schedule + + + lr = self.__initial_learning_rate + total_epochs = self.__num_epochs + + check_1 = int(total_epochs * 0.9) + check_2 = int(total_epochs * 0.8) + check_3 = int(total_epochs * 0.6) + check_4 = int(total_epochs * 0.4) + + if epoch > check_1: + lr *= 1e-4 + elif epoch > check_2: + lr *= 1e-3 + elif epoch > check_3: + lr *= 1e-2 + elif epoch > check_4: + lr *= 1e-1 + + + return lr + + + + + def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch_size = 32, initial_learning_rate=1e-3, show_network_summary=False, training_image_size = 224, continue_from_model=None, transfer_from_model=None, transfer_with_full_training=True, initial_num_objects = None, save_full_model = False): + + """ + 'trainModel()' function starts the model actual training. It accepts the following values: + - num_objects , which is the number of classes present in the dataset that is to be used for training + - num_experiments , also known as epochs, it is the number of times the network will train on all the training dataset + - enhance_data (optional) , this is used to modify the dataset and create more instance of the training set to enhance the training result + - batch_size (optional) , due to memory constraints, the network trains on a batch at once, until all the training set is exhausted. The value is set to 32 by default, but can be increased or decreased depending on the meormory of the compute used for training. The batch_size is conventionally set to 16, 32, 64, 128. + - initial_learning_rate(optional) , this value is used to adjust the weights generated in the network. You rae advised to keep this value as it is if you don't have deep understanding of this concept. + - show_network_summary(optional) , this value is used to show the structure of the network should you desire to see it. It is set to False by default + - training_image_size(optional) , this value is used to define the image size on which the model will be trained. The value is 224 by default and is kept at a minimum of 100. + - continue_from_model (optional) , this is used to set the path to a model file trained on the same dataset. It is primarily for continuos training from a previously saved model. + - transfer_from_model (optional) , this is used to set the path to a model file trained on another dataset. It is primarily used to perform tramsfer learning. + - transfer_with_full_training (optional) , this is used to set the pre-trained model to be re-trained across all the layers or only at the top layers. + - initial_num_objects (required if 'transfer_from_model' is set ), this is used to set the number of objects the model used for transfer learning is trained on. If 'transfer_from_model' is set, this must be set as well. + - save_full_model ( optional ), this is used to save the trained models with their network types. Any model saved by this specification can be loaded without specifying the network type. + + * + + :param num_objects: + :param num_experiments: + :param enhance_data: + :param batch_size: + :param initial_learning_rate: + :param show_network_summary: + :param training_image_size: + :param continue_from_model: + :param transfer_from_model: + :param initial_num_objects: + :param save_full_model: + :return: + """ + self.__num_epochs = num_experiments + self.__initial_learning_rate = initial_learning_rate + lr_scheduler = tf.keras.callbacks.LearningRateScheduler(self.lr_schedule) + + + if(training_image_size < 100): + warnings.warn("The specified training_image_size {} is less than 100. Hence the training_image_size will default to 100.".format(training_image_size)) + training_image_size = 100 + + + + if (self.__modelType == "mobilenetv2"): + if (continue_from_model != None): + model = tf.keras.applications.MobileNetV2(input_shape=(training_image_size, training_image_size, 3), weights=continue_from_model, classes=num_objects, + include_top=True) + if (show_network_summary == True): + print("Training using weights from a previouly model") + elif (transfer_from_model != None): + base_model = tf.keras.applications.MobileNetV2(input_shape=(training_image_size, training_image_size, 3), weights= transfer_from_model, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.model.Models(inputs=base_model.input, outputs=network) + + if (show_network_summary == True): + print("Training using weights from a pre-trained ImageNet model") + else: + base_model = tf.keras.applications.MobileNetV2(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.models.Model(inputs=base_model.input, outputs=network) + + elif (self.__modelType == "resnet50"): + if (continue_from_model != None): + model = tf.keras.applications.ResNet50(input_shape=(training_image_size, training_image_size, 3), weights=continue_from_model, classes=num_objects, + include_top=True) + if (show_network_summary == True): + print("Training using weights from a previouly model") + elif (transfer_from_model != None): + base_model = tf.keras.applications.ResNet50(input_shape=(training_image_size, training_image_size, 3), weights= transfer_from_model, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.model.Models(inputs=base_model.input, outputs=network) + + if (show_network_summary == True): + print("Training using weights from a pre-trained ImageNet model") + else: + base_model = tf.keras.applications.ResNet50(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.models.Model(inputs=base_model.input, outputs=network) + + elif (self.__modelType == "inceptionv3"): + None + + elif (self.__modelType == "densenet121"): + None + + + optimizer = tf.keras.optimizers.Adam(lr=self.__initial_learning_rate, decay=1e-4) + model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"]) + if (show_network_summary == True): + model.summary() + + model_name = 'model_ex-{epoch:03d}_acc-{accuracy:03f}.h5' + + #log_name = '{}_lr-{}_{}'.format(self.__modelType, initial_learning_rate, time.strftime("%Y-%m-%d-%H-%M-%S")) + + if not os.path.isdir(self.__trained_model_dir): + os.makedirs(self.__trained_model_dir) + + if not os.path.isdir(self.__model_class_dir): + os.makedirs(self.__model_class_dir) + + if not os.path.isdir(self.__logs_dir): + os.makedirs(self.__logs_dir) + + model_path = os.path.join(self.__trained_model_dir, model_name) + + + #logs_path = os.path.join(self.__logs_dir, log_name) + #if not os.path.isdir(logs_path): + # os.makedirs(logs_path) + + save_weights_condition = True + + if(save_full_model == True ): + save_weights_condition = False + + + checkpoint = tf.keras.callbacks.ModelCheckpoint(filepath=model_path, + monitor='accuracy', + verbose=1, + save_weights_only=save_weights_condition, + save_best_only=True, + period=1) + + + #tensorboard = tf.keras.callbacks.TensorBoard(log_dir=logs_path, + # histogram_freq=0, + # write_graph=False, + # write_images=False) + + + if (enhance_data == True): + print("Using Enhanced Data Generation") + + height_shift = 0 + width_shift = 0 + if (enhance_data == True): + height_shift = 0.1 + width_shift = 0.1 + + train_datagen = tf.keras.preprocessing.image.ImageDataGenerator( + rescale=1. / 255, + horizontal_flip=enhance_data, height_shift_range=height_shift, width_shift_range=width_shift) + + test_datagen = tf.keras.preprocessing.image.ImageDataGenerator( + rescale=1. / 255) + + train_generator = train_datagen.flow_from_directory(self.__train_dir, target_size=(training_image_size, training_image_size), + batch_size=batch_size, + class_mode="categorical") + test_generator = test_datagen.flow_from_directory(self.__test_dir, target_size=(training_image_size, training_image_size), + batch_size=batch_size, + class_mode="categorical") + + class_indices = train_generator.class_indices + class_json = {} + for eachClass in class_indices: + class_json[str(class_indices[eachClass])] = eachClass + + with open(os.path.join(self.__model_class_dir, "model_class.json"), "w+") as json_file: + json.dump(class_json, json_file, indent=4, separators=(",", " : "), + ensure_ascii=True) + json_file.close() + print("JSON Mapping for the model classes saved to ", os.path.join(self.__model_class_dir, "model_class.json")) + + num_train = len(train_generator.filenames) + num_test = len(test_generator.filenames) + print("Number of experiments (Epochs) : ", self.__num_epochs) + + + model.fit_generator(train_generator, steps_per_epoch=int(num_train / batch_size), epochs=self.__num_epochs, + validation_data=test_generator, + validation_steps=int(num_test / batch_size), callbacks=[checkpoint, lr_scheduler]) + + + + + +class CustomImageClassification: + """ + This is the image classification class for custom models trained with the 'ClassificationModelTrainer' class. It provides support for 4 different models which are: + ResNet50, MobileNetV2, DenseNet121 and Inception V3. After instantiating this class, you can set it's properties and + make image classification using it's pre-defined functions. + + The following functions are required to be called before a classification can be made + * setModelPath() , path to your custom model + * setJsonPath , , path to your custom model's corresponding JSON file + * At least of of the following and it must correspond to the model set in the setModelPath() + [setModelTypeAsMobileNetV2(), setModelTypeAsResNet50(), setModelTypeAsDenseNet121, setModelTypeAsInceptionV3] + * loadModel() [This must be called once only before making a classification] + + Once the above functions have been called, you can call the classifyImage() function of the classification instance + object at anytime to predict an image. + """ + def __init__(self): + self.__modelType = "" + self.modelPath = "" + self.jsonPath = "" + self.numObjects = 10 + self.__model_classes = dict() + self.__modelLoaded = False + self.__model_collection = [] + self.__input_image_size = 224 + + def setModelPath(self, model_path): + """ + 'setModelPath()' function is required and is used to set the file path to the model adopted from the list of the + available 4 model types. The model path must correspond to the model type set for the classification instance object. + + :param model_path: + :return: + """ + self.modelPath = model_path + + def setJsonPath(self, model_json): + """ + 'setJsonPath()' + + :param model_path: + :return: + """ + self.jsonPath = model_json + + def setModelTypeAsMobileNetV2(self): + """ + 'setModelTypeAsMobileNetV2()' is used to set the model type to the MobileNetV2 model + for the classification instance object . + :return: + """ + self.__modelType = "mobilenetv2" + + def setModelTypeAsResNet50(self): + """ + 'setModelTypeAsResNet50()' is used to set the model type to the ResNet50 model + for the classification instance object . + :return: + """ + self.__modelType = "resnet50" + + def setModelTypeAsDenseNet121(self): + """ + 'setModelTypeAsDenseNet121()' is used to set the model type to the DenseNet121 model + for the classification instance object . + :return: + """ + self.__modelType = "densenet121" + + def setModelTypeAsInceptionV3(self): + """ + 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model + for the classification instance object . + :return: + """ + self.__modelType = "inceptionv3" + + def loadModel(self, classification_speed="normal", num_objects=10): + """ + 'loadModel()' function is used to load the model structure into the program from the file path defined + in the setModelPath() function. This function receives an optional value which is "classification_speed". + The value is used to reduce the time it takes to classify an image, down to about 50% of the normal time, + with just slight changes or drop in classification accuracy, depending on the nature of the image. + * classification_speed (optional); Acceptable values are "normal", "fast", "faster" and "fastest" + + :param classification_speed : + :return: + """ + + self.__model_classes = json.load(open(self.jsonPath)) + + if(classification_speed=="normal"): + self.__input_image_size = 224 + elif(classification_speed=="fast"): + self.__input_image_size = 160 + elif(classification_speed=="faster"): + self.__input_image_size = 120 + elif (classification_speed == "fastest"): + self.__input_image_size = 100 + + if (self.__modelLoaded == False): + + image_input = tf.keras.layers.Input(shape=(self.__input_image_size, self.__input_image_size, 3)) + + if(self.__modelType == "" ): + raise ValueError("You must set a valid model type before loading the model.") + + elif(self.__modelType == "mobilenetv2"): + model = tf.keras.applications.MobileNetV2(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = num_objects ) + self.__model_collection.append(model) + self.__modelLoaded = True + try: + None + except: + raise ("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) + + elif(self.__modelType == "resnet50"): + try: + model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = num_objects ) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) + + elif (self.__modelType == "densenet121"): + try: + model = tf.keras.applications.DenseNet121(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = num_objects) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) + + elif (self.__modelType == "inceptionv3"): + try: + model = tf.keras.applications.InceptionV3(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = num_objects ) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is in {}".format(self.modelPath)) + def loadFullModel(self, classification_speed="normal", num_objects=10): + """ + 'loadFullModel()' function is used to load the model structure into the program from the file path defined + in the setModelPath() function. As opposed to the 'loadModel()' function, you don't need to specify the model type. This means you can load any Keras model trained with or without ImageAI and perform image prediction. + - prediction_speed (optional), Acceptable values are "normal", "fast", "faster" and "fastest" + - num_objects (required), the number of objects the model is trained to recognize + + :param prediction_speed: + :param num_objects: + :return: + """ + + self.numObjects = num_objects + self.__model_classes = json.load(open(self.jsonPath)) + + if (classification_speed == "normal"): + self.__input_image_size = 224 + elif (classification_speed == "fast"): + self.__input_image_size = 160 + elif (classification_speed == "faster"): + self.__input_image_size = 120 + elif (classification_speed == "fastest"): + self.__input_image_size = 100 + + if (self.__modelLoaded == False): + + model = tf.keras.models.load_model(filepath=self.modelPath) + self.__model_collection.append(model) + self.__modelLoaded = True + self.__modelType = "full" + + def getModels(self): + """ + 'getModels()' provides access to the internal model collection. Helpful if models are used down the line with tools like lime. + :return: + """ + return self.__model_collection + + + def classifyImage(self, image_input, result_count=5, input_type="file"): + """ + 'classifyImage()' function is used to classify a given image by receiving the following arguments: + * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" + * image_input , file path/numpy array/image file stream of the image. + * result_count (optional) , the number of classifications to be sent which must be whole numbers between + 1 and 1000. The default is 5. + + This function returns 2 arrays namely 'classification_results' and 'classification_probabilities'. The 'classification_results' + contains possible objects classes arranged in descending of their percentage probabilities. The 'classification_probabilities' + contains the percentage probability of each object class. The position of each object class in the 'classification_results' + array corresponds with the positions of the percentage probability in the 'classification_probabilities' array. + + + :param input_type: + :param image_input: + :param result_count: + :return classification_results, classification_probabilities: + """ + classification_results = [] + classification_probabilities = [] + if (self.__modelLoaded == False): + raise ValueError("You must call the loadModel() function before making classification.") + + else: + if (input_type == "file"): + try: + image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) + image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") + image_to_predict = np.expand_dims(image_to_predict, axis=0) + except: + raise ValueError("You have set a path to an invalid image file.") + elif (input_type == "array"): + try: + image_input = Image.fromarray(np.uint8(image_input)) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + except: + raise ValueError("You have parsed in a wrong numpy array for the image") + elif (input_type == "stream"): + try: + image_input = Image.open(image_input) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + + except: + raise ValueError("You have parsed in a wrong stream for the image") + + if (self.__modelType == "mobilenetv2"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "resnet50" or self.__modelType == "full"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "inceptionv3"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "densenet121"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + try: + model = self.__model_collection[0] + prediction = model.predict(image_to_predict, steps=1) + + predictiondata = [] + for pred in prediction: + top_indices = pred.argsort()[-result_count:][::-1] + for i in top_indices: + each_result = [] + each_result.append(self.__model_classes[str(i)]) + each_result.append(pred[i]) + predictiondata.append(each_result) + + for result in predictiondata: + classification_results.append(str(result[0])) + classification_probabilities.append(result[1] * 100) + + except: + raise ValueError("Error. Ensure your input image is valid") + + return classification_results, classification_probabilities + + + @deprecated(since="2.1.6", message="'.predictImage()' has been deprecated! Please use 'classifyImage()' instead.") + def predictImage(self, image_input, result_count=5, input_type="file"): + classification_results = [] + classification_probabilities = [] + if (self.__modelLoaded == False): + raise ValueError("You must call the loadModel() function before making classification.") + + else: + if (input_type == "file"): + try: + image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) + image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") + image_to_predict = np.expand_dims(image_to_predict, axis=0) + except: + raise ValueError("You have set a path to an invalid image file.") + elif (input_type == "array"): + try: + image_input = Image.fromarray(np.uint8(image_input)) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + except: + raise ValueError("You have parsed in a wrong numpy array for the image") + elif (input_type == "stream"): + try: + image_input = Image.open(image_input) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + + except: + raise ValueError("You have parsed in a wrong stream for the image") + + if (self.__modelType == "mobilenetv2"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "resnet50"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "inceptionv3"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "densenet121"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + try: + model = self.__model_collection[0] + prediction = model.predict(image_to_predict, steps=1) + + predictiondata = [] + for pred in prediction: + top_indices = pred.argsort()[-result_count:][::-1] + for i in top_indices: + each_result = [] + each_result.append(self.__model_classes[str(i)]) + each_result.append(pred[i]) + predictiondata.append(each_result) + + for result in predictiondata: + classification_results.append(str(result[0])) + classification_probabilities.append(result[1] * 100) + + except: + raise ValueError("Error. Ensure your input image is valid") + + return classification_results, classification_probabilities \ No newline at end of file diff --git a/imageai/Classification/__init__.py b/imageai/Classification/__init__.py new file mode 100644 index 00000000..b338ed8b --- /dev/null +++ b/imageai/Classification/__init__.py @@ -0,0 +1,300 @@ +import tensorflow as tf +from PIL import Image +import numpy as np +from matplotlib.cbook import deprecated + + +class ImageClassification: + """ + This is the image classification class in the ImageAI library. It provides support for 4 different models which are: + ResNet, MobileNetV2, DenseNet and Inception V3. After instantiating this class, you can set it's properties and + make image classification using it's pre-defined functions. + + The following functions are required to be called before a classification can be made + * setModelPath() + * At least of of the following and it must correspond to the model set in the setModelPath() + [setModelTypeAsMobileNetv2(), setModelTypeAsResNet(), setModelTypeAsDenseNet, setModelTypeAsInceptionV3] + * loadModel() [This must be called once only before making a classification] + + Once the above functions have been called, you can call the classifyImage() function of the classification instance + object at anytime to classify an image. + """ + def __init__(self): + self.__modelType = "" + self.modelPath = "" + self.__modelLoaded = False + self.__model_collection = [] + self.__input_image_size = 224 + + def setModelPath(self, model_path): + """ + 'setModelPath()' function is required and is used to set the file path to the model adopted from the list of the + available 4 model types. The model path must correspond to the model type set for the classification instance object. + + :param model_path: + :return: + """ + self.modelPath = model_path + def setModelTypeAsMobileNetV2(self): + """ + 'setModelTypeAsMobileNetV2()' is used to set the model type to the MobileNetV2 model + for the classification instance object . + :return: + """ + self.__modelType = "mobilenetv2" + + def setModelTypeAsResNet50(self): + """ + 'setModelTypeAsResNet50()' is used to set the model type to the ResNet50 model + for the classification instance object . + :return: + """ + self.__modelType = "resnet50" + + def setModelTypeAsDenseNet121(self): + """ + 'setModelTypeAsDenseNet121()' is used to set the model type to the DenseNet121 model + for the classification instance object . + :return: + """ + self.__modelType = "densenet121" + + def setModelTypeAsInceptionV3(self): + """ + 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model + for the classification instance object . + :return: + """ + self.__modelType = "inceptionv3" + + def loadModel(self, classification_speed="normal"): + """ + 'loadModel()' function is used to load the model structure into the program from the file path defined + in the setModelPath() function. This function receives an optional value which is "classification_speed". + The value is used to reduce the time it takes to classify an image, down to about 50% of the normal time, + with just slight changes or drop in classification accuracy, depending on the nature of the image. + * classification_speed (optional); Acceptable values are "normal", "fast", "faster" and "fastest" + + :param classification_speed : + :return: + """ + + if(classification_speed=="normal"): + self.__input_image_size = 224 + elif(classification_speed=="fast"): + self.__input_image_size = 160 + elif(classification_speed=="faster"): + self.__input_image_size = 120 + elif (classification_speed == "fastest"): + self.__input_image_size = 100 + + if (self.__modelLoaded == False): + + image_input = tf.keras.layers.Input(shape=(self.__input_image_size, self.__input_image_size, 3)) + + if(self.__modelType == "" ): + raise ValueError("You must set a valid model type before loading the model.") + + elif(self.__modelType == "mobilenetv2"): + model = tf.keras.applications.MobileNetV2(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + self.__model_collection.append(model) + self.__modelLoaded = True + try: + None + except: + raise ("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) + + elif(self.__modelType == "resnet50"): + try: + model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) + + elif (self.__modelType == "densenet121"): + try: + model = tf.keras.applications.DenseNet121(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) + + elif (self.__modelType == "inceptionv3"): + try: + model = tf.keras.applications.InceptionV3(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + self.__model_collection.append(model) + self.__modelLoaded = True + except: + raise ("An error occured. Ensure your model file is in {}".format(self.modelPath)) + + + def classifyImage(self, image_input, result_count=5, input_type="file"): + """ + 'classifyImage()' function is used to classify a given image by receiving the following arguments: + * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" + * image_input , file path/numpy array/image file stream of the image. + * result_count (optional) , the number of classifications to be sent which must be whole numbers between + 1 and 1000. The default is 5. + + This function returns 2 arrays namely 'classification_results' and 'classification_probabilities'. The 'classification_results' + contains possible objects classes arranged in descending of their percentage probabilities. The 'classification_probabilities' + contains the percentage probability of each object class. The position of each object class in the 'classification_results' + array corresponds with the positions of the percentage probability in the 'classification_probabilities' array. + + + :param input_type: + :param image_input: + :param result_count: + :return classification_results, classification_probabilities: + """ + classification_results = [] + classification_probabilities = [] + if (self.__modelLoaded == False): + raise ValueError("You must call the loadModel() function before making classification.") + + else: + if (input_type == "file"): + try: + image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) + image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") + image_to_predict = np.expand_dims(image_to_predict, axis=0) + except: + raise ValueError("You have set a path to an invalid image file.") + elif (input_type == "array"): + try: + image_input = Image.fromarray(np.uint8(image_input)) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + except: + raise ValueError("You have parsed in a wrong numpy array for the image") + elif (input_type == "stream"): + try: + image_input = Image.open(image_input) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + + except: + raise ValueError("You have parsed in a wrong stream for the image") + + if (self.__modelType == "mobilenetv2"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "resnet50"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "inceptionv3"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "densenet121"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + try: + model = self.__model_collection[0] + prediction = model.predict(image_to_predict, steps=1) + + if (self.__modelType == "mobilenetv2"): + predictiondata = tf.keras.applications.mobilenet_v2.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "resnet50"): + predictiondata = tf.keras.applications.resnet50.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "inceptionv3"): + predictiondata = tf.keras.applications.inception_v3.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "densenet121"): + predictiondata = tf.keras.applications.densenet.decode_predictions(prediction, top=int(result_count)) + + + + for results in predictiondata: + for result in results: + classification_results.append(str(result[1])) + classification_probabilities.append(result[2] * 100) + except: + raise ValueError("An error occured! Try again.") + + return classification_results, classification_probabilities + + + @deprecated(since="2.1.6", message="'.predictImage()' has been deprecated! Please use 'classifyImage()' instead.") + def predictImage(self, image_input, result_count=5, input_type="file"): + """ + 'classifyImage()' function is used to classify a given image by receiving the following arguments: + * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" + * image_input , file path/numpy array/image file stream of the image. + * result_count (optional) , the number of classifications to be sent which must be whole numbers between + 1 and 1000. The default is 5. + + This function returns 2 arrays namely 'classification_results' and 'classification_probabilities'. The 'classification_results' + contains possible objects classes arranged in descending of their percentage probabilities. The 'classification_probabilities' + contains the percentage probability of each object class. The position of each object class in the 'classification_results' + array corresponds with the positions of the percentage probability in the 'classification_probabilities' array. + + + :param input_type: + :param image_input: + :param result_count: + :return classification_results, classification_probabilities: + """ + classification_results = [] + classification_probabilities = [] + if (self.__modelLoaded == False): + raise ValueError("You must call the loadModel() function before making classification.") + + else: + if (input_type == "file"): + try: + image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) + image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") + image_to_predict = np.expand_dims(image_to_predict, axis=0) + except: + raise ValueError("You have set a path to an invalid image file.") + elif (input_type == "array"): + try: + image_input = Image.fromarray(np.uint8(image_input)) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + except: + raise ValueError("You have parsed in a wrong numpy array for the image") + elif (input_type == "stream"): + try: + image_input = Image.open(image_input) + image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) + image_input = np.expand_dims(image_input, axis=0) + image_to_predict = image_input.copy() + image_to_predict = np.asarray(image_to_predict, dtype=np.float64) + + except: + raise ValueError("You have parsed in a wrong stream for the image") + + if (self.__modelType == "mobilenetv2"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "resnet50"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "inceptionv3"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + elif (self.__modelType == "densenet121"): + image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + try: + model = self.__model_collection[0] + prediction = model.predict(image_to_predict, steps=1) + + if (self.__modelType == "mobilenetv2"): + predictiondata = tf.keras.applications.mobilenet_v2.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "resnet50"): + predictiondata = tf.keras.applications.resnet50.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "inceptionv3"): + predictiondata = tf.keras.applications.inception_v3.decode_predictions(prediction, top=int(result_count)) + elif (self.__modelType == "densenet121"): + predictiondata = tf.keras.applications.densenet.decode_predictions(prediction, top=int(result_count)) + + + + for results in predictiondata: + for result in results: + classification_results.append(str(result[1])) + classification_probabilities.append(result[2] * 100) + except: + raise ValueError("An error occured! Try again.") + + return classification_results, classification_probabilities \ No newline at end of file diff --git a/imageai/Prediction/Custom/__init__.py b/imageai/Prediction/Custom/__init__.py index 52e9131a..49f6ef76 100644 --- a/imageai/Prediction/Custom/__init__.py +++ b/imageai/Prediction/Custom/__init__.py @@ -1,1223 +1,22 @@ -from ..SqueezeNet.squeezenet import SqueezeNet -from ..ResNet.resnet50 import ResNet50 -from ..InceptionV3.inceptionv3 import InceptionV3 -from ..DenseNet.densenet import DenseNetImageNet121 -from tensorflow.python.keras.optimizers import Adam -from tensorflow.python.keras.preprocessing.image import ImageDataGenerator -from tensorflow.python.keras.callbacks import LearningRateScheduler -from tensorflow.python.keras.layers import Flatten, Dense, Input, Conv2D, GlobalAvgPool2D, Activation -from tensorflow.python.keras.models import Model -from tensorflow.python.keras.preprocessing import image -from tensorflow.python.keras.models import load_model, save_model -import tensorflow as tf -from tensorflow.python.keras import backend as K -from PIL import Image -import os -import time -from tensorflow.python.keras.callbacks import ModelCheckpoint, TensorBoard -from io import open -import json -import numpy as np -import warnings +from ...Classification.Custom import ClassificationModelTrainer, CustomImageClassification -class ModelTraining: +class ModelTraining(ClassificationModelTrainer): """ - This is the Model training class, that allows you to define a deep learning network - from the 4 available networks types supported by ImageAI which are SqueezeNet, ResNet50, - InceptionV3 and DenseNet121. Once you instantiate this class, you must call: - - * + Deprecated! + Replaced with 'imageai.Classification.Custom.ClassificationModelTrainer' """ + def __call__(self): + None - def __init__(self): - self.__modelType = "" - self.__use_pretrained_model = False - self.__data_dir = "" - self.__train_dir = "" - self.__test_dir = "" - self.__logs_dir = "" - self.__num_epochs = 10 - self.__trained_model_dir = "" - self.__model_class_dir = "" - self.__initial_learning_rate = 1e-3 - self.__model_collection = [] - - - - - def setModelTypeAsSqueezeNet(self): - """ - 'setModelTypeAsSqueezeNet()' is used to set the model type to the SqueezeNet model - for the training instance object . - :return: - """ - self.__modelType = "squeezenet" - - def setModelTypeAsResNet(self): - """ - 'setModelTypeAsResNet()' is used to set the model type to the ResNet model - for the training instance object . - :return: - """ - self.__modelType = "resnet" - - def setModelTypeAsDenseNet(self): - """ - 'setModelTypeAsDenseNet()' is used to set the model type to the DenseNet model - for the training instance object . - :return: - """ - self.__modelType = "densenet" - - def setModelTypeAsInceptionV3(self): - """ - 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model - for the training instance object . - :return: - """ - self.__modelType = "inceptionv3" - - def setDataDirectory(self, data_directory="", train_subdirectory="train", test_subdirectory="test", - models_subdirectory="models", json_subdirectory="json"): - """ - 'setDataDirectory()' - - - data_directory , is required to set the path to which the data/dataset to be used for - training is kept. The directory can have any name, but it must have 'train' and 'test' - sub-directory. In the 'train' and 'test' sub-directories, there must be sub-directories - with each having it's name corresponds to the name/label of the object whose images are - to be kept. The structure of the 'test' and 'train' folder must be as follows: - - >> train >> class1 >> class1_train_images - >> class2 >> class2_train_images - >> class3 >> class3_train_images - >> class4 >> class4_train_images - >> class5 >> class5_train_images - - >> test >> class1 >> class1_test_images - >> class2 >> class2_test_images - >> class3 >> class3_test_images - >> class4 >> class4_test_images - >> class5 >> class5_test_images - - - train_subdirectory (optional), subdirectory within 'data_directory' where the training set is. Defaults to 'train'. - - test_subdirectory (optional), subdirectory within 'data_directory' where the testing set is. Defaults to 'test'. - - models_subdirectory (optional), subdirectory within 'data_directory' where the output models will be saved. Defaults to 'models'. - - json_subdirectory (optional), subdirectory within 'data_directory' where the model classes json file will be saved. Defaults to 'json'. - - :param data_directory: - :param train_subdirectory: - :param test_subdirectory: - :param models_subdirectory: - :param json_subdirectory: - :return: - """ - - self.__data_dir = data_directory - - self.__train_dir = os.path.join(self.__data_dir, train_subdirectory) - self.__test_dir = os.path.join(self.__data_dir, test_subdirectory) - self.__trained_model_dir = os.path.join(self.__data_dir, models_subdirectory) - self.__model_class_dir = os.path.join(self.__data_dir, json_subdirectory) - self.__logs_dir = os.path.join(self.__data_dir, "logs") - - def lr_schedule(self, epoch): - - # Learning Rate Schedule - - - lr = self.__initial_learning_rate - total_epochs = self.__num_epochs - - check_1 = int(total_epochs * 0.9) - check_2 = int(total_epochs * 0.8) - check_3 = int(total_epochs * 0.6) - check_4 = int(total_epochs * 0.4) - - if epoch > check_1: - lr *= 1e-4 - elif epoch > check_2: - lr *= 1e-3 - elif epoch > check_3: - lr *= 1e-2 - elif epoch > check_4: - lr *= 1e-1 - - - return lr - - - - - def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch_size = 32, initial_learning_rate=1e-3, show_network_summary=False, training_image_size = 224, continue_from_model=None, transfer_from_model=None, transfer_with_full_training=True, initial_num_objects = None, save_full_model = False): - - """ - 'trainModel()' function starts the model actual training. It accepts the following values: - - num_objects , which is the number of classes present in the dataset that is to be used for training - - num_experiments , also known as epochs, it is the number of times the network will train on all the training dataset - - enhance_data (optional) , this is used to modify the dataset and create more instance of the training set to enhance the training result - - batch_size (optional) , due to memory constraints, the network trains on a batch at once, until all the training set is exhausted. The value is set to 32 by default, but can be increased or decreased depending on the meormory of the compute used for training. The batch_size is conventionally set to 16, 32, 64, 128. - - initial_learning_rate(optional) , this value is used to adjust the weights generated in the network. You rae advised to keep this value as it is if you don't have deep understanding of this concept. - - show_network_summary(optional) , this value is used to show the structure of the network should you desire to see it. It is set to False by default - - training_image_size(optional) , this value is used to define the image size on which the model will be trained. The value is 224 by default and is kept at a minimum of 100. - - continue_from_model (optional) , this is used to set the path to a model file trained on the same dataset. It is primarily for continuos training from a previously saved model. - - transfer_from_model (optional) , this is used to set the path to a model file trained on another dataset. It is primarily used to perform tramsfer learning. - - transfer_with_full_training (optional) , this is used to set the pre-trained model to be re-trained across all the layers or only at the top layers. - - initial_num_objects (required if 'transfer_from_model' is set ), this is used to set the number of objects the model used for transfer learning is trained on. If 'transfer_from_model' is set, this must be set as well. - - save_full_model ( optional ), this is used to save the trained models with their network types. Any model saved by this specification can be loaded without specifying the network type. - - * - - :param num_objects: - :param num_experiments: - :param enhance_data: - :param batch_size: - :param initial_learning_rate: - :param show_network_summary: - :param training_image_size: - :param continue_from_model: - :param transfer_from_model: - :param initial_num_objects: - :param save_full_model: - :return: - """ - self.__num_epochs = num_experiments - self.__initial_learning_rate = initial_learning_rate - lr_scheduler = LearningRateScheduler(self.lr_schedule) - - - num_classes = num_objects - - if(training_image_size < 100): - warnings.warn("The specified training_image_size {} is less than 100. Hence the training_image_size will default to 100.".format(training_image_size)) - training_image_size = 100 - - - - image_input = Input(shape=(training_image_size, training_image_size, 3)) - if (self.__modelType == "squeezenet"): - if (continue_from_model != None): - model = SqueezeNet(weights="continued", num_classes=num_classes, model_input=image_input, model_path=continue_from_model) - if (show_network_summary == True): - print("Resuming training with weights loaded from a previous model") - - elif (transfer_from_model != None): - model = SqueezeNet(weights="transfer", num_classes=num_classes, model_input=image_input, - model_path=transfer_from_model, initial_num_classes=initial_num_objects,transfer_with_full_training=transfer_with_full_training) - if (show_network_summary == True): - print("Training using weights from a pre-trained model") - else: - model = SqueezeNet(weights="custom", num_classes=num_classes, model_input=image_input) - elif (self.__modelType == "resnet"): - if(continue_from_model != None): - model = ResNet50(weights="continued", num_classes=num_classes, model_input=image_input, model_path=continue_from_model) - if (show_network_summary == True): - print("Resuming training with weights loaded from a previous model") - elif(transfer_from_model != None): - model = ResNet50(weights="transfer", num_classes=num_classes, model_input=image_input, model_path=transfer_from_model, initial_num_classes=initial_num_objects, transfer_with_full_training=transfer_with_full_training) - if (show_network_summary == True): - print("Training using weights from a pre-trained model") - else: - model = ResNet50(weights="custom", num_classes=num_classes, model_input=image_input) - - elif (self.__modelType == "inceptionv3"): - if (continue_from_model != None): - model = InceptionV3(weights="continued", classes=num_classes, model_input=image_input, model_path=continue_from_model) - if (show_network_summary == True): - print("Resuming training with weights loaded from a previous model") - elif (transfer_from_model != None): - model = InceptionV3(weights="transfer", classes=num_classes, model_input=image_input, - model_path=transfer_from_model, initial_classes=initial_num_objects, - transfer_with_full_training=transfer_with_full_training) - if (show_network_summary == True): - print("Training using weights from a pre-trained model") - else: - model = InceptionV3(weights="custom", classes=num_classes, model_input=image_input) - - elif (self.__modelType == "densenet"): - if (continue_from_model != None): - model = DenseNetImageNet121(weights="continued", classes=num_classes, model_input=image_input, model_path=continue_from_model) - if (show_network_summary == True): - print("Resuming training with weights loaded from a previous model") - elif (transfer_from_model != None): - model = DenseNetImageNet121(weights="transfer", classes=num_classes, model_input=image_input, - model_path=transfer_from_model, initial_num_classes=initial_num_objects, - transfer_with_full_training=transfer_with_full_training) - if (show_network_summary == True): - print("Training using weights from a pre-trained model") - else: - model = DenseNetImageNet121(weights="custom", classes=num_classes, model_input=image_input) - - - optimizer = Adam(lr=self.__initial_learning_rate, decay=1e-4) - model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["accuracy"]) - if (show_network_summary == True): - model.summary() - - model_name = 'model_ex-{epoch:03d}_acc-{val_acc:03f}.h5' - - log_name = '{}_lr-{}_{}'.format(self.__modelType, initial_learning_rate, time.strftime("%Y-%m-%d-%H-%M-%S")) - - if not os.path.isdir(self.__trained_model_dir): - os.makedirs(self.__trained_model_dir) - - if not os.path.isdir(self.__model_class_dir): - os.makedirs(self.__model_class_dir) - - if not os.path.isdir(self.__logs_dir): - os.makedirs(self.__logs_dir) - - model_path = os.path.join(self.__trained_model_dir, model_name) - - - logs_path = os.path.join(self.__logs_dir, log_name) - - save_weights_condition = True - - if(save_full_model == True ): - save_weights_condition = False - elif(save_full_model == False): - save_weights_condition = True - - - checkpoint = ModelCheckpoint(filepath=model_path, - monitor='val_acc', - verbose=1, - save_weights_only=save_weights_condition, - save_best_only=True, - period=1) - - - tensorboard = TensorBoard(log_dir=logs_path, - histogram_freq=0, - write_graph=False, - write_images=False) - - - if (enhance_data == True): - print("Using Enhanced Data Generation") - - height_shift = 0 - width_shift = 0 - if (enhance_data == True): - height_shift = 0.1 - width_shift = 0.1 - - train_datagen = ImageDataGenerator( - rescale=1. / 255, - horizontal_flip=enhance_data, height_shift_range=height_shift, width_shift_range=width_shift) - - test_datagen = ImageDataGenerator( - rescale=1. / 255) - - train_generator = train_datagen.flow_from_directory(self.__train_dir, target_size=(training_image_size, training_image_size), - batch_size=batch_size, - class_mode="categorical") - test_generator = test_datagen.flow_from_directory(self.__test_dir, target_size=(training_image_size, training_image_size), - batch_size=batch_size, - class_mode="categorical") - - class_indices = train_generator.class_indices - class_json = {} - for eachClass in class_indices: - class_json[str(class_indices[eachClass])] = eachClass - - with open(os.path.join(self.__model_class_dir, "model_class.json"), "w+") as json_file: - json.dump(class_json, json_file, indent=4, separators=(",", " : "), - ensure_ascii=True) - json_file.close() - print("JSON Mapping for the model classes saved to ", os.path.join(self.__model_class_dir, "model_class.json")) - - num_train = len(train_generator.filenames) - num_test = len(test_generator.filenames) - print("Number of experiments (Epochs) : ", self.__num_epochs) - - # - model.fit_generator(train_generator, steps_per_epoch=int(num_train / batch_size), epochs=self.__num_epochs, - validation_data=test_generator, - validation_steps=int(num_test / batch_size), callbacks=[checkpoint, lr_scheduler, tensorboard]) - - - - - -class CustomImagePrediction: +class CustomImagePrediction(CustomImageClassification): + """ + Deprecated! + Replaced with 'imageai.Classification.Custom.CustomImageClassification' """ - This is the image prediction class for custom models trained with the 'ModelTraining' class. It provides support for 4 different models which are: - ResNet50, SqueezeNet, DenseNet121 and Inception V3. After instantiating this class, you can set it's properties and - make image predictions using it's pre-defined functions. - - The following functions are required to be called before a prediction can be made - * setModelPath() , path to your custom model - * setJsonPath , , path to your custom model's corresponding JSON file - * At least of of the following and it must correspond to the model set in the setModelPath() - [setModelTypeAsSqueezeNet(), setModelTypeAsResNet(), setModelTypeAsDenseNet, setModelTypeAsInceptionV3] - * loadModel() [This must be called once only before making a prediction] - - Once the above functions have been called, you can call the predictImage() function of the prediction instance - object at anytime to predict an image. - """ - - def __init__(self): - self.__modelType = "" - self.modelPath = "" - self.jsonPath = "" - self.numObjects = 10 - self.__modelLoaded = False - self.__model_collection = [] - self.__input_image_size = 224 - - def getModels(self): - """ - 'getModels()' provides access to the internal model collection. Helpful if models are used down the line with tools like lime. - :return: - """ - return self.__model_collection - - def setModelPath(self, model_path): - """ - 'setModelPath()' function is required and is used to set the file path to the model adopted from the list of the - available 4 model types. The model path must correspond to the model type set for the prediction instance object. - - :param model_path: - :return: - """ - self.modelPath = model_path - - def setJsonPath(self, model_json): - """ - 'setJsonPath()' - - :param model_path: - :return: - """ - self.jsonPath = model_json - - def setModelTypeAsSqueezeNet(self): - """ - 'setModelTypeAsSqueezeNet()' is used to set the model type to the SqueezeNet model - for the prediction instance object . - :return: - """ - self.__modelType = "squeezenet" - - def setModelTypeAsResNet(self): - """ - 'setModelTypeAsResNet()' is used to set the model type to the ResNet model - for the prediction instance object . - :return: - """ - self.__modelType = "resnet" - - def setModelTypeAsDenseNet(self): - """ - 'setModelTypeAsDenseNet()' is used to set the model type to the DenseNet model - for the prediction instance object . - :return: - """ - self.__modelType = "densenet" - - def setModelTypeAsInceptionV3(self): - """ - 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model - for the prediction instance object . - :return: - """ - self.__modelType = "inceptionv3" - - def loadModel(self, prediction_speed="normal", num_objects=10): - """ - 'loadModel()' function is used to load the model structure into the program from the file path defined - in the setModelPath() function. This function receives an optional value which is "prediction_speed". - The value is used to reduce the time it takes to predict an image, down to about 50% of the normal time, - with just slight changes or drop in prediction accuracy, depending on the nature of the image. - - prediction_speed (optional), acceptable values are "normal", "fast", "faster" and "fastest" - - num_objects (required), the number of objects the model is trained to recognize - - :param prediction_speed: - :param num_objects: - :return: - """ - - self.numObjects = num_objects - - if (prediction_speed == "normal"): - self.__input_image_size = 224 - elif (prediction_speed == "fast"): - self.__input_image_size = 160 - elif (prediction_speed == "faster"): - self.__input_image_size = 120 - elif (prediction_speed == "fastest"): - self.__input_image_size = 100 - - if (self.__modelLoaded == False): - - image_input = Input(shape=(self.__input_image_size, self.__input_image_size, 3)) - - if (self.__modelType == ""): - raise ValueError("You must set a valid model type before loading the model.") - - - elif (self.__modelType == "squeezenet"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - from ..SqueezeNet.squeezenet import SqueezeNet - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - - model = SqueezeNet(model_path=self.modelPath, weights="trained", model_input=image_input, - num_classes=self.numObjects) - self.__model_collection.append(model) - self.__modelLoaded = True - try: - None - except: - raise ("You have specified an incorrect path to the SqueezeNet model file.") - elif (self.__modelType == "resnet"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - from ..ResNet.resnet50 import ResNet50 - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - try: - model = ResNet50(model_path=self.modelPath, weights="trained", model_input=image_input, num_classes=self.numObjects) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the ResNet model file.") - - elif (self.__modelType == "densenet"): - from tensorflow.python.keras.preprocessing import image - from ..DenseNet.densenet import DenseNetImageNet121 - from .custom_utils import decode_predictions, preprocess_input - import numpy as np - try: - model = DenseNetImageNet121(model_path=self.modelPath, weights="trained", model_input=image_input, classes=self.numObjects) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the DenseNet model file.") - - elif (self.__modelType == "inceptionv3"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3 - from .custom_utils import decode_predictions, preprocess_input - - - - try: - model = InceptionV3(include_top=True, weights="trained", model_path=self.modelPath, - model_input=image_input, classes=self.numObjects) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the InceptionV3 model file.") - - def loadFullModel(self, prediction_speed="normal", num_objects=10): - """ - 'loadFullModel()' function is used to load the model structure into the program from the file path defined - in the setModelPath() function. As opposed to the 'loadModel()' function, you don't need to specify the model type. This means you can load any Keras model trained with or without ImageAI and perform image prediction. - - prediction_speed (optional), Acceptable values are "normal", "fast", "faster" and "fastest" - - num_objects (required), the number of objects the model is trained to recognize - - :param prediction_speed: - :param num_objects: - :return: - """ - - self.numObjects = num_objects - - if (prediction_speed == "normal"): - self.__input_image_size = 224 - elif (prediction_speed == "fast"): - self.__input_image_size = 160 - elif (prediction_speed == "faster"): - self.__input_image_size = 120 - elif (prediction_speed == "fastest"): - self.__input_image_size = 100 - - if (self.__modelLoaded == False): - - image_input = Input(shape=(self.__input_image_size, self.__input_image_size, 3)) - - - model = load_model(filepath=self.modelPath) - self.__model_collection.append(model) - self.__modelLoaded = True - self.__modelType = "full" - - def save_model_to_tensorflow(self, new_model_folder, new_model_name=""): - - """ - 'save_model_to_tensorflow' function allows you to save your loaded Keras (.h5) model and save it to the Tensorflow (.pb) model format. - - new_model_folder (required), the path to the folder you want the converted Tensorflow model to be saved - - new_model_name (required), the desired filename for your converted Tensorflow model e.g 'my_new_model.pb' - - :param new_model_folder: - :param new_model_name: - :return: - """ - - if(self.__modelLoaded == True): - out_prefix = "output_" - output_dir = new_model_folder - if os.path.exists(output_dir) == False: - os.mkdir(output_dir) - model_name = os.path.join(output_dir, new_model_name) - - keras_model = self.__model_collection[0] - - - out_nodes = [] - - for i in range(len(keras_model.outputs)): - out_nodes.append(out_prefix + str(i + 1)) - tf.identity(keras_model.output[i], out_prefix + str(i + 1)) - - sess = K.get_session() - - from tensorflow.python.framework import graph_util, graph_io - - init_graph = sess.graph.as_graph_def() - - main_graph = graph_util.convert_variables_to_constants(sess, init_graph, out_nodes) - - graph_io.write_graph(main_graph, output_dir, name=model_name, as_text=False) - print("Tensorflow Model Saved") - - def save_model_for_deepstack(self, new_model_folder, new_model_name=""): - - """ - 'save_model_for_deepstack' function allows you to save your loaded Keras (.h5) model and save it to the deployment format of DeepStack custom API. This function will save the model and the JSON file you need for the deployment. - - new_model_folder (required), the path to the folder you want the model to be saved - - new_model_name (required), the desired filename for your model e.g 'my_new_model.h5' - - :param new_model_folder: - :param new_model_name: - :return: - - """ - - if(self.__modelLoaded == True): - print(self.jsonPath) - with open(self.jsonPath) as inputFile: - model_json = json.load(inputFile) - - deepstack_json = {"sys-version": "1.0", "framework":"KERAS","mean":0.5,"std":255} - deepstack_json["width"] = self.__input_image_size - deepstack_json["height"] = self.__input_image_size - - deepstack_classes_map = {} - - - for eachClass in model_json: - deepstack_classes_map[eachClass] = model_json[eachClass] - - deepstack_json["map"] = deepstack_classes_map - - output_dir = new_model_folder - if os.path.exists(output_dir) == False: - os.mkdir(output_dir) - - with open(os.path.join(output_dir,"config.json"), "w+") as json_file: - json.dump(deepstack_json, json_file, indent=4, separators=(",", " : "), - ensure_ascii=True) - json_file.close() - print("JSON Config file saved for DeepStack format in ", - os.path.join(output_dir, "config.json")) - - keras_model = self.__model_collection[0] - save_model(keras_model, os.path.join(new_model_folder, new_model_name)) - print("Model saved for DeepStack format in", - os.path.join(os.path.join(new_model_folder, new_model_name))) - - - - - - - - def predictImage(self, image_input, result_count=1, input_type="file", thread_safe=False): - """ - 'predictImage()' function is used to predict a given image by receiving the following arguments: - * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" - * image_input , file path/numpy array/image file stream of the image. - * result_count (optional) , the number of predictions to be sent which must be whole numbers between - 1 and the number of classes present in the model - * thread_safe (optional, False by default), enforce the loaded detection model works across all threads if set to true, made possible by forcing all Keras inference to run on the default graph - - This function returns 2 arrays namely 'prediction_results' and 'prediction_probabilities'. The 'prediction_results' - contains possible objects classes arranged in descending of their percentage probabilities. The 'prediction_probabilities' - contains the percentage probability of each object class. The position of each object class in the 'prediction_results' - array corresponds with the positions of the percentage possibilities in the 'prediction_probabilities' array. - - - :param input_type: - :param image_input: - :param result_count: - :param thread_safe: - :return prediction_results, prediction_probabilities: - """ - prediction_results = [] - prediction_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making predictions.") - - else: - - if (self.__modelType == "squeezenet"): - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - model = self.__model_collection[0] - - if(thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(image_to_predict, steps=1) - else: - prediction = model.predict(image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "resnet"): - - model = self.__model_collection[0] - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - - - - try: - - predictiondata = decode_predictions(prediction, top=int(result_count), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - - - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "densenet"): - - model = self.__model_collection[0] - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - from ..DenseNet.densenet import DenseNetImageNet121 - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if(thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "inceptionv3"): - - model = self.__model_collection[0] - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3 - from .custom_utils import decode_predictions, preprocess_input - - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if(thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - - elif (self.__modelType == "full"): - - model = self.__model_collection[0] - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3 - from .custom_utils import decode_predictions, preprocess_input - - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if(thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - - try: - predictiondata = decode_predictions(prediction, top=int(result_count), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - - - - - def predictMultipleImages(self, sent_images_array, result_count_per_image=1, input_type="file", thread_safe=False): - """ - 'predictMultipleImages()' function is used to predict more than one image by receiving the following arguments: - * input_type , the type of inputs contained in the parsed array. Acceptable values are "file", "array" and "stream" - * sent_images_array , an array of image file paths, image numpy array or image file stream - * result_count_per_image (optionally) , the number of predictions to be sent per image, which must be whole numbers between 1 and the number of classes present in the model - * thread_safe (optional, False by default), enforce the loaded detection model works across all threads if set to true, made possible by forcing all Keras inference to run on the default graph - - This function returns an array of dictionaries, with each dictionary containing 2 arrays namely 'prediction_results' and 'prediction_probabilities'. The 'prediction_results' - contains possible objects classes arranged in descending of their percentage probabilities. The 'prediction_probabilities' - contains the percentage probability of each object class. The position of each object class in the 'prediction_results' - array corresponds with the positions of the percentage possibilities in the 'prediction_probabilities' array. - - - :param input_type: - :param sent_images_array: - :param result_count_per_image: - :return output_array: - """ - - output_array = [] - - for image_input in sent_images_array: - - prediction_results = [] - prediction_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making predictions.") - - else: - if (self.__modelType == "squeezenet"): - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - model = self.__model_collection[0] - - if (thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - elif (self.__modelType == "resnet"): - - model = self.__model_collection[0] - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - - predictiondata = decode_predictions(prediction, top=int(result_count_per_image), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - - - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - - elif (self.__modelType == "densenet"): - - model = self.__model_collection[0] - - from .custom_utils import preprocess_input - from .custom_utils import decode_predictions - from ..DenseNet.densenet import DenseNetImageNet121 - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - - elif (self.__modelType == "inceptionv3"): - - model = self.__model_collection[0] - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3 - from .custom_utils import decode_predictions, preprocess_input - - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=( - self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (thread_safe == True): - with K.get_session().graph.as_default(): - prediction = model.predict(x=image_to_predict, steps=1) - else: - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image), model_json=self.jsonPath) - - for result in predictiondata: - prediction_results.append(str(result[0])) - prediction_probabilities.append(result[1] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - - return output_array - - + def __call__(self): + None \ No newline at end of file diff --git a/imageai/Prediction/__init__.py b/imageai/Prediction/__init__.py index fa3db385..0a8ca882 100644 --- a/imageai/Prediction/__init__.py +++ b/imageai/Prediction/__init__.py @@ -1,647 +1,12 @@ -import numpy as np -from tensorflow.python.keras.preprocessing import image -from PIL import Image +from ..Classification import ImageClassification +from matplotlib.cbook import deprecated -from tensorflow.python.keras.layers import Input, Conv2D, MaxPool2D, Activation, concatenate, Dropout -from tensorflow.python.keras.layers import GlobalAvgPool2D, GlobalMaxPool2D -from tensorflow.python.keras.models import Model -from tensorflow.python.keras.models import Sequential - - -class ImagePrediction: +class ImagePrediction(ImageClassification): """ - This is the image prediction class in the ImageAI library. It provides support for 4 different models which are: - ResNet, SqueezeNet, DenseNet and Inception V3. After instantiating this class, you can set it's properties and - make image predictions using it's pre-defined functions. - - The following functions are required to be called before a prediction can be made - * setModelPath() - * At least of of the following and it must correspond to the model set in the setModelPath() - [setModelTypeAsSqueezeNet(), setModelTypeAsResNet(), setModelTypeAsDenseNet, setModelTypeAsInceptionV3] - * loadModel() [This must be called once only before making a prediction] - - Once the above functions have been called, you can call the predictImage() function of the prediction instance - object at anytime to predict an image. + Deprecated! + Replaced with 'imageai.Classification.ImageClassification' """ - def __init__(self): - self.__modelType = "" - self.modelPath = "" - self.__modelLoaded = False - self.__model_collection = [] - self.__input_image_size = 224 - - - def setModelPath(self, model_path): - """ - 'setModelPath()' function is required and is used to set the file path to the model adopted from the list of the - available 4 model types. The model path must correspond to the model type set for the prediction instance object. - - :param model_path: - :return: - """ - self.modelPath = model_path - - - def setModelTypeAsSqueezeNet(self): - """ - 'setModelTypeAsSqueezeNet()' is used to set the model type to the SqueezeNet model - for the prediction instance object . - :return: - """ - self.__modelType = "squeezenet" - - def setModelTypeAsResNet(self): - """ - 'setModelTypeAsResNet()' is used to set the model type to the ResNet model - for the prediction instance object . - :return: - """ - self.__modelType = "resnet" - - def setModelTypeAsDenseNet(self): - """ - 'setModelTypeAsDenseNet()' is used to set the model type to the DenseNet model - for the prediction instance object . - :return: - """ - self.__modelType = "densenet" - - def setModelTypeAsInceptionV3(self): - """ - 'setModelTypeAsInceptionV3()' is used to set the model type to the InceptionV3 model - for the prediction instance object . - :return: - """ - self.__modelType = "inceptionv3" - - def loadModel(self, prediction_speed="normal"): - """ - 'loadModel()' function is used to load the model structure into the program from the file path defined - in the setModelPath() function. This function receives an optional value which is "prediction_speed". - The value is used to reduce the time it takes to predict an image, down to about 50% of the normal time, - with just slight changes or drop in prediction accuracy, depending on the nature of the image. - * prediction_speed (optional); Acceptable values are "normal", "fast", "faster" and "fastest" - - :param prediction_speed : - :return: - """ - - if(prediction_speed=="normal"): - self.__input_image_size = 224 - elif(prediction_speed=="fast"): - self.__input_image_size = 160 - elif(prediction_speed=="faster"): - self.__input_image_size = 120 - elif (prediction_speed == "fastest"): - self.__input_image_size = 100 - - if (self.__modelLoaded == False): - - image_input = Input(shape=(self.__input_image_size, self.__input_image_size, 3)) - - if(self.__modelType == "" ): - raise ValueError("You must set a valid model type before loading the model.") - - - elif(self.__modelType == "squeezenet"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - from .SqueezeNet.squeezenet import SqueezeNet - from .imagenet_utils import preprocess_input, decode_predictions - try: - model = SqueezeNet(model_path=self.modelPath, model_input=image_input) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ("You have specified an incorrect path to the SqueezeNet model file.") - elif(self.__modelType == "resnet"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - from .ResNet.resnet50 import ResNet50 - from .imagenet_utils import preprocess_input, decode_predictions - try: - model = ResNet50(model_path=self.modelPath, model_input=image_input) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the ResNet model file.") - - elif (self.__modelType == "densenet"): - from tensorflow.python.keras.preprocessing import image - from .DenseNet.densenet import DenseNetImageNet121, preprocess_input, decode_predictions - import numpy as np - try: - model = DenseNetImageNet121(model_path=self.modelPath, model_input=image_input) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the DenseNet model file.") - - elif (self.__modelType == "inceptionv3"): - import numpy as np - from tensorflow.python.keras.preprocessing import image - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3 - from imageai.Prediction.InceptionV3.inceptionv3 import preprocess_input, decode_predictions - - try: - model = InceptionV3(include_top=True, weights="imagenet", model_path=self.modelPath, model_input=image_input) - self.__model_collection.append(model) - self.__modelLoaded = True - except: - raise ValueError("You have specified an incorrect path to the InceptionV3 model file.") - - - - - - - - - def predictImage(self, image_input, result_count=5, input_type="file" ): - """ - 'predictImage()' function is used to predict a given image by receiving the following arguments: - * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" - * image_input , file path/numpy array/image file stream of the image. - * result_count (optional) , the number of predictions to be sent which must be whole numbers between - 1 and 1000. The default is 5. - - This function returns 2 arrays namely 'prediction_results' and 'prediction_probabilities'. The 'prediction_results' - contains possible objects classes arranged in descending of their percentage probabilities. The 'prediction_probabilities' - contains the percentage probability of each object class. The position of each object class in the 'prediction_results' - array corresponds with the positions of the percentage possibilities in the 'prediction_probabilities' array. - - - :param input_type: - :param image_input: - :param result_count: - :return prediction_results, prediction_probabilities: - """ - prediction_results = [] - prediction_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making predictions.") - - else: - - if (self.__modelType == "squeezenet"): - - from .imagenet_utils import preprocess_input, decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - model = self.__model_collection[0] - - prediction = model.predict(image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "resnet"): - - model = self.__model_collection[0] - - from .imagenet_utils import preprocess_input, decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "densenet"): - - model = self.__model_collection[0] - - from .DenseNet.densenet import preprocess_input, decode_predictions - from .DenseNet.densenet import DenseNetImageNet121 - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - elif (self.__modelType == "inceptionv3"): - - model = self.__model_collection[0] - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3, preprocess_input, decode_predictions - - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - - try: - predictiondata = decode_predictions(prediction, top=int(result_count)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - return prediction_results, prediction_probabilities - - - - def predictMultipleImages(self, sent_images_array, result_count_per_image=2, input_type="file"): - """ - 'predictMultipleImages()' function is used to predict more than one image by receiving the following arguments: - * input_type , the type of inputs contained in the parsed array. Acceptable values are "file", "array" and "stream" - * sent_images_array , an array of image file paths, image numpy array or image file stream - * result_count_per_image (optionally) , the number of predictions to be sent per image, which must be whole numbers between - 1 and 1000. The default is 2. - - This function returns an array of dictionaries, with each dictionary containing 2 arrays namely 'prediction_results' and 'prediction_probabilities'. The 'prediction_results' - contains possible objects classes arranged in descending of their percentage probabilities. The 'prediction_probabilities' - contains the percentage probability of each object class. The position of each object class in the 'prediction_results' - array corresponds with the positions of the percentage possibilities in the 'prediction_probabilities' array. - - - :param input_type: - :param sent_images_array: - :param result_count_per_image: - :return output_array: - """ - - output_array = [] - - for image_input in sent_images_array: - - prediction_results = [] - prediction_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making predictions.") - - else: - - if (self.__modelType == "squeezenet"): - - from .imagenet_utils import preprocess_input, decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - model = self.__model_collection[0] - - prediction = model.predict(image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - elif (self.__modelType == "resnet"): - - model = self.__model_collection[0] - - from .imagenet_utils import preprocess_input, decode_predictions - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - elif (self.__modelType == "densenet"): - - model = self.__model_collection[0] - - from .DenseNet.densenet import preprocess_input, decode_predictions - from .DenseNet.densenet import DenseNetImageNet121 - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - elif (self.__modelType == "inceptionv3"): - - model = self.__model_collection[0] - - from imageai.Prediction.InceptionV3.inceptionv3 import InceptionV3, preprocess_input, \ - decode_predictions - - if (input_type == "file"): - try: - image_to_predict = image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - image_to_predict = preprocess_input(image_to_predict) - except: - raise ValueError("You have parsed in a wrong stream for the image") - - prediction = model.predict(x=image_to_predict, steps=1) - - try: - predictiondata = decode_predictions(prediction, top=int(result_count_per_image)) - - for results in predictiondata: - countdown = 0 - for result in results: - countdown += 1 - prediction_results.append(str(result[1])) - prediction_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - each_image_details = {} - each_image_details["predictions"] = prediction_results - each_image_details["percentage_probabilities"] = prediction_probabilities - output_array.append(each_image_details) - - - return output_array - + def __call__(self): + None \ No newline at end of file From e76f87212a6e53b6271f7a225e20ca3df9b1d18e Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Thu, 15 Oct 2020 19:36:12 +0100 Subject: [PATCH 02/12] removed VSCode files --- .gitignore | 13 ++++++++++++- .vscode/settings.json | 3 --- 2 files changed, 12 insertions(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 49789297..a2f2b840 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,15 @@ nosetests.xml coverage.xml *.cover .hypothesis/ -*.egg-info \ No newline at end of file +*.egg-info + +# VSCode files +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3a268214..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python.pythonPath": "C:\\Users\\John Olafenwa\\AppData\\Local\\Programs\\Python\\Python36\\python.exe" -} \ No newline at end of file From 60e8123e0ca33d46b0cbf9ab64896a2056f204d1 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Thu, 22 Oct 2020 22:30:50 +0100 Subject: [PATCH 03/12] updated TinyYOLOv3 and YOLOv3 standard image/video detection to work with TF 2.0 --- imageai/Detection/YOLO/utils.py | 363 ++++++++++ imageai/Detection/YOLO/yolov3.py | 103 +++ imageai/Detection/YOLOv3/utils.py | 60 ++ imageai/Detection/__init__.py | 1032 ++++------------------------- 4 files changed, 669 insertions(+), 889 deletions(-) create mode 100644 imageai/Detection/YOLO/utils.py create mode 100644 imageai/Detection/YOLO/yolov3.py diff --git a/imageai/Detection/YOLO/utils.py b/imageai/Detection/YOLO/utils.py new file mode 100644 index 00000000..fdd8098e --- /dev/null +++ b/imageai/Detection/YOLO/utils.py @@ -0,0 +1,363 @@ +import tensorflow as tf +from keras import backend as K +import numpy as np +from PIL import Image +import cv2 + + +def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False): + + num_anchors = len(anchors) + + anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2]) + + grid_shape = K.shape(feats)[1:3] + grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]), + [1, grid_shape[1], 1, 1]) + grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]), + [grid_shape[0], 1, 1, 1]) + grid = K.concatenate([grid_x, grid_y]) + grid = K.cast(grid, K.dtype(feats)) + + feats = K.reshape( + feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5]) + + + box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats)) + box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats)) + box_confidence = K.sigmoid(feats[..., 4:5]) + box_class_probs = K.sigmoid(feats[..., 5:]) + + if calc_loss == True: + return grid, feats, box_xy, box_wh + return box_xy, box_wh, box_confidence, box_class_probs + + +def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape): + + box_yx = box_xy[..., ::-1] + box_hw = box_wh[..., ::-1] + input_shape = K.cast(input_shape, K.dtype(box_yx)) + image_shape = K.cast(image_shape, K.dtype(box_yx)) + new_shape = K.round(image_shape * K.min(input_shape/image_shape)) + offset = (input_shape-new_shape)/2./input_shape + scale = input_shape/new_shape + box_yx = (box_yx - offset) * scale + box_hw *= scale + + box_mins = box_yx - (box_hw / 2.) + box_maxes = box_yx + (box_hw / 2.) + boxes = K.concatenate([ + box_mins[..., 0:1], + box_mins[..., 1:2], + box_maxes[..., 0:1], + box_maxes[..., 1:2] + ]) + + + boxes *= K.concatenate([image_shape, image_shape]) + return boxes + + +def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape): + + box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats, + anchors, num_classes, input_shape) + boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape) + boxes = K.reshape(boxes, [-1, 4]) + box_scores = box_confidence * box_class_probs + box_scores = K.reshape(box_scores, [-1, num_classes]) + return boxes, box_scores + + +def yolo_eval(yolo_outputs, + anchors, + num_classes, + image_shape, + max_boxes=20, + score_threshold=.6, + iou_threshold=.5): + + num_layers = len(yolo_outputs) + anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] + input_shape = K.shape(yolo_outputs[0])[1:3] * 32 + boxes = [] + box_scores = [] + for l in range(num_layers): + _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], + anchors[anchor_mask[l]], num_classes, input_shape, image_shape) + boxes.append(_boxes) + box_scores.append(_box_scores) + boxes = K.concatenate(boxes, axis=0) + box_scores = K.concatenate(box_scores, axis=0) + + mask = box_scores >= score_threshold + max_boxes_tensor = K.constant(max_boxes, dtype='int32') + boxes_ = [] + scores_ = [] + classes_ = [] + for c in range(num_classes): + class_boxes = tf.boolean_mask(boxes, mask[:, c]) + class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c]) + nms_index = tf.image.non_max_suppression( + class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold) + class_boxes = K.gather(class_boxes, nms_index) + class_box_scores = K.gather(class_box_scores, nms_index) + classes = K.ones_like(class_box_scores, 'int32') * c + boxes_.append(class_boxes) + scores_.append(class_box_scores) + classes_.append(classes) + boxes_ = K.concatenate(boxes_, axis=0) + scores_ = K.concatenate(scores_, axis=0) + classes_ = K.concatenate(classes_, axis=0) + + return boxes_, scores_, classes_ + + + +def letterbox_image(image, size): + iw, ih = image.size + w, h = size + scale = min(w/iw, h/ih) + nw = int(iw*scale) + nh = int(ih*scale) + + image = image.resize((nw,nh), Image.BICUBIC) + new_image = Image.new('RGB', size, (128,128,128)) + new_image.paste(image, ((w-nw)//2, (h-nh)//2)) + return new_image + + + + +def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w): + if (float(net_w)/image_w) < (float(net_h)/image_h): + new_w = net_w + new_h = (image_h*net_w)/image_w + else: + new_h = net_w + new_w = (image_w*net_h)/image_h + + for i in range(len(boxes)): + x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w + y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h + + boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w) + boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w) + boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h) + boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h) + + + +class BoundBox: + def __init__(self, xmin, ymin, xmax, ymax, objness = None, classes = None): + self.xmin = xmin + self.ymin = ymin + self.xmax = xmax + self.ymax = ymax + + self.objness = objness + self.classes = classes + + self.label = -1 + self.score = -1 + + def get_label(self): + if self.label == -1: + self.label = np.argmax(self.classes) + + return self.label + + def get_score(self): + if self.score == -1: + self.score = self.classes[self.get_label()] + + return self.score + + +def _interval_overlap(interval_a, interval_b): + x1, x2 = interval_a + x3, x4 = interval_b + + if x3 < x1: + if x4 < x1: + return 0 + else: + return min(x2,x4) - x1 + else: + if x2 < x3: + return 0 + else: + return min(x2,x4) - x3 + +def _sigmoid(x): + return 1. / (1. + np.exp(-x)) + +def bbox_iou(box1, box2): + intersect_w = _interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax]) + intersect_h = _interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax]) + + intersect = intersect_w * intersect_h + + w1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin + w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin + + union = w1*h1 + w2*h2 - intersect + + return float(intersect) / union + + +def do_nms(boxes, nms_thresh): + if len(boxes) > 0: + nb_class = len(boxes[0].classes) + else: + return + + for c in range(nb_class): + sorted_indices = np.argsort([-box.classes[c] for box in boxes]) + + for i in range(len(sorted_indices)): + index_i = sorted_indices[i] + + if boxes[index_i].classes[c] == 0: continue + + for j in range(i+1, len(sorted_indices)): + index_j = sorted_indices[j] + + if bbox_iou(boxes[index_i], boxes[index_j]) >= nms_thresh: + boxes[index_j].classes[c] = 0 + +def decode_netout(netout, anchors, obj_thresh, nms_thresh, net_h, net_w): + grid_h, grid_w = netout.shape[:2] + nb_box = 3 + netout = netout.reshape((grid_h, grid_w, nb_box, -1)) + nb_class = netout.shape[-1] - 5 + + boxes = [] + + netout[..., :2] = _sigmoid(netout[..., :2]) + netout[..., 4:] = _sigmoid(netout[..., 4:]) + netout[..., 5:] = netout[..., 4][..., np.newaxis] * netout[..., 5:] + netout[..., 5:] *= netout[..., 5:] > obj_thresh + + for i in range(grid_h*grid_w): + row = i / grid_w + col = i % grid_w + + for b in range(nb_box): + # 4th element is objectness score + objectness = netout[int(row)][int(col)][b][4] + #objectness = netout[..., :4] + + if(objectness.all() <= obj_thresh): continue + + # first 4 elements are x, y, w, and h + x, y, w, h = netout[int(row)][int(col)][b][:4] + + x = (col + x) / grid_w # center position, unit: image width + y = (row + y) / grid_h # center position, unit: image height + w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width + h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height + + # last elements are class probabilities + classes = netout[int(row)][col][b][5:] + + box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes) + #box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, None, classes) + + boxes.append(box) + + return boxes + +def preprocess_input(image, input_shape): + net_h, net_w = input_shape + new_h, new_w, _ = image.shape + + # determine the new size of the image + if (float(net_w)/new_w) < (float(net_h)/new_h): + new_h = int((new_h * net_w)/new_w) + new_w = net_w + else: + new_w = int((new_w * net_h)/new_h) + new_h = net_h + + # resize the image to the new size + resized = cv2.resize(image[:,:,::-1]/255., (int(new_w), int(new_h))) + + # embed the image into the standard letter box + new_image = np.ones((net_h, net_w, 3)) * 0.5 + new_image[int((net_h-new_h)//2):int((net_h+new_h)//2), int((net_w-new_w)//2):int((net_w+new_w)//2), :] = resized + new_image = np.expand_dims(new_image, 0) + + return new_image + +def retrieve_yolo_detections(yolo_result, anchors, min_probability, nms_thresh, image_input_size, image_size, labels_dict ): + + boxes = [] + + for i in range(len(yolo_result)): + # decode the output of the network + boxes += decode_netout(yolo_result[i][0], + anchors[i], + min_probability, + nms_thresh, + image_input_size[0], + image_input_size[1]) + + # correct the sizes of the bounding boxes + correct_yolo_boxes(boxes, image_size[1], image_size[0], image_input_size[0], image_input_size[1]) + + # suppress non-maximal boxes + do_nms(boxes, nms_thresh) + + detections = list() + for box in boxes: + label = -1 + + for i in range(len(labels_dict.keys())): + if box.classes[i] > min_probability: + label = labels_dict[i] + + + percentage_probability = box.classes[i] * 100 + xmin = box.xmin + ymin = box.ymin + xmax = box.xmax + ymax = box.ymax + + if xmin < 0: + xmin = 0 + + if ymin < 0: + ymin = 0 + + detection = dict() + detection["name"] = label + detection["percentage_probability"] = percentage_probability + detection["box_points"] = [ xmin, ymin, xmax, ymax] + + detections.append(detection) + + return detections + + +def draw_boxes(image, box_points, draw_box, label, percentage_probability, color): + + xmin, ymin, xmax, ymax = box_points + + if draw_box is True: + cv2.rectangle(image, (xmin,ymin), (xmax,ymax), color, 2) + + if label is not None: + if percentage_probability is None: + label = "{}".format(label) + else: + label = "{} {:.2f}%".format(label, percentage_probability) + elif percentage_probability is not None: + label = "{:.2f}".format(percentage_probability) + + if label is not None or percentage_probability is not None: + cv2.putText(image, label, (xmin, ymin - 13), cv2.FONT_HERSHEY_SIMPLEX, 1e-3 * image.shape[0], (255, 0, 0), 2) + cv2.putText(image, label, (xmin, ymin - 13), cv2.FONT_HERSHEY_SIMPLEX, 1e-3 * image.shape[0], (255, 255, 255), 1) + + return image \ No newline at end of file diff --git a/imageai/Detection/YOLO/yolov3.py b/imageai/Detection/YOLO/yolov3.py new file mode 100644 index 00000000..69edb574 --- /dev/null +++ b/imageai/Detection/YOLO/yolov3.py @@ -0,0 +1,103 @@ +from tensorflow.keras.layers import Conv2D, MaxPool2D, Add, ZeroPadding2D, UpSampling2D, Concatenate +from tensorflow.keras.layers import LeakyReLU +from tensorflow.keras.layers import BatchNormalization +from tensorflow.keras.regularizers import l2 +from tensorflow.keras.models import Model +from tensorflow.keras import Input + + + +def NetworkConv2D_BN_Leaky(input, channels, kernel_size, kernel_regularizer = l2(5e-4), strides=(1,1), padding="same", use_bias=False): + + network = Conv2D( filters=channels, kernel_size=kernel_size, strides=strides, padding=padding, kernel_regularizer=kernel_regularizer, use_bias=use_bias)(input) + network = BatchNormalization()(network) + network = LeakyReLU(alpha=0.1)(network) + return network + +def residual_block(input, channels, num_blocks): + network = ZeroPadding2D(((1,0), (1,0)))(input) + network = NetworkConv2D_BN_Leaky(input=network,channels=channels, kernel_size=(3,3), strides=(2,2), padding="valid") + + for blocks in range(num_blocks): + network_1 = NetworkConv2D_BN_Leaky(input=network, channels= channels // 2, kernel_size=(1,1)) + network_1 = NetworkConv2D_BN_Leaky(input=network_1,channels= channels, kernel_size=(3,3)) + + network = Add()([network, network_1]) + return network + +def darknet(input): + network = NetworkConv2D_BN_Leaky(input=input, channels=32, kernel_size=(3,3)) + network = residual_block(input=network, channels=64, num_blocks=1) + network = residual_block(input=network, channels=128, num_blocks=2) + network = residual_block(input=network, channels=256, num_blocks=8) + network = residual_block(input=network, channels=512, num_blocks=8) + network = residual_block(input=network, channels=1024, num_blocks=4) + + + return network + +def last_layers(input, channels_in, channels_out, layer_name=""): + + + + network = NetworkConv2D_BN_Leaky( input=input, channels=channels_in, kernel_size=(1,1)) + network = NetworkConv2D_BN_Leaky(input=network, channels= (channels_in * 2) , kernel_size=(3, 3)) + network = NetworkConv2D_BN_Leaky(input=network, channels=channels_in, kernel_size=(1, 1)) + network = NetworkConv2D_BN_Leaky(input=network, channels=(channels_in * 2), kernel_size=(3, 3)) + network = NetworkConv2D_BN_Leaky(input=network, channels=channels_in, kernel_size=(1, 1)) + + network_1 = NetworkConv2D_BN_Leaky(input=network, channels=(channels_in * 2), kernel_size=(3, 3)) + network_1 = Conv2D(filters=channels_out, kernel_size=(1,1), name=layer_name)(network_1) + + return network, network_1 + +def yolov3_main(input, num_anchors, num_classes): + + darknet_network = Model(input, darknet(input)) + + network, network_1 = last_layers(darknet_network.output, 512, num_anchors * (num_classes + 5), layer_name="last1") + + network = NetworkConv2D_BN_Leaky( input=network, channels=256, kernel_size=(1,1)) + network = UpSampling2D(2)(network) + network = Concatenate()([network, darknet_network.layers[152].output]) + + network, network_2 = last_layers(network, 256, num_anchors * (num_classes + 5), layer_name="last2") + + network = NetworkConv2D_BN_Leaky(input=network, channels=128, kernel_size=(1, 1)) + network = UpSampling2D(2)(network) + network = Concatenate()([network, darknet_network.layers[92].output]) + + network, network_3 = last_layers(network, 128, num_anchors * (num_classes + 5), layer_name="last3") + + return Model(input, [network_1, network_2, network_3]) + + +def tiny_yolov3_main(input, num_anchors, num_classes): + + network_1 = NetworkConv2D_BN_Leaky(input=input, channels=16, kernel_size=(3,3) ) + network_1 = MaxPool2D(pool_size=(2,2), strides=(2,2), padding="same")(network_1) + network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=32, kernel_size=(3, 3)) + network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) + network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=64, kernel_size=(3, 3)) + network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) + network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=128, kernel_size=(3, 3)) + network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) + network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=256, kernel_size=(3, 3)) + + network_2 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) + network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=512, kernel_size=(3, 3)) + network_2 = MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding="same")(network_2) + network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=1024, kernel_size=(3, 3)) + network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=256, kernel_size=(1, 1)) + + network_3 = NetworkConv2D_BN_Leaky(input=network_2, channels=512, kernel_size=(3, 3)) + network_3 = Conv2D(num_anchors * (num_classes + 5), kernel_size=(1,1))(network_3) + + network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=128, kernel_size=(1, 1)) + network_2 = UpSampling2D(2)(network_2) + + network_4 = Concatenate()([network_2, network_1]) + network_4 = NetworkConv2D_BN_Leaky(input=network_4, channels=256, kernel_size=(3, 3)) + network_4 = Conv2D(num_anchors * (num_classes + 5), kernel_size=(1,1))(network_4) + + return Model(input, [network_3, network_4]) \ No newline at end of file diff --git a/imageai/Detection/YOLOv3/utils.py b/imageai/Detection/YOLOv3/utils.py index 240bb0f0..fd12687b 100644 --- a/imageai/Detection/YOLOv3/utils.py +++ b/imageai/Detection/YOLOv3/utils.py @@ -114,6 +114,66 @@ def yolo_eval(yolo_outputs, +def decode_netout(netout, anchors, obj_thresh, nms_thresh, net_h, net_w): + grid_h, grid_w = netout.shape[:2] + nb_box = 3 + netout = netout.reshape((grid_h, grid_w, nb_box, -1)) + nb_class = netout.shape[-1] - 5 + + boxes = [] + + netout[..., :2] = _sigmoid(netout[..., :2]) + netout[..., 4:] = _sigmoid(netout[..., 4:]) + netout[..., 5:] = netout[..., 4][..., np.newaxis] * netout[..., 5:] + netout[..., 5:] *= netout[..., 5:] > obj_thresh + + for i in range(grid_h*grid_w): + row = i / grid_w + col = i % grid_w + + for b in range(nb_box): + # 4th element is objectness score + objectness = netout[int(row)][int(col)][b][4] + #objectness = netout[..., :4] + + if(objectness.all() <= obj_thresh): continue + + # first 4 elements are x, y, w, and h + x, y, w, h = netout[int(row)][int(col)][b][:4] + + x = (col + x) / grid_w # center position, unit: image width + y = (row + y) / grid_h # center position, unit: image height + w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width + h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height + + # last elements are class probabilities + classes = netout[int(row)][col][b][5:] + + box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes) + #box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, None, classes) + + boxes.append(box) + + return boxes + +def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w): + if (float(net_w)/image_w) < (float(net_h)/image_h): + new_w = net_w + new_h = (image_h*net_w)/image_w + else: + new_h = net_w + new_w = (image_w*net_h)/image_h + + for i in range(len(boxes)): + x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w + y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h + + boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w) + boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w) + boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h) + boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h) + + def letterbox_image(image, size): iw, ih = image.size w, h = size diff --git a/imageai/Detection/__init__.py b/imageai/Detection/__init__.py index a31f7dc6..e120fca9 100644 --- a/imageai/Detection/__init__.py +++ b/imageai/Detection/__init__.py @@ -9,13 +9,14 @@ import numpy as np import tensorflow as tf import os -from keras import backend as K -from keras.layers import Input +from tensorflow.keras import backend as K +from tensorflow.keras.layers import Input from PIL import Image import colorsys +import warnings -from imageai.Detection.YOLOv3.models import yolo_main, tiny_yolo_main -from imageai.Detection.YOLOv3.utils import letterbox_image, yolo_eval +from imageai.Detection.YOLO.yolov3 import tiny_yolov3_main, yolov3_main +from imageai.Detection.YOLO.utils import letterbox_image, yolo_eval, preprocess_input, retrieve_yolo_detections, draw_boxes def get_session(): @@ -80,16 +81,13 @@ def __init__(self): # Unique instance variables for YOLOv3 and TinyYOLOv3 model self.__yolo_iou = 0.45 self.__yolo_score = 0.1 - self.__yolo_anchors = np.array( - [[10., 13.], [16., 30.], [33., 23.], [30., 61.], [62., 45.], [59., 119.], [116., 90.], [156., 198.], - [373., 326.]]) + self.__nms_thresh = 0.45 + self.__yolo_anchors = [[116,90, 156,198, 373,326], [30,61, 62,45, 59,119], [10,13, 16,30, 33,23]] self.__yolo_model_image_size = (416, 416) self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes = "", "", "" - self.sess = K.get_session() - - # Unique instance variables for TinyYOLOv3. - self.__tiny_yolo_anchors = np.array( - [[10., 14.], [23., 27.], [37., 58.], [81., 82.], [135., 169.], [344., 319.]]) + self.__tiny_yolo_anchors = [[81, 82, 135, 169, 344, 319], [10, 14, 23, 27, 37, 58]] + self.__box_color = (112, 19, 24) + def setModelTypeAsRetinaNet(self): """ @@ -191,61 +189,26 @@ def loadModel(self, detection_speed="normal"): model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True - elif (self.__modelType == "yolov3"): - model = yolo_main(Input(shape=(None, None, 3)), len(self.__yolo_anchors) // 3, - len(self.numbers_to_names)) - model.load_weights(self.modelPath) + elif (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): - hsv_tuples = [(x / len(self.numbers_to_names), 1., 1.) - for x in range(len(self.numbers_to_names))] - self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) - self.colors = list( - map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), - self.colors)) - np.random.seed(10101) - np.random.shuffle(self.colors) - np.random.seed(None) - - self.__yolo_input_image_shape = K.placeholder(shape=(2,)) - self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes = yolo_eval(model.output, - self.__yolo_anchors, - len(self.numbers_to_names), - self.__yolo_input_image_shape, - score_threshold=self.__yolo_score, - iou_threshold=self.__yolo_iou) + input_image = Input(shape=(None, None, 3)) - self.__model_collection.append(model) - self.__modelLoaded = True + if self.__modelType == "yolov3": + model = yolov3_main(input_image, len(self.__yolo_anchors), + len(self.numbers_to_names.keys())) + else: + model = tiny_yolov3_main(input_image, 3, + len(self.numbers_to_names.keys())) - elif (self.__modelType == "tinyyolov3"): - model = tiny_yolo_main(Input(shape=(None, None, 3)), len(self.__tiny_yolo_anchors) // 2, - len(self.numbers_to_names)) model.load_weights(self.modelPath) - - hsv_tuples = [(x / len(self.numbers_to_names), 1., 1.) - for x in range(len(self.numbers_to_names))] - self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) - self.colors = list( - map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), - self.colors)) - np.random.seed(10101) - np.random.shuffle(self.colors) - np.random.seed(None) - - self.__yolo_input_image_shape = K.placeholder(shape=(2,)) - self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes = yolo_eval(model.output, - self.__tiny_yolo_anchors, - len(self.numbers_to_names), - self.__yolo_input_image_shape, - score_threshold=self.__yolo_score, - iou_threshold=self.__yolo_iou) - + self.__model_collection.append(model) self.__modelLoaded = True def detectObjectsFromImage(self, input_image="", output_image_path="", input_type="file", output_type="file", extract_detected_objects=False, minimum_percentage_probability=50, - display_percentage_probability=True, display_object_name=True, thread_safe=False): + display_percentage_probability=True, display_object_name=True, + display_box=True, thread_safe=False, custom_objects=None): """ 'detectObjectsFromImage()' function is used to detect objects observable in the given image path: * input_image , which can be a filepath, image numpy array or image file stream @@ -318,226 +281,100 @@ def detectObjectsFromImage(self, input_image="", output_image_path="", input_typ raise ValueError("You must call the loadModel() function before making object detection.") elif (self.__modelLoaded == True): try: - if (self.__modelType == "retinanet"): - output_objects_array = [] - detected_objects_image_array = [] - - if (input_type == "file"): - image = read_image_bgr(input_image) - elif (input_type == "array"): - image = read_image_array(input_image) - elif (input_type == "stream"): - image = read_image_stream(input_image) - - detected_copy = image.copy() - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) - - detected_copy2 = image.copy() - detected_copy2 = cv2.cvtColor(detected_copy2, cv2.COLOR_BGR2RGB) - - image = preprocess_image(image) - image, scale = resize_image(image, min_side=self.__input_image_min, max_side=self.__input_image_max) - - model = self.__model_collection[0] - - if thread_safe == True: - with self.sess.graph.as_default(): - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) - else: - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) - - predicted_numbers = np.argmax(detections[0, :, 4:], axis=1) - scores = detections[0, np.arange(detections.shape[1]), 4 + predicted_numbers] - - detections[0, :, :4] /= scale - - min_probability = minimum_percentage_probability / 100 - counting = 0 - - for index, (label, score), in enumerate(zip(predicted_numbers, scores)): - if score < min_probability: - continue - - counting += 1 - - objects_dir = output_image_path + "-objects" - if (extract_detected_objects == True and output_type == "file"): - if (os.path.exists(objects_dir) == False): - os.mkdir(objects_dir) - color = label_color(label) - - detection_details = detections[0, index, :4].astype(int) - draw_box(detected_copy, detection_details, color=color) - - if (display_object_name == True and display_percentage_probability == True): - caption = "{} {:.3f}".format(self.numbers_to_names[label], (score * 100)) - draw_caption(detected_copy, detection_details, caption) - elif (display_object_name == True): - caption = "{} ".format(self.numbers_to_names[label]) - draw_caption(detected_copy, detection_details, caption) - elif (display_percentage_probability == True): - caption = " {:.3f}".format((score * 100)) - draw_caption(detected_copy, detection_details, caption) - - each_object_details = {} - each_object_details["name"] = self.numbers_to_names[label] - each_object_details["percentage_probability"] = score * 100 - each_object_details["box_points"] = detection_details.tolist() - - output_objects_array.append(each_object_details) - - if (extract_detected_objects == True): - splitted_copy = detected_copy2.copy()[detection_details[1]:detection_details[3], - detection_details[0]:detection_details[2]] - if (output_type == "file"): - splitted_image_path = os.path.join(objects_dir, - self.numbers_to_names[label] + "-" + str( - counting) + ".jpg") - pltimage.imsave(splitted_image_path, splitted_copy) - detected_objects_image_array.append(splitted_image_path) - elif (output_type == "array"): - detected_objects_image_array.append(splitted_copy) + model_detections = list() + detections = list() + image_copy = None - if (output_type == "file"): - pltimage.imsave(output_image_path, detected_copy) - - if (extract_detected_objects == True): - if (output_type == "file"): - return output_objects_array, detected_objects_image_array - elif (output_type == "array"): - return detected_copy, output_objects_array, detected_objects_image_array - - else: - if (output_type == "file"): - return output_objects_array - elif (output_type == "array"): - return detected_copy, output_objects_array - elif (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): + detected_objects_image_array = [] - output_objects_array = [] - detected_objects_image_array = [] + if (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): if (input_type == "file"): - image = Image.open(input_image) - input_image = read_image_bgr(input_image) + input_image = cv2.imread(input_image) elif (input_type == "array"): - image = Image.fromarray(np.uint8(input_image)) - input_image = read_image_array(input_image) - elif (input_type == "stream"): - image = Image.open(input_image) - input_image = read_image_stream(input_image) + input_image = np.array(input_image) detected_copy = input_image - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) + image_copy = input_image - detected_copy2 = input_image - detected_copy2 = cv2.cvtColor(detected_copy2, cv2.COLOR_BGR2RGB) - - new_image_size = (self.__yolo_model_image_size[0] - (self.__yolo_model_image_size[0] % 32), - self.__yolo_model_image_size[1] - (self.__yolo_model_image_size[1] % 32)) - boxed_image = letterbox_image(image, new_image_size) - image_data = np.array(boxed_image, dtype="float32") - - image_data /= 255. - image_data = np.expand_dims(image_data, 0) + image_h, image_w, _ = detected_copy.shape + detected_copy = preprocess_input(detected_copy, self.__yolo_model_image_size) model = self.__model_collection[0] + yolo_result = model.predict(detected_copy) + + min_probability = minimum_percentage_probability / 100 - if thread_safe == True: - with self.sess.graph.as_default(): - out_boxes, out_scores, out_classes = self.sess.run( - [self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes], - feed_dict={ - model.input: image_data, - self.__yolo_input_image_shape: [image.size[1], image.size[0]], - K.learning_phase(): 0 - }) - else: - out_boxes, out_scores, out_classes = self.sess.run( - [self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes], - feed_dict={ - model.input: image_data, - self.__yolo_input_image_shape: [image.size[1], image.size[0]], - K.learning_phase(): 0 - }) + model_detections = retrieve_yolo_detections(yolo_result, + self.__yolo_anchors, + min_probability, + self.__nms_thresh, + self.__yolo_model_image_size, + (image_w, image_h), + self.numbers_to_names) - min_probability = minimum_percentage_probability / 100 - counting = 0 + counting = 0 + objects_dir = output_image_path + "-objects" - for a, b in reversed(list(enumerate(out_classes))): - predicted_class = self.numbers_to_names[b] - box = out_boxes[a] - score = out_scores[a] - if score < min_probability: - continue + for detection in model_detections: + counting += 1 + label = detection["name"] + percentage_probability = detection["percentage_probability"] + box_points = detection["box_points"] - counting += 1 + if (custom_objects is not None): + if (custom_objects[label] != "valid"): + continue + + detections.append(detection) - objects_dir = output_image_path + "-objects" - if (extract_detected_objects == True and output_type == "file"): - if (os.path.exists(objects_dir) == False): - os.mkdir(objects_dir) + if display_object_name == False: + label = None - label = "{} {:.2f}".format(predicted_class, score) - - top, left, bottom, right = box - top = max(0, np.floor(top + 0.5).astype('int32')) - left = max(0, np.floor(left + 0.5).astype('int32')) - bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32')) - right = min(image.size[0], np.floor(right + 0.5).astype('int32')) - - try: - color = label_color(b) - except: - color = (255, 0, 0) - - detection_details = [left, top, right, bottom] - draw_box(detected_copy, detection_details, color=color) - - if (display_object_name == True and display_percentage_probability == True): - draw_caption(detected_copy, detection_details, label) - elif (display_object_name == True): - draw_caption(detected_copy, detection_details, predicted_class) - elif (display_percentage_probability == True): - draw_caption(detected_copy, detection_details, str(score * 100)) - - each_object_details = {} - each_object_details["name"] = predicted_class - each_object_details["percentage_probability"] = score * 100 - each_object_details["box_points"] = detection_details - - output_objects_array.append(each_object_details) - - if (extract_detected_objects == True): - splitted_copy = detected_copy2.copy()[detection_details[1]:detection_details[3], - detection_details[0]:detection_details[2]] - if (output_type == "file"): - splitted_image_path = os.path.join(objects_dir, - predicted_class + "-" + str( - counting) + ".jpg") - pltimage.imsave(splitted_image_path, splitted_copy) - detected_objects_image_array.append(splitted_image_path) - elif (output_type == "array"): - detected_objects_image_array.append(splitted_copy) + if display_percentage_probability == False: + percentage_probability = None - if (output_type == "file"): - pltimage.imsave(output_image_path, detected_copy) + + image_copy = draw_boxes(image_copy, + box_points, + display_box, + label, + percentage_probability, + self.__box_color) + + if (extract_detected_objects == True): + splitted_copy = image_copy.copy()[box_points[1]:box_points[3], + box_points[0]:box_points[2]] if (output_type == "file"): - return output_objects_array, detected_objects_image_array + if (os.path.exists(objects_dir) == False): + os.mkdir(objects_dir) + splitted_image_path = os.path.join(objects_dir, + detection["name"] + "-" + str( + counting) + ".jpg") + cv2.imwrite(splitted_image_path, splitted_copy) + detected_objects_image_array.append(splitted_image_path) elif (output_type == "array"): - return detected_copy, output_objects_array, detected_objects_image_array + detected_objects_image_array.append(splitted_copy) - else: - if (output_type == "file"): - return output_objects_array - elif (output_type == "array"): - return detected_copy, output_objects_array + + if (output_type == "file"): + cv2.imwrite(output_image_path, image_copy) + if (extract_detected_objects == True): + if (output_type == "file"): + return detections, detected_objects_image_array + elif (output_type == "array"): + return image_copy, detections, detected_objects_image_array + else: + if (output_type == "file"): + return detections + elif (output_type == "array"): + return image_copy, detections except: raise ValueError( @@ -613,311 +450,27 @@ def CustomObjects(self, person=False, bicycle=False, car=False, motorcycle=False return custom_objects_dict - def detectCustomObjectsFromImage(self, custom_objects=None, input_image="", output_image_path="", input_type="file", - output_type="file", extract_detected_objects=False, - minimum_percentage_probability=50, display_percentage_probability=True, - display_object_name=True, thread_safe=False): + def detectCustomObjectsFromImage(self, input_image="", output_image_path="", input_type="file", output_type="file", + extract_detected_objects=False, minimum_percentage_probability=50, + display_percentage_probability=True, display_object_name=True, + display_box=True, thread_safe=False, custom_objects=None): + + warnings.warn("'detectCustomObjectsFromImage()' function has been deprecated and will be removed in future versions of ImageAI. \n Kindly use 'detectObjectsFromImage()' ", + DeprecationWarning, stacklevel=2) + + return self.detectObjectsFromImage(input_image=input_image, + output_image_path=output_image_path, + input_type=input_type, + output_type=output_type, + extract_detected_objects=extract_detected_objects, + minimum_percentage_probability=minimum_percentage_probability, + display_percentage_probability=display_percentage_probability, + display_object_name=display_object_name, + display_box=display_box, + thread_safe=thread_safe, + custom_objects=custom_objects) + """ """ - 'detectCustomObjectsFromImage()' function is used to detect predefined objects observable in the given image path: - * custom_objects , an instance of the CustomObject class to filter which objects to detect - * input_image , which can be file to path, image numpy array or image file stream - * output_image_path , file path to the output image that will contain the detection boxes and label, if output_type="file" - * input_type (optional) , file path/numpy array/image file stream of the image. Acceptable values are "file", "array" and "stream" - * output_type (optional) , file path/numpy array/image file stream of the image. Acceptable values are "file" and "array" - * extract_detected_objects (optional, False by default) , option to save each object detected individually as an image and return an array of the objects' image path. - * minimum_percentage_probability (optional, 50 by default) , option to set the minimum percentage probability for nominating a detected object for output. - * display_percentage_probability (optional, True by default), option to show or hide the percentage probability of each object in the saved/returned detected image - * display_display_object_name (optional, True by default), option to show or hide the name of each object in the saved/returned detected image - * thread_safe (optional, False by default), enforce the loaded detection model works across all threads if set to true, made possible by forcing all Tensorflow inference to run on the default graph. - - The values returned by this function depends on the parameters parsed. The possible values returnable - are stated as below - - If extract_detected_objects = False or at its default value and output_type = 'file' or - at its default value, you must parse in the 'output_image_path' as a string to the path you want - the detected image to be saved. Then the function will return: - 1. an array of dictionaries, with each dictionary corresponding to the objects - detected in the image. Each dictionary contains the following property: - * name (string) - * percentage_probability (float) - * box_points (list of x1,y1,x2 and y2 coordinates) - - - If extract_detected_objects = False or at its default value and output_type = 'array' , - Then the function will return: - - 1. a numpy array of the detected image - 2. an array of dictionaries, with each dictionary corresponding to the objects - detected in the image. Each dictionary contains the following property: - * name (string) - * percentage_probability (float) - * box_points (list of x1,y1,x2 and y2 coordinates) - - - If extract_detected_objects = True and output_type = 'file' or - at its default value, you must parse in the 'output_image_path' as a string to the path you want - the detected image to be saved. Then the function will return: - 1. an array of dictionaries, with each dictionary corresponding to the objects - detected in the image. Each dictionary contains the following property: - * name (string) - * percentage_probability (float) - * box_points (list of x1,y1,x2 and y2 coordinates) - 2. an array of string paths to the image of each object extracted from the image - - - If extract_detected_objects = True and output_type = 'array', the the function will return: - 1. a numpy array of the detected image - 2. an array of dictionaries, with each dictionary corresponding to the objects - detected in the image. Each dictionary contains the following property: - * name (string) - * percentage_probability (float) - * box_points (list of x1,y1,x2 and y2 coordinates) - 3. an array of numpy arrays of each object detected in the image - - - :param input_image: - :param output_image_path: - :param input_type: - :param output_type: - :param extract_detected_objects: - :param minimum_percentage_probability: - :return output_objects_array: - :param display_percentage_probability: - :param display_object_name - :return detected_copy: - :return detected_detected_objects_image_array: - """ - - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making object detection.") - elif (self.__modelLoaded == True): - try: - if (self.__modelType == "retinanet"): - output_objects_array = [] - detected_objects_image_array = [] - - if (input_type == "file"): - image = read_image_bgr(input_image) - elif (input_type == "array"): - image = read_image_array(input_image) - elif (input_type == "stream"): - image = read_image_stream(input_image) - - detected_copy = image.copy() - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) - - detected_copy2 = image.copy() - detected_copy2 = cv2.cvtColor(detected_copy2, cv2.COLOR_BGR2RGB) - - image = preprocess_image(image) - image, scale = resize_image(image, min_side=self.__input_image_min, max_side=self.__input_image_max) - - model = self.__model_collection[0] - - if thread_safe == True: - with self.sess.graph.as_default(): - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) - else: - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) - - predicted_numbers = np.argmax(detections[0, :, 4:], axis=1) - scores = detections[0, np.arange(detections.shape[1]), 4 + predicted_numbers] - - detections[0, :, :4] /= scale - - min_probability = minimum_percentage_probability / 100 - counting = 0 - - for index, (label, score), in enumerate(zip(predicted_numbers, scores)): - if score < min_probability: - continue - - if (custom_objects != None): - check_name = self.numbers_to_names[label] - if (custom_objects[check_name] == "invalid"): - continue - - counting += 1 - - objects_dir = output_image_path + "-objects" - if (extract_detected_objects == True and output_type == "file"): - if (os.path.exists(objects_dir) == False): - os.mkdir(objects_dir) - - color = label_color(label) - - detection_details = detections[0, index, :4].astype(int) - draw_box(detected_copy, detection_details, color=color) - - if (display_object_name == True and display_percentage_probability == True): - caption = "{} {:.3f}".format(self.numbers_to_names[label], (score * 100)) - draw_caption(detected_copy, detection_details, caption) - elif (display_object_name == True): - caption = "{} ".format(self.numbers_to_names[label]) - draw_caption(detected_copy, detection_details, caption) - elif (display_percentage_probability == True): - caption = " {:.3f}".format((score * 100)) - draw_caption(detected_copy, detection_details, caption) - - each_object_details = {} - each_object_details["name"] = self.numbers_to_names[label] - each_object_details["percentage_probability"] = score * 100 - each_object_details["box_points"] = detection_details.tolist() - - output_objects_array.append(each_object_details) - - if (extract_detected_objects == True): - splitted_copy = detected_copy2.copy()[detection_details[1]:detection_details[3], - detection_details[0]:detection_details[2]] - if (output_type == "file"): - splitted_image_path = os.path.join(objects_dir, - self.numbers_to_names[label] + "-" + str( - counting) + ".jpg") - pltimage.imsave(splitted_image_path, splitted_copy) - detected_objects_image_array.append(splitted_image_path) - elif (output_type == "array"): - detected_objects_image_array.append(splitted_copy) - - if (output_type == "file"): - pltimage.imsave(output_image_path, detected_copy) - - if (extract_detected_objects == True): - if (output_type == "file"): - return output_objects_array, detected_objects_image_array - elif (output_type == "array"): - return detected_copy, output_objects_array, detected_objects_image_array - - else: - if (output_type == "file"): - return output_objects_array - elif (output_type == "array"): - return detected_copy, output_objects_array - elif (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): - output_objects_array = [] - detected_objects_image_array = [] - - if (input_type == "file"): - image = Image.open(input_image) - input_image = read_image_bgr(input_image) - elif (input_type == "array"): - image = Image.fromarray(np.uint8(input_image)) - input_image = read_image_array(input_image) - elif (input_type == "stream"): - image = Image.open(input_image) - input_image = read_image_stream(input_image) - - detected_copy = input_image - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) - - detected_copy2 = input_image - detected_copy2 = cv2.cvtColor(detected_copy2, cv2.COLOR_BGR2RGB) - - new_image_size = (self.__yolo_model_image_size[0] - (self.__yolo_model_image_size[0] % 32), - self.__yolo_model_image_size[1] - (self.__yolo_model_image_size[1] % 32)) - boxed_image = letterbox_image(image, new_image_size) - image_data = np.array(boxed_image, dtype="float32") - - image_data /= 255. - image_data = np.expand_dims(image_data, 0) - - model = self.__model_collection[0] - - if thread_safe == True: - with self.sess.graph.as_default(): - out_boxes, out_scores, out_classes = self.sess.run( - [self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes], - feed_dict={ - model.input: image_data, - self.__yolo_input_image_shape: [image.size[1], image.size[0]], - K.learning_phase(): 0 - }) - else: - out_boxes, out_scores, out_classes = self.sess.run( - [self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes], - feed_dict={ - model.input: image_data, - self.__yolo_input_image_shape: [image.size[1], image.size[0]], - K.learning_phase(): 0 - }) - - min_probability = minimum_percentage_probability / 100 - counting = 0 - - for a, b in reversed(list(enumerate(out_classes))): - predicted_class = self.numbers_to_names[b] - box = out_boxes[a] - score = out_scores[a] - - if score < min_probability: - continue - - if (custom_objects != None): - if (custom_objects[predicted_class] == "invalid"): - continue - - counting += 1 - - objects_dir = output_image_path + "-objects" - if (extract_detected_objects == True and output_type == "file"): - if (os.path.exists(objects_dir) == False): - os.mkdir(objects_dir) - - label = "{} {:.2f}".format(predicted_class, score) - - top, left, bottom, right = box - top = max(0, np.floor(top + 0.5).astype('int32')) - left = max(0, np.floor(left + 0.5).astype('int32')) - bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32')) - right = min(image.size[0], np.floor(right + 0.5).astype('int32')) - - try: - color = label_color(b) - except: - color = (255, 0, 0) - - detection_details = [left, top, right, bottom] - draw_box(detected_copy, detection_details, color=color) - - if (display_object_name == True and display_percentage_probability == True): - draw_caption(detected_copy, detection_details, label) - elif (display_object_name == True): - draw_caption(detected_copy, detection_details, predicted_class) - elif (display_percentage_probability == True): - draw_caption(detected_copy, detection_details, str(score * 100)) - - each_object_details = {} - each_object_details["name"] = predicted_class - each_object_details["percentage_probability"] = score * 100 - each_object_details["box_points"] = detection_details - - output_objects_array.append(each_object_details) - - if (extract_detected_objects == True): - splitted_copy = detected_copy2.copy()[detection_details[1]:detection_details[3], - detection_details[0]:detection_details[2]] - if (output_type == "file"): - splitted_image_path = os.path.join(objects_dir, - predicted_class + "-" + str( - counting) + ".jpg") - pltimage.imsave(splitted_image_path, splitted_copy) - detected_objects_image_array.append(splitted_image_path) - elif (output_type == "array"): - detected_objects_image_array.append(splitted_copy) - - if (output_type == "file"): - pltimage.imsave(output_image_path, detected_copy) - - if (extract_detected_objects == True): - if (output_type == "file"): - return output_objects_array, detected_objects_image_array - elif (output_type == "array"): - return detected_copy, output_objects_array, detected_objects_image_array - - else: - if (output_type == "file"): - return output_objects_array - elif (output_type == "array"): - return detected_copy, output_objects_array - except: - raise ValueError( - "Ensure you specified correct input image, input type, output type and/or output image path ") - class VideoObjectDetection: """ @@ -973,20 +526,6 @@ def __init__(self): 78: 'hair dryer', 79: 'toothbrush'} - # Unique instance variables for YOLOv3 model - self.__yolo_iou = 0.45 - self.__yolo_score = 0.1 - self.__yolo_anchors = np.array( - [[10., 13.], [16., 30.], [33., 23.], [30., 61.], [62., 45.], [59., 119.], [116., 90.], [156., 198.], - [373., 326.]]) - self.__yolo_model_image_size = (416, 416) - self.__yolo_boxes, self.__yolo_scores, self.__yolo_classes = "", "", "" - self.sess = K.get_session() - - # Unique instance variables for TinyYOLOv3. - self.__tiny_yolo_anchors = np.array( - [[10., 14.], [23., 27.], [37., 58.], [81., 82.], [135., 169.], [344., 319.]]) - def setModelTypeAsRetinaNet(self): """ 'setModelTypeAsRetinaNet()' is used to set the model type to the RetinaNet model @@ -1055,9 +594,10 @@ def loadModel(self, detection_speed="normal"): def detectObjectsFromVideo(self, input_file_path="", camera_input=None, output_file_path="", frames_per_second=20, frame_detection_interval=1, minimum_percentage_probability=50, log_progress=False, - display_percentage_probability=True, display_object_name=True, save_detected_video=True, + display_percentage_probability=True, display_object_name=True, display_box=True, save_detected_video=True, per_frame_function=None, per_second_function=None, per_minute_function=None, - video_complete_function=None, return_detected_frame=False, detection_timeout = None, thread_safe=False): + video_complete_function=None, return_detected_frame=False, detection_timeout = None, + thread_safe=False, custom_objects=None): """ 'detectObjectsFromVideo()' function is used to detect objects observable in the given video path or a camera input: @@ -1165,14 +705,10 @@ def detectObjectsFromVideo(self, input_file_path="", camera_input=None, output_f (frame_width, frame_height)) counting = 0 - predicted_numbers = None - scores = None - detections = None detection_timeout_count = 0 video_frames_count = 0 - while (input_video.isOpened()): ret, frame = input_video.read() @@ -1203,7 +739,9 @@ def detectObjectsFromVideo(self, input_file_path="", camera_input=None, output_f input_image=frame, input_type="array", output_type="array", minimum_percentage_probability=minimum_percentage_probability, display_percentage_probability=display_percentage_probability, - display_object_name=display_object_name) + display_object_name=display_object_name, + display_box=display_box, + custom_objects=custom_objects) except: None @@ -1219,8 +757,7 @@ def detectObjectsFromVideo(self, input_file_path="", camera_input=None, output_f output_frames_count_dict[counting] = output_objects_count - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) - + if (save_detected_video == True): output_video.write(detected_copy) @@ -1408,313 +945,30 @@ def CustomObjects(self, person=False, bicycle=False, car=False, motorcycle=False return custom_objects_dict - def detectCustomObjectsFromVideo(self, custom_objects=None, input_file_path="", camera_input=None, - output_file_path="", frames_per_second=20, frame_detection_interval=1, - minimum_percentage_probability=50, log_progress=False, - display_percentage_probability=True, display_object_name=True, - save_detected_video=True, per_frame_function=None, per_second_function=None, - per_minute_function=None, video_complete_function=None, - return_detected_frame=False, detection_timeout = None, thread_safe=False): - - """ - 'detectObjectsFromVideo()' function is used to detect specific object(s) observable in the given video path or given camera live stream input: - * custom_objects , which is the dictionary returned by the 'CustomObjects' function - * input_file_path , which is the file path to the input video. It is required only if 'camera_input' is not set - * camera_input , allows you to parse in camera input for live video detections - * output_file_path , which is the path to the output video. It is required only if 'save_detected_video' is not set to False - * frames_per_second , which is the number of frames to be used in the output video - * frame_detection_interval (optional, 1 by default) , which is the intervals of frames that will be detected. - * minimum_percentage_probability (optional, 50 by default) , option to set the minimum percentage probability for nominating a detected object for output. - * log_progress (optional) , which states if the progress of the frame processed is to be logged to console - * display_percentage_probability (optional), can be used to hide or show probability scores on the detected video frames - * display_object_name (optional), can be used to show or hide object names on the detected video frames - * save_save_detected_video (optional, True by default), can be set to or not to save the detected video - * per_frame_function (optional), this parameter allows you to parse in a function you will want to execute after - each frame of the video is detected. If this parameter is set to a function, after every video - frame is detected, the function will be executed with the following values parsed into it: - -- position number of the frame - -- an array of dictinaries, with each dictinary corresponding to each object detected. - Each dictionary contains 'name', 'percentage_probability' and 'box_points' - -- a dictionay with with keys being the name of each unique objects and value - are the number of instances of the object present - -- If return_detected_frame is set to True, the numpy array of the detected frame will be parsed - as the fourth value into the function - - * per_second_function (optional), this parameter allows you to parse in a function you will want to execute after - each second of the video is detected. If this parameter is set to a function, after every second of a video - is detected, the function will be executed with the following values parsed into it: - -- position number of the second - -- an array of dictionaries whose keys are position number of each frame present in the last second , and the value for each key is the array for each frame that contains the dictionaries for each object detected in the frame - - -- an array of dictionaries, with each dictionary corresponding to each frame in the past second, and the keys of each dictionary are the name of the number of unique objects detected in each frame, and the key values are the number of instances of the objects found in the frame - - -- a dictionary with its keys being the name of each unique object detected throughout the past second, and the key values are the average number of instances of the object found in all the frames contained in the past second - - -- If return_detected_frame is set to True, the numpy array of the detected frame will be parsed - as the fifth value into the function - - * per_minute_function (optional), this parameter allows you to parse in a function you will want to execute after - each minute of the video is detected. If this parameter is set to a function, after every minute of a video - is detected, the function will be executed with the following values parsed into it: - -- position number of the minute - -- an array of dictionaries whose keys are position number of each frame present in the last minute , and the value for each key is the array for each frame that contains the dictionaries for each object detected in the frame - - -- an array of dictionaries, with each dictionary corresponding to each frame in the past minute, and the keys of each dictionary are the name of the number of unique objects detected in each frame, and the key values are the number of instances of the objects found in the frame - - -- a dictionary with its keys being the name of each unique object detected throughout the past minute, and the key values are the average number of instances of the object found in all the frames contained in the past minute - - -- If return_detected_frame is set to True, the numpy array of the detected frame will be parsed - as the fifth value into the function - - * video_complete_function (optional), this parameter allows you to parse in a function you will want to execute after - all of the video frames have been detected. If this parameter is set to a function, after all of frames of a video - is detected, the function will be executed with the following values parsed into it: - -- an array of dictionaries whose keys are position number of each frame present in the entire video , and the value for each key is the array for each frame that contains the dictionaries for each object detected in the frame - - -- an array of dictionaries, with each dictionary corresponding to each frame in the entire video, and the keys of each dictionary are the name of the number of unique objects detected in each frame, and the key values are the number of instances of the objects found in the frame - - -- a dictionary with its keys being the name of each unique object detected throughout the entire video, and the key values are the average number of instances of the object found in all the frames contained in the entire video - - * return_detected_frame (optionally, False by default), option to obtain the return the last detected video frame into the per_per_frame_function, - per_per_second_function or per_per_minute_function - - * detection_timeout (optionally, None by default), option to state the number of seconds of a video that should be detected after which the detection function stop processing the video - * thread_safe (optional, False by default), enforce the loaded detection model works across all threads if set to true, made possible by forcing all Tensorflow inference to run on the default graph. - - - - - - - :param custom_objects: - :param input_file_path: - :param camera_input - :param output_file_path: - :param save_detected_video: - :param frames_per_second: - :param frame_detection_interval: - :param minimum_percentage_probability: - :param log_progress: - :param display_percentage_probability: - :param display_object_name: - :param per_frame_function: - :param per_second_function: - :param per_minute_function: - :param video_complete_function: - :param return_detected_frame: - :param thread_safe: - :return output_video_filepath: - :return counting: - :return output_objects_array: - :return output_objects_count: - :return detected_copy: - :return this_second_output_object_array: - :return this_second_counting_array: - :return this_second_counting: - :return this_minute_output_object_array: - :return this_minute_counting_array: - :return this_minute_counting: - :return this_video_output_object_array: - :return this_video_counting_array: - :return this_video_counting: - """ - - if (input_file_path == "" and camera_input == None): - raise ValueError( - "You must set 'input_file_path' to a valid video file, or set 'camera_input' to a valid camera") - elif (save_detected_video == True and output_file_path == ""): - raise ValueError( - "You must set 'output_video_filepath' to a valid video file name, in which the detected video will be saved. If you don't intend to save the detected video, set 'save_detected_video=False'") - - else: - try: - output_frames_dict = {} - output_frames_count_dict = {} - - input_video = cv2.VideoCapture(input_file_path) - if (camera_input != None): - input_video = camera_input - - output_video_filepath = output_file_path + '.avi' - - frame_width = int(input_video.get(3)) - frame_height = int(input_video.get(4)) - output_video = cv2.VideoWriter(output_video_filepath, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'), - frames_per_second, - (frame_width, frame_height)) - - counting = 0 - predicted_numbers = None - scores = None - detections = None - - detection_timeout_count = 0 - video_frames_count = 0 - - while (input_video.isOpened()): - ret, frame = input_video.read() - - if (ret == True): - - video_frames_count += 1 - if (detection_timeout != None): - if ((video_frames_count % frames_per_second) == 0): - detection_timeout_count += 1 - - if (detection_timeout_count >= detection_timeout): - break - - output_objects_array = [] - - counting += 1 - - if (log_progress == True): - print("Processing Frame : ", str(counting)) - - detected_copy = frame.copy() - - check_frame_interval = counting % frame_detection_interval - - if (counting == 1 or check_frame_interval == 0): - try: - detected_copy, output_objects_array = self.__detector.detectCustomObjectsFromImage( - input_image=frame, input_type="array", output_type="array", - minimum_percentage_probability=minimum_percentage_probability, - display_percentage_probability=display_percentage_probability, - display_object_name=display_object_name, - custom_objects=custom_objects) - except: - None - - output_frames_dict[counting] = output_objects_array - - output_objects_count = {} - for eachItem in output_objects_array: - eachItemName = eachItem["name"] - try: - output_objects_count[eachItemName] = output_objects_count[eachItemName] + 1 - except: - output_objects_count[eachItemName] = 1 - - output_frames_count_dict[counting] = output_objects_count - - detected_copy = cv2.cvtColor(detected_copy, cv2.COLOR_BGR2RGB) - - if (save_detected_video == True): - output_video.write(detected_copy) - - if (counting == 1 or check_frame_interval == 0): - if (per_frame_function != None): - if (return_detected_frame == True): - per_frame_function(counting, output_objects_array, output_objects_count, - detected_copy) - elif (return_detected_frame == False): - per_frame_function(counting, output_objects_array, output_objects_count) - - if (per_second_function != None): - if (counting != 1 and (counting % frames_per_second) == 0): - - this_second_output_object_array = [] - this_second_counting_array = [] - this_second_counting = {} - - for aa in range(counting): - if (aa >= (counting - frames_per_second)): - this_second_output_object_array.append(output_frames_dict[aa + 1]) - this_second_counting_array.append(output_frames_count_dict[aa + 1]) - - for eachCountingDict in this_second_counting_array: - for eachItem in eachCountingDict: - try: - this_second_counting[eachItem] = this_second_counting[eachItem] + \ - eachCountingDict[eachItem] - except: - this_second_counting[eachItem] = eachCountingDict[eachItem] - - for eachCountingItem in this_second_counting: - this_second_counting[eachCountingItem] = int(this_second_counting[eachCountingItem] / frames_per_second) - - if (return_detected_frame == True): - per_second_function(int(counting / frames_per_second), - this_second_output_object_array, this_second_counting_array, - this_second_counting, detected_copy) - - elif (return_detected_frame == False): - per_second_function(int(counting / frames_per_second), - this_second_output_object_array, this_second_counting_array, - this_second_counting) - - if (per_minute_function != None): - - if (counting != 1 and (counting % (frames_per_second * 60)) == 0): - - this_minute_output_object_array = [] - this_minute_counting_array = [] - this_minute_counting = {} - - for aa in range(counting): - if (aa >= (counting - (frames_per_second * 60))): - this_minute_output_object_array.append(output_frames_dict[aa + 1]) - this_minute_counting_array.append(output_frames_count_dict[aa + 1]) - - for eachCountingDict in this_minute_counting_array: - for eachItem in eachCountingDict: - try: - this_minute_counting[eachItem] = this_minute_counting[eachItem] + \ - eachCountingDict[eachItem] - except: - this_minute_counting[eachItem] = eachCountingDict[eachItem] - - for eachCountingItem in this_minute_counting: - this_minute_counting[eachCountingItem] = int(this_minute_counting[eachCountingItem] / (frames_per_second * 60)) - - if (return_detected_frame == True): - per_minute_function(int(counting / (frames_per_second * 60)), - this_minute_output_object_array, this_minute_counting_array, - this_minute_counting, detected_copy) - - elif (return_detected_frame == False): - per_minute_function(int(counting / (frames_per_second * 60)), - this_minute_output_object_array, this_minute_counting_array, - this_minute_counting) - - - else: - break - - if (video_complete_function != None): - - this_video_output_object_array = [] - this_video_counting_array = [] - this_video_counting = {} - - for aa in range(counting): - this_video_output_object_array.append(output_frames_dict[aa + 1]) - this_video_counting_array.append(output_frames_count_dict[aa + 1]) - - for eachCountingDict in this_video_counting_array: - for eachItem in eachCountingDict: - try: - this_video_counting[eachItem] = this_video_counting[eachItem] + \ - eachCountingDict[eachItem] - except: - this_video_counting[eachItem] = eachCountingDict[eachItem] - - for eachCountingItem in this_video_counting: - this_video_counting[eachCountingItem] = int(this_video_counting[eachCountingItem] / counting) - - video_complete_function(this_video_output_object_array, this_video_counting_array, - this_video_counting) - - input_video.release() - output_video.release() - - if (save_detected_video == True): - return output_video_filepath - - - except: - raise ValueError( - "An error occured. It may be that your input video is invalid. Ensure you specified a proper string value for 'output_file_path' is 'save_detected_video' is not False. " - "Also ensure your per_frame, per_second, per_minute or video_complete_analysis function is properly configured to receive the right parameters. ") - + def detectCustomObjectsFromVideo(self, input_file_path="", camera_input=None, output_file_path="", frames_per_second=20, + frame_detection_interval=1, minimum_percentage_probability=50, log_progress=False, + display_percentage_probability=True, display_object_name=True, display_box=True, save_detected_video=True, + per_frame_function=None, per_second_function=None, per_minute_function=None, + video_complete_function=None, return_detected_frame=False, detection_timeout = None, + thread_safe=False, custom_objects=None): + + + return self.detectObjectsFromVideo(input_file_path=input_file_path, + camera_input=camera_input, + output_file_path=output_file_path, + frames_per_second=frames_per_second, + frame_detection_interval=frame_detection_interval, + minimum_percentage_probability=minimum_percentage_probability, + log_progress=log_progress, + display_percentage_probability=display_percentage_probability, + display_object_name=display_object_name, + display_box=display_box, + save_detected_video=save_detected_video, + per_frame_function=per_frame_function, + per_second_function=per_second_function, + per_minute_function=per_minute_function, + video_complete_function=video_complete_function, + return_detected_frame=return_detected_frame, + detection_timeout = detection_timeout, + thread_safe=thread_safe, + custom_objects=custom_objects) \ No newline at end of file From e57a2f670e38a8fd7f24f62fd9f53aa56ad29f04 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Thu, 22 Oct 2020 22:34:19 +0100 Subject: [PATCH 04/12] removed redundant YOLO files --- imageai/Detection/YOLOv3/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 176 -> 0 bytes .../YOLOv3/__pycache__/models.cpython-35.pyc | Bin 4398 -> 0 bytes .../YOLOv3/__pycache__/utils.cpython-35.pyc | Bin 3962 -> 0 bytes imageai/Detection/YOLOv3/models.py | 106 ---------- imageai/Detection/YOLOv3/utils.py | 187 ------------------ 6 files changed, 293 deletions(-) delete mode 100644 imageai/Detection/YOLOv3/__init__.py delete mode 100644 imageai/Detection/YOLOv3/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/YOLOv3/__pycache__/models.cpython-35.pyc delete mode 100644 imageai/Detection/YOLOv3/__pycache__/utils.cpython-35.pyc delete mode 100644 imageai/Detection/YOLOv3/models.py delete mode 100644 imageai/Detection/YOLOv3/utils.py diff --git a/imageai/Detection/YOLOv3/__init__.py b/imageai/Detection/YOLOv3/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/imageai/Detection/YOLOv3/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/YOLOv3/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 4d1fadf3ad793de49860b5b01b2e9d9f297aeff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmWgV<>k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuUKcRn9$5dZ)H diff --git a/imageai/Detection/YOLOv3/__pycache__/models.cpython-35.pyc b/imageai/Detection/YOLOv3/__pycache__/models.cpython-35.pyc deleted file mode 100644 index 9d9b8384dd3ff2b1846d882bf71538fe769d68a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4398 zcmd5rjWlJz1M52rkLXhzWi_y-Ins8=z z)3c;Zg9HLdzaXb1*8l9)vNXT z!reQ6>h8CR{!N)*5%qiclDb9Y;h#kzQDl*0k;QGBLYthN$WYIdQ;>R&!XiZ_a>{00 zp27-63*;4;oF&sPQrMzsnVeU=m zqBc2g(=JoELeVNYs}x-)=Q>$_o4g9Ww8*7I57Ae9I^1`oVQ5-aDD~ZuALGMx zl}EmNboy)m(Ni>9U$~?G^SyzJ+%Wjo9R-6Jy>__Mv323`!1F_0co+|lM~DA?{hxn6 zes=G(F(4zIPu$4=vR~rO;#bC3$9ErJ@->Q)fO-Z)*>q-;m!q>hd3ibmjs-dcvPC*8 z(n~6y5!>^;L?a7LIx9_^&?=EvMyHi_qO#wq?CH`$5RQD6=<1QLVn6I9!MDCHB_kDh zexgf5V+!4xh?ReM9J&gFlrA49e(zb}Cc5B;!{_edU;o0te|*--sXVr>s$49f=#+IK z;KS>3|G67uokX=*2!AHE%*8b>R#50t?2ldy)X{5F>u7)X_5Dw}PZR91`*@J}$>x*L zJ@DffZuh~Ue;oPoDCvIwu=_A_4}JWjS9N^j&@)m$b;XFs*^l2o(LifS%p3-^#l4hB1u=XM}a_5Dq7(MC4OH)w} z62r47~)GJP#r|It3=?xH;Ja6Vss0vfRPtM_taNC-i^eWy3NYW zyqU}GVlwaqNlCavawrsVFzLX;w>=f{y!tsDA*b%&FIP#@U;6En6 z9Q_^;N?`E~XVa>{ou`*MtBX;2w(khuAXZKTZxWV36Bt>*7xi&B?byBGcQckQE6zS7cm+;f zgpc6jHk`nCstO8SG``R&%O-S{$=Z$k&j*}JaHEec;<^fWe`=GOxQTx=2AMS9|ViU1Yh?U7OcMii>rT+`e5G z9tho=^Sa1;p^J?7?Yg)>pt~`zyD7R`mvrZ8lF1Ib?}{!&pkvtTN22>)MmG(;40m(t z4&5J5b+aO~rQ27D%!+qJgus_OfWx%K#DMwU2P{@fyc2NHk0HiLo$0JVOtRn^F<_fh zVwlhY0^RHj$HEL|Uw35&h%PPufh-PwfoDLDSjBu2u{xUWoA2UzRZf0E>;;kV$ zw#kxasjm!tpGSAN;DD8=me7}bT&`>NF$5Q`>ukQ2t-mQJ`+plVW1smv@`v<)|Cpko zE+J1&MpQ~-LUo;zq0yhQh$+V0e?RBirzo5>-Te3>H8n;-eA-L-=?O+BAEF@KiCgoZ zJ#DXpuQr*p>^4Sh+Ug$0&$01_t{fc4{Sh1`QJkIX0{9e96+0g41w1~JfjS5WFSI>6 z&;>lp+(dEuuB~6`nl!f!H=8$@oSTy`lX9nrceb))w_Wc9^zC~+7Z0TqdA=s{baQUZ zjf=AnTzTL*{4{iv?aa$CQ8g^+Om<@)TjpNjYgsaCC3o6TDLe~4cjl>h($ diff --git a/imageai/Detection/YOLOv3/__pycache__/utils.cpython-35.pyc b/imageai/Detection/YOLOv3/__pycache__/utils.cpython-35.pyc deleted file mode 100644 index bdadb400c3c7d2bc9c15e51842fde820500790c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3962 zcmaJ^ZI2^I5w4!G$M5#O@A@`ymjF5WfD^g{i9|q-yAxqU*o$%rFP0-~&NF`5wP#Gv zIO`3zgtV7Gz(@W85`T#Pgp?1+e}OOXRQ0Uamjh;J+TGRF)z#HiPt_jQYUO)(|J*s- zCi;pto;>=G@FjCJ0bWI8qPCL0Lt}>~F12%{c+RD9o+ci(J&bb{J&8SC4`kJ*rH%t7%;2od245d-RZQYOyPy!I}f7(-TcYK zAB4Z?v5NdO@D=er!IwOCh*F}3qANwqJf(`x9BJK*2*LJUKuw1RdAf3F=?PDc7Q_pg z!KE)9YAy>hu_?$Sd&UKhmZRpCQaIIG>LADRFyv`PO*>(!%i{kP25uIHz2*4m*k##9 zR??cfEQzoJElRW~(!h}umxWlQh3#NU4#Y>a6o)A=eI_*161G@CYmW{yd%%jYTv?cL z5G--n9(|Yu_JQ<6p2t3R67x0s(xoO-H#R+9Rzy(cS`aoVZVK{fFB9a-E)Z1z?}DnY zY6N%kHUuqeuTJBVXmh$w3pi$p&K=SZ9Ow@#ETC1D`Z;=Tdy5(^>N0LMPFD)5_)Mz&@T1-aj!d$lZ+`_(XMBmF1AZz-7XLGD9F}$(!Xrm-?_>0 z*my7Ed3Szke=mln&SPlY9|d953r$1DtV4II;}^+aNxuoR{Hy)s-Oi^;sFTjGW2krM z+1T%g(S_f68uw-h{N0;b)JT4*h@!o)cO6h51)Phurq_% zlKaz3Q@V`Dad#N{KpDjTx6zPOb?!M$^+RR9eRb@3>aKgDPSkC6+o`H8rN0Hf9^W57 zReWps_z2g`@qrjbix)oTiq|iJ4Hg*eL}5JvY#=&APylDpL4W}QfIkR1OTm=;+LWaL zlxzpmvCySstlYx~Kw^SNM*yN@bdP~VmU0Xj!4`S#T+D}Qg~no55c-0MtL!GKRKnd%F4U^K=d@f}?L zRgOA2&RV#AT(?aZe7SH27#7yXEMVWEGYoN&z)WN?AU}K!*9{M0hrm@^4&0ddRmK6( z15=sZ`FSgUDtDqwP-Ej3auOV(w!M!U=35ng$ZSX4G`V5(>$kbNxsuyFyu-~sG^Wh9 zO<=m$t-3E(6>E0=2)H9$KgFt~j)oAi$7)|4!Kg<{e;v7!k z!%A^qEc?f-29V6hVI!HCk^eE;QU0b0a?lq%c@k5nk=W^Gw#JJU0*zz1E`vY(g?`J)6W)QFM$Nz*s4UNe2+Eo$F$z~YhjTR~ zRl+U+0ja@w)t2bGE$xo9yEOl&=q;7S)6f1+%RNeqlHcJA1e-I1r7-n_i1rJ3siwPwnta4S{{n7C|59bD1MF|aAr`zX(^BZ3X=B$S0RW6 z=(D#xqQ$ltYu{RKM+OIWuq%TjYsQb&wHdcJEVeCDVaA;e;?6a3cZ0agJArMnOMMri z)i~82d~qcWd*zDri;XntaV=q^kI`Ut2aU=3^HGuk-zZ8SJ}|XWJnN=It{&oX(5h>O z4;z$R@&X^ZY>@Ck4KbafIu@MN97D!Q`=n-NbtisuZt{YbW;cqNfY_W(rx2bX zvl?&Ur^8U2QXGXzcRV@|Tg`S=!Ze&C%F+-V)lCgh@ z)Zebyazpg6*ufB?zsG!P-1e-Xc7=a4G%z_0D+F}f<=uk$|_s=bdBn@ zD>BIBZLJ5H&~_=BB;8RIgmY8KmVF)aZBY?^?vH;7^^!+us905>s5gP^jO}IMeNAlx zp!QKt>a~Wgz*mk4#1J|fJ?s*89lBg`Qbv5T|=EW8oU%K2YnRBkSkTd zl`EN}L}$PQsDjN}TtuYSMG}P=sGC#wzgv$m}4OLZ#sQb1Y&MI#!{TonGpqFj&+aJdl`o|!d z{CTMTMDs;SoEi6{lMhAdjjYJ&NG?>wY^cHIu9m8y_>)N-%*Nr*IM782l%39&+jygT MtJ-jts~UIz0~ir1761SM diff --git a/imageai/Detection/YOLOv3/models.py b/imageai/Detection/YOLOv3/models.py deleted file mode 100644 index 6d12124e..00000000 --- a/imageai/Detection/YOLOv3/models.py +++ /dev/null @@ -1,106 +0,0 @@ -from functools import wraps - -import numpy as np -import tensorflow as tf -from keras import backend as K -from keras.layers import Conv2D, MaxPool2D, Add, ZeroPadding2D, UpSampling2D, Concatenate -from keras.layers.advanced_activations import LeakyReLU -from keras.layers.normalization import BatchNormalization -from keras.regularizers import l2 -from keras.models import Model, Input - - - -def NetworkConv2D_BN_Leaky(input, channels, kernel_size, kernel_regularizer = l2(5e-4), strides=(1,1), padding="same", use_bias=False): - - network = Conv2D( filters=channels, kernel_size=kernel_size, strides=strides, padding=padding, kernel_regularizer=kernel_regularizer, use_bias=use_bias)(input) - network = BatchNormalization()(network) - network = LeakyReLU(alpha=0.1)(network) - return network - -def residual_block(input, channels, num_blocks): - network = ZeroPadding2D(((1,0), (1,0)))(input) - network = NetworkConv2D_BN_Leaky(input=network,channels=channels, kernel_size=(3,3), strides=(2,2), padding="valid") - - for blocks in range(num_blocks): - network_1 = NetworkConv2D_BN_Leaky(input=network, channels= channels // 2, kernel_size=(1,1)) - network_1 = NetworkConv2D_BN_Leaky(input=network_1,channels= channels, kernel_size=(3,3)) - - network = Add()([network, network_1]) - return network - -def darknet(input): - network = NetworkConv2D_BN_Leaky(input=input, channels=32, kernel_size=(3,3)) - network = residual_block(input=network, channels=64, num_blocks=1) - network = residual_block(input=network, channels=128, num_blocks=2) - network = residual_block(input=network, channels=256, num_blocks=8) - network = residual_block(input=network, channels=512, num_blocks=8) - network = residual_block(input=network, channels=1024, num_blocks=4) - - - return network - -def last_layers(input, channels_in, channels_out, layer_name=""): - - - - network = NetworkConv2D_BN_Leaky( input=input, channels=channels_in, kernel_size=(1,1)) - network = NetworkConv2D_BN_Leaky(input=network, channels= (channels_in * 2) , kernel_size=(3, 3)) - network = NetworkConv2D_BN_Leaky(input=network, channels=channels_in, kernel_size=(1, 1)) - network = NetworkConv2D_BN_Leaky(input=network, channels=(channels_in * 2), kernel_size=(3, 3)) - network = NetworkConv2D_BN_Leaky(input=network, channels=channels_in, kernel_size=(1, 1)) - - network_1 = NetworkConv2D_BN_Leaky(input=network, channels=(channels_in * 2), kernel_size=(3, 3)) - network_1 = Conv2D(filters=channels_out, kernel_size=(1,1), name=layer_name)(network_1) - - return network, network_1 - -def yolo_main(input, num_anchors, num_classes): - - darknet_network = Model(input, darknet(input)) - - network, network_1 = last_layers(darknet_network.output, 512, num_anchors * (num_classes + 5), layer_name="last1") - - network = NetworkConv2D_BN_Leaky( input=network, channels=256, kernel_size=(1,1)) - network = UpSampling2D(2)(network) - network = Concatenate()([network, darknet_network.layers[152].output]) - - network, network_2 = last_layers(network, 256, num_anchors * (num_classes + 5), layer_name="last2") - - network = NetworkConv2D_BN_Leaky(input=network, channels=128, kernel_size=(1, 1)) - network = UpSampling2D(2)(network) - network = Concatenate()([network, darknet_network.layers[92].output]) - - network, network_3 = last_layers(network, 128, num_anchors * (num_classes + 5), layer_name="last3") - - return Model(input, [network_1, network_2, network_3]) - - -def tiny_yolo_main(input, num_anchors, num_classes): - network_1 = NetworkConv2D_BN_Leaky(input=input, channels=16, kernel_size=(3,3) ) - network_1 = MaxPool2D(pool_size=(2,2), strides=(2,2), padding="same")(network_1) - network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=32, kernel_size=(3, 3)) - network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) - network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=64, kernel_size=(3, 3)) - network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) - network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=128, kernel_size=(3, 3)) - network_1 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) - network_1 = NetworkConv2D_BN_Leaky(input=network_1, channels=256, kernel_size=(3, 3)) - - network_2 = MaxPool2D(pool_size=(2, 2), strides=(2, 2), padding="same")(network_1) - network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=512, kernel_size=(3, 3)) - network_2 = MaxPool2D(pool_size=(2, 2), strides=(1, 1), padding="same")(network_2) - network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=1024, kernel_size=(3, 3)) - network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=256, kernel_size=(1, 1)) - - network_3 = NetworkConv2D_BN_Leaky(input=network_2, channels=512, kernel_size=(3, 3)) - network_3 = Conv2D(num_anchors * (num_classes + 5), kernel_size=(1,1))(network_3) - - network_2 = NetworkConv2D_BN_Leaky(input=network_2, channels=128, kernel_size=(1, 1)) - network_2 = UpSampling2D(2)(network_2) - - network_4 = Concatenate()([network_2, network_1]) - network_4 = NetworkConv2D_BN_Leaky(input=network_4, channels=256, kernel_size=(3, 3)) - network_4 = Conv2D(num_anchors * (num_classes + 5), kernel_size=(1,1))(network_4) - - return Model(input, [network_3, network_4]) \ No newline at end of file diff --git a/imageai/Detection/YOLOv3/utils.py b/imageai/Detection/YOLOv3/utils.py deleted file mode 100644 index fd12687b..00000000 --- a/imageai/Detection/YOLOv3/utils.py +++ /dev/null @@ -1,187 +0,0 @@ -import tensorflow as tf -from keras import backend as K - -from PIL import Image - - -def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False): - - num_anchors = len(anchors) - - anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2]) - - grid_shape = K.shape(feats)[1:3] - grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]), - [1, grid_shape[1], 1, 1]) - grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]), - [grid_shape[0], 1, 1, 1]) - grid = K.concatenate([grid_x, grid_y]) - grid = K.cast(grid, K.dtype(feats)) - - feats = K.reshape( - feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5]) - - - box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats)) - box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats)) - box_confidence = K.sigmoid(feats[..., 4:5]) - box_class_probs = K.sigmoid(feats[..., 5:]) - - if calc_loss == True: - return grid, feats, box_xy, box_wh - return box_xy, box_wh, box_confidence, box_class_probs - - -def yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape): - - box_yx = box_xy[..., ::-1] - box_hw = box_wh[..., ::-1] - input_shape = K.cast(input_shape, K.dtype(box_yx)) - image_shape = K.cast(image_shape, K.dtype(box_yx)) - new_shape = K.round(image_shape * K.min(input_shape/image_shape)) - offset = (input_shape-new_shape)/2./input_shape - scale = input_shape/new_shape - box_yx = (box_yx - offset) * scale - box_hw *= scale - - box_mins = box_yx - (box_hw / 2.) - box_maxes = box_yx + (box_hw / 2.) - boxes = K.concatenate([ - box_mins[..., 0:1], - box_mins[..., 1:2], - box_maxes[..., 0:1], - box_maxes[..., 1:2] - ]) - - - boxes *= K.concatenate([image_shape, image_shape]) - return boxes - - -def yolo_boxes_and_scores(feats, anchors, num_classes, input_shape, image_shape): - - box_xy, box_wh, box_confidence, box_class_probs = yolo_head(feats, - anchors, num_classes, input_shape) - boxes = yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape) - boxes = K.reshape(boxes, [-1, 4]) - box_scores = box_confidence * box_class_probs - box_scores = K.reshape(box_scores, [-1, num_classes]) - return boxes, box_scores - - -def yolo_eval(yolo_outputs, - anchors, - num_classes, - image_shape, - max_boxes=20, - score_threshold=.6, - iou_threshold=.5): - - num_layers = len(yolo_outputs) - anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] - input_shape = K.shape(yolo_outputs[0])[1:3] * 32 - boxes = [] - box_scores = [] - for l in range(num_layers): - _boxes, _box_scores = yolo_boxes_and_scores(yolo_outputs[l], - anchors[anchor_mask[l]], num_classes, input_shape, image_shape) - boxes.append(_boxes) - box_scores.append(_box_scores) - boxes = K.concatenate(boxes, axis=0) - box_scores = K.concatenate(box_scores, axis=0) - - mask = box_scores >= score_threshold - max_boxes_tensor = K.constant(max_boxes, dtype='int32') - boxes_ = [] - scores_ = [] - classes_ = [] - for c in range(num_classes): - class_boxes = tf.boolean_mask(boxes, mask[:, c]) - class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c]) - nms_index = tf.image.non_max_suppression( - class_boxes, class_box_scores, max_boxes_tensor, iou_threshold=iou_threshold) - class_boxes = K.gather(class_boxes, nms_index) - class_box_scores = K.gather(class_box_scores, nms_index) - classes = K.ones_like(class_box_scores, 'int32') * c - boxes_.append(class_boxes) - scores_.append(class_box_scores) - classes_.append(classes) - boxes_ = K.concatenate(boxes_, axis=0) - scores_ = K.concatenate(scores_, axis=0) - classes_ = K.concatenate(classes_, axis=0) - - return boxes_, scores_, classes_ - - - -def decode_netout(netout, anchors, obj_thresh, nms_thresh, net_h, net_w): - grid_h, grid_w = netout.shape[:2] - nb_box = 3 - netout = netout.reshape((grid_h, grid_w, nb_box, -1)) - nb_class = netout.shape[-1] - 5 - - boxes = [] - - netout[..., :2] = _sigmoid(netout[..., :2]) - netout[..., 4:] = _sigmoid(netout[..., 4:]) - netout[..., 5:] = netout[..., 4][..., np.newaxis] * netout[..., 5:] - netout[..., 5:] *= netout[..., 5:] > obj_thresh - - for i in range(grid_h*grid_w): - row = i / grid_w - col = i % grid_w - - for b in range(nb_box): - # 4th element is objectness score - objectness = netout[int(row)][int(col)][b][4] - #objectness = netout[..., :4] - - if(objectness.all() <= obj_thresh): continue - - # first 4 elements are x, y, w, and h - x, y, w, h = netout[int(row)][int(col)][b][:4] - - x = (col + x) / grid_w # center position, unit: image width - y = (row + y) / grid_h # center position, unit: image height - w = anchors[2 * b + 0] * np.exp(w) / net_w # unit: image width - h = anchors[2 * b + 1] * np.exp(h) / net_h # unit: image height - - # last elements are class probabilities - classes = netout[int(row)][col][b][5:] - - box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, objectness, classes) - #box = BoundBox(x-w/2, y-h/2, x+w/2, y+h/2, None, classes) - - boxes.append(box) - - return boxes - -def correct_yolo_boxes(boxes, image_h, image_w, net_h, net_w): - if (float(net_w)/image_w) < (float(net_h)/image_h): - new_w = net_w - new_h = (image_h*net_w)/image_w - else: - new_h = net_w - new_w = (image_w*net_h)/image_h - - for i in range(len(boxes)): - x_offset, x_scale = (net_w - new_w)/2./net_w, float(new_w)/net_w - y_offset, y_scale = (net_h - new_h)/2./net_h, float(new_h)/net_h - - boxes[i].xmin = int((boxes[i].xmin - x_offset) / x_scale * image_w) - boxes[i].xmax = int((boxes[i].xmax - x_offset) / x_scale * image_w) - boxes[i].ymin = int((boxes[i].ymin - y_offset) / y_scale * image_h) - boxes[i].ymax = int((boxes[i].ymax - y_offset) / y_scale * image_h) - - -def letterbox_image(image, size): - iw, ih = image.size - w, h = size - scale = min(w/iw, h/ih) - nw = int(iw*scale) - nh = int(ih*scale) - - image = image.resize((nw,nh), Image.BICUBIC) - new_image = Image.new('RGB', size, (128,128,128)) - new_image.paste(image, ((w-nw)//2, (h-nh)//2)) - return new_image \ No newline at end of file From d6c681a0b0d6bb3bb07be38f404535ac5b8b8373 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Thu, 29 Oct 2020 22:34:41 +0100 Subject: [PATCH 05/12] Added support for TF 2.0 in custom YOLOv3 training & inference --- imageai/Detection/Custom/__init__.py | 34 ++-- imageai/Detection/Custom/callbacks.py | 2 +- imageai/Detection/Custom/generator.py | 2 +- imageai/Detection/Custom/yolo.py | 22 +-- imageai/Detection/YOLO/yolov3.py | 259 +++++++++++++++++++++++++- 5 files changed, 291 insertions(+), 28 deletions(-) diff --git a/imageai/Detection/Custom/__init__.py b/imageai/Detection/Custom/__init__.py index 50c4931b..df3671ef 100644 --- a/imageai/Detection/Custom/__init__.py +++ b/imageai/Detection/Custom/__init__.py @@ -3,21 +3,22 @@ import numpy as np import json from imageai.Detection.Custom.voc import parse_voc_annotation -from imageai.Detection.Custom.yolo import create_yolov3_model, dummy_loss -from imageai.Detection.YOLOv3.models import yolo_main +from imageai.Detection.YOLO.yolov3 import yolov3_main, yolov3_train, dummy_loss from imageai.Detection.Custom.generator import BatchGenerator from imageai.Detection.Custom.utils.utils import normalize, evaluate, makedirs -from keras.callbacks import ReduceLROnPlateau -from keras.optimizers import Adam -from imageai.Detection.Custom.callbacks import CustomModelCheckpoint, CustomTensorBoard +from tensorflow.keras.callbacks import ReduceLROnPlateau +from tensorflow.keras.optimizers import Adam +from imageai.Detection.Custom.callbacks import CustomModelCheckpoint from imageai.Detection.Custom.utils.multi_gpu_model import multi_gpu_model from imageai.Detection.Custom.gen_anchors import generateAnchors import tensorflow as tf -from keras.models import load_model, Input -from keras.callbacks import TensorBoard -import keras.backend as K +from tensorflow.keras.models import load_model +from tensorflow.keras import Input +from tensorflow.keras.callbacks import TensorBoard +import tensorflow.keras.backend as K import cv2 +tf.config.run_functions_eagerly(True) os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" @@ -166,6 +167,13 @@ def setTrainConfig(self, object_names_array, batch_size=4, num_experiments=100, :return: """ + # Remove cache files + if os.path.isfile(self.__train_cache_file) == True: + os.remove(self.__train_cache_file) + + if os.path.isfile(self.__validation_cache_file) == True: + os.remove(self.__validation_cache_file) + self.__model_anchors, self.__inference_anchors = generateAnchors(self.__train_annotations_folder, self.__train_images_folder, self.__train_cache_file, self.__model_labels) @@ -527,8 +535,8 @@ def _create_model( ): if len(multi_gpu) > 1: with tf.device('/cpu:0'): - template_model, infer_model = create_yolov3_model( - nb_class=nb_class, + template_model, infer_model = yolov3_train( + num_classes=nb_class, anchors=anchors, max_box_per_image=max_box_per_image, max_grid=max_grid, @@ -542,8 +550,8 @@ def _create_model( class_scale=class_scale ) else: - template_model, infer_model = create_yolov3_model( - nb_class=nb_class, + template_model, infer_model = yolov3_train( + num_classes=nb_class, anchors=anchors, max_box_per_image=max_box_per_image, max_grid=max_grid, @@ -636,7 +644,7 @@ def loadModel(self): self.__detection_utils = CustomDetectionUtils(labels=self.__model_labels) - self.__model = yolo_main(Input(shape=(None, None, 3)), 3, len(self.__model_labels)) + self.__model = yolov3_main(Input(shape=(None, None, 3)), 3, len(self.__model_labels)) self.__model.load_weights(self.__model_path) diff --git a/imageai/Detection/Custom/callbacks.py b/imageai/Detection/Custom/callbacks.py index ddeb70d6..e487c80c 100644 --- a/imageai/Detection/Custom/callbacks.py +++ b/imageai/Detection/Custom/callbacks.py @@ -1,4 +1,4 @@ -from keras.callbacks import TensorBoard, ModelCheckpoint +from tensorflow.keras.callbacks import TensorBoard, ModelCheckpoint import tensorflow as tf import numpy as np import warnings diff --git a/imageai/Detection/Custom/generator.py b/imageai/Detection/Custom/generator.py index 0bab3015..ceb10c7c 100644 --- a/imageai/Detection/Custom/generator.py +++ b/imageai/Detection/Custom/generator.py @@ -1,7 +1,7 @@ import cv2 import copy import numpy as np -from keras.utils import Sequence +from tensorflow.keras.utils import Sequence from imageai.Detection.Custom.utils.bbox import BoundBox, bbox_iou from imageai.Detection.Custom.utils.image import apply_random_scale_and_crop, random_distort_image, random_flip, correct_bounding_boxes diff --git a/imageai/Detection/Custom/yolo.py b/imageai/Detection/Custom/yolo.py index f9661226..ab54ecda 100644 --- a/imageai/Detection/Custom/yolo.py +++ b/imageai/Detection/Custom/yolo.py @@ -1,7 +1,7 @@ -from keras.layers import Conv2D, Input, BatchNormalization, LeakyReLU, ZeroPadding2D, UpSampling2D, Lambda -from keras.layers.merge import add, concatenate -from keras.models import Model -from keras.engine.topology import Layer +from tensorflow.keras.layers import Conv2D, Input, BatchNormalization, LeakyReLU, ZeroPadding2D, UpSampling2D, Lambda +from tensorflow.keras.layers import add, concatenate +from tensorflow.keras.models import Model +from tensorflow.keras.layers import Layer import tensorflow as tf class YoloLayer(Layer): @@ -21,7 +21,7 @@ def __init__(self, anchors, max_grid, batch_size, warmup_batches, ignore_thresh, # make a persistent mesh grid max_grid_h, max_grid_w = max_grid - cell_x = tf.to_float(tf.reshape(tf.tile(tf.range(max_grid_w), [max_grid_h]), (1, max_grid_h, max_grid_w, 1, 1))) + cell_x = tf.cast(tf.reshape(tf.tile(tf.range(max_grid_w), [max_grid_h]), (1, max_grid_h, max_grid_w, 1, 1)), dtype=tf.float32) cell_y = tf.transpose(cell_x, (0,2,1,3,4)) self.cell_grid = tf.tile(tf.concat([cell_x,cell_y],-1), [batch_size, 1, 1, 3, 1]) @@ -101,7 +101,7 @@ def call(self, x): iou_scores = tf.truediv(intersect_areas, union_areas) best_ious = tf.reduce_max(iou_scores, axis=4) - conf_delta *= tf.expand_dims(tf.to_float(best_ious < self.ignore_thresh), 4) + conf_delta *= tf.expand_dims(tf.cast((best_ious < self.ignore_thresh), dtype=tf.float32), 4) """ Compute some online statistics @@ -134,10 +134,10 @@ def call(self, x): count = tf.reduce_sum(object_mask) count_noobj = tf.reduce_sum(1 - object_mask) - detect_mask = tf.to_float((pred_box_conf*object_mask) >= 0.5) - class_mask = tf.expand_dims(tf.to_float(tf.equal(tf.argmax(pred_box_class, -1), true_box_class)), 4) - recall50 = tf.reduce_sum(tf.to_float(iou_scores >= 0.5 ) * detect_mask * class_mask) / (count + 1e-3) - recall75 = tf.reduce_sum(tf.to_float(iou_scores >= 0.75) * detect_mask * class_mask) / (count + 1e-3) + detect_mask = tf.cast((pred_box_conf*object_mask >= 0.5), dtype=tf.float32) + class_mask = tf.expand_dims(tf.cast(tf.equal(tf.argmax(pred_box_class, -1), true_box_class), dtype=tf.float32), 4) + recall50 = tf.reduce_sum(tf.cast((iou_scores >= 0.5), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) + recall75 = tf.reduce_sum(tf.cast((iou_scores >= 0.75), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) avg_iou = tf.reduce_sum(iou_scores) / (count + 1e-3) avg_obj = tf.reduce_sum(pred_box_conf * object_mask) / (count + 1e-3) avg_noobj = tf.reduce_sum(pred_box_conf * (1-object_mask)) / (count_noobj + 1e-3) @@ -146,7 +146,7 @@ def call(self, x): """ Warm-up training """ - batch_seen = tf.assign_add(batch_seen, 1.) + batch_seen = tf.compat.v1.assign_add(batch_seen, 1.) true_box_xy, true_box_wh, xywh_mask = tf.cond(tf.less(batch_seen, self.warmup_batches+1), lambda: [true_box_xy + (0.5 + self.cell_grid[:,:grid_h,:grid_w,:,:]) * (1-object_mask), diff --git a/imageai/Detection/YOLO/yolov3.py b/imageai/Detection/YOLO/yolov3.py index 69edb574..35e8b843 100644 --- a/imageai/Detection/YOLO/yolov3.py +++ b/imageai/Detection/YOLO/yolov3.py @@ -1,12 +1,199 @@ -from tensorflow.keras.layers import Conv2D, MaxPool2D, Add, ZeroPadding2D, UpSampling2D, Concatenate +from tensorflow.keras.layers import Conv2D, MaxPool2D, Add, ZeroPadding2D, UpSampling2D, Concatenate, LeakyReLU, Lambda from tensorflow.keras.layers import LeakyReLU from tensorflow.keras.layers import BatchNormalization from tensorflow.keras.regularizers import l2 from tensorflow.keras.models import Model from tensorflow.keras import Input +from tensorflow.keras.layers import add, concatenate +from tensorflow.keras.layers import Layer +import tensorflow as tf + +class YoloLayer(Layer): + def __init__(self, anchors, max_grid, batch_size, warmup_batches, ignore_thresh, + grid_scale, obj_scale, noobj_scale, xywh_scale, class_scale, + **kwargs): + # make the model settings persistent + self.ignore_thresh = ignore_thresh + self.warmup_batches = warmup_batches + self.anchors = tf.constant(anchors, dtype='float', shape=[1,1,1,3,2]) + self.grid_scale = grid_scale + self.obj_scale = obj_scale + self.noobj_scale = noobj_scale + self.xywh_scale = xywh_scale + self.class_scale = class_scale + + # make a persistent mesh grid + max_grid_h, max_grid_w = max_grid + + cell_x = tf.cast(tf.reshape(tf.tile(tf.range(max_grid_w), [max_grid_h]), (1, max_grid_h, max_grid_w, 1, 1)), dtype=tf.float32) + cell_y = tf.transpose(cell_x, (0,2,1,3,4)) + self.cell_grid = tf.tile(tf.concat([cell_x,cell_y],-1), [batch_size, 1, 1, 3, 1]) + + super(YoloLayer, self).__init__(**kwargs) + + def build(self, input_shape): + super(YoloLayer, self).build(input_shape) # Be sure to call this somewhere! + + def call(self, x): + input_image, y_pred, y_true, true_boxes = x + + # adjust the shape of the y_predict [batch, grid_h, grid_w, 3, 4+1+nb_class] + y_pred = tf.reshape(y_pred, tf.concat([tf.shape(y_pred)[:3], tf.constant([3, -1])], axis=0)) + + # initialize the masks + object_mask = tf.expand_dims(y_true[..., 4], 4) + + # the variable to keep track of number of batches processed + batch_seen = tf.Variable(0.) + + # compute grid factor and net factor + grid_h = tf.shape(y_true)[1] + grid_w = tf.shape(y_true)[2] + grid_factor = tf.reshape(tf.cast([grid_w, grid_h], tf.float32), [1,1,1,1,2]) + + net_h = tf.shape(input_image)[1] + net_w = tf.shape(input_image)[2] + net_factor = tf.reshape(tf.cast([net_w, net_h], tf.float32), [1,1,1,1,2]) + + """ + Adjust prediction + """ + pred_box_xy = (self.cell_grid[:,:grid_h,:grid_w,:,:] + tf.sigmoid(y_pred[..., :2])) # sigma(t_xy) + c_xy + pred_box_wh = y_pred[..., 2:4] # t_wh + pred_box_conf = tf.expand_dims(tf.sigmoid(y_pred[..., 4]), 4) # adjust confidence + pred_box_class = y_pred[..., 5:] # adjust class probabilities + + """ + Adjust ground truth + """ + true_box_xy = y_true[..., 0:2] # (sigma(t_xy) + c_xy) + true_box_wh = y_true[..., 2:4] # t_wh + true_box_conf = tf.expand_dims(y_true[..., 4], 4) + true_box_class = tf.argmax(y_true[..., 5:], -1) + + """ + Compare each predicted box to all true boxes + """ + # initially, drag all objectness of all boxes to 0 + conf_delta = pred_box_conf - 0 + + # then, ignore the boxes which have good overlap with some true box + true_xy = true_boxes[..., 0:2] / grid_factor + true_wh = true_boxes[..., 2:4] / net_factor + + true_wh_half = true_wh / 2. + true_mins = true_xy - true_wh_half + true_maxes = true_xy + true_wh_half + + pred_xy = tf.expand_dims(pred_box_xy / grid_factor, 4) + pred_wh = tf.expand_dims(tf.exp(pred_box_wh) * self.anchors / net_factor, 4) + + pred_wh_half = pred_wh / 2. + pred_mins = pred_xy - pred_wh_half + pred_maxes = pred_xy + pred_wh_half + + intersect_mins = tf.maximum(pred_mins, true_mins) + intersect_maxes = tf.minimum(pred_maxes, true_maxes) + + intersect_wh = tf.maximum(intersect_maxes - intersect_mins, 0.) + intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1] + + true_areas = true_wh[..., 0] * true_wh[..., 1] + pred_areas = pred_wh[..., 0] * pred_wh[..., 1] + + union_areas = pred_areas + true_areas - intersect_areas + iou_scores = tf.truediv(intersect_areas, union_areas) + + best_ious = tf.reduce_max(iou_scores, axis=4) + conf_delta *= tf.expand_dims(tf.cast((best_ious < self.ignore_thresh), dtype=tf.float32), 4) + + """ + Compute some online statistics + """ + true_xy = true_box_xy / grid_factor + true_wh = tf.exp(true_box_wh) * self.anchors / net_factor + + true_wh_half = true_wh / 2. + true_mins = true_xy - true_wh_half + true_maxes = true_xy + true_wh_half + + pred_xy = pred_box_xy / grid_factor + pred_wh = tf.exp(pred_box_wh) * self.anchors / net_factor + + pred_wh_half = pred_wh / 2. + pred_mins = pred_xy - pred_wh_half + pred_maxes = pred_xy + pred_wh_half + + intersect_mins = tf.maximum(pred_mins, true_mins) + intersect_maxes = tf.minimum(pred_maxes, true_maxes) + intersect_wh = tf.maximum(intersect_maxes - intersect_mins, 0.) + intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1] + + true_areas = true_wh[..., 0] * true_wh[..., 1] + pred_areas = pred_wh[..., 0] * pred_wh[..., 1] + + union_areas = pred_areas + true_areas - intersect_areas + iou_scores = tf.truediv(intersect_areas, union_areas) + iou_scores = object_mask * tf.expand_dims(iou_scores, 4) + + count = tf.reduce_sum(object_mask) + count_noobj = tf.reduce_sum(1 - object_mask) + detect_mask = tf.cast((pred_box_conf*object_mask >= 0.5), dtype=tf.float32) + class_mask = tf.expand_dims(tf.cast(tf.equal(tf.argmax(pred_box_class, -1), true_box_class), dtype=tf.float32), 4) + recall50 = tf.reduce_sum(tf.cast((iou_scores >= 0.5), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) + recall75 = tf.reduce_sum(tf.cast((iou_scores >= 0.75), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) + avg_iou = tf.reduce_sum(iou_scores) / (count + 1e-3) + avg_obj = tf.reduce_sum(pred_box_conf * object_mask) / (count + 1e-3) + avg_noobj = tf.reduce_sum(pred_box_conf * (1-object_mask)) / (count_noobj + 1e-3) + avg_cat = tf.reduce_sum(object_mask * class_mask) / (count + 1e-3) + + """ + Warm-up training + """ + batch_seen = tf.compat.v1.assign_add(batch_seen, 1.) + + true_box_xy, true_box_wh, xywh_mask = tf.cond(tf.less(batch_seen, self.warmup_batches+1), + lambda: [true_box_xy + (0.5 + self.cell_grid[:,:grid_h,:grid_w,:,:]) * (1-object_mask), + true_box_wh + tf.zeros_like(true_box_wh) * (1-object_mask), + tf.ones_like(object_mask)], + lambda: [true_box_xy, + true_box_wh, + object_mask]) + + """ + Compare each true box to all anchor boxes + """ + wh_scale = tf.exp(true_box_wh) * self.anchors / net_factor + wh_scale = tf.expand_dims(2 - wh_scale[..., 0] * wh_scale[..., 1], axis=4) # the smaller the box, the bigger the scale + + xy_delta = xywh_mask * (pred_box_xy-true_box_xy) * wh_scale * self.xywh_scale + wh_delta = xywh_mask * (pred_box_wh-true_box_wh) * wh_scale * self.xywh_scale + conf_delta = object_mask * (pred_box_conf-true_box_conf) * self.obj_scale + (1-object_mask) * conf_delta * self.noobj_scale + class_delta = object_mask * \ + tf.expand_dims(tf.nn.sparse_softmax_cross_entropy_with_logits(labels=true_box_class, logits=pred_box_class), 4) * \ + self.class_scale + + loss_xy = tf.reduce_sum(tf.square(xy_delta), list(range(1,5))) + loss_wh = tf.reduce_sum(tf.square(wh_delta), list(range(1,5))) + loss_conf = tf.reduce_sum(tf.square(conf_delta), list(range(1,5))) + loss_class = tf.reduce_sum(class_delta, list(range(1,5))) + + loss = loss_xy + loss_wh + loss_conf + loss_class + + + return loss*self.grid_scale + + def compute_output_shape(self, input_shape): + return [(None, 1)] + + + +def dummy_loss(y_true, y_pred): + return tf.sqrt(tf.reduce_sum(y_pred)) + def NetworkConv2D_BN_Leaky(input, channels, kernel_size, kernel_regularizer = l2(5e-4), strides=(1,1), padding="same", use_bias=False): network = Conv2D( filters=channels, kernel_size=kernel_size, strides=strides, padding=padding, kernel_regularizer=kernel_regularizer, use_bias=use_bias)(input) @@ -51,7 +238,7 @@ def last_layers(input, channels_in, channels_out, layer_name=""): return network, network_1 -def yolov3_main(input, num_anchors, num_classes): +def yolov3_base(input, num_anchors, num_classes): darknet_network = Model(input, darknet(input)) @@ -69,9 +256,77 @@ def yolov3_main(input, num_anchors, num_classes): network, network_3 = last_layers(network, 128, num_anchors * (num_classes + 5), layer_name="last3") + return input, network_1, network_2, network_3 + +def yolov3_main(input, num_anchors, num_classes): + + input, network_1, network_2, network_3 = yolov3_base(input, num_anchors, num_classes) + return Model(input, [network_1, network_2, network_3]) +def yolov3_train(num_classes, + anchors, + max_box_per_image, + max_grid, + batch_size, + warmup_batches, + ignore_thresh, + grid_scales, + obj_scale, + noobj_scale, + xywh_scale, + class_scale): + + input_image = Input(shape=(None, None, 3)) # net_h, net_w, 3 + true_boxes = Input(shape=(1, 1, 1, max_box_per_image, 4)) + true_yolo_1 = Input(shape=(None, None, len(anchors)//6, 4+1+num_classes)) # grid_h, grid_w, nb_anchor, 5+nb_class + true_yolo_2 = Input(shape=(None, None, len(anchors)//6, 4+1+num_classes)) # grid_h, grid_w, nb_anchor, 5+nb_class + true_yolo_3 = Input(shape=(None, None, len(anchors)//6, 4+1+num_classes)) # grid_h, grid_w, nb_anchor, 5+nb_class + + + + _ , network_1, network_2, network_3 = yolov3_base(input_image, len(anchors)//6, num_classes) + + loss_yolo_1 = YoloLayer(anchors[12:], + [1*num for num in max_grid], + batch_size, + warmup_batches, + ignore_thresh, + grid_scales[0], + obj_scale, + noobj_scale, + xywh_scale, + class_scale)([input_image, network_1, true_yolo_1, true_boxes]) + + loss_yolo_2 = YoloLayer(anchors[6:12], + [2*num for num in max_grid], + batch_size, + warmup_batches, + ignore_thresh, + grid_scales[1], + obj_scale, + noobj_scale, + xywh_scale, + class_scale)([input_image, network_2, true_yolo_2, true_boxes]) + + loss_yolo_3 = YoloLayer(anchors[:6], + [4*num for num in max_grid], + batch_size, + warmup_batches, + ignore_thresh, + grid_scales[2], + obj_scale, + noobj_scale, + xywh_scale, + class_scale)([input_image, network_3, true_yolo_3, true_boxes]) + + train_model = Model([input_image, true_boxes, true_yolo_1, true_yolo_2, true_yolo_3], [loss_yolo_1, loss_yolo_2, loss_yolo_3]) + infer_model = Model(input_image, [network_1, network_2, network_3]) + + return [train_model, infer_model] + + def tiny_yolov3_main(input, num_anchors, num_classes): network_1 = NetworkConv2D_BN_Leaky(input=input, channels=16, kernel_size=(3,3) ) From 7a5912c75faa29d2c8292739bdcbcf4a3728d90f Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Thu, 29 Oct 2020 22:55:07 +0100 Subject: [PATCH 06/12] fixed YOLO import error and increased version number --- imageai.egg-info/SOURCES.txt | 5 ++--- imageai/Detection/YOLO/__init__.py | 0 setup.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 imageai/Detection/YOLO/__init__.py diff --git a/imageai.egg-info/SOURCES.txt b/imageai.egg-info/SOURCES.txt index 60f40665..8a7de514 100644 --- a/imageai.egg-info/SOURCES.txt +++ b/imageai.egg-info/SOURCES.txt @@ -7,6 +7,8 @@ imageai.egg-info/dependency_links.txt imageai.egg-info/not-zip-safe imageai.egg-info/requires.txt imageai.egg-info/top_level.txt +imageai/Classification/__init__.py +imageai/Classification/Custom/__init__.py imageai/Detection/__init__.py imageai/Detection/Custom/__init__.py imageai/Detection/Custom/callbacks.py @@ -21,9 +23,6 @@ imageai/Detection/Custom/utils/colors.py imageai/Detection/Custom/utils/image.py imageai/Detection/Custom/utils/multi_gpu_model.py imageai/Detection/Custom/utils/utils.py -imageai/Detection/YOLOv3/__init__.py -imageai/Detection/YOLOv3/models.py -imageai/Detection/YOLOv3/utils.py imageai/Detection/keras_resnet/__init__.py imageai/Detection/keras_resnet/benchmarks/__init__.py imageai/Detection/keras_resnet/blocks/_1d.py diff --git a/imageai/Detection/YOLO/__init__.py b/imageai/Detection/YOLO/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/setup.py b/setup.py index 8e1f622a..aa231875 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup,find_packages setup(name="imageai", - version='2.1.5', + version='2.1.6', description='A python library built to empower developers to build applications and systems with self-contained Computer Vision capabilities', url="https://github.com/OlafenwaMoses/ImageAI", author='Moses Olafenwa and John Olafenwa', From 90476f078e62f5696da9db553a7c5402a816a282 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Mon, 28 Dec 2020 13:58:33 +0100 Subject: [PATCH 07/12] finalized custom YOLOv3 for TF 2.X --- imageai.egg-info/PKG-INFO | 10 - imageai.egg-info/SOURCES.txt | 97 ------- imageai.egg-info/dependency_links.txt | 1 - imageai.egg-info/not-zip-safe | 1 - imageai.egg-info/requires.txt | 5 - imageai.egg-info/top_level.txt | 1 - imageai/Detection/Custom/__init__.py | 17 ++ imageai/Detection/Custom/yolo.py | 352 -------------------------- imageai/Detection/YOLO/yolov3.py | 5 +- 9 files changed, 21 insertions(+), 468 deletions(-) delete mode 100644 imageai.egg-info/PKG-INFO delete mode 100644 imageai.egg-info/SOURCES.txt delete mode 100644 imageai.egg-info/dependency_links.txt delete mode 100644 imageai.egg-info/not-zip-safe delete mode 100644 imageai.egg-info/requires.txt delete mode 100644 imageai.egg-info/top_level.txt delete mode 100644 imageai/Detection/Custom/yolo.py diff --git a/imageai.egg-info/PKG-INFO b/imageai.egg-info/PKG-INFO deleted file mode 100644 index 6f841b27..00000000 --- a/imageai.egg-info/PKG-INFO +++ /dev/null @@ -1,10 +0,0 @@ -Metadata-Version: 1.0 -Name: imageai -Version: 2.1.5 -Summary: A python library built to empower developers to build applications and systems with self-contained Computer Vision capabilities -Home-page: https://github.com/OlafenwaMoses/ImageAI -Author: Moses Olafenwa and John Olafenwa -Author-email: guymodscientist@gmail.com -License: MIT -Description: UNKNOWN -Platform: UNKNOWN diff --git a/imageai.egg-info/SOURCES.txt b/imageai.egg-info/SOURCES.txt deleted file mode 100644 index 8a7de514..00000000 --- a/imageai.egg-info/SOURCES.txt +++ /dev/null @@ -1,97 +0,0 @@ -README.md -setup.py -imageai/__init__.py -imageai.egg-info/PKG-INFO -imageai.egg-info/SOURCES.txt -imageai.egg-info/dependency_links.txt -imageai.egg-info/not-zip-safe -imageai.egg-info/requires.txt -imageai.egg-info/top_level.txt -imageai/Classification/__init__.py -imageai/Classification/Custom/__init__.py -imageai/Detection/__init__.py -imageai/Detection/Custom/__init__.py -imageai/Detection/Custom/callbacks.py -imageai/Detection/Custom/evaluate.py -imageai/Detection/Custom/gen_anchors.py -imageai/Detection/Custom/generator.py -imageai/Detection/Custom/voc.py -imageai/Detection/Custom/yolo.py -imageai/Detection/Custom/utils/__init__.py -imageai/Detection/Custom/utils/bbox.py -imageai/Detection/Custom/utils/colors.py -imageai/Detection/Custom/utils/image.py -imageai/Detection/Custom/utils/multi_gpu_model.py -imageai/Detection/Custom/utils/utils.py -imageai/Detection/keras_resnet/__init__.py -imageai/Detection/keras_resnet/benchmarks/__init__.py -imageai/Detection/keras_resnet/blocks/_1d.py -imageai/Detection/keras_resnet/blocks/_2d.py -imageai/Detection/keras_resnet/blocks/_3d.py -imageai/Detection/keras_resnet/blocks/__init__.py -imageai/Detection/keras_resnet/blocks/_time_distributed_2d.py -imageai/Detection/keras_resnet/classifiers/_2d.py -imageai/Detection/keras_resnet/classifiers/__init__.py -imageai/Detection/keras_resnet/layers/__init__.py -imageai/Detection/keras_resnet/layers/_batch_normalization.py -imageai/Detection/keras_resnet/models/_2d.py -imageai/Detection/keras_resnet/models/__init__.py -imageai/Detection/keras_resnet/models/_time_distributed_2d.py -imageai/Detection/keras_retinanet/__init__.py -imageai/Detection/keras_retinanet/initializers.py -imageai/Detection/keras_retinanet/losses.py -imageai/Detection/keras_retinanet/backend/__init__.py -imageai/Detection/keras_retinanet/backend/common.py -imageai/Detection/keras_retinanet/backend/dynamic.py -imageai/Detection/keras_retinanet/backend/tensorflow_backend.py -imageai/Detection/keras_retinanet/callbacks/__init__.py -imageai/Detection/keras_retinanet/callbacks/coco.py -imageai/Detection/keras_retinanet/callbacks/common.py -imageai/Detection/keras_retinanet/callbacks/eval.py -imageai/Detection/keras_retinanet/layers/__init__.py -imageai/Detection/keras_retinanet/layers/_misc.py -imageai/Detection/keras_retinanet/models/__init__.py -imageai/Detection/keras_retinanet/models/mobilenet.py -imageai/Detection/keras_retinanet/models/resnet.py -imageai/Detection/keras_retinanet/models/retinanet.py -imageai/Detection/keras_retinanet/preprocessing/__init__.py -imageai/Detection/keras_retinanet/preprocessing/coco.py -imageai/Detection/keras_retinanet/preprocessing/csv_generator.py -imageai/Detection/keras_retinanet/preprocessing/generator.py -imageai/Detection/keras_retinanet/preprocessing/kitti.py -imageai/Detection/keras_retinanet/preprocessing/open_images.py -imageai/Detection/keras_retinanet/preprocessing/pascal_voc.py -imageai/Detection/keras_retinanet/utils/__init__.py -imageai/Detection/keras_retinanet/utils/anchors.py -imageai/Detection/keras_retinanet/utils/coco_eval.py -imageai/Detection/keras_retinanet/utils/colors.py -imageai/Detection/keras_retinanet/utils/eval.py -imageai/Detection/keras_retinanet/utils/image.py -imageai/Detection/keras_retinanet/utils/keras_version.py -imageai/Detection/keras_retinanet/utils/model.py -imageai/Detection/keras_retinanet/utils/transform.py -imageai/Detection/keras_retinanet/utils/visualization.py -imageai/Prediction/__init__.py -imageai/Prediction/imagenet_utils.py -imageai/Prediction/Custom/__init__.py -imageai/Prediction/Custom/custom_utils.py -imageai/Prediction/DenseNet/__init__.py -imageai/Prediction/DenseNet/densenet.py -imageai/Prediction/DenseNet/imagenet_utils.py -imageai/Prediction/DenseNet/subpixel.py -imageai/Prediction/DenseNet/tensorflow_backend.py -imageai/Prediction/InceptionV3/__init__.py -imageai/Prediction/InceptionV3/imagenet_utils.py -imageai/Prediction/InceptionV3/inceptionv3.py -imageai/Prediction/ResNet/__init__.py -imageai/Prediction/ResNet/resnet50.py -imageai/Prediction/SqueezeNet/__init__.py -imageai/Prediction/SqueezeNet/squeezenet.py -test/test_custom_object_detection.py -test/test_custom_recognition.py -test/test_custom_video_detection.py -test/test_detection_model_training.py -test/test_image_recognition.py -test/test_model_training.py -test/test_object_detection.py -test/test_video_object_detection.py \ No newline at end of file diff --git a/imageai.egg-info/dependency_links.txt b/imageai.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/imageai.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/imageai.egg-info/not-zip-safe b/imageai.egg-info/not-zip-safe deleted file mode 100644 index 8b137891..00000000 --- a/imageai.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/imageai.egg-info/requires.txt b/imageai.egg-info/requires.txt deleted file mode 100644 index 24c8ac75..00000000 --- a/imageai.egg-info/requires.txt +++ /dev/null @@ -1,5 +0,0 @@ -numpy -scipy -pillow -matplotlib -h5py diff --git a/imageai.egg-info/top_level.txt b/imageai.egg-info/top_level.txt deleted file mode 100644 index 4e604c07..00000000 --- a/imageai.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -imageai diff --git a/imageai/Detection/Custom/__init__.py b/imageai/Detection/Custom/__init__.py index df3671ef..e20e188e 100644 --- a/imageai/Detection/Custom/__init__.py +++ b/imageai/Detection/Custom/__init__.py @@ -266,6 +266,23 @@ def trainModel(self): os.environ['CUDA_VISIBLE_DEVICES'] = self.__train_gpus multi_gpu = [int(gpu) for gpu in self.__train_gpus.split(',')] + """train_model, infer_model = self._create_model( + nb_class=len(labels), + anchors=self.__model_anchors, + max_box_per_image=max_box_per_image, + max_grid=[self.__model_max_input_size, self.__model_max_input_size], + batch_size=self.__train_batch_size, + warmup_batches=warmup_batches, + ignore_thresh=self.__train_ignore_treshold, + multi_gpu=multi_gpu, + lr=self.__train_learning_rate, + grid_scales=self.__train_grid_scales, + obj_scale=self.__train_obj_scale, + noobj_scale=self.__train_noobj_scale, + xywh_scale=self.__train_xywh_scale, + class_scale=self.__train_class_scale, + )""" + train_model, infer_model = self._create_model( nb_class=len(labels), anchors=self.__model_anchors, diff --git a/imageai/Detection/Custom/yolo.py b/imageai/Detection/Custom/yolo.py deleted file mode 100644 index ab54ecda..00000000 --- a/imageai/Detection/Custom/yolo.py +++ /dev/null @@ -1,352 +0,0 @@ -from tensorflow.keras.layers import Conv2D, Input, BatchNormalization, LeakyReLU, ZeroPadding2D, UpSampling2D, Lambda -from tensorflow.keras.layers import add, concatenate -from tensorflow.keras.models import Model -from tensorflow.keras.layers import Layer -import tensorflow as tf - -class YoloLayer(Layer): - def __init__(self, anchors, max_grid, batch_size, warmup_batches, ignore_thresh, - grid_scale, obj_scale, noobj_scale, xywh_scale, class_scale, - **kwargs): - # make the model settings persistent - self.ignore_thresh = ignore_thresh - self.warmup_batches = warmup_batches - self.anchors = tf.constant(anchors, dtype='float', shape=[1,1,1,3,2]) - self.grid_scale = grid_scale - self.obj_scale = obj_scale - self.noobj_scale = noobj_scale - self.xywh_scale = xywh_scale - self.class_scale = class_scale - - # make a persistent mesh grid - max_grid_h, max_grid_w = max_grid - - cell_x = tf.cast(tf.reshape(tf.tile(tf.range(max_grid_w), [max_grid_h]), (1, max_grid_h, max_grid_w, 1, 1)), dtype=tf.float32) - cell_y = tf.transpose(cell_x, (0,2,1,3,4)) - self.cell_grid = tf.tile(tf.concat([cell_x,cell_y],-1), [batch_size, 1, 1, 3, 1]) - - super(YoloLayer, self).__init__(**kwargs) - - def build(self, input_shape): - super(YoloLayer, self).build(input_shape) # Be sure to call this somewhere! - - def call(self, x): - input_image, y_pred, y_true, true_boxes = x - - # adjust the shape of the y_predict [batch, grid_h, grid_w, 3, 4+1+nb_class] - y_pred = tf.reshape(y_pred, tf.concat([tf.shape(y_pred)[:3], tf.constant([3, -1])], axis=0)) - - # initialize the masks - object_mask = tf.expand_dims(y_true[..., 4], 4) - - # the variable to keep track of number of batches processed - batch_seen = tf.Variable(0.) - - # compute grid factor and net factor - grid_h = tf.shape(y_true)[1] - grid_w = tf.shape(y_true)[2] - grid_factor = tf.reshape(tf.cast([grid_w, grid_h], tf.float32), [1,1,1,1,2]) - - net_h = tf.shape(input_image)[1] - net_w = tf.shape(input_image)[2] - net_factor = tf.reshape(tf.cast([net_w, net_h], tf.float32), [1,1,1,1,2]) - - """ - Adjust prediction - """ - pred_box_xy = (self.cell_grid[:,:grid_h,:grid_w,:,:] + tf.sigmoid(y_pred[..., :2])) # sigma(t_xy) + c_xy - pred_box_wh = y_pred[..., 2:4] # t_wh - pred_box_conf = tf.expand_dims(tf.sigmoid(y_pred[..., 4]), 4) # adjust confidence - pred_box_class = y_pred[..., 5:] # adjust class probabilities - - """ - Adjust ground truth - """ - true_box_xy = y_true[..., 0:2] # (sigma(t_xy) + c_xy) - true_box_wh = y_true[..., 2:4] # t_wh - true_box_conf = tf.expand_dims(y_true[..., 4], 4) - true_box_class = tf.argmax(y_true[..., 5:], -1) - - """ - Compare each predicted box to all true boxes - """ - # initially, drag all objectness of all boxes to 0 - conf_delta = pred_box_conf - 0 - - # then, ignore the boxes which have good overlap with some true box - true_xy = true_boxes[..., 0:2] / grid_factor - true_wh = true_boxes[..., 2:4] / net_factor - - true_wh_half = true_wh / 2. - true_mins = true_xy - true_wh_half - true_maxes = true_xy + true_wh_half - - pred_xy = tf.expand_dims(pred_box_xy / grid_factor, 4) - pred_wh = tf.expand_dims(tf.exp(pred_box_wh) * self.anchors / net_factor, 4) - - pred_wh_half = pred_wh / 2. - pred_mins = pred_xy - pred_wh_half - pred_maxes = pred_xy + pred_wh_half - - intersect_mins = tf.maximum(pred_mins, true_mins) - intersect_maxes = tf.minimum(pred_maxes, true_maxes) - - intersect_wh = tf.maximum(intersect_maxes - intersect_mins, 0.) - intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1] - - true_areas = true_wh[..., 0] * true_wh[..., 1] - pred_areas = pred_wh[..., 0] * pred_wh[..., 1] - - union_areas = pred_areas + true_areas - intersect_areas - iou_scores = tf.truediv(intersect_areas, union_areas) - - best_ious = tf.reduce_max(iou_scores, axis=4) - conf_delta *= tf.expand_dims(tf.cast((best_ious < self.ignore_thresh), dtype=tf.float32), 4) - - """ - Compute some online statistics - """ - true_xy = true_box_xy / grid_factor - true_wh = tf.exp(true_box_wh) * self.anchors / net_factor - - true_wh_half = true_wh / 2. - true_mins = true_xy - true_wh_half - true_maxes = true_xy + true_wh_half - - pred_xy = pred_box_xy / grid_factor - pred_wh = tf.exp(pred_box_wh) * self.anchors / net_factor - - pred_wh_half = pred_wh / 2. - pred_mins = pred_xy - pred_wh_half - pred_maxes = pred_xy + pred_wh_half - - intersect_mins = tf.maximum(pred_mins, true_mins) - intersect_maxes = tf.minimum(pred_maxes, true_maxes) - intersect_wh = tf.maximum(intersect_maxes - intersect_mins, 0.) - intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1] - - true_areas = true_wh[..., 0] * true_wh[..., 1] - pred_areas = pred_wh[..., 0] * pred_wh[..., 1] - - union_areas = pred_areas + true_areas - intersect_areas - iou_scores = tf.truediv(intersect_areas, union_areas) - iou_scores = object_mask * tf.expand_dims(iou_scores, 4) - - count = tf.reduce_sum(object_mask) - count_noobj = tf.reduce_sum(1 - object_mask) - detect_mask = tf.cast((pred_box_conf*object_mask >= 0.5), dtype=tf.float32) - class_mask = tf.expand_dims(tf.cast(tf.equal(tf.argmax(pred_box_class, -1), true_box_class), dtype=tf.float32), 4) - recall50 = tf.reduce_sum(tf.cast((iou_scores >= 0.5), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) - recall75 = tf.reduce_sum(tf.cast((iou_scores >= 0.75), dtype=tf.float32) * detect_mask * class_mask) / (count + 1e-3) - avg_iou = tf.reduce_sum(iou_scores) / (count + 1e-3) - avg_obj = tf.reduce_sum(pred_box_conf * object_mask) / (count + 1e-3) - avg_noobj = tf.reduce_sum(pred_box_conf * (1-object_mask)) / (count_noobj + 1e-3) - avg_cat = tf.reduce_sum(object_mask * class_mask) / (count + 1e-3) - - """ - Warm-up training - """ - batch_seen = tf.compat.v1.assign_add(batch_seen, 1.) - - true_box_xy, true_box_wh, xywh_mask = tf.cond(tf.less(batch_seen, self.warmup_batches+1), - lambda: [true_box_xy + (0.5 + self.cell_grid[:,:grid_h,:grid_w,:,:]) * (1-object_mask), - true_box_wh + tf.zeros_like(true_box_wh) * (1-object_mask), - tf.ones_like(object_mask)], - lambda: [true_box_xy, - true_box_wh, - object_mask]) - - """ - Compare each true box to all anchor boxes - """ - wh_scale = tf.exp(true_box_wh) * self.anchors / net_factor - wh_scale = tf.expand_dims(2 - wh_scale[..., 0] * wh_scale[..., 1], axis=4) # the smaller the box, the bigger the scale - - xy_delta = xywh_mask * (pred_box_xy-true_box_xy) * wh_scale * self.xywh_scale - wh_delta = xywh_mask * (pred_box_wh-true_box_wh) * wh_scale * self.xywh_scale - conf_delta = object_mask * (pred_box_conf-true_box_conf) * self.obj_scale + (1-object_mask) * conf_delta * self.noobj_scale - class_delta = object_mask * \ - tf.expand_dims(tf.nn.sparse_softmax_cross_entropy_with_logits(labels=true_box_class, logits=pred_box_class), 4) * \ - self.class_scale - - loss_xy = tf.reduce_sum(tf.square(xy_delta), list(range(1,5))) - loss_wh = tf.reduce_sum(tf.square(wh_delta), list(range(1,5))) - loss_conf = tf.reduce_sum(tf.square(conf_delta), list(range(1,5))) - loss_class = tf.reduce_sum(class_delta, list(range(1,5))) - - loss = loss_xy + loss_wh + loss_conf + loss_class - - - return loss*self.grid_scale - - def compute_output_shape(self, input_shape): - return [(None, 1)] - -def _conv_block(inp, convs, do_skip=True): - x = inp - count = 0 - - for conv in convs: - if count == (len(convs) - 2) and do_skip: - skip_connection = x - count += 1 - - if conv['stride'] > 1: x = ZeroPadding2D(((1,0),(1,0)))(x) # unlike tensorflow darknet prefer left and top paddings - x = Conv2D(conv['filter'], - conv['kernel'], - strides=conv['stride'], - padding='valid' if conv['stride'] > 1 else 'same', # unlike tensorflow darknet prefer left and top paddings - name='conv_' + str(conv['layer_idx']), - use_bias=False if conv['bnorm'] else True)(x) - if conv['bnorm']: x = BatchNormalization(epsilon=0.001, name='bnorm_' + str(conv['layer_idx']))(x) - if conv['leaky']: x = LeakyReLU(alpha=0.1, name='leaky_' + str(conv['layer_idx']))(x) - - return add([skip_connection, x]) if do_skip else x - -def create_yolov3_model( - nb_class, - anchors, - max_box_per_image, - max_grid, - batch_size, - warmup_batches, - ignore_thresh, - grid_scales, - obj_scale, - noobj_scale, - xywh_scale, - class_scale -): - input_image = Input(shape=(None, None, 3)) # net_h, net_w, 3 - true_boxes = Input(shape=(1, 1, 1, max_box_per_image, 4)) - true_yolo_1 = Input(shape=(None, None, len(anchors)//6, 4+1+nb_class)) # grid_h, grid_w, nb_anchor, 5+nb_class - true_yolo_2 = Input(shape=(None, None, len(anchors)//6, 4+1+nb_class)) # grid_h, grid_w, nb_anchor, 5+nb_class - true_yolo_3 = Input(shape=(None, None, len(anchors)//6, 4+1+nb_class)) # grid_h, grid_w, nb_anchor, 5+nb_class - - # Layer 0 => 4 - x = _conv_block(input_image, [{'filter': 32, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 0}, - {'filter': 64, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 1}, - {'filter': 32, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 2}, - {'filter': 64, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 3}]) - - # Layer 5 => 8 - x = _conv_block(x, [{'filter': 128, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 5}, - {'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 6}, - {'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 7}]) - - # Layer 9 => 11 - x = _conv_block(x, [{'filter': 64, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 9}, - {'filter': 128, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 10}]) - - # Layer 12 => 15 - x = _conv_block(x, [{'filter': 256, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 12}, - {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 13}, - {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 14}]) - - # Layer 16 => 36 - for i in range(7): - x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 16+i*3}, - {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 17+i*3}]) - - skip_36 = x - - # Layer 37 => 40 - x = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 37}, - {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 38}, - {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 39}]) - - # Layer 41 => 61 - for i in range(7): - x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 41+i*3}, - {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 42+i*3}]) - - skip_61 = x - - # Layer 62 => 65 - x = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 2, 'bnorm': True, 'leaky': True, 'layer_idx': 62}, - {'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 63}, - {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 64}]) - - # Layer 66 => 74 - for i in range(3): - x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 66+i*3}, - {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 67+i*3}]) - - # Layer 75 => 79 - x = _conv_block(x, [{'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 75}, - {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 76}, - {'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 77}, - {'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 78}, - {'filter': 512, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 79}], do_skip=False) - - # Layer 80 => 82 - pred_yolo_1 = _conv_block(x, [{'filter': 1024, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 80}, - {'filter': (3*(5+nb_class)), 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 81}], do_skip=False) - loss_yolo_1 = YoloLayer(anchors[12:], - [1*num for num in max_grid], - batch_size, - warmup_batches, - ignore_thresh, - grid_scales[0], - obj_scale, - noobj_scale, - xywh_scale, - class_scale)([input_image, pred_yolo_1, true_yolo_1, true_boxes]) - - # Layer 83 => 86 - x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 84}], do_skip=False) - x = UpSampling2D(2)(x) - x = concatenate([x, skip_61]) - - # Layer 87 => 91 - x = _conv_block(x, [{'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 87}, - {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 88}, - {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 89}, - {'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 90}, - {'filter': 256, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 91}], do_skip=False) - - # Layer 92 => 94 - pred_yolo_2 = _conv_block(x, [{'filter': 512, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 92}, - {'filter': (3*(5+nb_class)), 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 93}], do_skip=False) - loss_yolo_2 = YoloLayer(anchors[6:12], - [2*num for num in max_grid], - batch_size, - warmup_batches, - ignore_thresh, - grid_scales[1], - obj_scale, - noobj_scale, - xywh_scale, - class_scale)([input_image, pred_yolo_2, true_yolo_2, true_boxes]) - - # Layer 95 => 98 - x = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 96}], do_skip=False) - x = UpSampling2D(2)(x) - x = concatenate([x, skip_36]) - - # Layer 99 => 106 - pred_yolo_3 = _conv_block(x, [{'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 99}, - {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 100}, - {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 101}, - {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 102}, - {'filter': 128, 'kernel': 1, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 103}, - {'filter': 256, 'kernel': 3, 'stride': 1, 'bnorm': True, 'leaky': True, 'layer_idx': 104}, - {'filter': (3*(5+nb_class)), 'kernel': 1, 'stride': 1, 'bnorm': False, 'leaky': False, 'layer_idx': 105}], do_skip=False) - loss_yolo_3 = YoloLayer(anchors[:6], - [4*num for num in max_grid], - batch_size, - warmup_batches, - ignore_thresh, - grid_scales[2], - obj_scale, - noobj_scale, - xywh_scale, - class_scale)([input_image, pred_yolo_3, true_yolo_3, true_boxes]) - - train_model = Model([input_image, true_boxes, true_yolo_1, true_yolo_2, true_yolo_3], [loss_yolo_1, loss_yolo_2, loss_yolo_3]) - infer_model = Model(input_image, [pred_yolo_1, pred_yolo_2, pred_yolo_3]) - - return [train_model, infer_model] - -def dummy_loss(y_true, y_pred): - return tf.sqrt(tf.reduce_sum(y_pred)) \ No newline at end of file diff --git a/imageai/Detection/YOLO/yolov3.py b/imageai/Detection/YOLO/yolov3.py index 35e8b843..efe8cd71 100644 --- a/imageai/Detection/YOLO/yolov3.py +++ b/imageai/Detection/YOLO/yolov3.py @@ -355,4 +355,7 @@ def tiny_yolov3_main(input, num_anchors, num_classes): network_4 = NetworkConv2D_BN_Leaky(input=network_4, channels=256, kernel_size=(3, 3)) network_4 = Conv2D(num_anchors * (num_classes + 5), kernel_size=(1,1))(network_4) - return Model(input, [network_3, network_4]) \ No newline at end of file + return Model(input, [network_3, network_4]) + +def dummy_loss(y_true, y_pred): + return tf.sqrt(tf.reduce_sum(y_pred)) \ No newline at end of file From 8d90f2023ab2bc2ab26999334c26ba07e8b7aa8f Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Sat, 2 Jan 2021 22:38:34 +0100 Subject: [PATCH 08/12] resolved issues with ResNet50 and added updates for deprecated functions --- README.md | 2 +- .../CUSTOMCLASSIFICATION.md} | 0 .../CUSTOMTRAINING.md | 2 +- imageai/Classification/Custom/__init__.py | 225 ++++++----- imageai/Classification/__init__.py | 155 ++----- imageai/Prediction/DenseNet/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 179 -> 0 bytes .../__pycache__/densenet.cpython-35.pyc | Bin 12212 -> 0 bytes .../__pycache__/imagenet_utils.cpython-35.pyc | Bin 44685 -> 0 bytes .../__pycache__/subpixel.cpython-35.pyc | Bin 3931 -> 0 bytes .../tensorflow_backend.cpython-35.pyc | Bin 532 -> 0 bytes imageai/Prediction/DenseNet/densenet.py | 375 ----------------- imageai/Prediction/DenseNet/imagenet_utils.py | 173 -------- imageai/Prediction/DenseNet/subpixel.py | 74 ---- .../Prediction/DenseNet/tensorflow_backend.py | 11 - imageai/Prediction/InceptionV3/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 182 -> 0 bytes .../__pycache__/imagenet_utils.cpython-35.pyc | Bin 44688 -> 0 bytes .../__pycache__/inceptionv3.cpython-35.pyc | Bin 9641 -> 0 bytes .../Prediction/InceptionV3/imagenet_utils.py | 173 -------- imageai/Prediction/InceptionV3/inceptionv3.py | 381 ------------------ imageai/Prediction/ResNet/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 177 -> 0 bytes .../__pycache__/resnet50.cpython-35.pyc | Bin 3727 -> 0 bytes imageai/Prediction/ResNet/resnet50.py | 158 -------- imageai/Prediction/SqueezeNet/__init__.py | 0 .../__pycache__/__init__.cpython-35.pyc | Bin 181 -> 0 bytes .../__pycache__/squeezenet.cpython-35.pyc | Bin 3092 -> 0 bytes imageai/Prediction/SqueezeNet/squeezenet.py | 110 ----- test/test_custom_object_detection.py | 21 +- test/test_custom_recognition.py | 35 +- test/test_custom_video_detection.py | 18 +- test/test_detection_model_training.py | 21 +- test/test_image_recognition.py | 71 +--- test/test_model_training.py | 36 +- test/test_object_detection.py | 38 +- test/test_video_object_detection.py | 31 +- 37 files changed, 216 insertions(+), 1894 deletions(-) rename imageai/{Prediction/CUSTOMPREDICTION.md => Classification/CUSTOMCLASSIFICATION.md} (100%) rename imageai/{Prediction => Classification}/CUSTOMTRAINING.md (99%) delete mode 100644 imageai/Prediction/DenseNet/__init__.py delete mode 100644 imageai/Prediction/DenseNet/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Prediction/DenseNet/__pycache__/densenet.cpython-35.pyc delete mode 100644 imageai/Prediction/DenseNet/__pycache__/imagenet_utils.cpython-35.pyc delete mode 100644 imageai/Prediction/DenseNet/__pycache__/subpixel.cpython-35.pyc delete mode 100644 imageai/Prediction/DenseNet/__pycache__/tensorflow_backend.cpython-35.pyc delete mode 100644 imageai/Prediction/DenseNet/densenet.py delete mode 100644 imageai/Prediction/DenseNet/imagenet_utils.py delete mode 100644 imageai/Prediction/DenseNet/subpixel.py delete mode 100644 imageai/Prediction/DenseNet/tensorflow_backend.py delete mode 100644 imageai/Prediction/InceptionV3/__init__.py delete mode 100644 imageai/Prediction/InceptionV3/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Prediction/InceptionV3/__pycache__/imagenet_utils.cpython-35.pyc delete mode 100644 imageai/Prediction/InceptionV3/__pycache__/inceptionv3.cpython-35.pyc delete mode 100644 imageai/Prediction/InceptionV3/imagenet_utils.py delete mode 100644 imageai/Prediction/InceptionV3/inceptionv3.py delete mode 100644 imageai/Prediction/ResNet/__init__.py delete mode 100644 imageai/Prediction/ResNet/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Prediction/ResNet/__pycache__/resnet50.cpython-35.pyc delete mode 100644 imageai/Prediction/ResNet/resnet50.py delete mode 100644 imageai/Prediction/SqueezeNet/__init__.py delete mode 100644 imageai/Prediction/SqueezeNet/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Prediction/SqueezeNet/__pycache__/squeezenet.cpython-35.pyc delete mode 100644 imageai/Prediction/SqueezeNet/squeezenet.py diff --git a/README.md b/README.md index eafad979..f8ae68c3 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ You can use your custom models trained with SqueezeNet, ResNet50, InceptionV3 an Click the link below to see the guide to sample training codes, explanations, and best practices guide. -[>>> Tutorials & Documentation](imageai/Prediction/CUSTOMPREDICTION.md) +[>>> Tutorials & Documentation](imageai/Classification/CUSTOMCLASSIFICATION.md) diff --git a/imageai/Prediction/CUSTOMPREDICTION.md b/imageai/Classification/CUSTOMCLASSIFICATION.md similarity index 100% rename from imageai/Prediction/CUSTOMPREDICTION.md rename to imageai/Classification/CUSTOMCLASSIFICATION.md diff --git a/imageai/Prediction/CUSTOMTRAINING.md b/imageai/Classification/CUSTOMTRAINING.md similarity index 99% rename from imageai/Prediction/CUSTOMTRAINING.md rename to imageai/Classification/CUSTOMTRAINING.md index d4eb947c..9dd79c96 100644 --- a/imageai/Prediction/CUSTOMTRAINING.md +++ b/imageai/Classification/CUSTOMTRAINING.md @@ -505,7 +505,7 @@ Let us explain the details shown above: 3. The line `Epoch 00000: saving model to C:\Users\User\PycharmProjects\ImageAITest\pets\models\model_ex-000_acc-0.100000.h5` refers to the model saved after the present experiment. The **ex_000** represents the experiment at this stage while the **acc_0.100000** and **val_acc: 0.1000** represents the accuracy of the model on the test images after the present experiment (maximum value value of accuracy is 1.0). This result helps to know the best performed model you can use for custom image prediction. Once you are done training your custom model, you can use the "CustomImagePrediction" class to perform image prediction with your model. Simply follow the link below. -[imageai/Prediction/CUSTOMPREDICTION.md](https://github.com/OlafenwaMoses/ImageAI/blob/master/imageai/Prediction/CUSTOMPREDICTION.md) +[imageai/Classification/CUSTOMCLASSIFICATION.md](https://github.com/OlafenwaMoses/ImageAI/blob/master/imageai/Classification/CUSTOMCLASSIFICATION.md) ### Saving Full Custom Model
diff --git a/imageai/Classification/Custom/__init__.py b/imageai/Classification/Custom/__init__.py index bb1038fe..49c16c51 100644 --- a/imageai/Classification/Custom/__init__.py +++ b/imageai/Classification/Custom/__init__.py @@ -11,9 +11,7 @@ class ClassificationModelTrainer: """ This is the Classification Model training class, that allows you to define a deep learning network from the 4 available networks types supported by ImageAI which are MobileNetv2, ResNet50, - InceptionV3 and DenseNet121. Once you instantiate this class, you must call: - - * + InceptionV3 and DenseNet121. """ def __init__(self): @@ -30,7 +28,8 @@ def __init__(self): self.__model_collection = [] - + def setModelTypeAsSqueezeNet(self): + raise ValueError("ImageAI no longer support SqueezeNet. You can use MobileNetV2 instead by downloading the MobileNetV2 model and call the function 'setModelTypeAsMobileNetV2'") def setModelTypeAsMobileNetV2(self): """ @@ -40,6 +39,10 @@ def setModelTypeAsMobileNetV2(self): """ self.__modelType = "mobilenetv2" + @deprecated(since="2.1.6", message="'.setModelTypeAsResNet()' has been deprecated! Please use 'setModelTypeAsResNet50()' instead.") + def setModelTypeAsResNet(self): + return self.setModelTypeAsResNet50() + def setModelTypeAsResNet50(self): """ 'setModelTypeAsResNet()' is used to set the model type to the ResNet model @@ -48,6 +51,11 @@ def setModelTypeAsResNet50(self): """ self.__modelType = "resnet50" + + @deprecated(since="2.1.6", message="'.setModelTypeAsDenseNet()' has been deprecated! Please use 'setModelTypeAsDenseNet121()' instead.") + def setModelTypeAsDenseNet(self): + return self.setModelTypeAsDenseNet121() + def setModelTypeAsDenseNet121(self): """ 'setModelTypeAsDenseNet()' is used to set the model type to the DenseNet model @@ -139,35 +147,34 @@ def lr_schedule(self, epoch): def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch_size = 32, initial_learning_rate=1e-3, show_network_summary=False, training_image_size = 224, continue_from_model=None, transfer_from_model=None, transfer_with_full_training=True, initial_num_objects = None, save_full_model = False): """ - 'trainModel()' function starts the model actual training. It accepts the following values: - - num_objects , which is the number of classes present in the dataset that is to be used for training - - num_experiments , also known as epochs, it is the number of times the network will train on all the training dataset - - enhance_data (optional) , this is used to modify the dataset and create more instance of the training set to enhance the training result - - batch_size (optional) , due to memory constraints, the network trains on a batch at once, until all the training set is exhausted. The value is set to 32 by default, but can be increased or decreased depending on the meormory of the compute used for training. The batch_size is conventionally set to 16, 32, 64, 128. - - initial_learning_rate(optional) , this value is used to adjust the weights generated in the network. You rae advised to keep this value as it is if you don't have deep understanding of this concept. - - show_network_summary(optional) , this value is used to show the structure of the network should you desire to see it. It is set to False by default - - training_image_size(optional) , this value is used to define the image size on which the model will be trained. The value is 224 by default and is kept at a minimum of 100. - - continue_from_model (optional) , this is used to set the path to a model file trained on the same dataset. It is primarily for continuos training from a previously saved model. - - transfer_from_model (optional) , this is used to set the path to a model file trained on another dataset. It is primarily used to perform tramsfer learning. - - transfer_with_full_training (optional) , this is used to set the pre-trained model to be re-trained across all the layers or only at the top layers. - - initial_num_objects (required if 'transfer_from_model' is set ), this is used to set the number of objects the model used for transfer learning is trained on. If 'transfer_from_model' is set, this must be set as well. - - save_full_model ( optional ), this is used to save the trained models with their network types. Any model saved by this specification can be loaded without specifying the network type. - - * - - :param num_objects: - :param num_experiments: - :param enhance_data: - :param batch_size: - :param initial_learning_rate: - :param show_network_summary: - :param training_image_size: - :param continue_from_model: - :param transfer_from_model: - :param initial_num_objects: - :param save_full_model: - :return: - """ + 'trainModel()' function starts the model actual training. It accepts the following values: + - num_objects , which is the number of classes present in the dataset that is to be used for training + - num_experiments , also known as epochs, it is the number of times the network will train on all the training dataset + - enhance_data (optional) , this is used to modify the dataset and create more instance of the training set to enhance the training result + - batch_size (optional) , due to memory constraints, the network trains on a batch at once, until all the training set is exhausted. The value is set to 32 by default, but can be increased or decreased depending on the meormory of the compute used for training. The batch_size is conventionally set to 16, 32, 64, 128. + - initial_learning_rate(optional) , this value is used to adjust the weights generated in the network. You rae advised to keep this value as it is if you don't have deep understanding of this concept. + - show_network_summary(optional) , this value is used to show the structure of the network should you desire to see it. It is set to False by default + - training_image_size(optional) , this value is used to define the image size on which the model will be trained. The value is 224 by default and is kept at a minimum of 100. + - continue_from_model (optional) , this is used to set the path to a model file trained on the same dataset. It is primarily for continuos training from a previously saved model. + - transfer_from_model (optional) , this is used to set the path to a model file trained on another dataset. It is primarily used to perform tramsfer learning. + - transfer_with_full_training (optional) , this is used to set the pre-trained model to be re-trained across all the layers or only at the top layers. + - initial_num_objects (required if 'transfer_from_model' is set ), this is used to set the number of objects the model used for transfer learning is trained on. If 'transfer_from_model' is set, this must be set as well. + - save_full_model ( optional ), this is used to save the trained models with their network types. Any model saved by this specification can be loaded without specifying the network type. + + + :param num_objects: + :param num_experiments: + :param enhance_data: + :param batch_size: + :param initial_learning_rate: + :param show_network_summary: + :param training_image_size: + :param continue_from_model: + :param transfer_from_model: + :param initial_num_objects: + :param save_full_model: + :return: + """ self.__num_epochs = num_experiments self.__initial_learning_rate = initial_learning_rate lr_scheduler = tf.keras.callbacks.LearningRateScheduler(self.lr_schedule) @@ -236,10 +243,67 @@ def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch model = tf.keras.models.Model(inputs=base_model.input, outputs=network) elif (self.__modelType == "inceptionv3"): - None + + if (continue_from_model != None): + model = tf.keras.applications.InceptionV3(input_shape=(training_image_size, training_image_size, 3), weights=continue_from_model, classes=num_objects, + include_top=True) + if (show_network_summary == True): + print("Training using weights from a previouly model") + elif (transfer_from_model != None): + base_model = tf.keras.applications.InceptionV3(input_shape=(training_image_size, training_image_size, 3), weights= transfer_from_model, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.model.Models(inputs=base_model.input, outputs=network) + + if (show_network_summary == True): + print("Training using weights from a pre-trained ImageNet model") + else: + base_model = tf.keras.applications.InceptionV3(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.models.Model(inputs=base_model.input, outputs=network) + + base_model = tf.keras.applications.InceptionV3(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") elif (self.__modelType == "densenet121"): - None + if (continue_from_model != None): + model = tf.keras.applications.DenseNet121(input_shape=(training_image_size, training_image_size, 3), weights=continue_from_model, classes=num_objects, + include_top=True) + if (show_network_summary == True): + print("Training using weights from a previouly model") + elif (transfer_from_model != None): + base_model = tf.keras.applications.DenseNet121(input_shape=(training_image_size, training_image_size, 3), weights= transfer_from_model, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.model.Models(inputs=base_model.input, outputs=network) + + if (show_network_summary == True): + print("Training using weights from a pre-trained ImageNet model") + else: + base_model = tf.keras.applications.DenseNet121(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") + + network = base_model.output + network = tf.keras.layers.Dense(num_objects, activation='softmax', + use_bias=True)(network) + + model = tf.keras.models.Model(inputs=base_model.input, outputs=network) + + base_model = tf.keras.applications.DenseNet121(input_shape=(training_image_size, training_image_size, 3), weights= None, classes=num_objects, + include_top=False, pooling="avg") optimizer = tf.keras.optimizers.Adam(lr=self.__initial_learning_rate, decay=1e-4) @@ -249,7 +313,7 @@ def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch model_name = 'model_ex-{epoch:03d}_acc-{accuracy:03f}.h5' - #log_name = '{}_lr-{}_{}'.format(self.__modelType, initial_learning_rate, time.strftime("%Y-%m-%d-%H-%M-%S")) + log_name = '{}_lr-{}_{}'.format(self.__modelType, initial_learning_rate, time.strftime("%Y-%m-%d-%H-%M-%S")) if not os.path.isdir(self.__trained_model_dir): os.makedirs(self.__trained_model_dir) @@ -263,9 +327,9 @@ def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch model_path = os.path.join(self.__trained_model_dir, model_name) - #logs_path = os.path.join(self.__logs_dir, log_name) - #if not os.path.isdir(logs_path): - # os.makedirs(logs_path) + logs_path = os.path.join(self.__logs_dir, log_name) + if not os.path.isdir(logs_path): + os.makedirs(logs_path) save_weights_condition = True @@ -281,10 +345,10 @@ def trainModel(self, num_objects, num_experiments=200, enhance_data=False, batch period=1) - #tensorboard = tf.keras.callbacks.TensorBoard(log_dir=logs_path, - # histogram_freq=0, - # write_graph=False, - # write_images=False) + tensorboard = tf.keras.callbacks.TensorBoard(log_dir=logs_path, + histogram_freq=0, + write_graph=False, + write_images=False) if (enhance_data == True): @@ -448,15 +512,16 @@ def loadModel(self, classification_speed="normal", num_objects=10): try: None except: - raise ("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) elif(self.__modelType == "resnet50"): try: - model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = num_objects ) + model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=None, classes = num_objects ) + model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True except: - raise ("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) elif (self.__modelType == "densenet121"): try: @@ -464,7 +529,7 @@ def loadModel(self, classification_speed="normal", num_objects=10): self.__model_collection.append(model) self.__modelLoaded = True except: - raise ("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) elif (self.__modelType == "inceptionv3"): try: @@ -472,7 +537,7 @@ def loadModel(self, classification_speed="normal", num_objects=10): self.__model_collection.append(model) self.__modelLoaded = True except: - raise ("An error occured. Ensure your model file is in {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is in {}".format(self.modelPath)) def loadFullModel(self, classification_speed="normal", num_objects=10): """ 'loadFullModel()' function is used to load the model structure into the program from the file path defined @@ -566,7 +631,7 @@ def classifyImage(self, image_input, result_count=5, input_type="file"): if (self.__modelType == "mobilenetv2"): image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "resnet50" or self.__modelType == "full"): + elif (self.__modelType == "full"): image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) elif (self.__modelType == "inceptionv3"): image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) @@ -597,65 +662,5 @@ def classifyImage(self, image_input, result_count=5, input_type="file"): @deprecated(since="2.1.6", message="'.predictImage()' has been deprecated! Please use 'classifyImage()' instead.") def predictImage(self, image_input, result_count=5, input_type="file"): - classification_results = [] - classification_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making classification.") - - else: - if (input_type == "file"): - try: - image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (self.__modelType == "mobilenetv2"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "resnet50"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "inceptionv3"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "densenet121"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - try: - model = self.__model_collection[0] - prediction = model.predict(image_to_predict, steps=1) - - predictiondata = [] - for pred in prediction: - top_indices = pred.argsort()[-result_count:][::-1] - for i in top_indices: - each_result = [] - each_result.append(self.__model_classes[str(i)]) - each_result.append(pred[i]) - predictiondata.append(each_result) - - for result in predictiondata: - classification_results.append(str(result[0])) - classification_probabilities.append(result[1] * 100) - - except: - raise ValueError("Error. Ensure your input image is valid") - return classification_results, classification_probabilities \ No newline at end of file + return self.classifyImage(image_input, result_count, input_type) \ No newline at end of file diff --git a/imageai/Classification/__init__.py b/imageai/Classification/__init__.py index b338ed8b..f02928e5 100644 --- a/imageai/Classification/__init__.py +++ b/imageai/Classification/__init__.py @@ -6,18 +6,18 @@ class ImageClassification: """ - This is the image classification class in the ImageAI library. It provides support for 4 different models which are: - ResNet, MobileNetV2, DenseNet and Inception V3. After instantiating this class, you can set it's properties and - make image classification using it's pre-defined functions. - - The following functions are required to be called before a classification can be made - * setModelPath() - * At least of of the following and it must correspond to the model set in the setModelPath() - [setModelTypeAsMobileNetv2(), setModelTypeAsResNet(), setModelTypeAsDenseNet, setModelTypeAsInceptionV3] - * loadModel() [This must be called once only before making a classification] - - Once the above functions have been called, you can call the classifyImage() function of the classification instance - object at anytime to classify an image. + This is the image classification class in the ImageAI library. It provides support for 4 different models which are: + ResNet, MobileNetV2, DenseNet and Inception V3. After instantiating this class, you can set it's properties and + make image classification using it's pre-defined functions. + + The following functions are required to be called before a classification can be made + * setModelPath() + * At least of of the following and it must correspond to the model set in the setModelPath() + [setModelTypeAsMobileNetv2(), setModelTypeAsResNet(), setModelTypeAsDenseNet, setModelTypeAsInceptionV3] + * loadModel() [This must be called once only before making a classification] + + Once the above functions have been called, you can call the classifyImage() function of the classification instance + object at anytime to classify an image. """ def __init__(self): self.__modelType = "" @@ -35,6 +35,10 @@ def setModelPath(self, model_path): :return: """ self.modelPath = model_path + + def setModelTypeAsSqueezeNet(self): + raise ValueError("ImageAI no longer support SqueezeNet. You can use MobileNetV2 instead by downloading the MobileNetV2 model and call the function 'setModelTypeAsMobileNetV2'") + def setModelTypeAsMobileNetV2(self): """ 'setModelTypeAsMobileNetV2()' is used to set the model type to the MobileNetV2 model @@ -43,6 +47,10 @@ def setModelTypeAsMobileNetV2(self): """ self.__modelType = "mobilenetv2" + @deprecated(since="2.1.6", message="'.setModelTypeAsResNet()' has been deprecated! Please use 'setModelTypeAsResNet50()' instead.") + def setModelTypeAsResNet(self): + return self.setModelTypeAsResNet50() + def setModelTypeAsResNet50(self): """ 'setModelTypeAsResNet50()' is used to set the model type to the ResNet50 model @@ -51,6 +59,10 @@ def setModelTypeAsResNet50(self): """ self.__modelType = "resnet50" + @deprecated(since="2.1.6", message="'.setModelTypeAsDenseNet()' has been deprecated! Please use 'setModelTypeAsDenseNet121()' instead.") + def setModelTypeAsDenseNet(self): + return self.setModelTypeAsDenseNet121() + def setModelTypeAsDenseNet121(self): """ 'setModelTypeAsDenseNet121()' is used to set the model type to the DenseNet121 model @@ -90,43 +102,45 @@ def loadModel(self, classification_speed="normal"): if (self.__modelLoaded == False): - image_input = tf.keras.layers.Input(shape=(self.__input_image_size, self.__input_image_size, 3)) - if(self.__modelType == "" ): raise ValueError("You must set a valid model type before loading the model.") elif(self.__modelType == "mobilenetv2"): - model = tf.keras.applications.MobileNetV2(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + model = tf.keras.applications.MobileNetV2(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=None, classes = 1000 ) + model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True try: None except: - raise ("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is a MobileNetV2 Model and is located in the path {}".format(self.modelPath)) elif(self.__modelType == "resnet50"): try: - model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + model = tf.keras.applications.ResNet50(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=None, classes = 1000 ) + model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True - except: - raise ("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) + except Exception as e: + raise ValueError("An error occured. Ensure your model file is a ResNet50 Model and is located in the path {}".format(self.modelPath)) elif (self.__modelType == "densenet121"): try: - model = tf.keras.applications.DenseNet121(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + model = tf.keras.applications.DenseNet121(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=None, classes = 1000 ) + model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True except: - raise ("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is a DenseNet121 Model and is located in the path {}".format(self.modelPath)) elif (self.__modelType == "inceptionv3"): try: - model = tf.keras.applications.InceptionV3(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=self.modelPath, classes = 1000 ) + model = tf.keras.applications.InceptionV3(input_shape=(self.__input_image_size, self.__input_image_size, 3), weights=None, classes = 1000 ) + model.load_weights(self.modelPath) self.__model_collection.append(model) self.__modelLoaded = True except: - raise ("An error occured. Ensure your model file is in {}".format(self.modelPath)) + raise ValueError("An error occured. Ensure your model file is in {}".format(self.modelPath)) def classifyImage(self, image_input, result_count=5, input_type="file"): @@ -180,15 +194,14 @@ def classifyImage(self, image_input, result_count=5, input_type="file"): except: raise ValueError("You have parsed in a wrong stream for the image") - + if (self.__modelType == "mobilenetv2"): image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "resnet50"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "inceptionv3"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) elif (self.__modelType == "densenet121"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + image_to_predict = tf.keras.applications.densenet.preprocess_input(image_to_predict) + elif (self.__modelType == "inceptionv3"): + image_to_predict = tf.keras.applications.inception_v3.preprocess_input(image_to_predict) + try: model = self.__model_collection[0] prediction = model.predict(image_to_predict, steps=1) @@ -216,85 +229,5 @@ def classifyImage(self, image_input, result_count=5, input_type="file"): @deprecated(since="2.1.6", message="'.predictImage()' has been deprecated! Please use 'classifyImage()' instead.") def predictImage(self, image_input, result_count=5, input_type="file"): - """ - 'classifyImage()' function is used to classify a given image by receiving the following arguments: - * input_type (optional) , the type of input to be parsed. Acceptable values are "file", "array" and "stream" - * image_input , file path/numpy array/image file stream of the image. - * result_count (optional) , the number of classifications to be sent which must be whole numbers between - 1 and 1000. The default is 5. - - This function returns 2 arrays namely 'classification_results' and 'classification_probabilities'. The 'classification_results' - contains possible objects classes arranged in descending of their percentage probabilities. The 'classification_probabilities' - contains the percentage probability of each object class. The position of each object class in the 'classification_results' - array corresponds with the positions of the percentage probability in the 'classification_probabilities' array. - - - :param input_type: - :param image_input: - :param result_count: - :return classification_results, classification_probabilities: - """ - classification_results = [] - classification_probabilities = [] - if (self.__modelLoaded == False): - raise ValueError("You must call the loadModel() function before making classification.") - - else: - if (input_type == "file"): - try: - image_to_predict = tf.keras.preprocessing.image.load_img(image_input, target_size=(self.__input_image_size, self.__input_image_size)) - image_to_predict = tf.keras.preprocessing.image.img_to_array(image_to_predict, data_format="channels_last") - image_to_predict = np.expand_dims(image_to_predict, axis=0) - except: - raise ValueError("You have set a path to an invalid image file.") - elif (input_type == "array"): - try: - image_input = Image.fromarray(np.uint8(image_input)) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - except: - raise ValueError("You have parsed in a wrong numpy array for the image") - elif (input_type == "stream"): - try: - image_input = Image.open(image_input) - image_input = image_input.resize((self.__input_image_size, self.__input_image_size)) - image_input = np.expand_dims(image_input, axis=0) - image_to_predict = image_input.copy() - image_to_predict = np.asarray(image_to_predict, dtype=np.float64) - - except: - raise ValueError("You have parsed in a wrong stream for the image") - - if (self.__modelType == "mobilenetv2"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "resnet50"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "inceptionv3"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - elif (self.__modelType == "densenet121"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) - try: - model = self.__model_collection[0] - prediction = model.predict(image_to_predict, steps=1) - - if (self.__modelType == "mobilenetv2"): - predictiondata = tf.keras.applications.mobilenet_v2.decode_predictions(prediction, top=int(result_count)) - elif (self.__modelType == "resnet50"): - predictiondata = tf.keras.applications.resnet50.decode_predictions(prediction, top=int(result_count)) - elif (self.__modelType == "inceptionv3"): - predictiondata = tf.keras.applications.inception_v3.decode_predictions(prediction, top=int(result_count)) - elif (self.__modelType == "densenet121"): - predictiondata = tf.keras.applications.densenet.decode_predictions(prediction, top=int(result_count)) - - - - for results in predictiondata: - for result in results: - classification_results.append(str(result[1])) - classification_probabilities.append(result[2] * 100) - except: - raise ValueError("An error occured! Try again.") - - return classification_results, classification_probabilities \ No newline at end of file + + return self.classifyImage(image_input, result_count, input_type) \ No newline at end of file diff --git a/imageai/Prediction/DenseNet/__init__.py b/imageai/Prediction/DenseNet/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/imageai/Prediction/DenseNet/__pycache__/__init__.cpython-35.pyc b/imageai/Prediction/DenseNet/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 7f46d20ee875bc3da2fa344ece25f4cf17c41909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179 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 diff --git a/imageai/Prediction/DenseNet/__pycache__/imagenet_utils.cpython-35.pyc b/imageai/Prediction/DenseNet/__pycache__/imagenet_utils.cpython-35.pyc deleted file mode 100644 index ed74a3f19ae8da74b60be07d8cb5595dd12f908e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44685 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 diff --git a/imageai/Prediction/DenseNet/__pycache__/subpixel.cpython-35.pyc b/imageai/Prediction/DenseNet/__pycache__/subpixel.cpython-35.pyc deleted file mode 100644 index ed05d1b24489b649d291fec8bf12eabb6cb6ec78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3931 zcmb_fTW{RP6&`Xgy4#N9*g={Eg9NQaLtZ;_oV03H)R2PKMY|57I1lO;)GRr>ONqD4 z40VJ2q##8f3Kabp{Y!l9Q~pAp`kmqKawWO$EtJR|4$qwPo$s78JiNEEvUq>>Z@s^+ zF!m2Nb2Ksj6;7Sw65`)sF=L6t_8jKWy~|>kB@MRMsO}pqZnC7s_FA}avarSCHcRH% z-W+p&9ky9G$G*X`IVKzI0?X!^Y_SW*4i@a>Jc}H5LAvMJuD9?VtTN9jZ;HOo;=)Kj zO7ct@OtpR|PNl-&_D~u>D748EKkFaJ!07MXYT1oY=4RxZ%-6XHWI);`M{7LLLkuJ% zhv3hiOD@70I2Jb8pBNip-um&a%l0ssxBv$s0GQzd`vyxezsSNR@(#X^7cR4Kg?)>e zd79bvZj^8A7X8no6B)nAbs*v>9g6Sg7~`0JkkdR5vh;|2jj~k4yqJjbnEd5^p=HRk zl$(*{xyUi~!531*559~N$#FhH{bP5NXK2ib9#e0AwG0AuW&4&0lB;Lx6ua z7(_uNQ)59`aD4j`h^{OYuymz4&eY)tR`c4(m|Sl@d??gObkxbz@F9Zq@S{&2{jBro z$wyCq>UFH#m!n9Ju`Bs?5h!AX1y-_#Hf%?MOJ-l(+^3Vz8F6cgAwq+>iY%Es$w zNJG@fJ8hTRd?uo7KJyF@1trQPA!c%C0(ITyfRd(ADVVP)lc@#|$B`LXwK_VJT-a zxi)szHlBR4&L7+VT!a+atyL`&xeDZgiH@v;TdPXO#j3Z~>2zv}3n;r*PnTl7wc)Lu z)C>ObJg@5Nk3SfoqS6jtrYfJM$Y{bI$aSPNM*$$621QJfLwbX2t|ej>&KCQ)NW=j7 zPEnbvyXN6o(x!n#dKS5jVl0J9sW5Vt1ybuOfRu#4$&TfbQ~)#Jr&+-T(i|ZR4oMn? zVm{#i;JlOAF7odWoHhntzAvJdOcXnzd}4T?eKzuk@a1jss9 z*)diGS**Q%3f&C)tr}Z}cZvkK|6?$%d$h>Up(=|IbWaX5z}WT@iC-(t91Y z|C6XIre0~EXq=#a9h4cG6g48CC2i^0Fc&KbLK*@4T>8|*KPIp=oD zM*~s~(3ntv;daZFiG81pc&)Of>CLauWUzAH_oFm2zF#iSmQ1-clUiQ0Y-$+~6hTnS zw5)x2jF+57v$f#+0rYC$|2zABe|xj{0`E!P`+cVIT7MpkflQA@Zzl_?0_{D0*0aqB zejj%pO~(;6C?e{8u4EWtqs-FY4$4QO!|3VpE2NX3szq4u;M8qgm~+=%aL(SjwrO-G zUIo$pg0N(I@V)57{Q21um?+sGffO#PN1_L}UaOUPbg&|>E{%Sa5Z zugl(i=e%hbIqc3kp4$04gC^Upm)dpY8kW977#`24qop<;jc$2knyt1vZ80D|ly#OE zuWdO|w=q#V{n7y|s&s;@#M~M8hkn&IRzdwa7HLW$=G=5{JDz*nIs3^g=&D-FOG^XF z9y+KNiEH}RySP*ZM-0MO%S8n49A;c*teaG)P5Pt(r>4bBn^-xRqZxeI(5+M7-ow<_}C}8D#|4Im-SFu(U*u5W3cAicx?Dx?e$uz7g k)NY;aBF82vYX@pV%lThaeEgEK#%A0t_m+b{_wM#T0SW9p$N&HU diff --git a/imageai/Prediction/DenseNet/__pycache__/tensorflow_backend.cpython-35.pyc b/imageai/Prediction/DenseNet/__pycache__/tensorflow_backend.cpython-35.pyc deleted file mode 100644 index 34b2a3627ff09b982c84ee5e468601de649e439b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 532 zcmYjO!EV$r5S_%ys@k?(;phuO%V9y{h7ba65A@Kb5<*BTOOZ(?O%}(F>e9vH4q~(WI@tdizty5_l1zX9sVGC<^%(~6tki#C}4icse_M8DY%>8)lF3)ZS z90d0h-!A0GfI*I(4k$y{wa=xc-kmO|T>{s+8>rPvXCOm6!q)xWZ%P?x{wUHi! zGZXdB%2;gQecHt1Mw70XGiX|gSQ>YYKIYWM2XlEvuHxz%S4Av)J5ChKP-}xeT`USW z(NFM8#((ykmx>kope|ez`rhak?X^~CuAT<86x7>ubw21T'BGR' - x = x[::-1, ...] - # Zero-center by mean pixel - x[0, :, :] -= 103.939 - x[1, :, :] -= 116.779 - x[2, :, :] -= 123.68 - else: - x = x[:, ::-1, ...] - x[:, 0, :, :] -= 103.939 - x[:, 1, :, :] -= 116.779 - x[:, 2, :, :] -= 123.68 - else: - # 'RGB'->'BGR' - x = x[..., ::-1] - # Zero-center by mean pixel - x[..., 0] -= 103.939 - x[..., 1] -= 116.779 - x[..., 2] -= 123.68 - - x *= 0.017 # scale values - - return x - - -def DenseNet(model_input, depth=40, nb_dense_block=3, growth_rate=12, nb_filter=-1, nb_layers_per_block=-1, - bottleneck=False, reduction=0.0, dropout_rate=0.0, weight_decay=1e-4, subsample_initial_block=False, - include_top=True, weights=None, input_tensor=None, - classes=10, activation='softmax', model_path = '', initial_num_classes=None, transfer_with_full_training = True): - - - if weights == 'imagenet' and include_top and classes != 1000: - raise ValueError('If using `weights` as ImageNet with `include_top`' - ' as true, `classes` should be 1000') - - if activation not in ['softmax', 'sigmoid']: - raise ValueError('activation must be one of "softmax" or "sigmoid"') - - if activation == 'sigmoid' and classes != 1: - raise ValueError('sigmoid activation can only be used when classes = 1') - - # Determine proper input shape - img_input = model_input - - x = __create_dense_net(classes, img_input, include_top, depth, nb_dense_block, - growth_rate, nb_filter, nb_layers_per_block, bottleneck, reduction, - dropout_rate, weight_decay, subsample_initial_block, activation, initial_num_classes=initial_num_classes) - - # Ensure that the model takes into account - # any potential predecessors of `input_tensor`. - inputs = img_input - # Create model. - model = Model(inputs, x, name='densenet') - - # load weights - if (weights == 'imagenet'): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "trained"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "continued"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "transfer"): - weights_path = model_path - model.load_weights(weights_path) - - if (transfer_with_full_training == False): - for eachlayer in model.layers: - eachlayer.trainable = False - print("Training with top layers of the Model") - else: - print("Training with all layers of the Model") - - x2 = model.layers[-2].output - x2 = Dense(classes, activation=activation)(x2) - - - new_model = Model(inputs=model.input, outputs=x2) - - return new_model - elif (weights == "custom"): - return model - - - - - -def DenseNetImageNet121(model_input=None, - bottleneck=True, - reduction=0.5, - dropout_rate=0.0, - weight_decay=1e-4, - include_top=True, - weights='imagenet', - input_tensor=None, - classes=1000, - activation='softmax', model_path = '', initial_num_classes= None, transfer_with_full_training = True): - return DenseNet(model_input, depth=121, nb_dense_block=4, growth_rate=32, nb_filter=64, - nb_layers_per_block=[6, 12, 24, 16], bottleneck=bottleneck, reduction=reduction, - dropout_rate=dropout_rate, weight_decay=weight_decay, subsample_initial_block=True, - include_top=include_top, weights=weights, input_tensor=input_tensor, - classes=classes, activation=activation, model_path=model_path, initial_num_classes=initial_num_classes, transfer_with_full_training = transfer_with_full_training) - - - -def __conv_block(ip, nb_filter, bottleneck=False, dropout_rate=None, weight_decay=1e-4): - ''' Apply BatchNorm, Relu, 3x3 Conv2D, optional bottleneck block and dropout - Args: - ip: Input keras tensor - nb_filter: number of filters - bottleneck: add bottleneck block - dropout_rate: dropout rate - weight_decay: weight decay factor - Returns: keras tensor with batch_norm, relu and convolution2d added (optional bottleneck) - ''' - concat_axis = 1 if K.image_data_format() == 'channels_first' else -1 - - x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(ip) - x = Activation('relu')(x) - - if bottleneck: - inter_channel = nb_filter * 4 # Obtained from https://github.com/liuzhuang13/DenseNet/blob/master/densenet.lua - - x = Conv2D(inter_channel, (1, 1), kernel_initializer='he_normal', padding='same', use_bias=False, - kernel_regularizer=l2(weight_decay))(x) - x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x) - x = Activation('relu')(x) - - x = Conv2D(nb_filter, (3, 3), kernel_initializer='he_normal', padding='same', use_bias=False)(x) - if dropout_rate: - x = Dropout(dropout_rate)(x) - - return x - - -def __dense_block(x, nb_layers, nb_filter, growth_rate, bottleneck=False, dropout_rate=None, weight_decay=1e-4, - grow_nb_filters=True, return_concat_list=False): - ''' Build a dense_block where the output of each conv_block is fed to subsequent ones - Args: - x: keras tensor - nb_layers: the number of layers of conv_block to append to the model. - nb_filter: number of filters - growth_rate: growth rate - bottleneck: bottleneck block - dropout_rate: dropout rate - weight_decay: weight decay factor - grow_nb_filters: flag to decide to allow number of filters to grow - return_concat_list: return the list of feature maps along with the actual output - Returns: keras tensor with nb_layers of conv_block appended - ''' - concat_axis = 1 if K.image_data_format() == 'channels_first' else -1 - - x_list = [x] - - for i in range(nb_layers): - cb = __conv_block(x, growth_rate, bottleneck, dropout_rate, weight_decay) - x_list.append(cb) - - x = concatenate([x, cb], axis=concat_axis) - - if grow_nb_filters: - nb_filter += growth_rate - - if return_concat_list: - return x, nb_filter, x_list - else: - return x, nb_filter - - -def __transition_block(ip, nb_filter, compression=1.0, weight_decay=1e-4): - ''' Apply BatchNorm, Relu 1x1, Conv2D, optional compression, dropout and Maxpooling2D - Args: - ip: keras tensor - nb_filter: number of filters - compression: calculated as 1 - reduction. Reduces the number of feature maps - in the transition block. - dropout_rate: dropout rate - weight_decay: weight decay factor - Returns: keras tensor, after applying batch_norm, relu-conv, dropout, maxpool - ''' - concat_axis = 1 if K.image_data_format() == 'channels_first' else -1 - - x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(ip) - x = Activation('relu')(x) - x = Conv2D(int(nb_filter * compression), (1, 1), kernel_initializer='he_normal', padding='same', use_bias=False, - kernel_regularizer=l2(weight_decay))(x) - x = AveragePooling2D((2, 2), strides=(2, 2))(x) - - return x - - -def __transition_up_block(ip, nb_filters, type='deconv', weight_decay=1E-4): - ''' SubpixelConvolutional Upscaling (factor = 2) - Args: - ip: keras tensor - nb_filters: number of layers - type: can be 'upsampling', 'subpixel', 'deconv'. Determines type of upsampling performed - weight_decay: weight decay factor - Returns: keras tensor, after applying upsampling operation. - ''' - - if type == 'upsampling': - x = UpSampling2D()(ip) - elif type == 'subpixel': - x = Conv2D(nb_filters, (3, 3), activation='relu', padding='same', kernel_regularizer=l2(weight_decay), - use_bias=False, kernel_initializer='he_normal')(ip) - x = SubPixelUpscaling(scale_factor=2)(x) - x = Conv2D(nb_filters, (3, 3), activation='relu', padding='same', kernel_regularizer=l2(weight_decay), - use_bias=False, kernel_initializer='he_normal')(x) - else: - x = Conv2DTranspose(nb_filters, (3, 3), activation='relu', padding='same', strides=(2, 2), - kernel_initializer='he_normal', kernel_regularizer=l2(weight_decay))(ip) - - return x - - -def __create_dense_net(nb_classes, img_input, include_top, depth=40, nb_dense_block=3, growth_rate=12, nb_filter=-1, - nb_layers_per_block=-1, bottleneck=False, reduction=0.0, dropout_rate=None, weight_decay=1e-4, - subsample_initial_block=False, activation='softmax', initial_num_classes=None): - ''' Build the DenseNet model - Args: - nb_classes: number of classes - img_input: tuple of shape (channels, rows, columns) or (rows, columns, channels) - include_top: flag to include the final Dense layer - depth: number or layers - nb_dense_block: number of dense blocks to add to end (generally = 3) - growth_rate: number of filters to add per dense block - nb_filter: initial number of filters. Default -1 indicates initial number of filters is 2 * growth_rate - nb_layers_per_block: number of layers in each dense block. - Can be a -1, positive integer or a list. - If -1, calculates nb_layer_per_block from the depth of the network. - If positive integer, a set number of layers per dense block. - If list, nb_layer is used as provided. Note that list size must - be (nb_dense_block + 1) - bottleneck: add bottleneck blocks - reduction: reduction factor of transition blocks. Note : reduction value is inverted to compute compression - dropout_rate: dropout rate - weight_decay: weight decay rate - subsample_initial_block: Set to True to subsample the initial convolution and - add a MaxPool2D before the dense blocks are added. - subsample_initial: - activation: Type of activation at the top layer. Can be one of 'softmax' or 'sigmoid'. - Note that if sigmoid is used, classes must be 1. - Returns: keras tensor with nb_layers of conv_block appended - ''' - - concat_axis = 1 if K.image_data_format() == 'channels_first' else -1 - - if reduction != 0.0: - assert reduction <= 1.0 and reduction > 0.0, 'reduction value must lie between 0.0 and 1.0' - - # layers in each dense block - if type(nb_layers_per_block) is list or type(nb_layers_per_block) is tuple: - nb_layers = list(nb_layers_per_block) # Convert tuple to list - - assert len(nb_layers) == (nb_dense_block), 'If list, nb_layer is used as provided. ' \ - 'Note that list size must be (nb_dense_block)' - final_nb_layer = nb_layers[-1] - nb_layers = nb_layers[:-1] - else: - if nb_layers_per_block == -1: - assert (depth - 4) % 3 == 0, 'Depth must be 3 N + 4 if nb_layers_per_block == -1' - count = int((depth - 4) / 3) - - if bottleneck: - count = count // 2 - - nb_layers = [count for _ in range(nb_dense_block)] - final_nb_layer = count - else: - final_nb_layer = nb_layers_per_block - nb_layers = [nb_layers_per_block] * nb_dense_block - - # compute initial nb_filter if -1, else accept users initial nb_filter - if nb_filter <= 0: - nb_filter = 2 * growth_rate - - # compute compression factor - compression = 1.0 - reduction - - # Initial convolution - if subsample_initial_block: - initial_kernel = (7, 7) - initial_strides = (2, 2) - else: - initial_kernel = (3, 3) - initial_strides = (1, 1) - - x = Conv2D(nb_filter, initial_kernel, kernel_initializer='he_normal', padding='same', - strides=initial_strides, use_bias=False, kernel_regularizer=l2(weight_decay))(img_input) - - if subsample_initial_block: - x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x) - x = Activation('relu')(x) - x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x) - - # Add dense blocks - for block_idx in range(nb_dense_block - 1): - x, nb_filter = __dense_block(x, nb_layers[block_idx], nb_filter, growth_rate, bottleneck=bottleneck, - dropout_rate=dropout_rate, weight_decay=weight_decay) - # add transition_block - x = __transition_block(x, nb_filter, compression=compression, weight_decay=weight_decay) - nb_filter = int(nb_filter * compression) - - # The last dense_block does not have a transition_block - x, nb_filter = __dense_block(x, final_nb_layer, nb_filter, growth_rate, bottleneck=bottleneck, - dropout_rate=dropout_rate, weight_decay=weight_decay) - - x = BatchNormalization(axis=concat_axis, epsilon=1.1e-5)(x) - x = Activation('relu')(x) - x = GlobalAveragePooling2D(name="global_avg_pooling")(x) - - if include_top: - if (initial_num_classes != None): - x = Dense(initial_num_classes, activation=activation)(x) - else: - x = Dense(nb_classes, activation=activation)(x) - - - return x - - -if __name__ == '__main__': - - - model = DenseNet((32, 32, 3), depth=100, nb_dense_block=3, - growth_rate=12, bottleneck=True, reduction=0.5, weights=None) - model.summary() diff --git a/imageai/Prediction/DenseNet/imagenet_utils.py b/imageai/Prediction/DenseNet/imagenet_utils.py deleted file mode 100644 index 6cf6928f..00000000 --- a/imageai/Prediction/DenseNet/imagenet_utils.py +++ /dev/null @@ -1,173 +0,0 @@ -import json -import warnings - -from tensorflow.python.keras import backend as K - -CLASS_INDEX = None - - -def preprocess_input(x, data_format=None): - """Preprocesses a tensor encoding a batch of images. - - # Arguments - x: input Numpy tensor, 4D. - data_format: data format of the image tensor. - - # Returns - Preprocessed tensor. - """ - if data_format is None: - data_format = K.image_data_format() - assert data_format in {'channels_last', 'channels_first'} - - if data_format == 'channels_first': - if x.ndim == 3: - # 'RGB'->'BGR' - x = x[::-1, ...] - # Zero-center by mean pixel - x[0, :, :] -= 103.939 - x[1, :, :] -= 116.779 - x[2, :, :] -= 123.68 - else: - x = x[:, ::-1, ...] - x[:, 0, :, :] -= 103.939 - x[:, 1, :, :] -= 116.779 - x[:, 2, :, :] -= 123.68 - else: - # 'RGB'->'BGR' - x = x[..., ::-1] - # Zero-center by mean pixel - x[..., 0] -= 103.939 - x[..., 1] -= 116.779 - x[..., 2] -= 123.68 - return x - - -def decode_predictions(preds, top=5): - """Decodes the prediction of an ImageNet model. - - # Arguments - preds: Numpy tensor encoding a batch of predictions. - top: integer, how many top-guesses to return. - - # Returns - A list of lists of top class prediction tuples - `(class_name, class_description, score)`. - One list of tuples per sample in batch input. - - # Raises - ValueError: in case of invalid shape of the `pred` array - (must be 2D). - """ - global CLASS_INDEX - if len(preds.shape) != 2 or preds.shape[1] != 1000: - raise ValueError('`decode_predictions` expects ' - 'a batch of predictions ' - '(i.e. a 2D array of shape (samples, 1000)). ' - 'Found array with shape: ' + str(preds.shape)) - if CLASS_INDEX is None: - CLASS_INDEX = {"0": ["n01440764", "tench"], "1": ["n01443537", "goldfish"], "2": ["n01484850", "great_white_shark"], "3": ["n01491361", "tiger_shark"], "4": ["n01494475", "hammerhead"], "5": ["n01496331", "electric_ray"], "6": ["n01498041", "stingray"], "7": ["n01514668", "cock"], "8": ["n01514859", "hen"], "9": ["n01518878", "ostrich"], "10": ["n01530575", "brambling"], "11": ["n01531178", "goldfinch"], "12": ["n01532829", "house_finch"], "13": ["n01534433", "junco"], "14": ["n01537544", "indigo_bunting"], "15": ["n01558993", "robin"], "16": ["n01560419", "bulbul"], "17": ["n01580077", "jay"], "18": ["n01582220", "magpie"], "19": ["n01592084", "chickadee"], "20": ["n01601694", "water_ouzel"], "21": ["n01608432", "kite"], "22": ["n01614925", "bald_eagle"], "23": ["n01616318", "vulture"], "24": ["n01622779", "great_grey_owl"], "25": ["n01629819", "European_fire_salamander"], "26": ["n01630670", "common_newt"], "27": ["n01631663", "eft"], "28": ["n01632458", "spotted_salamander"], "29": ["n01632777", "axolotl"], "30": ["n01641577", "bullfrog"], "31": ["n01644373", "tree_frog"], "32": ["n01644900", "tailed_frog"], "33": ["n01664065", "loggerhead"], "34": ["n01665541", "leatherback_turtle"], "35": ["n01667114", "mud_turtle"], "36": ["n01667778", "terrapin"], "37": ["n01669191", "box_turtle"], "38": ["n01675722", "banded_gecko"], "39": ["n01677366", "common_iguana"], "40": ["n01682714", "American_chameleon"], "41": ["n01685808", "whiptail"], "42": ["n01687978", "agama"], "43": ["n01688243", "frilled_lizard"], "44": ["n01689811", "alligator_lizard"], "45": ["n01692333", "Gila_monster"], "46": ["n01693334", "green_lizard"], "47": ["n01694178", "African_chameleon"], "48": ["n01695060", "Komodo_dragon"], "49": ["n01697457", "African_crocodile"], "50": ["n01698640", "American_alligator"], "51": ["n01704323", "triceratops"], "52": ["n01728572", "thunder_snake"], "53": ["n01728920", "ringneck_snake"], "54": ["n01729322", "hognose_snake"], "55": ["n01729977", "green_snake"], "56": ["n01734418", "king_snake"], "57": ["n01735189", "garter_snake"], "58": ["n01737021", "water_snake"], "59": ["n01739381", "vine_snake"], "60": ["n01740131", "night_snake"], "61": ["n01742172", "boa_constrictor"], "62": ["n01744401", "rock_python"], "63": ["n01748264", "Indian_cobra"], "64": ["n01749939", "green_mamba"], "65": ["n01751748", "sea_snake"], "66": ["n01753488", "horned_viper"], "67": ["n01755581", "diamondback"], "68": ["n01756291", "sidewinder"], "69": ["n01768244", "trilobite"], "70": ["n01770081", "harvestman"], "71": ["n01770393", "scorpion"], "72": ["n01773157", "black_and_gold_garden_spider"], "73": ["n01773549", "barn_spider"], "74": ["n01773797", "garden_spider"], "75": ["n01774384", "black_widow"], "76": ["n01774750", "tarantula"], "77": ["n01775062", "wolf_spider"], "78": ["n01776313", "tick"], "79": ["n01784675", "centipede"], "80": ["n01795545", "black_grouse"], "81": ["n01796340", "ptarmigan"], "82": ["n01797886", "ruffed_grouse"], "83": ["n01798484", "prairie_chicken"], "84": ["n01806143", "peacock"], "85": ["n01806567", "quail"], "86": ["n01807496", "partridge"], "87": ["n01817953", "African_grey"], "88": ["n01818515", "macaw"], "89": ["n01819313", "sulphur-crested_cockatoo"], "90": ["n01820546", "lorikeet"], "91": ["n01824575", "coucal"], "92": ["n01828970", "bee_eater"], "93": ["n01829413", "hornbill"], "94": ["n01833805", "hummingbird"], "95": ["n01843065", "jacamar"], "96": ["n01843383", "toucan"], "97": ["n01847000", "drake"], "98": ["n01855032", "red-breasted_merganser"], "99": ["n01855672", "goose"], "100": ["n01860187", "black_swan"], "101": ["n01871265", "tusker"], "102": ["n01872401", "echidna"], "103": ["n01873310", "platypus"], "104": ["n01877812", "wallaby"], "105": ["n01882714", "koala"], "106": ["n01883070", "wombat"], "107": ["n01910747", "jellyfish"], "108": ["n01914609", "sea_anemone"], "109": ["n01917289", "brain_coral"], "110": ["n01924916", "flatworm"], "111": ["n01930112", "nematode"], "112": ["n01943899", "conch"], "113": ["n01944390", "snail"], "114": ["n01945685", "slug"], "115": ["n01950731", "sea_slug"], "116": ["n01955084", "chiton"], "117": ["n01968897", "chambered_nautilus"], "118": ["n01978287", "Dungeness_crab"], "119": ["n01978455", "rock_crab"], "120": ["n01980166", "fiddler_crab"], "121": ["n01981276", "king_crab"], "122": ["n01983481", "American_lobster"], "123": ["n01984695", "spiny_lobster"], "124": ["n01985128", "crayfish"], "125": ["n01986214", "hermit_crab"], "126": ["n01990800", "isopod"], "127": ["n02002556", "white_stork"], "128": ["n02002724", "black_stork"], "129": ["n02006656", "spoonbill"], "130": ["n02007558", "flamingo"], "131": ["n02009229", "little_blue_heron"], "132": ["n02009912", "American_egret"], "133": ["n02011460", "bittern"], "134": ["n02012849", "crane"], "135": ["n02013706", "limpkin"], "136": ["n02017213", "European_gallinule"], "137": ["n02018207", "American_coot"], "138": ["n02018795", "bustard"], "139": ["n02025239", "ruddy_turnstone"], "140": ["n02027492", "red-backed_sandpiper"], "141": ["n02028035", "redshank"], "142": ["n02033041", "dowitcher"], "143": ["n02037110", "oystercatcher"], "144": ["n02051845", "pelican"], "145": ["n02056570", "king_penguin"], "146": ["n02058221", "albatross"], "147": ["n02066245", "grey_whale"], "148": ["n02071294", "killer_whale"], "149": ["n02074367", "dugong"], "150": ["n02077923", "sea_lion"], "151": ["n02085620", "Chihuahua"], "152": ["n02085782", "Japanese_spaniel"], "153": ["n02085936", "Maltese_dog"], "154": ["n02086079", "Pekinese"], "155": ["n02086240", "Shih-Tzu"], "156": ["n02086646", "Blenheim_spaniel"], "157": ["n02086910", "papillon"], "158": ["n02087046", "toy_terrier"], "159": ["n02087394", "Rhodesian_ridgeback"], "160": ["n02088094", "Afghan_hound"], "161": ["n02088238", "basset"], "162": ["n02088364", "beagle"], "163": ["n02088466", "bloodhound"], "164": ["n02088632", "bluetick"], "165": ["n02089078", "black-and-tan_coonhound"], "166": ["n02089867", "Walker_hound"], "167": ["n02089973", "English_foxhound"], "168": ["n02090379", "redbone"], "169": ["n02090622", "borzoi"], "170": ["n02090721", "Irish_wolfhound"], "171": ["n02091032", "Italian_greyhound"], "172": ["n02091134", "whippet"], "173": ["n02091244", "Ibizan_hound"], "174": ["n02091467", "Norwegian_elkhound"], "175": ["n02091635", "otterhound"], "176": ["n02091831", "Saluki"], "177": ["n02092002", "Scottish_deerhound"], "178": ["n02092339", "Weimaraner"], "179": ["n02093256", "Staffordshire_bullterrier"], "180": ["n02093428", "American_Staffordshire_terrier"], "181": ["n02093647", "Bedlington_terrier"], "182": ["n02093754", "Border_terrier"], "183": ["n02093859", "Kerry_blue_terrier"], "184": ["n02093991", "Irish_terrier"], "185": ["n02094114", "Norfolk_terrier"], "186": ["n02094258", "Norwich_terrier"], "187": ["n02094433", "Yorkshire_terrier"], "188": ["n02095314", "wire-haired_fox_terrier"], "189": ["n02095570", "Lakeland_terrier"], "190": ["n02095889", "Sealyham_terrier"], "191": ["n02096051", "Airedale"], "192": ["n02096177", "cairn"], "193": ["n02096294", "Australian_terrier"], "194": ["n02096437", "Dandie_Dinmont"], "195": ["n02096585", "Boston_bull"], "196": ["n02097047", "miniature_schnauzer"], "197": ["n02097130", "giant_schnauzer"], "198": ["n02097209", "standard_schnauzer"], "199": ["n02097298", "Scotch_terrier"], "200": ["n02097474", "Tibetan_terrier"], "201": ["n02097658", "silky_terrier"], "202": ["n02098105", "soft-coated_wheaten_terrier"], "203": ["n02098286", "West_Highland_white_terrier"], "204": ["n02098413", "Lhasa"], "205": ["n02099267", "flat-coated_retriever"], "206": ["n02099429", "curly-coated_retriever"], "207": ["n02099601", "golden_retriever"], "208": ["n02099712", "Labrador_retriever"], "209": ["n02099849", "Chesapeake_Bay_retriever"], "210": ["n02100236", "German_short-haired_pointer"], "211": ["n02100583", "vizsla"], "212": ["n02100735", "English_setter"], "213": ["n02100877", "Irish_setter"], "214": ["n02101006", "Gordon_setter"], "215": ["n02101388", "Brittany_spaniel"], "216": ["n02101556", "clumber"], "217": ["n02102040", "English_springer"], "218": ["n02102177", "Welsh_springer_spaniel"], "219": ["n02102318", "cocker_spaniel"], "220": ["n02102480", "Sussex_spaniel"], "221": ["n02102973", "Irish_water_spaniel"], "222": ["n02104029", "kuvasz"], "223": ["n02104365", "schipperke"], "224": ["n02105056", "groenendael"], "225": ["n02105162", "malinois"], "226": ["n02105251", "briard"], "227": ["n02105412", "kelpie"], "228": ["n02105505", "komondor"], "229": ["n02105641", "Old_English_sheepdog"], "230": ["n02105855", "Shetland_sheepdog"], "231": ["n02106030", "collie"], "232": ["n02106166", "Border_collie"], "233": ["n02106382", "Bouvier_des_Flandres"], "234": ["n02106550", "Rottweiler"], "235": ["n02106662", "German_shepherd"], "236": ["n02107142", "Doberman"], "237": ["n02107312", "miniature_pinscher"], "238": ["n02107574", "Greater_Swiss_Mountain_dog"], "239": ["n02107683", "Bernese_mountain_dog"], "240": ["n02107908", "Appenzeller"], "241": ["n02108000", "EntleBucher"], "242": ["n02108089", "boxer"], "243": ["n02108422", "bull_mastiff"], "244": ["n02108551", "Tibetan_mastiff"], "245": ["n02108915", "French_bulldog"], "246": ["n02109047", "Great_Dane"], "247": ["n02109525", "Saint_Bernard"], "248": ["n02109961", "Eskimo_dog"], "249": ["n02110063", "malamute"], "250": ["n02110185", "Siberian_husky"], "251": ["n02110341", "dalmatian"], "252": ["n02110627", "affenpinscher"], "253": ["n02110806", "basenji"], "254": ["n02110958", "pug"], "255": ["n02111129", "Leonberg"], "256": ["n02111277", "Newfoundland"], "257": ["n02111500", "Great_Pyrenees"], "258": ["n02111889", "Samoyed"], "259": ["n02112018", "Pomeranian"], "260": ["n02112137", "chow"], "261": ["n02112350", "keeshond"], "262": ["n02112706", "Brabancon_griffon"], "263": ["n02113023", "Pembroke"], "264": ["n02113186", "Cardigan"], "265": ["n02113624", "toy_poodle"], "266": ["n02113712", "miniature_poodle"], "267": ["n02113799", "standard_poodle"], "268": ["n02113978", "Mexican_hairless"], "269": ["n02114367", "timber_wolf"], "270": ["n02114548", "white_wolf"], "271": ["n02114712", "red_wolf"], "272": ["n02114855", "coyote"], "273": ["n02115641", "dingo"], "274": ["n02115913", "dhole"], "275": ["n02116738", "African_hunting_dog"], "276": ["n02117135", "hyena"], "277": ["n02119022", "red_fox"], "278": ["n02119789", "kit_fox"], "279": ["n02120079", "Arctic_fox"], "280": ["n02120505", "grey_fox"], "281": ["n02123045", "tabby"], "282": ["n02123159", "tiger_cat"], "283": ["n02123394", "Persian_cat"], "284": ["n02123597", "Siamese_cat"], "285": ["n02124075", "Egyptian_cat"], "286": ["n02125311", "cougar"], "287": ["n02127052", "lynx"], "288": ["n02128385", "leopard"], "289": ["n02128757", "snow_leopard"], "290": ["n02128925", "jaguar"], "291": ["n02129165", "lion"], "292": ["n02129604", "tiger"], "293": ["n02130308", "cheetah"], "294": ["n02132136", "brown_bear"], "295": ["n02133161", "American_black_bear"], "296": ["n02134084", "ice_bear"], "297": ["n02134418", "sloth_bear"], "298": ["n02137549", "mongoose"], "299": ["n02138441", "meerkat"], "300": ["n02165105", "tiger_beetle"], "301": ["n02165456", "ladybug"], "302": ["n02167151", "ground_beetle"], "303": ["n02168699", "long-horned_beetle"], "304": ["n02169497", "leaf_beetle"], "305": ["n02172182", "dung_beetle"], "306": ["n02174001", "rhinoceros_beetle"], "307": ["n02177972", "weevil"], "308": ["n02190166", "fly"], "309": ["n02206856", "bee"], "310": ["n02219486", "ant"], "311": ["n02226429", "grasshopper"], "312": ["n02229544", "cricket"], "313": ["n02231487", "walking_stick"], "314": ["n02233338", "cockroach"], "315": ["n02236044", "mantis"], "316": ["n02256656", "cicada"], "317": ["n02259212", "leafhopper"], "318": ["n02264363", "lacewing"], "319": ["n02268443", "dragonfly"], "320": ["n02268853", "damselfly"], "321": ["n02276258", "admiral"], "322": ["n02277742", "ringlet"], "323": ["n02279972", "monarch"], "324": ["n02280649", "cabbage_butterfly"], "325": ["n02281406", "sulphur_butterfly"], "326": ["n02281787", "lycaenid"], "327": ["n02317335", "starfish"], "328": ["n02319095", "sea_urchin"], "329": ["n02321529", "sea_cucumber"], "330": ["n02325366", "wood_rabbit"], "331": ["n02326432", "hare"], "332": ["n02328150", "Angora"], "333": ["n02342885", "hamster"], "334": ["n02346627", "porcupine"], "335": ["n02356798", "fox_squirrel"], "336": ["n02361337", "marmot"], "337": ["n02363005", "beaver"], "338": ["n02364673", "guinea_pig"], "339": ["n02389026", "sorrel"], "340": ["n02391049", "zebra"], "341": ["n02395406", "hog"], "342": ["n02396427", "wild_boar"], "343": ["n02397096", "warthog"], "344": ["n02398521", "hippopotamus"], "345": ["n02403003", "ox"], "346": ["n02408429", "water_buffalo"], "347": ["n02410509", "bison"], "348": ["n02412080", "ram"], "349": ["n02415577", "bighorn"], "350": ["n02417914", "ibex"], "351": ["n02422106", "hartebeest"], "352": ["n02422699", "impala"], "353": ["n02423022", "gazelle"], "354": ["n02437312", "Arabian_camel"], "355": ["n02437616", "llama"], "356": ["n02441942", "weasel"], "357": ["n02442845", "mink"], "358": ["n02443114", "polecat"], "359": ["n02443484", "black-footed_ferret"], "360": ["n02444819", "otter"], "361": ["n02445715", "skunk"], "362": ["n02447366", "badger"], "363": ["n02454379", "armadillo"], "364": ["n02457408", "three-toed_sloth"], "365": ["n02480495", "orangutan"], "366": ["n02480855", "gorilla"], "367": ["n02481823", "chimpanzee"], "368": ["n02483362", "gibbon"], "369": ["n02483708", "siamang"], "370": ["n02484975", "guenon"], "371": ["n02486261", "patas"], "372": ["n02486410", "baboon"], "373": ["n02487347", "macaque"], "374": ["n02488291", "langur"], "375": ["n02488702", "colobus"], "376": ["n02489166", "proboscis_monkey"], "377": ["n02490219", "marmoset"], "378": ["n02492035", "capuchin"], "379": ["n02492660", "howler_monkey"], "380": ["n02493509", "titi"], "381": ["n02493793", "spider_monkey"], "382": ["n02494079", "squirrel_monkey"], "383": ["n02497673", "Madagascar_cat"], "384": ["n02500267", "indri"], "385": ["n02504013", "Indian_elephant"], "386": ["n02504458", "African_elephant"], "387": ["n02509815", "lesser_panda"], "388": ["n02510455", "giant_panda"], "389": ["n02514041", "barracouta"], "390": ["n02526121", "eel"], "391": ["n02536864", "coho"], "392": ["n02606052", "rock_beauty"], "393": ["n02607072", "anemone_fish"], "394": ["n02640242", "sturgeon"], "395": ["n02641379", "gar"], "396": ["n02643566", "lionfish"], "397": ["n02655020", "puffer"], "398": ["n02666196", "abacus"], "399": ["n02667093", "abaya"], "400": ["n02669723", "academic_gown"], "401": ["n02672831", "accordion"], "402": ["n02676566", "acoustic_guitar"], "403": ["n02687172", "aircraft_carrier"], "404": ["n02690373", "airliner"], "405": ["n02692877", "airship"], "406": ["n02699494", "altar"], "407": ["n02701002", "ambulance"], "408": ["n02704792", "amphibian"], "409": ["n02708093", "analog_clock"], "410": ["n02727426", "apiary"], "411": ["n02730930", "apron"], "412": ["n02747177", "ashcan"], "413": ["n02749479", "assault_rifle"], "414": ["n02769748", "backpack"], "415": ["n02776631", "bakery"], "416": ["n02777292", "balance_beam"], "417": ["n02782093", "balloon"], "418": ["n02783161", "ballpoint"], "419": ["n02786058", "Band_Aid"], "420": ["n02787622", "banjo"], "421": ["n02788148", "bannister"], "422": ["n02790996", "barbell"], "423": ["n02791124", "barber_chair"], "424": ["n02791270", "barbershop"], "425": ["n02793495", "barn"], "426": ["n02794156", "barometer"], "427": ["n02795169", "barrel"], "428": ["n02797295", "barrow"], "429": ["n02799071", "baseball"], "430": ["n02802426", "basketball"], "431": ["n02804414", "bassinet"], "432": ["n02804610", "bassoon"], "433": ["n02807133", "bathing_cap"], "434": ["n02808304", "bath_towel"], "435": ["n02808440", "bathtub"], "436": ["n02814533", "beach_wagon"], "437": ["n02814860", "beacon"], "438": ["n02815834", "beaker"], "439": ["n02817516", "bearskin"], "440": ["n02823428", "beer_bottle"], "441": ["n02823750", "beer_glass"], "442": ["n02825657", "bell_cote"], "443": ["n02834397", "bib"], "444": ["n02835271", "bicycle-built-for-two"], "445": ["n02837789", "bikini"], "446": ["n02840245", "binder"], "447": ["n02841315", "binoculars"], "448": ["n02843684", "birdhouse"], "449": ["n02859443", "boathouse"], "450": ["n02860847", "bobsled"], "451": ["n02865351", "bolo_tie"], "452": ["n02869837", "bonnet"], "453": ["n02870880", "bookcase"], "454": ["n02871525", "bookshop"], "455": ["n02877765", "bottlecap"], "456": ["n02879718", "bow"], "457": ["n02883205", "bow_tie"], "458": ["n02892201", "brass"], "459": ["n02892767", "brassiere"], "460": ["n02894605", "breakwater"], "461": ["n02895154", "breastplate"], "462": ["n02906734", "broom"], "463": ["n02909870", "bucket"], "464": ["n02910353", "buckle"], "465": ["n02916936", "bulletproof_vest"], "466": ["n02917067", "bullet_train"], "467": ["n02927161", "butcher_shop"], "468": ["n02930766", "cab"], "469": ["n02939185", "caldron"], "470": ["n02948072", "candle"], "471": ["n02950826", "cannon"], "472": ["n02951358", "canoe"], "473": ["n02951585", "can_opener"], "474": ["n02963159", "cardigan"], "475": ["n02965783", "car_mirror"], "476": ["n02966193", "carousel"], "477": ["n02966687", "carpenter's_kit"], "478": ["n02971356", "carton"], "479": ["n02974003", "car_wheel"], "480": ["n02977058", "cash_machine"], "481": ["n02978881", "cassette"], "482": ["n02979186", "cassette_player"], "483": ["n02980441", "castle"], "484": ["n02981792", "catamaran"], "485": ["n02988304", "CD_player"], "486": ["n02992211", "cello"], "487": ["n02992529", "cellular_telephone"], "488": ["n02999410", "chain"], "489": ["n03000134", "chainlink_fence"], "490": ["n03000247", "chain_mail"], "491": ["n03000684", "chain_saw"], "492": ["n03014705", "chest"], "493": ["n03016953", "chiffonier"], "494": ["n03017168", "chime"], "495": ["n03018349", "china_cabinet"], "496": ["n03026506", "Christmas_stocking"], "497": ["n03028079", "church"], "498": ["n03032252", "cinema"], "499": ["n03041632", "cleaver"], "500": ["n03042490", "cliff_dwelling"], "501": ["n03045698", "cloak"], "502": ["n03047690", "clog"], "503": ["n03062245", "cocktail_shaker"], "504": ["n03063599", "coffee_mug"], "505": ["n03063689", "coffeepot"], "506": ["n03065424", "coil"], "507": ["n03075370", "combination_lock"], "508": ["n03085013", "computer_keyboard"], "509": ["n03089624", "confectionery"], "510": ["n03095699", "container_ship"], "511": ["n03100240", "convertible"], "512": ["n03109150", "corkscrew"], "513": ["n03110669", "cornet"], "514": ["n03124043", "cowboy_boot"], "515": ["n03124170", "cowboy_hat"], "516": ["n03125729", "cradle"], "517": ["n03126707", "crane"], "518": ["n03127747", "crash_helmet"], "519": ["n03127925", "crate"], "520": ["n03131574", "crib"], "521": ["n03133878", "Crock_Pot"], "522": ["n03134739", "croquet_ball"], "523": ["n03141823", "crutch"], "524": ["n03146219", "cuirass"], "525": ["n03160309", "dam"], "526": ["n03179701", "desk"], "527": ["n03180011", "desktop_computer"], "528": ["n03187595", "dial_telephone"], "529": ["n03188531", "diaper"], "530": ["n03196217", "digital_clock"], "531": ["n03197337", "digital_watch"], "532": ["n03201208", "dining_table"], "533": ["n03207743", "dishrag"], "534": ["n03207941", "dishwasher"], "535": ["n03208938", "disk_brake"], "536": ["n03216828", "dock"], "537": ["n03218198", "dogsled"], "538": ["n03220513", "dome"], "539": ["n03223299", "doormat"], "540": ["n03240683", "drilling_platform"], "541": ["n03249569", "drum"], "542": ["n03250847", "drumstick"], "543": ["n03255030", "dumbbell"], "544": ["n03259280", "Dutch_oven"], "545": ["n03271574", "electric_fan"], "546": ["n03272010", "electric_guitar"], "547": ["n03272562", "electric_locomotive"], "548": ["n03290653", "entertainment_center"], "549": ["n03291819", "envelope"], "550": ["n03297495", "espresso_maker"], "551": ["n03314780", "face_powder"], "552": ["n03325584", "feather_boa"], "553": ["n03337140", "file"], "554": ["n03344393", "fireboat"], "555": ["n03345487", "fire_engine"], "556": ["n03347037", "fire_screen"], "557": ["n03355925", "flagpole"], "558": ["n03372029", "flute"], "559": ["n03376595", "folding_chair"], "560": ["n03379051", "football_helmet"], "561": ["n03384352", "forklift"], "562": ["n03388043", "fountain"], "563": ["n03388183", "fountain_pen"], "564": ["n03388549", "four-poster"], "565": ["n03393912", "freight_car"], "566": ["n03394916", "French_horn"], "567": ["n03400231", "frying_pan"], "568": ["n03404251", "fur_coat"], "569": ["n03417042", "garbage_truck"], "570": ["n03424325", "gasmask"], "571": ["n03425413", "gas_pump"], "572": ["n03443371", "goblet"], "573": ["n03444034", "go-kart"], "574": ["n03445777", "golf_ball"], "575": ["n03445924", "golfcart"], "576": ["n03447447", "gondola"], "577": ["n03447721", "gong"], "578": ["n03450230", "gown"], "579": ["n03452741", "grand_piano"], "580": ["n03457902", "greenhouse"], "581": ["n03459775", "grille"], "582": ["n03461385", "grocery_store"], "583": ["n03467068", "guillotine"], "584": ["n03476684", "hair_slide"], "585": ["n03476991", "hair_spray"], "586": ["n03478589", "half_track"], "587": ["n03481172", "hammer"], "588": ["n03482405", "hamper"], "589": ["n03483316", "hand_blower"], "590": ["n03485407", "hand-held_computer"], "591": ["n03485794", "handkerchief"], "592": ["n03492542", "hard_disc"], "593": ["n03494278", "harmonica"], "594": ["n03495258", "harp"], "595": ["n03496892", "harvester"], "596": ["n03498962", "hatchet"], "597": ["n03527444", "holster"], "598": ["n03529860", "home_theater"], "599": ["n03530642", "honeycomb"], "600": ["n03532672", "hook"], "601": ["n03534580", "hoopskirt"], "602": ["n03535780", "horizontal_bar"], "603": ["n03538406", "horse_cart"], "604": ["n03544143", "hourglass"], "605": ["n03584254", "iPod"], "606": ["n03584829", "iron"], "607": ["n03590841", "jack-o'-lantern"], "608": ["n03594734", "jean"], "609": ["n03594945", "jeep"], "610": ["n03595614", "jersey"], "611": ["n03598930", "jigsaw_puzzle"], "612": ["n03599486", "jinrikisha"], "613": ["n03602883", "joystick"], "614": ["n03617480", "kimono"], "615": ["n03623198", "knee_pad"], "616": ["n03627232", "knot"], "617": ["n03630383", "lab_coat"], "618": ["n03633091", "ladle"], "619": ["n03637318", "lampshade"], "620": ["n03642806", "laptop"], "621": ["n03649909", "lawn_mower"], "622": ["n03657121", "lens_cap"], "623": ["n03658185", "letter_opener"], "624": ["n03661043", "library"], "625": ["n03662601", "lifeboat"], "626": ["n03666591", "lighter"], "627": ["n03670208", "limousine"], "628": ["n03673027", "liner"], "629": ["n03676483", "lipstick"], "630": ["n03680355", "Loafer"], "631": ["n03690938", "lotion"], "632": ["n03691459", "loudspeaker"], "633": ["n03692522", "loupe"], "634": ["n03697007", "lumbermill"], "635": ["n03706229", "magnetic_compass"], "636": ["n03709823", "mailbag"], "637": ["n03710193", "mailbox"], "638": ["n03710637", "maillot"], "639": ["n03710721", "maillot"], "640": ["n03717622", "manhole_cover"], "641": ["n03720891", "maraca"], "642": ["n03721384", "marimba"], "643": ["n03724870", "mask"], "644": ["n03729826", "matchstick"], "645": ["n03733131", "maypole"], "646": ["n03733281", "maze"], "647": ["n03733805", "measuring_cup"], "648": ["n03742115", "medicine_chest"], "649": ["n03743016", "megalith"], "650": ["n03759954", "microphone"], "651": ["n03761084", "microwave"], "652": ["n03763968", "military_uniform"], "653": ["n03764736", "milk_can"], "654": ["n03769881", "minibus"], "655": ["n03770439", "miniskirt"], "656": ["n03770679", "minivan"], "657": ["n03773504", "missile"], "658": ["n03775071", "mitten"], "659": ["n03775546", "mixing_bowl"], "660": ["n03776460", "mobile_home"], "661": ["n03777568", "Model_T"], "662": ["n03777754", "modem"], "663": ["n03781244", "monastery"], "664": ["n03782006", "monitor"], "665": ["n03785016", "moped"], "666": ["n03786901", "mortar"], "667": ["n03787032", "mortarboard"], "668": ["n03788195", "mosque"], "669": ["n03788365", "mosquito_net"], "670": ["n03791053", "motor_scooter"], "671": ["n03792782", "mountain_bike"], "672": ["n03792972", "mountain_tent"], "673": ["n03793489", "mouse"], "674": ["n03794056", "mousetrap"], "675": ["n03796401", "moving_van"], "676": ["n03803284", "muzzle"], "677": ["n03804744", "nail"], "678": ["n03814639", "neck_brace"], "679": ["n03814906", "necklace"], "680": ["n03825788", "nipple"], "681": ["n03832673", "notebook"], "682": ["n03837869", "obelisk"], "683": ["n03838899", "oboe"], "684": ["n03840681", "ocarina"], "685": ["n03841143", "odometer"], "686": ["n03843555", "oil_filter"], "687": ["n03854065", "organ"], "688": ["n03857828", "oscilloscope"], "689": ["n03866082", "overskirt"], "690": ["n03868242", "oxcart"], "691": ["n03868863", "oxygen_mask"], "692": ["n03871628", "packet"], "693": ["n03873416", "paddle"], "694": ["n03874293", "paddlewheel"], "695": ["n03874599", "padlock"], "696": ["n03876231", "paintbrush"], "697": ["n03877472", "pajama"], "698": ["n03877845", "palace"], "699": ["n03884397", "panpipe"], "700": ["n03887697", "paper_towel"], "701": ["n03888257", "parachute"], "702": ["n03888605", "parallel_bars"], "703": ["n03891251", "park_bench"], "704": ["n03891332", "parking_meter"], "705": ["n03895866", "passenger_car"], "706": ["n03899768", "patio"], "707": ["n03902125", "pay-phone"], "708": ["n03903868", "pedestal"], "709": ["n03908618", "pencil_box"], "710": ["n03908714", "pencil_sharpener"], "711": ["n03916031", "perfume"], "712": ["n03920288", "Petri_dish"], "713": ["n03924679", "photocopier"], "714": ["n03929660", "pick"], "715": ["n03929855", "pickelhaube"], "716": ["n03930313", "picket_fence"], "717": ["n03930630", "pickup"], "718": ["n03933933", "pier"], "719": ["n03935335", "piggy_bank"], "720": ["n03937543", "pill_bottle"], "721": ["n03938244", "pillow"], "722": ["n03942813", "ping-pong_ball"], "723": ["n03944341", "pinwheel"], "724": ["n03947888", "pirate"], "725": ["n03950228", "pitcher"], "726": ["n03954731", "plane"], "727": ["n03956157", "planetarium"], "728": ["n03958227", "plastic_bag"], "729": ["n03961711", "plate_rack"], "730": ["n03967562", "plow"], "731": ["n03970156", "plunger"], "732": ["n03976467", "Polaroid_camera"], "733": ["n03976657", "pole"], "734": ["n03977966", "police_van"], "735": ["n03980874", "poncho"], "736": ["n03982430", "pool_table"], "737": ["n03983396", "pop_bottle"], "738": ["n03991062", "pot"], "739": ["n03992509", "potter's_wheel"], "740": ["n03995372", "power_drill"], "741": ["n03998194", "prayer_rug"], "742": ["n04004767", "printer"], "743": ["n04005630", "prison"], "744": ["n04008634", "projectile"], "745": ["n04009552", "projector"], "746": ["n04019541", "puck"], "747": ["n04023962", "punching_bag"], "748": ["n04026417", "purse"], "749": ["n04033901", "quill"], "750": ["n04033995", "quilt"], "751": ["n04037443", "racer"], "752": ["n04039381", "racket"], "753": ["n04040759", "radiator"], "754": ["n04041544", "radio"], "755": ["n04044716", "radio_telescope"], "756": ["n04049303", "rain_barrel"], "757": ["n04065272", "recreational_vehicle"], "758": ["n04067472", "reel"], "759": ["n04069434", "reflex_camera"], "760": ["n04070727", "refrigerator"], "761": ["n04074963", "remote_control"], "762": ["n04081281", "restaurant"], "763": ["n04086273", "revolver"], "764": ["n04090263", "rifle"], "765": ["n04099969", "rocking_chair"], "766": ["n04111531", "rotisserie"], "767": ["n04116512", "rubber_eraser"], "768": ["n04118538", "rugby_ball"], "769": ["n04118776", "rule"], "770": ["n04120489", "running_shoe"], "771": ["n04125021", "safe"], "772": ["n04127249", "safety_pin"], "773": ["n04131690", "saltshaker"], "774": ["n04133789", "sandal"], "775": ["n04136333", "sarong"], "776": ["n04141076", "sax"], "777": ["n04141327", "scabbard"], "778": ["n04141975", "scale"], "779": ["n04146614", "school_bus"], "780": ["n04147183", "schooner"], "781": ["n04149813", "scoreboard"], "782": ["n04152593", "screen"], "783": ["n04153751", "screw"], "784": ["n04154565", "screwdriver"], "785": ["n04162706", "seat_belt"], "786": ["n04179913", "sewing_machine"], "787": ["n04192698", "shield"], "788": ["n04200800", "shoe_shop"], "789": ["n04201297", "shoji"], "790": ["n04204238", "shopping_basket"], "791": ["n04204347", "shopping_cart"], "792": ["n04208210", "shovel"], "793": ["n04209133", "shower_cap"], "794": ["n04209239", "shower_curtain"], "795": ["n04228054", "ski"], "796": ["n04229816", "ski_mask"], "797": ["n04235860", "sleeping_bag"], "798": ["n04238763", "slide_rule"], "799": ["n04239074", "sliding_door"], "800": ["n04243546", "slot"], "801": ["n04251144", "snorkel"], "802": ["n04252077", "snowmobile"], "803": ["n04252225", "snowplow"], "804": ["n04254120", "soap_dispenser"], "805": ["n04254680", "soccer_ball"], "806": ["n04254777", "sock"], "807": ["n04258138", "solar_dish"], "808": ["n04259630", "sombrero"], "809": ["n04263257", "soup_bowl"], "810": ["n04264628", "space_bar"], "811": ["n04265275", "space_heater"], "812": ["n04266014", "space_shuttle"], "813": ["n04270147", "spatula"], "814": ["n04273569", "speedboat"], "815": ["n04275548", "spider_web"], "816": ["n04277352", "spindle"], "817": ["n04285008", "sports_car"], "818": ["n04286575", "spotlight"], "819": ["n04296562", "stage"], "820": ["n04310018", "steam_locomotive"], "821": ["n04311004", "steel_arch_bridge"], "822": ["n04311174", "steel_drum"], "823": ["n04317175", "stethoscope"], "824": ["n04325704", "stole"], "825": ["n04326547", "stone_wall"], "826": ["n04328186", "stopwatch"], "827": ["n04330267", "stove"], "828": ["n04332243", "strainer"], "829": ["n04335435", "streetcar"], "830": ["n04336792", "stretcher"], "831": ["n04344873", "studio_couch"], "832": ["n04346328", "stupa"], "833": ["n04347754", "submarine"], "834": ["n04350905", "suit"], "835": ["n04355338", "sundial"], "836": ["n04355933", "sunglass"], "837": ["n04356056", "sunglasses"], "838": ["n04357314", "sunscreen"], "839": ["n04366367", "suspension_bridge"], "840": ["n04367480", "swab"], "841": ["n04370456", "sweatshirt"], "842": ["n04371430", "swimming_trunks"], "843": ["n04371774", "swing"], "844": ["n04372370", "switch"], "845": ["n04376876", "syringe"], "846": ["n04380533", "table_lamp"], "847": ["n04389033", "tank"], "848": ["n04392985", "tape_player"], "849": ["n04398044", "teapot"], "850": ["n04399382", "teddy"], "851": ["n04404412", "television"], "852": ["n04409515", "tennis_ball"], "853": ["n04417672", "thatch"], "854": ["n04418357", "theater_curtain"], "855": ["n04423845", "thimble"], "856": ["n04428191", "thresher"], "857": ["n04429376", "throne"], "858": ["n04435653", "tile_roof"], "859": ["n04442312", "toaster"], "860": ["n04443257", "tobacco_shop"], "861": ["n04447861", "toilet_seat"], "862": ["n04456115", "torch"], "863": ["n04458633", "totem_pole"], "864": ["n04461696", "tow_truck"], "865": ["n04462240", "toyshop"], "866": ["n04465501", "tractor"], "867": ["n04467665", "trailer_truck"], "868": ["n04476259", "tray"], "869": ["n04479046", "trench_coat"], "870": ["n04482393", "tricycle"], "871": ["n04483307", "trimaran"], "872": ["n04485082", "tripod"], "873": ["n04486054", "triumphal_arch"], "874": ["n04487081", "trolleybus"], "875": ["n04487394", "trombone"], "876": ["n04493381", "tub"], "877": ["n04501370", "turnstile"], "878": ["n04505470", "typewriter_keyboard"], "879": ["n04507155", "umbrella"], "880": ["n04509417", "unicycle"], "881": ["n04515003", "upright"], "882": ["n04517823", "vacuum"], "883": ["n04522168", "vase"], "884": ["n04523525", "vault"], "885": ["n04525038", "velvet"], "886": ["n04525305", "vending_machine"], "887": ["n04532106", "vestment"], "888": ["n04532670", "viaduct"], "889": ["n04536866", "violin"], "890": ["n04540053", "volleyball"], "891": ["n04542943", "waffle_iron"], "892": ["n04548280", "wall_clock"], "893": ["n04548362", "wallet"], "894": ["n04550184", "wardrobe"], "895": ["n04552348", "warplane"], "896": ["n04553703", "washbasin"], "897": ["n04554684", "washer"], "898": ["n04557648", "water_bottle"], "899": ["n04560804", "water_jug"], "900": ["n04562935", "water_tower"], "901": ["n04579145", "whiskey_jug"], "902": ["n04579432", "whistle"], "903": ["n04584207", "wig"], "904": ["n04589890", "window_screen"], "905": ["n04590129", "window_shade"], "906": ["n04591157", "Windsor_tie"], "907": ["n04591713", "wine_bottle"], "908": ["n04592741", "wing"], "909": ["n04596742", "wok"], "910": ["n04597913", "wooden_spoon"], "911": ["n04599235", "wool"], "912": ["n04604644", "worm_fence"], "913": ["n04606251", "wreck"], "914": ["n04612504", "yawl"], "915": ["n04613696", "yurt"], "916": ["n06359193", "web_site"], "917": ["n06596364", "comic_book"], "918": ["n06785654", "crossword_puzzle"], "919": ["n06794110", "street_sign"], "920": ["n06874185", "traffic_light"], "921": ["n07248320", "book_jacket"], "922": ["n07565083", "menu"], "923": ["n07579787", "plate"], "924": ["n07583066", "guacamole"], "925": ["n07584110", "consomme"], "926": ["n07590611", "hot_pot"], "927": ["n07613480", "trifle"], "928": ["n07614500", "ice_cream"], "929": ["n07615774", "ice_lolly"], "930": ["n07684084", "French_loaf"], "931": ["n07693725", "bagel"], "932": ["n07695742", "pretzel"], "933": ["n07697313", "cheeseburger"], "934": ["n07697537", "hotdog"], "935": ["n07711569", "mashed_potato"], "936": ["n07714571", "head_cabbage"], "937": ["n07714990", "broccoli"], "938": ["n07715103", "cauliflower"], "939": ["n07716358", "zucchini"], "940": ["n07716906", "spaghetti_squash"], "941": ["n07717410", "acorn_squash"], "942": ["n07717556", "butternut_squash"], "943": ["n07718472", "cucumber"], "944": ["n07718747", "artichoke"], "945": ["n07720875", "bell_pepper"], "946": ["n07730033", "cardoon"], "947": ["n07734744", "mushroom"], "948": ["n07742313", "Granny_Smith"], "949": ["n07745940", "strawberry"], "950": ["n07747607", "orange"], "951": ["n07749582", "lemon"], "952": ["n07753113", "fig"], "953": ["n07753275", "pineapple"], "954": ["n07753592", "banana"], "955": ["n07754684", "jackfruit"], "956": ["n07760859", "custard_apple"], "957": ["n07768694", "pomegranate"], "958": ["n07802026", "hay"], "959": ["n07831146", "carbonara"], "960": ["n07836838", "chocolate_sauce"], "961": ["n07860988", "dough"], "962": ["n07871810", "meat_loaf"], "963": ["n07873807", "pizza"], "964": ["n07875152", "potpie"], "965": ["n07880968", "burrito"], "966": ["n07892512", "red_wine"], "967": ["n07920052", "espresso"], "968": ["n07930864", "cup"], "969": ["n07932039", "eggnog"], "970": ["n09193705", "alp"], "971": ["n09229709", "bubble"], "972": ["n09246464", "cliff"], "973": ["n09256479", "coral_reef"], "974": ["n09288635", "geyser"], "975": ["n09332890", "lakeside"], "976": ["n09399592", "promontory"], "977": ["n09421951", "sandbar"], "978": ["n09428293", "seashore"], "979": ["n09468604", "valley"], "980": ["n09472597", "volcano"], "981": ["n09835506", "ballplayer"], "982": ["n10148035", "groom"], "983": ["n10565667", "scuba_diver"], "984": ["n11879895", "rapeseed"], "985": ["n11939491", "daisy"], "986": ["n12057211", "yellow_lady's_slipper"], "987": ["n12144580", "corn"], "988": ["n12267677", "acorn"], "989": ["n12620546", "hip"], "990": ["n12768682", "buckeye"], "991": ["n12985857", "coral_fungus"], "992": ["n12998815", "agaric"], "993": ["n13037406", "gyromitra"], "994": ["n13040303", "stinkhorn"], "995": ["n13044778", "earthstar"], "996": ["n13052670", "hen-of-the-woods"], "997": ["n13054560", "bolete"], "998": ["n13133613", "ear"], "999": ["n15075141", "toilet_tissue"]} - results = [] - for pred in preds: - top_indices = pred.argsort()[-top:][::-1] - result = [tuple(CLASS_INDEX[str(i)]) + (pred[i],) for i in top_indices] - result.sort(key=lambda x: x[2], reverse=True) - results.append(result) - return results - - -def _obtain_input_shape(input_shape, - default_size, - min_size, - data_format, - require_flatten, - weights=None): - """Internal utility to compute/validate an ImageNet model's input shape. - - # Arguments - input_shape: either None (will return the default network input shape), - or a user-provided shape to be validated. - default_size: default input width/height for the model. - min_size: minimum input width/height accepted by the model. - data_format: image data format to use. - require_flatten: whether the model is expected to - be linked to a classifier via a Flatten layer. - weights: one of `None` (random initialization) - or 'imagenet' (pre-training on ImageNet). - If weights='imagenet' input channels must be equal to 3. - - # Returns - An integer shape tuple (may include None entries). - - # Raises - ValueError: in case of invalid argument values. - """ - if weights != 'imagenet' and input_shape and len(input_shape) == 3: - if data_format == 'channels_first': - if input_shape[0] not in {1, 3}: - warnings.warn( - 'This model usually expects 1 or 3 input channels. ' - 'However, it was passed an input_shape with ' + - str(input_shape[0]) + ' input channels.') - default_shape = (input_shape[0], default_size, default_size) - else: - if input_shape[-1] not in {1, 3}: - warnings.warn( - 'This model usually expects 1 or 3 input channels. ' - 'However, it was passed an input_shape with ' + - str(input_shape[-1]) + ' input channels.') - default_shape = (default_size, default_size, input_shape[-1]) - else: - if data_format == 'channels_first': - default_shape = (3, default_size, default_size) - else: - default_shape = (default_size, default_size, 3) - if weights == 'imagenet' and require_flatten: - if input_shape is not None: - if input_shape != default_shape: - raise ValueError('When setting`include_top=True` ' - 'and loading `imagenet` weights, ' - '`input_shape` should be ' + - str(default_shape) + '.') - return default_shape - if input_shape: - if data_format == 'channels_first': - if input_shape is not None: - if len(input_shape) != 3: - raise ValueError( - '`input_shape` must be a tuple of three integers.') - if input_shape[0] != 3 and weights == 'imagenet': - raise ValueError('The input must have 3 channels; got ' - '`input_shape=' + str(input_shape) + '`') - if ((input_shape[1] is not None and input_shape[1] < min_size) or - (input_shape[2] is not None and input_shape[2] < min_size)): - raise ValueError('Input size must be at least ' + - str(min_size) + 'x' + str(min_size) + '; got ' - '`input_shape=' + str(input_shape) + '`') - else: - if input_shape is not None: - if len(input_shape) != 3: - raise ValueError( - '`input_shape` must be a tuple of three integers.') - if input_shape[-1] != 3 and weights == 'imagenet': - raise ValueError('The input must have 3 channels; got ' - '`input_shape=' + str(input_shape) + '`') - if ((input_shape[0] is not None and input_shape[0] < min_size) or - (input_shape[1] is not None and input_shape[1] < min_size)): - raise ValueError('Input size must be at least ' + - str(min_size) + 'x' + str(min_size) + '; got ' - '`input_shape=' + str(input_shape) + '`') - else: - if require_flatten: - input_shape = default_shape - else: - if data_format == 'channels_first': - input_shape = (3, None, None) - else: - input_shape = (None, None, 3) - if require_flatten: - if None in input_shape: - raise ValueError('If `include_top` is True, ' - 'you should specify a static `input_shape`. ' - 'Got `input_shape=' + str(input_shape) + '`') - return input_shape diff --git a/imageai/Prediction/DenseNet/subpixel.py b/imageai/Prediction/DenseNet/subpixel.py deleted file mode 100644 index 4b6323df..00000000 --- a/imageai/Prediction/DenseNet/subpixel.py +++ /dev/null @@ -1,74 +0,0 @@ -from __future__ import absolute_import - -from tensorflow.python.keras.layers import Layer -from tensorflow.python.keras.utils import get_custom_objects -from ..DenseNet.tensorflow_backend import depth_to_space - -class SubPixelUpscaling(Layer): - """ Sub-pixel convolutional upscaling layer based on the paper "Real-Time Single Image - and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network" - (https://arxiv.org/abs/1609.05158). - This layer requires a Convolution2D prior to it, having output filters computed according to - the formula : - filters = k * (scale_factor * scale_factor) - where k = a user defined number of filters (generally larger than 32) - scale_factor = the upscaling factor (generally 2) - This layer performs the depth to space operation on the convolution filters, and returns a - tensor with the size as defined below. - # Example : - ```python - # A standard subpixel upscaling block - x = Convolution2D(256, 3, 3, padding='same', activation='relu')(...) - u = SubPixelUpscaling(scale_factor=2)(x) - [Optional] - x = Convolution2D(256, 3, 3, padding='same', activation='relu')(u) - ``` - In practice, it is useful to have a second convolution layer after the - SubPixelUpscaling layer to speed up the learning process. - However, if you are stacking multiple SubPixelUpscaling blocks, it may increase - the number of parameters greatly, so the Convolution layer after SubPixelUpscaling - layer can be removed. - # Arguments - scale_factor: Upscaling factor. - data_format: Can be None, 'channels_first' or 'channels_last'. - # Input shape - 4D tensor with shape: - `(samples, k * (scale_factor * scale_factor) channels, rows, cols)` if data_format='channels_first' - or 4D tensor with shape: - `(samples, rows, cols, k * (scale_factor * scale_factor) channels)` if data_format='channels_last'. - # Output shape - 4D tensor with shape: - `(samples, k channels, rows * scale_factor, cols * scale_factor))` if data_format='channels_first' - or 4D tensor with shape: - `(samples, rows * scale_factor, cols * scale_factor, k channels)` if data_format='channels_last'. - """ - - def __init__(self, scale_factor=2, data_format=None, **kwargs): - super(SubPixelUpscaling, self).__init__(**kwargs) - - self.scale_factor = scale_factor - self.data_format = "channels_last" - - def build(self, input_shape): - pass - - def call(self, x, mask=None): - y = depth_to_space(x, self.scale_factor, self.data_format) - return y - - def compute_output_shape(self, input_shape): - if self.data_format == 'channels_first': - b, k, r, c = input_shape - return (b, k // (self.scale_factor ** 2), r * self.scale_factor, c * self.scale_factor) - else: - b, r, c, k = input_shape - return (b, r * self.scale_factor, c * self.scale_factor, k // (self.scale_factor ** 2)) - - def get_config(self): - config = {'scale_factor': self.scale_factor, - 'data_format': self.data_format} - base_config = super(SubPixelUpscaling, self).get_config() - return dict(list(base_config.items()) + list(config.items())) - - -get_custom_objects().update({'SubPixelUpscaling': SubPixelUpscaling}) diff --git a/imageai/Prediction/DenseNet/tensorflow_backend.py b/imageai/Prediction/DenseNet/tensorflow_backend.py deleted file mode 100644 index 4cc08eed..00000000 --- a/imageai/Prediction/DenseNet/tensorflow_backend.py +++ /dev/null @@ -1,11 +0,0 @@ -import tensorflow as tf - -py_all = all - -def depth_to_space(input, scale, data_format=None): - ''' Uses phase shift algorithm to convert channels/depth for spatial resolution ''' - data_format = 'NHWC' - - data_format = data_format.lower() - out = tf.depth_to_space(input, scale, data_format=data_format) - return out diff --git a/imageai/Prediction/InceptionV3/__init__.py b/imageai/Prediction/InceptionV3/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/imageai/Prediction/InceptionV3/__pycache__/__init__.cpython-35.pyc b/imageai/Prediction/InceptionV3/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index ea3b129607805701b0c21c191ac7c57d57a900f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmWgV<>k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuViPdn9$oGLuU( s^Ydan^O92wK&&w1nE3e2yv&mLc)fzkTO2mI`6;D2sdgaCi-DK{0CFBM&j0`b diff --git a/imageai/Prediction/InceptionV3/__pycache__/imagenet_utils.cpython-35.pyc b/imageai/Prediction/InceptionV3/__pycache__/imagenet_utils.cpython-35.pyc deleted file mode 100644 index 899dffa4dd11cb4bc45acdb98e78df7bc7c07f4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44688 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 jAOFirsx^j;Rmt^O9i6Yu>6GDi+I diff --git a/imageai/Prediction/InceptionV3/imagenet_utils.py b/imageai/Prediction/InceptionV3/imagenet_utils.py deleted file mode 100644 index 6cf6928f..00000000 --- a/imageai/Prediction/InceptionV3/imagenet_utils.py +++ /dev/null @@ -1,173 +0,0 @@ -import json -import warnings - -from tensorflow.python.keras import backend as K - -CLASS_INDEX = None - - -def preprocess_input(x, data_format=None): - """Preprocesses a tensor encoding a batch of images. - - # Arguments - x: input Numpy tensor, 4D. - data_format: data format of the image tensor. - - # Returns - Preprocessed tensor. - """ - if data_format is None: - data_format = K.image_data_format() - assert data_format in {'channels_last', 'channels_first'} - - if data_format == 'channels_first': - if x.ndim == 3: - # 'RGB'->'BGR' - x = x[::-1, ...] - # Zero-center by mean pixel - x[0, :, :] -= 103.939 - x[1, :, :] -= 116.779 - x[2, :, :] -= 123.68 - else: - x = x[:, ::-1, ...] - x[:, 0, :, :] -= 103.939 - x[:, 1, :, :] -= 116.779 - x[:, 2, :, :] -= 123.68 - else: - # 'RGB'->'BGR' - x = x[..., ::-1] - # Zero-center by mean pixel - x[..., 0] -= 103.939 - x[..., 1] -= 116.779 - x[..., 2] -= 123.68 - return x - - -def decode_predictions(preds, top=5): - """Decodes the prediction of an ImageNet model. - - # Arguments - preds: Numpy tensor encoding a batch of predictions. - top: integer, how many top-guesses to return. - - # Returns - A list of lists of top class prediction tuples - `(class_name, class_description, score)`. - One list of tuples per sample in batch input. - - # Raises - ValueError: in case of invalid shape of the `pred` array - (must be 2D). - """ - global CLASS_INDEX - if len(preds.shape) != 2 or preds.shape[1] != 1000: - raise ValueError('`decode_predictions` expects ' - 'a batch of predictions ' - '(i.e. a 2D array of shape (samples, 1000)). ' - 'Found array with shape: ' + str(preds.shape)) - if CLASS_INDEX is None: - CLASS_INDEX = {"0": ["n01440764", "tench"], "1": ["n01443537", "goldfish"], "2": ["n01484850", "great_white_shark"], "3": ["n01491361", "tiger_shark"], "4": ["n01494475", "hammerhead"], "5": ["n01496331", "electric_ray"], "6": ["n01498041", "stingray"], "7": ["n01514668", "cock"], "8": ["n01514859", "hen"], "9": ["n01518878", "ostrich"], "10": ["n01530575", "brambling"], "11": ["n01531178", "goldfinch"], "12": ["n01532829", "house_finch"], "13": ["n01534433", "junco"], "14": ["n01537544", "indigo_bunting"], "15": ["n01558993", "robin"], "16": ["n01560419", "bulbul"], "17": ["n01580077", "jay"], "18": ["n01582220", "magpie"], "19": ["n01592084", "chickadee"], "20": ["n01601694", "water_ouzel"], "21": ["n01608432", "kite"], "22": ["n01614925", "bald_eagle"], "23": ["n01616318", "vulture"], "24": ["n01622779", "great_grey_owl"], "25": ["n01629819", "European_fire_salamander"], "26": ["n01630670", "common_newt"], "27": ["n01631663", "eft"], "28": ["n01632458", "spotted_salamander"], "29": ["n01632777", "axolotl"], "30": ["n01641577", "bullfrog"], "31": ["n01644373", "tree_frog"], "32": ["n01644900", "tailed_frog"], "33": ["n01664065", "loggerhead"], "34": ["n01665541", "leatherback_turtle"], "35": ["n01667114", "mud_turtle"], "36": ["n01667778", "terrapin"], "37": ["n01669191", "box_turtle"], "38": ["n01675722", "banded_gecko"], "39": ["n01677366", "common_iguana"], "40": ["n01682714", "American_chameleon"], "41": ["n01685808", "whiptail"], "42": ["n01687978", "agama"], "43": ["n01688243", "frilled_lizard"], "44": ["n01689811", "alligator_lizard"], "45": ["n01692333", "Gila_monster"], "46": ["n01693334", "green_lizard"], "47": ["n01694178", "African_chameleon"], "48": ["n01695060", "Komodo_dragon"], "49": ["n01697457", "African_crocodile"], "50": ["n01698640", "American_alligator"], "51": ["n01704323", "triceratops"], "52": ["n01728572", "thunder_snake"], "53": ["n01728920", "ringneck_snake"], "54": ["n01729322", "hognose_snake"], "55": ["n01729977", "green_snake"], "56": ["n01734418", "king_snake"], "57": ["n01735189", "garter_snake"], "58": ["n01737021", "water_snake"], "59": ["n01739381", "vine_snake"], "60": ["n01740131", "night_snake"], "61": ["n01742172", "boa_constrictor"], "62": ["n01744401", "rock_python"], "63": ["n01748264", "Indian_cobra"], "64": ["n01749939", "green_mamba"], "65": ["n01751748", "sea_snake"], "66": ["n01753488", "horned_viper"], "67": ["n01755581", "diamondback"], "68": ["n01756291", "sidewinder"], "69": ["n01768244", "trilobite"], "70": ["n01770081", "harvestman"], "71": ["n01770393", "scorpion"], "72": ["n01773157", "black_and_gold_garden_spider"], "73": ["n01773549", "barn_spider"], "74": ["n01773797", "garden_spider"], "75": ["n01774384", "black_widow"], "76": ["n01774750", "tarantula"], "77": ["n01775062", "wolf_spider"], "78": ["n01776313", "tick"], "79": ["n01784675", "centipede"], "80": ["n01795545", "black_grouse"], "81": ["n01796340", "ptarmigan"], "82": ["n01797886", "ruffed_grouse"], "83": ["n01798484", "prairie_chicken"], "84": ["n01806143", "peacock"], "85": ["n01806567", "quail"], "86": ["n01807496", "partridge"], "87": ["n01817953", "African_grey"], "88": ["n01818515", "macaw"], "89": ["n01819313", "sulphur-crested_cockatoo"], "90": ["n01820546", "lorikeet"], "91": ["n01824575", "coucal"], "92": ["n01828970", "bee_eater"], "93": ["n01829413", "hornbill"], "94": ["n01833805", "hummingbird"], "95": ["n01843065", "jacamar"], "96": ["n01843383", "toucan"], "97": ["n01847000", "drake"], "98": ["n01855032", "red-breasted_merganser"], "99": ["n01855672", "goose"], "100": ["n01860187", "black_swan"], "101": ["n01871265", "tusker"], "102": ["n01872401", "echidna"], "103": ["n01873310", "platypus"], "104": ["n01877812", "wallaby"], "105": ["n01882714", "koala"], "106": ["n01883070", "wombat"], "107": ["n01910747", "jellyfish"], "108": ["n01914609", "sea_anemone"], "109": ["n01917289", "brain_coral"], "110": ["n01924916", "flatworm"], "111": ["n01930112", "nematode"], "112": ["n01943899", "conch"], "113": ["n01944390", "snail"], "114": ["n01945685", "slug"], "115": ["n01950731", "sea_slug"], "116": ["n01955084", "chiton"], "117": ["n01968897", "chambered_nautilus"], "118": ["n01978287", "Dungeness_crab"], "119": ["n01978455", "rock_crab"], "120": ["n01980166", "fiddler_crab"], "121": ["n01981276", "king_crab"], "122": ["n01983481", "American_lobster"], "123": ["n01984695", "spiny_lobster"], "124": ["n01985128", "crayfish"], "125": ["n01986214", "hermit_crab"], "126": ["n01990800", "isopod"], "127": ["n02002556", "white_stork"], "128": ["n02002724", "black_stork"], "129": ["n02006656", "spoonbill"], "130": ["n02007558", "flamingo"], "131": ["n02009229", "little_blue_heron"], "132": ["n02009912", "American_egret"], "133": ["n02011460", "bittern"], "134": ["n02012849", "crane"], "135": ["n02013706", "limpkin"], "136": ["n02017213", "European_gallinule"], "137": ["n02018207", "American_coot"], "138": ["n02018795", "bustard"], "139": ["n02025239", "ruddy_turnstone"], "140": ["n02027492", "red-backed_sandpiper"], "141": ["n02028035", "redshank"], "142": ["n02033041", "dowitcher"], "143": ["n02037110", "oystercatcher"], "144": ["n02051845", "pelican"], "145": ["n02056570", "king_penguin"], "146": ["n02058221", "albatross"], "147": ["n02066245", "grey_whale"], "148": ["n02071294", "killer_whale"], "149": ["n02074367", "dugong"], "150": ["n02077923", "sea_lion"], "151": ["n02085620", "Chihuahua"], "152": ["n02085782", "Japanese_spaniel"], "153": ["n02085936", "Maltese_dog"], "154": ["n02086079", "Pekinese"], "155": ["n02086240", "Shih-Tzu"], "156": ["n02086646", "Blenheim_spaniel"], "157": ["n02086910", "papillon"], "158": ["n02087046", "toy_terrier"], "159": ["n02087394", "Rhodesian_ridgeback"], "160": ["n02088094", "Afghan_hound"], "161": ["n02088238", "basset"], "162": ["n02088364", "beagle"], "163": ["n02088466", "bloodhound"], "164": ["n02088632", "bluetick"], "165": ["n02089078", "black-and-tan_coonhound"], "166": ["n02089867", "Walker_hound"], "167": ["n02089973", "English_foxhound"], "168": ["n02090379", "redbone"], "169": ["n02090622", "borzoi"], "170": ["n02090721", "Irish_wolfhound"], "171": ["n02091032", "Italian_greyhound"], "172": ["n02091134", "whippet"], "173": ["n02091244", "Ibizan_hound"], "174": ["n02091467", "Norwegian_elkhound"], "175": ["n02091635", "otterhound"], "176": ["n02091831", "Saluki"], "177": ["n02092002", "Scottish_deerhound"], "178": ["n02092339", "Weimaraner"], "179": ["n02093256", "Staffordshire_bullterrier"], "180": ["n02093428", "American_Staffordshire_terrier"], "181": ["n02093647", "Bedlington_terrier"], "182": ["n02093754", "Border_terrier"], "183": ["n02093859", "Kerry_blue_terrier"], "184": ["n02093991", "Irish_terrier"], "185": ["n02094114", "Norfolk_terrier"], "186": ["n02094258", "Norwich_terrier"], "187": ["n02094433", "Yorkshire_terrier"], "188": ["n02095314", "wire-haired_fox_terrier"], "189": ["n02095570", "Lakeland_terrier"], "190": ["n02095889", "Sealyham_terrier"], "191": ["n02096051", "Airedale"], "192": ["n02096177", "cairn"], "193": ["n02096294", "Australian_terrier"], "194": ["n02096437", "Dandie_Dinmont"], "195": ["n02096585", "Boston_bull"], "196": ["n02097047", "miniature_schnauzer"], "197": ["n02097130", "giant_schnauzer"], "198": ["n02097209", "standard_schnauzer"], "199": ["n02097298", "Scotch_terrier"], "200": ["n02097474", "Tibetan_terrier"], "201": ["n02097658", "silky_terrier"], "202": ["n02098105", "soft-coated_wheaten_terrier"], "203": ["n02098286", "West_Highland_white_terrier"], "204": ["n02098413", "Lhasa"], "205": ["n02099267", "flat-coated_retriever"], "206": ["n02099429", "curly-coated_retriever"], "207": ["n02099601", "golden_retriever"], "208": ["n02099712", "Labrador_retriever"], "209": ["n02099849", "Chesapeake_Bay_retriever"], "210": ["n02100236", "German_short-haired_pointer"], "211": ["n02100583", "vizsla"], "212": ["n02100735", "English_setter"], "213": ["n02100877", "Irish_setter"], "214": ["n02101006", "Gordon_setter"], "215": ["n02101388", "Brittany_spaniel"], "216": ["n02101556", "clumber"], "217": ["n02102040", "English_springer"], "218": ["n02102177", "Welsh_springer_spaniel"], "219": ["n02102318", "cocker_spaniel"], "220": ["n02102480", "Sussex_spaniel"], "221": ["n02102973", "Irish_water_spaniel"], "222": ["n02104029", "kuvasz"], "223": ["n02104365", "schipperke"], "224": ["n02105056", "groenendael"], "225": ["n02105162", "malinois"], "226": ["n02105251", "briard"], "227": ["n02105412", "kelpie"], "228": ["n02105505", "komondor"], "229": ["n02105641", "Old_English_sheepdog"], "230": ["n02105855", "Shetland_sheepdog"], "231": ["n02106030", "collie"], "232": ["n02106166", "Border_collie"], "233": ["n02106382", "Bouvier_des_Flandres"], "234": ["n02106550", "Rottweiler"], "235": ["n02106662", "German_shepherd"], "236": ["n02107142", "Doberman"], "237": ["n02107312", "miniature_pinscher"], "238": ["n02107574", "Greater_Swiss_Mountain_dog"], "239": ["n02107683", "Bernese_mountain_dog"], "240": ["n02107908", "Appenzeller"], "241": ["n02108000", "EntleBucher"], "242": ["n02108089", "boxer"], "243": ["n02108422", "bull_mastiff"], "244": ["n02108551", "Tibetan_mastiff"], "245": ["n02108915", "French_bulldog"], "246": ["n02109047", "Great_Dane"], "247": ["n02109525", "Saint_Bernard"], "248": ["n02109961", "Eskimo_dog"], "249": ["n02110063", "malamute"], "250": ["n02110185", "Siberian_husky"], "251": ["n02110341", "dalmatian"], "252": ["n02110627", "affenpinscher"], "253": ["n02110806", "basenji"], "254": ["n02110958", "pug"], "255": ["n02111129", "Leonberg"], "256": ["n02111277", "Newfoundland"], "257": ["n02111500", "Great_Pyrenees"], "258": ["n02111889", "Samoyed"], "259": ["n02112018", "Pomeranian"], "260": ["n02112137", "chow"], "261": ["n02112350", "keeshond"], "262": ["n02112706", "Brabancon_griffon"], "263": ["n02113023", "Pembroke"], "264": ["n02113186", "Cardigan"], "265": ["n02113624", "toy_poodle"], "266": ["n02113712", "miniature_poodle"], "267": ["n02113799", "standard_poodle"], "268": ["n02113978", "Mexican_hairless"], "269": ["n02114367", "timber_wolf"], "270": ["n02114548", "white_wolf"], "271": ["n02114712", "red_wolf"], "272": ["n02114855", "coyote"], "273": ["n02115641", "dingo"], "274": ["n02115913", "dhole"], "275": ["n02116738", "African_hunting_dog"], "276": ["n02117135", "hyena"], "277": ["n02119022", "red_fox"], "278": ["n02119789", "kit_fox"], "279": ["n02120079", "Arctic_fox"], "280": ["n02120505", "grey_fox"], "281": ["n02123045", "tabby"], "282": ["n02123159", "tiger_cat"], "283": ["n02123394", "Persian_cat"], "284": ["n02123597", "Siamese_cat"], "285": ["n02124075", "Egyptian_cat"], "286": ["n02125311", "cougar"], "287": ["n02127052", "lynx"], "288": ["n02128385", "leopard"], "289": ["n02128757", "snow_leopard"], "290": ["n02128925", "jaguar"], "291": ["n02129165", "lion"], "292": ["n02129604", "tiger"], "293": ["n02130308", "cheetah"], "294": ["n02132136", "brown_bear"], "295": ["n02133161", "American_black_bear"], "296": ["n02134084", "ice_bear"], "297": ["n02134418", "sloth_bear"], "298": ["n02137549", "mongoose"], "299": ["n02138441", "meerkat"], "300": ["n02165105", "tiger_beetle"], "301": ["n02165456", "ladybug"], "302": ["n02167151", "ground_beetle"], "303": ["n02168699", "long-horned_beetle"], "304": ["n02169497", "leaf_beetle"], "305": ["n02172182", "dung_beetle"], "306": ["n02174001", "rhinoceros_beetle"], "307": ["n02177972", "weevil"], "308": ["n02190166", "fly"], "309": ["n02206856", "bee"], "310": ["n02219486", "ant"], "311": ["n02226429", "grasshopper"], "312": ["n02229544", "cricket"], "313": ["n02231487", "walking_stick"], "314": ["n02233338", "cockroach"], "315": ["n02236044", "mantis"], "316": ["n02256656", "cicada"], "317": ["n02259212", "leafhopper"], "318": ["n02264363", "lacewing"], "319": ["n02268443", "dragonfly"], "320": ["n02268853", "damselfly"], "321": ["n02276258", "admiral"], "322": ["n02277742", "ringlet"], "323": ["n02279972", "monarch"], "324": ["n02280649", "cabbage_butterfly"], "325": ["n02281406", "sulphur_butterfly"], "326": ["n02281787", "lycaenid"], "327": ["n02317335", "starfish"], "328": ["n02319095", "sea_urchin"], "329": ["n02321529", "sea_cucumber"], "330": ["n02325366", "wood_rabbit"], "331": ["n02326432", "hare"], "332": ["n02328150", "Angora"], "333": ["n02342885", "hamster"], "334": ["n02346627", "porcupine"], "335": ["n02356798", "fox_squirrel"], "336": ["n02361337", "marmot"], "337": ["n02363005", "beaver"], "338": ["n02364673", "guinea_pig"], "339": ["n02389026", "sorrel"], "340": ["n02391049", "zebra"], "341": ["n02395406", "hog"], "342": ["n02396427", "wild_boar"], "343": ["n02397096", "warthog"], "344": ["n02398521", "hippopotamus"], "345": ["n02403003", "ox"], "346": ["n02408429", "water_buffalo"], "347": ["n02410509", "bison"], "348": ["n02412080", "ram"], "349": ["n02415577", "bighorn"], "350": ["n02417914", "ibex"], "351": ["n02422106", "hartebeest"], "352": ["n02422699", "impala"], "353": ["n02423022", "gazelle"], "354": ["n02437312", "Arabian_camel"], "355": ["n02437616", "llama"], "356": ["n02441942", "weasel"], "357": ["n02442845", "mink"], "358": ["n02443114", "polecat"], "359": ["n02443484", "black-footed_ferret"], "360": ["n02444819", "otter"], "361": ["n02445715", "skunk"], "362": ["n02447366", "badger"], "363": ["n02454379", "armadillo"], "364": ["n02457408", "three-toed_sloth"], "365": ["n02480495", "orangutan"], "366": ["n02480855", "gorilla"], "367": ["n02481823", "chimpanzee"], "368": ["n02483362", "gibbon"], "369": ["n02483708", "siamang"], "370": ["n02484975", "guenon"], "371": ["n02486261", "patas"], "372": ["n02486410", "baboon"], "373": ["n02487347", "macaque"], "374": ["n02488291", "langur"], "375": ["n02488702", "colobus"], "376": ["n02489166", "proboscis_monkey"], "377": ["n02490219", "marmoset"], "378": ["n02492035", "capuchin"], "379": ["n02492660", "howler_monkey"], "380": ["n02493509", "titi"], "381": ["n02493793", "spider_monkey"], "382": ["n02494079", "squirrel_monkey"], "383": ["n02497673", "Madagascar_cat"], "384": ["n02500267", "indri"], "385": ["n02504013", "Indian_elephant"], "386": ["n02504458", "African_elephant"], "387": ["n02509815", "lesser_panda"], "388": ["n02510455", "giant_panda"], "389": ["n02514041", "barracouta"], "390": ["n02526121", "eel"], "391": ["n02536864", "coho"], "392": ["n02606052", "rock_beauty"], "393": ["n02607072", "anemone_fish"], "394": ["n02640242", "sturgeon"], "395": ["n02641379", "gar"], "396": ["n02643566", "lionfish"], "397": ["n02655020", "puffer"], "398": ["n02666196", "abacus"], "399": ["n02667093", "abaya"], "400": ["n02669723", "academic_gown"], "401": ["n02672831", "accordion"], "402": ["n02676566", "acoustic_guitar"], "403": ["n02687172", "aircraft_carrier"], "404": ["n02690373", "airliner"], "405": ["n02692877", "airship"], "406": ["n02699494", "altar"], "407": ["n02701002", "ambulance"], "408": ["n02704792", "amphibian"], "409": ["n02708093", "analog_clock"], "410": ["n02727426", "apiary"], "411": ["n02730930", "apron"], "412": ["n02747177", "ashcan"], "413": ["n02749479", "assault_rifle"], "414": ["n02769748", "backpack"], "415": ["n02776631", "bakery"], "416": ["n02777292", "balance_beam"], "417": ["n02782093", "balloon"], "418": ["n02783161", "ballpoint"], "419": ["n02786058", "Band_Aid"], "420": ["n02787622", "banjo"], "421": ["n02788148", "bannister"], "422": ["n02790996", "barbell"], "423": ["n02791124", "barber_chair"], "424": ["n02791270", "barbershop"], "425": ["n02793495", "barn"], "426": ["n02794156", "barometer"], "427": ["n02795169", "barrel"], "428": ["n02797295", "barrow"], "429": ["n02799071", "baseball"], "430": ["n02802426", "basketball"], "431": ["n02804414", "bassinet"], "432": ["n02804610", "bassoon"], "433": ["n02807133", "bathing_cap"], "434": ["n02808304", "bath_towel"], "435": ["n02808440", "bathtub"], "436": ["n02814533", "beach_wagon"], "437": ["n02814860", "beacon"], "438": ["n02815834", "beaker"], "439": ["n02817516", "bearskin"], "440": ["n02823428", "beer_bottle"], "441": ["n02823750", "beer_glass"], "442": ["n02825657", "bell_cote"], "443": ["n02834397", "bib"], "444": ["n02835271", "bicycle-built-for-two"], "445": ["n02837789", "bikini"], "446": ["n02840245", "binder"], "447": ["n02841315", "binoculars"], "448": ["n02843684", "birdhouse"], "449": ["n02859443", "boathouse"], "450": ["n02860847", "bobsled"], "451": ["n02865351", "bolo_tie"], "452": ["n02869837", "bonnet"], "453": ["n02870880", "bookcase"], "454": ["n02871525", "bookshop"], "455": ["n02877765", "bottlecap"], "456": ["n02879718", "bow"], "457": ["n02883205", "bow_tie"], "458": ["n02892201", "brass"], "459": ["n02892767", "brassiere"], "460": ["n02894605", "breakwater"], "461": ["n02895154", "breastplate"], "462": ["n02906734", "broom"], "463": ["n02909870", "bucket"], "464": ["n02910353", "buckle"], "465": ["n02916936", "bulletproof_vest"], "466": ["n02917067", "bullet_train"], "467": ["n02927161", "butcher_shop"], "468": ["n02930766", "cab"], "469": ["n02939185", "caldron"], "470": ["n02948072", "candle"], "471": ["n02950826", "cannon"], "472": ["n02951358", "canoe"], "473": ["n02951585", "can_opener"], "474": ["n02963159", "cardigan"], "475": ["n02965783", "car_mirror"], "476": ["n02966193", "carousel"], "477": ["n02966687", "carpenter's_kit"], "478": ["n02971356", "carton"], "479": ["n02974003", "car_wheel"], "480": ["n02977058", "cash_machine"], "481": ["n02978881", "cassette"], "482": ["n02979186", "cassette_player"], "483": ["n02980441", "castle"], "484": ["n02981792", "catamaran"], "485": ["n02988304", "CD_player"], "486": ["n02992211", "cello"], "487": ["n02992529", "cellular_telephone"], "488": ["n02999410", "chain"], "489": ["n03000134", "chainlink_fence"], "490": ["n03000247", "chain_mail"], "491": ["n03000684", "chain_saw"], "492": ["n03014705", "chest"], "493": ["n03016953", "chiffonier"], "494": ["n03017168", "chime"], "495": ["n03018349", "china_cabinet"], "496": ["n03026506", "Christmas_stocking"], "497": ["n03028079", "church"], "498": ["n03032252", "cinema"], "499": ["n03041632", "cleaver"], "500": ["n03042490", "cliff_dwelling"], "501": ["n03045698", "cloak"], "502": ["n03047690", "clog"], "503": ["n03062245", "cocktail_shaker"], "504": ["n03063599", "coffee_mug"], "505": ["n03063689", "coffeepot"], "506": ["n03065424", "coil"], "507": ["n03075370", "combination_lock"], "508": ["n03085013", "computer_keyboard"], "509": ["n03089624", "confectionery"], "510": ["n03095699", "container_ship"], "511": ["n03100240", "convertible"], "512": ["n03109150", "corkscrew"], "513": ["n03110669", "cornet"], "514": ["n03124043", "cowboy_boot"], "515": ["n03124170", "cowboy_hat"], "516": ["n03125729", "cradle"], "517": ["n03126707", "crane"], "518": ["n03127747", "crash_helmet"], "519": ["n03127925", "crate"], "520": ["n03131574", "crib"], "521": ["n03133878", "Crock_Pot"], "522": ["n03134739", "croquet_ball"], "523": ["n03141823", "crutch"], "524": ["n03146219", "cuirass"], "525": ["n03160309", "dam"], "526": ["n03179701", "desk"], "527": ["n03180011", "desktop_computer"], "528": ["n03187595", "dial_telephone"], "529": ["n03188531", "diaper"], "530": ["n03196217", "digital_clock"], "531": ["n03197337", "digital_watch"], "532": ["n03201208", "dining_table"], "533": ["n03207743", "dishrag"], "534": ["n03207941", "dishwasher"], "535": ["n03208938", "disk_brake"], "536": ["n03216828", "dock"], "537": ["n03218198", "dogsled"], "538": ["n03220513", "dome"], "539": ["n03223299", "doormat"], "540": ["n03240683", "drilling_platform"], "541": ["n03249569", "drum"], "542": ["n03250847", "drumstick"], "543": ["n03255030", "dumbbell"], "544": ["n03259280", "Dutch_oven"], "545": ["n03271574", "electric_fan"], "546": ["n03272010", "electric_guitar"], "547": ["n03272562", "electric_locomotive"], "548": ["n03290653", "entertainment_center"], "549": ["n03291819", "envelope"], "550": ["n03297495", "espresso_maker"], "551": ["n03314780", "face_powder"], "552": ["n03325584", "feather_boa"], "553": ["n03337140", "file"], "554": ["n03344393", "fireboat"], "555": ["n03345487", "fire_engine"], "556": ["n03347037", "fire_screen"], "557": ["n03355925", "flagpole"], "558": ["n03372029", "flute"], "559": ["n03376595", "folding_chair"], "560": ["n03379051", "football_helmet"], "561": ["n03384352", "forklift"], "562": ["n03388043", "fountain"], "563": ["n03388183", "fountain_pen"], "564": ["n03388549", "four-poster"], "565": ["n03393912", "freight_car"], "566": ["n03394916", "French_horn"], "567": ["n03400231", "frying_pan"], "568": ["n03404251", "fur_coat"], "569": ["n03417042", "garbage_truck"], "570": ["n03424325", "gasmask"], "571": ["n03425413", "gas_pump"], "572": ["n03443371", "goblet"], "573": ["n03444034", "go-kart"], "574": ["n03445777", "golf_ball"], "575": ["n03445924", "golfcart"], "576": ["n03447447", "gondola"], "577": ["n03447721", "gong"], "578": ["n03450230", "gown"], "579": ["n03452741", "grand_piano"], "580": ["n03457902", "greenhouse"], "581": ["n03459775", "grille"], "582": ["n03461385", "grocery_store"], "583": ["n03467068", "guillotine"], "584": ["n03476684", "hair_slide"], "585": ["n03476991", "hair_spray"], "586": ["n03478589", "half_track"], "587": ["n03481172", "hammer"], "588": ["n03482405", "hamper"], "589": ["n03483316", "hand_blower"], "590": ["n03485407", "hand-held_computer"], "591": ["n03485794", "handkerchief"], "592": ["n03492542", "hard_disc"], "593": ["n03494278", "harmonica"], "594": ["n03495258", "harp"], "595": ["n03496892", "harvester"], "596": ["n03498962", "hatchet"], "597": ["n03527444", "holster"], "598": ["n03529860", "home_theater"], "599": ["n03530642", "honeycomb"], "600": ["n03532672", "hook"], "601": ["n03534580", "hoopskirt"], "602": ["n03535780", "horizontal_bar"], "603": ["n03538406", "horse_cart"], "604": ["n03544143", "hourglass"], "605": ["n03584254", "iPod"], "606": ["n03584829", "iron"], "607": ["n03590841", "jack-o'-lantern"], "608": ["n03594734", "jean"], "609": ["n03594945", "jeep"], "610": ["n03595614", "jersey"], "611": ["n03598930", "jigsaw_puzzle"], "612": ["n03599486", "jinrikisha"], "613": ["n03602883", "joystick"], "614": ["n03617480", "kimono"], "615": ["n03623198", "knee_pad"], "616": ["n03627232", "knot"], "617": ["n03630383", "lab_coat"], "618": ["n03633091", "ladle"], "619": ["n03637318", "lampshade"], "620": ["n03642806", "laptop"], "621": ["n03649909", "lawn_mower"], "622": ["n03657121", "lens_cap"], "623": ["n03658185", "letter_opener"], "624": ["n03661043", "library"], "625": ["n03662601", "lifeboat"], "626": ["n03666591", "lighter"], "627": ["n03670208", "limousine"], "628": ["n03673027", "liner"], "629": ["n03676483", "lipstick"], "630": ["n03680355", "Loafer"], "631": ["n03690938", "lotion"], "632": ["n03691459", "loudspeaker"], "633": ["n03692522", "loupe"], "634": ["n03697007", "lumbermill"], "635": ["n03706229", "magnetic_compass"], "636": ["n03709823", "mailbag"], "637": ["n03710193", "mailbox"], "638": ["n03710637", "maillot"], "639": ["n03710721", "maillot"], "640": ["n03717622", "manhole_cover"], "641": ["n03720891", "maraca"], "642": ["n03721384", "marimba"], "643": ["n03724870", "mask"], "644": ["n03729826", "matchstick"], "645": ["n03733131", "maypole"], "646": ["n03733281", "maze"], "647": ["n03733805", "measuring_cup"], "648": ["n03742115", "medicine_chest"], "649": ["n03743016", "megalith"], "650": ["n03759954", "microphone"], "651": ["n03761084", "microwave"], "652": ["n03763968", "military_uniform"], "653": ["n03764736", "milk_can"], "654": ["n03769881", "minibus"], "655": ["n03770439", "miniskirt"], "656": ["n03770679", "minivan"], "657": ["n03773504", "missile"], "658": ["n03775071", "mitten"], "659": ["n03775546", "mixing_bowl"], "660": ["n03776460", "mobile_home"], "661": ["n03777568", "Model_T"], "662": ["n03777754", "modem"], "663": ["n03781244", "monastery"], "664": ["n03782006", "monitor"], "665": ["n03785016", "moped"], "666": ["n03786901", "mortar"], "667": ["n03787032", "mortarboard"], "668": ["n03788195", "mosque"], "669": ["n03788365", "mosquito_net"], "670": ["n03791053", "motor_scooter"], "671": ["n03792782", "mountain_bike"], "672": ["n03792972", "mountain_tent"], "673": ["n03793489", "mouse"], "674": ["n03794056", "mousetrap"], "675": ["n03796401", "moving_van"], "676": ["n03803284", "muzzle"], "677": ["n03804744", "nail"], "678": ["n03814639", "neck_brace"], "679": ["n03814906", "necklace"], "680": ["n03825788", "nipple"], "681": ["n03832673", "notebook"], "682": ["n03837869", "obelisk"], "683": ["n03838899", "oboe"], "684": ["n03840681", "ocarina"], "685": ["n03841143", "odometer"], "686": ["n03843555", "oil_filter"], "687": ["n03854065", "organ"], "688": ["n03857828", "oscilloscope"], "689": ["n03866082", "overskirt"], "690": ["n03868242", "oxcart"], "691": ["n03868863", "oxygen_mask"], "692": ["n03871628", "packet"], "693": ["n03873416", "paddle"], "694": ["n03874293", "paddlewheel"], "695": ["n03874599", "padlock"], "696": ["n03876231", "paintbrush"], "697": ["n03877472", "pajama"], "698": ["n03877845", "palace"], "699": ["n03884397", "panpipe"], "700": ["n03887697", "paper_towel"], "701": ["n03888257", "parachute"], "702": ["n03888605", "parallel_bars"], "703": ["n03891251", "park_bench"], "704": ["n03891332", "parking_meter"], "705": ["n03895866", "passenger_car"], "706": ["n03899768", "patio"], "707": ["n03902125", "pay-phone"], "708": ["n03903868", "pedestal"], "709": ["n03908618", "pencil_box"], "710": ["n03908714", "pencil_sharpener"], "711": ["n03916031", "perfume"], "712": ["n03920288", "Petri_dish"], "713": ["n03924679", "photocopier"], "714": ["n03929660", "pick"], "715": ["n03929855", "pickelhaube"], "716": ["n03930313", "picket_fence"], "717": ["n03930630", "pickup"], "718": ["n03933933", "pier"], "719": ["n03935335", "piggy_bank"], "720": ["n03937543", "pill_bottle"], "721": ["n03938244", "pillow"], "722": ["n03942813", "ping-pong_ball"], "723": ["n03944341", "pinwheel"], "724": ["n03947888", "pirate"], "725": ["n03950228", "pitcher"], "726": ["n03954731", "plane"], "727": ["n03956157", "planetarium"], "728": ["n03958227", "plastic_bag"], "729": ["n03961711", "plate_rack"], "730": ["n03967562", "plow"], "731": ["n03970156", "plunger"], "732": ["n03976467", "Polaroid_camera"], "733": ["n03976657", "pole"], "734": ["n03977966", "police_van"], "735": ["n03980874", "poncho"], "736": ["n03982430", "pool_table"], "737": ["n03983396", "pop_bottle"], "738": ["n03991062", "pot"], "739": ["n03992509", "potter's_wheel"], "740": ["n03995372", "power_drill"], "741": ["n03998194", "prayer_rug"], "742": ["n04004767", "printer"], "743": ["n04005630", "prison"], "744": ["n04008634", "projectile"], "745": ["n04009552", "projector"], "746": ["n04019541", "puck"], "747": ["n04023962", "punching_bag"], "748": ["n04026417", "purse"], "749": ["n04033901", "quill"], "750": ["n04033995", "quilt"], "751": ["n04037443", "racer"], "752": ["n04039381", "racket"], "753": ["n04040759", "radiator"], "754": ["n04041544", "radio"], "755": ["n04044716", "radio_telescope"], "756": ["n04049303", "rain_barrel"], "757": ["n04065272", "recreational_vehicle"], "758": ["n04067472", "reel"], "759": ["n04069434", "reflex_camera"], "760": ["n04070727", "refrigerator"], "761": ["n04074963", "remote_control"], "762": ["n04081281", "restaurant"], "763": ["n04086273", "revolver"], "764": ["n04090263", "rifle"], "765": ["n04099969", "rocking_chair"], "766": ["n04111531", "rotisserie"], "767": ["n04116512", "rubber_eraser"], "768": ["n04118538", "rugby_ball"], "769": ["n04118776", "rule"], "770": ["n04120489", "running_shoe"], "771": ["n04125021", "safe"], "772": ["n04127249", "safety_pin"], "773": ["n04131690", "saltshaker"], "774": ["n04133789", "sandal"], "775": ["n04136333", "sarong"], "776": ["n04141076", "sax"], "777": ["n04141327", "scabbard"], "778": ["n04141975", "scale"], "779": ["n04146614", "school_bus"], "780": ["n04147183", "schooner"], "781": ["n04149813", "scoreboard"], "782": ["n04152593", "screen"], "783": ["n04153751", "screw"], "784": ["n04154565", "screwdriver"], "785": ["n04162706", "seat_belt"], "786": ["n04179913", "sewing_machine"], "787": ["n04192698", "shield"], "788": ["n04200800", "shoe_shop"], "789": ["n04201297", "shoji"], "790": ["n04204238", "shopping_basket"], "791": ["n04204347", "shopping_cart"], "792": ["n04208210", "shovel"], "793": ["n04209133", "shower_cap"], "794": ["n04209239", "shower_curtain"], "795": ["n04228054", "ski"], "796": ["n04229816", "ski_mask"], "797": ["n04235860", "sleeping_bag"], "798": ["n04238763", "slide_rule"], "799": ["n04239074", "sliding_door"], "800": ["n04243546", "slot"], "801": ["n04251144", "snorkel"], "802": ["n04252077", "snowmobile"], "803": ["n04252225", "snowplow"], "804": ["n04254120", "soap_dispenser"], "805": ["n04254680", "soccer_ball"], "806": ["n04254777", "sock"], "807": ["n04258138", "solar_dish"], "808": ["n04259630", "sombrero"], "809": ["n04263257", "soup_bowl"], "810": ["n04264628", "space_bar"], "811": ["n04265275", "space_heater"], "812": ["n04266014", "space_shuttle"], "813": ["n04270147", "spatula"], "814": ["n04273569", "speedboat"], "815": ["n04275548", "spider_web"], "816": ["n04277352", "spindle"], "817": ["n04285008", "sports_car"], "818": ["n04286575", "spotlight"], "819": ["n04296562", "stage"], "820": ["n04310018", "steam_locomotive"], "821": ["n04311004", "steel_arch_bridge"], "822": ["n04311174", "steel_drum"], "823": ["n04317175", "stethoscope"], "824": ["n04325704", "stole"], "825": ["n04326547", "stone_wall"], "826": ["n04328186", "stopwatch"], "827": ["n04330267", "stove"], "828": ["n04332243", "strainer"], "829": ["n04335435", "streetcar"], "830": ["n04336792", "stretcher"], "831": ["n04344873", "studio_couch"], "832": ["n04346328", "stupa"], "833": ["n04347754", "submarine"], "834": ["n04350905", "suit"], "835": ["n04355338", "sundial"], "836": ["n04355933", "sunglass"], "837": ["n04356056", "sunglasses"], "838": ["n04357314", "sunscreen"], "839": ["n04366367", "suspension_bridge"], "840": ["n04367480", "swab"], "841": ["n04370456", "sweatshirt"], "842": ["n04371430", "swimming_trunks"], "843": ["n04371774", "swing"], "844": ["n04372370", "switch"], "845": ["n04376876", "syringe"], "846": ["n04380533", "table_lamp"], "847": ["n04389033", "tank"], "848": ["n04392985", "tape_player"], "849": ["n04398044", "teapot"], "850": ["n04399382", "teddy"], "851": ["n04404412", "television"], "852": ["n04409515", "tennis_ball"], "853": ["n04417672", "thatch"], "854": ["n04418357", "theater_curtain"], "855": ["n04423845", "thimble"], "856": ["n04428191", "thresher"], "857": ["n04429376", "throne"], "858": ["n04435653", "tile_roof"], "859": ["n04442312", "toaster"], "860": ["n04443257", "tobacco_shop"], "861": ["n04447861", "toilet_seat"], "862": ["n04456115", "torch"], "863": ["n04458633", "totem_pole"], "864": ["n04461696", "tow_truck"], "865": ["n04462240", "toyshop"], "866": ["n04465501", "tractor"], "867": ["n04467665", "trailer_truck"], "868": ["n04476259", "tray"], "869": ["n04479046", "trench_coat"], "870": ["n04482393", "tricycle"], "871": ["n04483307", "trimaran"], "872": ["n04485082", "tripod"], "873": ["n04486054", "triumphal_arch"], "874": ["n04487081", "trolleybus"], "875": ["n04487394", "trombone"], "876": ["n04493381", "tub"], "877": ["n04501370", "turnstile"], "878": ["n04505470", "typewriter_keyboard"], "879": ["n04507155", "umbrella"], "880": ["n04509417", "unicycle"], "881": ["n04515003", "upright"], "882": ["n04517823", "vacuum"], "883": ["n04522168", "vase"], "884": ["n04523525", "vault"], "885": ["n04525038", "velvet"], "886": ["n04525305", "vending_machine"], "887": ["n04532106", "vestment"], "888": ["n04532670", "viaduct"], "889": ["n04536866", "violin"], "890": ["n04540053", "volleyball"], "891": ["n04542943", "waffle_iron"], "892": ["n04548280", "wall_clock"], "893": ["n04548362", "wallet"], "894": ["n04550184", "wardrobe"], "895": ["n04552348", "warplane"], "896": ["n04553703", "washbasin"], "897": ["n04554684", "washer"], "898": ["n04557648", "water_bottle"], "899": ["n04560804", "water_jug"], "900": ["n04562935", "water_tower"], "901": ["n04579145", "whiskey_jug"], "902": ["n04579432", "whistle"], "903": ["n04584207", "wig"], "904": ["n04589890", "window_screen"], "905": ["n04590129", "window_shade"], "906": ["n04591157", "Windsor_tie"], "907": ["n04591713", "wine_bottle"], "908": ["n04592741", "wing"], "909": ["n04596742", "wok"], "910": ["n04597913", "wooden_spoon"], "911": ["n04599235", "wool"], "912": ["n04604644", "worm_fence"], "913": ["n04606251", "wreck"], "914": ["n04612504", "yawl"], "915": ["n04613696", "yurt"], "916": ["n06359193", "web_site"], "917": ["n06596364", "comic_book"], "918": ["n06785654", "crossword_puzzle"], "919": ["n06794110", "street_sign"], "920": ["n06874185", "traffic_light"], "921": ["n07248320", "book_jacket"], "922": ["n07565083", "menu"], "923": ["n07579787", "plate"], "924": ["n07583066", "guacamole"], "925": ["n07584110", "consomme"], "926": ["n07590611", "hot_pot"], "927": ["n07613480", "trifle"], "928": ["n07614500", "ice_cream"], "929": ["n07615774", "ice_lolly"], "930": ["n07684084", "French_loaf"], "931": ["n07693725", "bagel"], "932": ["n07695742", "pretzel"], "933": ["n07697313", "cheeseburger"], "934": ["n07697537", "hotdog"], "935": ["n07711569", "mashed_potato"], "936": ["n07714571", "head_cabbage"], "937": ["n07714990", "broccoli"], "938": ["n07715103", "cauliflower"], "939": ["n07716358", "zucchini"], "940": ["n07716906", "spaghetti_squash"], "941": ["n07717410", "acorn_squash"], "942": ["n07717556", "butternut_squash"], "943": ["n07718472", "cucumber"], "944": ["n07718747", "artichoke"], "945": ["n07720875", "bell_pepper"], "946": ["n07730033", "cardoon"], "947": ["n07734744", "mushroom"], "948": ["n07742313", "Granny_Smith"], "949": ["n07745940", "strawberry"], "950": ["n07747607", "orange"], "951": ["n07749582", "lemon"], "952": ["n07753113", "fig"], "953": ["n07753275", "pineapple"], "954": ["n07753592", "banana"], "955": ["n07754684", "jackfruit"], "956": ["n07760859", "custard_apple"], "957": ["n07768694", "pomegranate"], "958": ["n07802026", "hay"], "959": ["n07831146", "carbonara"], "960": ["n07836838", "chocolate_sauce"], "961": ["n07860988", "dough"], "962": ["n07871810", "meat_loaf"], "963": ["n07873807", "pizza"], "964": ["n07875152", "potpie"], "965": ["n07880968", "burrito"], "966": ["n07892512", "red_wine"], "967": ["n07920052", "espresso"], "968": ["n07930864", "cup"], "969": ["n07932039", "eggnog"], "970": ["n09193705", "alp"], "971": ["n09229709", "bubble"], "972": ["n09246464", "cliff"], "973": ["n09256479", "coral_reef"], "974": ["n09288635", "geyser"], "975": ["n09332890", "lakeside"], "976": ["n09399592", "promontory"], "977": ["n09421951", "sandbar"], "978": ["n09428293", "seashore"], "979": ["n09468604", "valley"], "980": ["n09472597", "volcano"], "981": ["n09835506", "ballplayer"], "982": ["n10148035", "groom"], "983": ["n10565667", "scuba_diver"], "984": ["n11879895", "rapeseed"], "985": ["n11939491", "daisy"], "986": ["n12057211", "yellow_lady's_slipper"], "987": ["n12144580", "corn"], "988": ["n12267677", "acorn"], "989": ["n12620546", "hip"], "990": ["n12768682", "buckeye"], "991": ["n12985857", "coral_fungus"], "992": ["n12998815", "agaric"], "993": ["n13037406", "gyromitra"], "994": ["n13040303", "stinkhorn"], "995": ["n13044778", "earthstar"], "996": ["n13052670", "hen-of-the-woods"], "997": ["n13054560", "bolete"], "998": ["n13133613", "ear"], "999": ["n15075141", "toilet_tissue"]} - results = [] - for pred in preds: - top_indices = pred.argsort()[-top:][::-1] - result = [tuple(CLASS_INDEX[str(i)]) + (pred[i],) for i in top_indices] - result.sort(key=lambda x: x[2], reverse=True) - results.append(result) - return results - - -def _obtain_input_shape(input_shape, - default_size, - min_size, - data_format, - require_flatten, - weights=None): - """Internal utility to compute/validate an ImageNet model's input shape. - - # Arguments - input_shape: either None (will return the default network input shape), - or a user-provided shape to be validated. - default_size: default input width/height for the model. - min_size: minimum input width/height accepted by the model. - data_format: image data format to use. - require_flatten: whether the model is expected to - be linked to a classifier via a Flatten layer. - weights: one of `None` (random initialization) - or 'imagenet' (pre-training on ImageNet). - If weights='imagenet' input channels must be equal to 3. - - # Returns - An integer shape tuple (may include None entries). - - # Raises - ValueError: in case of invalid argument values. - """ - if weights != 'imagenet' and input_shape and len(input_shape) == 3: - if data_format == 'channels_first': - if input_shape[0] not in {1, 3}: - warnings.warn( - 'This model usually expects 1 or 3 input channels. ' - 'However, it was passed an input_shape with ' + - str(input_shape[0]) + ' input channels.') - default_shape = (input_shape[0], default_size, default_size) - else: - if input_shape[-1] not in {1, 3}: - warnings.warn( - 'This model usually expects 1 or 3 input channels. ' - 'However, it was passed an input_shape with ' + - str(input_shape[-1]) + ' input channels.') - default_shape = (default_size, default_size, input_shape[-1]) - else: - if data_format == 'channels_first': - default_shape = (3, default_size, default_size) - else: - default_shape = (default_size, default_size, 3) - if weights == 'imagenet' and require_flatten: - if input_shape is not None: - if input_shape != default_shape: - raise ValueError('When setting`include_top=True` ' - 'and loading `imagenet` weights, ' - '`input_shape` should be ' + - str(default_shape) + '.') - return default_shape - if input_shape: - if data_format == 'channels_first': - if input_shape is not None: - if len(input_shape) != 3: - raise ValueError( - '`input_shape` must be a tuple of three integers.') - if input_shape[0] != 3 and weights == 'imagenet': - raise ValueError('The input must have 3 channels; got ' - '`input_shape=' + str(input_shape) + '`') - if ((input_shape[1] is not None and input_shape[1] < min_size) or - (input_shape[2] is not None and input_shape[2] < min_size)): - raise ValueError('Input size must be at least ' + - str(min_size) + 'x' + str(min_size) + '; got ' - '`input_shape=' + str(input_shape) + '`') - else: - if input_shape is not None: - if len(input_shape) != 3: - raise ValueError( - '`input_shape` must be a tuple of three integers.') - if input_shape[-1] != 3 and weights == 'imagenet': - raise ValueError('The input must have 3 channels; got ' - '`input_shape=' + str(input_shape) + '`') - if ((input_shape[0] is not None and input_shape[0] < min_size) or - (input_shape[1] is not None and input_shape[1] < min_size)): - raise ValueError('Input size must be at least ' + - str(min_size) + 'x' + str(min_size) + '; got ' - '`input_shape=' + str(input_shape) + '`') - else: - if require_flatten: - input_shape = default_shape - else: - if data_format == 'channels_first': - input_shape = (3, None, None) - else: - input_shape = (None, None, 3) - if require_flatten: - if None in input_shape: - raise ValueError('If `include_top` is True, ' - 'you should specify a static `input_shape`. ' - 'Got `input_shape=' + str(input_shape) + '`') - return input_shape diff --git a/imageai/Prediction/InceptionV3/inceptionv3.py b/imageai/Prediction/InceptionV3/inceptionv3.py deleted file mode 100644 index 40a7b84e..00000000 --- a/imageai/Prediction/InceptionV3/inceptionv3.py +++ /dev/null @@ -1,381 +0,0 @@ -# -*- coding: utf-8 -*- -"""Inception V3 model for Keras. - -Note that the input image format for this model is different than for -the VGG16 and ResNet models (299x299 instead of 224x224), -and that the input preprocessing function is also different (same as Xception). - -# Reference - -- [Rethinking the Inception Architecture for Computer Vision](http://arxiv.org/abs/1512.00567) - -""" - -from __future__ import absolute_import -from __future__ import print_function - -import warnings - -from tensorflow.python.keras import backend as K -from tensorflow.python.keras import layers -from tensorflow.python.keras.layers import Activation -from tensorflow.python.keras.layers import AveragePooling2D -from tensorflow.python.keras.layers import BatchNormalization -from tensorflow.python.keras.layers import Conv2D -from tensorflow.python.keras.layers import Dense -from tensorflow.python.keras.layers import GlobalAveragePooling2D -from tensorflow.python.keras.layers import GlobalMaxPooling2D -from tensorflow.python.keras.layers import Input -from tensorflow.python.keras.layers import MaxPooling2D -from tensorflow.python.keras.models import Model - -from ..InceptionV3.imagenet_utils import _obtain_input_shape, decode_predictions - - -def conv2d_bn(x, - filters, - num_row, - num_col, - padding='same', - strides=(1, 1), - name=None): - """Utility function to apply conv + BN. - - # Arguments - x: input tensor. - filters: filters in `Conv2D`. - num_row: height of the convolution kernel. - num_col: width of the convolution kernel. - padding: padding mode in `Conv2D`. - strides: strides in `Conv2D`. - name: name of the ops; will become `name + '_conv'` - for the convolution and `name + '_bn'` for the - batch norm layer. - - # Returns - Output tensor after applying `Conv2D` and `BatchNormalization`. - """ - if name is not None: - bn_name = name + '_bn' - conv_name = name + '_conv' - else: - bn_name = None - conv_name = None - if K.image_data_format() == 'channels_first': - bn_axis = 1 - else: - bn_axis = 3 - x = Conv2D( - filters, (num_row, num_col), - strides=strides, - padding=padding, - use_bias=False, - name=conv_name)(x) - x = BatchNormalization(axis=bn_axis, scale=False, name=bn_name)(x) - x = Activation('relu', name=name)(x) - return x - - -def InceptionV3(include_top=True, - weights='imagenet', - input_tensor=None, - model_input=None, - pooling=None, - classes=1000, model_path="", initial_classes=None, transfer_with_full_training = True): - """Instantiates the Inception v3 architecture. - - Optionally loads weights pre-trained - on ImageNet. Note that when using TensorFlow, - for best performance you should set - `image_data_format='channels_last'` in your Keras config - at ~/.keras/keras.json. - The model and the weights are compatible with both - TensorFlow and Theano. The data format - convention used by the model is the one - specified in your Keras config file. - Note that the default input image size for this model is 299x299. - - # Arguments - include_top: whether to include the fully-connected - layer at the top of the network. - weights: one of `None` (random initialization) - or 'imagenet' (pre-training on ImageNet). - input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) - to use as image input for the model. - input_shape: optional shape tuple, only to be specified - if `include_top` is False (otherwise the input shape - has to be `(299, 299, 3)` (with `channels_last` data format) - or `(3, 299, 299)` (with `channels_first` data format). - It should have exactly 3 inputs channels, - and width and height should be no smaller than 139. - E.g. `(150, 150, 3)` would be one valid value. - pooling: Optional pooling mode for feature extraction - when `include_top` is `False`. - - `None` means that the output of the model will be - the 4D tensor output of the - last convolutional layer. - - `avg` means that global average pooling - will be applied to the output of the - last convolutional layer, and thus - the output of the model will be a 2D tensor. - - `max` means that global max pooling will - be applied. - classes: optional number of classes to classify images - into, only to be specified if `include_top` is True, and - if no `weights` argument is specified. - - # Returns - A Keras model instance. - - # Raises - ValueError: in case of invalid argument for `weights`, - or invalid input shape. - """ - - - if weights == 'imagenet' and include_top and classes != 1000: - raise ValueError('If using `weights` as imagenet with `include_top`' - ' as true, `classes` should be 1000') - - - img_input = model_input - channel_axis = 3 - - - x = conv2d_bn(img_input, 32, 3, 3, strides=(2, 2), padding='valid') - x = conv2d_bn(x, 32, 3, 3, padding='valid') - x = conv2d_bn(x, 64, 3, 3) - x = MaxPooling2D((3, 3), strides=(2, 2))(x) - - x = conv2d_bn(x, 80, 1, 1, padding='valid') - x = conv2d_bn(x, 192, 3, 3, padding='valid') - x = MaxPooling2D((3, 3), strides=(2, 2))(x) - - # mixed 0, 1, 2: 35 x 35 x 256 - branch1x1 = conv2d_bn(x, 64, 1, 1) - - branch5x5 = conv2d_bn(x, 48, 1, 1) - branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) - - branch3x3dbl = conv2d_bn(x, 64, 1, 1) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - - branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 32, 1, 1) - x = layers.concatenate( - [branch1x1, branch5x5, branch3x3dbl, branch_pool], - axis=channel_axis, - name='mixed0') - - # mixed 1: 35 x 35 x 256 - branch1x1 = conv2d_bn(x, 64, 1, 1) - - branch5x5 = conv2d_bn(x, 48, 1, 1) - branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) - - branch3x3dbl = conv2d_bn(x, 64, 1, 1) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - - branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 64, 1, 1) - x = layers.concatenate( - [branch1x1, branch5x5, branch3x3dbl, branch_pool], - axis=channel_axis, - name='mixed1') - - # mixed 2: 35 x 35 x 256 - branch1x1 = conv2d_bn(x, 64, 1, 1) - - branch5x5 = conv2d_bn(x, 48, 1, 1) - branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) - - branch3x3dbl = conv2d_bn(x, 64, 1, 1) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - - branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 64, 1, 1) - x = layers.concatenate( - [branch1x1, branch5x5, branch3x3dbl, branch_pool], - axis=channel_axis, - name='mixed2') - - # mixed 3: 17 x 17 x 768 - branch3x3 = conv2d_bn(x, 384, 3, 3, strides=(2, 2), padding='valid') - - branch3x3dbl = conv2d_bn(x, 64, 1, 1) - branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) - branch3x3dbl = conv2d_bn( - branch3x3dbl, 96, 3, 3, strides=(2, 2), padding='valid') - - branch_pool = MaxPooling2D((3, 3), strides=(2, 2))(x) - x = layers.concatenate( - [branch3x3, branch3x3dbl, branch_pool], axis=channel_axis, name='mixed3') - - # mixed 4: 17 x 17 x 768 - branch1x1 = conv2d_bn(x, 192, 1, 1) - - branch7x7 = conv2d_bn(x, 128, 1, 1) - branch7x7 = conv2d_bn(branch7x7, 128, 1, 7) - branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) - - branch7x7dbl = conv2d_bn(x, 128, 1, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 1, 7) - branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) - - branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 192, 1, 1) - x = layers.concatenate( - [branch1x1, branch7x7, branch7x7dbl, branch_pool], - axis=channel_axis, - name='mixed4') - - # mixed 5, 6: 17 x 17 x 768 - for i in range(2): - branch1x1 = conv2d_bn(x, 192, 1, 1) - - branch7x7 = conv2d_bn(x, 160, 1, 1) - branch7x7 = conv2d_bn(branch7x7, 160, 1, 7) - branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) - - branch7x7dbl = conv2d_bn(x, 160, 1, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 1, 7) - branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) - - branch_pool = AveragePooling2D( - (3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 192, 1, 1) - x = layers.concatenate( - [branch1x1, branch7x7, branch7x7dbl, branch_pool], - axis=channel_axis, - name='mixed' + str(5 + i)) - - # mixed 7: 17 x 17 x 768 - branch1x1 = conv2d_bn(x, 192, 1, 1) - - branch7x7 = conv2d_bn(x, 192, 1, 1) - branch7x7 = conv2d_bn(branch7x7, 192, 1, 7) - branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) - - branch7x7dbl = conv2d_bn(x, 192, 1, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1) - branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) - - branch_pool = AveragePooling2D((3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 192, 1, 1) - x = layers.concatenate( - [branch1x1, branch7x7, branch7x7dbl, branch_pool], - axis=channel_axis, - name='mixed7') - - # mixed 8: 8 x 8 x 1280 - branch3x3 = conv2d_bn(x, 192, 1, 1) - branch3x3 = conv2d_bn(branch3x3, 320, 3, 3, - strides=(2, 2), padding='valid') - - branch7x7x3 = conv2d_bn(x, 192, 1, 1) - branch7x7x3 = conv2d_bn(branch7x7x3, 192, 1, 7) - branch7x7x3 = conv2d_bn(branch7x7x3, 192, 7, 1) - branch7x7x3 = conv2d_bn( - branch7x7x3, 192, 3, 3, strides=(2, 2), padding='valid') - - branch_pool = MaxPooling2D((3, 3), strides=(2, 2))(x) - x = layers.concatenate( - [branch3x3, branch7x7x3, branch_pool], axis=channel_axis, name='mixed8') - - # mixed 9: 8 x 8 x 2048 - for i in range(2): - branch1x1 = conv2d_bn(x, 320, 1, 1) - - branch3x3 = conv2d_bn(x, 384, 1, 1) - branch3x3_1 = conv2d_bn(branch3x3, 384, 1, 3) - branch3x3_2 = conv2d_bn(branch3x3, 384, 3, 1) - branch3x3 = layers.concatenate( - [branch3x3_1, branch3x3_2], axis=channel_axis, name='mixed9_' + str(i)) - - branch3x3dbl = conv2d_bn(x, 448, 1, 1) - branch3x3dbl = conv2d_bn(branch3x3dbl, 384, 3, 3) - branch3x3dbl_1 = conv2d_bn(branch3x3dbl, 384, 1, 3) - branch3x3dbl_2 = conv2d_bn(branch3x3dbl, 384, 3, 1) - branch3x3dbl = layers.concatenate( - [branch3x3dbl_1, branch3x3dbl_2], axis=channel_axis) - - branch_pool = AveragePooling2D( - (3, 3), strides=(1, 1), padding='same')(x) - branch_pool = conv2d_bn(branch_pool, 192, 1, 1) - x = layers.concatenate( - [branch1x1, branch3x3, branch3x3dbl, branch_pool], - axis=channel_axis, - name='mixed' + str(9 + i)) - if include_top: - # Classification block - x = GlobalAveragePooling2D(name='avg_pool')(x) - if(initial_classes != None): - x = Dense(initial_classes, activation='softmax', name='predictions')(x) - else: - x = Dense(classes, activation='softmax', name='predictions')(x) - else: - if pooling == 'avg': - x = GlobalAveragePooling2D()(x) - elif pooling == 'max': - x = GlobalMaxPooling2D()(x) - - # Ensure that the model takes into account - # any potential predecessors of `input_tensor`. - inputs = img_input - # Create model. - model = Model(inputs, x, name='inception_v3') - - - - # load weights - if weights == 'imagenet': - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "trained"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "continued"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "transfer"): - weights_path = model_path - model.load_weights(weights_path) - - print(model.layers[-2]) - - if (transfer_with_full_training == False): - for eachlayer in model.layers: - eachlayer.trainable = False - print("Training with top layers of the Model") - else: - print("Training with all layers of the Model") - - x2 = model.layers[-2].output - x2 = Dense(classes, activation='softmax', name='predictions')(x2) - - new_model = Model(inputs=model.input, outputs=x2) - - return new_model - elif (weights == "custom"): - return model - - - - -def preprocess_input(x): - x /= 255. - x -= 0.5 - x *= 2. - return x diff --git a/imageai/Prediction/ResNet/__init__.py b/imageai/Prediction/ResNet/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/imageai/Prediction/ResNet/__pycache__/__init__.cpython-35.pyc b/imageai/Prediction/ResNet/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index ab104c64116915a2bc6a7076ff668eb456627bc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177 zcmWgV<>k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuQ+F`n9$oGLuU( p^YdbYQj7gkOJd^VGxIV_;^XxSDsOSv@5aj1^{tyF6jUO diff --git a/imageai/Prediction/ResNet/__pycache__/resnet50.cpython-35.pyc b/imageai/Prediction/ResNet/__pycache__/resnet50.cpython-35.pyc deleted file mode 100644 index 371c289ea9fd06cf11b8da667c1e2e8325805f88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3727 zcmbVP-ESMm5uZDf$8S+0C0nu*n{n(k?1SK>2+#&a5Z8iJBefdDE&?2w5@+6tI_o~< zy`!WUOCR!7pml#UOHq=)kmR5n?#|5Z&d&UHW^}pP ztbP0W|N4LJ5dEFbJvEF!#hd*F#K)gS5m9WBYmvosnBz1!qh$gk2fBDX^Wf_CHLVlZ`!H_lPJm__1I1}|O zUD^+nmto*U-Zz2DdUaje4w5X;wcEoyJoNG~O?3HggvM!J}JaWE^gW>E#vc*ddW zl~fr&s=@3+nB&V*;k|VFboesxwkmOna_PzeS*c%1-{mIs?#W-Z1 z7xuqUfgb|HY102P$aaFfj~$aB|LE4%!8f{TCWAQj$5Fs%$*zNt@BNh zwda)QZOqg=&>FJi_+hs!;Q7-zJZ*s|;AS8x(*?GG1creLkbR1(#dLuo$3XJ*V}O>C zWIlcahRUyjiQki2N6K(e}{mf=j*W`bNyYL&?vlS@o4 zGg${Yk3F@)+%xPI)fHxJGI^WHRVIumL%JC0YxCIehAPVkkD_$AKQLp11_naL9~v~g z77463WCZZz%wI`VJD41QCIFDgZh!qV&QYATN|Ksv+DGpCfa3s)fPjw#!?za>hR+aa z+d@)#(xk7%Kc$_!itp@0^%iK))+Oa7qd?%;bIy*fYsoksm~5`w=dYYOqqag_+ zR~mD#h(A8Y9vNS5vO2g7ZM++{dLQ$LJK;Z(0dz&~8Say&A>ROgI9OG@;)8E-M>uH# zEdZNEqasa#z0@$)xGy-^2sQ6L zu897R=!ERaLIj`IX@Y#bLK`R8vQEc)l%sym1LV@Q!SYFiHl|IQRLls4v`y0%Pw*uN z?H}ZKgagqktxuOIcX(}ojV36*muLdPWty;Q1#I)=PZB}3Z{=n3*U8_2Vb;R12}(QB zK5(#Y<=B)f7B9y&rA|qkm|3{MMA7oET;L?X6x?izfxqXCPJpDvB0m=CTO$2~l~*rH zacqPMHsEl7wsQ0nZ_@^}_;ZELurPVR1-C0IOZFR7 z-Vv1#-lTF}RQ~o#6?m{C;@{cFSHy@0ZGYXODf*%gP1-bQ(vw~Ls~j?zR#)Fq!@L`Sa(!m9tjEWO`0Fij%K;mkDPjRc8X{lH$HywLo+u zN})=?nP z&zO#-&WUyyc^Uc>U7l~FYewb3L&wvBr}7Xj&UwwvQ(q3)gM;016b-~GI8}^vmoD^8 zncI;42I-^6!7x{!@cG_j@&X1*va@c?Eg6Uj)xaC>Nrc>am_O9jBsk*6CwQp_RKAo> z>7!ec!4qsk$o*~GDWC}Achl+=I#$i9p+#z-Ce*C!1;<(|G^{l=M{9Ni+VU2d=U7dv zD0(mbLUS3?rd=y6%MR-{YoeP+ZM&pb@Xtn;s@*6(LbI0drHR7rA$|EJ{+9d$V}g72 zHPS7EPy-L?75G*fR0)>2W*OWFjJWPeE31}x)7LI5F3bdLQicsC1&(}k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuOw%yn9$oGLuU( t^YdbY3rkZ|t5W?^OJd^VGxIV_;^XxSDsOSv@Egk1^`80Fr@$h diff --git a/imageai/Prediction/SqueezeNet/__pycache__/squeezenet.cpython-35.pyc b/imageai/Prediction/SqueezeNet/__pycache__/squeezenet.cpython-35.pyc deleted file mode 100644 index 69681369dadcd6803fb8aa3b2f65333c0dc9fa73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3092 zcmb_eOK%)S5U!co_rvQriQ~i&CLs_u3C3}52tlkkfTbivasaVfG@9(R?TP2H>6x`1 zYga-zXZ}Zy`~&_15*PY}#DzU@MgocORgb-1J9bXY?$mTweO=vMRbAER>h;Rxy?@)u zDWczL?5UuA8*lOhh=;#H0a0jB%b?JtmPw&SEsMe&wQ>~Zsg)(&Evg;H+ca90}U)niTZ}#iAC7q zu4(j%b|E54!OwI1fCI3p_k0AL8v{cLhJ%xVVI6=AW1910V6A@uj!FF-_4Bj_{YC0q zP-0*Wn0lpIeK6!*3=I%f?7E)kM{6p-=?1>1a#936l}p@EsGPg)CrwjM0I124cAfc5 znwDgZk_{r+GVB{)`{uO%IGuGjibUWfp&JD12s3cynozZU4A0t{$i_XEZq!+KZXW3` zydXJ)dH(k9eAxan5i)7tj}wvDUj*)|h_>AJQrzi<`&!}L$qJ{m!qdkoe2oYj!7|PqH|nqm2Si>s zQzWLdhYZ+9o+9m$pA8&j?oN~U!G6w~A@4l!*2t<690wSb12%sm>=;ksXd@U#j)N;N z99l{AIUi?lR)@;6nI6VR*R=EvEnPFy;vsDj9F4Kq2*NDkfEiRMMTUKo`baDn^}J*b zs$;zIaPXk!}>Z!ZFpO+Ey9-2xw5~|C)n9dI{&0jgNZSxEWF~B2_C_i z(eW8^G9!<=Bd3fwnGIp032jF3Va5ja8=0Nn)ms=^Y&V#HrFrXtoW<$aSz+hyH*wDn zdr4|{U6}~mkL-g~WFyyzz)q#>N5Zq?$WGUVeRF<(-tGi$lGr-AA!o@N6zX)Wib*Pc zPb5Q=Sq&zfZzPI5sd5)3(n3jH;Q8$nRoxI$ z=Wo8*>vx+KfbGnZMq4HhnN4Oqkme$^GPO7ROb%LHJ9GFrS_lH+? zm^s=xtwt0_EOt0l_;OH##u-TM%~DP zbIcdgNl5Ev#hlz5KV{CCjFwB7yIEHkaR*Iexf;Y!d!dxC)Te zjvw8oj(GoMJ=zk9j*cvBc;Ye3@Rdnw^OQaU7FU>$`4X6LIL#T;#-ufMs%HELhy}p; diff --git a/imageai/Prediction/SqueezeNet/squeezenet.py b/imageai/Prediction/SqueezeNet/squeezenet.py deleted file mode 100644 index 53dd41a3..00000000 --- a/imageai/Prediction/SqueezeNet/squeezenet.py +++ /dev/null @@ -1,110 +0,0 @@ -from tensorflow.python.keras.layers import Input, Conv2D, MaxPool2D, Activation, concatenate, Dropout -from tensorflow.python.keras.layers import GlobalAvgPool2D, GlobalMaxPool2D -from tensorflow.python.keras.models import Model - - -def squeezenet_fire_module(input, input_channel_small=16, input_channel_large=64): - - channel_axis = 3 - - input = Conv2D(input_channel_small, (1,1), padding="valid" )(input) - input = Activation("relu")(input) - - input_branch_1 = Conv2D(input_channel_large, (1,1), padding="valid" )(input) - input_branch_1 = Activation("relu")(input_branch_1) - - input_branch_2 = Conv2D(input_channel_large, (3, 3), padding="same")(input) - input_branch_2 = Activation("relu")(input_branch_2) - - input = concatenate([input_branch_1, input_branch_2], axis=channel_axis) - - return input - -def SqueezeNet(include_top = True, weights="imagenet", model_input=None, non_top_pooling=None, - num_classes=1000, model_path="", initial_num_classes=None, transfer_with_full_training=True): - - if(weights == "imagenet" and num_classes != 1000): - raise ValueError("You must parse in SqueezeNet model trained on the 1000 class ImageNet") - - - - - image_input = model_input - - - network = Conv2D(64, (3,3), strides=(2,2), padding="valid")(image_input) - network = Activation("relu")(network) - network = MaxPool2D( pool_size=(3,3) , strides=(2,2))(network) - - network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64) - network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64) - network = MaxPool2D(pool_size=(3,3), strides=(2,2))(network) - - network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128) - network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128) - network = MaxPool2D(pool_size=(3, 3), strides=(2, 2))(network) - - network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192) - network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192) - network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256) - network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256) - - if(include_top): - network = Dropout(0.5)(network) - - if(initial_num_classes != None): - network = Conv2D(initial_num_classes, kernel_size=(1, 1), padding="valid", name="last_conv")(network) - else: - network = Conv2D(num_classes, kernel_size=(1, 1), padding="valid", name="last_conv")(network) - network = Activation("relu")(network) - - network = GlobalAvgPool2D()(network) - network = Activation("softmax")(network) - - else: - if(non_top_pooling == "Average"): - network = GlobalAvgPool2D()(network) - elif(non_top_pooling == "Maximum"): - network = GlobalMaxPool2D()(network) - elif(non_top_pooling == None): - pass - - input_image = image_input - model = Model(inputs=input_image, outputs=network) - - if(weights =="imagenet"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif(weights =="trained"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "continued"): - weights_path = model_path - model.load_weights(weights_path) - return model - elif (weights == "transfer"): - weights_path = model_path - model.load_weights(weights_path) - - if (transfer_with_full_training == False): - for eachlayer in model.layers: - eachlayer.trainable = False - print("Training with top layers of the Model") - else: - print("Training with all layers of the Model") - - network2 = model.layers[-5].output - - network2 = Conv2D(num_classes, kernel_size=(1, 1), padding="valid", name="last_conv")(network2) - network2 = Activation("relu")(network2) - - network2 = GlobalAvgPool2D()(network2) - network2 = Activation("softmax")(network2) - - new_model = Model(inputs=model.input, outputs=network2) - - return new_model - elif (weights == "custom"): - return model \ No newline at end of file diff --git a/test/test_custom_object_detection.py b/test/test_custom_object_detection.py index 20625ddd..462ab37d 100644 --- a/test/test_custom_object_detection.py +++ b/test/test_custom_object_detection.py @@ -15,19 +15,7 @@ model_json = os.path.join(main_folder, "data-json", "detection_config.json") -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None - - - -@pytest.mark.detection -@pytest.mark.yolov3 -@pytest.mark.custom_detection -def test_object_detection_yolov3(clear_keras_session): +def test_object_detection_yolov3(): detector = CustomObjectDetection() detector.setModelTypeAsYOLOv3() @@ -64,12 +52,7 @@ def test_object_detection_yolov3(clear_keras_session): shutil.rmtree(objects_output) - -@pytest.mark.detection -@pytest.mark.yolov3 -@pytest.mark.custom_detection -@pytest.mark.array_io -def test_object_detection_yolov3_array_io(clear_keras_session): +def test_object_detection_yolov3_array_io(): image_input_array = cv2.imread(image_input) diff --git a/test/test_custom_recognition.py b/test/test_custom_recognition.py index 0885e35f..0468cb29 100644 --- a/test/test_custom_recognition.py +++ b/test/test_custom_recognition.py @@ -1,8 +1,7 @@ -from imageai.Prediction.Custom import CustomImagePrediction -import os +from imageai.Prediction.Custom import CustomImageClassification import pytest from os.path import dirname -import keras +import os main_folder = os.getcwd() @@ -17,14 +16,12 @@ def images_to_image_array(): -@pytest.mark.recognition -@pytest.mark.resnet -@pytest.mark.recognition_custom + def test_custom_recognition_model_resnet(): - predictor = CustomImagePrediction() - predictor.setModelTypeAsResNet() + predictor = CustomImageClassification() + predictor.setModelTypeAsResNet50() predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_resnet.h5")) predictor.setJsonPath(model_json=os.path.join(main_folder, "data-json", "idenprof.json")) predictor.loadModel(num_objects=10) @@ -35,29 +32,11 @@ def test_custom_recognition_model_resnet(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.recognition -@pytest.mark.resnet -@pytest.mark.recognition_custom -def test_custom_recognition_full_model_resnet(): - - predictor = CustomImagePrediction() - predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_full_resnet_ex-001_acc-0.119792.h5")) - predictor.setJsonPath(model_json=os.path.join(main_folder, "data-json", "idenprof.json")) - predictor.loadFullModel(num_objects=10) - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "9.jpg")) - - assert isinstance(predictions, list) - assert isinstance(probabilities, list) - assert isinstance(predictions[0], str) - assert isinstance(probabilities[0], float) -@pytest.mark.recognition -@pytest.mark.densenet -@pytest.mark.recognition_custom def test_custom_recognition_model_densenet(): - predictor = CustomImagePrediction() - predictor.setModelTypeAsDenseNet() + predictor = CustomImageClassification() + predictor.setModelTypeAsDenseNet121() predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_densenet-0.763500.h5")) predictor.setJsonPath(model_json=os.path.join(main_folder, "data-json", "idenprof.json")) predictor.loadModel(num_objects=10) diff --git a/test/test_custom_video_detection.py b/test/test_custom_video_detection.py index bffd9897..55a343c1 100644 --- a/test/test_custom_video_detection.py +++ b/test/test_custom_video_detection.py @@ -2,7 +2,6 @@ import os from numpy import ndarray import pytest -import keras @@ -14,20 +13,7 @@ model_json = os.path.join(main_folder, "data-json", "detection_config.json") -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None - - -@pytest.mark.detection -@pytest.mark.custom_detection -@pytest.mark.video_detection -@pytest.mark.custom_video_detection -@pytest.mark.yolov3 -def test_custom_video_detection_yolov3(clear_keras_session): +def test_custom_video_detection_yolov3(): detector = CustomVideoObjectDetection() @@ -49,7 +35,7 @@ def test_custom_video_detection_yolov3(clear_keras_session): @pytest.mark.custom_video_detection @pytest.mark.yolov3 @pytest.mark.custom_video_detection_analysis -def test_custom_video_detection_yolov3_analysis(clear_keras_session): +def test_custom_video_detection_yolov3_analysis(): detector = CustomVideoObjectDetection() diff --git a/test/test_detection_model_training.py b/test/test_detection_model_training.py index 77720281..e8a084c8 100644 --- a/test/test_detection_model_training.py +++ b/test/test_detection_model_training.py @@ -1,10 +1,8 @@ from imageai.Detection.Custom import DetectionModelTrainer import os import shutil -import keras import pytest -""" main_folder = os.getcwd() sample_dataset = os.path.join(main_folder, "data-datasets", "hololens") @@ -14,20 +12,7 @@ pretrained_model = os.path.join(main_folder, "data-models", "pretrained-yolov3.h5") -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None - - -@pytest.mark.detection -@pytest.mark.training -@pytest.mark.detection_training -@pytest.mark.detection_transfer_learning -@pytest.mark.yolov3 -def test_detection_training(clear_keras_session): +def test_detection_training(): trainer = DetectionModelTrainer() @@ -43,7 +28,3 @@ def test_detection_training(clear_keras_session): shutil.rmtree(os.path.join(sample_dataset_json_folder)) shutil.rmtree(os.path.join(sample_dataset_models_folder)) shutil.rmtree(os.path.join(sample_dataset_cache_folder)) - - - -""" \ No newline at end of file diff --git a/test/test_image_recognition.py b/test/test_image_recognition.py index b487e257..755fe2b0 100644 --- a/test/test_image_recognition.py +++ b/test/test_image_recognition.py @@ -1,4 +1,4 @@ -from imageai.Prediction import ImagePrediction +from imageai.Prediction import ImageClassification import os import cv2 import pytest @@ -8,16 +8,12 @@ main_folder = os.getcwd() +def test_recognition_model_mobilenetv2(): -@pytest.mark.squeezenet -@pytest.mark.recognition -def test_recognition_model_squeezenet(): - - - predictor = ImagePrediction() - predictor.setModelTypeAsSqueezeNet() - predictor.setModelPath(os.path.join(main_folder, "data-models", "squeezenet_weights_tf_dim_ordering_tf_kernels.h5")) + predictor = ImageClassification() + predictor.setModelTypeAsMobileNetV2() + predictor.setModelPath(os.path.join(main_folder, "data-models", "mobilenet_v2.h5")) predictor.loadModel() predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) @@ -26,13 +22,12 @@ def test_recognition_model_squeezenet(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.resnet -@pytest.mark.recognition + def test_recognition_model_resnet(): - predictor = ImagePrediction() - predictor.setModelTypeAsResNet() - predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) + predictor = ImageClassification() + predictor.setModelTypeAsResNet50() + predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_imagenet_tf.2.0.h5")) predictor.loadModel() predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) @@ -41,11 +36,10 @@ def test_recognition_model_resnet(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.inceptionv3 -@pytest.mark.recognition + def test_recognition_model_inceptionv3(): - predictor = ImagePrediction() + predictor = ImageClassification() predictor.setModelTypeAsInceptionV3() predictor.setModelPath(os.path.join(main_folder, "data-models", "inception_v3_weights_tf_dim_ordering_tf_kernels.h5")) predictor.loadModel() @@ -56,12 +50,11 @@ def test_recognition_model_inceptionv3(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.densenet -@pytest.mark.recognition + def test_recognition_model_densenet(): - predictor = ImagePrediction() - predictor.setModelTypeAsDenseNet() + predictor = ImageClassification() + predictor.setModelTypeAsDenseNet121() predictor.setModelPath(os.path.join(main_folder, "data-models", "DenseNet-BC-121-32.h5")) predictor.loadModel() predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) @@ -72,30 +65,12 @@ def test_recognition_model_densenet(): assert isinstance(probabilities[0], float) -@pytest.mark.squeezenet -@pytest.mark.recognition -def test_recognition_model_squeezenet_array_input(): - - predictor = ImagePrediction() - predictor.setModelTypeAsSqueezeNet() - predictor.setModelPath(os.path.join(main_folder, "data-models", "squeezenet_weights_tf_dim_ordering_tf_kernels.h5")) - predictor.loadModel() - image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) - predictions, probabilities = predictor.predictImage(image_input=image_array, input_type="array") - - assert isinstance(predictions, list) - assert isinstance(probabilities, list) - assert isinstance(predictions[0], str) - assert isinstance(probabilities[0], float) - -@pytest.mark.resnet -@pytest.mark.recognition def test_recognition_model_resnet_array_input(): - predictor = ImagePrediction() - predictor.setModelTypeAsResNet() - predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) + predictor = ImageClassification() + predictor.setModelTypeAsResNet50() + predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_imagenet_tf.2.0.h5")) predictor.loadModel() image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) predictions, probabilities = predictor.predictImage(image_input=image_array, input_type="array") @@ -105,11 +80,10 @@ def test_recognition_model_resnet_array_input(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.inceptionv3 -@pytest.mark.recognition + def test_recognition_model_inceptionv3_array_input(): - predictor = ImagePrediction() + predictor = ImageClassification() predictor.setModelTypeAsInceptionV3() predictor.setModelPath(os.path.join(main_folder, "data-models", "inception_v3_weights_tf_dim_ordering_tf_kernels.h5")) predictor.loadModel() @@ -121,12 +95,11 @@ def test_recognition_model_inceptionv3_array_input(): assert isinstance(predictions[0], str) assert isinstance(probabilities[0], float) -@pytest.mark.densenet -@pytest.mark.recognition + def test_recognition_model_densenet_array_input(): - predictor = ImagePrediction() - predictor.setModelTypeAsDenseNet() + predictor = ImageClassification() + predictor.setModelTypeAsDenseNet121() predictor.setModelPath(os.path.join(main_folder, "data-models", "DenseNet-BC-121-32.h5")) predictor.loadModel() image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) diff --git a/test/test_model_training.py b/test/test_model_training.py index b47504b2..437d2577 100644 --- a/test/test_model_training.py +++ b/test/test_model_training.py @@ -10,22 +10,12 @@ sample_dataset_models_folder = os.path.join(sample_dataset, "models") -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None - - -@pytest.mark.training -@pytest.mark.training_resnet -@pytest.mark.resnet -@pytest.mark.recognition + + def test_resnet_training(): trainer = ModelTraining() - trainer.setModelTypeAsResNet() + trainer.setModelTypeAsResNet50() trainer.setDataDirectory(data_directory=sample_dataset) trainer.trainModel(num_objects=10, num_experiments=1, enhance_data=True, batch_size=16, show_network_summary=True) @@ -37,29 +27,9 @@ def test_resnet_training(): shutil.rmtree(os.path.join(sample_dataset_models_folder)) -@pytest.mark.training -@pytest.mark.training_squeezenet -@pytest.mark.squeezenet -@pytest.mark.recognition -def test_squeezenet_training(): - - trainer = ModelTraining() - trainer.setModelTypeAsSqueezeNet() - trainer.setDataDirectory(data_directory=sample_dataset) - trainer.trainModel(num_objects=10, num_experiments=1, enhance_data=True, batch_size=16, show_network_summary=True) - assert os.path.isdir(sample_dataset_json_folder) - assert os.path.isdir(sample_dataset_models_folder) - assert os.path.isfile(os.path.join(sample_dataset_json_folder, "model_class.json")) - assert (len(os.listdir(sample_dataset_models_folder)) > 0) - shutil.rmtree(os.path.join(sample_dataset_json_folder)) - shutil.rmtree(os.path.join(sample_dataset_models_folder)) -@pytest.mark.training -@pytest.mark.training_inception_v3 -@pytest.mark.inception_v3 -@pytest.mark.recognition def test_inception_v3_training(): trainer = ModelTraining() diff --git a/test/test_object_detection.py b/test/test_object_detection.py index c24f4f64..043ba081 100644 --- a/test/test_object_detection.py +++ b/test/test_object_detection.py @@ -13,17 +13,9 @@ image_output = os.path.join(main_folder, "data-temp", "11-detected.jpg") -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None -@pytest.mark.detection -@pytest.mark.retinanet -def test_object_detection_retinanet(clear_keras_session): +def test_object_detection_retinanet(): detector = ObjectDetection() detector.setModelTypeAsRetinaNet() @@ -58,9 +50,8 @@ def test_object_detection_retinanet(clear_keras_session): -@pytest.mark.detection -@pytest.mark.yolov3 -def test_object_detection_yolov3(clear_keras_session): + +def test_object_detection_yolov3(): detector = ObjectDetection() detector.setModelTypeAsYOLOv3() @@ -95,9 +86,8 @@ def test_object_detection_yolov3(clear_keras_session): -@pytest.mark.detection -@pytest.mark.tiny_yolov3 -def test_object_detection_tiny_yolov3(clear_keras_session): + +def test_object_detection_tiny_yolov3(): detector = ObjectDetection() detector.setModelTypeAsTinyYOLOv3() @@ -135,10 +125,8 @@ def test_object_detection_tiny_yolov3(clear_keras_session): -@pytest.mark.detection -@pytest.mark.retinanet -@pytest.mark.array_io -def test_object_detection_retinanet_array_io(clear_keras_session): + +def test_object_detection_retinanet_array_io(): image_input_array = cv2.imread(image_input) @@ -171,10 +159,8 @@ def test_object_detection_retinanet_array_io(clear_keras_session): -@pytest.mark.detection -@pytest.mark.yolov3 -@pytest.mark.array_io -def test_object_detection_yolov3_array_io(clear_keras_session): + +def test_object_detection_yolov3_array_io(): image_input_array = cv2.imread(image_input) @@ -205,10 +191,8 @@ def test_object_detection_yolov3_array_io(clear_keras_session): assert isinstance(extracted_array, ndarray) -@pytest.mark.detection -@pytest.mark.tiny_yolov3 -@pytest.mark.array_io -def test_object_detection_tiny_yolov3_array_io(clear_keras_session): + +def test_object_detection_tiny_yolov3_array_io(): image_input_array = cv2.imread(image_input) diff --git a/test/test_video_object_detection.py b/test/test_video_object_detection.py index a33244da..56cf2221 100644 --- a/test/test_video_object_detection.py +++ b/test/test_video_object_detection.py @@ -12,18 +12,8 @@ -@pytest.fixture -def clear_keras_session(): - try: - keras.backend.clear_session() - except: - None - -@pytest.mark.detection -@pytest.mark.video_detection -@pytest.mark.retinanet -def test_video_detection_retinanet(clear_keras_session): +def test_video_detection_retinanet(): detector = VideoObjectDetection() @@ -38,10 +28,8 @@ def test_video_detection_retinanet(clear_keras_session): -@pytest.mark.detection -@pytest.mark.video_detection -@pytest.mark.yolov3 -def test_video_detection_yolov3(clear_keras_session): + +def test_video_detection_yolov3(): detector = VideoObjectDetection() @@ -55,10 +43,8 @@ def test_video_detection_yolov3(clear_keras_session): os.remove(video_file_output + ".avi") -@pytest.mark.detection -@pytest.mark.video_detection -@pytest.mark.tiny_yolov3 -def test_video_detection_tiny_yolov3(clear_keras_session): + +def test_video_detection_tiny_yolov3(): detector = VideoObjectDetection() detector.setModelTypeAsTinyYOLOv3() @@ -73,11 +59,8 @@ def test_video_detection_tiny_yolov3(clear_keras_session): -@pytest.mark.detection -@pytest.mark.video_detection -@pytest.mark.retinanet -@pytest.mark.video_analysis -def test_video_detection_retinanet_analysis(clear_keras_session): + +def test_video_detection_retinanet_analysis(): detector = VideoObjectDetection() detector.setModelTypeAsRetinaNet() From 7cc90a7239f93a1c2a113d2d930decce6fff37bc Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Mon, 4 Jan 2021 23:17:17 +0100 Subject: [PATCH 09/12] migrated KerasRetinanet detection to tensorflow 2.x --- .travis.yml | 8 +- imageai/Classification/Custom/__init__.py | 4 +- imageai/Detection/__init__.py | 80 +-- imageai/Detection/keras_resnet/__init__.py | 5 - .../__pycache__/__init__.cpython-35.pyc | Bin 275 -> 0 bytes .../keras_resnet/benchmarks/__init__.py | 134 ----- imageai/Detection/keras_resnet/blocks/_1d.py | 149 ----- imageai/Detection/keras_resnet/blocks/_2d.py | 152 ----- imageai/Detection/keras_resnet/blocks/_3d.py | 152 ----- .../Detection/keras_resnet/blocks/__init__.py | 28 - .../blocks/__pycache__/_1d.cpython-35.pyc | Bin 5486 -> 0 bytes .../blocks/__pycache__/_2d.cpython-35.pyc | Bin 5493 -> 0 bytes .../blocks/__pycache__/_3d.cpython-35.pyc | Bin 5499 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 625 -> 0 bytes .../_time_distributed_2d.cpython-35.pyc | Bin 5987 -> 0 bytes .../blocks/_time_distributed_2d.py | 156 ----- .../Detection/keras_resnet/classifiers/_2d.py | 184 ------ .../keras_resnet/classifiers/__init__.py | 17 - .../Detection/keras_resnet/layers/__init__.py | 1 - .../__pycache__/__init__.cpython-35.pyc | Bin 256 -> 0 bytes .../_batch_normalization.cpython-35.pyc | Bin 1194 -> 0 bytes .../layers/_batch_normalization.py | 22 - imageai/Detection/keras_resnet/models/_2d.py | 289 --------- .../Detection/keras_resnet/models/__init__.py | 28 - .../models/__pycache__/_2d.cpython-35.pyc | Bin 9632 -> 0 bytes .../__pycache__/__init__.cpython-35.pyc | Bin 754 -> 0 bytes .../_time_distributed_2d.cpython-35.pyc | Bin 11293 -> 0 bytes .../models/_time_distributed_2d.py | 318 ---------- imageai/Detection/keras_retinanet/LICENSE.txt | 201 ------- .../__pycache__/__init__.cpython-35.pyc | Bin 185 -> 0 bytes .../__pycache__/initializers.cpython-35.pyc | Bin 1663 -> 0 bytes .../__pycache__/losses.cpython-35.pyc | Bin 2862 -> 0 bytes .../keras_retinanet/backend/__init__.py | 5 +- .../__pycache__/__init__.cpython-35.pyc | Bin 262 -> 0 bytes .../backend/__pycache__/common.cpython-35.pyc | Bin 2768 -> 0 bytes .../__pycache__/dynamic.cpython-35.pyc | Bin 490 -> 0 bytes .../tensorflow_backend.cpython-35.pyc | Bin 1782 -> 0 bytes .../keras_retinanet/backend/backend.py | 119 ++++ .../keras_retinanet/backend/common.py | 80 --- .../keras_retinanet/backend/dynamic.py | 19 - .../backend/tensorflow_backend.py | 46 -- .../Detection/keras_retinanet/bin/__init__.py | 0 .../keras_retinanet/bin/convert_model.py | 100 ++++ .../Detection/keras_retinanet/bin/debug.py | 321 ++++++++++ .../Detection/keras_retinanet/bin/evaluate.py | 195 ++++++ .../Detection/keras_retinanet/bin/train.py | 553 ++++++++++++++++++ .../keras_retinanet/callbacks/__init__.py | 2 +- .../keras_retinanet/callbacks/coco.py | 44 +- .../keras_retinanet/callbacks/common.py | 23 +- .../keras_retinanet/callbacks/eval.py | 71 ++- .../Detection/keras_retinanet/initializers.py | 10 +- .../keras_retinanet/layers/__init__.py | 3 +- .../__pycache__/__init__.cpython-35.pyc | Bin 317 -> 0 bytes .../layers/__pycache__/_misc.cpython-35.pyc | Bin 7163 -> 0 bytes .../Detection/keras_retinanet/layers/_misc.py | 153 ++--- .../layers/filter_detections.py | 228 ++++++++ imageai/Detection/keras_retinanet/losses.py | 110 ++-- .../keras_retinanet/models/__init__.py | 125 ++++ .../__pycache__/__init__.cpython-35.pyc | Bin 192 -> 0 bytes .../models/__pycache__/resnet.cpython-35.pyc | Bin 3177 -> 0 bytes .../__pycache__/retinanet.cpython-35.pyc | Bin 12631 -> 0 bytes .../keras_retinanet/models/densenet.py | 111 ++++ .../keras_retinanet/models/effnet.py | 159 +++++ .../keras_retinanet/models/mobilenet.py | 133 +++-- .../keras_retinanet/models/resnet.py | 128 ++-- .../keras_retinanet/models/retinanet.py | 269 +++++---- .../Detection/keras_retinanet/models/senet.py | 161 +++++ .../Detection/keras_retinanet/models/vgg.py | 106 ++++ .../keras_retinanet/preprocessing/coco.py | 71 ++- .../preprocessing/csv_generator.py | 82 ++- .../preprocessing/generator.py | 316 +++++++--- .../keras_retinanet/preprocessing/kitti.py | 74 ++- .../preprocessing/open_images.py | 267 +++++++-- .../preprocessing/pascal_voc.py | 79 ++- .../utils/__pycache__/__init__.cpython-35.pyc | Bin 191 -> 0 bytes .../utils/__pycache__/anchors.cpython-35.pyc | Bin 6713 -> 0 bytes .../utils/__pycache__/colors.cpython-35.pyc | Bin 2152 -> 0 bytes .../utils/__pycache__/image.cpython-35.pyc | Bin 6512 -> 0 bytes .../__pycache__/transform.cpython-35.pyc | Bin 10450 -> 0 bytes .../__pycache__/visualization.cpython-35.pyc | Bin 4051 -> 0 bytes .../keras_retinanet/utils/anchors.py | 285 ++++++--- .../keras_retinanet/utils/coco_eval.py | 52 +- .../Detection/keras_retinanet/utils/colors.py | 1 + .../keras_retinanet/utils/compute_overlap.pyx | 53 ++ .../Detection/keras_retinanet/utils/config.py | 57 ++ .../Detection/keras_retinanet/utils/eval.py | 82 +-- .../Detection/keras_retinanet/utils/gpu.py | 43 ++ .../Detection/keras_retinanet/utils/image.py | 274 +++++++-- .../keras_retinanet/utils/keras_version.py | 44 -- .../keras_retinanet/utils/tf_version.py | 55 ++ .../keras_retinanet/utils/transform.py | 74 ++- .../keras_retinanet/utils/visualization.py | 60 +- requirements.txt | 15 +- setup.py | 2 +- test/test_custom_recognition.py | 6 +- test/test_custom_video_detection.py | 7 - test/test_image_recognition.py | 14 +- test/test_model_training.py | 6 +- test/test_object_detection.py | 4 +- test/test_video_object_detection.py | 4 +- 100 files changed, 4254 insertions(+), 2975 deletions(-) delete mode 100644 imageai/Detection/keras_resnet/__init__.py delete mode 100644 imageai/Detection/keras_resnet/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/benchmarks/__init__.py delete mode 100644 imageai/Detection/keras_resnet/blocks/_1d.py delete mode 100644 imageai/Detection/keras_resnet/blocks/_2d.py delete mode 100644 imageai/Detection/keras_resnet/blocks/_3d.py delete mode 100644 imageai/Detection/keras_resnet/blocks/__init__.py delete mode 100644 imageai/Detection/keras_resnet/blocks/__pycache__/_1d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/blocks/__pycache__/_2d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/blocks/__pycache__/_3d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/blocks/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/blocks/__pycache__/_time_distributed_2d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/blocks/_time_distributed_2d.py delete mode 100644 imageai/Detection/keras_resnet/classifiers/_2d.py delete mode 100644 imageai/Detection/keras_resnet/classifiers/__init__.py delete mode 100644 imageai/Detection/keras_resnet/layers/__init__.py delete mode 100644 imageai/Detection/keras_resnet/layers/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/layers/__pycache__/_batch_normalization.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/layers/_batch_normalization.py delete mode 100644 imageai/Detection/keras_resnet/models/_2d.py delete mode 100644 imageai/Detection/keras_resnet/models/__init__.py delete mode 100644 imageai/Detection/keras_resnet/models/__pycache__/_2d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/models/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/models/__pycache__/_time_distributed_2d.cpython-35.pyc delete mode 100644 imageai/Detection/keras_resnet/models/_time_distributed_2d.py delete mode 100644 imageai/Detection/keras_retinanet/LICENSE.txt delete mode 100644 imageai/Detection/keras_retinanet/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/__pycache__/initializers.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/__pycache__/losses.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/backend/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/backend/__pycache__/common.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/backend/__pycache__/dynamic.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/backend/__pycache__/tensorflow_backend.cpython-35.pyc create mode 100644 imageai/Detection/keras_retinanet/backend/backend.py delete mode 100644 imageai/Detection/keras_retinanet/backend/common.py delete mode 100644 imageai/Detection/keras_retinanet/backend/dynamic.py delete mode 100644 imageai/Detection/keras_retinanet/backend/tensorflow_backend.py create mode 100644 imageai/Detection/keras_retinanet/bin/__init__.py create mode 100644 imageai/Detection/keras_retinanet/bin/convert_model.py create mode 100644 imageai/Detection/keras_retinanet/bin/debug.py create mode 100644 imageai/Detection/keras_retinanet/bin/evaluate.py create mode 100644 imageai/Detection/keras_retinanet/bin/train.py delete mode 100644 imageai/Detection/keras_retinanet/layers/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/layers/__pycache__/_misc.cpython-35.pyc create mode 100644 imageai/Detection/keras_retinanet/layers/filter_detections.py delete mode 100644 imageai/Detection/keras_retinanet/models/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/models/__pycache__/resnet.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/models/__pycache__/retinanet.cpython-35.pyc create mode 100644 imageai/Detection/keras_retinanet/models/densenet.py create mode 100644 imageai/Detection/keras_retinanet/models/effnet.py create mode 100644 imageai/Detection/keras_retinanet/models/senet.py create mode 100644 imageai/Detection/keras_retinanet/models/vgg.py delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/__init__.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/anchors.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/colors.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/image.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/transform.cpython-35.pyc delete mode 100644 imageai/Detection/keras_retinanet/utils/__pycache__/visualization.cpython-35.pyc create mode 100644 imageai/Detection/keras_retinanet/utils/compute_overlap.pyx create mode 100644 imageai/Detection/keras_retinanet/utils/config.py create mode 100644 imageai/Detection/keras_retinanet/utils/gpu.py delete mode 100644 imageai/Detection/keras_retinanet/utils/keras_version.py create mode 100644 imageai/Detection/keras_retinanet/utils/tf_version.py diff --git a/.travis.yml b/.travis.yml index 509c12d2..040b11a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,12 +14,12 @@ script: - mkdir data-temp - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/DenseNet-BC-121-32.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/inception_v3_weights_tf_dim_ordering_tf_kernels.h5 - - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/resnet50_weights_tf_dim_ordering_tf_kernels.h5 - - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/squeezenet_weights_tf_dim_ordering_tf_kernels.h5 + - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/resnet50_imagenet_tf.2.0.h5 + - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/mobilenet_v2.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/models-v3/idenprof_densenet-0.763500.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/models-v3/idenprof_full_resnet_ex-001_acc-0.119792.h5 - - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/models-v3/idenprof_resnet.h5 - - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/resnet50_coco_best_v2.0.1.h5 + - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/idenprof_resnet_ex-056_acc-0.993062.h5 + - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/resnet50_coco_best_v2.1.0.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/yolo.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/yolo-tiny.h5 - wget -P data-models/ https://github.com/OlafenwaMoses/ImageAI/releases/download/essential-v4/pretrained-yolov3.h5 diff --git a/imageai/Classification/Custom/__init__.py b/imageai/Classification/Custom/__init__.py index 49c16c51..4ba8222e 100644 --- a/imageai/Classification/Custom/__init__.py +++ b/imageai/Classification/Custom/__init__.py @@ -634,9 +634,9 @@ def classifyImage(self, image_input, result_count=5, input_type="file"): elif (self.__modelType == "full"): image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) elif (self.__modelType == "inceptionv3"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + image_to_predict = tf.keras.applications.inception_v3.preprocess_input(image_to_predict) elif (self.__modelType == "densenet121"): - image_to_predict = tf.keras.applications.mobilenet_v2.preprocess_input(image_to_predict) + image_to_predict = tf.keras.applications.densenet.preprocess_input(image_to_predict) try: model = self.__model_collection[0] prediction = model.predict(image_to_predict, steps=1) diff --git a/imageai/Detection/__init__.py b/imageai/Detection/__init__.py index e120fca9..f1fdf4c8 100644 --- a/imageai/Detection/__init__.py +++ b/imageai/Detection/__init__.py @@ -1,9 +1,7 @@ import cv2 -from imageai.Detection.keras_retinanet.models.resnet import resnet50_retinanet -from imageai.Detection.keras_retinanet.utils.image import read_image_bgr, read_image_array, read_image_stream, \ - preprocess_image, resize_image +from imageai.Detection.keras_retinanet import models as retinanet_models +from imageai.Detection.keras_retinanet.utils.image import read_image_bgr, preprocess_image, resize_image from imageai.Detection.keras_retinanet.utils.visualization import draw_box, draw_caption -from imageai.Detection.keras_retinanet.utils.colors import label_color import matplotlib.pyplot as plt import matplotlib.image as pltimage import numpy as np @@ -19,26 +17,21 @@ from imageai.Detection.YOLO.utils import letterbox_image, yolo_eval, preprocess_input, retrieve_yolo_detections, draw_boxes -def get_session(): - config = tf.ConfigProto() - config.gpu_options.allow_growth = True - return tf.Session(config=config) - class ObjectDetection: """ - This is the object detection class for images in the ImageAI library. It provides support for RetinaNet - , YOLOv3 and TinyYOLOv3 object detection networks . After instantiating this class, you can set it's properties and - make object detections using it's pre-defined functions. - - The following functions are required to be called before object detection can be made - * setModelPath() - * At least of of the following and it must correspond to the model set in the setModelPath() - [setModelTypeAsRetinaNet(), setModelTypeAsYOLOv3(), setModelTypeAsTinyYOLOv3()] - * loadModel() [This must be called once only before performing object detection] - - Once the above functions have been called, you can call the detectObjectsFromImage() function of - the object detection instance object at anytime to obtain observable objects in any image. + This is the object detection class for images in the ImageAI library. It provides support for RetinaNet + , YOLOv3 and TinyYOLOv3 object detection networks . After instantiating this class, you can set it's properties and + make object detections using it's pre-defined functions. + + The following functions are required to be called before object detection can be made + * setModelPath() + * At least of of the following and it must correspond to the model set in the setModelPath() + [setModelTypeAsRetinaNet(), setModelTypeAsYOLOv3(), setModelTypeAsTinyYOLOv3()] + * loadModel() [This must be called once only before performing object detection] + + Once the above functions have been called, you can call the detectObjectsFromImage() function of + the object detection instance object at anytime to obtain observable objects in any image. """ def __init__(self): @@ -185,8 +178,7 @@ def loadModel(self, detection_speed="normal"): if (self.__modelType == ""): raise ValueError("You must set a valid model type before loading the model.") elif (self.__modelType == "retinanet"): - model = resnet50_retinanet(num_classes=80) - model.load_weights(self.modelPath) + model = retinanet_models.load_model(self.modelPath, backbone_name='resnet50') self.__model_collection.append(model) self.__modelLoaded = True elif (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): @@ -287,24 +279,23 @@ def detectObjectsFromImage(self, input_image="", output_image_path="", input_typ image_copy = None detected_objects_image_array = [] + min_probability = minimum_percentage_probability / 100 - if (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): + if (input_type == "file"): + input_image = cv2.imread(input_image) + elif (input_type == "array"): + input_image = np.array(input_image) - if (input_type == "file"): - input_image = cv2.imread(input_image) - elif (input_type == "array"): - input_image = np.array(input_image) + detected_copy = input_image + image_copy = input_image - detected_copy = input_image - image_copy = input_image + if (self.__modelType == "yolov3" or self.__modelType == "tinyyolov3"): image_h, image_w, _ = detected_copy.shape detected_copy = preprocess_input(detected_copy, self.__yolo_model_image_size) model = self.__model_collection[0] yolo_result = model.predict(detected_copy) - - min_probability = minimum_percentage_probability / 100 model_detections = retrieve_yolo_detections(yolo_result, self.__yolo_anchors, @@ -313,11 +304,31 @@ def detectObjectsFromImage(self, input_image="", output_image_path="", input_typ self.__yolo_model_image_size, (image_w, image_h), self.numbers_to_names) + + elif (self.__modelType == "retinanet"): + detected_copy = preprocess_image(detected_copy) + detected_copy, scale = resize_image(detected_copy) + + model = self.__model_collection[0] + boxes, scores, labels = model.predict_on_batch(np.expand_dims(detected_copy, axis=0)) + + + boxes /= scale + + for box, score, label in zip(boxes[0], scores[0], labels[0]): + # scores are sorted so we can break + if score < min_probability: + break + + detection_dict = dict() + detection_dict["name"] = self.numbers_to_names[label] + detection_dict["percentage_probability"] = score * 100 + detection_dict["box_points"] = box.astype(int).tolist() + model_detections.append(detection_dict) counting = 0 objects_dir = output_image_path + "-objects" - for detection in model_detections: counting += 1 label = detection["name"] @@ -469,8 +480,7 @@ def detectCustomObjectsFromImage(self, input_image="", output_image_path="", inp display_box=display_box, thread_safe=thread_safe, custom_objects=custom_objects) - """ - """ + class VideoObjectDetection: """ diff --git a/imageai/Detection/keras_resnet/__init__.py b/imageai/Detection/keras_resnet/__init__.py deleted file mode 100644 index fa10b7d2..00000000 --- a/imageai/Detection/keras_resnet/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from . import layers - -custom_objects = { - 'BatchNormalization': layers.BatchNormalization, -} diff --git a/imageai/Detection/keras_resnet/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_resnet/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 6bf25629a46c01becc90e117ebf581e2617addfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmX|5yGlbr5S_g@1Q8-)V=Gw5O%Vh;5%G~Gg|QHuu&}IqN8Pv&+1ZQ4&+sq&rERVJ z1uJKp3^QlWJUBCllgaM%>> import keras_resnet.blocks - - >>> keras_resnet.blocks.basic_1d(64) - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.ZeroPadding1D(padding=1, name="padding{}{}_branch2a".format(stage_char, block_char))(x) - y = keras.layers.Conv1D(filters, kernel_size, strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding1D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv1D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv1D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f - - -def bottleneck_1d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - A one-dimensional bottleneck block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.bottleneck_1d(64) - """ - if stride is None: - stride = 1 if block != 0 or stage == 0 else 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.Conv1D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(x) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding1D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv1D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2b_relu".format(stage_char, block_char))(y) - - y = keras.layers.Conv1D(filters * 4, (1, 1), use_bias=False, name="res{}{}_branch2c".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2c".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv1D(filters * 4, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f diff --git a/imageai/Detection/keras_resnet/blocks/_2d.py b/imageai/Detection/keras_resnet/blocks/_2d.py deleted file mode 100644 index a91f1306..00000000 --- a/imageai/Detection/keras_resnet/blocks/_2d.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.blocks._2d -~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements a number of popular two-dimensional residual blocks. -""" - -import keras.layers -import keras.regularizers -from imageai.Detection import keras_resnet -from imageai.Detection.keras_resnet import layers - -parameters = { - "kernel_initializer": "he_normal" -} - - -def basic_2d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - A two-dimensional basic block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.basic_2d(64) - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.ZeroPadding2D(padding=1, name="padding{}{}_branch2a".format(stage_char, block_char))(x) - y = keras.layers.Conv2D(filters, kernel_size, strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding2D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv2D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv2D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f - - -def bottleneck_2d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - A two-dimensional bottleneck block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.bottleneck_2d(64) - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.Conv2D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(x) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding2D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv2D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2b_relu".format(stage_char, block_char))(y) - - y = keras.layers.Conv2D(filters * 4, (1, 1), use_bias=False, name="res{}{}_branch2c".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2c".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv2D(filters * 4, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f diff --git a/imageai/Detection/keras_resnet/blocks/_3d.py b/imageai/Detection/keras_resnet/blocks/_3d.py deleted file mode 100644 index a713b2d9..00000000 --- a/imageai/Detection/keras_resnet/blocks/_3d.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.blocks._3d -~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements a number of popular three-dimensional residual blocks. -""" - -import keras.layers -import keras.regularizers -from imageai.Detection import keras_resnet -from imageai.Detection.keras_resnet import layers - -parameters = { - "kernel_initializer": "he_normal" -} - - -def basic_3d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - A three-dimensional basic block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.basic_3d(64) - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.ZeroPadding3D(padding=1, name="padding{}{}_branch2a".format(stage_char, block_char))(x) - y = keras.layers.Conv3D(filters, kernel_size, strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding3D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv3D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv3D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f - - -def bottleneck_3d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - A three-dimensional bottleneck block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.bottleneck_3d(64) - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.Conv3D(filters, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch2a".format(stage_char, block_char), **parameters)(x) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.ZeroPadding3D(padding=1, name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Conv3D(filters, kernel_size, use_bias=False, name="res{}{}_branch2b".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.Activation("relu", name="res{}{}_branch2b_relu".format(stage_char, block_char))(y) - - y = keras.layers.Conv3D(filters * 4, (1, 1), use_bias=False, name="res{}{}_branch2c".format(stage_char, block_char), **parameters)(y) - y = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch2c".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.Conv3D(filters * 4, (1, 1), strides=stride, use_bias=False, name="res{}{}_branch1".format(stage_char, block_char), **parameters)(x) - shortcut = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.Activation("relu", name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f diff --git a/imageai/Detection/keras_resnet/blocks/__init__.py b/imageai/Detection/keras_resnet/blocks/__init__.py deleted file mode 100644 index 11799d07..00000000 --- a/imageai/Detection/keras_resnet/blocks/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.blocks -~~~~~~~~~~~~~~~~~~~ - -This module implements a number of popular residual blocks. -""" - -from ._1d import ( - basic_1d, - bottleneck_1d -) - -from ._2d import ( - basic_2d, - bottleneck_2d -) - -from ._3d import ( - basic_3d, - bottleneck_3d -) - -from ._time_distributed_2d import ( - time_distributed_basic_2d, - time_distributed_bottleneck_2d -) diff --git a/imageai/Detection/keras_resnet/blocks/__pycache__/_1d.cpython-35.pyc b/imageai/Detection/keras_resnet/blocks/__pycache__/_1d.cpython-35.pyc deleted file mode 100644 index 612ff016cae1de2789852f24fecebf641920b2f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5486 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 E4brq`<^TWy diff --git a/imageai/Detection/keras_resnet/blocks/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_resnet/blocks/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index db79772ed69b03d25f9c13463181cf4a334f3802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 625 zcmZXRv5wR*5Qb-yO*WgmbO=O)+@Ymd!ET2TI>a?s00q!kmMkZp1+#X%vK=9=xQE~^ zD0rc?RJ;NeHj%hJt*!i?`9Evyj2|>j{p9g?_HzdC3yww>2JobI@}z1<(Ra3ACKtIp_tL3TOpp33NHR1?U>g8PGE@=RnV+Tf+O*;(Ax_ z(FxBT`X0l&)wbLF`sQ@h^@q>O6JzC|ktlP}Xs{2S2+|LxMMvz89PDAx!jV`_$w6o` z)mzuU?1$)$wO?Y+4(F#vZ$UOmETVF4`QS?On7@=wVO>xl}$l)eZqApH7Hn z9-X4wBRBHxsu>rYOWScy^LPiBy6^HRW6n3y%~M)LiF7MnnbJj~_xMTWxV=hz8RZAK zCdEnqe0lwxee~!&dt*I%@=l8#_Fo0NwB7Kpf$Y^4yE5Vv#&x@X`af356N+78z%D4; iv*VXyQ)e0Hs#k&Y_2JuCy_jC-Wx9$_QfBvyH2(r-9JV0< diff --git a/imageai/Detection/keras_resnet/blocks/__pycache__/_time_distributed_2d.cpython-35.pyc b/imageai/Detection/keras_resnet/blocks/__pycache__/_time_distributed_2d.cpython-35.pyc deleted file mode 100644 index 5d2136c7677791223e705e3f28e3eff5098bcb72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5987 zcmeHLOK&5`5w00hq(n(8t#+l=u5D$KY_dy7%Sdu!z=9!oZFyk>i4wm6Lm)6{&NQWw zhBHL>aP6g}Ab^cX8z7h5@)L5*AwMCv{DL{!zU(o#z^8oGGm`kwg9v;HVnQr6tE;NJ zy1KvqX6{T+S6+Sa=lX{)6MaoRpK-L`!dL$h!YA5>w8*z9v}oHtZ5K}41qzGQ8l&wo zve>RjVToF0+Aibw82Kd%;5Tmk&|M}UZG?fGAiqLimS`IdbPe=Ql0Rj7D{TK!b-Mev z%Dz-WyGrUvCbdQwd;7ZPCP7QOexMT-G&+g&-8H}RyAeN?%E!Bbc3QFD38fRX+M#U8 zDAA5^qE4$Jl@m9ecHHiSLODE;(=(8hd=@|Qp^S78M`FJ{@Llo75xn3-r89QHd*qMnbe_(Vk6TpsZuG51S9TNYBaGz!+gO z_adnSIy4ex+bgjC3yYrH6fICvqy*I+XASgsvW}!=#x7>ROLRB{Vj}y2COXUOHJv-(c9JQU#s}-AJ@J2TW9TNJt1G3>N3}R5f-311%yP?3U zl%VSt8V5(IB|Ns`4@1EOzPIqz|A5=s`OX^!gR6YpEg%6fV6R9gB|3oyf0lJfe1eax zA;&Q~!HLHaM*}be`~Y~A=ufuIamI(@7;NZ^1Iz%@n3H}2umOx4NIxlJG6fDYJwlKS zu5dxz;CZsw%*b9aHb|D|K0C7GS?=dqo7KBHLWFCxp1U*%ZZSO-v_MMEjEwhR6CWZ~L5v}q^v8s1U8MZDARi+y;y zf6(}-dUq==w}tNmg%#g^X_4Wrdpp-19333F4J9IPcTJ?_3^H^&4(M?kfzVI7vq15F ztD^Wk&}mt=br8nU&cAlQKKc8=rM&ID8-EwZwojSGTQLDCD3?r(O>6if;#SoCrd<+`0dCq~->20av-)2j= zwuvkm6qB^{4xksmE9S10d{HU`Hgm(!nN}WqN$}LbXj*ve`_)2feV$s+(s7;RdUagQ z@bF^>snd%2=kk4$mh*c!oj3#RbTYfaT)x!Oin&@d<5E__ztXo)-&wDJqA{)duVcQ# zeiMqOj6M_f&Dg_T2WnP-{C@rY7T_8GU#qR0wFB0PpuUN$Fz-03_X2UVvDu* zv(#$7g+w&SFp>3!^`7;v^}79r^|tk{J%wNI*(LiCG~N4`B6;mi@KF)!jasu>Rt(5# zxgorL8Tsi=GdUMmk8ra}s0B7Cc)Kbs#L7<#SOKi&4_P~AR!02*ZS`YT+~~jN)lazR zM=Tz)_$fqQiOk7iEr-gCIEK2a{^K zR^6L5Sm}sfS>bNcZU5z=1i(YDjdE=Q;GvfUVS>D12=X^W1o?bguUxXHz&Quv4wK|; zVXoLyi$g)KAjzok-G2LPNpf#Z?)Cc)lH{eW>?}_6F}wlsL`^{su~1<+nwmx58SPa! z*>aHcsyWtPOnKEL_x-oLcPMDX@aPvs4mwEIy}^lj-yLo4rO>_YNyFF-nO4kz)EyR! zESRYolC17>i>Z;CXYmq5wV;?Ys#jPnu_B|yqYyFXZHn2Lp>bcLp&bb7WuM@jPYehB z)j0>P4s*9zN1s*0%URTd_hpK)uM#UqHEcB&dH&Pb>FIsg0)nC8;ev;7pqTunC@ z)f&%oGA+BVAA7EwzfG-W?@%+U%`Z}gt|__0uWI={s?J%r2x@1K1lhCBQvRrLuCMhz sDrB3$l%9F68QpN~9$`52_?y{#;|;zIHJ`u|9u}soS!=d1Rb1Nq4^Ud_G5`Po diff --git a/imageai/Detection/keras_resnet/blocks/_time_distributed_2d.py b/imageai/Detection/keras_resnet/blocks/_time_distributed_2d.py deleted file mode 100644 index 8c4e46b5..00000000 --- a/imageai/Detection/keras_resnet/blocks/_time_distributed_2d.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.blocks._time_distributed_2d -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements a number of popular time distributed two-dimensional residual blocks. -""" - -import keras.layers -import keras.regularizers -from imageai.Detection import keras_resnet -from imageai.Detection.keras_resnet import layers - -parameters = { - "kernel_initializer": "he_normal" -} - - -def time_distributed_basic_2d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - - A time distributed two-dimensional basic block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.time_distributed_basic_2d(64) - - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.TimeDistributed(keras.layers.ZeroPadding2D(padding=1), name="padding{}{}_branch2a".format(stage_char, block_char))(x) - y = keras.layers.TimeDistributed(keras.layers.Conv2D(filters, kernel_size, strides=stride, use_bias=False, **parameters), name="res{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.TimeDistributed(keras.layers.ZeroPadding2D(padding=1), name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras.layers.Conv2D(filters, kernel_size, use_bias=False, **parameters), name="res{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch2b".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.TimeDistributed(keras.layers.Conv2D(filters, (1, 1), strides=stride, use_bias=False, **parameters), name="res{}{}_branch1".format(stage_char, block_char))(x) - shortcut = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f - - -def time_distributed_bottleneck_2d(filters, stage=0, block=0, kernel_size=3, numerical_name=False, stride=None, freeze_bn=False): - """ - - A time distributed two-dimensional bottleneck block. - - :param filters: the output’s feature space - - :param stage: int representing the stage of this block (starting from 0) - - :param block: int representing this block (starting from 0) - - :param kernel_size: size of the kernel - - :param numerical_name: if true, uses numbers to represent blocks instead of chars (ResNet{101, 152, 200}) - - :param stride: int representing the stride used in the shortcut and the first conv layer, default derives stride from block id - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - Usage: - - >>> import keras_resnet.blocks - - >>> keras_resnet.blocks.time_distributed_bottleneck_2d(64) - - """ - if stride is None: - if block != 0 or stage == 0: - stride = 1 - else: - stride = 2 - - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if block > 0 and numerical_name: - block_char = "b{}".format(block) - else: - block_char = chr(ord('a') + block) - - stage_char = str(stage + 2) - - def f(x): - y = keras.layers.TimeDistributed(keras.layers.Conv2D(filters, (1, 1), strides=stride, use_bias=False, **parameters), name="res{}{}_branch2a".format(stage_char, block_char))(x) - y = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch2a".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="res{}{}_branch2a_relu".format(stage_char, block_char))(y) - - y = keras.layers.TimeDistributed(keras.layers.ZeroPadding2D(padding=1), name="padding{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras.layers.Conv2D(filters, kernel_size, use_bias=False, **parameters), name="res{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch2b".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="res{}{}_branch2b_relu".format(stage_char, block_char))(y) - - y = keras.layers.TimeDistributed(keras.layers.Conv2D(filters * 4, (1, 1), use_bias=False, **parameters), name="res{}{}_branch2c".format(stage_char, block_char))(y) - y = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch2c".format(stage_char, block_char))(y) - - if block == 0: - shortcut = keras.layers.TimeDistributed(keras.layers.Conv2D(filters * 4, (1, 1), strides=stride, use_bias=False, **parameters), name="res{}{}_branch1".format(stage_char, block_char))(x) - shortcut = keras.layers.TimeDistributed(keras.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn{}{}_branch1".format(stage_char, block_char))(shortcut) - else: - shortcut = x - - y = keras.layers.Add(name="res{}{}".format(stage_char, block_char))([y, shortcut]) - y = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="res{}{}_relu".format(stage_char, block_char))(y) - - return y - - return f diff --git a/imageai/Detection/keras_resnet/classifiers/_2d.py b/imageai/Detection/keras_resnet/classifiers/_2d.py deleted file mode 100644 index 7d2bc346..00000000 --- a/imageai/Detection/keras_resnet/classifiers/_2d.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.classifiers -~~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements popular residual two-dimensional classifiers. -""" - -import keras.backend -import keras.layers -import keras.models -import keras.regularizers -from imageai.Detection import keras_resnet -from imageai.Detection.keras_resnet import models - - -class ResNet18(keras.models.Model): - """ - A :class:`ResNet18 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet18(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet18(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet18, self).__init__(inputs, outputs) - - -class ResNet34(keras.models.Model): - """ - A :class:`ResNet34 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet34(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet34(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet34, self).__init__(inputs, outputs) - - -class ResNet50(keras.models.Model): - """ - A :class:`ResNet50 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet50(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet50(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet50, self).__init__(inputs, outputs) - - -class ResNet101(keras.models.Model): - """ - A :class:`ResNet101 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet101(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet101(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet101, self).__init__(inputs, outputs) - - -class ResNet152(keras.models.Model): - """ - A :class:`ResNet152 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet152(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet152(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet152, self).__init__(inputs, outputs) - - -class ResNet200(keras.models.Model): - """ - A :class:`ResNet200 ` object. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - Usage: - - >>> import keras_resnet.classifiers - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.classifiers.ResNet200(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - def __init__(self, inputs, classes): - outputs = keras_resnet.models.ResNet200(inputs) - - outputs = keras.layers.Flatten()(outputs.output) - - outputs = keras.layers.Dense(classes, activation="softmax")(outputs) - - super(ResNet200, self).__init__(inputs, outputs) diff --git a/imageai/Detection/keras_resnet/classifiers/__init__.py b/imageai/Detection/keras_resnet/classifiers/__init__.py deleted file mode 100644 index 6beae879..00000000 --- a/imageai/Detection/keras_resnet/classifiers/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.classifiers -~~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements popular residual classifiers. -""" - -from ._2d import ( - ResNet18, - ResNet34, - ResNet50, - ResNet101, - ResNet152, - ResNet200 -) diff --git a/imageai/Detection/keras_resnet/layers/__init__.py b/imageai/Detection/keras_resnet/layers/__init__.py deleted file mode 100644 index 7a622b36..00000000 --- a/imageai/Detection/keras_resnet/layers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._batch_normalization import BatchNormalization diff --git a/imageai/Detection/keras_resnet/layers/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_resnet/layers/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 3cc05184c40259ff3676c8dd2091a8f66743110c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmWgV<>k^+pA=)pz`*brh~a<<$Z`PUVi6#b0z`}qISdTBj0{nX42&sE48fYrFBySS znvA!EoDxftGyL+4auahhs}f5x^Yi>PnW9AElR)C}dB_q)AahqT6fpxSF!3wP*(xTq zIJKxa#y7t>wOGMFCowHGuRJlvB|o_|H#M)MIL6U4#xplDJrzhB>lwh=nIN&m%ovx{ zlGJ37tufiDMTy1nMXAMksU3b)=uljFmn z1Q-Fl%RR___=b=V5@5<;>cPr~5#kK?li&|Vz%V1bUYOl2{T^Q!G-8yqi5U6t17M7Z zOZ*-2V2uQjhd@ZUMk2^t4v7faPujNsMwskySgV;Ri={9{T`fg7#V^haR zQVBgM#femUa6M!zK3JGoxtKtvl zCn`i{ZQMFS1FXXy_{)daMIWrI%NU)*MI}+NQ}Z!8#_4!2A}WYu;`Bv9d#LCcu>Hgv z+O|`ts3u9Es3VPf0rtLhG9|uh5lp012Tj6Mm;75~dSq^+ao4)eddtll_)@+Lu5^z0 z($?E({^Pm=_=;Td|M1tO11K1*7>)q_Dc-Q*7G9y;ok+v8x*8V~8!hI!z>%X163-Q2 zqvqRn`b7OMF=-^f$L_P`qZ>-N%%8Mqyeb@P9Ir0tvpQdtQqv6SEC%sx8~whBvGgVnw0MO5$m`5f$K)%+tAIBjhg9k=w+JAR@nB7hRaJD3r#6| F{y(@a4JrTt diff --git a/imageai/Detection/keras_resnet/layers/_batch_normalization.py b/imageai/Detection/keras_resnet/layers/_batch_normalization.py deleted file mode 100644 index ba296eb2..00000000 --- a/imageai/Detection/keras_resnet/layers/_batch_normalization.py +++ /dev/null @@ -1,22 +0,0 @@ -import keras - - -class BatchNormalization(keras.layers.BatchNormalization): - """ - Identical to keras.layers.BatchNormalization, but adds the option to freeze parameters. - """ - def __init__(self, freeze, *args, **kwargs): - self.freeze = freeze - super(BatchNormalization, self).__init__(*args, **kwargs) - - # set to non-trainable if freeze is true - self.trainable = not self.freeze - - def call(self, *args, **kwargs): - # return super.call, but set training - return super(BatchNormalization, self).call(training=(not self.freeze), *args, **kwargs) - - def get_config(self): - config = super(BatchNormalization, self).get_config() - config.update({'freeze': self.freeze}) - return config diff --git a/imageai/Detection/keras_resnet/models/_2d.py b/imageai/Detection/keras_resnet/models/_2d.py deleted file mode 100644 index 28384c26..00000000 --- a/imageai/Detection/keras_resnet/models/_2d.py +++ /dev/null @@ -1,289 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.models._2d -~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements popular two-dimensional residual models. -""" - -import keras.backend -import keras.layers -import keras.models -import keras.regularizers - -from imageai.Detection import keras_resnet as keras_resnet - -from imageai.Detection.keras_resnet import blocks -from imageai.Detection.keras_resnet import layers - - -def ResNet(inputs, blocks, block, include_top=True, classes=1000, freeze_bn=True, numerical_names=None, *args, **kwargs): - """ - Constructs a `keras.models.Model` object using the given block count. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param block: a residual block (e.g. an instance of `keras_resnet.blocks.basic_2d`) - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - :param numerical_names: list of bool, same size as blocks, used to indicate whether names of layers should include numbers or letters - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.blocks - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> blocks = [2, 2, 2, 2] - - >>> block = keras_resnet.blocks.basic_2d - - >>> model = keras_resnet.models.ResNet(x, classes, blocks, block, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - if numerical_names is None: - numerical_names = [True] * len(blocks) - - x = keras.layers.ZeroPadding2D(padding=3, name="padding_conv1")(inputs) - x = keras.layers.Conv2D(64, (7, 7), strides=(2, 2), use_bias=False, name="conv1")(x) - x = keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn, name="bn_conv1")(x) - x = keras.layers.Activation("relu", name="conv1_relu")(x) - x = keras.layers.MaxPooling2D((3, 3), strides=(2, 2), padding="same", name="pool1")(x) - - features = 64 - - outputs = [] - - for stage_id, iterations in enumerate(blocks): - for block_id in range(iterations): - x = block(features, stage_id, block_id, numerical_name=(block_id > 0 and numerical_names[stage_id]), freeze_bn=freeze_bn)(x) - - features *= 2 - - outputs.append(x) - - if include_top: - assert classes > 0 - - x = keras.layers.GlobalAveragePooling2D(name="pool5")(x) - x = keras.layers.Dense(classes, activation="softmax", name="fc1000")(x) - - return keras.models.Model(inputs=inputs, outputs=x, *args, **kwargs) - else: - # Else output each stages features - return keras.models.Model(inputs=inputs, outputs=outputs, *args, **kwargs) - - -def ResNet18(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet18 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet18(x, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [2, 2, 2, 2] - - return ResNet(inputs, blocks, block=keras_resnet.blocks.basic_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def ResNet34(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet34 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet34(x, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 6, 3] - - return ResNet(inputs, blocks, block=keras_resnet.blocks.basic_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def ResNet50(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet50 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet50(x) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 6, 3] - numerical_names = [False, False, False, False] - - return ResNet(inputs, blocks, numerical_names=numerical_names, block=keras_resnet.blocks.bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def ResNet101(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet101 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet101(x, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 23, 3] - numerical_names = [False, True, True, False] - - return ResNet(inputs, blocks, numerical_names=numerical_names, block=keras_resnet.blocks.bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def ResNet152(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet152 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet152(x, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 8, 36, 3] - numerical_names = [False, True, True, False] - - return ResNet(inputs, blocks, numerical_names=numerical_names, block=keras_resnet.blocks.bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def ResNet200(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a `keras.models.Model` according to the ResNet200 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> model = keras_resnet.models.ResNet200(x, classes=classes) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 24, 36, 3] - numerical_names = [False, True, True, False] - - return ResNet(inputs, blocks, numerical_names=numerical_names, block=keras_resnet.blocks.bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) diff --git a/imageai/Detection/keras_resnet/models/__init__.py b/imageai/Detection/keras_resnet/models/__init__.py deleted file mode 100644 index 0d39d931..00000000 --- a/imageai/Detection/keras_resnet/models/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.models -~~~~~~~~~~~~~~~~~~~ - -This module implements popular residual models. -""" - -from ._2d import ( - ResNet, - ResNet18, - ResNet34, - ResNet50, - ResNet101, - ResNet152, - ResNet200 -) - -from ._time_distributed_2d import ( - TimeDistributedResNet, - TimeDistributedResNet18, - TimeDistributedResNet34, - TimeDistributedResNet50, - TimeDistributedResNet101, - TimeDistributedResNet152, - TimeDistributedResNet200 -) diff --git a/imageai/Detection/keras_resnet/models/__pycache__/_2d.cpython-35.pyc b/imageai/Detection/keras_resnet/models/__pycache__/_2d.cpython-35.pyc deleted file mode 100644 index 909eb64074fa22dd407317923c262b02d6be8001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9632 zcmeHN%WoUU8UJ?q6eY^C{LaH3t4<>t3QJKI;Ha@`$Es7KuoBb`3NSLLRc^Tz1q$>xvwSRV$8zIZXbSN$oSFURn{U4N z%#0ONov3H}^%QwoYUQYw zBSVc(lb5H~7}dt&c1E`gR4bsJB_~I!1pYiZWAr(2j_YRps6fs*eU3>d5}l|Qb!Rcr zIZ4hj-H8gY#lxq_Ij%d8B|4|cIiWl8bO3phoKw2J)zW>{p?1Z_||wfE|xBx7GG|i~FHu?V#Q9EWyH^;2pPH4MhVLKiIR*12>|#QJvLL_LLWFsTfhb(Pf-`}V6&D(7x)2u5}4CNU{DWW zfSuA$PXmeZ24)m#Z<2O@ zMd1W>Cl#S9_m^nz7A@zDC<=3f2uU4gKew*9nwAPMHI$KD}!E7K5A# zdKYtcCuj@9_3DjJFW^y%M9%D{$-`<2{_8AgY;ike9qIZ_7H)FZbRTe^Y4v1w(DB1^L8Dq~Tf%BF*Kc=1xfK7h z5UL*tHp|P+GP8X2%Fyy{&Vr5pbg_DuZ>w?Zr4d}Q4J_$tVUat5*#7#nKg*#RS;F3Q zL(ta|{OOTPSku7bVuGJTNZ;(^Ny`mOx;AY836fmj_Bsx)hd~=_++biHpTn|ruVl7o zN$GC5wiUwSS-iFp$}|KAgZxgb!G&T(uZx92fBZu>o0y2TIx=Jpt|uGi(1zgr5wAD= z1Tk@+Wba#{y}25QmgTvRlJo#MFEc-2owj3z9HJMTIe`xs<||%H-XnZ;E)dKGR_4`x ztEE=pxgZPlH-f;MV-lTAx{o-sWUL-?4r;?4wJz6p6i+zY+2oi(FpWYDj#oHMq$l8zn=Mwu3m#aLCTWrS-HyQWsz)`9ibV4W;mI_5n@bcO% z%aeS)q%c1rtnV9f4X-l&jQBQ<@B4WA#|49`nN&=+n?2(Ti}ggl2aK3z{3~hMs89f33IRCkWPP%68CdyB?pNu^g*4Gsk9> zE;WN#8*LFtiLfhz_Cq}TWX7`Xj9Gxtkc0#VW4w0s}IybkjW_Yp^FA1M#5-ivZ= z%Tf9grIkk3ioIst4*Uo6`_~nu3b|1ZZqjwQ+()RTC=b1^H(X2JiZU89N?W_GjB>m! zT`%yP-)(;Lhp+zex8*1sTVs@O_%Y5XEx6Z-3VIlrg?d_uiaMc#_vM8GN@}2Snk>iS0N9aSWEhxwRpvA zh*{fnf(idv}Bsm5an{oWIIso7;o1EB_ZWl7zM;Vs)?g zFcXI#Nd%3xc{I?x($J_Ud~Ot*znX|+uP|P%#vb}-Ff>t7#XO2qTHB;{s<9u-_fynT zcIe%P1H^(FdPNn=@5IFtYEiy#HtT3fW!&`I1WaFCL+!RmC(nB5YEmWQi_oJ4NfIj#rU!V_?h6w=dUmwj4(}2A82sn=Fr2*-? zr9$Ul$QEBN4Opxkd4wa6@UlDPgFV7xWp?)!>{3_bz1TABP5c7IEm2j~1KdDVYy@G5 zofyX^qw17$2c!ZT=d<&H#mZkWq0FPu`GCp@-bejq`~oZ=9~?D5?H7;{;88x2DZnEj zQ@~N?&@YU4re1z~P?-EoiLa#2_byU~{pI9E ziauS{7hCa(t2z}G*Hxi*Lqg>{LU}ZyXil81>RnZQ!1^Aj!e4@xmHUVjGD(g0BLI{EAWe0`?<6>E|LSw)XCsmvX!GRCK zx8N(~%89SQiD$ZwNd!yz`5*uNp5K#w6vynr{ci#u@B_C7{o@IJwkG5NCk~K%C0o}mdeYo*-|x2Q{}3#?CZaSv8(smCbXd~RHDtcP)3!FO}5pxEwVbH z3_5SK!fl}(OZIa>O)bJMc&Y5AYPx7QAD{2l$?0C5rQP`2O^5x<&U@6mOvm&abZWmrMcnI8MK%4$p%VKYs`zqo*o9K&)kaG0%T->x>#lu= zp~J`_a0neD0>2|0YEtHZyL5qL=QD?i!_*;mm^sV|-%%er?eOcvvxnlfRkan*E30hs zqR8H=@>3?()u#P(YvSpJxG>o}Mf1rrz0umOWm>FNqc)AMN^$#nh~3o+DRrqEDVN*N SUG!-8Bp$o+t?MiBPT6nbS=#IX diff --git a/imageai/Detection/keras_resnet/models/__pycache__/_time_distributed_2d.cpython-35.pyc b/imageai/Detection/keras_resnet/models/__pycache__/_time_distributed_2d.cpython-35.pyc deleted file mode 100644 index 62f9eff70f32ddd537a337e46720aa6c3c68791d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11293 zcmeHN&2JmW6`x&xiV`JDw)_#N&O~(+(ohJJl3YiP)!0@XxUH3-woz1}L63Gv)Jn@; zW@af%E>#qD3-pvjZutv}qUYZGAN0P*ribd-LxG||fxb7hq?VNI$nghafTTE@ef#Fk zoA=)Q=FQHY$>(#I-u<%nqY@!slh`K(^$+1E{|ALl$SRZuu}$I{WYz4}6R~7 z3F4+nGecH0#K87R;$}&6gshI}>y*0Ak<}brr-_{*SZ0YmLVgFrN3q9l-mbik(Htmj>Ywb>-3uITU!!qF5Bfo z-gW+&AjC>z1Ncn9F9*L*;3t1+5)u%yX^>5mJXU_+GKsi=*4WDu4;+-h3i2e#X0lsJ zp?iVnF}M=yY4X@GIs{6xH`3+lF>7DXprh^Yqi5|g^4K)7VO}*HIn*$tx`WslCmkrk zkIJW=6!e(DhEq>ysP%-7PNGcYJoQvgh_s7Ju^*@rO#`!1>~vo%h}FFuX%MnEN_MtM zFiJWG%3r>}L26*BF|wB@JJ(4tMmjhoxG_)mKzEaLa-;)P2+uRv2y6^-+QEw9ad&q{Cx^Repl59?q4*2ga0^YSLs?*7kFNk>u#T~v4hS9g}w_!HD z)BX~$B}d_N!}nnBv@M8wb~xT2s2J`}w%a)89#DU6lUo68OUG-_V1v_!v&}uK!kb!t z+Y3rLC3~^O1Zz^qYqbNpsQ=RdCXX*@ftMO3$~>UTfO!_D{yL4i)6=7L3)?*?_Q}$7 zXi;?=&AGq-<|}zH5t*2(%Z8!NtOGS_(&*9V0267_uAZrLp05G{gJ z+xGxBJ@kg;k>cIH1P2#*2(H{+q<06A^ygfz@Ic?D51n9xa?kQ@3@N`Ifa9S<2#DB- zuHIy>S7PjRSQ_`&Py8)7bR-(=SeL zuogJB>zb>yP_53-QdP_qXKAHeF889PQD zqEIXrc1zm1&n0geFozc+zsyo*1Uk{pWX9l|(=tGUXYmPn)#Bm?VU9$XU z%W-*OhS{t+GfQV+QuBr{U~;>4OZZYkf+76YF5LTUhFMlyFl%?_eo+Nnn71~V=Rsmq z2TTa|Aw3}b1{7hY#cZ4$VbWtw9_FLVy5)P@mHiKqFQfP{5wf}5hx~zr*|vmv?l5^X zOexMV$#xtWW_U|FuJ1Mewej^I{_>xHEQM(e0AY5`(=x-P;BGt2sb&xhP)|ziI@R)h zw-Sy66mdN))My{Gm~f;AEkbFnco)Llfx*K}WN_%w!ub%U_3{vAw4dcl(G+7KOchWc z$2t~J$4*qyM?V>+RP=YSUJi z!!)dh+tp>Ej5l={3&Y&CUg(5lx7p68U<>dHG#%l}AmHsVC79RXVVbpCsLc2^aJoQf z|40a5gr`4t{Wa!Z+lF+u!4D*)RFsBkJ>kOCZA8xEgrGQRjD#?yCU`g+1H~{CMYTAO z97!e`GE8qh#Q$OvNybi?UFQtfLn+=yN){Geu#RJgIan!$ng|j+kLf8;|3t)9VEga( zjm6qM&|KDT`w{^E6PK-X?;)!#`xf{f^5ojJTeVwgY4|@^D);Kj`pl^{cs_F%0j-Pm#sN~88MH= z0u;rhLL{}iXh1&~KvvcF^rE|iD5ei^|3HGcjIw@+1wyngmY^1%ITAE0SL$$6swWI| z41iHwgX`l%qJld>#Umgw6@$dIg2YT560sJGVzkQh%J7RrC_0n!DP zmwoig0BQUsY*ovZAtD_ckrs!E*~>`GD&@)$BcB%|_s<|uep6<)3{1}oglCW5pq49t z>mi}up?>^YBjG}Ih=fBVgs0^%_VDkSwy2eb>JSN^0}1!D073f~p?H-@xKRD~022P> zbwZx6&D*ZmAMC{Bf$ zdfoP|dVMvoHxSWrznFpB(d(Acsnn`|`7(NGV|7aN3*NxjI-N&ws2#l{z?@R=lv*GA zUx-ex59lS;I(j&)4sO)j1o|)s4^#v`P87I>7nr>Y1a5&k$4+U~d5t=%$$kLOn&5@g dC0u6Yn^2_9F++XL69#+}(?6Yl2fi1v{67pj-vR&t diff --git a/imageai/Detection/keras_resnet/models/_time_distributed_2d.py b/imageai/Detection/keras_resnet/models/_time_distributed_2d.py deleted file mode 100644 index 0e99d8f1..00000000 --- a/imageai/Detection/keras_resnet/models/_time_distributed_2d.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -keras_resnet.models._time_distributed_2d -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This module implements popular time distributed two-dimensional residual networks. -""" - -import keras.backend -import keras.layers -import keras.models -import keras.regularizers - -from imageai.Detection import keras_resnet as keras_resnet -from imageai.Detection.keras_resnet import blocks -from imageai.Detection.keras_resnet import layers - - -def TimeDistributedResNet(inputs, blocks, block, include_top=True, classes=1000, freeze_bn=True, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` object using the given block count. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param block: a time distributed residual block (e.g. an instance of `keras_resnet.blocks.time_distributed_basic_2d`) - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :param freeze_bn: if true, freezes BatchNormalization layers (ie. no updates are done in these layers) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.blocks - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> blocks = [2, 2, 2, 2] - - >>> blocks = keras_resnet.blocks.time_distributed_basic_2d - - >>> y = keras_resnet.models.TimeDistributedResNet(x, classes, blocks, blocks) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if keras.backend.image_data_format() == "channels_last": - axis = 3 - else: - axis = 1 - - x = keras.layers.TimeDistributed(keras.layers.ZeroPadding2D(padding=3), name="padding_conv1")(inputs) - x = keras.layers.TimeDistributed(keras.layers.Conv2D(64, (7, 7), strides=(2, 2), use_bias=False), name="conv1")(x) - x = keras.layers.TimeDistributed(keras_resnet.layers.BatchNormalization(axis=axis, epsilon=1e-5, freeze=freeze_bn), name="bn_conv1")(x) - x = keras.layers.TimeDistributed(keras.layers.Activation("relu"), name="conv1_relu")(x) - x = keras.layers.TimeDistributed(keras.layers.MaxPooling2D((3, 3), strides=(2, 2), padding="same"), name="pool1")(x) - - features = 64 - - outputs = [] - - for stage_id, iterations in enumerate(blocks): - for block_id in range(iterations): - x = block(features, stage_id, block_id, numerical_name=(blocks[stage_id] > 6), freeze_bn=freeze_bn)(x) - - features *= 2 - outputs.append(x) - - if include_top: - assert classes > 0 - - x = keras.layers.TimeDistributed(keras.layers.GlobalAveragePooling2D(), name="pool5")(x) - x = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"), name="fc1000")(x) - - return keras.models.Model(inputs=inputs, outputs=x, *args, **kwargs) - else: - # Else output each stages features - return keras.models.Model(inputs=inputs, outputs=outputs, *args, **kwargs) - - -def TimeDistributedResNet18(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet18 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet18(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [2, 2, 2, 2] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_basic_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def TimeDistributedResNet34(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet34 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet34(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 6, 3] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_basic_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def TimeDistributedResNet50(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet50 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet50(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 6, 3] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def TimeDistributedResNet101(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet101 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet101(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 4, 23, 3] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def TimeDistributedResNet152(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet152 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet152(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 8, 36, 3] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) - - -def TimeDistributedResNet200(inputs, blocks=None, include_top=True, classes=1000, *args, **kwargs): - """ - Constructs a time distributed `keras.models.Model` according to the ResNet200 specifications. - - :param inputs: input tensor (e.g. an instance of `keras.layers.Input`) - - :param blocks: the network’s residual architecture - - :param include_top: if true, includes classification layers - - :param classes: number of classes to classify (include_top must be true) - - :return model: Time distributed ResNet model with encoding output (if `include_top=False`) or classification output (if `include_top=True`) - - Usage: - - >>> import keras_resnet.models - - >>> shape, classes = (224, 224, 3), 1000 - - >>> x = keras.layers.Input(shape) - - >>> y = keras_resnet.models.TimeDistributedResNet200(x) - - >>> y = keras.layers.TimeDistributed(keras.layers.Flatten())(y.output) - - >>> y = keras.layers.TimeDistributed(keras.layers.Dense(classes, activation="softmax"))(y) - - >>> model = keras.models.Model(x, y) - - >>> model.compile("adam", "categorical_crossentropy", ["accuracy"]) - """ - if blocks is None: - blocks = [3, 24, 36, 3] - - return TimeDistributedResNet(inputs, blocks, block=keras_resnet.blocks.time_distributed_bottleneck_2d, include_top=include_top, classes=classes, *args, **kwargs) diff --git a/imageai/Detection/keras_retinanet/LICENSE.txt b/imageai/Detection/keras_retinanet/LICENSE.txt deleted file mode 100644 index 5c304d1a..00000000 --- a/imageai/Detection/keras_retinanet/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/imageai/Detection/keras_retinanet/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_retinanet/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index edf4a67d3c9c436a8be1b7e95fd9c7e812dde0a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmWgV<>k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuQX??n9$@Nml1^^)~GHn0= diff --git a/imageai/Detection/keras_retinanet/__pycache__/initializers.cpython-35.pyc b/imageai/Detection/keras_retinanet/__pycache__/initializers.cpython-35.pyc deleted file mode 100644 index 677d27ed41bd657f3f560a32f19a147a03285946..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1663 zcmZuxUvJws5T|6>Nfig(fDYZz0V9F|t%lS|GYlKLVyow*owiOc;$%e)0|G74GBa6J zNouaW>?!#K`vQC3%RZ3ahCTHw>}hwhlem8f@OVd_c=x;eQ8za?-Oq0SG5FIVK{PFg!DmLB(_Ovkv=F3-6s9oh3=5PLmtOqJLDX^9l|YgQYR-3a#kb#HT<~e z)!74^5Vs@qnG(s^(7oMzU)}}r6@4V;Gez%=jmh=>ot+Vytw>Hh*L@-)o@pM_B8$1A zX3S|jXAvyRlWqDlS6ax7?zML59dy`Q9&LGFyE9qP37gSO8d_)$7YI#9BIT67jCgLS z$S6e0Q^B%`)2T4yD#-GRmiv5pfgBnJz6`wc8I_||FUpMTQW%H;08FRT7OS#sNtNuR z3k-VaiQhR0dIxtQQ`bGtQm!>s{6!%Y036OJ%OQ`54I#UfO{r9rC5pqDk(lRH2_v#( zn`${SQ>M5Z3vHAb7Us1AEpw-UbkzY$#4@_o?oq$DMZatJ{NA?v%s=|RdwfKnwGR*5 z!I6K^quoQ==?44$k>3r#dPLj7bNZtn>~B*J<$*5nm$|~+Awhwq<8jOFaV+j-Tj&T5 z(bhSS#7IPdC`$^KaGJ0VZ zgsD+*ASa=>f`YQ3C72G0>m+U5z@xj#78K=#2;QbYW1%0@h!vcbNLiV4q* z9<=>|KY<#<`gv>jQWv<##9*Hr9$|S0zi`ELs5tP6W!wzJ6+Yd{XXSbr;#L@bj2Txj ztocW87N>RL6c@3zlQYXQHaV%Nfr&Zf!^Lvz%pw~%ey@?! z8YEf^%6e>OIe&$Syn1P8dFjZE>(ZGpGcIeXOco#;~g?3MM38)ZE!Ch(`;fGvfLEt{(xDK}PL eMD_VfpekBLscS8>GQ7G;c=4k zzLszhYTgbb$>p{$dnpfNj+J^*C}LmoL6~ltMRFH4_j&Fh*-Qm=1+;rRJZX<>agn+% z$3qz)fx%!<6DG=9qB=LDVGjD{S*P{p0-}ZN(u@BWoO5kX0+2*cARt83!F2W$gERd4t{ean8)+KpwsN5;{NP z-(qb$Kh8fewr{gNI-O2qWaUD!g!dd?{To)>8>(4s-(e|ZFD#bY>;)PQ*#a2{_R&az zMh+|^9kyRM8ZWXvpaRMUIsnXpWgpL$Xg2pb!44QXJf1Bd*#cv5@|G=-HbDv}Ox!$- zH;kR%FP;qglnJ^-273m$kaGtJ_W$*O?mdUi;UztEkWPucvDmru)GHmuK1r@g%f_;Z z$^Lb4o_Ab;LOXc_);pN zGnY{70kk5@++C>>?M2~BnK@mtofUkc)2!5v5|M6aj_!A}35Y>MhZeRW~c`c&X}3`T~PNR*J-?jC6L|j|7ke zpc(vR6G8YY)QQ@d6j5(Wcx~aQ)N3cch`fO$2Hq(95hB9k*(BI+D^Z>X{A4x}#sN4> zSM985%(G%gbi3jg_II#)r}b2Waa-RcT57%)iMEUfqV*v0`&}fVTPyWey$hh@`$=tS z)DCG(gslfMl|Es-WeVyk2~rcWOj{I*)HM{L|Z2R9vzatOaMmnRPB$ zSMhh+zT~*hoV8%jIJ>jPqP84Sg8FW4=s|H9R~@y0kGf2P+zm(VvC;Q2niY+USu{1> zfXte`qCUVkKNjWtKAj(W=&+24g`)$Cz@a<$lcAWlxhny6;umm%1%4?sR1tbUS<+L% zJ0h81iDYo(0UmST;>!IVC^7B4VbMt2VVA%`|E&aGqiGPyTNGu82cXIk0g3`!jKE`< z3Ei9lhQwwdPF#g3MwA177|!CNHwy4cq%j->Cc!2!A!J$@A5y^_2qM?05K~|!8W|4} zWCdmepD;ELmtX**t=tG~Wqw*<;yks?~c&< zNMoExrj9}d>wtu@jg=9ROy|5ztHG#QbsL9tN(^x*)1v)cA90A;@4WZNJwrV?qo^|3 z(Fss9B=GZ#sZ%7TNt}VmPT`KlmFN*UWsca?S%HWrb9%`@(GgWsBnTnuG(@$cE>e3A zB6F3*?*?9Erko(AoKbqs!# z!HnzaZjz*1UUX{(s`^6+W|yoh)@5fIc%60Timo*S{9dr$vCbA;XTh3r?%KQWjqJuS zI~vOo3f`l2)yHr;9J16kYMZqyBlWu&(^P(Dx%SKzbpx$xDVy-TAn`rV)H-8^&JCiC f!Y~UC)vno47Q2_pR{}4spaxipXz(4V6)cXX#T2|&-Mu8? zd9Q?(YSpOfv^wS0ncuOE!L>b?VodxEO-~~K_~pktTxM!AKBY|g@};)##Ge*t$!#NX z$@nnGd9-VieLKE6c{to`z!?>CB_zfTH8z`uN?^-W@Xfjnu}5P9hhoh5W7qa4G5Kvx N)5Dj!D>&B!^#}KZMfCsx diff --git a/imageai/Detection/keras_retinanet/backend/__pycache__/common.cpython-35.pyc b/imageai/Detection/keras_retinanet/backend/__pycache__/common.cpython-35.pyc deleted file mode 100644 index 12eda25b4de4ac2bda78e720af54ba3ea1c1d468..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2768 zcma)8&2HO95MEN$AN`j&j^zMFvoH!IDjYd+fwoAErmmf!f>SxLoCXmD2rF_WF_uVn zmx`o7IyrrT-r6_lwYNS(UxB9rJ^2-SX}?`j{zF}q;{MHioSm7SnN?w7!T$K(kM-6Z zA%Bt!kA?YHXz~R*myjBIgSaN?8l+|tH$_~FbW@~fk(xz}gqJ3@i~^}QP23E5Z<1P; zxOj8qJ*4Cmt6I#nZIcjtHwZ__g5K{$Q79j8Zno62b5e?<>H1!dfqWVUL17?XeN{gNXJe zhX7B~me=K!4;>yx)blBJ3cDWj9Zm;c)X|$HB1-n_L_pAt7_bcJa72UFMHFR`ZBsnT z0vIqD3`$IESqemZvpcmxZay#XKCM)rK7gjS{nGDpDXHM!_dNjvn|HJs0>=sfC+B9o{WUo>_?Xzl;}=j=|uos#C*EGQ>EqVI{kX5 zTCQ%`ugXW?9K1ZDuXYX(cPdBar&W4zNOun^d*!3@K?SR4bf@x~?w2cj8%X46n-|n+Er|sY^7d{N3As0PQs>72|&9%E; z&x=^3*XQSbb`+-wOHw&C&=-w3*Wv^3f-nrtE1#t2D5j&nqOs3RG{ ztlzdpCMFppo~PXHS^R>MB8rG*|v!c9&u`tP91D{G&@Js^7SeWFm zfhRr!+)CgW3lsYqc;Xho(+M17VKPU?uo5b1gXhT@#$ZQn*8+(Md1H__I7FMA7#cKL zyg;4PRDdT-WIV5m(Rh)ptW9o^@q%70snyL3yuxJp00>UKjI&MV z9CP1|Q^>41Eh8+8x8rm(7;+hBT;7d@ks3U9H4p*gA*3wQS8pG)@ zicNQjHmaHK0IgHAk*;oI3OS#TY8g!*Bx9$xpx1iW#_-Xl<>>NqxOJJkmF%Iv{e_5| zu=B6(?&JDPiRxVcHjrG>gDz`vf57T{fz$7CKa%yGa=qL`QNZ{~>EYSjQ#9t)_jtq| zRS)XgQ4PUS0GZFDdg8GRyK8rl$jH&}Bxr3UKP*dcD zkQ38LmWUO+SGa~zKvu4x#9S4HB)kg#f1)fBp>QBj5tb&Yo2q`H3?=fBMo6wKX&Z`r zzAxxHm=pRL9=W1LO?wsaxYqG40zN&HiM&j!6g#INH%&!Rr3CdU*;{AqjGCQMu`_B8 zk{5p*GNcW!nKPC>n*alw@FxPuE11N~L{+SF8=(?3&yW=bhbk}?q@2pncl-Kwejx(4 zk4s*5yq3B#apQLaA!(DTKNz?kWru})cDMIfs4gX{Zdex|dGENEqH|s6bDVagQOM8I zT4WF?r)9?41Tr`4KBLT<8acwDa z!;GeLcG@kg4X`QP^eLut9UZaujeYaBv1Y89c_XENc_VKwBY9TML*uR~6s(w!vyFxu zIE_YaK__h~NmcEKIO~pl*7KaH#c61U)HVH3XPuUCK@U|$gjOcE-NV^+`Ik!|N#&o6 NId9xB=Tpmf{{dK^nBf2b diff --git a/imageai/Detection/keras_retinanet/backend/__pycache__/dynamic.cpython-35.pyc b/imageai/Detection/keras_retinanet/backend/__pycache__/dynamic.cpython-35.pyc deleted file mode 100644 index f9d7ff43d41fcc0affb4c4adc9a8dee49a68518d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmYjNyH3L}6unN;HdF*e2M}8~)D9IATZIs%?NA{=;x$B;kZEoqB(ag5Qo&gMfWP3E zyfXC{n7Dxyu9%1yu;L{&h)P`onO zqD3ZZebK5dCa4TCU2z+PR$1VD0XQ&& AuK)l5 diff --git a/imageai/Detection/keras_retinanet/backend/__pycache__/tensorflow_backend.cpython-35.pyc b/imageai/Detection/keras_retinanet/backend/__pycache__/tensorflow_backend.cpython-35.pyc deleted file mode 100644 index c51b629b2813eb1b183c8be6fb39921d80b76a8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1782 zcmbVLQE%f!5Z<^+n(ifc2PcGhKpH9`E~rh*@c=qbh|u?yG6O`)6UnkjKq>9A23eOF(HH9iYHk_kGR)3bpcc(6 zV3tzA%PBwsWm%3`&2pr4yq8>RFniDZgQLmpXcw7MdYUD|7|`OULTVH^UjfUJN5tmH zE@34o4J_6I&#ZDhOD(O;;vFz*VN0e3<Y#T*wF&WpTk`0kL`|bcRA87dn;34bLDoP7|3*%WNHA z&--c=f8g?xN#v}%+){7gJ;mERKw}Ac*&;7J@`jK%E%FP&t!qt&8x^&% z!1b~G#|=0a-wLjm3bTy0yiwc$s}CD!F1{67r$lKt&h0Li-fcj+cu1?wFytxii^DdgM*KhSV`t9rA-=S%W A>Hq)$ diff --git a/imageai/Detection/keras_retinanet/backend/backend.py b/imageai/Detection/keras_retinanet/backend/backend.py new file mode 100644 index 00000000..129d9652 --- /dev/null +++ b/imageai/Detection/keras_retinanet/backend/backend.py @@ -0,0 +1,119 @@ +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import tensorflow +from tensorflow import keras + + +def bbox_transform_inv(boxes, deltas, mean=None, std=None): + """ Applies deltas (usually regression results) to boxes (usually anchors). + + Before applying the deltas to the boxes, the normalization that was previously applied (in the generator) has to be removed. + The mean and std are the mean and std as applied in the generator. They are unnormalized in this function and then applied to the boxes. + + Args + boxes : np.array of shape (B, N, 4), where B is the batch size, N the number of boxes and 4 values for (x1, y1, x2, y2). + deltas: np.array of same shape as boxes. These deltas (d_x1, d_y1, d_x2, d_y2) are a factor of the width/height. + mean : The mean value used when computing deltas (defaults to [0, 0, 0, 0]). + std : The standard deviation used when computing deltas (defaults to [0.2, 0.2, 0.2, 0.2]). + + Returns + A np.array of the same shape as boxes, but with deltas applied to each box. + The mean and std are used during training to normalize the regression values (networks love normalization). + """ + if mean is None: + mean = [0, 0, 0, 0] + if std is None: + std = [0.2, 0.2, 0.2, 0.2] + + width = boxes[:, :, 2] - boxes[:, :, 0] + height = boxes[:, :, 3] - boxes[:, :, 1] + + x1 = boxes[:, :, 0] + (deltas[:, :, 0] * std[0] + mean[0]) * width + y1 = boxes[:, :, 1] + (deltas[:, :, 1] * std[1] + mean[1]) * height + x2 = boxes[:, :, 2] + (deltas[:, :, 2] * std[2] + mean[2]) * width + y2 = boxes[:, :, 3] + (deltas[:, :, 3] * std[3] + mean[3]) * height + + pred_boxes = keras.backend.stack([x1, y1, x2, y2], axis=2) + + return pred_boxes + + +def shift(shape, stride, anchors): + """ Produce shifted anchors based on shape of the map and stride size. + + Args + shape : Shape to shift the anchors over. + stride : Stride to shift the anchors with over the shape. + anchors: The anchors to apply at each location. + """ + shift_x = (keras.backend.arange(0, shape[1], dtype=keras.backend.floatx()) + keras.backend.constant(0.5, dtype=keras.backend.floatx())) * stride + shift_y = (keras.backend.arange(0, shape[0], dtype=keras.backend.floatx()) + keras.backend.constant(0.5, dtype=keras.backend.floatx())) * stride + + shift_x, shift_y = tensorflow.meshgrid(shift_x, shift_y) + shift_x = keras.backend.reshape(shift_x, [-1]) + shift_y = keras.backend.reshape(shift_y, [-1]) + + shifts = keras.backend.stack([ + shift_x, + shift_y, + shift_x, + shift_y + ], axis=0) + + shifts = keras.backend.transpose(shifts) + number_of_anchors = keras.backend.shape(anchors)[0] + + k = keras.backend.shape(shifts)[0] # number of base points = feat_h * feat_w + + shifted_anchors = keras.backend.reshape(anchors, [1, number_of_anchors, 4]) + keras.backend.cast(keras.backend.reshape(shifts, [k, 1, 4]), keras.backend.floatx()) + shifted_anchors = keras.backend.reshape(shifted_anchors, [k * number_of_anchors, 4]) + + return shifted_anchors + + +def map_fn(*args, **kwargs): + """ See https://www.tensorflow.org/api_docs/python/tf/map_fn . + """ + + if "shapes" in kwargs: + shapes = kwargs.pop("shapes") + dtype = kwargs.pop("dtype") + sig = [tensorflow.TensorSpec(shapes[i], dtype=t) for i, t in + enumerate(dtype)] + + # Try to use the new feature fn_output_signature in TF 2.3, use fallback if this is not available + try: + return tensorflow.map_fn(*args, **kwargs, fn_output_signature=sig) + except TypeError: + kwargs["dtype"] = dtype + + return tensorflow.map_fn(*args, **kwargs) + + +def resize_images(images, size, method='bilinear', align_corners=False): + """ See https://www.tensorflow.org/versions/r1.14/api_docs/python/tf/image/resize_images . + + Args + method: The method used for interpolation. One of ('bilinear', 'nearest', 'bicubic', 'area'). + """ + methods = { + 'bilinear': tensorflow.image.ResizeMethod.BILINEAR, + 'nearest' : tensorflow.image.ResizeMethod.NEAREST_NEIGHBOR, + 'bicubic' : tensorflow.image.ResizeMethod.BICUBIC, + 'area' : tensorflow.image.ResizeMethod.AREA, + } + return tensorflow.compat.v1.image.resize_images(images, size, methods[method], align_corners) diff --git a/imageai/Detection/keras_retinanet/backend/common.py b/imageai/Detection/keras_retinanet/backend/common.py deleted file mode 100644 index bce3ba37..00000000 --- a/imageai/Detection/keras_retinanet/backend/common.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Copyright 2017-2018 Fizyr (https://fizyr.com) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import keras.backend -from .dynamic import meshgrid - -import numpy as np - - -def bbox_transform_inv(boxes, deltas, mean=None, std=None): - if mean is None: - mean = [0, 0, 0, 0] - if std is None: - std = [0.1, 0.1, 0.2, 0.2] - - widths = boxes[:, :, 2] - boxes[:, :, 0] - heights = boxes[:, :, 3] - boxes[:, :, 1] - ctr_x = boxes[:, :, 0] + 0.5 * widths - ctr_y = boxes[:, :, 1] + 0.5 * heights - - dx = deltas[:, :, 0] * std[0] + mean[0] - dy = deltas[:, :, 1] * std[1] + mean[1] - dw = deltas[:, :, 2] * std[2] + mean[2] - dh = deltas[:, :, 3] * std[3] + mean[3] - - pred_ctr_x = ctr_x + dx * widths - pred_ctr_y = ctr_y + dy * heights - pred_w = keras.backend.exp(dw) * widths - pred_h = keras.backend.exp(dh) * heights - - pred_boxes_x1 = pred_ctr_x - 0.5 * pred_w - pred_boxes_y1 = pred_ctr_y - 0.5 * pred_h - pred_boxes_x2 = pred_ctr_x + 0.5 * pred_w - pred_boxes_y2 = pred_ctr_y + 0.5 * pred_h - - pred_boxes = keras.backend.stack([pred_boxes_x1, pred_boxes_y1, pred_boxes_x2, pred_boxes_y2], axis=2) - - return pred_boxes - - -def shift(shape, stride, anchors): - """ - Produce shifted anchors based on shape of the map and stride size - """ - shift_x = (keras.backend.arange(0, shape[1], dtype=keras.backend.floatx()) + keras.backend.constant(0.5, dtype=keras.backend.floatx())) * stride - shift_y = (keras.backend.arange(0, shape[0], dtype=keras.backend.floatx()) + keras.backend.constant(0.5, dtype=keras.backend.floatx())) * stride - - shift_x, shift_y = meshgrid(shift_x, shift_y) - shift_x = keras.backend.reshape(shift_x, [-1]) - shift_y = keras.backend.reshape(shift_y, [-1]) - - shifts = keras.backend.stack([ - shift_x, - shift_y, - shift_x, - shift_y - ], axis=0) - - shifts = keras.backend.transpose(shifts) - number_of_anchors = keras.backend.shape(anchors)[0] - - k = keras.backend.shape(shifts)[0] # number of base points = feat_h * feat_w - - shifted_anchors = keras.backend.reshape(anchors, [1, number_of_anchors, 4]) + keras.backend.cast(keras.backend.reshape(shifts, [k, 1, 4]), keras.backend.floatx()) - shifted_anchors = keras.backend.reshape(shifted_anchors, [k * number_of_anchors, 4]) - - return shifted_anchors diff --git a/imageai/Detection/keras_retinanet/backend/dynamic.py b/imageai/Detection/keras_retinanet/backend/dynamic.py deleted file mode 100644 index cf0b2e6a..00000000 --- a/imageai/Detection/keras_retinanet/backend/dynamic.py +++ /dev/null @@ -1,19 +0,0 @@ -import os - -_BACKEND = "tensorflow" - -if "KERAS_BACKEND" in os.environ: - _backend = os.environ["KERAS_BACKEND"] - - backends = { - "tensorflow" - } - - assert _backend in backends - - _BACKEND = _backend - -if _BACKEND == "tensorflow": - from .tensorflow_backend import * -else: - raise ValueError("Unknown backend: " + str(_BACKEND)) diff --git a/imageai/Detection/keras_retinanet/backend/tensorflow_backend.py b/imageai/Detection/keras_retinanet/backend/tensorflow_backend.py deleted file mode 100644 index 522f13a9..00000000 --- a/imageai/Detection/keras_retinanet/backend/tensorflow_backend.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Copyright 2017-2018 Fizyr (https://fizyr.com) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import tensorflow -import keras - - -def resize_images(*args, **kwargs): - return tensorflow.image.resize_images(*args, **kwargs) - - -def non_max_suppression(*args, **kwargs): - return tensorflow.image.non_max_suppression(*args, **kwargs) - - -def range(*args, **kwargs): - return tensorflow.range(*args, **kwargs) - - -def scatter_nd(*args, **kwargs): - return tensorflow.scatter_nd(*args, **kwargs) - - -def gather_nd(*args, **kwargs): - return tensorflow.gather_nd(*args, **kwargs) - - -def meshgrid(*args, **kwargs): - return tensorflow.meshgrid(*args, **kwargs) - - -def where(*args, **kwargs): - return tensorflow.where(*args, **kwargs) diff --git a/imageai/Detection/keras_retinanet/bin/__init__.py b/imageai/Detection/keras_retinanet/bin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/imageai/Detection/keras_retinanet/bin/convert_model.py b/imageai/Detection/keras_retinanet/bin/convert_model.py new file mode 100644 index 00000000..381fb157 --- /dev/null +++ b/imageai/Detection/keras_retinanet/bin/convert_model.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +import os +import sys + +# Allow relative imports when being executed as script. +if __name__ == "__main__" and __package__ is None: + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + import keras_retinanet.bin # noqa: F401 + __package__ = "keras_retinanet.bin" + +# Change these to absolute imports if you copy this script outside the keras_retinanet package. +from .. import models +from ..utils.config import read_config_file, parse_anchor_parameters, parse_pyramid_levels +from ..utils.gpu import setup_gpu +from ..utils.tf_version import check_tf_version + + +def parse_args(args): + parser = argparse.ArgumentParser(description='Script for converting a training model to an inference model.') + + parser.add_argument('model_in', help='The model to convert.') + parser.add_argument('model_out', help='Path to save the converted model to.') + parser.add_argument('--backbone', help='The backbone of the model to convert.', default='resnet50') + parser.add_argument('--no-nms', help='Disables non maximum suppression.', dest='nms', action='store_false') + parser.add_argument('--no-class-specific-filter', help='Disables class specific filtering.', dest='class_specific_filter', action='store_false') + parser.add_argument('--config', help='Path to a configuration parameters .ini file.') + parser.add_argument('--nms-threshold', help='Value for non maximum suppression threshold.', type=float, default=0.5) + parser.add_argument('--score-threshold', help='Threshold for prefiltering boxes.', type=float, default=0.05) + parser.add_argument('--max-detections', help='Maximum number of detections to keep.', type=int, default=300) + parser.add_argument('--parallel-iterations', help='Number of batch items to process in parallel.', type=int, default=32) + + return parser.parse_args(args) + + +def main(args=None): + # parse arguments + if args is None: + args = sys.argv[1:] + args = parse_args(args) + + # make sure tensorflow is the minimum required version + check_tf_version() + + # set modified tf session to avoid using the GPUs + setup_gpu('cpu') + + # optionally load config parameters + anchor_parameters = None + pyramid_levels = None + if args.config: + args.config = read_config_file(args.config) + if 'anchor_parameters' in args.config: + anchor_parameters = parse_anchor_parameters(args.config) + + if 'pyramid_levels' in args.config: + pyramid_levels = parse_pyramid_levels(args.config) + + # load the model + model = models.load_model(args.model_in, backbone_name=args.backbone) + + # check if this is indeed a training model + models.check_training_model(model) + + # convert the model + model = models.convert_model( + model, + nms=args.nms, + class_specific_filter=args.class_specific_filter, + anchor_params=anchor_parameters, + pyramid_levels=pyramid_levels, + nms_threshold=args.nms_threshold, + score_threshold=args.score_threshold, + max_detections=args.max_detections, + parallel_iterations=args.parallel_iterations + ) + + # save model + model.save(args.model_out) + + +if __name__ == '__main__': + main() diff --git a/imageai/Detection/keras_retinanet/bin/debug.py b/imageai/Detection/keras_retinanet/bin/debug.py new file mode 100644 index 00000000..39185a95 --- /dev/null +++ b/imageai/Detection/keras_retinanet/bin/debug.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python + +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +import os +import sys +import cv2 + +# Set keycodes for changing images +# 81, 83 are left and right arrows on linux in Ascii code (probably not needed) +# 65361, 65363 are left and right arrows in linux +# 2424832, 2555904 are left and right arrows on Windows +# 110, 109 are 'n' and 'm' on mac, windows, linux +# (unfortunately arrow keys not picked up on mac) +leftkeys = (81, 110, 65361, 2424832) +rightkeys = (83, 109, 65363, 2555904) + +# Allow relative imports when being executed as script. +if __name__ == "__main__" and __package__ is None: + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + import keras_retinanet.bin # noqa: F401 + __package__ = "keras_retinanet.bin" + +# Change these to absolute imports if you copy this script outside the keras_retinanet package. +from ..preprocessing.pascal_voc import PascalVocGenerator +from ..preprocessing.csv_generator import CSVGenerator +from ..preprocessing.kitti import KittiGenerator +from ..preprocessing.open_images import OpenImagesGenerator +from ..utils.anchors import anchors_for_shape, compute_gt_annotations +from ..utils.config import read_config_file, parse_anchor_parameters, parse_pyramid_levels +from ..utils.image import random_visual_effect_generator +from ..utils.tf_version import check_tf_version +from ..utils.transform import random_transform_generator +from ..utils.visualization import draw_annotations, draw_boxes, draw_caption + + +def create_generator(args): + """ Create the data generators. + + Args: + args: parseargs arguments object. + """ + common_args = { + 'config' : args.config, + 'image_min_side' : args.image_min_side, + 'image_max_side' : args.image_max_side, + 'group_method' : args.group_method + } + + # create random transform generator for augmenting training data + transform_generator = random_transform_generator( + min_rotation=-0.1, + max_rotation=0.1, + min_translation=(-0.1, -0.1), + max_translation=(0.1, 0.1), + min_shear=-0.1, + max_shear=0.1, + min_scaling=(0.9, 0.9), + max_scaling=(1.1, 1.1), + flip_x_chance=0.5, + flip_y_chance=0.5, + ) + + visual_effect_generator = random_visual_effect_generator( + contrast_range=(0.9, 1.1), + brightness_range=(-.1, .1), + hue_range=(-0.05, 0.05), + saturation_range=(0.95, 1.05) + ) + + if args.dataset_type == 'coco': + # import here to prevent unnecessary dependency on cocoapi + from ..preprocessing.coco import CocoGenerator + + generator = CocoGenerator( + args.coco_path, + args.coco_set, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + elif args.dataset_type == 'pascal': + generator = PascalVocGenerator( + args.pascal_path, + args.pascal_set, + image_extension=args.image_extension, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + elif args.dataset_type == 'csv': + generator = CSVGenerator( + args.annotations, + args.classes, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + elif args.dataset_type == 'oid': + generator = OpenImagesGenerator( + args.main_dir, + subset=args.subset, + version=args.version, + labels_filter=args.labels_filter, + parent_label=args.parent_label, + annotation_cache_dir=args.annotation_cache_dir, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + elif args.dataset_type == 'kitti': + generator = KittiGenerator( + args.kitti_path, + subset=args.subset, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + else: + raise ValueError('Invalid data type received: {}'.format(args.dataset_type)) + + return generator + + +def parse_args(args): + """ Parse the arguments. + """ + parser = argparse.ArgumentParser(description='Debug script for a RetinaNet network.') + subparsers = parser.add_subparsers(help='Arguments for specific dataset types.', dest='dataset_type') + subparsers.required = True + + coco_parser = subparsers.add_parser('coco') + coco_parser.add_argument('coco_path', help='Path to dataset directory (ie. /tmp/COCO).') + coco_parser.add_argument('--coco-set', help='Name of the set to show (defaults to val2017).', default='val2017') + + pascal_parser = subparsers.add_parser('pascal') + pascal_parser.add_argument('pascal_path', help='Path to dataset directory (ie. /tmp/VOCdevkit).') + pascal_parser.add_argument('--pascal-set', help='Name of the set to show (defaults to test).', default='test') + pascal_parser.add_argument('--image-extension', help='Declares the dataset images\' extension.', default='.jpg') + + kitti_parser = subparsers.add_parser('kitti') + kitti_parser.add_argument('kitti_path', help='Path to dataset directory (ie. /tmp/kitti).') + kitti_parser.add_argument('subset', help='Argument for loading a subset from train/val.') + + def csv_list(string): + return string.split(',') + + oid_parser = subparsers.add_parser('oid') + oid_parser.add_argument('main_dir', help='Path to dataset directory.') + oid_parser.add_argument('subset', help='Argument for loading a subset from train/validation/test.') + oid_parser.add_argument('--version', help='The current dataset version is v4.', default='v4') + oid_parser.add_argument('--labels-filter', help='A list of labels to filter.', type=csv_list, default=None) + oid_parser.add_argument('--annotation-cache-dir', help='Path to store annotation cache.', default='.') + oid_parser.add_argument('--parent-label', help='Use the hierarchy children of this label.', default=None) + + csv_parser = subparsers.add_parser('csv') + csv_parser.add_argument('annotations', help='Path to CSV file containing annotations for evaluation.') + csv_parser.add_argument('classes', help='Path to a CSV file containing class label mapping.') + + parser.add_argument('--no-resize', help='Disable image resizing.', dest='resize', action='store_false') + parser.add_argument('--anchors', help='Show positive anchors on the image.', action='store_true') + parser.add_argument('--display-name', help='Display image name on the bottom left corner.', action='store_true') + parser.add_argument('--show-annotations', help='Show annotations on the image. Green annotations have anchors, red annotations don\'t and therefore don\'t contribute to training.', action='store_true') + parser.add_argument('--random-transform', help='Randomly transform image and annotations.', action='store_true') + parser.add_argument('--image-min-side', help='Rescale the image so the smallest side is min_side.', type=int, default=800) + parser.add_argument('--image-max-side', help='Rescale the image if the largest side is larger than max_side.', type=int, default=1333) + parser.add_argument('--config', help='Path to a configuration parameters .ini file.') + parser.add_argument('--no-gui', help='Do not open a GUI window. Save images to an output directory instead.', action='store_true') + parser.add_argument('--output-dir', help='The output directory to save images to if --no-gui is specified.', default='.') + parser.add_argument('--flatten-output', help='Flatten the folder structure of saved output images into a single folder.', action='store_true') + parser.add_argument('--group-method', help='Determines how images are grouped together', type=str, default='ratio', choices=['none', 'random', 'ratio']) + + return parser.parse_args(args) + + +def run(generator, args, anchor_params, pyramid_levels): + """ Main loop. + + Args + generator: The generator to debug. + args: parseargs args object. + """ + # display images, one at a time + i = 0 + while True: + # load the data + image = generator.load_image(i) + annotations = generator.load_annotations(i) + if len(annotations['labels']) > 0 : + # apply random transformations + if args.random_transform: + image, annotations = generator.random_transform_group_entry(image, annotations) + image, annotations = generator.random_visual_effect_group_entry(image, annotations) + + # resize the image and annotations + if args.resize: + image, image_scale = generator.resize_image(image) + annotations['bboxes'] *= image_scale + + anchors = anchors_for_shape(image.shape, anchor_params=anchor_params, pyramid_levels=pyramid_levels) + positive_indices, _, max_indices = compute_gt_annotations(anchors, annotations['bboxes']) + + # draw anchors on the image + if args.anchors: + draw_boxes(image, anchors[positive_indices], (255, 255, 0), thickness=1) + + # draw annotations on the image + if args.show_annotations: + # draw annotations in red + draw_annotations(image, annotations, color=(0, 0, 255), label_to_name=generator.label_to_name) + + # draw regressed anchors in green to override most red annotations + # result is that annotations without anchors are red, with anchors are green + draw_boxes(image, annotations['bboxes'][max_indices[positive_indices], :], (0, 255, 0)) + + # display name on the image + if args.display_name: + draw_caption(image, [0, image.shape[0]], os.path.basename(generator.image_path(i))) + + # write to file and advance if no-gui selected + if args.no_gui: + output_path = make_output_path(args.output_dir, generator.image_path(i), flatten=args.flatten_output) + os.makedirs(os.path.dirname(output_path), exist_ok=True) + cv2.imwrite(output_path, image) + i += 1 + if i == generator.size(): # have written all images + break + else: + continue + + # if we are using the GUI, then show an image + cv2.imshow('Image', image) + key = cv2.waitKeyEx() + + # press right for next image and left for previous (linux or windows, doesn't work for macOS) + # if you run macOS, press "n" or "m" (will also work on linux and windows) + + if key in rightkeys: + i = (i + 1) % generator.size() + if key in leftkeys: + i -= 1 + if i < 0: + i = generator.size() - 1 + + # press q or Esc to quit + if (key == ord('q')) or (key == 27): + return False + + return True + + +def make_output_path(output_dir, image_path, flatten = False): + """ Compute the output path for a debug image. """ + + # If the output hierarchy is flattened to a single folder, throw away all leading folders. + if flatten: + path = os.path.basename(image_path) + + # Otherwise, make sure absolute paths are taken relative to the filesystem root. + else: + # Make sure to drop drive letters on Windows, otherwise relpath wil fail. + _, path = os.path.splitdrive(image_path) + if os.path.isabs(path): + path = os.path.relpath(path, '/') + + # In all cases, append "_debug" to the filename, before the extension. + base, extension = os.path.splitext(path) + path = base + "_debug" + extension + + # Finally, join the whole thing to the output directory. + return os.path.join(output_dir, path) + + +def main(args=None): + # parse arguments + if args is None: + args = sys.argv[1:] + args = parse_args(args) + + # make sure tensorflow is the minimum required version + check_tf_version() + + # create the generator + generator = create_generator(args) + + # optionally load config parameters + if args.config: + args.config = read_config_file(args.config) + + # optionally load anchor parameters + anchor_params = None + if args.config and 'anchor_parameters' in args.config: + anchor_params = parse_anchor_parameters(args.config) + + pyramid_levels = None + if args.config and 'pyramid_levels' in args.config: + pyramid_levels = parse_pyramid_levels(args.config) + # create the display window if necessary + if not args.no_gui: + cv2.namedWindow('Image', cv2.WINDOW_NORMAL) + + run(generator, args, anchor_params=anchor_params, pyramid_levels=pyramid_levels) + + +if __name__ == '__main__': + main() diff --git a/imageai/Detection/keras_retinanet/bin/evaluate.py b/imageai/Detection/keras_retinanet/bin/evaluate.py new file mode 100644 index 00000000..d0a7f88d --- /dev/null +++ b/imageai/Detection/keras_retinanet/bin/evaluate.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python + +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +import os +import sys + +# Allow relative imports when being executed as script. +if __name__ == "__main__" and __package__ is None: + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + import keras_retinanet.bin # noqa: F401 + __package__ = "keras_retinanet.bin" + +# Change these to absolute imports if you copy this script outside the keras_retinanet package. +from .. import models +from ..preprocessing.csv_generator import CSVGenerator +from ..preprocessing.pascal_voc import PascalVocGenerator +from ..utils.anchors import make_shapes_callback +from ..utils.config import read_config_file, parse_anchor_parameters, parse_pyramid_levels +from ..utils.eval import evaluate +from ..utils.gpu import setup_gpu +from ..utils.tf_version import check_tf_version + + +def create_generator(args, preprocess_image): + """ Create generators for evaluation. + """ + common_args = { + 'config' : args.config, + 'image_min_side' : args.image_min_side, + 'image_max_side' : args.image_max_side, + 'no_resize' : args.no_resize, + 'preprocess_image' : preprocess_image, + 'group_method' : args.group_method + } + + if args.dataset_type == 'coco': + # import here to prevent unnecessary dependency on cocoapi + from ..preprocessing.coco import CocoGenerator + + validation_generator = CocoGenerator( + args.coco_path, + 'val2017', + shuffle_groups=False, + **common_args + ) + elif args.dataset_type == 'pascal': + validation_generator = PascalVocGenerator( + args.pascal_path, + 'test', + image_extension=args.image_extension, + shuffle_groups=False, + **common_args + ) + elif args.dataset_type == 'csv': + validation_generator = CSVGenerator( + args.annotations, + args.classes, + shuffle_groups=False, + **common_args + ) + else: + raise ValueError('Invalid data type received: {}'.format(args.dataset_type)) + + return validation_generator + + +def parse_args(args): + """ Parse the arguments. + """ + parser = argparse.ArgumentParser(description='Evaluation script for a RetinaNet network.') + subparsers = parser.add_subparsers(help='Arguments for specific dataset types.', dest='dataset_type') + subparsers.required = True + + coco_parser = subparsers.add_parser('coco') + coco_parser.add_argument('coco_path', help='Path to dataset directory (ie. /tmp/COCO).') + + pascal_parser = subparsers.add_parser('pascal') + pascal_parser.add_argument('pascal_path', help='Path to dataset directory (ie. /tmp/VOCdevkit).') + pascal_parser.add_argument('--image-extension', help='Declares the dataset images\' extension.', default='.jpg') + + csv_parser = subparsers.add_parser('csv') + csv_parser.add_argument('annotations', help='Path to CSV file containing annotations for evaluation.') + csv_parser.add_argument('classes', help='Path to a CSV file containing class label mapping.') + + parser.add_argument('model', help='Path to RetinaNet model.') + parser.add_argument('--convert-model', help='Convert the model to an inference model (ie. the input is a training model).', action='store_true') + parser.add_argument('--backbone', help='The backbone of the model.', default='resnet50') + parser.add_argument('--gpu', help='Id of the GPU to use (as reported by nvidia-smi).') + parser.add_argument('--score-threshold', help='Threshold on score to filter detections with (defaults to 0.05).', default=0.05, type=float) + parser.add_argument('--iou-threshold', help='IoU Threshold to count for a positive detection (defaults to 0.5).', default=0.5, type=float) + parser.add_argument('--max-detections', help='Max Detections per image (defaults to 100).', default=100, type=int) + parser.add_argument('--save-path', help='Path for saving images with detections (doesn\'t work for COCO).') + parser.add_argument('--image-min-side', help='Rescale the image so the smallest side is min_side.', type=int, default=800) + parser.add_argument('--image-max-side', help='Rescale the image if the largest side is larger than max_side.', type=int, default=1333) + parser.add_argument('--no-resize', help='Don''t rescale the image.', action='store_true') + parser.add_argument('--config', help='Path to a configuration parameters .ini file (only used with --convert-model).') + parser.add_argument('--group-method', help='Determines how images are grouped together', type=str, default='ratio', choices=['none', 'random', 'ratio']) + + return parser.parse_args(args) + + +def main(args=None): + # parse arguments + if args is None: + args = sys.argv[1:] + args = parse_args(args) + + # make sure tensorflow is the minimum required version + check_tf_version() + + # optionally choose specific GPU + if args.gpu: + setup_gpu(args.gpu) + + # make save path if it doesn't exist + if args.save_path is not None and not os.path.exists(args.save_path): + os.makedirs(args.save_path) + + # optionally load config parameters + if args.config: + args.config = read_config_file(args.config) + + # create the generator + backbone = models.backbone(args.backbone) + generator = create_generator(args, backbone.preprocess_image) + + # optionally load anchor parameters + anchor_params = None + pyramid_levels = None + if args.config and 'anchor_parameters' in args.config: + anchor_params = parse_anchor_parameters(args.config) + if args.config and 'pyramid_levels' in args.config: + pyramid_levels = parse_pyramid_levels(args.config) + + # load the model + print('Loading model, this may take a second...') + model = models.load_model(args.model, backbone_name=args.backbone) + generator.compute_shapes = make_shapes_callback(model) + + # optionally convert the model + if args.convert_model: + model = models.convert_model(model, anchor_params=anchor_params, pyramid_levels=pyramid_levels) + + # print model summary + # print(model.summary()) + + # start evaluation + if args.dataset_type == 'coco': + from ..utils.coco_eval import evaluate_coco + evaluate_coco(generator, model, args.score_threshold) + else: + average_precisions, inference_time = evaluate( + generator, + model, + iou_threshold=args.iou_threshold, + score_threshold=args.score_threshold, + max_detections=args.max_detections, + save_path=args.save_path + ) + + # print evaluation + total_instances = [] + precisions = [] + for label, (average_precision, num_annotations) in average_precisions.items(): + print('{:.0f} instances of class'.format(num_annotations), + generator.label_to_name(label), 'with average precision: {:.4f}'.format(average_precision)) + total_instances.append(num_annotations) + precisions.append(average_precision) + + if sum(total_instances) == 0: + print('No test instances found.') + return + + print('Inference time for {:.0f} images: {:.4f}'.format(generator.size(), inference_time)) + + print('mAP using the weighted average of precisions among classes: {:.4f}'.format(sum([a * b for a, b in zip(total_instances, precisions)]) / sum(total_instances))) + print('mAP: {:.4f}'.format(sum(precisions) / sum(x > 0 for x in total_instances))) + + +if __name__ == '__main__': + main() diff --git a/imageai/Detection/keras_retinanet/bin/train.py b/imageai/Detection/keras_retinanet/bin/train.py new file mode 100644 index 00000000..2ec4792f --- /dev/null +++ b/imageai/Detection/keras_retinanet/bin/train.py @@ -0,0 +1,553 @@ +#!/usr/bin/env python + +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +import os +import sys +import warnings + +from tensorflow import keras +import tensorflow as tf + +# Allow relative imports when being executed as script. +if __name__ == "__main__" and __package__ is None: + sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) + import keras_retinanet.bin # noqa: F401 + __package__ = "keras_retinanet.bin" + +# Change these to absolute imports if you copy this script outside the keras_retinanet package. +from .. import layers # noqa: F401 +from .. import losses +from .. import models +from ..callbacks import RedirectModel +from ..callbacks.eval import Evaluate +from ..models.retinanet import retinanet_bbox +from ..preprocessing.csv_generator import CSVGenerator +from ..preprocessing.kitti import KittiGenerator +from ..preprocessing.open_images import OpenImagesGenerator +from ..preprocessing.pascal_voc import PascalVocGenerator +from ..utils.anchors import make_shapes_callback +from ..utils.config import read_config_file, parse_anchor_parameters, parse_pyramid_levels +from ..utils.gpu import setup_gpu +from ..utils.image import random_visual_effect_generator +from ..utils.model import freeze as freeze_model +from ..utils.tf_version import check_tf_version +from ..utils.transform import random_transform_generator + + +def makedirs(path): + # Intended behavior: try to create the directory, + # pass if the directory exists already, fails otherwise. + # Meant for Python 2.7/3.n compatibility. + try: + os.makedirs(path) + except OSError: + if not os.path.isdir(path): + raise + + +def model_with_weights(model, weights, skip_mismatch): + """ Load weights for model. + + Args + model : The model to load weights for. + weights : The weights to load. + skip_mismatch : If True, skips layers whose shape of weights doesn't match with the model. + """ + if weights is not None: + model.load_weights(weights, by_name=True, skip_mismatch=skip_mismatch) + return model + + +def create_models(backbone_retinanet, num_classes, weights, multi_gpu=0, + freeze_backbone=False, lr=1e-5, optimizer_clipnorm=0.001, config=None): + """ Creates three models (model, training_model, prediction_model). + + Args + backbone_retinanet : A function to call to create a retinanet model with a given backbone. + num_classes : The number of classes to train. + weights : The weights to load into the model. + multi_gpu : The number of GPUs to use for training. + freeze_backbone : If True, disables learning for the backbone. + config : Config parameters, None indicates the default configuration. + + Returns + model : The base model. This is also the model that is saved in snapshots. + training_model : The training model. If multi_gpu=0, this is identical to model. + prediction_model : The model wrapped with utility functions to perform object detection (applies regression values and performs NMS). + """ + + modifier = freeze_model if freeze_backbone else None + + # load anchor parameters, or pass None (so that defaults will be used) + anchor_params = None + num_anchors = None + pyramid_levels = None + if config and 'anchor_parameters' in config: + anchor_params = parse_anchor_parameters(config) + num_anchors = anchor_params.num_anchors() + if config and 'pyramid_levels' in config: + pyramid_levels = parse_pyramid_levels(config) + + # Keras recommends initialising a multi-gpu model on the CPU to ease weight sharing, and to prevent OOM errors. + # optionally wrap in a parallel model + if multi_gpu > 1: + from keras.utils import multi_gpu_model + with tf.device('/cpu:0'): + model = model_with_weights(backbone_retinanet(num_classes, num_anchors=num_anchors, modifier=modifier, pyramid_levels=pyramid_levels), weights=weights, skip_mismatch=True) + training_model = multi_gpu_model(model, gpus=multi_gpu) + else: + model = model_with_weights(backbone_retinanet(num_classes, num_anchors=num_anchors, modifier=modifier, pyramid_levels=pyramid_levels), weights=weights, skip_mismatch=True) + training_model = model + + # make prediction model + prediction_model = retinanet_bbox(model=model, anchor_params=anchor_params, pyramid_levels=pyramid_levels) + + # compile model + training_model.compile( + loss={ + 'regression' : losses.smooth_l1(), + 'classification': losses.focal() + }, + optimizer=keras.optimizers.Adam(lr=lr, clipnorm=optimizer_clipnorm) + ) + + return model, training_model, prediction_model + + +def create_callbacks(model, training_model, prediction_model, validation_generator, args): + """ Creates the callbacks to use during training. + + Args + model: The base model. + training_model: The model that is used for training. + prediction_model: The model that should be used for validation. + validation_generator: The generator for creating validation data. + args: parseargs args object. + + Returns: + A list of callbacks used for training. + """ + callbacks = [] + + tensorboard_callback = None + + if args.tensorboard_dir: + makedirs(args.tensorboard_dir) + update_freq = args.tensorboard_freq + if update_freq not in ['epoch', 'batch']: + update_freq = int(update_freq) + tensorboard_callback = keras.callbacks.TensorBoard( + log_dir = args.tensorboard_dir, + histogram_freq = 0, + batch_size = args.batch_size, + write_graph = True, + write_grads = False, + write_images = False, + update_freq = update_freq, + embeddings_freq = 0, + embeddings_layer_names = None, + embeddings_metadata = None + ) + + if args.evaluation and validation_generator: + if args.dataset_type == 'coco': + from ..callbacks.coco import CocoEval + + # use prediction model for evaluation + evaluation = CocoEval(validation_generator, tensorboard=tensorboard_callback) + else: + evaluation = Evaluate(validation_generator, tensorboard=tensorboard_callback, weighted_average=args.weighted_average) + evaluation = RedirectModel(evaluation, prediction_model) + callbacks.append(evaluation) + + # save the model + if args.snapshots: + # ensure directory created first; otherwise h5py will error after epoch. + makedirs(args.snapshot_path) + checkpoint = keras.callbacks.ModelCheckpoint( + os.path.join( + args.snapshot_path, + '{backbone}_{dataset_type}_{{epoch:02d}}.h5'.format(backbone=args.backbone, dataset_type=args.dataset_type) + ), + verbose=1, + # save_best_only=True, + # monitor="mAP", + # mode='max' + ) + checkpoint = RedirectModel(checkpoint, model) + callbacks.append(checkpoint) + + callbacks.append(keras.callbacks.ReduceLROnPlateau( + monitor = 'loss', + factor = args.reduce_lr_factor, + patience = args.reduce_lr_patience, + verbose = 1, + mode = 'auto', + min_delta = 0.0001, + cooldown = 0, + min_lr = 0 + )) + + if args.evaluation and validation_generator: + callbacks.append(keras.callbacks.EarlyStopping( + monitor = 'mAP', + patience = 5, + mode = 'max', + min_delta = 0.01 + )) + + if args.tensorboard_dir: + callbacks.append(tensorboard_callback) + + return callbacks + + +def create_generators(args, preprocess_image): + """ Create generators for training and validation. + + Args + args : parseargs object containing configuration for generators. + preprocess_image : Function that preprocesses an image for the network. + """ + common_args = { + 'batch_size' : args.batch_size, + 'config' : args.config, + 'image_min_side' : args.image_min_side, + 'image_max_side' : args.image_max_side, + 'no_resize' : args.no_resize, + 'preprocess_image' : preprocess_image, + 'group_method' : args.group_method + } + + # create random transform generator for augmenting training data + if args.random_transform: + transform_generator = random_transform_generator( + min_rotation=-0.1, + max_rotation=0.1, + min_translation=(-0.1, -0.1), + max_translation=(0.1, 0.1), + min_shear=-0.1, + max_shear=0.1, + min_scaling=(0.9, 0.9), + max_scaling=(1.1, 1.1), + flip_x_chance=0.5, + flip_y_chance=0.5, + ) + visual_effect_generator = random_visual_effect_generator( + contrast_range=(0.9, 1.1), + brightness_range=(-.1, .1), + hue_range=(-0.05, 0.05), + saturation_range=(0.95, 1.05) + ) + else: + transform_generator = random_transform_generator(flip_x_chance=0.5) + visual_effect_generator = None + + if args.dataset_type == 'coco': + # import here to prevent unnecessary dependency on cocoapi + from ..preprocessing.coco import CocoGenerator + + train_generator = CocoGenerator( + args.coco_path, + 'train2017', + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + + validation_generator = CocoGenerator( + args.coco_path, + 'val2017', + shuffle_groups=False, + **common_args + ) + elif args.dataset_type == 'pascal': + train_generator = PascalVocGenerator( + args.pascal_path, + 'train', + image_extension=args.image_extension, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + + validation_generator = PascalVocGenerator( + args.pascal_path, + 'val', + image_extension=args.image_extension, + shuffle_groups=False, + **common_args + ) + elif args.dataset_type == 'csv': + train_generator = CSVGenerator( + args.annotations, + args.classes, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + + if args.val_annotations: + validation_generator = CSVGenerator( + args.val_annotations, + args.classes, + shuffle_groups=False, + **common_args + ) + else: + validation_generator = None + elif args.dataset_type == 'oid': + train_generator = OpenImagesGenerator( + args.main_dir, + subset='train', + version=args.version, + labels_filter=args.labels_filter, + annotation_cache_dir=args.annotation_cache_dir, + parent_label=args.parent_label, + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + + validation_generator = OpenImagesGenerator( + args.main_dir, + subset='validation', + version=args.version, + labels_filter=args.labels_filter, + annotation_cache_dir=args.annotation_cache_dir, + parent_label=args.parent_label, + shuffle_groups=False, + **common_args + ) + elif args.dataset_type == 'kitti': + train_generator = KittiGenerator( + args.kitti_path, + subset='train', + transform_generator=transform_generator, + visual_effect_generator=visual_effect_generator, + **common_args + ) + + validation_generator = KittiGenerator( + args.kitti_path, + subset='val', + shuffle_groups=False, + **common_args + ) + else: + raise ValueError('Invalid data type received: {}'.format(args.dataset_type)) + + return train_generator, validation_generator + + +def check_args(parsed_args): + """ Function to check for inherent contradictions within parsed arguments. + For example, batch_size < num_gpus + Intended to raise errors prior to backend initialisation. + + Args + parsed_args: parser.parse_args() + + Returns + parsed_args + """ + + if parsed_args.multi_gpu > 1 and parsed_args.batch_size < parsed_args.multi_gpu: + raise ValueError( + "Batch size ({}) must be equal to or higher than the number of GPUs ({})".format(parsed_args.batch_size, + parsed_args.multi_gpu)) + + if parsed_args.multi_gpu > 1 and parsed_args.snapshot: + raise ValueError( + "Multi GPU training ({}) and resuming from snapshots ({}) is not supported.".format(parsed_args.multi_gpu, + parsed_args.snapshot)) + + if parsed_args.multi_gpu > 1 and not parsed_args.multi_gpu_force: + raise ValueError("Multi-GPU support is experimental, use at own risk! Run with --multi-gpu-force if you wish to continue.") + + if 'resnet' not in parsed_args.backbone: + warnings.warn('Using experimental backbone {}. Only resnet50 has been properly tested.'.format(parsed_args.backbone)) + + return parsed_args + + +def parse_args(args): + """ Parse the arguments. + """ + parser = argparse.ArgumentParser(description='Simple training script for training a RetinaNet network.') + subparsers = parser.add_subparsers(help='Arguments for specific dataset types.', dest='dataset_type') + subparsers.required = True + + coco_parser = subparsers.add_parser('coco') + coco_parser.add_argument('coco_path', help='Path to dataset directory (ie. /tmp/COCO).') + + pascal_parser = subparsers.add_parser('pascal') + pascal_parser.add_argument('pascal_path', help='Path to dataset directory (ie. /tmp/VOCdevkit).') + pascal_parser.add_argument('--image-extension', help='Declares the dataset images\' extension.', default='.jpg') + + kitti_parser = subparsers.add_parser('kitti') + kitti_parser.add_argument('kitti_path', help='Path to dataset directory (ie. /tmp/kitti).') + + def csv_list(string): + return string.split(',') + + oid_parser = subparsers.add_parser('oid') + oid_parser.add_argument('main_dir', help='Path to dataset directory.') + oid_parser.add_argument('--version', help='The current dataset version is v4.', default='v4') + oid_parser.add_argument('--labels-filter', help='A list of labels to filter.', type=csv_list, default=None) + oid_parser.add_argument('--annotation-cache-dir', help='Path to store annotation cache.', default='.') + oid_parser.add_argument('--parent-label', help='Use the hierarchy children of this label.', default=None) + + csv_parser = subparsers.add_parser('csv') + csv_parser.add_argument('annotations', help='Path to CSV file containing annotations for training.') + csv_parser.add_argument('classes', help='Path to a CSV file containing class label mapping.') + csv_parser.add_argument('--val-annotations', help='Path to CSV file containing annotations for validation (optional).') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--snapshot', help='Resume training from a snapshot.') + group.add_argument('--imagenet-weights', help='Initialize the model with pretrained imagenet weights. This is the default behaviour.', action='store_const', const=True, default=True) + group.add_argument('--weights', help='Initialize the model with weights from a file.') + group.add_argument('--no-weights', help='Don\'t initialize the model with any weights.', dest='imagenet_weights', action='store_const', const=False) + parser.add_argument('--backbone', help='Backbone model used by retinanet.', default='resnet50', type=str) + parser.add_argument('--batch-size', help='Size of the batches.', default=1, type=int) + parser.add_argument('--gpu', help='Id of the GPU to use (as reported by nvidia-smi).') + parser.add_argument('--multi-gpu', help='Number of GPUs to use for parallel processing.', type=int, default=0) + parser.add_argument('--multi-gpu-force', help='Extra flag needed to enable (experimental) multi-gpu support.', action='store_true') + parser.add_argument('--initial-epoch', help='Epoch from which to begin the train, useful if resuming from snapshot.', type=int, default=0) + parser.add_argument('--epochs', help='Number of epochs to train.', type=int, default=50) + parser.add_argument('--steps', help='Number of steps per epoch.', type=int, default=10000) + parser.add_argument('--lr', help='Learning rate.', type=float, default=1e-5) + parser.add_argument('--optimizer-clipnorm', help='Clipnorm parameter for optimizer.', type=float, default=0.001) + parser.add_argument('--snapshot-path', help='Path to store snapshots of models during training (defaults to \'./snapshots\')', default='./snapshots') + parser.add_argument('--tensorboard-dir', help='Log directory for Tensorboard output', default='') # default='./logs') => https://github.com/tensorflow/tensorflow/pull/34870 + parser.add_argument('--tensorboard-freq', help='Update frequency for Tensorboard output. Values \'epoch\', \'batch\' or int', default='epoch') + parser.add_argument('--no-snapshots', help='Disable saving snapshots.', dest='snapshots', action='store_false') + parser.add_argument('--no-evaluation', help='Disable per epoch evaluation.', dest='evaluation', action='store_false') + parser.add_argument('--freeze-backbone', help='Freeze training of backbone layers.', action='store_true') + parser.add_argument('--random-transform', help='Randomly transform image and annotations.', action='store_true') + parser.add_argument('--image-min-side', help='Rescale the image so the smallest side is min_side.', type=int, default=800) + parser.add_argument('--image-max-side', help='Rescale the image if the largest side is larger than max_side.', type=int, default=1333) + parser.add_argument('--no-resize', help='Don''t rescale the image.', action='store_true') + parser.add_argument('--config', help='Path to a configuration parameters .ini file.') + parser.add_argument('--weighted-average', help='Compute the mAP using the weighted average of precisions among classes.', action='store_true') + parser.add_argument('--compute-val-loss', help='Compute validation loss during training', dest='compute_val_loss', action='store_true') + parser.add_argument('--reduce-lr-patience', help='Reduce learning rate after validation loss decreases over reduce_lr_patience epochs', type=int, default=2) + parser.add_argument('--reduce-lr-factor', help='When learning rate is reduced due to reduce_lr_patience, multiply by reduce_lr_factor', type=float, default=0.1) + parser.add_argument('--group-method', help='Determines how images are grouped together', type=str, default='ratio', choices=['none', 'random', 'ratio']) + + # Fit generator arguments + parser.add_argument('--multiprocessing', help='Use multiprocessing in fit_generator.', action='store_true') + parser.add_argument('--workers', help='Number of generator workers.', type=int, default=1) + parser.add_argument('--max-queue-size', help='Queue length for multiprocessing workers in fit_generator.', type=int, default=10) + + return check_args(parser.parse_args(args)) + + +def main(args=None): + # parse arguments + if args is None: + args = sys.argv[1:] + args = parse_args(args) + + # create object that stores backbone information + backbone = models.backbone(args.backbone) + + # make sure tensorflow is the minimum required version + check_tf_version() + + # optionally choose specific GPU + if args.gpu is not None: + setup_gpu(args.gpu) + + # optionally load config parameters + if args.config: + args.config = read_config_file(args.config) + + # create the generators + train_generator, validation_generator = create_generators(args, backbone.preprocess_image) + + # create the model + if args.snapshot is not None: + print('Loading model, this may take a second...') + model = models.load_model(args.snapshot, backbone_name=args.backbone) + training_model = model + anchor_params = None + pyramid_levels = None + if args.config and 'anchor_parameters' in args.config: + anchor_params = parse_anchor_parameters(args.config) + if args.config and 'pyramid_levels' in args.config: + pyramid_levels = parse_pyramid_levels(args.config) + + prediction_model = retinanet_bbox(model=model, anchor_params=anchor_params, pyramid_levels=pyramid_levels) + else: + weights = args.weights + # default to imagenet if nothing else is specified + if weights is None and args.imagenet_weights: + weights = backbone.download_imagenet() + + print('Creating model, this may take a second...') + model, training_model, prediction_model = create_models( + backbone_retinanet=backbone.retinanet, + num_classes=train_generator.num_classes(), + weights=weights, + multi_gpu=args.multi_gpu, + freeze_backbone=args.freeze_backbone, + lr=args.lr, + optimizer_clipnorm=args.optimizer_clipnorm, + config=args.config + ) + + # print model summary + print(model.summary()) + + # this lets the generator compute backbone layer shapes using the actual backbone model + if 'vgg' in args.backbone or 'densenet' in args.backbone: + train_generator.compute_shapes = make_shapes_callback(model) + if validation_generator: + validation_generator.compute_shapes = train_generator.compute_shapes + + # create the callbacks + callbacks = create_callbacks( + model, + training_model, + prediction_model, + validation_generator, + args, + ) + + if not args.compute_val_loss: + validation_generator = None + + # start training + return training_model.fit_generator( + generator=train_generator, + steps_per_epoch=args.steps, + epochs=args.epochs, + verbose=1, + callbacks=callbacks, + workers=args.workers, + use_multiprocessing=args.multiprocessing, + max_queue_size=args.max_queue_size, + validation_data=validation_generator, + initial_epoch=args.initial_epoch + ) + + +if __name__ == '__main__': + main() diff --git a/imageai/Detection/keras_retinanet/callbacks/__init__.py b/imageai/Detection/keras_retinanet/callbacks/__init__.py index 55e5f844..7316c99a 100644 --- a/imageai/Detection/keras_retinanet/callbacks/__init__.py +++ b/imageai/Detection/keras_retinanet/callbacks/__init__.py @@ -1 +1 @@ -from .common import * +from .common import * # noqa: F401,F403 diff --git a/imageai/Detection/keras_retinanet/callbacks/coco.py b/imageai/Detection/keras_retinanet/callbacks/coco.py index ab86acdb..3518dd29 100644 --- a/imageai/Detection/keras_retinanet/callbacks/coco.py +++ b/imageai/Detection/keras_retinanet/callbacks/coco.py @@ -14,16 +14,52 @@ limitations under the License. """ -import keras +from tensorflow import keras from ..utils.coco_eval import evaluate_coco class CocoEval(keras.callbacks.Callback): - def __init__(self, generator, threshold=0.05): + """ Performs COCO evaluation on each epoch. + """ + def __init__(self, generator, tensorboard=None, threshold=0.05): + """ CocoEval callback intializer. + + Args + generator : The generator used for creating validation data. + tensorboard : If given, the results will be written to tensorboard. + threshold : The score threshold to use. + """ self.generator = generator self.threshold = threshold + self.tensorboard = tensorboard super(CocoEval, self).__init__() - def on_epoch_end(self, epoch, logs={}): - evaluate_coco(self.generator, self.model, self.threshold) + def on_epoch_end(self, epoch, logs=None): + logs = logs or {} + + coco_tag = ['AP @[ IoU=0.50:0.95 | area= all | maxDets=100 ]', + 'AP @[ IoU=0.50 | area= all | maxDets=100 ]', + 'AP @[ IoU=0.75 | area= all | maxDets=100 ]', + 'AP @[ IoU=0.50:0.95 | area= small | maxDets=100 ]', + 'AP @[ IoU=0.50:0.95 | area=medium | maxDets=100 ]', + 'AP @[ IoU=0.50:0.95 | area= large | maxDets=100 ]', + 'AR @[ IoU=0.50:0.95 | area= all | maxDets= 1 ]', + 'AR @[ IoU=0.50:0.95 | area= all | maxDets= 10 ]', + 'AR @[ IoU=0.50:0.95 | area= all | maxDets=100 ]', + 'AR @[ IoU=0.50:0.95 | area= small | maxDets=100 ]', + 'AR @[ IoU=0.50:0.95 | area=medium | maxDets=100 ]', + 'AR @[ IoU=0.50:0.95 | area= large | maxDets=100 ]'] + coco_eval_stats = evaluate_coco(self.generator, self.model, self.threshold) + + if coco_eval_stats is not None: + for index, result in enumerate(coco_eval_stats): + logs[coco_tag[index]] = result + + if self.tensorboard: + import tensorflow as tf + writer = tf.summary.create_file_writer(self.tensorboard.log_dir) + with writer.as_default(): + for index, result in enumerate(coco_eval_stats): + tf.summary.scalar('{}. {}'.format(index + 1, coco_tag[index]), result, step=epoch) + writer.flush() diff --git a/imageai/Detection/keras_retinanet/callbacks/common.py b/imageai/Detection/keras_retinanet/callbacks/common.py index 76e6bf63..1c849bd0 100644 --- a/imageai/Detection/keras_retinanet/callbacks/common.py +++ b/imageai/Detection/keras_retinanet/callbacks/common.py @@ -1,18 +1,19 @@ -import keras.callbacks +from tensorflow import keras class RedirectModel(keras.callbacks.Callback): """Callback which wraps another callback, but executed on a different model. - # Arguments - callback: callback to wrap. - model: model to use when executing callbacks. - # Example - ```python - model = keras.models.load_model('model.h5') - model_checkpoint = ModelCheckpoint(filepath='snapshot.h5') - parallel_model = multi_gpu_model(model, gpus=2) - parallel_model.fit(X_train, Y_train, callbacks=[RedirectModel(model_checkpoint, model)]) - ``` + + ```python + model = keras.models.load_model('model.h5') + model_checkpoint = ModelCheckpoint(filepath='snapshot.h5') + parallel_model = multi_gpu_model(model, gpus=2) + parallel_model.fit(X_train, Y_train, callbacks=[RedirectModel(model_checkpoint, model)]) + ``` + + Args + callback : callback to wrap. + model : model to use when executing callbacks. """ def __init__(self, diff --git a/imageai/Detection/keras_retinanet/callbacks/eval.py b/imageai/Detection/keras_retinanet/callbacks/eval.py index 9fe1d39b..365305a5 100644 --- a/imageai/Detection/keras_retinanet/callbacks/eval.py +++ b/imageai/Detection/keras_retinanet/callbacks/eval.py @@ -14,22 +14,36 @@ limitations under the License. """ -import keras +from tensorflow import keras from ..utils.eval import evaluate class Evaluate(keras.callbacks.Callback): - def __init__(self, generator, iou_threshold=0.5, score_threshold=0.05, max_detections=100, save_path=None, tensorboard=None, verbose=1): + """ Evaluation callback for arbitrary datasets. + """ + + def __init__( + self, + generator, + iou_threshold=0.5, + score_threshold=0.05, + max_detections=100, + save_path=None, + tensorboard=None, + weighted_average=False, + verbose=1 + ): """ Evaluate a given dataset using a given model at the end of every epoch during training. # Arguments - generator : The generator that represents the dataset to evaluate. - iou_threshold : The threshold used to consider when a detection is positive or negative. - score_threshold : The score confidence threshold to use for detections. - max_detections : The maximum number of detections to use per image. - save_path : The path to save images with visualized detections to. - tensorboard : Instance of keras.callbacks.TensorBoard used to log the mAP value. - verbose : Set the verbosity level, by default this is set to 1. + generator : The generator that represents the dataset to evaluate. + iou_threshold : The threshold used to consider when a detection is positive or negative. + score_threshold : The score confidence threshold to use for detections. + max_detections : The maximum number of detections to use per image. + save_path : The path to save images with visualized detections to. + tensorboard : Instance of keras.callbacks.TensorBoard used to log the mAP value. + weighted_average : Compute the mAP using the weighted average of precisions among classes. + verbose : Set the verbosity level, by default this is set to 1. """ self.generator = generator self.iou_threshold = iou_threshold @@ -37,13 +51,16 @@ def __init__(self, generator, iou_threshold=0.5, score_threshold=0.05, max_detec self.max_detections = max_detections self.save_path = save_path self.tensorboard = tensorboard + self.weighted_average = weighted_average self.verbose = verbose super(Evaluate, self).__init__() - def on_epoch_end(self, epoch, logs={}): + def on_epoch_end(self, epoch, logs=None): + logs = logs or {} + # run evaluation - average_precisions = evaluate( + average_precisions, _ = evaluate( self.generator, self.model, iou_threshold=self.iou_threshold, @@ -52,17 +69,31 @@ def on_epoch_end(self, epoch, logs={}): save_path=self.save_path ) - self.mean_ap = sum(average_precisions.values()) / len(average_precisions) + # compute per class average precision + total_instances = [] + precisions = [] + for label, (average_precision, num_annotations) in average_precisions.items(): + if self.verbose == 1: + print('{:.0f} instances of class'.format(num_annotations), + self.generator.label_to_name(label), 'with average precision: {:.4f}'.format(average_precision)) + total_instances.append(num_annotations) + precisions.append(average_precision) + if self.weighted_average: + self.mean_ap = sum([a * b for a, b in zip(total_instances, precisions)]) / sum(total_instances) + else: + self.mean_ap = sum(precisions) / sum(x > 0 for x in total_instances) - if self.tensorboard is not None and self.tensorboard.writer is not None: + if self.tensorboard: import tensorflow as tf - summary = tf.Summary() - summary_value = summary.value.add() - summary_value.simple_value = self.mean_ap - summary_value.tag = "mAP" - self.tensorboard.writer.add_summary(summary, epoch) + writer = tf.summary.create_file_writer(self.tensorboard.log_dir) + with writer.as_default(): + tf.summary.scalar("mAP", self.mean_ap, step=epoch) + if self.verbose == 1: + for label, (average_precision, num_annotations) in average_precisions.items(): + tf.summary.scalar("AP_" + self.generator.label_to_name(label), average_precision, step=epoch) + writer.flush() + + logs['mAP'] = self.mean_ap if self.verbose == 1: - for label, average_precision in average_precisions.items(): - print(self.generator.label_to_name(label), '{:.4f}'.format(average_precision)) print('mAP: {:.4f}'.format(self.mean_ap)) diff --git a/imageai/Detection/keras_retinanet/initializers.py b/imageai/Detection/keras_retinanet/initializers.py index d993a7cf..be8124ab 100644 --- a/imageai/Detection/keras_retinanet/initializers.py +++ b/imageai/Detection/keras_retinanet/initializers.py @@ -14,15 +14,13 @@ limitations under the License. """ -import keras +from tensorflow import keras -import numpy as np import math class PriorProbability(keras.initializers.Initializer): - """ - Initializer applies a prior probability. + """ Apply a prior probability to the weights. """ def __init__(self, probability=0.01): @@ -34,7 +32,7 @@ def get_config(self): } def __call__(self, shape, dtype=None): - # set bias to -log((1 - p)/p) for foregound - result = np.ones(shape, dtype=dtype) * -math.log((1 - self.probability) / self.probability) + # set bias to -log((1 - p)/p) for foreground + result = keras.backend.ones(shape, dtype=dtype) * -math.log((1 - self.probability) / self.probability) return result diff --git a/imageai/Detection/keras_retinanet/layers/__init__.py b/imageai/Detection/keras_retinanet/layers/__init__.py index 87d59b75..5a8c7d32 100644 --- a/imageai/Detection/keras_retinanet/layers/__init__.py +++ b/imageai/Detection/keras_retinanet/layers/__init__.py @@ -1 +1,2 @@ -from ._misc import NonMaximumSuppression, RegressBoxes, UpsampleLike, Anchors \ No newline at end of file +from ._misc import RegressBoxes, UpsampleLike, Anchors, ClipBoxes # noqa: F401 +from .filter_detections import FilterDetections # noqa: F401 diff --git a/imageai/Detection/keras_retinanet/layers/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_retinanet/layers/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index b64c187d5fa4c6cd9e9adb2191bf677c072ab9cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmX|+&q@P9492tnRHQwMS5Hbk6v3N_SnZ|YRM+hM~ACFJ3uf9YH06#&8NP~L};>&<0WCAS!B~T?mDO4#?22}=>LzP3) zq_CP~zeyOL1qf6n(@WJ>Q8nqhnE8M!tA$)jOI`CFZv735{nlTZjdz4TgF< yy*YRtdrFHrdaQjoO>EIAmJYo(%Agmm+Q-FGYNNfB({4YU+>>qk79Zl190dQwkXRf5 diff --git a/imageai/Detection/keras_retinanet/layers/__pycache__/_misc.cpython-35.pyc b/imageai/Detection/keras_retinanet/layers/__pycache__/_misc.cpython-35.pyc deleted file mode 100644 index 3db51d96a7a0845243ef8bb50cf5b444f7b64e3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7163 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 WFj=gPo6sp^$}C$mFV4)(IR6HzHtend diff --git a/imageai/Detection/keras_retinanet/layers/_misc.py b/imageai/Detection/keras_retinanet/layers/_misc.py index 9a2e541d..d0861213 100644 --- a/imageai/Detection/keras_retinanet/layers/_misc.py +++ b/imageai/Detection/keras_retinanet/layers/_misc.py @@ -14,7 +14,8 @@ limitations under the License. """ -import keras +import tensorflow +from tensorflow import keras from .. import backend from ..utils import anchors as utils_anchors @@ -22,43 +23,61 @@ class Anchors(keras.layers.Layer): + """ Keras layer for generating achors for a given shape. + """ + def __init__(self, size, stride, ratios=None, scales=None, *args, **kwargs): + """ Initializer for an Anchors layer. + + Args + size: The base size of the anchors to generate. + stride: The stride of the anchors to generate. + ratios: The ratios of the anchors to generate (defaults to AnchorParameters.default.ratios). + scales: The scales of the anchors to generate (defaults to AnchorParameters.default.scales). + """ self.size = size self.stride = stride self.ratios = ratios self.scales = scales if ratios is None: - self.ratios = np.array([0.5, 1, 2], keras.backend.floatx()), + self.ratios = utils_anchors.AnchorParameters.default.ratios elif isinstance(ratios, list): self.ratios = np.array(ratios) if scales is None: - self.scales = np.array([2 ** 0, 2 ** (1.0 / 3.0), 2 ** (2.0 / 3.0)], keras.backend.floatx()), + self.scales = utils_anchors.AnchorParameters.default.scales elif isinstance(scales, list): self.scales = np.array(scales) - self.num_anchors = len(ratios) * len(scales) - self.anchors = keras.backend.variable(utils_anchors.generate_anchors( - base_size=size, - ratios=ratios, - scales=scales, - )) + self.num_anchors = len(self.ratios) * len(self.scales) + self.anchors = utils_anchors.generate_anchors( + base_size=self.size, + ratios=self.ratios, + scales=self.scales, + ).astype(np.float32) super(Anchors, self).__init__(*args, **kwargs) def call(self, inputs, **kwargs): features = inputs - features_shape = keras.backend.shape(features)[:3] + features_shape = keras.backend.shape(features) # generate proposals from bbox deltas and shifted anchors - anchors = backend.shift(features_shape[1:3], self.stride, self.anchors) + if keras.backend.image_data_format() == 'channels_first': + anchors = backend.shift(features_shape[2:4], self.stride, self.anchors) + else: + anchors = backend.shift(features_shape[1:3], self.stride, self.anchors) anchors = keras.backend.tile(keras.backend.expand_dims(anchors, axis=0), (features_shape[0], 1, 1)) return anchors def compute_output_shape(self, input_shape): if None not in input_shape[1:]: - total = np.prod(input_shape[1:3]) * self.num_anchors + if keras.backend.image_data_format() == 'channels_first': + total = np.prod(input_shape[2:4]) * self.num_anchors + else: + total = np.prod(input_shape[1:3]) * self.num_anchors + return (input_shape[0], total, 4) else: return (input_shape[0], None, 4) @@ -75,82 +94,43 @@ def get_config(self): return config -class NonMaximumSuppression(keras.layers.Layer): - def __init__(self, nms_threshold=0.5, score_threshold=0.05, max_boxes=300, *args, **kwargs): - self.nms_threshold = nms_threshold - self.score_threshold = score_threshold - self.max_boxes = max_boxes - super(NonMaximumSuppression, self).__init__(*args, **kwargs) - - def call(self, inputs, **kwargs): - # TODO: support batch size > 1. - boxes = inputs[0][0] - classification = inputs[1][0] - other = [i[0] for i in inputs[2:]] # can be any user-specified additional data - indices = backend.range(keras.backend.shape(classification)[0]) - selected_scores = [] - - # perform per class NMS - for c in range(int(classification.shape[1])): - scores = classification[:, c] - - # threshold based on score - score_indices = backend.where(keras.backend.greater(scores, self.score_threshold)) - score_indices = keras.backend.cast(score_indices, 'int32') - boxes_ = backend.gather_nd(boxes, score_indices) - scores = keras.backend.gather(scores, score_indices)[:, 0] - - # perform NMS - nms_indices = backend.non_max_suppression(boxes_, scores, max_output_size=self.max_boxes, iou_threshold=self.nms_threshold) - - # filter set of original indices - selected_indices = keras.backend.gather(score_indices, nms_indices) - - # mask original classification column, setting all suppressed values to 0 - scores = keras.backend.gather(scores, nms_indices) - scores = backend.scatter_nd(selected_indices, scores, keras.backend.shape(classification[:, c])) - scores = keras.backend.expand_dims(scores, axis=1) - - selected_scores.append(scores) - - # reconstruct the (suppressed) classification scores - classification = keras.backend.concatenate(selected_scores, axis=1) - - # reconstruct into the expected output - detections = keras.backend.concatenate([boxes, classification] + other, axis=1) - - return keras.backend.expand_dims(detections, axis=0) - - def compute_output_shape(self, input_shape): - return (input_shape[0][0], input_shape[0][1], sum([i[2] for i in input_shape])) - - def get_config(self): - config = super(NonMaximumSuppression, self).get_config() - config.update({ - 'nms_threshold' : self.nms_threshold, - 'score_threshold' : self.score_threshold, - 'max_boxes' : self.max_boxes, - }) - - return config - - class UpsampleLike(keras.layers.Layer): + """ Keras layer for upsampling a Tensor to be the same shape as another Tensor. + """ + def call(self, inputs, **kwargs): source, target = inputs target_shape = keras.backend.shape(target) - return backend.resize_images(source, (target_shape[1], target_shape[2])) + if keras.backend.image_data_format() == 'channels_first': + source = tensorflow.transpose(source, (0, 2, 3, 1)) + output = backend.resize_images(source, (target_shape[2], target_shape[3]), method='nearest') + output = tensorflow.transpose(output, (0, 3, 1, 2)) + return output + else: + return backend.resize_images(source, (target_shape[1], target_shape[2]), method='nearest') def compute_output_shape(self, input_shape): - return (input_shape[0][0],) + input_shape[1][1:3] + (input_shape[0][-1],) + if keras.backend.image_data_format() == 'channels_first': + return (input_shape[0][0], input_shape[0][1]) + input_shape[1][2:4] + else: + return (input_shape[0][0],) + input_shape[1][1:3] + (input_shape[0][-1],) class RegressBoxes(keras.layers.Layer): + """ Keras layer for applying regression values to boxes. + """ + def __init__(self, mean=None, std=None, *args, **kwargs): + """ Initializer for the RegressBoxes layer. + + Args + mean: The mean value of the regression values which was used for normalization. + std: The standard value of the regression values which was used for normalization. + """ if mean is None: mean = np.array([0, 0, 0, 0]) if std is None: - std = np.array([0.1, 0.1, 0.2, 0.2]) + std = np.array([0.2, 0.2, 0.2, 0.2]) if isinstance(mean, (list, tuple)): mean = np.array(mean) @@ -181,3 +161,26 @@ def get_config(self): }) return config + + +class ClipBoxes(keras.layers.Layer): + """ Keras layer to clip box values to lie inside a given shape. + """ + def call(self, inputs, **kwargs): + image, boxes = inputs + shape = keras.backend.cast(keras.backend.shape(image), keras.backend.floatx()) + if keras.backend.image_data_format() == 'channels_first': + _, _, height, width = tensorflow.unstack(shape, axis=0) + else: + _, height, width, _ = tensorflow.unstack(shape, axis=0) + + x1, y1, x2, y2 = tensorflow.unstack(boxes, axis=-1) + x1 = tensorflow.clip_by_value(x1, 0, width - 1) + y1 = tensorflow.clip_by_value(y1, 0, height - 1) + x2 = tensorflow.clip_by_value(x2, 0, width - 1) + y2 = tensorflow.clip_by_value(y2, 0, height - 1) + + return keras.backend.stack([x1, y1, x2, y2], axis=2) + + def compute_output_shape(self, input_shape): + return input_shape[1] diff --git a/imageai/Detection/keras_retinanet/layers/filter_detections.py b/imageai/Detection/keras_retinanet/layers/filter_detections.py new file mode 100644 index 00000000..1da7bf46 --- /dev/null +++ b/imageai/Detection/keras_retinanet/layers/filter_detections.py @@ -0,0 +1,228 @@ +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import tensorflow +from tensorflow import keras +from .. import backend + + +def filter_detections( + boxes, + classification, + other = [], + class_specific_filter = True, + nms = True, + score_threshold = 0.05, + max_detections = 300, + nms_threshold = 0.5 +): + """ Filter detections using the boxes and classification values. + + Args + boxes : Tensor of shape (num_boxes, 4) containing the boxes in (x1, y1, x2, y2) format. + classification : Tensor of shape (num_boxes, num_classes) containing the classification scores. + other : List of tensors of shape (num_boxes, ...) to filter along with the boxes and classification scores. + class_specific_filter : Whether to perform filtering per class, or take the best scoring class and filter those. + nms : Flag to enable/disable non maximum suppression. + score_threshold : Threshold used to prefilter the boxes with. + max_detections : Maximum number of detections to keep. + nms_threshold : Threshold for the IoU value to determine when a box should be suppressed. + + Returns + A list of [boxes, scores, labels, other[0], other[1], ...]. + boxes is shaped (max_detections, 4) and contains the (x1, y1, x2, y2) of the non-suppressed boxes. + scores is shaped (max_detections,) and contains the scores of the predicted class. + labels is shaped (max_detections,) and contains the predicted label. + other[i] is shaped (max_detections, ...) and contains the filtered other[i] data. + In case there are less than max_detections detections, the tensors are padded with -1's. + """ + def _filter_detections(scores, labels): + # threshold based on score + indices = tensorflow.where(keras.backend.greater(scores, score_threshold)) + + if nms: + filtered_boxes = tensorflow.gather_nd(boxes, indices) + filtered_scores = keras.backend.gather(scores, indices)[:, 0] + + # perform NMS + nms_indices = tensorflow.image.non_max_suppression(filtered_boxes, filtered_scores, max_output_size=max_detections, iou_threshold=nms_threshold) + + # filter indices based on NMS + indices = keras.backend.gather(indices, nms_indices) + + # add indices to list of all indices + labels = tensorflow.gather_nd(labels, indices) + indices = keras.backend.stack([indices[:, 0], labels], axis=1) + + return indices + + if class_specific_filter: + all_indices = [] + # perform per class filtering + for c in range(int(classification.shape[1])): + scores = classification[:, c] + labels = c * tensorflow.ones((keras.backend.shape(scores)[0],), dtype='int64') + all_indices.append(_filter_detections(scores, labels)) + + # concatenate indices to single tensor + indices = keras.backend.concatenate(all_indices, axis=0) + else: + scores = keras.backend.max(classification, axis = 1) + labels = keras.backend.argmax(classification, axis = 1) + indices = _filter_detections(scores, labels) + + # select top k + scores = tensorflow.gather_nd(classification, indices) + labels = indices[:, 1] + scores, top_indices = tensorflow.nn.top_k(scores, k=keras.backend.minimum(max_detections, keras.backend.shape(scores)[0])) + + # filter input using the final set of indices + indices = keras.backend.gather(indices[:, 0], top_indices) + boxes = keras.backend.gather(boxes, indices) + labels = keras.backend.gather(labels, top_indices) + other_ = [keras.backend.gather(o, indices) for o in other] + + # zero pad the outputs + pad_size = keras.backend.maximum(0, max_detections - keras.backend.shape(scores)[0]) + boxes = tensorflow.pad(boxes, [[0, pad_size], [0, 0]], constant_values=-1) + scores = tensorflow.pad(scores, [[0, pad_size]], constant_values=-1) + labels = tensorflow.pad(labels, [[0, pad_size]], constant_values=-1) + labels = keras.backend.cast(labels, 'int32') + other_ = [tensorflow.pad(o, [[0, pad_size]] + [[0, 0] for _ in range(1, len(o.shape))], constant_values=-1) for o in other_] + + # set shapes, since we know what they are + boxes.set_shape([max_detections, 4]) + scores.set_shape([max_detections]) + labels.set_shape([max_detections]) + for o, s in zip(other_, [list(keras.backend.int_shape(o)) for o in other]): + o.set_shape([max_detections] + s[1:]) + + return [boxes, scores, labels] + other_ + + +class FilterDetections(keras.layers.Layer): + """ Keras layer for filtering detections using score threshold and NMS. + """ + + def __init__( + self, + nms = True, + class_specific_filter = True, + nms_threshold = 0.5, + score_threshold = 0.05, + max_detections = 300, + parallel_iterations = 32, + **kwargs + ): + """ Filters detections using score threshold, NMS and selecting the top-k detections. + + Args + nms : Flag to enable/disable NMS. + class_specific_filter : Whether to perform filtering per class, or take the best scoring class and filter those. + nms_threshold : Threshold for the IoU value to determine when a box should be suppressed. + score_threshold : Threshold used to prefilter the boxes with. + max_detections : Maximum number of detections to keep. + parallel_iterations : Number of batch items to process in parallel. + """ + self.nms = nms + self.class_specific_filter = class_specific_filter + self.nms_threshold = nms_threshold + self.score_threshold = score_threshold + self.max_detections = max_detections + self.parallel_iterations = parallel_iterations + super(FilterDetections, self).__init__(**kwargs) + + def call(self, inputs, **kwargs): + """ Constructs the NMS graph. + + Args + inputs : List of [boxes, classification, other[0], other[1], ...] tensors. + """ + boxes = inputs[0] + classification = inputs[1] + other = inputs[2:] + + # wrap nms with our parameters + def _filter_detections(args): + boxes = args[0] + classification = args[1] + other = args[2] + + return filter_detections( + boxes, + classification, + other, + nms = self.nms, + class_specific_filter = self.class_specific_filter, + score_threshold = self.score_threshold, + max_detections = self.max_detections, + nms_threshold = self.nms_threshold, + ) + + # call filter_detections on each batch + dtypes = [keras.backend.floatx(), keras.backend.floatx(), 'int32'] + [o.dtype for o in other] + shapes = [(self.max_detections, 4), (self.max_detections,), (self.max_detections,)] + shapes.extend([(self.max_detections,) + o.shape[2:] for o in other]) + outputs = backend.map_fn( + _filter_detections, + elems=[boxes, classification, other], + dtype=dtypes, + shapes=shapes, + parallel_iterations=self.parallel_iterations, + ) + + return outputs + + def compute_output_shape(self, input_shape): + """ Computes the output shapes given the input shapes. + + Args + input_shape : List of input shapes [boxes, classification, other[0], other[1], ...]. + + Returns + List of tuples representing the output shapes: + [filtered_boxes.shape, filtered_scores.shape, filtered_labels.shape, filtered_other[0].shape, filtered_other[1].shape, ...] + """ + return [ + (input_shape[0][0], self.max_detections, 4), + (input_shape[1][0], self.max_detections), + (input_shape[1][0], self.max_detections), + ] + [ + tuple([input_shape[i][0], self.max_detections] + list(input_shape[i][2:])) for i in range(2, len(input_shape)) + ] + + def compute_mask(self, inputs, mask=None): + """ This is required in Keras when there is more than 1 output. + """ + return (len(inputs) + 1) * [None] + + def get_config(self): + """ Gets the configuration of this layer. + + Returns + Dictionary containing the parameters of this layer. + """ + config = super(FilterDetections, self).get_config() + config.update({ + 'nms' : self.nms, + 'class_specific_filter' : self.class_specific_filter, + 'nms_threshold' : self.nms_threshold, + 'score_threshold' : self.score_threshold, + 'max_detections' : self.max_detections, + 'parallel_iterations' : self.parallel_iterations, + }) + + return config diff --git a/imageai/Detection/keras_retinanet/losses.py b/imageai/Detection/keras_retinanet/losses.py index 1545fb50..15517884 100644 --- a/imageai/Detection/keras_retinanet/losses.py +++ b/imageai/Detection/keras_retinanet/losses.py @@ -14,89 +14,105 @@ limitations under the License. """ -import keras -from . import backend +import tensorflow +from tensorflow import keras -def focal(alpha=0.25, gamma=2.0): +def focal(alpha=0.25, gamma=2.0, cutoff=0.5): + """ Create a functor for computing the focal loss. + + Args + alpha: Scale the focal weight with alpha. + gamma: Take the power of the focal weight with gamma. + cutoff: Positive prediction cutoff for soft targets + + Returns + A functor that computes the focal loss using the alpha and gamma. + """ def _focal(y_true, y_pred): - labels = y_true - classification = y_pred + """ Compute the focal loss given the target tensor and the predicted tensor. - # compute the divisor: for each image in the batch, we want the number of positive anchors + As defined in https://arxiv.org/abs/1708.02002 - # clip the labels to 0, 1 so that we ignore the "ignore" label (-1) in the divisor - divisor = backend.where(keras.backend.less_equal(labels, 0), keras.backend.zeros_like(labels), labels) - divisor = keras.backend.max(divisor, axis=2, keepdims=True) - divisor = keras.backend.cast(divisor, keras.backend.floatx()) + Args + y_true: Tensor of target data from the generator with shape (B, N, num_classes). + y_pred: Tensor of predicted data from the network with shape (B, N, num_classes). - # compute the number of positive anchors - divisor = keras.backend.sum(divisor, axis=1, keepdims=True) + Returns + The focal loss of y_pred w.r.t. y_true. + """ + labels = y_true[:, :, :-1] + anchor_state = y_true[:, :, -1] # -1 for ignore, 0 for background, 1 for object + classification = y_pred - # ensure we do not divide by 0 - divisor = keras.backend.maximum(1.0, divisor) + # filter out "ignore" anchors + indices = tensorflow.where(keras.backend.not_equal(anchor_state, -1)) + labels = tensorflow.gather_nd(labels, indices) + classification = tensorflow.gather_nd(classification, indices) # compute the focal loss alpha_factor = keras.backend.ones_like(labels) * alpha - alpha_factor = backend.where(keras.backend.equal(labels, 1), alpha_factor, 1 - alpha_factor) - focal_weight = backend.where(keras.backend.equal(labels, 1), 1 - classification, classification) + alpha_factor = tensorflow.where(keras.backend.greater(labels, cutoff), alpha_factor, 1 - alpha_factor) + focal_weight = tensorflow.where(keras.backend.greater(labels, cutoff), 1 - classification, classification) focal_weight = alpha_factor * focal_weight ** gamma cls_loss = focal_weight * keras.backend.binary_crossentropy(labels, classification) - # normalise by the number of positive anchors for each entry in the minibatch - cls_loss = cls_loss / divisor + # compute the normalizer: the number of positive anchors + normalizer = tensorflow.where(keras.backend.equal(anchor_state, 1)) + normalizer = keras.backend.cast(keras.backend.shape(normalizer)[0], keras.backend.floatx()) + normalizer = keras.backend.maximum(keras.backend.cast_to_floatx(1.0), normalizer) - # filter out "ignore" anchors - anchor_state = keras.backend.max(labels, axis=2) # -1 for ignore, 0 for background, 1 for object - indices = backend.where(keras.backend.not_equal(anchor_state, -1)) - - cls_loss = backend.gather_nd(cls_loss, indices) - - # divide by the size of the minibatch - return keras.backend.sum(cls_loss) / keras.backend.cast(keras.backend.shape(labels)[0], keras.backend.floatx()) + return keras.backend.sum(cls_loss) / normalizer return _focal def smooth_l1(sigma=3.0): + """ Create a smooth L1 loss functor. + + Args + sigma: This argument defines the point where the loss changes from L2 to L1. + + Returns + A functor for computing the smooth L1 loss given target data and predicted data. + """ sigma_squared = sigma ** 2 def _smooth_l1(y_true, y_pred): + """ Compute the smooth L1 loss of y_pred w.r.t. y_true. + + Args + y_true: Tensor from the generator of shape (B, N, 5). The last value for each box is the state of the anchor (ignore, negative, positive). + y_pred: Tensor from the network of shape (B, N, 4). + + Returns + The smooth L1 loss of y_pred w.r.t. y_true. + """ # separate target and state regression = y_pred - regression_target = y_true[:, :, :4] - anchor_state = y_true[:, :, 4] + regression_target = y_true[:, :, :-1] + anchor_state = y_true[:, :, -1] - # compute the divisor: for each image in the batch, we want the number of positive anchors - divisor = backend.where(keras.backend.equal(anchor_state, 1), keras.backend.ones_like(anchor_state), keras.backend.zeros_like(anchor_state)) - divisor = keras.backend.sum(divisor, axis=1, keepdims=True) - divisor = keras.backend.maximum(1.0, divisor) - - # pad the tensor to have shape (batch_size, 1, 1) for future division - divisor = keras.backend.expand_dims(divisor, axis=2) + # filter out "ignore" anchors + indices = tensorflow.where(keras.backend.equal(anchor_state, 1)) + regression = tensorflow.gather_nd(regression, indices) + regression_target = tensorflow.gather_nd(regression_target, indices) # compute smooth L1 loss # f(x) = 0.5 * (sigma * x)^2 if |x| < 1 / sigma / sigma # |x| - 0.5 / sigma / sigma otherwise regression_diff = regression - regression_target regression_diff = keras.backend.abs(regression_diff) - regression_loss = backend.where( + regression_loss = tensorflow.where( keras.backend.less(regression_diff, 1.0 / sigma_squared), 0.5 * sigma_squared * keras.backend.pow(regression_diff, 2), regression_diff - 0.5 / sigma_squared ) - # normalise by the number of positive and negative anchors for each entry in the minibatch - regression_loss = regression_loss / divisor - - # filter out "ignore" anchors - indices = backend.where(keras.backend.equal(anchor_state, 1)) - regression_loss = backend.gather_nd(regression_loss, indices) - - # divide by the size of the minibatch - regression_loss = keras.backend.sum(regression_loss) / keras.backend.cast(keras.backend.shape(y_true)[0], keras.backend.floatx()) - - return regression_loss + # compute the normalizer: the number of positive anchors + normalizer = keras.backend.maximum(1, keras.backend.shape(indices)[0]) + normalizer = keras.backend.cast(normalizer, dtype=keras.backend.floatx()) + return keras.backend.sum(regression_loss) / normalizer return _smooth_l1 diff --git a/imageai/Detection/keras_retinanet/models/__init__.py b/imageai/Detection/keras_retinanet/models/__init__.py index e69de29b..e9b81d10 100644 --- a/imageai/Detection/keras_retinanet/models/__init__.py +++ b/imageai/Detection/keras_retinanet/models/__init__.py @@ -0,0 +1,125 @@ +from __future__ import print_function +import sys + + +class Backbone(object): + """ This class stores additional information on backbones. + """ + def __init__(self, backbone): + # a dictionary mapping custom layer names to the correct classes + from .. import layers + from .. import losses + from .. import initializers + self.custom_objects = { + 'UpsampleLike' : layers.UpsampleLike, + 'PriorProbability' : initializers.PriorProbability, + 'RegressBoxes' : layers.RegressBoxes, + 'FilterDetections' : layers.FilterDetections, + 'Anchors' : layers.Anchors, + 'ClipBoxes' : layers.ClipBoxes, + '_smooth_l1' : losses.smooth_l1(), + '_focal' : losses.focal(), + } + + self.backbone = backbone + self.validate() + + def retinanet(self, *args, **kwargs): + """ Returns a retinanet model using the correct backbone. + """ + raise NotImplementedError('retinanet method not implemented.') + + def download_imagenet(self): + """ Downloads ImageNet weights and returns path to weights file. + """ + raise NotImplementedError('download_imagenet method not implemented.') + + def validate(self): + """ Checks whether the backbone string is correct. + """ + raise NotImplementedError('validate method not implemented.') + + def preprocess_image(self, inputs): + """ Takes as input an image and prepares it for being passed through the network. + Having this function in Backbone allows other backbones to define a specific preprocessing step. + """ + raise NotImplementedError('preprocess_image method not implemented.') + + +def backbone(backbone_name): + """ Returns a backbone object for the given backbone. + """ + if 'densenet' in backbone_name: + from .densenet import DenseNetBackbone as b + elif 'seresnext' in backbone_name or 'seresnet' in backbone_name or 'senet' in backbone_name: + from .senet import SeBackbone as b + elif 'resnet' in backbone_name: + from .resnet import ResNetBackbone as b + elif 'mobilenet' in backbone_name: + from .mobilenet import MobileNetBackbone as b + elif 'vgg' in backbone_name: + from .vgg import VGGBackbone as b + elif 'EfficientNet' in backbone_name: + from .effnet import EfficientNetBackbone as b + else: + raise NotImplementedError('Backbone class for \'{}\' not implemented.'.format(backbone)) + + return b(backbone_name) + + +def load_model(filepath, backbone_name='resnet50'): + """ Loads a retinanet model using the correct custom objects. + + Args + filepath: one of the following: + - string, path to the saved model, or + - h5py.File object from which to load the model + backbone_name : Backbone with which the model was trained. + + Returns + A keras.models.Model object. + + Raises + ImportError: if h5py is not available. + ValueError: In case of an invalid savefile. + """ + from tensorflow import keras + return keras.models.load_model(filepath, custom_objects=backbone(backbone_name).custom_objects) + + +def convert_model(model, nms=True, class_specific_filter=True, anchor_params=None, **kwargs): + """ Converts a training model to an inference model. + + Args + model : A retinanet training model. + nms : Boolean, whether to add NMS filtering to the converted model. + class_specific_filter : Whether to use class specific filtering or filter for the best scoring class only. + anchor_params : Anchor parameters object. If omitted, default values are used. + **kwargs : Inference and minimal retinanet model settings. + + Returns + A keras.models.Model object. + + Raises + ImportError: if h5py is not available. + ValueError: In case of an invalid savefile. + """ + from .retinanet import retinanet_bbox + return retinanet_bbox(model=model, nms=nms, class_specific_filter=class_specific_filter, anchor_params=anchor_params, **kwargs) + + +def assert_training_model(model): + """ Assert that the model is a training model. + """ + assert(all(output in model.output_names for output in ['regression', 'classification'])), \ + "Input is not a training model (no 'regression' and 'classification' outputs were found, outputs are: {}).".format(model.output_names) + + +def check_training_model(model): + """ Check that model is a training model and exit otherwise. + """ + try: + assert_training_model(model) + except AssertionError as e: + print(e, file=sys.stderr) + sys.exit(1) diff --git a/imageai/Detection/keras_retinanet/models/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_retinanet/models/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 550df50d77f36d38d655a4858acdcdfce2648710..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192 zcmWgV<>k^+pA^FY1dl-k3@`#24nSPY0whux7=kq!{Z=v*frJsnuUu!Vn9$j#DPQ+cl=3<(uq3-#)m_v5RrOcZbAM&U z{B-m8-M<$J`762g$wB@Zn)nAgj}QyJM!XCOG-5$W_@~~SL!{b zeoVakO2v4FbjlCxrx`-b!)ScY{lRHW>($!s6Lfp@g@19*>DFl+kHzQP+kL54+-Oub z&98iyg@So>5_*i&_>|FuvF+k-KJ%D<&A9NRkk%_zx+M)Z=2IKxpP1*-gpTZU8b&dl z2!;iGLHmBdD0}O&aZLS?VyAK7+o8+o)Q?ZqCi5jK=Ii+akrUgnwP8Izr&0g56}4m2 zr1(e=;J|b`t=I}>CE|naAoW3Pf7LpC-tIhq0;HyS5(Z2N%Gs|cK8J(OIkm^Y;o1(+ z1@@FioZ17=al=g9}XT<5%uG#&6(->BIdp`iT^D?bLO^vhQJ*<;EO z9-IPuJLVEQ0Qxd?%&V9kCWE^;7TU+8)Ww*&e&2WDQ8<{`14akY8^%L8LdT4cd?61{ z0Gek8{>YE*Sm`(Cy*P?1WDRMTwWN5)xGj3X5V9D$F^W7Eh%4}mKwaVtak4RY@!*Jw zHjAIUn>|rB74>B0w0UuG&9(u2nmCefDe<(ISZIAnArBBX&6MdxBaGCsa9&+ z?`BCM1<Wui)#lz_ zz3MpoY`10=U76xNF>ySfb6q}E7+X0rCi4t&p!Sf_pDWa1ft?Vdz07?gjEN;d*sT5mDx z=0T>Um9(O^G;cvO+d>wx(q-pm+s;lUB(S$@x;FTJ2m4*SCa|N_7(Zt`-$-L84-bvcSqXOYSmL9>K zp|0-S72K8D4Bo+`G{t^NV%(TWKtm-0qW$_3IVJmxY=T!twz{BS&pMY0@DMm;yM1qiF+Ue?=f z{ymtKmsN!MM{-Fcu+Ks4B-fIUaanxlsd}V}ZnJ7Fr4s7mLTuK#d=0Wz7e%_~IMLg3 zo_}91ppID*HYtGKxHuS3j5Azz1HqT%a89xQKbVl*Ns|Cq!aO;i!d83-9f+?NwRN;d zAc$-abwexak1`-LNH1Z_j||mC!vViDdxqP<)IZ%#OxkR8lo>rDbqm-gT zP2_d1*sR#f*Cb+DiTFKq$*MaMaWv{h&JeX(Bw6`Vm>83>yp*`4ovNs5vPep(GvzXV zTw=XN2t^H9&62`XP$(u*G#bL*d!vZd_50G9jz Ai2wiq diff --git a/imageai/Detection/keras_retinanet/models/__pycache__/retinanet.cpython-35.pyc b/imageai/Detection/keras_retinanet/models/__pycache__/retinanet.cpython-35.pyc deleted file mode 100644 index bf79beac0e710c78455d37dfac0c604d02e8f970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12631 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 label) categories = self.coco.loadCats(self.coco.getCatIds()) categories.sort(key=lambda x: x['id']) @@ -53,39 +66,76 @@ def load_classes(self): self.labels[value] = key def size(self): + """ Size of the COCO dataset. + """ return len(self.image_ids) def num_classes(self): + """ Number of classes in the dataset. For COCO this is 80. + """ return len(self.classes) + def has_label(self, label): + """ Return True if label is a known label. + """ + return label in self.labels + + def has_name(self, name): + """ Returns True if name is a known class. + """ + return name in self.classes + def name_to_label(self, name): + """ Map name to label. + """ return self.classes[name] def label_to_name(self, label): + """ Map label to name. + """ return self.labels[label] def coco_label_to_label(self, coco_label): + """ Map COCO label to the label as used in the network. + COCO has some gaps in the order of labels. The highest label is 90, but there are 80 classes. + """ return self.coco_labels_inverse[coco_label] def coco_label_to_name(self, coco_label): + """ Map COCO label to name. + """ return self.label_to_name(self.coco_label_to_label(coco_label)) def label_to_coco_label(self, label): + """ Map label as used by the network to labels as used by COCO. + """ return self.coco_labels[label] + def image_path(self, image_index): + """ Returns the image path for image_index. + """ + image_info = self.coco.loadImgs(self.image_ids[image_index])[0] + path = os.path.join(self.data_dir, 'images', self.set_name, image_info['file_name']) + return path + def image_aspect_ratio(self, image_index): + """ Compute the aspect ratio for an image with image_index. + """ image = self.coco.loadImgs(self.image_ids[image_index])[0] return float(image['width']) / float(image['height']) def load_image(self, image_index): - image_info = self.coco.loadImgs(self.image_ids[image_index])[0] - path = os.path.join(self.data_dir, 'images', self.set_name, image_info['file_name']) + """ Load an image at the image_index. + """ + path = self.image_path(image_index) return read_image_bgr(path) def load_annotations(self, image_index): + """ Load annotations for an image_index. + """ # get ground truth annotations annotations_ids = self.coco.getAnnIds(imgIds=self.image_ids[image_index], iscrowd=False) - annotations = np.zeros((0, 5)) + annotations = {'labels': np.empty((0,)), 'bboxes': np.empty((0, 4))} # some images appear to miss annotations (like image with id 257034) if len(annotations_ids) == 0: @@ -98,13 +148,12 @@ def load_annotations(self, image_index): if a['bbox'][2] < 1 or a['bbox'][3] < 1: continue - annotation = np.zeros((1, 5)) - annotation[0, :4] = a['bbox'] - annotation[0, 4] = self.coco_label_to_label(a['category_id']) - annotations = np.append(annotations, annotation, axis=0) - - # transform from [x, y, w, h] to [x1, y1, x2, y2] - annotations[:, 2] = annotations[:, 0] + annotations[:, 2] - annotations[:, 3] = annotations[:, 1] + annotations[:, 3] + annotations['labels'] = np.concatenate([annotations['labels'], [self.coco_label_to_label(a['category_id'])]], axis=0) + annotations['bboxes'] = np.concatenate([annotations['bboxes'], [[ + a['bbox'][0], + a['bbox'][1], + a['bbox'][0] + a['bbox'][2], + a['bbox'][1] + a['bbox'][3], + ]]], axis=0) return annotations diff --git a/imageai/Detection/keras_retinanet/preprocessing/csv_generator.py b/imageai/Detection/keras_retinanet/preprocessing/csv_generator.py index a82c54f7..c756224e 100644 --- a/imageai/Detection/keras_retinanet/preprocessing/csv_generator.py +++ b/imageai/Detection/keras_retinanet/preprocessing/csv_generator.py @@ -25,6 +25,7 @@ import csv import sys import os.path +from collections import OrderedDict def _parse(value, function, fmt): @@ -42,8 +43,12 @@ def _parse(value, function, fmt): def _read_classes(csv_reader): - result = {} + """ Parse the classes file given by csv_reader. + """ + result = OrderedDict() for line, row in enumerate(csv_reader): + line += 1 + try: class_name, class_id = row except ValueError: @@ -57,10 +62,14 @@ def _read_classes(csv_reader): def _read_annotations(csv_reader, classes): - result = {} + """ Read annotations from the csv_reader. + """ + result = OrderedDict() for line, row in enumerate(csv_reader): + line += 1 + try: - img_file, x1, y1, x2, y2, class_name = row + img_file, x1, y1, x2, y2, class_name = row[:6] except ValueError: raise_from(ValueError('line {}: format should be \'img_file,x1,y1,x2,y2,class_name\' or \'img_file,,,,,\''.format(line)), None) @@ -91,8 +100,7 @@ def _read_annotations(csv_reader, classes): def _open_for_csv(path): - """ - Open a file with flags suitable for csv.reader. + """ Open a file with flags suitable for csv.reader. This is different for python2 it means with mode 'rb', for python3 this means 'r' with "universal newlines". @@ -104,6 +112,11 @@ def _open_for_csv(path): class CSVGenerator(Generator): + """ Generate data for a custom CSV dataset. + + See https://github.com/fizyr/keras-retinanet#csv-datasets for more information. + """ + def __init__( self, csv_data_file, @@ -111,6 +124,13 @@ def __init__( base_dir=None, **kwargs ): + """ Initialize a CSV data generator. + + Args + csv_data_file: Path to the CSV annotations file. + csv_class_file: Path to the CSV classes file. + base_dir: Directory w.r.t. where the files are to be searched (defaults to the directory containing the csv_data_file). + """ self.image_names = [] self.image_data = {} self.base_dir = base_dir @@ -141,39 +161,65 @@ def __init__( super(CSVGenerator, self).__init__(**kwargs) def size(self): + """ Size of the dataset. + """ return len(self.image_names) def num_classes(self): + """ Number of classes in the dataset. + """ return max(self.classes.values()) + 1 + def has_label(self, label): + """ Return True if label is a known label. + """ + return label in self.labels + + def has_name(self, name): + """ Returns True if name is a known class. + """ + return name in self.classes + def name_to_label(self, name): + """ Map name to label. + """ return self.classes[name] def label_to_name(self, label): + """ Map label to name. + """ return self.labels[label] def image_path(self, image_index): + """ Returns the image path for image_index. + """ return os.path.join(self.base_dir, self.image_names[image_index]) def image_aspect_ratio(self, image_index): + """ Compute the aspect ratio for an image with image_index. + """ # PIL is fast for metadata image = Image.open(self.image_path(image_index)) return float(image.width) / float(image.height) def load_image(self, image_index): + """ Load an image at the image_index. + """ return read_image_bgr(self.image_path(image_index)) def load_annotations(self, image_index): - path = self.image_names[image_index] - annots = self.image_data[path] - boxes = np.zeros((len(annots), 5)) - - for idx, annot in enumerate(annots): - class_name = annot['class'] - boxes[idx, 0] = float(annot['x1']) - boxes[idx, 1] = float(annot['y1']) - boxes[idx, 2] = float(annot['x2']) - boxes[idx, 3] = float(annot['y2']) - boxes[idx, 4] = self.name_to_label(class_name) - - return boxes + """ Load annotations for an image_index. + """ + path = self.image_names[image_index] + annotations = {'labels': np.empty((0,)), 'bboxes': np.empty((0, 4))} + + for idx, annot in enumerate(self.image_data[path]): + annotations['labels'] = np.concatenate((annotations['labels'], [self.name_to_label(annot['class'])])) + annotations['bboxes'] = np.concatenate((annotations['bboxes'], [[ + float(annot['x1']), + float(annot['y1']), + float(annot['x2']), + float(annot['y2']), + ]])) + + return annotations diff --git a/imageai/Detection/keras_retinanet/preprocessing/generator.py b/imageai/Detection/keras_retinanet/preprocessing/generator.py index ed37ddf2..7c3bb0a6 100644 --- a/imageai/Detection/keras_retinanet/preprocessing/generator.py +++ b/imageai/Detection/keras_retinanet/preprocessing/generator.py @@ -16,13 +16,16 @@ import numpy as np import random -import threading -import time import warnings -import keras +from tensorflow import keras -from ..utils.anchors import anchor_targets_bbox, bbox_transform +from ..utils.anchors import ( + anchor_targets_bbox, + anchors_for_shape, + guess_shapes +) +from ..utils.config import parse_anchor_parameters, parse_pyramid_levels from ..utils.image import ( TransformParameters, adjust_transform_for_image, @@ -33,129 +36,253 @@ from ..utils.transform import transform_aabb -class Generator(object): +class Generator(keras.utils.Sequence): + """ Abstract generator class. + """ + def __init__( self, transform_generator = None, + visual_effect_generator=None, batch_size=1, group_method='ratio', # one of 'none', 'random', 'ratio' shuffle_groups=True, image_min_side=800, image_max_side=1333, + no_resize=False, transform_parameters=None, + compute_anchor_targets=anchor_targets_bbox, + compute_shapes=guess_shapes, + preprocess_image=preprocess_image, + config=None ): - self.transform_generator = transform_generator - self.batch_size = int(batch_size) - self.group_method = group_method - self.shuffle_groups = shuffle_groups - self.image_min_side = image_min_side - self.image_max_side = image_max_side - self.transform_parameters = transform_parameters or TransformParameters() + """ Initialize Generator object. + + Args + transform_generator : A generator used to randomly transform images and annotations. + batch_size : The size of the batches to generate. + group_method : Determines how images are grouped together (defaults to 'ratio', one of ('none', 'random', 'ratio')). + shuffle_groups : If True, shuffles the groups each epoch. + image_min_side : After resizing the minimum side of an image is equal to image_min_side. + image_max_side : If after resizing the maximum side is larger than image_max_side, scales down further so that the max side is equal to image_max_side. + no_resize : If True, no image/annotation resizing is performed. + transform_parameters : The transform parameters used for data augmentation. + compute_anchor_targets : Function handler for computing the targets of anchors for an image and its annotations. + compute_shapes : Function handler for computing the shapes of the pyramid for a given input. + preprocess_image : Function handler for preprocessing an image (scaling / normalizing) for passing through a network. + """ + self.transform_generator = transform_generator + self.visual_effect_generator = visual_effect_generator + self.batch_size = int(batch_size) + self.group_method = group_method + self.shuffle_groups = shuffle_groups + self.image_min_side = image_min_side + self.image_max_side = image_max_side + self.no_resize = no_resize + self.transform_parameters = transform_parameters or TransformParameters() + self.compute_anchor_targets = compute_anchor_targets + self.compute_shapes = compute_shapes + self.preprocess_image = preprocess_image + self.config = config + + # Define groups + self.group_images() - self.group_index = 0 - self.lock = threading.Lock() + # Shuffle when initializing + if self.shuffle_groups: + self.on_epoch_end() - self.group_images() + def on_epoch_end(self): + if self.shuffle_groups: + random.shuffle(self.groups) def size(self): + """ Size of the dataset. + """ raise NotImplementedError('size method not implemented') def num_classes(self): + """ Number of classes in the dataset. + """ raise NotImplementedError('num_classes method not implemented') + def has_label(self, label): + """ Returns True if label is a known label. + """ + raise NotImplementedError('has_label method not implemented') + + def has_name(self, name): + """ Returns True if name is a known class. + """ + raise NotImplementedError('has_name method not implemented') + def name_to_label(self, name): + """ Map name to label. + """ raise NotImplementedError('name_to_label method not implemented') def label_to_name(self, label): + """ Map label to name. + """ raise NotImplementedError('label_to_name method not implemented') def image_aspect_ratio(self, image_index): + """ Compute the aspect ratio for an image with image_index. + """ raise NotImplementedError('image_aspect_ratio method not implemented') + def image_path(self, image_index): + """ Get the path to an image. + """ + raise NotImplementedError('image_path method not implemented') + def load_image(self, image_index): + """ Load an image at the image_index. + """ raise NotImplementedError('load_image method not implemented') def load_annotations(self, image_index): + """ Load annotations for an image_index. + """ raise NotImplementedError('load_annotations method not implemented') def load_annotations_group(self, group): - return [self.load_annotations(image_index) for image_index in group] + """ Load annotations for all images in group. + """ + annotations_group = [self.load_annotations(image_index) for image_index in group] + for annotations in annotations_group: + assert(isinstance(annotations, dict)), '\'load_annotations\' should return a list of dictionaries, received: {}'.format(type(annotations)) + assert('labels' in annotations), '\'load_annotations\' should return a list of dictionaries that contain \'labels\' and \'bboxes\'.' + assert('bboxes' in annotations), '\'load_annotations\' should return a list of dictionaries that contain \'labels\' and \'bboxes\'.' + + return annotations_group def filter_annotations(self, image_group, annotations_group, group): + """ Filter annotations by removing those that are outside of the image bounds or whose width/height < 0. + """ # test all annotations for index, (image, annotations) in enumerate(zip(image_group, annotations_group)): - assert(isinstance(annotations, np.ndarray)), '\'load_annotations\' should return a list of numpy arrays, received: {}'.format(type(annotations)) - # test x2 < x1 | y2 < y1 | x1 < 0 | y1 < 0 | x2 <= 0 | y2 <= 0 | x2 >= image.shape[1] | y2 >= image.shape[0] invalid_indices = np.where( - (annotations[:, 2] <= annotations[:, 0]) | - (annotations[:, 3] <= annotations[:, 1]) | - (annotations[:, 0] < 0) | - (annotations[:, 1] < 0) | - (annotations[:, 2] > image.shape[1]) | - (annotations[:, 3] > image.shape[0]) + (annotations['bboxes'][:, 2] <= annotations['bboxes'][:, 0]) | + (annotations['bboxes'][:, 3] <= annotations['bboxes'][:, 1]) | + (annotations['bboxes'][:, 0] < 0) | + (annotations['bboxes'][:, 1] < 0) | + (annotations['bboxes'][:, 2] > image.shape[1]) | + (annotations['bboxes'][:, 3] > image.shape[0]) )[0] # delete invalid indices if len(invalid_indices): - warnings.warn('Image with id {} (shape {}) contains the following invalid boxes: {}.'.format( + warnings.warn('Image {} with id {} (shape {}) contains the following invalid boxes: {}.'.format( + self.image_path(group[index]), group[index], image.shape, - [annotations[invalid_index, :] for invalid_index in invalid_indices] + annotations['bboxes'][invalid_indices, :] )) - annotations_group[index] = np.delete(annotations, invalid_indices, axis=0) - + for k in annotations_group[index].keys(): + annotations_group[index][k] = np.delete(annotations[k], invalid_indices, axis=0) return image_group, annotations_group def load_image_group(self, group): + """ Load images for all images in a group. + """ return [self.load_image(image_index) for image_index in group] - def random_transform_group_entry(self, image, annotations): + def random_visual_effect_group_entry(self, image, annotations): + """ Randomly transforms image and annotation. + """ + visual_effect = next(self.visual_effect_generator) + # apply visual effect + image = visual_effect(image) + return image, annotations + + def random_visual_effect_group(self, image_group, annotations_group): + """ Randomly apply visual effect on each image. + """ + assert(len(image_group) == len(annotations_group)) + + if self.visual_effect_generator is None: + # do nothing + return image_group, annotations_group + + for index in range(len(image_group)): + # apply effect on a single group entry + image_group[index], annotations_group[index] = self.random_visual_effect_group_entry( + image_group[index], annotations_group[index] + ) + + return image_group, annotations_group + + def random_transform_group_entry(self, image, annotations, transform=None): + """ Randomly transforms image and annotation. + """ # randomly transform both image and annotations - if self.transform_generator: - transform = adjust_transform_for_image(next(self.transform_generator), image, self.transform_parameters.relative_translation) - image = apply_transform(transform, image, self.transform_parameters) + if transform is not None or self.transform_generator: + if transform is None: + transform = adjust_transform_for_image(next(self.transform_generator), image, self.transform_parameters.relative_translation) + + # apply transformation to image + image = apply_transform(transform, image, self.transform_parameters) # Transform the bounding boxes in the annotations. - annotations = annotations.copy() - for index in range(annotations.shape[0]): - annotations[index, :4] = transform_aabb(transform, annotations[index, :4]) + annotations['bboxes'] = annotations['bboxes'].copy() + for index in range(annotations['bboxes'].shape[0]): + annotations['bboxes'][index, :] = transform_aabb(transform, annotations['bboxes'][index, :]) return image, annotations - def resize_image(self, image): - return resize_image(image, min_side=self.image_min_side, max_side=self.image_max_side) + def random_transform_group(self, image_group, annotations_group): + """ Randomly transforms each image and its annotations. + """ - def preprocess_image(self, image): - return preprocess_image(image) + assert(len(image_group) == len(annotations_group)) - def preprocess_group_entry(self, image, annotations): - # preprocess the image - image = self.preprocess_image(image) + for index in range(len(image_group)): + # transform a single group entry + image_group[index], annotations_group[index] = self.random_transform_group_entry(image_group[index], annotations_group[index]) - # randomly transform image and annotations - image, annotations = self.random_transform_group_entry(image, annotations) + return image_group, annotations_group + def resize_image(self, image): + """ Resize an image using image_min_side and image_max_side. + """ + if self.no_resize: + return image, 1 + else: + return resize_image(image, min_side=self.image_min_side, max_side=self.image_max_side) + + def preprocess_group_entry(self, image, annotations): + """ Preprocess image and its annotations. + """ # resize image image, image_scale = self.resize_image(image) + # preprocess the image + image = self.preprocess_image(image) + # apply resizing to annotations too - annotations[:, :4] *= image_scale + annotations['bboxes'] *= image_scale + + # convert to the wanted keras floatx + image = keras.backend.cast_to_floatx(image) return image, annotations def preprocess_group(self, image_group, annotations_group): - for index, (image, annotations) in enumerate(zip(image_group, annotations_group)): - # preprocess a single group entry - image, annotations = self.preprocess_group_entry(image, annotations) + """ Preprocess each image and its annotations in its group. + """ + assert(len(image_group) == len(annotations_group)) - # copy processed data back to group - image_group[index] = image - annotations_group[index] = annotations + for index in range(len(image_group)): + # preprocess a single group entry + image_group[index], annotations_group[index] = self.preprocess_group_entry(image_group[index], annotations_group[index]) return image_group, annotations_group def group_images(self): + """ Order the images according to self.order and makes groups of self.batch_size. + """ # determine the order of the images order = list(range(self.size())) if self.group_method == 'random': @@ -167,6 +294,8 @@ def group_images(self): self.groups = [[order[x % len(order)] for x in range(i, i + self.batch_size)] for i in range(0, len(order), self.batch_size)] def compute_inputs(self, image_group): + """ Compute inputs for the network using an image_group. + """ # get the max image shape max_shape = tuple(max(image.shape[x] for image in image_group) for x in range(3)) @@ -177,47 +306,40 @@ def compute_inputs(self, image_group): for image_index, image in enumerate(image_group): image_batch[image_index, :image.shape[0], :image.shape[1], :image.shape[2]] = image + if keras.backend.image_data_format() == 'channels_first': + image_batch = image_batch.transpose((0, 3, 1, 2)) + return image_batch - def anchor_targets( - self, - image_shape, - annotations, - num_classes, - mask_shape=None, - negative_overlap=0.4, - positive_overlap=0.5, - **kwargs - ): - return anchor_targets_bbox(image_shape, annotations, num_classes, mask_shape, negative_overlap, positive_overlap, **kwargs) + def generate_anchors(self, image_shape): + anchor_params = None + pyramid_levels = None + if self.config and 'anchor_parameters' in self.config: + anchor_params = parse_anchor_parameters(self.config) + if self.config and 'pyramid_levels' in self.config: + pyramid_levels = parse_pyramid_levels(self.config) + + return anchors_for_shape(image_shape, anchor_params=anchor_params, pyramid_levels=pyramid_levels, shapes_callback=self.compute_shapes) def compute_targets(self, image_group, annotations_group): + """ Compute target outputs for the network using images and their annotations. + """ # get the max image shape max_shape = tuple(max(image.shape[x] for image in image_group) for x in range(3)) + anchors = self.generate_anchors(max_shape) - # compute labels and regression targets - labels_group = [None] * self.batch_size - regression_group = [None] * self.batch_size - for index, (image, annotations) in enumerate(zip(image_group, annotations_group)): - # compute regression targets - labels_group[index], annotations, anchors = self.anchor_targets(max_shape, annotations, self.num_classes(), mask_shape=image.shape) - regression_group[index] = bbox_transform(anchors, annotations) - - # append anchor states to regression targets (necessary for filtering 'ignore', 'positive' and 'negative' anchors) - anchor_states = np.max(labels_group[index], axis=1, keepdims=True) - regression_group[index] = np.append(regression_group[index], anchor_states, axis=1) - - labels_batch = np.zeros((self.batch_size,) + labels_group[0].shape, dtype=keras.backend.floatx()) - regression_batch = np.zeros((self.batch_size,) + regression_group[0].shape, dtype=keras.backend.floatx()) + batches = self.compute_anchor_targets( + anchors, + image_group, + annotations_group, + self.num_classes() + ) - # copy all labels and regression values to the batch blob - for index, (labels, regression) in enumerate(zip(labels_group, regression_group)): - labels_batch[index, ...] = labels - regression_batch[index, ...] = regression - - return [regression_batch, labels_batch] + return list(batches) def compute_input_output(self, group): + """ Compute inputs and target outputs for the network. + """ # load images and annotations image_group = self.load_image_group(group) annotations_group = self.load_annotations_group(group) @@ -225,6 +347,12 @@ def compute_input_output(self, group): # check validity of annotations image_group, annotations_group = self.filter_annotations(image_group, annotations_group, group) + # randomly apply visual effect + image_group, annotations_group = self.random_visual_effect_group(image_group, annotations_group) + + # randomly transform data + image_group, annotations_group = self.random_transform_group(image_group, annotations_group) + # perform preprocessing steps image_group, annotations_group = self.preprocess_group(image_group, annotations_group) @@ -236,16 +364,18 @@ def compute_input_output(self, group): return inputs, targets - def __next__(self): - return self.next() + def __len__(self): + """ + Number of batches for generator. + """ - def next(self): - # advance the group index - with self.lock: - if self.group_index == 0 and self.shuffle_groups: - # shuffle groups at start of epoch - random.shuffle(self.groups) - group = self.groups[self.group_index] - self.group_index = (self.group_index + 1) % len(self.groups) + return len(self.groups) - return self.compute_input_output(group) + def __getitem__(self, index): + """ + Keras sequence method for generating batches. + """ + group = self.groups[index] + inputs, targets = self.compute_input_output(group) + + return inputs, targets diff --git a/imageai/Detection/keras_retinanet/preprocessing/kitti.py b/imageai/Detection/keras_retinanet/preprocessing/kitti.py index 8f420074..59225582 100644 --- a/imageai/Detection/keras_retinanet/preprocessing/kitti.py +++ b/imageai/Detection/keras_retinanet/preprocessing/kitti.py @@ -37,12 +37,23 @@ class KittiGenerator(Generator): + """ Generate data for a KITTI dataset. + + See http://www.cvlibs.net/datasets/kitti/ for more information. + """ + def __init__( self, base_dir, subset='train', **kwargs ): + """ Initialize a KITTI data generator. + + Args + base_dir: Directory w.r.t. where the files are to be searched (defaults to the directory containing the csv_data_file). + subset: The subset to generate data for (defaults to 'train'). + """ self.base_dir = base_dir label_dir = os.path.join(self.base_dir, subset, 'labels') @@ -65,9 +76,10 @@ def __init__( 1 rotation_y Rotation ry around Y-axis in camera coordinates [-pi..pi] """ - self.id_to_labels = {} - for label, id in kitti_classes.items(): - self.id_to_labels[id] = label + self.labels = {} + self.classes = kitti_classes + for name, label in self.classes.items(): + self.labels[label] = name self.image_data = dict() self.images = [] @@ -94,33 +106,63 @@ def __init__( super(KittiGenerator, self).__init__(**kwargs) def size(self): + """ Size of the dataset. + """ return len(self.images) def num_classes(self): - return max(kitti_classes.values()) + 1 + """ Number of classes in the dataset. + """ + return max(self.classes.values()) + 1 + + def has_label(self, label): + """ Return True if label is a known label. + """ + return label in self.labels + + def has_name(self, name): + """ Returns True if name is a known class. + """ + return name in self.classes def name_to_label(self, name): + """ Map name to label. + """ raise NotImplementedError() def label_to_name(self, label): - return self.id_to_labels[label] + """ Map label to name. + """ + return self.labels[label] def image_aspect_ratio(self, image_index): + """ Compute the aspect ratio for an image with image_index. + """ # PIL is fast for metadata image = Image.open(self.images[image_index]) return float(image.width) / float(image.height) + def image_path(self, image_index): + """ Get the path to an image. + """ + return self.images[image_index] + def load_image(self, image_index): - return read_image_bgr(self.images[image_index]) + """ Load an image at the image_index. + """ + return read_image_bgr(self.image_path(image_index)) def load_annotations(self, image_index): - annotations = self.image_data[image_index] - - boxes = np.zeros((len(annotations), 5)) - for idx, ann in enumerate(annotations): - boxes[idx, 0] = float(ann['x1']) - boxes[idx, 1] = float(ann['y1']) - boxes[idx, 2] = float(ann['x2']) - boxes[idx, 3] = float(ann['y2']) - boxes[idx, 4] = int(ann['cls_id']) - return boxes + """ Load annotations for an image_index. + """ + image_data = self.image_data[image_index] + annotations = {'labels': np.empty((len(image_data),)), 'bboxes': np.empty((len(image_data), 4))} + + for idx, ann in enumerate(image_data): + annotations['bboxes'][idx, 0] = float(ann['x1']) + annotations['bboxes'][idx, 1] = float(ann['y1']) + annotations['bboxes'][idx, 2] = float(ann['x2']) + annotations['bboxes'][idx, 3] = float(ann['y2']) + annotations['labels'][idx] = int(ann['cls_id']) + + return annotations diff --git a/imageai/Detection/keras_retinanet/preprocessing/open_images.py b/imageai/Detection/keras_retinanet/preprocessing/open_images.py index 9038fa19..a5ac7379 100644 --- a/imageai/Detection/keras_retinanet/preprocessing/open_images.py +++ b/imageai/Detection/keras_retinanet/preprocessing/open_images.py @@ -26,50 +26,129 @@ from ..utils.image import read_image_bgr -def get_labels(metadata_dir): - trainable_classes_path = os.path.join(metadata_dir, 'classes-bbox-trainable.txt') - description_path = os.path.join(metadata_dir, 'class-descriptions.csv') +def load_hierarchy(metadata_dir, version='v4'): + hierarchy = None + if version == 'challenge2018': + hierarchy = 'bbox_labels_500_hierarchy.json' + elif version == 'v4': + hierarchy = 'bbox_labels_600_hierarchy.json' + elif version == 'v3': + hierarchy = 'bbox_labels_600_hierarchy.json' - description_table = {} - with open(description_path) as f: - for row in csv.reader(f): - # make sure the csv row is not empty (usually the last one) - if len(row): - description_table[row[0]] = row[1].replace("\"", "").replace("'", "").replace('`', '') + hierarchy_json = os.path.join(metadata_dir, hierarchy) + with open(hierarchy_json) as f: + hierarchy_data = json.loads(f.read()) - with open(trainable_classes_path, 'rb') as f: - trainable_classes = f.read().split('\n') + return hierarchy_data - id_to_labels = dict([(i, description_table[c]) for i, c in enumerate(trainable_classes)]) - cls_index = dict([(c, i) for i, c in enumerate(trainable_classes)]) + +def load_hierarchy_children(hierarchy): + res = [hierarchy['LabelName']] + + if 'Subcategory' in hierarchy: + for subcategory in hierarchy['Subcategory']: + children = load_hierarchy_children(subcategory) + + for c in children: + res.append(c) + + return res + + +def find_hierarchy_parent(hierarchy, parent_cls): + if hierarchy['LabelName'] == parent_cls: + return hierarchy + elif 'Subcategory' in hierarchy: + for child in hierarchy['Subcategory']: + res = find_hierarchy_parent(child, parent_cls) + if res is not None: + return res + + return None + + +def get_labels(metadata_dir, version='v4'): + if version == 'v4' or version == 'challenge2018': + csv_file = 'class-descriptions-boxable.csv' if version == 'v4' else 'challenge-2018-class-descriptions-500.csv' + + boxable_classes_descriptions = os.path.join(metadata_dir, csv_file) + id_to_labels = {} + cls_index = {} + + i = 0 + with open(boxable_classes_descriptions) as f: + for row in csv.reader(f): + # make sure the csv row is not empty (usually the last one) + if len(row): + label = row[0] + description = row[1].replace("\"", "").replace("'", "").replace('`', '') + + id_to_labels[i] = description + cls_index[label] = i + + i += 1 + else: + trainable_classes_path = os.path.join(metadata_dir, 'classes-bbox-trainable.txt') + description_path = os.path.join(metadata_dir, 'class-descriptions.csv') + + description_table = {} + with open(description_path) as f: + for row in csv.reader(f): + # make sure the csv row is not empty (usually the last one) + if len(row): + description_table[row[0]] = row[1].replace("\"", "").replace("'", "").replace('`', '') + + with open(trainable_classes_path, 'rb') as f: + trainable_classes = f.read().split('\n') + + id_to_labels = dict([(i, description_table[c]) for i, c in enumerate(trainable_classes)]) + cls_index = dict([(c, i) for i, c in enumerate(trainable_classes)]) return id_to_labels, cls_index -def generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index): - annotations_path = os.path.join(metadata_dir, subset, 'annotations-human-bbox.csv') +def generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index, version='v4'): + validation_image_ids = {} - cnt = 0 - with open(annotations_path, 'r') as csv_file: - reader = csv.DictReader(csv_file, - fieldnames=['ImageID', 'Source', 'LabelName', - 'Confidence', 'XMin', 'XMax', 'YMin', - 'YMax']) - reader.next() - for _ in reader: - cnt += 1 + if version == 'v4': + annotations_path = os.path.join(metadata_dir, subset, '{}-annotations-bbox.csv'.format(subset)) + elif version == 'challenge2018': + validation_image_ids_path = os.path.join(metadata_dir, 'challenge-2018-image-ids-valset-od.csv') + + with open(validation_image_ids_path, 'r') as csv_file: + reader = csv.DictReader(csv_file, fieldnames=['ImageID']) + next(reader) + for line, row in enumerate(reader): + image_id = row['ImageID'] + validation_image_ids[image_id] = True + + annotations_path = os.path.join(metadata_dir, 'challenge-2018-train-annotations-bbox.csv') + else: + annotations_path = os.path.join(metadata_dir, subset, 'annotations-human-bbox.csv') + + fieldnames = ['ImageID', 'Source', 'LabelName', 'Confidence', + 'XMin', 'XMax', 'YMin', 'YMax', + 'IsOccluded', 'IsTruncated', 'IsGroupOf', 'IsDepiction', 'IsInside'] id_annotations = dict() with open(annotations_path, 'r') as csv_file: - reader = csv.DictReader(csv_file, - fieldnames=['ImageID', 'Source', 'LabelName', - 'Confidence', 'XMin', 'XMax', 'YMin', - 'YMax']) - reader.next() + reader = csv.DictReader(csv_file, fieldnames=fieldnames) + next(reader) images_sizes = {} for line, row in enumerate(reader): frame = row['ImageID'] + + if version == 'challenge2018': + if subset == 'train': + if frame in validation_image_ids: + continue + elif subset == 'validation': + if frame not in validation_image_ids: + continue + else: + raise NotImplementedError('This generator handles only the train and validation subsets') + class_name = row['LabelName'] if class_name not in cls_index: @@ -77,7 +156,13 @@ def generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index): cls_id = cls_index[class_name] - img_path = os.path.join(main_dir, 'images', subset, frame + '.jpg') + if version == 'challenge2018': + # We recommend participants to use the provided subset of the training set as a validation set. + # This is preferable over using the V4 val/test sets, as the training set is more densely annotated. + img_path = os.path.join(main_dir, 'images', 'train', frame + '.jpg') + else: + img_path = os.path.join(main_dir, 'images', subset, frame + '.jpg') + if frame in images_sizes: width, height = images_sizes[frame] else: @@ -85,7 +170,9 @@ def generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index): with Image.open(img_path) as img: width, height = img.width, img.height images_sizes[frame] = (width, height) - except Exception: + except Exception as ex: + if version == 'challenge2018': + raise ex continue x1 = float(row['XMin']) @@ -125,53 +212,93 @@ def generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index): class OpenImagesGenerator(Generator): def __init__( - self, main_dir, subset, version='2017_11', + self, main_dir, subset, version='v4', labels_filter=None, annotation_cache_dir='.', - fixed_labels=False, + parent_label=None, **kwargs ): - self.base_dir = os.path.join(main_dir, 'images', subset) - metadata_dir = os.path.join(main_dir, version) + if version == 'challenge2018': + metadata = 'challenge2018' + elif version == 'v4': + metadata = '2018_04' + elif version == 'v3': + metadata = '2017_11' + else: + raise NotImplementedError('There is currently no implementation for versions older than v3') + + if version == 'challenge2018': + self.base_dir = os.path.join(main_dir, 'images', 'train') + else: + self.base_dir = os.path.join(main_dir, 'images', subset) + + metadata_dir = os.path.join(main_dir, metadata) annotation_cache_json = os.path.join(annotation_cache_dir, subset + '.json') - self.id_to_labels, cls_index = get_labels(metadata_dir) + self.hierarchy = load_hierarchy(metadata_dir, version=version) + id_to_labels, cls_index = get_labels(metadata_dir, version=version) if os.path.exists(annotation_cache_json): with open(annotation_cache_json, 'r') as f: self.annotations = json.loads(f.read()) else: - self.annotations = generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index) + self.annotations = generate_images_annotations_json(main_dir, metadata_dir, subset, cls_index, version=version) json.dump(self.annotations, open(annotation_cache_json, "w")) - if labels_filter is not None: - self.id_to_labels, self.annotations = self.__filter_data(labels_filter, fixed_labels) + if labels_filter is not None or parent_label is not None: + self.id_to_labels, self.annotations = self.__filter_data(id_to_labels, cls_index, labels_filter, parent_label) + else: + self.id_to_labels = id_to_labels - self.id_to_image_id = dict() - for i, k in enumerate(self.annotations): - self.id_to_image_id[i] = k + self.id_to_image_id = dict([(i, k) for i, k in enumerate(self.annotations)]) super(OpenImagesGenerator, self).__init__(**kwargs) - def __filter_data(self, labels_filter, fixed_labels): + def __filter_data(self, id_to_labels, cls_index, labels_filter=None, parent_label=None): """ If you want to work with a subset of the labels just set a list with trainable labels :param labels_filter: Ex: labels_filter = ['Helmet', 'Hat', 'Analog television'] - :param fixed_labels: If fixed_labels is true this will bring you the 'Helmet' label - but also: 'bicycle helmet', 'welding helmet', 'ski helmet' etc... + :param parent_label: If parent_label is set this will bring you the parent label + but also its children in the semantic hierarchy as defined in OID, ex: Animal + hierarchical tree :return: """ - labels_to_id = dict([(l, i) for i, l in enumerate(labels_filter)]) + children_id_to_labels = {} - sub_labels_to_id = {} - if fixed_labels: + if parent_label is None: # there is/are no other sublabel(s) other than the labels itself - sub_labels_to_id = labels_to_id + + for label in labels_filter: + for i, lb in id_to_labels.items(): + if lb == label: + children_id_to_labels[i] = label + break else: - for l in labels_filter: - label = str.lower(l) - for v in [v for v in self.id_to_labels.values() if label in str.lower(v)]: - sub_labels_to_id[v] = labels_to_id[l] + parent_cls = None + for i, lb in id_to_labels.items(): + if lb == parent_label: + parent_id = i + for c, index in cls_index.items(): + if index == parent_id: + parent_cls = c + break + + if parent_cls is None: + raise Exception('Couldnt find label {}'.format(parent_label)) + + parent_tree = find_hierarchy_parent(self.hierarchy, parent_cls) + + if parent_tree is None: + raise Exception('Couldnt find parent {} in the semantic hierarchical tree'.format(parent_label)) + + children = load_hierarchy_children(parent_tree) + + for cls in children: + index = cls_index[cls] + label = id_to_labels[index] + children_id_to_labels[index] = label + + id_map = dict([(ind, i) for i, ind in enumerate(children_id_to_labels.keys())]) filtered_annotations = {} for k in self.annotations: @@ -180,16 +307,16 @@ def __filter_data(self, labels_filter, fixed_labels): filtered_boxes = [] for ann in img_ann['boxes']: cls_id = ann['cls_id'] - label = self.id_to_labels[cls_id] - if label in sub_labels_to_id: - ann['cls_id'] = sub_labels_to_id[label] + if cls_id in children_id_to_labels: + ann['cls_id'] = id_map[cls_id] filtered_boxes.append(ann) if len(filtered_boxes) > 0: filtered_annotations[k] = {'w': img_ann['w'], 'h': img_ann['h'], 'boxes': filtered_boxes} - id_to_labels = dict([(labels_to_id[k], k) for k in labels_to_id]) - return id_to_labels, filtered_annotations + children_id_to_labels = dict([(id_map[i], l) for (i, l) in children_id_to_labels.items()]) + + return children_id_to_labels, filtered_annotations def size(self): return len(self.annotations) @@ -197,6 +324,16 @@ def size(self): def num_classes(self): return len(self.id_to_labels) + def has_label(self, label): + """ Return True if label is a known label. + """ + return label in self.id_to_labels + + def has_name(self, name): + """ Returns True if name is a known class. + """ + raise NotImplementedError() + def name_to_label(self, name): raise NotImplementedError() @@ -221,7 +358,7 @@ def load_annotations(self, image_index): labels = image_annotations['boxes'] height, width = image_annotations['h'], image_annotations['w'] - boxes = np.zeros((len(labels), 5)) + annotations = {'labels': np.empty((len(labels),)), 'bboxes': np.empty((len(labels), 4))} for idx, ann in enumerate(labels): cls_id = ann['cls_id'] x1 = ann['x1'] * width @@ -229,10 +366,10 @@ def load_annotations(self, image_index): y1 = ann['y1'] * height y2 = ann['y2'] * height - boxes[idx, 0] = x1 - boxes[idx, 1] = y1 - boxes[idx, 2] = x2 - boxes[idx, 3] = y2 - boxes[idx, 4] = cls_id + annotations['bboxes'][idx, 0] = x1 + annotations['bboxes'][idx, 1] = y1 + annotations['bboxes'][idx, 2] = x2 + annotations['bboxes'][idx, 3] = y2 + annotations['labels'][idx] = cls_id - return boxes + return annotations diff --git a/imageai/Detection/keras_retinanet/preprocessing/pascal_voc.py b/imageai/Detection/keras_retinanet/preprocessing/pascal_voc.py index e273aacb..3428fa8b 100644 --- a/imageai/Detection/keras_retinanet/preprocessing/pascal_voc.py +++ b/imageai/Detection/keras_retinanet/preprocessing/pascal_voc.py @@ -51,7 +51,7 @@ } -def _findNode(parent, name, debug_name = None, parse = None): +def _findNode(parent, name, debug_name=None, parse=None): if debug_name is None: debug_name = name @@ -67,6 +67,11 @@ def _findNode(parent, name, debug_name = None, parse = None): class PascalVocGenerator(Generator): + """ Generate data for a Pascal VOC dataset. + + See http://host.robots.ox.ac.uk/pascal/VOC/ for more information. + """ + def __init__( self, data_dir, @@ -77,10 +82,16 @@ def __init__( skip_difficult=False, **kwargs ): + """ Initialize a Pascal VOC data generator. + + Args + base_dir: Directory w.r.t. where the files are to be searched (defaults to the directory containing the csv_data_file). + csv_class_file: Path to the CSV classes file. + """ self.data_dir = data_dir self.set_name = set_name self.classes = classes - self.image_names = [l.strip().split(None, 1)[0] for l in open(os.path.join(data_dir, 'ImageSets', 'Main', set_name + '.txt')).readlines()] + self.image_names = [line.strip().split(None, 1)[0] for line in open(os.path.join(data_dir, 'ImageSets', 'Main', set_name + '.txt')).readlines()] self.image_extension = image_extension self.skip_truncated = skip_truncated self.skip_difficult = skip_difficult @@ -92,27 +103,55 @@ def __init__( super(PascalVocGenerator, self).__init__(**kwargs) def size(self): + """ Size of the dataset. + """ return len(self.image_names) def num_classes(self): + """ Number of classes in the dataset. + """ return len(self.classes) + def has_label(self, label): + """ Return True if label is a known label. + """ + return label in self.labels + + def has_name(self, name): + """ Returns True if name is a known class. + """ + return name in self.classes + def name_to_label(self, name): + """ Map name to label. + """ return self.classes[name] def label_to_name(self, label): + """ Map label to name. + """ return self.labels[label] def image_aspect_ratio(self, image_index): + """ Compute the aspect ratio for an image with image_index. + """ path = os.path.join(self.data_dir, 'JPEGImages', self.image_names[image_index] + self.image_extension) image = Image.open(path) return float(image.width) / float(image.height) + def image_path(self, image_index): + """ Get the path to an image. + """ + return os.path.join(self.data_dir, 'JPEGImages', self.image_names[image_index] + self.image_extension) + def load_image(self, image_index): - path = os.path.join(self.data_dir, 'JPEGImages', self.image_names[image_index] + self.image_extension) - return read_image_bgr(path) + """ Load an image at the image_index. + """ + return read_image_bgr(self.image_path(image_index)) def __parse_annotation(self, element): + """ Parse an annotation given an XML element. + """ truncated = _findNode(element, 'truncated', parse=int) difficult = _findNode(element, 'difficult', parse=int) @@ -120,26 +159,24 @@ def __parse_annotation(self, element): if class_name not in self.classes: raise ValueError('class name \'{}\' not found in classes: {}'.format(class_name, list(self.classes.keys()))) - box = np.zeros((1, 5)) - box[0, 4] = self.name_to_label(class_name) + box = np.zeros((4,)) + label = self.name_to_label(class_name) bndbox = _findNode(element, 'bndbox') - box[0, 0] = _findNode(bndbox, 'xmin', 'bndbox.xmin', parse=float) - 1 - box[0, 1] = _findNode(bndbox, 'ymin', 'bndbox.ymin', parse=float) - 1 - box[0, 2] = _findNode(bndbox, 'xmax', 'bndbox.xmax', parse=float) - 1 - box[0, 3] = _findNode(bndbox, 'ymax', 'bndbox.ymax', parse=float) - 1 + box[0] = _findNode(bndbox, 'xmin', 'bndbox.xmin', parse=float) - 1 + box[1] = _findNode(bndbox, 'ymin', 'bndbox.ymin', parse=float) - 1 + box[2] = _findNode(bndbox, 'xmax', 'bndbox.xmax', parse=float) - 1 + box[3] = _findNode(bndbox, 'ymax', 'bndbox.ymax', parse=float) - 1 - return truncated, difficult, box + return truncated, difficult, box, label def __parse_annotations(self, xml_root): - size_node = _findNode(xml_root, 'size') - width = _findNode(size_node, 'width', 'size.width', parse=float) - height = _findNode(size_node, 'height', 'size.height', parse=float) - - boxes = np.zeros((0, 5)) + """ Parse all annotations under the xml_root. + """ + annotations = {'labels': np.empty((len(xml_root.findall('object')),)), 'bboxes': np.empty((len(xml_root.findall('object')), 4))} for i, element in enumerate(xml_root.iter('object')): try: - truncated, difficult, box = self.__parse_annotation(element) + truncated, difficult, box, label = self.__parse_annotation(element) except ValueError as e: raise_from(ValueError('could not parse object #{}: {}'.format(i, e)), None) @@ -147,11 +184,15 @@ def __parse_annotations(self, xml_root): continue if difficult and self.skip_difficult: continue - boxes = np.append(boxes, box, axis=0) - return boxes + annotations['bboxes'][i, :] = box + annotations['labels'][i] = label + + return annotations def load_annotations(self, image_index): + """ Load annotations for an image_index. + """ filename = self.image_names[image_index] + '.xml' try: tree = ET.parse(os.path.join(self.data_dir, 'Annotations', filename)) diff --git a/imageai/Detection/keras_retinanet/utils/__pycache__/__init__.cpython-35.pyc b/imageai/Detection/keras_retinanet/utils/__pycache__/__init__.cpython-35.pyc deleted file mode 100644 index 9e42e02942fc8524385cd203bd1652378429217b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191 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 D0Pf2( diff --git a/imageai/Detection/keras_retinanet/utils/__pycache__/colors.cpython-35.pyc b/imageai/Detection/keras_retinanet/utils/__pycache__/colors.cpython-35.pyc deleted file mode 100644 index c35dffe4e92df7aa9e5acbdf9e4f7087923156e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2152 zcmd5-zi%8x6rT0@F6X;*0x|jFgd~$d?3^)&MVb&rfN?}*DFkFgL9~*!c<0XR?9Ey; zdqhb<6eL}$R7r(|M1e#_MMp&iqM$<8Ab}`ObW|u!^1Zh^m%0Cd_3pRdy!qZY@6Fu# zj?B(BmKGm`zcz&UOL*4?_}h5wpA{iu+<{1ixDGNU;)=Kn9*B6Fr>de?VNw%u!K)(P zM-hsH*FDZ|WqiiP5rnMa^cXfBZ#m5@k6P#PbPkNs7 z%uh|$1AWcuPq^BUd%)w;ahx5OTAsyeR|5y*GtLLYmzcjisXy!VMQxHbMeaW4*f*Xc zf9x$!m1`|b<}SM2b6$MT)meBhI1M~cGY4rq4fsjNan}A?XFN~DmpJzoPlMcs%LRv~ z?fjP~hzd8%N%6MXR+ZcH~;7^C;0)URWt(GSM>6@+j>z8WPv4=~*T;NTW|wdQpD3qY4V8 z?7=)opjGl|Ys+~NLix35xn^3LaS zC$i%em&<%g!OGDf%{zw9ZS(|itzF@PnZkfsyE?3-N)KziA9h$T*R#WEV#6BB?nik$ z7*=^VzZGT%xsP|(E`}djWo-CfX0b2tr%_kwn^AZ<+aC0>KQ>%l57+xqPvL#Ovohuh z#iAs1m_8UBKVKs zV}dUT9ufRbaEstwg0~3XAoz`-OYkqjj|7I`AA-LL?h`B%d_$lJekS;Z;B^3wtcn-+ jX{dcZ01xmhEr#_={VX1&>PNa%bn!+tc0C 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 diff --git a/imageai/Detection/keras_retinanet/utils/__pycache__/transform.cpython-35.pyc b/imageai/Detection/keras_retinanet/utils/__pycache__/transform.cpython-35.pyc deleted file mode 100644 index ea497bf72cc88b136b54940ab277a9e4bef8cd30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10450 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 N&M#KI{r{S8{TF!QEMouw diff --git a/imageai/Detection/keras_retinanet/utils/__pycache__/visualization.cpython-35.pyc b/imageai/Detection/keras_retinanet/utils/__pycache__/visualization.cpython-35.pyc deleted file mode 100644 index ab445ad57bbe94f19a300aaf4add4826c74441ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4051 zcmc&%TW=f36`ti)T#J$H1hrMT?bLA_ZKBY&>Y{-X1Z5<-ChAbBDaWwGfEex$skN88 z^z70k6$>b8_Yd^Bk9{fnhl+j8lm0?q+V9LPFEQjKZh&^dot?{>GiT;}=R3oT)vEpO zJAY|^_y%KNv!$ni`fu^Ze?;Li_7J7Tyc`QG_7D|Q&#{Mj=HXXhL4gg5>|v2vUXghv z=9SqCh@!H>yb5~(6=g<4HuKKuA@t|;Z;+O-}vYi=}fZ2OMy$}pB5ABCP&JUNoQ zJ`^rK*~k|ET&mcQLcU$Q&NoTmN;Y)G`Ouz5BR&vQ9!3ct#S#kqnD_iZayfD3FyVg4 zvC=T`Md(UC_LC#MNT#A@KgbkB-9$iEKzcakQEyp_i^R4$9@+smFdmO~HZn>Lp{Lr0w}}qWtbC`NjcdwsY)sSpY{QLVl&*;*Hi7{z<*nXl>c|8;7?K?j7>` z^}BcL&BMliiyz$Oy9dp^#$n^2iPkM%Z$99^Z#4I|xWw_m3vx14WIGJ>>FA_avs;o5 zckUM6!yvWep>+M8?_x(`en_oJs$h1dumN;U9fa$XG6JUj3Qej*axUif?Nic`Rt zHBT*+j+LGZL{|nKHwq$k3S=>t@J>$=ZyE0{-uOO>gt12!duFlc7JHOqNuDYv1(p=p zqau68*s}sf!v=kFY@?Uc9eLAHFdgX2>j@?Hyrcu9<&?x*Foy3bF~$aXH=1yB$o_yf zs=*|9-+w9zWU#YlVDdJvtNv&p!z9+QA?TPXRNLW)I5*RV<2`7H+S*)_mNCx`uk!#9 zAUNLSGLTv&-<;go;#0hnZG5+##Z+yCo@*8{?F`J&yO2bjxQ|0}vXNZ0GwHy`C;?O3 zXEsk8HYyZ3Ns(6+dF3>ZqUn^=TsTaNLMbs#OCnCDLz(9NFiG?7)9tjPq?-UGflSMH z8qNJqz3vp#qCSZ<5A)KZHaM-!tx2u!-(ARz}m54h^BHTnMllCa_gSh?Fk4GZ#k2OMT z!)dxgd+)%oi!dP8@T*+1)LUq|v}1axw}MxL@dYYpgVA8ifKfwD;}NV3Jc67r0h5GQ zP%-!xtXp8u%gkB;_&eu-mw{Z&0jakf`}m)8*t%j!o93d6z|=8I2qUfw4GO;pYQHaL zUmvlVaXY*PrUPfVQAk8gRic+fPLdf4UxwbPjpJ6t2B-g~8o8qB!5kqxxIh4b$kCo=d(NYM$;*%DhVPDLohp4 zb)LEipr&|}23Iw1GKs6`iD^N$a>=@BsUM?uHkG}Hk;zr!vAr)ZYyJXrY4UmoP=MgT zYkA9hQfBvc#?6cK3wnZ25fuL$dcs2Vkh8yNZtHlOMoLGXAf*$?SZIo7QoT zO~=HkQ`AmNi&{#pVMx*VnSQfp@h;;VkjWyxYN^X;&3#5_A%E(K{|73QzZswXHS-ze zBAsyHr9UISDH$R1^+oD&g{cpy9#&X_pf2iERmvuyd2!>UOrBPKDj(%D5fU$Cb?^nH zwK7e;c(TG0OgAuD0YJbSP+)Q%W`GoOOPDE&KP@oh%?L4mO*+ud|Mo1lqhYkvL{9XxC10&M0zS3@Ryjs zrkNjiQ6%rUwshk_at{LDm2|1Z?MLGsMPG(U9SQC}Sba0n#3}-Z|8CwtMq)kUF1U3d zhPV{r4nzE<_sl~vkVe_5CadGh4*%-LQR_-AH!a3)q@>y)1uxOpnht}7qK_dH z6YH@`)!MLrYQ16Ato5AQq`5?^^UxqRoreY`!^w#WjS+=Lb_@GPEI6}R0AOdaK={^d zr#{m$Fbk7#77U0G-0?EZBMb=hQ2%Po4;GjgA^s|4chJ|%frNqY%7e?=XR!bEtAVb) zaW=UB&*=Vv@K!&8^P9)HuQFU0HI&uQ(W`z&#R9l12Ds;zj?U2Z+mK5wp?*Q~zogSgjO49y-HZ6+&a4&=$il#!JKnxxzq6?x6?7{zX*E5-&cZgS~8?y z45urfvH3F9H1bL1Qt0R)(&>NYqe0}20{Jmzo0zgqDYt5^=GOCT)z#`+^}?I~0S5{I A&Hw-a diff --git a/imageai/Detection/keras_retinanet/utils/anchors.py b/imageai/Detection/keras_retinanet/utils/anchors.py index 620e53b2..12257d32 100644 --- a/imageai/Detection/keras_retinanet/utils/anchors.py +++ b/imageai/Detection/keras_retinanet/utils/anchors.py @@ -15,88 +15,213 @@ """ import numpy as np +from tensorflow import keras + +#from ..utils.compute_overlap import compute_overlap + + +class AnchorParameters: + """ The parameteres that define how anchors are generated. + + Args + sizes : List of sizes to use. Each size corresponds to one feature level. + strides : List of strides to use. Each stride correspond to one feature level. + ratios : List of ratios to use per location in a feature map. + scales : List of scales to use per location in a feature map. + """ + def __init__(self, sizes, strides, ratios, scales): + self.sizes = sizes + self.strides = strides + self.ratios = ratios + self.scales = scales + + def num_anchors(self): + return len(self.ratios) * len(self.scales) + + +""" +The default anchor parameters. +""" +AnchorParameters.default = AnchorParameters( + sizes = [32, 64, 128, 256, 512], + strides = [8, 16, 32, 64, 128], + ratios = np.array([0.5, 1, 2], keras.backend.floatx()), + scales = np.array([2 ** 0, 2 ** (1.0 / 3.0), 2 ** (2.0 / 3.0)], keras.backend.floatx()), +) def anchor_targets_bbox( - image_shape, - annotations, + anchors, + image_group, + annotations_group, num_classes, - mask_shape=None, negative_overlap=0.4, - positive_overlap=0.5, - **kwargs + positive_overlap=0.5 ): - anchors = anchors_for_shape(image_shape, **kwargs) + """ Generate anchor targets for bbox detection. + + Args + anchors: np.array of annotations of shape (N, 4) for (x1, y1, x2, y2). + image_group: List of BGR images. + annotations_group: List of annotation dictionaries with each annotation containing 'labels' and 'bboxes' of an image. + num_classes: Number of classes to predict. + mask_shape: If the image is padded with zeros, mask_shape can be used to mark the relevant part of the image. + negative_overlap: IoU overlap for negative anchors (all anchors with overlap < negative_overlap are negative). + positive_overlap: IoU overlap or positive anchors (all anchors with overlap > positive_overlap are positive). + + Returns + labels_batch: batch that contains labels & anchor states (np.array of shape (batch_size, N, num_classes + 1), + where N is the number of anchors for an image and the last column defines the anchor state (-1 for ignore, 0 for bg, 1 for fg). + regression_batch: batch that contains bounding-box regression targets for an image & anchor states (np.array of shape (batch_size, N, 4 + 1), + where N is the number of anchors for an image, the first 4 columns define regression targets for (x1, y1, x2, y2) and the + last column defines anchor states (-1 for ignore, 0 for bg, 1 for fg). + """ - # label: 1 is positive, 0 is negative, -1 is dont care - labels = np.ones((anchors.shape[0], num_classes)) * -1 + assert(len(image_group) == len(annotations_group)), "The length of the images and annotations need to be equal." + assert(len(annotations_group) > 0), "No data received to compute anchor targets for." + for annotations in annotations_group: + assert('bboxes' in annotations), "Annotations should contain bboxes." + assert('labels' in annotations), "Annotations should contain labels." - if annotations.shape[0]: - # obtain indices of gt annotations with the greatest overlap - overlaps = compute_overlap(anchors, annotations[:, :4]) - argmax_overlaps_inds = np.argmax(overlaps, axis=1) - max_overlaps = overlaps[np.arange(overlaps.shape[0]), argmax_overlaps_inds] + batch_size = len(image_group) - # assign bg labels first so that positive labels can clobber them - labels[max_overlaps < negative_overlap, :] = 0 + regression_batch = np.zeros((batch_size, anchors.shape[0], 4 + 1), dtype=keras.backend.floatx()) + labels_batch = np.zeros((batch_size, anchors.shape[0], num_classes + 1), dtype=keras.backend.floatx()) - # compute box regression targets - annotations = annotations[argmax_overlaps_inds] + # compute labels and regression targets + for index, (image, annotations) in enumerate(zip(image_group, annotations_group)): + if annotations['bboxes'].shape[0]: + # obtain indices of gt annotations with the greatest overlap + positive_indices, ignore_indices, argmax_overlaps_inds = compute_gt_annotations(anchors, annotations['bboxes'], negative_overlap, positive_overlap) - # fg label: above threshold IOU - positive_indices = max_overlaps >= positive_overlap - labels[positive_indices, :] = 0 - labels[positive_indices, annotations[positive_indices, 4].astype(int)] = 1 - else: - # no annotations? then everything is background - labels[:] = 0 - annotations = np.zeros_like(anchors) + labels_batch[index, ignore_indices, -1] = -1 + labels_batch[index, positive_indices, -1] = 1 - # ignore annotations outside of image - mask_shape = image_shape if mask_shape is None else mask_shape - anchors_centers = np.vstack([(anchors[:, 0] + anchors[:, 2]) / 2, (anchors[:, 1] + anchors[:, 3]) / 2]).T - indices = np.logical_or(anchors_centers[:, 0] >= mask_shape[1], anchors_centers[:, 1] >= mask_shape[0]) - labels[indices, :] = -1 + regression_batch[index, ignore_indices, -1] = -1 + regression_batch[index, positive_indices, -1] = 1 - return labels, annotations, anchors + # compute target class labels + labels_batch[index, positive_indices, annotations['labels'][argmax_overlaps_inds[positive_indices]].astype(int)] = 1 + + regression_batch[index, :, :-1] = bbox_transform(anchors, annotations['bboxes'][argmax_overlaps_inds, :]) + + # ignore annotations outside of image + if image.shape: + anchors_centers = np.vstack([(anchors[:, 0] + anchors[:, 2]) / 2, (anchors[:, 1] + anchors[:, 3]) / 2]).T + indices = np.logical_or(anchors_centers[:, 0] >= image.shape[1], anchors_centers[:, 1] >= image.shape[0]) + + labels_batch[index, indices, -1] = -1 + regression_batch[index, indices, -1] = -1 + + return regression_batch, labels_batch + + +def layer_shapes(image_shape, model): + """Compute layer shapes given input image shape and the model. + + Args + image_shape: The shape of the image. + model: The model to use for computing how the image shape is transformed in the pyramid. + + Returns + A dictionary mapping layer names to image shapes. + """ + shape = { + model.layers[0].name: (None,) + image_shape, + } + + for layer in model.layers[1:]: + nodes = layer._inbound_nodes + for node in nodes: + if isinstance(node.inbound_layers, keras.layers.Layer): + inputs = [shape[node.inbound_layers.name]] + else: + inputs = [shape[lr.name] for lr in node.inbound_layers] + if not inputs: + continue + shape[layer.name] = layer.compute_output_shape(inputs[0] if len(inputs) == 1 else inputs) + + return shape + + +def make_shapes_callback(model): + """ Make a function for getting the shape of the pyramid levels. + """ + def get_shapes(image_shape, pyramid_levels): + shape = layer_shapes(image_shape, model) + image_shapes = [shape["P{}".format(level)][1:3] for level in pyramid_levels] + return image_shapes + + return get_shapes + + +def guess_shapes(image_shape, pyramid_levels): + """Guess shapes based on pyramid levels. + + Args + image_shape: The shape of the image. + pyramid_levels: A list of what pyramid levels are used. + + Returns + A list of image shapes at each pyramid level. + """ + image_shape = np.array(image_shape[:2]) + image_shapes = [(image_shape + 2 ** x - 1) // (2 ** x) for x in pyramid_levels] + return image_shapes def anchors_for_shape( image_shape, pyramid_levels=None, - ratios=None, - scales=None, - strides=None, - sizes=None + anchor_params=None, + shapes_callback=None, ): + """ Generators anchors for a given shape. + + Args + image_shape: The shape of the image. + pyramid_levels: List of ints representing which pyramids to use (defaults to [3, 4, 5, 6, 7]). + anchor_params: Struct containing anchor parameters. If None, default values are used. + shapes_callback: Function to call for getting the shape of the image at different pyramid levels. + + Returns + np.array of shape (N, 4) containing the (x1, y1, x2, y2) coordinates for the anchors. + """ + if pyramid_levels is None: pyramid_levels = [3, 4, 5, 6, 7] - if strides is None: - strides = [2 ** x for x in pyramid_levels] - if sizes is None: - sizes = [2 ** (x + 2) for x in pyramid_levels] - if ratios is None: - ratios = np.array([0.5, 1, 2]) - if scales is None: - scales = np.array([2 ** 0, 2 ** (1.0 / 3.0), 2 ** (2.0 / 3.0)]) - # skip the first two levels - image_shape = np.array(image_shape[:2]) - for i in range(pyramid_levels[0] - 1): - image_shape = (image_shape + 1) // 2 + if anchor_params is None: + anchor_params = AnchorParameters.default + + if shapes_callback is None: + shapes_callback = guess_shapes + image_shapes = shapes_callback(image_shape, pyramid_levels) # compute anchors over all pyramid levels all_anchors = np.zeros((0, 4)) for idx, p in enumerate(pyramid_levels): - image_shape = (image_shape + 1) // 2 - anchors = generate_anchors(base_size=sizes[idx], ratios=ratios, scales=scales) - shifted_anchors = shift(image_shape, strides[idx], anchors) + anchors = generate_anchors( + base_size=anchor_params.sizes[idx], + ratios=anchor_params.ratios, + scales=anchor_params.scales + ) + shifted_anchors = shift(image_shapes[idx], anchor_params.strides[idx], anchors) all_anchors = np.append(all_anchors, shifted_anchors, axis=0) return all_anchors def shift(shape, stride, anchors): + """ Produce shifted anchors based on shape of the map and stride size. + + Args + shape : Shape to shift the anchors over. + stride : Stride to shift the anchors with over the shape. + anchors: The anchors to apply at each location. + """ + + # create a grid starting from half stride from the top left corner shift_x = (np.arange(0, shape[1]) + 0.5) * stride shift_y = (np.arange(0, shape[0]) + 0.5) * stride @@ -126,10 +251,10 @@ def generate_anchors(base_size=16, ratios=None, scales=None): """ if ratios is None: - ratios = np.array([0.5, 1, 2]) + ratios = AnchorParameters.default.ratios if scales is None: - scales = np.array([2 ** 0, 2 ** (1.0 / 3.0), 2 ** (2.0 / 3.0)]) + scales = AnchorParameters.default.scales num_anchors = len(ratios) * len(scales) @@ -156,10 +281,13 @@ def generate_anchors(base_size=16, ratios=None, scales=None): def bbox_transform(anchors, gt_boxes, mean=None, std=None): """Compute bounding-box regression targets for an image.""" + # The Mean and std are calculated from COCO dataset. + # Bounding box normalization was firstly introduced in the Fast R-CNN paper. + # See https://github.com/fizyr/keras-retinanet/issues/1273#issuecomment-585828825 for more details if mean is None: mean = np.array([0, 0, 0, 0]) if std is None: - std = np.array([0.1, 0.1, 0.2, 0.2]) + std = np.array([0.2, 0.2, 0.2, 0.2]) if isinstance(mean, (list, tuple)): mean = np.array(mean) @@ -173,53 +301,18 @@ def bbox_transform(anchors, gt_boxes, mean=None, std=None): anchor_widths = anchors[:, 2] - anchors[:, 0] anchor_heights = anchors[:, 3] - anchors[:, 1] - anchor_ctr_x = anchors[:, 0] + 0.5 * anchor_widths - anchor_ctr_y = anchors[:, 1] + 0.5 * anchor_heights - - gt_widths = gt_boxes[:, 2] - gt_boxes[:, 0] - gt_heights = gt_boxes[:, 3] - gt_boxes[:, 1] - gt_ctr_x = gt_boxes[:, 0] + 0.5 * gt_widths - gt_ctr_y = gt_boxes[:, 1] + 0.5 * gt_heights - # clip widths to 1 - gt_widths = np.maximum(gt_widths, 1) - gt_heights = np.maximum(gt_heights, 1) + # According to the information provided by a keras-retinanet author, they got marginally better results using + # the following way of bounding box parametrization. + # See https://github.com/fizyr/keras-retinanet/issues/1273#issuecomment-585828825 for more details + targets_dx1 = (gt_boxes[:, 0] - anchors[:, 0]) / anchor_widths + targets_dy1 = (gt_boxes[:, 1] - anchors[:, 1]) / anchor_heights + targets_dx2 = (gt_boxes[:, 2] - anchors[:, 2]) / anchor_widths + targets_dy2 = (gt_boxes[:, 3] - anchors[:, 3]) / anchor_heights - targets_dx = (gt_ctr_x - anchor_ctr_x) / anchor_widths - targets_dy = (gt_ctr_y - anchor_ctr_y) / anchor_heights - targets_dw = np.log(gt_widths / anchor_widths) - targets_dh = np.log(gt_heights / anchor_heights) - - targets = np.stack((targets_dx, targets_dy, targets_dw, targets_dh)) + targets = np.stack((targets_dx1, targets_dy1, targets_dx2, targets_dy2)) targets = targets.T targets = (targets - mean) / std return targets - - -def compute_overlap(a, b): - """ - Parameters - ---------- - a: (N, 4) ndarray of float - b: (K, 4) ndarray of float - Returns - ------- - overlaps: (N, K) ndarray of overlap between boxes and query_boxes - """ - area = (b[:, 2] - b[:, 0]) * (b[:, 3] - b[:, 1]) - - iw = np.minimum(np.expand_dims(a[:, 2], axis=1), b[:, 2]) - np.maximum(np.expand_dims(a[:, 0], 1), b[:, 0]) - ih = np.minimum(np.expand_dims(a[:, 3], axis=1), b[:, 3]) - np.maximum(np.expand_dims(a[:, 1], 1), b[:, 1]) - - iw = np.maximum(iw, 0) - ih = np.maximum(ih, 0) - - ua = np.expand_dims((a[:, 2] - a[:, 0]) * (a[:, 3] - a[:, 1]), axis=1) + area - iw * ih - - ua = np.maximum(ua, np.finfo(float).eps) - - intersection = iw * ih - - return intersection / ua diff --git a/imageai/Detection/keras_retinanet/utils/coco_eval.py b/imageai/Detection/keras_retinanet/utils/coco_eval.py index 72b3062c..7ad020e9 100644 --- a/imageai/Detection/keras_retinanet/utils/coco_eval.py +++ b/imageai/Detection/keras_retinanet/utils/coco_eval.py @@ -14,49 +14,57 @@ limitations under the License. """ -from __future__ import print_function - -from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval +from tensorflow import keras import numpy as np import json -import os + +import progressbar +assert(callable(progressbar.progressbar)), "Using wrong progressbar module, install 'progressbar2' instead." def evaluate_coco(generator, model, threshold=0.05): + """ Use the pycocotools to evaluate a COCO model on a dataset. + + Args + generator : The generator for generating the evaluation data. + model : The model to evaluate. + threshold : The score threshold to use. + """ # start collecting results results = [] image_ids = [] - for index in range(generator.size()): + for index in progressbar.progressbar(range(generator.size()), prefix='COCO evaluation: '): image = generator.load_image(index) image = generator.preprocess_image(image) image, scale = generator.resize_image(image) - # run network - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) + if keras.backend.image_data_format() == 'channels_first': + image = image.transpose((2, 0, 1)) - # clip to image shape - detections[:, :, 0] = np.maximum(0, detections[:, :, 0]) - detections[:, :, 1] = np.maximum(0, detections[:, :, 1]) - detections[:, :, 2] = np.minimum(image.shape[1], detections[:, :, 2]) - detections[:, :, 3] = np.minimum(image.shape[0], detections[:, :, 3]) + # run network + boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0)) # correct boxes for image scale - detections[0, :, :4] /= scale + boxes /= scale # change to (x, y, w, h) (MS COCO standard) - detections[:, :, 2] -= detections[:, :, 0] - detections[:, :, 3] -= detections[:, :, 1] + boxes[:, :, 2] -= boxes[:, :, 0] + boxes[:, :, 3] -= boxes[:, :, 1] # compute predicted labels and scores - for i, j in np.transpose(np.where(detections[0, :, 4:] > threshold)): - # append detections for each positively labeled class + for box, score, label in zip(boxes[0], scores[0], labels[0]): + # scores are sorted, so we can break + if score < threshold: + break + + # append detection for each positively labeled class image_result = { 'image_id' : generator.image_ids[index], - 'category_id' : generator.label_to_coco_label(j), - 'score' : float(detections[0, i, 4 + j]), - 'bbox' : (detections[0, i, :4]).tolist(), + 'category_id' : generator.label_to_coco_label(label), + 'score' : float(score), + 'bbox' : box.tolist(), } # append detection to results @@ -65,9 +73,6 @@ def evaluate_coco(generator, model, threshold=0.05): # append image to list of processed images image_ids.append(generator.image_ids[index]) - # print progress - print('{}/{}'.format(index, generator.size()), end='\r') - if not len(results): return @@ -85,3 +90,4 @@ def evaluate_coco(generator, model, threshold=0.05): coco_eval.evaluate() coco_eval.accumulate() coco_eval.summarize() + return coco_eval.stats diff --git a/imageai/Detection/keras_retinanet/utils/colors.py b/imageai/Detection/keras_retinanet/utils/colors.py index e3674ac2..7f1b6850 100644 --- a/imageai/Detection/keras_retinanet/utils/colors.py +++ b/imageai/Detection/keras_retinanet/utils/colors.py @@ -18,6 +18,7 @@ def label_color(label): warnings.warn('Label {} has no color, returning default.'.format(label)) return (0, 255, 0) + """ Generated using: diff --git a/imageai/Detection/keras_retinanet/utils/compute_overlap.pyx b/imageai/Detection/keras_retinanet/utils/compute_overlap.pyx new file mode 100644 index 00000000..e8b79301 --- /dev/null +++ b/imageai/Detection/keras_retinanet/utils/compute_overlap.pyx @@ -0,0 +1,53 @@ +# -------------------------------------------------------- +# Fast R-CNN +# Copyright (c) 2015 Microsoft +# Licensed under The MIT License [see LICENSE for details] +# Written by Sergey Karayev +# -------------------------------------------------------- + +cimport cython +import numpy as np +cimport numpy as np + + +def compute_overlap( + np.ndarray[double, ndim=2] boxes, + np.ndarray[double, ndim=2] query_boxes +): + """ + Args + a: (N, 4) ndarray of float + b: (K, 4) ndarray of float + + Returns + overlaps: (N, K) ndarray of overlap between boxes and query_boxes + """ + cdef unsigned int N = boxes.shape[0] + cdef unsigned int K = query_boxes.shape[0] + cdef np.ndarray[double, ndim=2] overlaps = np.zeros((N, K), dtype=np.float64) + cdef double iw, ih, box_area + cdef double ua + cdef unsigned int k, n + for k in range(K): + box_area = ( + (query_boxes[k, 2] - query_boxes[k, 0]) * + (query_boxes[k, 3] - query_boxes[k, 1]) + ) + for n in range(N): + iw = ( + min(boxes[n, 2], query_boxes[k, 2]) - + max(boxes[n, 0], query_boxes[k, 0]) + ) + if iw > 0: + ih = ( + min(boxes[n, 3], query_boxes[k, 3]) - + max(boxes[n, 1], query_boxes[k, 1]) + ) + if ih > 0: + ua = np.float64( + (boxes[n, 2] - boxes[n, 0]) * + (boxes[n, 3] - boxes[n, 1]) + + box_area - iw * ih + ) + overlaps[n, k] = iw * ih / ua + return overlaps diff --git a/imageai/Detection/keras_retinanet/utils/config.py b/imageai/Detection/keras_retinanet/utils/config.py new file mode 100644 index 00000000..58de9228 --- /dev/null +++ b/imageai/Detection/keras_retinanet/utils/config.py @@ -0,0 +1,57 @@ +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import configparser +import numpy as np +from tensorflow import keras +from ..utils.anchors import AnchorParameters + + +def read_config_file(config_path): + config = configparser.ConfigParser() + + with open(config_path, 'r') as file: + config.read_file(file) + + assert 'anchor_parameters' in config, \ + "Malformed config file. Verify that it contains the anchor_parameters section." + + config_keys = set(config['anchor_parameters']) + default_keys = set(AnchorParameters.default.__dict__.keys()) + + assert config_keys <= default_keys, \ + "Malformed config file. These keys are not valid: {}".format(config_keys - default_keys) + + if 'pyramid_levels' in config: + assert('levels' in config['pyramid_levels']), "pyramid levels specified by levels key" + + return config + + +def parse_anchor_parameters(config): + ratios = np.array(list(map(float, config['anchor_parameters']['ratios'].split(' '))), keras.backend.floatx()) + scales = np.array(list(map(float, config['anchor_parameters']['scales'].split(' '))), keras.backend.floatx()) + sizes = list(map(int, config['anchor_parameters']['sizes'].split(' '))) + strides = list(map(int, config['anchor_parameters']['strides'].split(' '))) + assert (len(sizes) == len(strides)), "sizes and strides should have an equal number of values" + + return AnchorParameters(sizes, strides, ratios, scales) + + +def parse_pyramid_levels(config): + levels = list(map(int, config['pyramid_levels']['levels'].split(' '))) + + return levels diff --git a/imageai/Detection/keras_retinanet/utils/eval.py b/imageai/Detection/keras_retinanet/utils/eval.py index 5bdd8004..f6b723fc 100644 --- a/imageai/Detection/keras_retinanet/utils/eval.py +++ b/imageai/Detection/keras_retinanet/utils/eval.py @@ -14,16 +14,17 @@ limitations under the License. """ -from __future__ import print_function - from .anchors import compute_overlap from .visualization import draw_detections, draw_annotations +from tensorflow import keras import numpy as np import os +import time import cv2 -import pickle +import progressbar +assert(callable(progressbar.progressbar)), "Using wrong progressbar module, install 'progressbar2' instead." def _compute_ap(recall, precision): @@ -70,56 +71,56 @@ def _get_detections(generator, model, score_threshold=0.05, max_detections=100, # Returns A list of lists containing the detections for each image in the generator. """ - all_detections = [[None for i in range(generator.num_classes())] for j in range(generator.size())] + all_detections = [[None for i in range(generator.num_classes()) if generator.has_label(i)] for j in range(generator.size())] + all_inferences = [None for i in range(generator.size())] - for i in range(generator.size()): + for i in progressbar.progressbar(range(generator.size()), prefix='Running network: '): raw_image = generator.load_image(i) - image = generator.preprocess_image(raw_image.copy()) - image, scale = generator.resize_image(image) + image, scale = generator.resize_image(raw_image.copy()) + image = generator.preprocess_image(image) - # run network - _, _, detections = model.predict_on_batch(np.expand_dims(image, axis=0)) + if keras.backend.image_data_format() == 'channels_first': + image = image.transpose((2, 0, 1)) - # clip to image shape - detections[:, :, 0] = np.maximum(0, detections[:, :, 0]) - detections[:, :, 1] = np.maximum(0, detections[:, :, 1]) - detections[:, :, 2] = np.minimum(image.shape[1], detections[:, :, 2]) - detections[:, :, 3] = np.minimum(image.shape[0], detections[:, :, 3]) + # run network + start = time.time() + boxes, scores, labels = model.predict_on_batch(np.expand_dims(image, axis=0))[:3] + inference_time = time.time() - start # correct boxes for image scale - detections[0, :, :4] /= scale - - # select scores from detections - scores = detections[0, :, 4:] + boxes /= scale # select indices which have a score above the threshold - indices = np.where(detections[0, :, 4:] > score_threshold) + indices = np.where(scores[0, :] > score_threshold)[0] # select those scores - scores = scores[indices] + scores = scores[0][indices] # find the order with which to sort the scores scores_sort = np.argsort(-scores)[:max_detections] # select detections - image_boxes = detections[0, indices[0][scores_sort], :4] - image_scores = np.expand_dims(detections[0, indices[0][scores_sort], 4 + indices[1][scores_sort]], axis=1) - image_detections = np.append(image_boxes, image_scores, axis=1) - image_predicted_labels = indices[1][scores_sort] + image_boxes = boxes[0, indices[scores_sort], :] + image_scores = scores[scores_sort] + image_labels = labels[0, indices[scores_sort]] + image_detections = np.concatenate([image_boxes, np.expand_dims(image_scores, axis=1), np.expand_dims(image_labels, axis=1)], axis=1) if save_path is not None: - draw_annotations(raw_image, generator.load_annotations(i), generator=generator) - draw_detections(raw_image, detections[0, indices[0][scores_sort], :], generator=generator) + draw_annotations(raw_image, generator.load_annotations(i), label_to_name=generator.label_to_name) + draw_detections(raw_image, image_boxes, image_scores, image_labels, label_to_name=generator.label_to_name, score_threshold=score_threshold) cv2.imwrite(os.path.join(save_path, '{}.png'.format(i)), raw_image) # copy detections to all_detections for label in range(generator.num_classes()): - all_detections[i][label] = image_detections[image_predicted_labels == label, :] + if not generator.has_label(label): + continue - print('{}/{}'.format(i, generator.size()), end='\r') + all_detections[i][label] = image_detections[image_detections[:, -1] == label, :-1] - return all_detections + all_inferences[i] = inference_time + + return all_detections, all_inferences def _get_annotations(generator): @@ -135,15 +136,16 @@ def _get_annotations(generator): """ all_annotations = [[None for i in range(generator.num_classes())] for j in range(generator.size())] - for i in range(generator.size()): + for i in progressbar.progressbar(range(generator.size()), prefix='Parsing annotations: '): # load the annotations annotations = generator.load_annotations(i) # copy detections to all_annotations for label in range(generator.num_classes()): - all_annotations[i][label] = annotations[annotations[:, 4] == label, :4].copy() + if not generator.has_label(label): + continue - print('{}/{}'.format(i, generator.size()), end='\r') + all_annotations[i][label] = annotations['bboxes'][annotations['labels'] == label, :].copy() return all_annotations @@ -169,9 +171,9 @@ def evaluate( A dict mapping class names to mAP scores. """ # gather all detections and annotations - all_detections = _get_detections(generator, model, score_threshold=score_threshold, max_detections=max_detections, save_path=save_path) + all_detections, all_inferences = _get_detections(generator, model, score_threshold=score_threshold, max_detections=max_detections, save_path=save_path) all_annotations = _get_annotations(generator) - average_precisions = dict() + average_precisions = {} # all_detections = pickle.load(open('all_detections.pkl', 'rb')) # all_annotations = pickle.load(open('all_annotations.pkl', 'rb')) @@ -180,6 +182,9 @@ def evaluate( # process detections and annotations for label in range(generator.num_classes()): + if not generator.has_label(label): + continue + false_positives = np.zeros((0,)) true_positives = np.zeros((0,)) scores = np.zeros((0,)) @@ -213,7 +218,7 @@ def evaluate( # no annotations -> AP for this class is 0 (is this correct?) if num_annotations == 0: - average_precisions[label] = 0 + average_precisions[label] = 0, 0 continue # sort by score @@ -231,6 +236,9 @@ def evaluate( # compute average precision average_precision = _compute_ap(recall, precision) - average_precisions[label] = average_precision + average_precisions[label] = average_precision, num_annotations + + # inference time + inference_time = np.sum(all_inferences) / generator.size() - return average_precisions + return average_precisions, inference_time diff --git a/imageai/Detection/keras_retinanet/utils/gpu.py b/imageai/Detection/keras_retinanet/utils/gpu.py new file mode 100644 index 00000000..067f30b4 --- /dev/null +++ b/imageai/Detection/keras_retinanet/utils/gpu.py @@ -0,0 +1,43 @@ +""" +Copyright 2017-2019 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import tensorflow as tf + + +def setup_gpu(gpu_id): + try: + visible_gpu_indices = [int(id) for id in gpu_id.split(',')] + available_gpus = tf.config.list_physical_devices('GPU') + visible_gpus = [gpu for idx, gpu in enumerate(available_gpus) if idx in visible_gpu_indices] + + if visible_gpus: + try: + # Currently, memory growth needs to be the same across GPUs. + for gpu in available_gpus: + tf.config.experimental.set_memory_growth(gpu, True) + + # Use only the selcted gpu. + tf.config.set_visible_devices(visible_gpus, 'GPU') + except RuntimeError as e: + # Visible devices must be set before GPUs have been initialized. + print(e) + + logical_gpus = tf.config.list_logical_devices('GPU') + print(len(available_gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs") + else: + tf.config.set_visible_devices([], 'GPU') + except ValueError: + tf.config.set_visible_devices([], 'GPU') diff --git a/imageai/Detection/keras_retinanet/utils/image.py b/imageai/Detection/keras_retinanet/utils/image.py index 65a5514f..b3116cd9 100644 --- a/imageai/Detection/keras_retinanet/utils/image.py +++ b/imageai/Detection/keras_retinanet/utils/image.py @@ -15,46 +15,48 @@ """ from __future__ import division -import keras -import time import numpy as np -import scipy.ndimage as ndi import cv2 from PIL import Image -from .transform import change_transform_origin, transform_aabb +from .transform import change_transform_origin def read_image_bgr(path): - image = np.asarray(Image.open(path).convert('RGB')) - return image[:, :, ::-1].copy() + """ Read an image in BGR format. + + Args + path: Path to the image. + """ + # We deliberately don't use cv2.imread here, since it gives no feedback on errors while reading the image. + image = np.ascontiguousarray(Image.open(path).convert('RGB')) + return image[:, :, ::-1] -def read_image_array(image_array): - image = np.asarray(Image.fromarray(np.uint8(image_array))) - return image[:, :, ::-1].copy() -def read_image_stream(image_stream): - image = np.asarray(Image.open(image_stream)) - return image[:, :, ::-1].copy() +def preprocess_image(x, mode='caffe'): + """ Preprocess an image by subtracting the ImageNet mean. + Args + x: np.array of shape (None, None, 3) or (3, None, None). + mode: One of "caffe" or "tf". + - caffe: will zero-center each color channel with + respect to the ImageNet dataset, without scaling. + - tf: will scale pixels between -1 and 1, sample-wise. -def preprocess_image(x): - # mostly identical to "https://github.com/fchollet/keras/blob/master/keras/applications/imagenet_utils.py" + Returns + The input with the ImageNet mean subtracted. + """ + # mostly identical to "https://github.com/keras-team/keras-applications/blob/master/keras_applications/imagenet_utils.py" # except for converting RGB -> BGR since we assume BGR already - x = x.astype(keras.backend.floatx()) - if keras.backend.image_data_format() == 'channels_first': - if x.ndim == 3: - x[0, :, :] -= 103.939 - x[1, :, :] -= 116.779 - x[2, :, :] -= 123.68 - else: - x[:, 0, :, :] -= 103.939 - x[:, 1, :, :] -= 116.779 - x[:, 2, :, :] -= 123.68 - else: - x[..., 0] -= 103.939 - x[..., 1] -= 116.779 - x[..., 2] -= 123.68 + + # covert always to float32 to keep compatibility with opencv + x = x.astype(np.float32) + + if mode == 'tf': + x /= 127.5 + x -= 1. + elif mode == 'caffe': + x -= [103.939, 116.779, 123.68] return x @@ -82,11 +84,10 @@ def adjust_transform_for_image(transform, image, relative_translation): class TransformParameters: """ Struct holding parameters determining how to apply a transformation to an image. - # Arguments + Args fill_mode: One of: 'constant', 'nearest', 'reflect', 'wrap' interpolation: One of: 'nearest', 'linear', 'cubic', 'area', 'lanczos4' cval: Fill value to use with fill_mode='constant' - data_format: Same as for keras.preprocessing.image.apply_transform relative_translation: If true (the default), interpret translation as a factor of the image size. If false, interpret it as absolute pixels. """ @@ -95,7 +96,6 @@ def __init__( fill_mode = 'nearest', interpolation = 'linear', cval = 0, - data_format = None, relative_translation = True, ): self.fill_mode = fill_mode @@ -103,17 +103,6 @@ def __init__( self.interpolation = interpolation self.relative_translation = relative_translation - if data_format is None: - data_format = keras.backend.image_data_format() - self.data_format = data_format - - if data_format == 'channels_first': - self.channel_axis = 0 - elif data_format == 'channels_last': - self.channel_axis = 2 - else: - raise ValueError("invalid data_format, expected 'channels_first' or 'channels_last', got '{}'".format(data_format)) - def cvBorderMode(self): if self.fill_mode == 'constant': return cv2.BORDER_CONSTANT @@ -146,14 +135,11 @@ def apply_transform(matrix, image, params): The matrix is interpreted such that a point (x, y) on the original image is moved to transform * (x, y) in the generated image. Mathematically speaking, that means that the matrix is a transformation from the transformed image space to the original image space. - Parameters: - matrix: A homogenous 3 by 3 matrix holding representing the transformation to apply. + Args + matrix: A homogeneous 3 by 3 matrix holding representing the transformation to apply. image: The image to transform. params: The transform parameters (see TransformParameters) """ - if params.channel_axis != 2: - image = np.moveaxis(image, params.channel_axis, 2) - output = cv2.warpAffine( image, matrix[:2, :], @@ -162,27 +148,209 @@ def apply_transform(matrix, image, params): borderMode = params.cvBorderMode(), borderValue = params.cval, ) - - if params.channel_axis != 2: - output = np.moveaxis(output, 2, params.channel_axis) return output -def resize_image(img, min_side=800, max_side=1333): - (rows, cols, _) = img.shape +def compute_resize_scale(image_shape, min_side=800, max_side=1333): + """ Compute an image scale such that the image size is constrained to min_side and max_side. + + Args + min_side: The image's min side will be equal to min_side after resizing. + max_side: If after resizing the image's max side is above max_side, resize until the max side is equal to max_side. + + Returns + A resizing scale. + """ + (rows, cols, _) = image_shape smallest_side = min(rows, cols) # rescale the image so the smallest side is min_side scale = min_side / smallest_side - # check if the largest side is now greater than max_side, wich can happen + # check if the largest side is now greater than max_side, which can happen # when images have a large aspect ratio largest_side = max(rows, cols) if largest_side * scale > max_side: scale = max_side / largest_side + return scale + + +def resize_image(img, min_side=800, max_side=1333): + """ Resize an image such that the size is constrained to min_side and max_side. + + Args + min_side: The image's min side will be equal to min_side after resizing. + max_side: If after resizing the image's max side is above max_side, resize until the max side is equal to max_side. + + Returns + A resized image. + """ + # compute scale to resize the image + scale = compute_resize_scale(img.shape, min_side=min_side, max_side=max_side) + # resize the image with the computed scale img = cv2.resize(img, None, fx=scale, fy=scale) return img, scale + + +def _uniform(val_range): + """ Uniformly sample from the given range. + + Args + val_range: A pair of lower and upper bound. + """ + return np.random.uniform(val_range[0], val_range[1]) + + +def _check_range(val_range, min_val=None, max_val=None): + """ Check whether the range is a valid range. + + Args + val_range: A pair of lower and upper bound. + min_val: Minimal value for the lower bound. + max_val: Maximal value for the upper bound. + """ + if val_range[0] > val_range[1]: + raise ValueError('interval lower bound > upper bound') + if min_val is not None and val_range[0] < min_val: + raise ValueError('invalid interval lower bound') + if max_val is not None and val_range[1] > max_val: + raise ValueError('invalid interval upper bound') + + +def _clip(image): + """ + Clip and convert an image to np.uint8. + + Args + image: Image to clip. + """ + return np.clip(image, 0, 255).astype(np.uint8) + + +class VisualEffect: + """ Struct holding parameters and applying image color transformation. + + Args + contrast_factor: A factor for adjusting contrast. Should be between 0 and 3. + brightness_delta: Brightness offset between -1 and 1 added to the pixel values. + hue_delta: Hue offset between -1 and 1 added to the hue channel. + saturation_factor: A factor multiplying the saturation values of each pixel. + """ + + def __init__( + self, + contrast_factor, + brightness_delta, + hue_delta, + saturation_factor, + ): + self.contrast_factor = contrast_factor + self.brightness_delta = brightness_delta + self.hue_delta = hue_delta + self.saturation_factor = saturation_factor + + def __call__(self, image): + """ Apply a visual effect on the image. + + Args + image: Image to adjust + """ + + if self.contrast_factor: + image = adjust_contrast(image, self.contrast_factor) + if self.brightness_delta: + image = adjust_brightness(image, self.brightness_delta) + + if self.hue_delta or self.saturation_factor: + + image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) + + if self.hue_delta: + image = adjust_hue(image, self.hue_delta) + if self.saturation_factor: + image = adjust_saturation(image, self.saturation_factor) + + image = cv2.cvtColor(image, cv2.COLOR_HSV2BGR) + + return image + + +def random_visual_effect_generator( + contrast_range=(0.9, 1.1), + brightness_range=(-.1, .1), + hue_range=(-0.05, 0.05), + saturation_range=(0.95, 1.05) +): + """ Generate visual effect parameters uniformly sampled from the given intervals. + + Args + contrast_factor: A factor interval for adjusting contrast. Should be between 0 and 3. + brightness_delta: An interval between -1 and 1 for the amount added to the pixels. + hue_delta: An interval between -1 and 1 for the amount added to the hue channel. + The values are rotated if they exceed 180. + saturation_factor: An interval for the factor multiplying the saturation values of each + pixel. + """ + _check_range(contrast_range, 0) + _check_range(brightness_range, -1, 1) + _check_range(hue_range, -1, 1) + _check_range(saturation_range, 0) + + def _generate(): + while True: + yield VisualEffect( + contrast_factor=_uniform(contrast_range), + brightness_delta=_uniform(brightness_range), + hue_delta=_uniform(hue_range), + saturation_factor=_uniform(saturation_range), + ) + + return _generate() + + +def adjust_contrast(image, factor): + """ Adjust contrast of an image. + + Args + image: Image to adjust. + factor: A factor for adjusting contrast. + """ + mean = image.mean(axis=0).mean(axis=0) + return _clip((image - mean) * factor + mean) + + +def adjust_brightness(image, delta): + """ Adjust brightness of an image + + Args + image: Image to adjust. + delta: Brightness offset between -1 and 1 added to the pixel values. + """ + return _clip(image + delta * 255) + + +def adjust_hue(image, delta): + """ Adjust hue of an image. + + Args + image: Image to adjust. + delta: An interval between -1 and 1 for the amount added to the hue channel. + The values are rotated if they exceed 180. + """ + image[..., 0] = np.mod(image[..., 0] + delta * 180, 180) + return image + + +def adjust_saturation(image, factor): + """ Adjust saturation of an image. + + Args + image: Image to adjust. + factor: An interval for the factor multiplying the saturation values of each pixel. + """ + image[..., 1] = np.clip(image[..., 1] * factor, 0 , 255) + return image diff --git a/imageai/Detection/keras_retinanet/utils/keras_version.py b/imageai/Detection/keras_retinanet/utils/keras_version.py deleted file mode 100644 index 75b2caaa..00000000 --- a/imageai/Detection/keras_retinanet/utils/keras_version.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Copyright 2017-2018 Fizyr (https://fizyr.com) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from __future__ import print_function - -import keras -import sys - -minimum_keras_version = 2, 1, 3 - - -def keras_version(): - return tuple(map(int, keras.__version__.split('.'))) - - -def keras_version_ok(): - return keras_version() >= minimum_keras_version - - -def assert_keras_version(): - detected = keras.__version__ - required = '.'.join(map(str, minimum_keras_version)) - assert(keras_version() >= minimum_keras_version), 'You are using keras version {}. The minimum required version is {}.'.format(detected, required) - - -def check_keras_version(): - try: - assert_keras_version() - except AssertionError as e: - print(e, file=sys.stderr) - sys.exit(1) diff --git a/imageai/Detection/keras_retinanet/utils/tf_version.py b/imageai/Detection/keras_retinanet/utils/tf_version.py new file mode 100644 index 00000000..5a9aa90b --- /dev/null +++ b/imageai/Detection/keras_retinanet/utils/tf_version.py @@ -0,0 +1,55 @@ +""" +Copyright 2017-2019 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from __future__ import print_function + +import tensorflow as tf +import sys + +MINIMUM_TF_VERSION = 2, 3, 0 +BLACKLISTED_TF_VERSIONS = [] + + +def tf_version(): + """ Get the Tensorflow version. + Returns + tuple of (major, minor, patch). + """ + return tuple(map(int, tf.version.VERSION.split('-')[0].split('.'))) + + +def tf_version_ok(minimum_tf_version=MINIMUM_TF_VERSION, blacklisted=BLACKLISTED_TF_VERSIONS): + """ Check if the current Tensorflow version is higher than the minimum version. + """ + return tf_version() >= minimum_tf_version and tf_version() not in blacklisted + + +def assert_tf_version(minimum_tf_version=MINIMUM_TF_VERSION, blacklisted=BLACKLISTED_TF_VERSIONS): + """ Assert that the Tensorflow version is up to date. + """ + detected = tf.version.VERSION + required = '.'.join(map(str, minimum_tf_version)) + assert(tf_version_ok(minimum_tf_version, blacklisted)), 'You are using tensorflow version {}. The minimum required version is {} (blacklisted: {}).'.format(detected, required, blacklisted) + + +def check_tf_version(): + """ Check that the Tensorflow version is up to date. If it isn't, print an error message and exit the script. + """ + try: + assert_tf_version() + except AssertionError as e: + print(e, file=sys.stderr) + sys.exit(1) diff --git a/imageai/Detection/keras_retinanet/utils/transform.py b/imageai/Detection/keras_retinanet/utils/transform.py index 12be7bdc..4c6afe62 100644 --- a/imageai/Detection/keras_retinanet/utils/transform.py +++ b/imageai/Detection/keras_retinanet/utils/transform.py @@ -1,3 +1,19 @@ +""" +Copyright 2017-2018 Fizyr (https://fizyr.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + import numpy as np DEFAULT_PRNG = np.random @@ -14,13 +30,13 @@ def transform_aabb(transform, aabb): The result is a new AABB in the same coordinate system as the original AABB. The new AABB contains all corner points of the original AABB after applying the given transformation. - # Arguments - transform: The transormation to apply. - x1: The minimum X value of the AABB. + Args + transform: The transformation to apply. + x1: The minimum x value of the AABB. y1: The minimum y value of the AABB. - x2: The maximum X value of the AABB. + x2: The maximum x value of the AABB. y2: The maximum y value of the AABB. - # Returns + Returns The new AABB as tuple (x1, y1, x2, y2) """ x1, y1, x2, y2 = aabb @@ -40,7 +56,7 @@ def transform_aabb(transform, aabb): def _random_vector(min, max, prng=DEFAULT_PRNG): """ Construct a random vector between min and max. - # Arguments + Args min: the minimum value for each component max: the maximum value for each component """ @@ -53,9 +69,9 @@ def _random_vector(min, max, prng=DEFAULT_PRNG): def rotation(angle): """ Construct a homogeneous 2D rotation matrix. - # Arguments + Args angle: the angle in radians - # Returns + Returns the rotation matrix as 3 by 3 numpy array """ return np.array([ @@ -67,11 +83,11 @@ def rotation(angle): def random_rotation(min, max, prng=DEFAULT_PRNG): """ Construct a random rotation between -max and max. - # Arguments - min: a scalar for the minumum absolute angle in radians + Args + min: a scalar for the minimum absolute angle in radians max: a scalar for the maximum absolute angle in radians prng: the pseudo-random number generator to use. - # Returns + Returns a homogeneous 3 by 3 rotation matrix """ return rotation(prng.uniform(min, max)) @@ -93,11 +109,11 @@ def translation(translation): def random_translation(min, max, prng=DEFAULT_PRNG): """ Construct a random 2D translation between min and max. - # Arguments - min: a 2D vector with the minumum translation for each dimension + Args + min: a 2D vector with the minimum translation for each dimension max: a 2D vector with the maximum translation for each dimension prng: the pseudo-random number generator to use. - # Returns + Returns a homogeneous 3 by 3 translation matrix """ return translation(_random_vector(min, max, prng)) @@ -105,9 +121,9 @@ def random_translation(min, max, prng=DEFAULT_PRNG): def shear(angle): """ Construct a homogeneous 2D shear matrix. - # Arguments + Args angle: the shear angle in radians - # Returns + Returns the shear matrix as 3 by 3 numpy array """ return np.array([ @@ -119,11 +135,11 @@ def shear(angle): def random_shear(min, max, prng=DEFAULT_PRNG): """ Construct a random 2D shear matrix with shear angle between -max and max. - # Arguments - min: the minumum shear angle in radians. + Args + min: the minimum shear angle in radians. max: the maximum shear angle in radians. prng: the pseudo-random number generator to use. - # Returns + Returns a homogeneous 3 by 3 shear matrix """ return shear(prng.uniform(min, max)) @@ -131,9 +147,9 @@ def random_shear(min, max, prng=DEFAULT_PRNG): def scaling(factor): """ Construct a homogeneous 2D scaling matrix. - # Arguments + Args factor: a 2D vector for X and Y scaling - # Returns + Returns the zoom matrix as 3 by 3 numpy array """ return np.array([ @@ -145,11 +161,11 @@ def scaling(factor): def random_scaling(min, max, prng=DEFAULT_PRNG): """ Construct a random 2D scale matrix between -max and max. - # Arguments + Args min: a 2D vector containing the minimum scaling factor for X and Y. min: a 2D vector containing The maximum scaling factor for X and Y. prng: the pseudo-random number generator to use. - # Returns + Returns a homogeneous 3 by 3 scaling matrix """ return scaling(_random_vector(min, max, prng)) @@ -157,11 +173,11 @@ def random_scaling(min, max, prng=DEFAULT_PRNG): def random_flip(flip_x_chance, flip_y_chance, prng=DEFAULT_PRNG): """ Construct a transformation randomly containing X/Y flips (or not). - # Arguments + Args flip_x_chance: The chance that the result will contain a flip along the X axis. flip_y_chance: The chance that the result will contain a flip along the Y axis. prng: The pseudo-random number generator to use. - # Returns + Returns a homogeneous 3 by 3 transformation matrix """ flip_x = prng.uniform(0, 1) < flip_x_chance @@ -173,10 +189,10 @@ def random_flip(flip_x_chance, flip_y_chance, prng=DEFAULT_PRNG): def change_transform_origin(transform, center): """ Create a new transform representing the same transformation, only with the origin of the linear part changed. - # Arguments: + Args transform: the transformation matrix center: the new origin of the transformation - # Return: + Returns translate(center) * transform * translate(-center) """ center = np.array(center) @@ -211,7 +227,7 @@ def random_transform( Set `relative_translation` to `False` in the `TransformParameters` of a data generator to have it interpret the translation directly as pixel distances instead. - # Arguments + Args min_rotation: The minimum rotation in radians for the transform as scalar. max_rotation: The maximum rotation in radians for the transform as scalar. min_translation: The minimum translation for the transform as 2D column vector. @@ -251,7 +267,7 @@ def random_transform_generator(prng=None, **kwargs): Set `relative_translation` to `False` in the `TransformParameters` of a data generator to have it interpret the translation directly as pixel distances instead. - # Arguments + Args min_rotation: The minimum rotation in radians for the transform as scalar. max_rotation: The maximum rotation in radians for the transform as scalar. min_translation: The minimum translation for the transform as 2D column vector. diff --git a/imageai/Detection/keras_retinanet/utils/visualization.py b/imageai/Detection/keras_retinanet/utils/visualization.py index 1cdcdfa1..8aba4c93 100644 --- a/imageai/Detection/keras_retinanet/utils/visualization.py +++ b/imageai/Detection/keras_retinanet/utils/visualization.py @@ -42,8 +42,8 @@ def draw_caption(image, box, caption): caption : String containing the text to draw. """ b = np.array(box).astype(int) - cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 0), 3) - cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (255, 255, 255), 2) + cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 0), 2) + cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (255, 255, 255), 1) def draw_boxes(image, boxes, color, thickness=2): @@ -59,38 +59,48 @@ def draw_boxes(image, boxes, color, thickness=2): draw_box(image, b, color, thickness=thickness) -def draw_detections(image, detections, color=None, generator=None): +def draw_detections(image, boxes, scores, labels, color=None, label_to_name=None, score_threshold=0.5): """ Draws detections in an image. # Arguments - image : The image to draw on. - detections : A [N, 4 + num_classes] matrix (x1, y1, x2, y2, cls_1, cls_2, ...). - color : The color of the boxes. By default the color from keras_retinanet.utils.colors.label_color will be used. - generator : (optional) Generator which can map label to class name. + image : The image to draw on. + boxes : A [N, 4] matrix (x1, y1, x2, y2). + scores : A list of N classification scores. + labels : A list of N labels. + color : The color of the boxes. By default the color from keras_retinanet.utils.colors.label_color will be used. + label_to_name : (optional) Functor for mapping a label to a name. + score_threshold : Threshold used for determining what detections to draw. """ - for d in detections: - label = np.argmax(d[4:]) - c = color if color is not None else label_color(label) - score = d[4 + label] - caption = (generator.label_to_name(label) if generator else str(label)) + ': {0:.2f}'.format(score) - draw_caption(image, d, caption) + selection = np.where(scores > score_threshold)[0] + + for i in selection: + c = color if color is not None else label_color(labels[i]) + draw_box(image, boxes[i, :], color=c) - draw_box(image, d, color=c) + # draw labels + caption = (label_to_name(labels[i]) if label_to_name else labels[i]) + ': {0:.2f}'.format(scores[i]) + draw_caption(image, boxes[i, :], caption) -def draw_annotations(image, annotations, color=(0, 255, 0), generator=None): +def draw_annotations(image, annotations, color=(0, 255, 0), label_to_name=None): """ Draws annotations in an image. # Arguments - image : The image to draw on. - annotations : A [N, 5] matrix (x1, y1, x2, y2, label). - color : The color of the boxes. By default the color from keras_retinanet.utils.colors.label_color will be used. - generator : (optional) Generator which can map label to class name. + image : The image to draw on. + annotations : A [N, 5] matrix (x1, y1, x2, y2, label) or dictionary containing bboxes (shaped [N, 4]) and labels (shaped [N]). + color : The color of the boxes. By default the color from keras_retinanet.utils.colors.label_color will be used. + label_to_name : (optional) Functor for mapping a label to a name. """ - for a in annotations: - label = a[4] - c = color if color is not None else label_color(label) - caption = '{}'.format(generator.label_to_name(label) if generator else label) - draw_caption(image, a, caption) + if isinstance(annotations, np.ndarray): + annotations = {'bboxes': annotations[:, :4], 'labels': annotations[:, 4]} - draw_box(image, a, color=c) + assert('bboxes' in annotations) + assert('labels' in annotations) + assert(annotations['bboxes'].shape[0] == annotations['labels'].shape[0]) + + for i in range(annotations['bboxes'].shape[0]): + label = annotations['labels'][i] + c = color if color is not None else label_color(label) + caption = '{}'.format(label_to_name(label) if label_to_name else label) + draw_caption(image, annotations['bboxes'][i], caption) + draw_box(image, annotations['bboxes'][i], color=c) diff --git a/requirements.txt b/requirements.txt index ca4c3d44..54a3947e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ -tensorflow -keras -numpy -pillow -scipy -h5py -matplotlib +tensorflow==2.4.0 +keras==2.4.3 +numpy==1.19.3 +pillow==7.0.0 +scipy==1.4.1 +h5py==2.10.0 +matplotlib==3.3.2 opencv-python +keras-resnet==0.2.0 diff --git a/setup.py b/setup.py index aa231875..c2ab2093 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ author_email='guymodscientist@gmail.com', license='MIT', packages= find_packages(), - install_requires=['numpy','scipy','pillow',"matplotlib", "h5py"], + install_requires=['numpy==1.19.3','scipy==1.4.1','pillow==7.0.0',"matplotlib==3.3.2", "h5py==2.10.0", "keras-resnet==0.2.0", "opencv-python", "keras==2.4.3"], zip_safe=False ) \ No newline at end of file diff --git a/test/test_custom_recognition.py b/test/test_custom_recognition.py index 0468cb29..8b61a269 100644 --- a/test/test_custom_recognition.py +++ b/test/test_custom_recognition.py @@ -22,10 +22,10 @@ def test_custom_recognition_model_resnet(): predictor = CustomImageClassification() predictor.setModelTypeAsResNet50() - predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_resnet.h5")) + predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_resnet_ex-056_acc-0.993062.h5")) predictor.setJsonPath(model_json=os.path.join(main_folder, "data-json", "idenprof.json")) predictor.loadModel(num_objects=10) - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "9.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "9.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -40,7 +40,7 @@ def test_custom_recognition_model_densenet(): predictor.setModelPath(os.path.join(main_folder, "data-models", "idenprof_densenet-0.763500.h5")) predictor.setJsonPath(model_json=os.path.join(main_folder, "data-json", "idenprof.json")) predictor.loadModel(num_objects=10) - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "9.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "9.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) diff --git a/test/test_custom_video_detection.py b/test/test_custom_video_detection.py index 55a343c1..19adf98a 100644 --- a/test/test_custom_video_detection.py +++ b/test/test_custom_video_detection.py @@ -29,15 +29,8 @@ def test_custom_video_detection_yolov3(): -@pytest.mark.detection -@pytest.mark.custom_detection -@pytest.mark.video_detection -@pytest.mark.custom_video_detection -@pytest.mark.yolov3 -@pytest.mark.custom_video_detection_analysis def test_custom_video_detection_yolov3_analysis(): - detector = CustomVideoObjectDetection() detector.setModelTypeAsYOLOv3() detector.setModelPath(model_path) diff --git a/test/test_image_recognition.py b/test/test_image_recognition.py index 755fe2b0..fe10121d 100644 --- a/test/test_image_recognition.py +++ b/test/test_image_recognition.py @@ -15,7 +15,7 @@ def test_recognition_model_mobilenetv2(): predictor.setModelTypeAsMobileNetV2() predictor.setModelPath(os.path.join(main_folder, "data-models", "mobilenet_v2.h5")) predictor.loadModel() - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -29,7 +29,7 @@ def test_recognition_model_resnet(): predictor.setModelTypeAsResNet50() predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_imagenet_tf.2.0.h5")) predictor.loadModel() - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -43,7 +43,7 @@ def test_recognition_model_inceptionv3(): predictor.setModelTypeAsInceptionV3() predictor.setModelPath(os.path.join(main_folder, "data-models", "inception_v3_weights_tf_dim_ordering_tf_kernels.h5")) predictor.loadModel() - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -57,7 +57,7 @@ def test_recognition_model_densenet(): predictor.setModelTypeAsDenseNet121() predictor.setModelPath(os.path.join(main_folder, "data-models", "DenseNet-BC-121-32.h5")) predictor.loadModel() - predictions, probabilities = predictor.predictImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) + predictions, probabilities = predictor.classifyImage(image_input=os.path.join(main_folder, main_folder, "data-images", "1.jpg")) assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -73,7 +73,7 @@ def test_recognition_model_resnet_array_input(): predictor.setModelPath(os.path.join(main_folder, "data-models", "resnet50_imagenet_tf.2.0.h5")) predictor.loadModel() image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) - predictions, probabilities = predictor.predictImage(image_input=image_array, input_type="array") + predictions, probabilities = predictor.classifyImage(image_input=image_array, input_type="array") assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -88,7 +88,7 @@ def test_recognition_model_inceptionv3_array_input(): predictor.setModelPath(os.path.join(main_folder, "data-models", "inception_v3_weights_tf_dim_ordering_tf_kernels.h5")) predictor.loadModel() image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) - predictions, probabilities = predictor.predictImage(image_input=image_array, input_type="array") + predictions, probabilities = predictor.classifyImage(image_input=image_array, input_type="array") assert isinstance(predictions, list) assert isinstance(probabilities, list) @@ -103,7 +103,7 @@ def test_recognition_model_densenet_array_input(): predictor.setModelPath(os.path.join(main_folder, "data-models", "DenseNet-BC-121-32.h5")) predictor.loadModel() image_array = cv2.imread(os.path.join(main_folder, main_folder, "data-images", "1.jpg")) - predictions, probabilities = predictor.predictImage(image_input=image_array, input_type="array") + predictions, probabilities = predictor.classifyImage(image_input=image_array, input_type="array") assert isinstance(predictions, list) assert isinstance(probabilities, list) diff --git a/test/test_model_training.py b/test/test_model_training.py index 437d2577..8341c0eb 100644 --- a/test/test_model_training.py +++ b/test/test_model_training.py @@ -1,4 +1,4 @@ -from imageai.Prediction.Custom import ModelTraining +from imageai.Prediction.Custom import ClassificationModelTrainer import os import pytest import shutil @@ -14,7 +14,7 @@ def test_resnet_training(): - trainer = ModelTraining() + trainer = ClassificationModelTrainer() trainer.setModelTypeAsResNet50() trainer.setDataDirectory(data_directory=sample_dataset) trainer.trainModel(num_objects=10, num_experiments=1, enhance_data=True, batch_size=16, show_network_summary=True) @@ -32,7 +32,7 @@ def test_resnet_training(): def test_inception_v3_training(): - trainer = ModelTraining() + trainer = ClassificationModelTrainer() trainer.setModelTypeAsInceptionV3() trainer.setDataDirectory(data_directory=sample_dataset) trainer.trainModel(num_objects=10, num_experiments=1, enhance_data=True, batch_size=4, show_network_summary=True) diff --git a/test/test_object_detection.py b/test/test_object_detection.py index 043ba081..56886f29 100644 --- a/test/test_object_detection.py +++ b/test/test_object_detection.py @@ -19,7 +19,7 @@ def test_object_detection_retinanet(): detector = ObjectDetection() detector.setModelTypeAsRetinaNet() - detector.setModelPath(os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.0.1.h5")) + detector.setModelPath(os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.1.0.h5")) detector.loadModel() results = detector.detectObjectsFromImage(input_image=image_input, output_image_path=image_output, minimum_percentage_probability=40) @@ -133,7 +133,7 @@ def test_object_detection_retinanet_array_io(): detector = ObjectDetection() detector.setModelTypeAsRetinaNet() - detector.setModelPath(os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.0.1.h5")) + detector.setModelPath(os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.1.0.h5")) detector.loadModel() detected_array, results = detector.detectObjectsFromImage(input_image=image_input_array, input_type="array", minimum_percentage_probability=40, output_type="array") diff --git a/test/test_video_object_detection.py b/test/test_video_object_detection.py index 56cf2221..fde84e7f 100644 --- a/test/test_video_object_detection.py +++ b/test/test_video_object_detection.py @@ -18,7 +18,7 @@ def test_video_detection_retinanet(): detector = VideoObjectDetection() detector.setModelTypeAsRetinaNet() - detector.setModelPath(model_path=os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.0.1.h5")) + detector.setModelPath(model_path=os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.1.0.h5")) detector.loadModel(detection_speed="fastest") video_path = detector.detectObjectsFromVideo(input_file_path=video_file, output_file_path=video_file_output, save_detected_video=True, frames_per_second=30, log_progress=True) @@ -64,7 +64,7 @@ def test_video_detection_retinanet_analysis(): detector = VideoObjectDetection() detector.setModelTypeAsRetinaNet() - detector.setModelPath(model_path=os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.0.1.h5")) + detector.setModelPath(model_path=os.path.join(main_folder, "data-models", "resnet50_coco_best_v2.1.0.h5")) detector.loadModel(detection_speed="fastest") video_path = detector.detectObjectsFromVideo(input_file_path=video_file, output_file_path=video_file_output, save_detected_video=True, frames_per_second=30, log_progress=True, per_frame_function=forFrame, per_second_function=forSecond, return_detected_frame=True) From fedb3353123bbb22f224813d2d610a03d32754c3 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Mon, 4 Jan 2021 23:19:54 +0100 Subject: [PATCH 10/12] corrected python version in test pipeline --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 040b11a8..4187bad0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ dist: xenial sudo: required language: python python: - - '3.5' + - '3.7.6' install: - pip install -r requirements.txt - pip install pytest From 2b56e73015ba7b6d81fd36acd5e2b1d39eb17853 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Tue, 5 Jan 2021 00:48:47 +0100 Subject: [PATCH 11/12] updated main README and image recognition README --- README.md | 67 ++++++---- .../{Prediction => Classification}/README.md | 114 ++++-------------- test/test_image_recognition.py | 1 - 3 files changed, 63 insertions(+), 119 deletions(-) rename imageai/{Prediction => Classification}/README.md (65%) diff --git a/README.md b/README.md index f8ae68c3..f8b489a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# ImageAI (v2.1.5) +# ImageAI (v2.1.6) -![Discourse status](https://img.shields.io/discourse/https/forum.imageai.org/status) [![Build Status](https://travis-ci.com/OlafenwaMoses/ImageAI.svg?branch=master)](https://travis-ci.com/OlafenwaMoses/ImageAI) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/OlafenwaMoses/ImageAI/blob/master/LICENSE) [![PyPI version](https://badge.fury.io/py/imageai.svg)](https://badge.fury.io/py/imageai) [![Downloads](https://pepy.tech/badge/imageai/month)](https://pepy.tech/project/imageai/month) [![Downloads](https://pepy.tech/badge/imageai/week)](https://pepy.tech/project/imageai/week) [![codecov](https://codecov.io/gh/TechnionYP5777/project-name/branch/master/graph/badge.svg)](https://codecov.io/gh/OlafenwaMoses/ImageAI) + + +[![Build Status](https://travis-ci.com/OlafenwaMoses/ImageAI.svg?branch=master)](https://travis-ci.com/OlafenwaMoses/ImageAI) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/OlafenwaMoses/ImageAI/blob/master/LICENSE) [![PyPI version](https://badge.fury.io/py/imageai.svg)](https://badge.fury.io/py/imageai) [![Downloads](https://pepy.tech/badge/imageai/month)](https://pepy.tech/project/imageai) [![Downloads](https://pepy.tech/badge/imageai/week)](https://pepy.tech/project/imageai) An open-source python library built to empower developers to build applications and systems with self-contained Deep Learning and Computer Vision capabilities using simple and few lines of code. @@ -9,7 +11,9 @@ An open-source python library built to empower developers to build applications Patreon donate button -[ Click here](#sponsors) to see all [sponsors](#sponsors) for the **ImageAI** project! +## --------------------------------------------------- +## ImageAI will switch to PyTorch backend starting from June, 2021. +## --------------------------------------------------- ![](logo1.png) @@ -29,16 +33,18 @@ Eventually, **ImageAI** will provide support for a wider recognition in special environments and special fields. -**New Release : ImageAI 2.1.5** +**New Release : ImageAI 2.1.6** What's new: -- **Tensorboard** logging for all prediction and detection model training. -- **Progress bar** for detection training -- Allow prediction and detection in multi-threaded code -- Automatic **Multi-GPU** utilization for detection training -- Custom detection model **metrics** retrieval -- Bug fix: change custom model probability from **string to float** +- **Support Tensorflow 2.4.0** +- **SqueezeNet replaced with MobileNetV2** +- **Added TF 2.x compatible pre-trained models for ResNet recognition and RetinaNet detection** +- **Deprecates '.predictImage()' function for '.classifyImage()'** +- **Renames Model types as below:** + - ResNet >> ResNet50 + - DenseNet >> DenseNet121 +- ### TABLE OF CONTENTS @@ -70,13 +76,26 @@ What's new: To use **ImageAI** in your application developments, you must have installed the following dependencies before you install **ImageAI** : - - Python 3.5.1 (and later versions) - - Tensorflow 1.4.0 (and later versions) **(Tensorflow 2.0 coming soon)** + - Python 3.7.6 + - Tensorflow 2.4.0 - OpenCV - - Keras 2.x - - ```bash -pip install -U tensorflow keras opencv-python + - Keras 2.4.3 + +You can install all the dependencies by running the commands below + +**Tensorflow** +```bash +pip install tensorflow==2.4.0 +``` + +or **Tensorflow GPU** if you have NVIDIA GPU with CUDA and cuDNN installed. +```bash +pip install tensorflow-gpu==2.4.0 +``` + +**Other Dependencies** +```bash +pip install keras==2.4.3 numpy==1.19.3 pillow==7.0.0 scipy==1.4.1 h5py==2.10.0 matplotlib==3.3.2 opencv-python keras-resnet==0.2.0 ``` ### Installation @@ -85,7 +104,7 @@ pip install -U tensorflow keras opencv-python To install ImageAI, run the python installation instruction below in the command line: ```bash -pip3 install imageai --upgrade +pip install imageai --upgrade ``` ### Image Prediction @@ -102,11 +121,11 @@ minivan : 1.7487050965428352 ``` **ImageAI** provides 4 different algorithms and model types to perform image prediction, trained on the ImageNet-1000 dataset. -The 4 algorithms provided for image prediction include **SqueezeNet**, **ResNet**, **InceptionV3** and **DenseNet**. +The 4 algorithms provided for image prediction include **MobileNetV2**, **ResNet50**, **InceptionV3** and **DenseNet121**. Click the link below to see the full sample codes, explanations and best practices guide. -[>>> Tutorial & Guide](imageai/Prediction/README.md) +[>>> Tutorial & Guide](imageai/Classification/README.md) ### Object Detection @@ -179,7 +198,7 @@ _A sample from the IdenProf Dataset used to train a Model for predicting profess You can train your custom models using SqueezeNet, ResNet50, InceptionV3 and DenseNet in **5** lines of code. Click the link below to see the guide to preparing training images, sample training codes, explanations and best practices. -[>>> Tutorials & Documentation](imageai/Prediction/CUSTOMTRAINING.md) +[>>> Tutorials & Documentation](imageai/Classification/CUSTOMTRAINING.md) ### Custom Image Prediction @@ -360,12 +379,6 @@ You can cite **ImageAI** in your projects and research papers via the **BibTeX** ``` -

- -### >>> Basic Sponsors - -[David Lopez](https://github.com/daviddbarrero) - ### References
@@ -398,3 +411,5 @@ You can cite **ImageAI** in your projects and research papers via the **BibTeX** [https://arxiv.org/abs/1804.02767](https://arxiv.org/abs/1804.02767) 14. Experiencor, Training and Detecting Objects with YOLO3 [https://github.com/experiencor/keras-yolo3](https://github.com/experiencor/keras-yolo3) + 15. MobileNetV2: Inverted Residuals and Linear Bottlenecks +[https://arxiv.org/abs/1801.04381](https://arxiv.org/abs/1801.04381) diff --git a/imageai/Prediction/README.md b/imageai/Classification/README.md similarity index 65% rename from imageai/Prediction/README.md rename to imageai/Classification/README.md index 1cd34bcb..bb4def1f 100644 --- a/imageai/Prediction/README.md +++ b/imageai/Classification/README.md @@ -7,18 +7,17 @@ A **DeepQuest AI** project [https://deepquestai.com](https://deepquestai.com) - :white_square_button: First Prediction - :white_square_button: Prediction Speed - :white_square_button: Image Input Types -- :white_square_button: Multiple Images Prediction - :white_square_button: Prediction in MultiThreading - :white_square_button: Documentation ImageAI provides 4 different algorithms and model types to perform image prediction. To perform image prediction on any picture, take the following simple steps. The 4 algorithms provided for - image prediction include **SqueezeNet**, **ResNet**, **InceptionV3** and **DenseNet**. Each of these + image prediction include **MobileNetV2**, **ResNet50**, **InceptionV3** and **DenseNet121**. Each of these algorithms have individual model files which you must use depending on the choice of your algorithm. To download the model file for your choice of algorithm, click on any of the links below: -- **[SqueezeNet](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/squeezenet_weights_tf_dim_ordering_tf_kernels.h5)** _(Size = 4.82 mb, fastest prediction time and moderate accuracy)_ -- **[ResNet50](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/resnet50_weights_tf_dim_ordering_tf_kernels.h5)** by Microsoft Research _(Size = 98 mb, fast prediction time and high accuracy)_ +- **[MobileNetV2](https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/mobilenet_v2.h5)** _(Size = 4.82 mb, fastest prediction time and moderate accuracy)_ +- **[ResNet50](https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/resnet50_imagenet_tf.2.0.h5)** by Microsoft Research _(Size = 98 mb, fast prediction time and high accuracy)_ - **[InceptionV3](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/inception_v3_weights_tf_dim_ordering_tf_kernels.h5)** by Google Brain team _(Size = 91.6 mb, slow prediction time and higher accuracy)_ - **[DenseNet121](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/DenseNet-BC-121-32.h5)** by Facebook AI Research _(Size = 31.6 mb, slower prediction time and highest accuracy)_ @@ -31,17 +30,17 @@ To perform image prediction on any picture, take the following simple steps. Th
```python -from imageai.Prediction import ImagePrediction +from imageai.Classification import ImageClassification import os execution_path = os.getcwd() -prediction = ImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) +prediction = ImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "resnet50_imagenet_tf.2.0.h5")) prediction.loadModel() -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "1.jpg"), result_count=5 ) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "1.jpg"), result_count=5 ) for eachPrediction, eachProbability in zip(predictions, probabilities): print(eachPrediction , " : " , eachProbability) ``` @@ -59,7 +58,7 @@ minivan : 1.7487050965428352 The code above works as follows: ```python -from imageai.Prediction import ImagePrediction +from imageai.Classification import ImageClassification import os ``` The code above imports the `ImageAI` library and the python `os` class. @@ -69,17 +68,17 @@ execution_path = os.getcwd() The above line obtains the path to the folder that contains your python file (in this example, your FirstPrediction.py). ```python -prediction = ImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) +prediction = ImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "resnet50_imagenet_tf.2.0.h5")) ``` -In the lines above, we created and instance of the `ImagePrediction()` class in the first line, then we set the model type of the prediction object to ResNet by caling the `.setModelTypeAsResNet()` in the second line and then we set the model path of the prediction object to the path of the model file (`resnet50_weights_tf_dim_ordering_tf_kernels.h5`) we copied to the python file folder in the third line. +In the lines above, we created and instance of the `ImagePrediction()` class in the first line, then we set the model type of the prediction object to ResNet by caling the `.setModelTypeAsResNet50()` in the second line and then we set the model path of the prediction object to the path of the model file (`resnet50_imagenet_tf.2.0.h5`) we copied to the python file folder in the third line. ```python -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "1.jpg"), result_count=5 ) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "1.jpg"), result_count=5 ) ``` -In the above line, we defined 2 variables to be equal to the function called to predict an image, which is the `.predictImage()` function, into which we parsed the path to our image and also state the number of prediction results we want to have (values from 1 to 1000) parsing `result_count=5`. The `.predictImage()` function will return 2 array objects with the first (**predictions**) being an array of predictions and the second (**percentage_probabilities**) being an array of the corresponding percentage probability for each prediction. +In the above line, we defined 2 variables to be equal to the function called to predict an image, which is the `.classifyImage()` function, into which we parsed the path to our image and also state the number of prediction results we want to have (values from 1 to 1000) parsing `result_count=5`. The `.classifyImage()` function will return 2 array objects with the first (**predictions**) being an array of predictions and the second (**percentage_probabilities**) being an array of the corresponding percentage probability for each prediction. ```python for eachPrediction, eachProbability in zip(predictions, probabilities): @@ -88,75 +87,6 @@ for eachPrediction, eachProbability in zip(predictions, probabilities): The above line obtains each object in the **predictions** array, and also obtains the corresponding percentage probability from the **percentage_probabilities**, and finally prints the result of both to console. -### Multiple Images Prediction -
- - You can run image prediction on more than one image using a single function, which is the `.predictMultipleImages()` function. It works by doing the following: -- Define your normal `ImagePrediction` instance -- Set the model type and model path -- Call the `.loadModel()` function -- Create an array and add all the string path to each of the images you want to predict to the array. -- You then perform prediction by calling the `.predictMultipleImages()` function and parse in the array of images, and also set the number predictions you want per image by parsing `result_count_per_image=5` (default value is 2) - -Find the sample code below: - -```python -from imageai.Prediction import ImagePrediction -import os - -execution_path = os.getcwd() - -multiple_prediction = ImagePrediction() -multiple_prediction.setModelTypeAsResNet() -multiple_prediction.setModelPath(os.path.join(execution_path, "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) -multiple_prediction.loadModel() - -all_images_array = [] - -all_files = os.listdir(execution_path) -for each_file in all_files: - if(each_file.endswith(".jpg") or each_file.endswith(".png")): - all_images_array.append(each_file) - -results_array = multiple_prediction.predictMultipleImages(all_images_array, result_count_per_image=5) - -for each_result in results_array: - predictions, percentage_probabilities = each_result["predictions"], each_result["percentage_probabilities"] - for index in range(len(predictions)): - print(predictions[index] , " : " , percentage_probabilities[index]) - print("-----------------------") -``` - -In the above code, the `.predictMultipleImages()` function will return an array which contains a dictionary per image. -Each dictionary contains the arrays for predictions and percentage probability for each prediction. - -Sample Result: -![](../../data-images/1.jpg) -![](../../data-images/2.jpg) -![](../../data-images/3.jpg) - -``` -convertible : 52.459555864334106 -sports_car : 37.61284649372101 -pickup : 3.1751200556755066 -car_wheel : 1.817505806684494 -minivan : 1.7487050965428352 ------------------------ -toilet_tissue : 13.99008333683014 -jeep : 6.842949986457825 -car_wheel : 6.71963095664978 -seat_belt : 6.704962253570557 -minivan : 5.861184373497963 ------------------------ -bustard : 52.03368067741394 -vulture : 20.936034619808197 -crane : 10.620515048503876 -kite : 10.20539253950119 -white_stork : 1.6472270712256432 ------------------------ -``` - - ### Prediction Speed
@@ -269,11 +199,11 @@ This means you can now perform image prediction in production applications such that returns file in any of the above stated formats. To perform image prediction with numpy array or file stream input, you just need to state the input type -in the `.predictImage()` function or the `.predictMultipleImages()` function. See example below. +in the `.classifyImage()` function. See example below. ```python -predictions, probabilities = prediction.predictImage(image_array, result_count=5 , input_type="array" ) # For numpy array input type -predictions, probabilities = prediction.predictImage(image_stream, result_count=5 , input_type="stream" ) # For file stream input type +predictions, probabilities = prediction.classifyImage(image_array, result_count=5 , input_type="array" ) # For numpy array input type +predictions, probabilities = prediction.classifyImage(image_stream, result_count=5 , input_type="stream" ) # For file stream input type ``` ### Prediction in MultiThreading @@ -284,19 +214,19 @@ When developing programs that run heavy task on the deafult thread like User Int a new thread, you must take note the following: - You can create your prediction object, set its model type, set model path and json path outside the new thread. -- The `.loadModel()` must be in the new thread and image prediction (`predictImage()`) must take place in th new thread. +- The `.loadModel()` must be in the new thread and image prediction (`classifyImage()`) must take place in th new thread. Take a look of a sample code below on image prediction using multithreading: ```python -from imageai.Prediction import ImagePrediction +from imageai.Prediction import ImageClassification import os import threading execution_path = os.getcwd() -prediction = ImagePrediction() +prediction = ImageClassification() prediction.setModelTypeAsResNet() -prediction.setModelPath( os.path.join(execution_path, "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) +prediction.setModelPath( os.path.join(execution_path, "resnet50_imagenet_tf.2.0.h5")) picturesfolder = os.environ["USERPROFILE"] + "\\Pictures\\" allfiles = os.listdir(picturesfolder) diff --git a/test/test_image_recognition.py b/test/test_image_recognition.py index fe10121d..4a79a289 100644 --- a/test/test_image_recognition.py +++ b/test/test_image_recognition.py @@ -3,7 +3,6 @@ import cv2 import pytest from os.path import dirname -import keras main_folder = os.getcwd() From 94a2d22132c3cdafc49a4c5850a6b8a2d4ec6e61 Mon Sep 17 00:00:00 2001 From: OlafenwaMoses Date: Tue, 5 Jan 2021 02:22:33 +0100 Subject: [PATCH 12/12] updated README for Custom Classification and Training --- examples/custom_full_model_prediction.py | 18 - examples/custom_model_continuos_training.py | 6 +- examples/custom_model_convert_deepstack.py | 16 - .../custom_model_convert_to_tensorflow.py | 16 - examples/custom_model_prediction.py | 10 +- examples/custom_model_training.py | 6 +- ...custom_model_transfer_learning_training.py | 6 +- examples/custom_multiple_models_predict.py | 40 -- examples/detection_config.json | 9 - examples/image_prediction.py | 10 +- .../Classification/CUSTOMCLASSIFICATION.md | 142 ++---- imageai/Classification/CUSTOMTRAINING.md | 427 +----------------- imageai/Detection/README.md | 3 +- test/test_custom_recognition.py | 2 +- test/test_image_recognition.py | 2 +- test/test_model_training.py | 2 +- 16 files changed, 72 insertions(+), 643 deletions(-) delete mode 100644 examples/custom_full_model_prediction.py delete mode 100644 examples/custom_model_convert_deepstack.py delete mode 100644 examples/custom_model_convert_to_tensorflow.py delete mode 100644 examples/custom_multiple_models_predict.py delete mode 100644 examples/detection_config.json diff --git a/examples/custom_full_model_prediction.py b/examples/custom_full_model_prediction.py deleted file mode 100644 index f114f18d..00000000 --- a/examples/custom_full_model_prediction.py +++ /dev/null @@ -1,18 +0,0 @@ -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_full_resnet_ex-001_acc-0.119792.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadFullModel(num_objects=10) - - -results, probabilities = predictor.predictImage(image_input=os.path.join(execution_path, "1.jpg"), result_count=5) -print(results) -print(probabilities) - - - diff --git a/examples/custom_model_continuos_training.py b/examples/custom_model_continuos_training.py index 69b7cfe8..24ccaf2a 100644 --- a/examples/custom_model_continuos_training.py +++ b/examples/custom_model_continuos_training.py @@ -1,8 +1,8 @@ -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer import os -trainer = ModelTraining() -trainer.setModelTypeAsDenseNet() +trainer = ClassificationModelTrainer() +trainer.setModelTypeAsDenseNet121() trainer.setDataDirectory("idenprof") trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=8, show_network_summary=True, continue_from_model="idenprof_densenet-0.763500.h5") # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 diff --git a/examples/custom_model_convert_deepstack.py b/examples/custom_model_convert_deepstack.py deleted file mode 100644 index bf515e5a..00000000 --- a/examples/custom_model_convert_deepstack.py +++ /dev/null @@ -1,16 +0,0 @@ -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelTypeAsResNet() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_resnet.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadModel(num_objects=10) -predictor.save_model_for_deepstack(new_model_folder= os.path.join(execution_path, "deepstack_model"), new_model_name="idenprof_resnet_deepstack.h5") - - - - diff --git a/examples/custom_model_convert_to_tensorflow.py b/examples/custom_model_convert_to_tensorflow.py deleted file mode 100644 index 3a2b8479..00000000 --- a/examples/custom_model_convert_to_tensorflow.py +++ /dev/null @@ -1,16 +0,0 @@ -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelTypeAsResNet() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_resnet.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadModel(num_objects=10) -predictor.save_model_to_tensorflow(new_model_folder= os.path.join(execution_path, "tensorflow_model"), new_model_name="idenprof_resnet_tensorflow.pb") - - - - diff --git a/examples/custom_model_prediction.py b/examples/custom_model_prediction.py index dbd1b138..e4b279e9 100644 --- a/examples/custom_model_prediction.py +++ b/examples/custom_model_prediction.py @@ -1,15 +1,15 @@ -from imageai.Prediction.Custom import CustomImagePrediction +from imageai.Classification.Custom import CustomImageClassification import os execution_path = os.getcwd() -prediction = CustomImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "idenprof_resnet.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 +prediction = CustomImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "idenprof_resnet_ex-056_acc-0.993062.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 prediction.setJsonPath(os.path.join(execution_path, "idenprof.json")) prediction.loadModel(num_objects=10) -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "9.jpg"), result_count=5) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "9.jpg"), result_count=5) for eachPrediction, eachProbability in zip(predictions, probabilities): print(eachPrediction , " : " , eachProbability) \ No newline at end of file diff --git a/examples/custom_model_training.py b/examples/custom_model_training.py index 504beb30..f7b3c0b1 100644 --- a/examples/custom_model_training.py +++ b/examples/custom_model_training.py @@ -1,7 +1,7 @@ -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer -model_trainer = ModelTraining() -model_trainer.setModelTypeAsResNet() +model_trainer = ClassificationModelTrainer() +model_trainer.setModelTypeAsResNet50() model_trainer.setDataDirectory("idenprof") model_trainer.trainModel(num_objects=10, num_experiments=200, enhance_data=True, batch_size=32, show_network_summary=True) diff --git a/examples/custom_model_transfer_learning_training.py b/examples/custom_model_transfer_learning_training.py index 02cda825..deb14b57 100644 --- a/examples/custom_model_transfer_learning_training.py +++ b/examples/custom_model_transfer_learning_training.py @@ -1,7 +1,7 @@ -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer import os -trainer = ModelTraining() -trainer.setModelTypeAsResNet() +trainer = ClassificationModelTrainer() +trainer.setModelTypeAsResNet50() trainer.setDataDirectory("idenprof") trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=16, show_network_summary=True,transfer_from_model="resnet50_weights_tf_dim_ordering_tf_kernels.h5", initial_num_objects=1000) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 diff --git a/examples/custom_multiple_models_predict.py b/examples/custom_multiple_models_predict.py deleted file mode 100644 index a0f51ea0..00000000 --- a/examples/custom_multiple_models_predict.py +++ /dev/null @@ -1,40 +0,0 @@ -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_resnet.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.setModelTypeAsResNet() -predictor.loadModel(num_objects=10) - - -predictor2 = CustomImagePrediction() -predictor2.setModelPath(model_path=os.path.join(execution_path, "idenprof_full_resnet_ex-001_acc-0.119792.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor2.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor2.loadFullModel(num_objects=10) - -predictor3 = CustomImagePrediction() -predictor3.setModelPath(model_path=os.path.join(execution_path, "idenprof_inception_0.719500.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/models-v3 -predictor3.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor3.setModelTypeAsInceptionV3() -predictor3.loadModel(num_objects=10) - -results, probabilities = predictor.predictImage(image_input=os.path.join(execution_path, "9.jpg"), result_count=5) -print(results) -print(probabilities) - -results2, probabilities2 = predictor2.predictImage(image_input=os.path.join(execution_path, "9.jpg"), - result_count=5) -print(results2) -print(probabilities2) - -results3, probabilities3 = predictor3.predictImage(image_input=os.path.join(execution_path, "9.jpg"), - result_count=5) -print(results3) -print(probabilities3) -print("-------------------------------") - - diff --git a/examples/detection_config.json b/examples/detection_config.json deleted file mode 100644 index f3573ecf..00000000 --- a/examples/detection_config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "labels" : [ - "hololens" - ], - "anchors" : [ - [160, 171, 261, 167, 301, 285], - [78, 86, 104, 69, 138, 105], - [37, 32, 58, 61, 62, 44]] -} \ No newline at end of file diff --git a/examples/image_prediction.py b/examples/image_prediction.py index 1d116d33..a1182570 100644 --- a/examples/image_prediction.py +++ b/examples/image_prediction.py @@ -1,13 +1,13 @@ -from imageai.Prediction import ImagePrediction +from imageai.Classification import ImageClassification import os execution_path = os.getcwd() -prediction = ImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "resnet50_weights_tf_dim_ordering_tf_kernels.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/1.0 +prediction = ImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "resnet50_imagenet_tf.2.0.h5")) # Download the model via this link https://github.com/OlafenwaMoses/ImageAI/releases/tag/1.0 prediction.loadModel() -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "1.jpg"), result_count=10) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "1.jpg"), result_count=10) for eachPrediction, eachProbability in zip(predictions, probabilities): print(eachPrediction , " : " , eachProbability) \ No newline at end of file diff --git a/imageai/Classification/CUSTOMCLASSIFICATION.md b/imageai/Classification/CUSTOMCLASSIFICATION.md index b7a88906..bffcc4ef 100644 --- a/imageai/Classification/CUSTOMCLASSIFICATION.md +++ b/imageai/Classification/CUSTOMCLASSIFICATION.md @@ -1,4 +1,4 @@ -# ImageAI : Custom Image Prediction +# ImageAI : Custom Image Classification A **DeepQuest AI** project https://deepquestai.com

--- @@ -23,8 +23,8 @@ In this example, we will be using the model trained for 20 experiments on **Iden (You can use your own trained model and generated JSON file. This 'class' is provided mainly for the purpose to use your own custom models.) Download the ResNet model of the model and JSON files in links below: -- [**ResNet**](https://github.com/OlafenwaMoses/IdenProf/releases/download/v1.0/idenprof_061-0.7933.h5) _(Size = 90.4 mb)_ -- [**IdenProf model_class.json file**](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0.1/model_class.json) +- [**ResNet50**](https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/idenprof_resnet_ex-056_acc-0.993062.h5) _(Size = 90.4 mb)_ +- [**IdenProf model_class.json file**](https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/idenprof.json) Great! Once you have downloaded this model file and the JSON file, start a new python project, and then copy the model file and the JSON file to your project folder where your python files (.py files) will be. @@ -35,18 +35,18 @@ Then write the code below into the python file: ### FirstCustomPrediction.py ```python -from imageai.Prediction.Custom import CustomImagePrediction +from imageai.Classification.Custom import CustomImageClassification import os execution_path = os.getcwd() -prediction = CustomImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "idenprof_061-0.7933.h5")) -prediction.setJsonPath(os.path.join(execution_path, "model_class.json")) +prediction = CustomImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "idenprof_resnet_ex-056_acc-0.993062.h5")) +prediction.setJsonPath(os.path.join(execution_path, "idenprof.json")) prediction.loadModel(num_objects=10) -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "4.jpg"), result_count=5) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "4.jpg"), result_count=5) for eachPrediction, eachProbability in zip(predictions, probabilities): print(eachPrediction + " : " + eachProbability) @@ -65,7 +65,7 @@ pilot : 2.239348366856575 The code above works as follows: ```python -from imageai.Prediction.Custom import CustomImagePrediction +from imageai.Classification.Custom import CustomImageClassification import os ``` The code above imports the **ImageAI** library for custom image prediction and the python **os** class. @@ -77,23 +77,23 @@ execution_path = os.getcwd() The above line obtains the path to the folder that contains your python file (in this example, your FirstCustomPrediction.py). ```python -prediction = CustomImagePrediction() -prediction.setModelTypeAsResNet() -prediction.setModelPath(os.path.join(execution_path, "resnet_model_ex-020_acc-0.651714.h5")) -prediction.setJsonPath(os.path.join(execution_path, "model_class.json")) +prediction = CustomImageClassification() +prediction.setModelTypeAsResNet50() +prediction.setModelPath(os.path.join(execution_path, "idenprof_resnet_ex-056_acc-0.993062.h5")) +prediction.setJsonPath(os.path.join(execution_path, "idenprof.json")) prediction.loadModel(num_objects=10) ``` -In the lines above, we created and instance of the `CustomImagePrediction()` - class in the first line, then we set the model type of the prediction object to ResNet by caling the `.setModelTypeAsResNet()` - in the second line, we set the model path of the prediction object to the path of the custom model file (`resnet_model_ex-020_acc-0.651714.h5`) we copied to the python file folder +In the lines above, we created and instance of the `CustomImageClassification()` + class in the first line, then we set the model type of the prediction object to ResNet by caling the `.setModelTypeAsResNet50()` + in the second line, we set the model path of the prediction object to the path of the custom model file (`idenprof_resnet_ex-056_acc-0.993062.h5`) we copied to the python file folder in the third line, we set the path to the model_class.json of the model, we load the model and parse the number of objected that can be predicted in the model. ```python -predictions, probabilities = prediction.predictImage(os.path.join(execution_path, "4.jpg"), result_count=5) +predictions, probabilities = prediction.classifyImage(os.path.join(execution_path, "4.jpg"), result_count=5) ``` -In the above line, we defined 2 variables to be equal to the function called to predict an image, which is the `.predictImage()` function, into which we parsed the path to our image and also state the number of prediction results we want to have (values from 1 to 10 in this case) parsing `result_count=5`. The `.predictImage()` function will return 2 array objects with the first (**predictions**) being an array of predictions and the second (**percentage_probabilities**) being an array of the corresponding percentage probability for each prediction. +In the above line, we defined 2 variables to be equal to the function called to predict an image, which is the `.classifyImage()` function, into which we parsed the path to our image and also state the number of prediction results we want to have (values from 1 to 10 in this case) parsing `result_count=5`. The `.classifyImage()` function will return 2 array objects with the first (**predictions**) being an array of predictions and the second (**percentage_probabilities**) being an array of the corresponding percentage probability for each prediction. ```python for eachPrediction, eachProbability in zip(predictions, probabilities): @@ -102,35 +102,10 @@ for eachPrediction, eachProbability in zip(predictions, probabilities): The above line obtains each object in the **predictions** array, and also obtains the corresponding percentage probability from the **percentage_probabilities**, and finally prints the result of both to console. -**CustomImagePrediction** class also supports the multiple predictions, input types and prediction speeds that are contained -in the **ImagePrediction** class. Follow this [link](README.md) to see all the details. +**CustomImageClassification** class also supports the multiple predictions, input types and prediction speeds that are contained +in the **ImageClassification** class. Follow this [link](README.md) to see all the details. -### Custom Model Prediction with Full Model -
- -**ImageAI** now allows you to perform prediction using your custom model without specifying the model type. This means you: -1) use your custom, fully saved model that you trained with **ImageAI** -2) use any **Keras** model that is fully saved (with weights and parameters). - -All you need to do is load your model using the `loadFullModel()` function instead of `loadModel()`. See the example code below for performing prediction using a fully saved model. - -```python -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_full_resnet_ex-001_acc-0.119792.h5")) -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadFullModel(num_objects=10) - -results, probabilities = predictor.predictImage(image_input=os.path.join(execution_path, "1.jpg"), result_count=5) -print(results) -print(probabilities) -``` ### Custom Prediction with multiple models
@@ -141,88 +116,35 @@ Now you can run multiple custom models, as many as your computer memory can acco See the example code below for running multiple custom prediction models. ```python -from imageai.Prediction.Custom import CustomImagePrediction +from imageai.Classification.Custom import CustomImageClassification import os execution_path = os.getcwd() -predictor = CustomImagePrediction() +predictor = CustomImageClassification() predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_resnet.h5")) predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.setModelTypeAsResNet() +predictor.setModelTypeAsResNet50() predictor.loadModel(num_objects=10) -predictor2 = CustomImagePrediction() -predictor2.setModelPath(model_path=os.path.join(execution_path, "idenprof_full_resnet_ex-001_acc-0.119792.h5")) +predictor2 = CustomImageClassification() +predictor2.setModelPath(model_path=os.path.join(execution_path, "idenprof_inception_0.719500.h5")) predictor2.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor2.loadFullModel(num_objects=10) - -predictor3 = CustomImagePrediction() -predictor3.setModelPath(model_path=os.path.join(execution_path, "idenprof_inception_0.719500.h5")) -predictor3.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor3.setModelTypeAsInceptionV3() -predictor3.loadModel(num_objects=10) +predictor2.setModelTypeAsInceptionV3() +predictor2.loadModel(num_objects=10) -results, probabilities = predictor.predictImage(image_input=os.path.join(execution_path, "9.jpg"), result_count=5) +results, probabilities = predictor.classifyImage(image_input=os.path.join(execution_path, "9.jpg"), result_count=5) print(results) print(probabilities) -results2, probabilities2 = predictor2.predictImage(image_input=os.path.join(execution_path, "9.jpg"), - result_count=5) -print(results2) -print(probabilities2) -results3, probabilities3 = predictor3.predictImage(image_input=os.path.join(execution_path, "9.jpg"), +results2, probabilities2 = predictor3.classifyImage(image_input=os.path.join(execution_path, "9.jpg"), result_count=5) -print(results3) -print(probabilities3) +print(results2) +print(probabilities2) print("-------------------------------") ``` -### Convert custom model to Tensorflow's format -
- -Using the same `CustomImagePrediction` class you use for custom predictions, you can can now convert your Keras (**.h5**) models into Tensorflow's (**.pb**) model format. -All you need to do is to call the function `save_model_to_tensorflow()` and parse in the necessary parameters as seen in the example code below. - -```python -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - - -predictor = CustomImagePrediction() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_full_resnet_ex-001_acc-0.119792.h5")) -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadFullModel(num_objects=10) - -results, probabilities = predictor.predictImage(image_input=os.path.join(execution_path, "1.jpg"), result_count=5) -print(results) -print(probabilities) -``` - -### Convert custom model to DeepStack's format -
- -With the `CustomImagePrediction` class you use for custom predictions, you can can now convert your Keras (**.h5**) models into a format deployable on [DeepStack AI Server](https://python.deepstack.cc). -All you need to do is to call the function `save_model_for_deepstack()` and parse in the necessary parameters as seen in the example code below. -It will generate your model and a configuration JSON file. - -```python -from imageai.Prediction.Custom import CustomImagePrediction -import os - -execution_path = os.getcwd() - -predictor = CustomImagePrediction() -predictor.setModelTypeAsResNet() -predictor.setModelPath(model_path=os.path.join(execution_path, "idenprof_resnet.h5")) -predictor.setJsonPath(model_json=os.path.join(execution_path, "idenprof.json")) -predictor.loadModel(num_objects=10) -predictor.save_model_for_deepstack(new_model_folder= os.path.join(execution_path, "deepstack_model"), new_model_name="idenprof_resnet_deepstack.h5") -``` - ### Documentation We have provided full documentation for all **ImageAI** classes and functions in 3 major languages. Find links below:** diff --git a/imageai/Classification/CUSTOMTRAINING.md b/imageai/Classification/CUSTOMTRAINING.md index 9dd79c96..1314b0ec 100644 --- a/imageai/Classification/CUSTOMTRAINING.md +++ b/imageai/Classification/CUSTOMTRAINING.md @@ -4,7 +4,7 @@ **ImageAI** provides the most simple and powerful approach to training custom image prediction models using state-of-the-art SqueezeNet, ResNet50, InceptionV3 and DenseNet -which you can load into the `imageai.Prediction.Custom.CustomImagePrediction` class. This allows +which you can load into the `imageai.Classification.Custom.CustomImageClassification` class. This allows you to train your own model on any set of images that corresponds to any type of objects/persons. The training process generates a JSON file that maps the objects types in your image dataset and creates lots of models. You will then pick the model with the highest accuracy and perform custom @@ -47,9 +47,9 @@ You will prepare the images as follows: ``` 9. Then your training code goes as follows: ```python - from imageai.Prediction.Custom import ModelTraining - model_trainer = ModelTraining() - model_trainer.setModelTypeAsResNet() + from imageai.Classification.Custom import ClassificationModelTrainer + model_trainer = ClassificationModelTrainer() + model_trainer.setModelTypeAsResNet50() model_trainer.setDataDirectory("pets") model_trainer.trainModel(num_objects=4, num_experiments=100, enhance_data=True, batch_size=32, show_network_summary=True) ``` @@ -58,10 +58,9 @@ You will prepare the images as follows: Now lets take a look at how the code above works. ```python -from imageai.Prediction.Custom import ModelTraining - -model_trainer = ModelTraining() -model_trainer.setModelTypeAsResNet() +from imageai.Classification.Custom import ClassificationModelTrainer +model_trainer = ClassificationModelTrainer() +model_trainer.setModelTypeAsResNet50() model_trainer.setDataDirectory("pets") ``` @@ -86,377 +85,6 @@ images for better performance. When you start the training, you should see something like this in the console: ``` -____________________________________________________________________________________________________ -Layer (type) Output Shape Param # Connected to -==================================================================================================== -input_2 (InputLayer) (None, 224, 224, 3) 0 -____________________________________________________________________________________________________ -conv2d_1 (Conv2D) (None, 112, 112, 64) 9472 input_2[0][0] -____________________________________________________________________________________________________ -batch_normalization_1 (BatchNorm (None, 112, 112, 64) 256 conv2d_1[0][0] -____________________________________________________________________________________________________ -activation_1 (Activation) (None, 112, 112, 64) 0 batch_normalization_1[0][0] -____________________________________________________________________________________________________ -max_pooling2d_1 (MaxPooling2D) (None, 55, 55, 64) 0 activation_1[0][0] -____________________________________________________________________________________________________ -conv2d_3 (Conv2D) (None, 55, 55, 64) 4160 max_pooling2d_1[0][0] -____________________________________________________________________________________________________ -batch_normalization_3 (BatchNorm (None, 55, 55, 64) 256 conv2d_3[0][0] -____________________________________________________________________________________________________ -activation_2 (Activation) (None, 55, 55, 64) 0 batch_normalization_3[0][0] -____________________________________________________________________________________________________ -conv2d_4 (Conv2D) (None, 55, 55, 64) 36928 activation_2[0][0] -____________________________________________________________________________________________________ -batch_normalization_4 (BatchNorm (None, 55, 55, 64) 256 conv2d_4[0][0] -____________________________________________________________________________________________________ -activation_3 (Activation) (None, 55, 55, 64) 0 batch_normalization_4[0][0] -____________________________________________________________________________________________________ -conv2d_5 (Conv2D) (None, 55, 55, 256) 16640 activation_3[0][0] -____________________________________________________________________________________________________ -conv2d_2 (Conv2D) (None, 55, 55, 256) 16640 max_pooling2d_1[0][0] -____________________________________________________________________________________________________ -batch_normalization_5 (BatchNorm (None, 55, 55, 256) 1024 conv2d_5[0][0] -____________________________________________________________________________________________________ -batch_normalization_2 (BatchNorm (None, 55, 55, 256) 1024 conv2d_2[0][0] -____________________________________________________________________________________________________ -add_1 (Add) (None, 55, 55, 256) 0 batch_normalization_5[0][0] - batch_normalization_2[0][0] -____________________________________________________________________________________________________ -activation_4 (Activation) (None, 55, 55, 256) 0 add_1[0][0] -____________________________________________________________________________________________________ -conv2d_6 (Conv2D) (None, 55, 55, 64) 16448 activation_4[0][0] -____________________________________________________________________________________________________ -batch_normalization_6 (BatchNorm (None, 55, 55, 64) 256 conv2d_6[0][0] -____________________________________________________________________________________________________ -activation_5 (Activation) (None, 55, 55, 64) 0 batch_normalization_6[0][0] -____________________________________________________________________________________________________ -conv2d_7 (Conv2D) (None, 55, 55, 64) 36928 activation_5[0][0] -____________________________________________________________________________________________________ -batch_normalization_7 (BatchNorm (None, 55, 55, 64) 256 conv2d_7[0][0] -____________________________________________________________________________________________________ -activation_6 (Activation) (None, 55, 55, 64) 0 batch_normalization_7[0][0] -____________________________________________________________________________________________________ -conv2d_8 (Conv2D) (None, 55, 55, 256) 16640 activation_6[0][0] -____________________________________________________________________________________________________ -batch_normalization_8 (BatchNorm (None, 55, 55, 256) 1024 conv2d_8[0][0] -____________________________________________________________________________________________________ -add_2 (Add) (None, 55, 55, 256) 0 batch_normalization_8[0][0] - activation_4[0][0] -____________________________________________________________________________________________________ -activation_7 (Activation) (None, 55, 55, 256) 0 add_2[0][0] -____________________________________________________________________________________________________ -conv2d_9 (Conv2D) (None, 55, 55, 64) 16448 activation_7[0][0] -____________________________________________________________________________________________________ -batch_normalization_9 (BatchNorm (None, 55, 55, 64) 256 conv2d_9[0][0] -____________________________________________________________________________________________________ -activation_8 (Activation) (None, 55, 55, 64) 0 batch_normalization_9[0][0] -____________________________________________________________________________________________________ -conv2d_10 (Conv2D) (None, 55, 55, 64) 36928 activation_8[0][0] -____________________________________________________________________________________________________ -batch_normalization_10 (BatchNor (None, 55, 55, 64) 256 conv2d_10[0][0] -____________________________________________________________________________________________________ -activation_9 (Activation) (None, 55, 55, 64) 0 batch_normalization_10[0][0] -____________________________________________________________________________________________________ -conv2d_11 (Conv2D) (None, 55, 55, 256) 16640 activation_9[0][0] -____________________________________________________________________________________________________ -batch_normalization_11 (BatchNor (None, 55, 55, 256) 1024 conv2d_11[0][0] -____________________________________________________________________________________________________ -add_3 (Add) (None, 55, 55, 256) 0 batch_normalization_11[0][0] - activation_7[0][0] -____________________________________________________________________________________________________ -activation_10 (Activation) (None, 55, 55, 256) 0 add_3[0][0] -____________________________________________________________________________________________________ -conv2d_13 (Conv2D) (None, 28, 28, 128) 32896 activation_10[0][0] -____________________________________________________________________________________________________ -batch_normalization_13 (BatchNor (None, 28, 28, 128) 512 conv2d_13[0][0] -____________________________________________________________________________________________________ -activation_11 (Activation) (None, 28, 28, 128) 0 batch_normalization_13[0][0] -____________________________________________________________________________________________________ -conv2d_14 (Conv2D) (None, 28, 28, 128) 147584 activation_11[0][0] -____________________________________________________________________________________________________ -batch_normalization_14 (BatchNor (None, 28, 28, 128) 512 conv2d_14[0][0] -____________________________________________________________________________________________________ -activation_12 (Activation) (None, 28, 28, 128) 0 batch_normalization_14[0][0] -____________________________________________________________________________________________________ -conv2d_15 (Conv2D) (None, 28, 28, 512) 66048 activation_12[0][0] -____________________________________________________________________________________________________ -conv2d_12 (Conv2D) (None, 28, 28, 512) 131584 activation_10[0][0] -____________________________________________________________________________________________________ -batch_normalization_15 (BatchNor (None, 28, 28, 512) 2048 conv2d_15[0][0] -____________________________________________________________________________________________________ -batch_normalization_12 (BatchNor (None, 28, 28, 512) 2048 conv2d_12[0][0] -____________________________________________________________________________________________________ -add_4 (Add) (None, 28, 28, 512) 0 batch_normalization_15[0][0] - batch_normalization_12[0][0] -____________________________________________________________________________________________________ -activation_13 (Activation) (None, 28, 28, 512) 0 add_4[0][0] -____________________________________________________________________________________________________ -conv2d_16 (Conv2D) (None, 28, 28, 128) 65664 activation_13[0][0] -____________________________________________________________________________________________________ -batch_normalization_16 (BatchNor (None, 28, 28, 128) 512 conv2d_16[0][0] -____________________________________________________________________________________________________ -activation_14 (Activation) (None, 28, 28, 128) 0 batch_normalization_16[0][0] -____________________________________________________________________________________________________ -conv2d_17 (Conv2D) (None, 28, 28, 128) 147584 activation_14[0][0] -____________________________________________________________________________________________________ -batch_normalization_17 (BatchNor (None, 28, 28, 128) 512 conv2d_17[0][0] -____________________________________________________________________________________________________ -activation_15 (Activation) (None, 28, 28, 128) 0 batch_normalization_17[0][0] -____________________________________________________________________________________________________ -conv2d_18 (Conv2D) (None, 28, 28, 512) 66048 activation_15[0][0] -____________________________________________________________________________________________________ -batch_normalization_18 (BatchNor (None, 28, 28, 512) 2048 conv2d_18[0][0] -____________________________________________________________________________________________________ -add_5 (Add) (None, 28, 28, 512) 0 batch_normalization_18[0][0] - activation_13[0][0] -____________________________________________________________________________________________________ -activation_16 (Activation) (None, 28, 28, 512) 0 add_5[0][0] -____________________________________________________________________________________________________ -conv2d_19 (Conv2D) (None, 28, 28, 128) 65664 activation_16[0][0] -____________________________________________________________________________________________________ -batch_normalization_19 (BatchNor (None, 28, 28, 128) 512 conv2d_19[0][0] -____________________________________________________________________________________________________ -activation_17 (Activation) (None, 28, 28, 128) 0 batch_normalization_19[0][0] -____________________________________________________________________________________________________ -conv2d_20 (Conv2D) (None, 28, 28, 128) 147584 activation_17[0][0] -____________________________________________________________________________________________________ -batch_normalization_20 (BatchNor (None, 28, 28, 128) 512 conv2d_20[0][0] -____________________________________________________________________________________________________ -activation_18 (Activation) (None, 28, 28, 128) 0 batch_normalization_20[0][0] -____________________________________________________________________________________________________ -conv2d_21 (Conv2D) (None, 28, 28, 512) 66048 activation_18[0][0] -____________________________________________________________________________________________________ -batch_normalization_21 (BatchNor (None, 28, 28, 512) 2048 conv2d_21[0][0] -____________________________________________________________________________________________________ -add_6 (Add) (None, 28, 28, 512) 0 batch_normalization_21[0][0] - activation_16[0][0] -____________________________________________________________________________________________________ -activation_19 (Activation) (None, 28, 28, 512) 0 add_6[0][0] -____________________________________________________________________________________________________ -conv2d_22 (Conv2D) (None, 28, 28, 128) 65664 activation_19[0][0] -____________________________________________________________________________________________________ -batch_normalization_22 (BatchNor (None, 28, 28, 128) 512 conv2d_22[0][0] -____________________________________________________________________________________________________ -activation_20 (Activation) (None, 28, 28, 128) 0 batch_normalization_22[0][0] -____________________________________________________________________________________________________ -conv2d_23 (Conv2D) (None, 28, 28, 128) 147584 activation_20[0][0] -____________________________________________________________________________________________________ -batch_normalization_23 (BatchNor (None, 28, 28, 128) 512 conv2d_23[0][0] -____________________________________________________________________________________________________ -activation_21 (Activation) (None, 28, 28, 128) 0 batch_normalization_23[0][0] -____________________________________________________________________________________________________ -conv2d_24 (Conv2D) (None, 28, 28, 512) 66048 activation_21[0][0] -____________________________________________________________________________________________________ -batch_normalization_24 (BatchNor (None, 28, 28, 512) 2048 conv2d_24[0][0] -____________________________________________________________________________________________________ -add_7 (Add) (None, 28, 28, 512) 0 batch_normalization_24[0][0] - activation_19[0][0] -____________________________________________________________________________________________________ -activation_22 (Activation) (None, 28, 28, 512) 0 add_7[0][0] -____________________________________________________________________________________________________ -conv2d_26 (Conv2D) (None, 14, 14, 256) 131328 activation_22[0][0] -____________________________________________________________________________________________________ -batch_normalization_26 (BatchNor (None, 14, 14, 256) 1024 conv2d_26[0][0] -____________________________________________________________________________________________________ -activation_23 (Activation) (None, 14, 14, 256) 0 batch_normalization_26[0][0] -____________________________________________________________________________________________________ -conv2d_27 (Conv2D) (None, 14, 14, 256) 590080 activation_23[0][0] -____________________________________________________________________________________________________ -batch_normalization_27 (BatchNor (None, 14, 14, 256) 1024 conv2d_27[0][0] -____________________________________________________________________________________________________ -activation_24 (Activation) (None, 14, 14, 256) 0 batch_normalization_27[0][0] -____________________________________________________________________________________________________ -conv2d_28 (Conv2D) (None, 14, 14, 1024) 263168 activation_24[0][0] -____________________________________________________________________________________________________ -conv2d_25 (Conv2D) (None, 14, 14, 1024) 525312 activation_22[0][0] -____________________________________________________________________________________________________ -batch_normalization_28 (BatchNor (None, 14, 14, 1024) 4096 conv2d_28[0][0] -____________________________________________________________________________________________________ -batch_normalization_25 (BatchNor (None, 14, 14, 1024) 4096 conv2d_25[0][0] -____________________________________________________________________________________________________ -add_8 (Add) (None, 14, 14, 1024) 0 batch_normalization_28[0][0] - batch_normalization_25[0][0] -____________________________________________________________________________________________________ -activation_25 (Activation) (None, 14, 14, 1024) 0 add_8[0][0] -____________________________________________________________________________________________________ -conv2d_29 (Conv2D) (None, 14, 14, 256) 262400 activation_25[0][0] -____________________________________________________________________________________________________ -batch_normalization_29 (BatchNor (None, 14, 14, 256) 1024 conv2d_29[0][0] -____________________________________________________________________________________________________ -activation_26 (Activation) (None, 14, 14, 256) 0 batch_normalization_29[0][0] -____________________________________________________________________________________________________ -conv2d_30 (Conv2D) (None, 14, 14, 256) 590080 activation_26[0][0] -____________________________________________________________________________________________________ -batch_normalization_30 (BatchNor (None, 14, 14, 256) 1024 conv2d_30[0][0] -____________________________________________________________________________________________________ -activation_27 (Activation) (None, 14, 14, 256) 0 batch_normalization_30[0][0] -____________________________________________________________________________________________________ -conv2d_31 (Conv2D) (None, 14, 14, 1024) 263168 activation_27[0][0] -____________________________________________________________________________________________________ -batch_normalization_31 (BatchNor (None, 14, 14, 1024) 4096 conv2d_31[0][0] -____________________________________________________________________________________________________ -add_9 (Add) (None, 14, 14, 1024) 0 batch_normalization_31[0][0] - activation_25[0][0] -____________________________________________________________________________________________________ -activation_28 (Activation) (None, 14, 14, 1024) 0 add_9[0][0] -____________________________________________________________________________________________________ -conv2d_32 (Conv2D) (None, 14, 14, 256) 262400 activation_28[0][0] -____________________________________________________________________________________________________ -batch_normalization_32 (BatchNor (None, 14, 14, 256) 1024 conv2d_32[0][0] -____________________________________________________________________________________________________ -activation_29 (Activation) (None, 14, 14, 256) 0 batch_normalization_32[0][0] -____________________________________________________________________________________________________ -conv2d_33 (Conv2D) (None, 14, 14, 256) 590080 activation_29[0][0] -____________________________________________________________________________________________________ -batch_normalization_33 (BatchNor (None, 14, 14, 256) 1024 conv2d_33[0][0] -____________________________________________________________________________________________________ -activation_30 (Activation) (None, 14, 14, 256) 0 batch_normalization_33[0][0] -____________________________________________________________________________________________________ -conv2d_34 (Conv2D) (None, 14, 14, 1024) 263168 activation_30[0][0] -____________________________________________________________________________________________________ -batch_normalization_34 (BatchNor (None, 14, 14, 1024) 4096 conv2d_34[0][0] -____________________________________________________________________________________________________ -add_10 (Add) (None, 14, 14, 1024) 0 batch_normalization_34[0][0] - activation_28[0][0] -____________________________________________________________________________________________________ -activation_31 (Activation) (None, 14, 14, 1024) 0 add_10[0][0] -____________________________________________________________________________________________________ -conv2d_35 (Conv2D) (None, 14, 14, 256) 262400 activation_31[0][0] -____________________________________________________________________________________________________ -batch_normalization_35 (BatchNor (None, 14, 14, 256) 1024 conv2d_35[0][0] -____________________________________________________________________________________________________ -activation_32 (Activation) (None, 14, 14, 256) 0 batch_normalization_35[0][0] -____________________________________________________________________________________________________ -conv2d_36 (Conv2D) (None, 14, 14, 256) 590080 activation_32[0][0] -____________________________________________________________________________________________________ -batch_normalization_36 (BatchNor (None, 14, 14, 256) 1024 conv2d_36[0][0] -____________________________________________________________________________________________________ -activation_33 (Activation) (None, 14, 14, 256) 0 batch_normalization_36[0][0] -____________________________________________________________________________________________________ -conv2d_37 (Conv2D) (None, 14, 14, 1024) 263168 activation_33[0][0] -____________________________________________________________________________________________________ -batch_normalization_37 (BatchNor (None, 14, 14, 1024) 4096 conv2d_37[0][0] -____________________________________________________________________________________________________ -add_11 (Add) (None, 14, 14, 1024) 0 batch_normalization_37[0][0] - activation_31[0][0] -____________________________________________________________________________________________________ -activation_34 (Activation) (None, 14, 14, 1024) 0 add_11[0][0] -____________________________________________________________________________________________________ -conv2d_38 (Conv2D) (None, 14, 14, 256) 262400 activation_34[0][0] -____________________________________________________________________________________________________ -batch_normalization_38 (BatchNor (None, 14, 14, 256) 1024 conv2d_38[0][0] -____________________________________________________________________________________________________ -activation_35 (Activation) (None, 14, 14, 256) 0 batch_normalization_38[0][0] -____________________________________________________________________________________________________ -conv2d_39 (Conv2D) (None, 14, 14, 256) 590080 activation_35[0][0] -____________________________________________________________________________________________________ -batch_normalization_39 (BatchNor (None, 14, 14, 256) 1024 conv2d_39[0][0] -____________________________________________________________________________________________________ -activation_36 (Activation) (None, 14, 14, 256) 0 batch_normalization_39[0][0] -____________________________________________________________________________________________________ -conv2d_40 (Conv2D) (None, 14, 14, 1024) 263168 activation_36[0][0] -____________________________________________________________________________________________________ -batch_normalization_40 (BatchNor (None, 14, 14, 1024) 4096 conv2d_40[0][0] -____________________________________________________________________________________________________ -add_12 (Add) (None, 14, 14, 1024) 0 batch_normalization_40[0][0] - activation_34[0][0] -____________________________________________________________________________________________________ -activation_37 (Activation) (None, 14, 14, 1024) 0 add_12[0][0] -____________________________________________________________________________________________________ -conv2d_41 (Conv2D) (None, 14, 14, 256) 262400 activation_37[0][0] -____________________________________________________________________________________________________ -batch_normalization_41 (BatchNor (None, 14, 14, 256) 1024 conv2d_41[0][0] -____________________________________________________________________________________________________ -activation_38 (Activation) (None, 14, 14, 256) 0 batch_normalization_41[0][0] -____________________________________________________________________________________________________ -conv2d_42 (Conv2D) (None, 14, 14, 256) 590080 activation_38[0][0] -____________________________________________________________________________________________________ -batch_normalization_42 (BatchNor (None, 14, 14, 256) 1024 conv2d_42[0][0] -____________________________________________________________________________________________________ -activation_39 (Activation) (None, 14, 14, 256) 0 batch_normalization_42[0][0] -____________________________________________________________________________________________________ -conv2d_43 (Conv2D) (None, 14, 14, 1024) 263168 activation_39[0][0] -____________________________________________________________________________________________________ -batch_normalization_43 (BatchNor (None, 14, 14, 1024) 4096 conv2d_43[0][0] -____________________________________________________________________________________________________ -add_13 (Add) (None, 14, 14, 1024) 0 batch_normalization_43[0][0] - activation_37[0][0] -____________________________________________________________________________________________________ -activation_40 (Activation) (None, 14, 14, 1024) 0 add_13[0][0] -____________________________________________________________________________________________________ -conv2d_45 (Conv2D) (None, 7, 7, 512) 524800 activation_40[0][0] -____________________________________________________________________________________________________ -batch_normalization_45 (BatchNor (None, 7, 7, 512) 2048 conv2d_45[0][0] -____________________________________________________________________________________________________ -activation_41 (Activation) (None, 7, 7, 512) 0 batch_normalization_45[0][0] -____________________________________________________________________________________________________ -conv2d_46 (Conv2D) (None, 7, 7, 512) 2359808 activation_41[0][0] -____________________________________________________________________________________________________ -batch_normalization_46 (BatchNor (None, 7, 7, 512) 2048 conv2d_46[0][0] -____________________________________________________________________________________________________ -activation_42 (Activation) (None, 7, 7, 512) 0 batch_normalization_46[0][0] -____________________________________________________________________________________________________ -conv2d_47 (Conv2D) (None, 7, 7, 2048) 1050624 activation_42[0][0] -____________________________________________________________________________________________________ -conv2d_44 (Conv2D) (None, 7, 7, 2048) 2099200 activation_40[0][0] -____________________________________________________________________________________________________ -batch_normalization_47 (BatchNor (None, 7, 7, 2048) 8192 conv2d_47[0][0] -____________________________________________________________________________________________________ -batch_normalization_44 (BatchNor (None, 7, 7, 2048) 8192 conv2d_44[0][0] -____________________________________________________________________________________________________ -add_14 (Add) (None, 7, 7, 2048) 0 batch_normalization_47[0][0] - batch_normalization_44[0][0] -____________________________________________________________________________________________________ -activation_43 (Activation) (None, 7, 7, 2048) 0 add_14[0][0] -____________________________________________________________________________________________________ -conv2d_48 (Conv2D) (None, 7, 7, 512) 1049088 activation_43[0][0] -____________________________________________________________________________________________________ -batch_normalization_48 (BatchNor (None, 7, 7, 512) 2048 conv2d_48[0][0] -____________________________________________________________________________________________________ -activation_44 (Activation) (None, 7, 7, 512) 0 batch_normalization_48[0][0] -____________________________________________________________________________________________________ -conv2d_49 (Conv2D) (None, 7, 7, 512) 2359808 activation_44[0][0] -____________________________________________________________________________________________________ -batch_normalization_49 (BatchNor (None, 7, 7, 512) 2048 conv2d_49[0][0] -____________________________________________________________________________________________________ -activation_45 (Activation) (None, 7, 7, 512) 0 batch_normalization_49[0][0] -____________________________________________________________________________________________________ -conv2d_50 (Conv2D) (None, 7, 7, 2048) 1050624 activation_45[0][0] -____________________________________________________________________________________________________ -batch_normalization_50 (BatchNor (None, 7, 7, 2048) 8192 conv2d_50[0][0] -____________________________________________________________________________________________________ -add_15 (Add) (None, 7, 7, 2048) 0 batch_normalization_50[0][0] - activation_43[0][0] -____________________________________________________________________________________________________ -activation_46 (Activation) (None, 7, 7, 2048) 0 add_15[0][0] -____________________________________________________________________________________________________ -conv2d_51 (Conv2D) (None, 7, 7, 512) 1049088 activation_46[0][0] -____________________________________________________________________________________________________ -batch_normalization_51 (BatchNor (None, 7, 7, 512) 2048 conv2d_51[0][0] -____________________________________________________________________________________________________ -activation_47 (Activation) (None, 7, 7, 512) 0 batch_normalization_51[0][0] -____________________________________________________________________________________________________ -conv2d_52 (Conv2D) (None, 7, 7, 512) 2359808 activation_47[0][0] -____________________________________________________________________________________________________ -batch_normalization_52 (BatchNor (None, 7, 7, 512) 2048 conv2d_52[0][0] -____________________________________________________________________________________________________ -activation_48 (Activation) (None, 7, 7, 512) 0 batch_normalization_52[0][0] -____________________________________________________________________________________________________ -conv2d_53 (Conv2D) (None, 7, 7, 2048) 1050624 activation_48[0][0] -____________________________________________________________________________________________________ -batch_normalization_53 (BatchNor (None, 7, 7, 2048) 8192 conv2d_53[0][0] -____________________________________________________________________________________________________ -add_16 (Add) (None, 7, 7, 2048) 0 batch_normalization_53[0][0] - activation_46[0][0] -____________________________________________________________________________________________________ -activation_49 (Activation) (None, 7, 7, 2048) 0 add_16[0][0] -____________________________________________________________________________________________________ -global_avg_pooling (GlobalAverag (None, 2048) 0 activation_49[0][0] -____________________________________________________________________________________________________ -dense_1 (Dense) (None, 10) 20490 global_avg_pooling[0][0] -____________________________________________________________________________________________________ -activation_50 (Activation) (None, 10) 0 dense_1[0][0] -==================================================================================================== Total params: 23,608,202 Trainable params: 23,555,082 Non-trainable params: 53,120 @@ -507,22 +135,6 @@ Let us explain the details shown above: Once you are done training your custom model, you can use the "CustomImagePrediction" class to perform image prediction with your model. Simply follow the link below. [imageai/Classification/CUSTOMCLASSIFICATION.md](https://github.com/OlafenwaMoses/ImageAI/blob/master/imageai/Classification/CUSTOMCLASSIFICATION.md) -### Saving Full Custom Model -
- -**ImageAI** now allows you to your custom model in full during training, which ensures you can perform custom prediction without necessarily specifying the network type. -All you need to do is set the paramater `save_full_model` to `True` in your `trainModel()` function. -See an example code below. - -```python -from imageai.Prediction.Custom import ModelTraining -import os - -trainer = ModelTraining() -trainer.setModelTypeAsDenseNet() -trainer.setDataDirectory("idenprof") -trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=16, show_network_summary=True, save_full_model=True) -``` ### Training on the IdenProf data @@ -537,7 +149,7 @@ import requests import shutil from zipfile import ZipFile import os -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer execution_path = os.getcwd() @@ -591,8 +203,8 @@ if(len(os.listdir(DATASET_TEST_DIR)) < 10): extract.close() -model_trainer = ModelTraining() -model_trainer.setModelTypeAsResNet() +model_trainer = ClassificationModelTrainer() +model_trainer.setModelTypeAsResNet50() model_trainer.setDataDirectory(DATASET_DIR) model_trainer.trainModel(num_objects=10, num_experiments=100, enhance_data=True, batch_size=32, show_network_summary=True) ``` @@ -607,11 +219,11 @@ All you need to do is specify the `continue_from_model` parameter to the path of See an example code below. ```python -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer import os -trainer = ModelTraining() -trainer.setModelTypeAsDenseNet() +trainer = ClassificationModelTrainer() +trainer.setModelTypeAsDenseNet121() trainer.setDataDirectory("idenprof") trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=8, show_network_summary=True, continue_from_model="idenprof_densenet-0.763500.h5") ``` @@ -625,20 +237,15 @@ To ensure they can still train very accurate custom models using few number of i All you need to do is specify the `transfer_from_model` parameter to the path of the pre-trained model, `initial_num_objects` parameter which corresponds to the number of objects in the previous dataset the pre-trained model was trained on, all in your `trainModel()` function. See an example code below, showing how to perform transfer learning from a ResNet50 model trained on the ImageNet dataset. ```python -from imageai.Prediction.Custom import ModelTraining +from imageai.Classification.Custom import ClassificationModelTrainer import os -trainer = ModelTraining() -trainer.setModelTypeAsResNet() +trainer = ClassificationModelTrainer() +trainer.setModelTypeAsResNet50() trainer.setDataDirectory("idenprof") -trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=32, show_network_summary=True,transfer_from_model="resnet50_weights_tf_dim_ordering_tf_kernels.h5", initial_num_objects=1000) +trainer.trainModel(num_objects=10, num_experiments=50, enhance_data=True, batch_size=32, show_network_summary=True,transfer_from_model="resnet50_imagenet_tf.2.0.h5", initial_num_objects=1000) ``` -### Submitting Custom Model - -We are providing an opportunity for anyone that uses to train a model to submit the model and its JSON mapping file - and have it listed in this repository. Reach to the details below should intend to share your trained model in this repository. - ### Contact Developer - **Moses Olafenwa** diff --git a/imageai/Detection/README.md b/imageai/Detection/README.md index 1798082a..44cd80ea 100644 --- a/imageai/Detection/README.md +++ b/imageai/Detection/README.md @@ -14,10 +14,9 @@ A **DeepQuest AI** project [https://deepquestai.com](https://deepquestai.com) ImageAI provides very convenient and powerful methods to perform object detection on images and extract each object from the image. The object detection class supports RetinaNet, YOLOv3 and TinyYOLOv3. To start performing object detection, you must download the RetinaNet, YOLOv3 or TinyYOLOv3 object detection model via the links below: -* **[RetinaNet](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/resnet50_coco_best_v2.0.1.h5)** _(Size = 145 mb, high performance and accuracy, with longer detection time)_ +* **[RetinaNet](https://github.com/OlafenwaMoses/ImageAI/releases/download/essentials-v5/resnet50_coco_best_v2.1.0.h5)** _(Size = 145 mb, high performance and accuracy, with longer detection time)_ * **[YOLOv3](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/yolo.h5)** _(Size = 237 mb, moderate performance and accuracy, with a moderate detection time)_ * **[TinyYOLOv3](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/yolo-tiny.h5)** _(Size = 34 mb, optimized for speed and moderate performance, with fast detection time)_ -* **[RetinaNet](https://github.com/OlafenwaMoses/ImageAI/releases/download/1.0/resnet50_coco_best_v2.0.1.h5)** _(Size = 145 mb, high performance and accuracy, with longer detection time)_ Once you download the object detection model file, you should copy the model file to the your project folder where your .py files will be. diff --git a/test/test_custom_recognition.py b/test/test_custom_recognition.py index 8b61a269..a93f572b 100644 --- a/test/test_custom_recognition.py +++ b/test/test_custom_recognition.py @@ -1,4 +1,4 @@ -from imageai.Prediction.Custom import CustomImageClassification +from imageai.Classification.Custom import CustomImageClassification import pytest from os.path import dirname import os diff --git a/test/test_image_recognition.py b/test/test_image_recognition.py index 4a79a289..547764cc 100644 --- a/test/test_image_recognition.py +++ b/test/test_image_recognition.py @@ -1,4 +1,4 @@ -from imageai.Prediction import ImageClassification +from imageai.Classification import ImageClassification import os import cv2 import pytest diff --git a/test/test_model_training.py b/test/test_model_training.py index 8341c0eb..d9d4d00b 100644 --- a/test/test_model_training.py +++ b/test/test_model_training.py @@ -1,4 +1,4 @@ -from imageai.Prediction.Custom import ClassificationModelTrainer +from imageai.Classification.Custom import ClassificationModelTrainer import os import pytest import shutil