├── LICENSE ├── Portfolio.ipynb ├── README.md ├── Research_paper.pdf └── code ├── YOLOV3 ├── Portfolio.ipynb ├── core │ ├── __pycache__ │ │ ├── backbone.cpython-36.pyc │ │ ├── backbone.cpython-37.pyc │ │ ├── common.cpython-36.pyc │ │ ├── common.cpython-37.pyc │ │ ├── config.cpython-36.pyc │ │ ├── config.cpython-37.pyc │ │ ├── dataset.cpython-36.pyc │ │ ├── utils.cpython-36.pyc │ │ ├── utils.cpython-37.pyc │ │ ├── yolov3.cpython-36.pyc │ │ └── yolov3.cpython-37.pyc │ ├── backbone.py │ ├── common.py │ ├── config.py │ ├── dataset.py │ ├── untitled │ ├── utils.py │ └── yolov3.py ├── data │ ├── anchors │ │ └── basline_anchors.txt │ ├── classes │ │ ├── coco.names │ │ └── darkdata.names │ └── dataset │ │ ├── annotations_preprocessing_methods.ipynb │ │ ├── exdark_test.txt │ │ ├── exdark_train.txt │ │ ├── test_benchmark.txt │ │ ├── test_dhe.txt │ │ ├── test_he.txt │ │ ├── test_ying.txt │ │ └── train_test_annotations.ipynb ├── image_demo.py ├── predictions │ ├── detection_2015_00001.png │ ├── detection_2015_00049.jpg │ ├── detection_2015_00058.jpg │ ├── detection_2015_00108.jpg │ ├── detection_2015_03065.jpg │ ├── detection_2015_03165.jpg │ ├── detection_2015_04977.jpg │ └── detection_2015_05218.jpg ├── requirements.txt ├── test.ipynb ├── test.py └── train_yolov3_darkdata.ipynb ├── image_enhancement_techniques ├── Ying.ipynb ├── dhe.ipynb └── he.ipynb └── mAP ├── __init__.py ├── extra ├── README.md ├── class_list.txt ├── convert_gt_xml.py ├── convert_gt_yolo.py ├── convert_keras-yolo3.py ├── convert_pred_darkflow_json.py ├── convert_pred_yolo.py ├── find_class.py ├── intersect-gt-and-pred.py ├── remove_class.py ├── remove_delimiter_char.py ├── remove_space.py ├── rename_class.py └── result.txt ├── mAP.ipynb └── main.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jaminaveen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YOLOv3_Low_Light_Object_Detection 2 | This is a research project to understand the performance of YOLOv3 model in low light and find efficient ways to improve its performance in low light. 3 | We have used the Exclusively Dark (ExDark) dataset. We have compared the mAP(mean Average Precision) of YOLOv3 obtained on COCO dataset to that of the mAP obtained on ExDark dataset and identified that the efficiency of the algorithm is very less in low light when compared to normal image dataset. This research sums up the contrast-based image enhancement techniques applied to preprocess our dataset and compared the results with our benchmark obtained on original ExDark dataset. Also we have compared the training YOLOv3 from scratch and transfer learning techniques. 4 | 5 | 6 | Steps : 7 | 8 | [1] Clone this file 9 | 10 | $ git clone https://github.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/ 11 | 12 | [2] Download the ExDark data from the link : https://github.com/cs-chan/Exclusively-Dark-Image-Dataset 13 | 14 | [3] Dependencies to run the code 15 | 16 | $ pip install -r ./docs/requirements.txt 17 | 18 | [4] Exporting loaded COCO weights as TF checkpoint(yolov3_coco.ckpt) 19 | 20 | $ cd YOLO 21 | $ wget wget https://pjreddie.com/media/files/yolov3.weights 22 | 23 | [5] Data preprocessing to get all images from ExDark and ExDark_Anno into a folder called test_benchmark folder and creating annotations file called test_benchmark.txt, test_he.txt, test_dhe.txt,test_ying.txt are present in /data/dataset/ folder. We have also uploaded notebooks in the same folder that will guide through the generation of annotations file. 24 | 25 | Annotation files should be in following format 26 | xxx/xxx.jpg 18.19,6.32,424.13,421.83,20 323.86,2.65,640.0,421.94,20 27 | xxx/xxx.jpg 48,240,195,371,11 8,12,352,498,14 28 | # image_path x_min, y_min, x_max, y_max, class_id x_min, y_min ,..., class_id 29 | 30 | 31 | [6] Testing on ExDark data set to obtain the Benchmark mAP 32 | 33 | ---> Edit the file $ cd core/config.py to make necessary configurations. 34 | 35 | __C.YOLO.CLASSES = "./data/classes/coco.names" 36 | __C.TEST.ANNOT_PATH = "./data/dataset/test_benchmark.txt" 37 | 38 | ---> Testing and Evaluation 39 | 40 | $ python test.py 41 | $ python ../mAP/main.py 42 | 43 | [7] Applying the histogram equalization enhancement technique on the ExDark data. 44 | 45 | ---> Run the $ he.py code to perform the image contrast enhancement. The enhanced images are saved in the path /data/dataset/he_train 46 | ---> Run the dataprep.py to obtain the annotations path as dataset_he.txt in the path /data/dataset/dataset_he.txt 47 | ---> Edit the file $ cd core/config.py to make necessary configurations. 48 | 49 | __C.TEST.ANNOT_PATH = "./data/dataset/test_he.txt" 50 | 51 | ---> Now run the test.py to test the YOLOv3 on the he enhanced images 52 | ---> Evaluate the performance by computing the mAP score. To do this : 53 | 54 | $ python ..mAP/main.py 55 | 56 | [8] Applying the dynamic histogram equalization enhancement technique on the ExDark data. 57 | 58 | ---> Run the $ dhe.py code to perform the image contrast enhancement. The enhanced images are saved in the path /data/dataset/dhe_train 59 | ---> Run the dataprep.py to obtain the annotations path as dataset_he.txt in the path /data/dataset/dataset_dhe.txt 60 | ---> Edit the file $ cd core/config.py to make necessary configurations. 61 | 62 | __C.TEST.ANNOT_PATH = "./data/dataset/test_dhe.txt" 63 | 64 | ---> Now run the test.py to test the YOLOv3 on the dhe enhanced images 65 | ---> Evaluate the performance by computing the mAP score. To do this : 66 | 67 | $ cd mAP 68 | $ python main.py -na 69 | 70 | [9] Applying the exposure fusion framework enhancement technique on the ExDark data. 71 | 72 | ---> Run the $ Ying.py code to perform the image contrast enhancement. The enhanced images are saved in the path /data/dataset/ying_train 73 | ---> Run the dataprep.py to obtain the annotations path as dataset_ying.txt in the path /data/dataset/dataset_ying.txt 74 | ---> Edit the file $ cd core/config.py to make necessary configurations. 75 | 76 | __C.TEST.ANNOT_PATH = "./data/dataset/test_ying.txt" 77 | 78 | ---> Now run the test.py to test the YOLOv3 on the he enhanced images 79 | ---> Evaluate the performance by computing the mAP score. To do this : 80 | 81 | python ..mAP/main.py 82 | 83 | [10] Code for transfer learning and fine tuning on final layers is present in train_yolov3_darkdata.ipynb 84 | 85 | [11] Insights we found on low light images using ExDark dataset were documented in our paper. Please refer our ([Research paper](https://github.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/blob/master/Research_paper.pdf)) 86 | 87 | 88 | #### Reference: 89 | 90 | 1. https://github.com/YunYang1994/TensorFlow2.0-Examples/tree/master/4-Object_Detection/YOLOV3 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /Research_paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/Research_paper.pdf -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/backbone.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/backbone.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/backbone.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/backbone.cpython-37.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/common.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/common.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/common.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/common.cpython-37.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/config.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/config.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/config.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/config.cpython-37.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/dataset.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/dataset.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/utils.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/utils.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/utils.cpython-37.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/yolov3.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/yolov3.cpython-36.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/__pycache__/yolov3.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/__pycache__/yolov3.cpython-37.pyc -------------------------------------------------------------------------------- /code/YOLOV3/core/backbone.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : backbone.py 8 | # Author : YunYang1994 9 | # Created date: 2019-07-11 23:37:51 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | import tensorflow as tf 15 | import core.common as common 16 | 17 | 18 | def darknet53(input_data): 19 | 20 | input_data = common.convolutional(input_data, (3, 3, 3, 32)) 21 | input_data = common.convolutional(input_data, (3, 3, 32, 64), downsample=True) 22 | 23 | for i in range(1): 24 | input_data = common.residual_block(input_data, 64, 32, 64) 25 | 26 | input_data = common.convolutional(input_data, (3, 3, 64, 128), downsample=True) 27 | 28 | for i in range(2): 29 | input_data = common.residual_block(input_data, 128, 64, 128) 30 | 31 | input_data = common.convolutional(input_data, (3, 3, 128, 256), downsample=True) 32 | 33 | for i in range(8): 34 | input_data = common.residual_block(input_data, 256, 128, 256) 35 | 36 | route_1 = input_data 37 | input_data = common.convolutional(input_data, (3, 3, 256, 512), downsample=True) 38 | 39 | for i in range(8): 40 | input_data = common.residual_block(input_data, 512, 256, 512) 41 | 42 | route_2 = input_data 43 | input_data = common.convolutional(input_data, (3, 3, 512, 1024), downsample=True) 44 | 45 | for i in range(4): 46 | input_data = common.residual_block(input_data, 1024, 512, 1024) 47 | 48 | return route_1, route_2, input_data 49 | 50 | 51 | -------------------------------------------------------------------------------- /code/YOLOV3/core/common.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | #! /usr/bin/env python 5 | # coding=utf-8 6 | #================================================================ 7 | # Copyright (C) 2019 * Ltd. All rights reserved. 8 | # 9 | # Editor : VIM 10 | # File name : common.py 11 | # Author : YunYang1994 12 | # Created date: 2019-07-11 23:12:53 13 | # Description : 14 | # 15 | #================================================================ 16 | 17 | import tensorflow as tf 18 | 19 | class BatchNormalization(tf.keras.layers.BatchNormalization): 20 | """ 21 | "Frozen state" and "inference mode" are two separate concepts. 22 | `layer.trainable = False` is to freeze the layer, so the layer will use 23 | stored moving `var` and `mean` in the "inference mode", and both `gama` 24 | and `beta` will not be updated ! 25 | """ 26 | def call(self, x, training=False): 27 | if not training: 28 | training = tf.constant(False) 29 | training = tf.logical_and(training, self.trainable) 30 | return super().call(x, training) 31 | 32 | def convolutional(input_layer, filters_shape, downsample=False, activate=True, bn=True): 33 | if downsample: 34 | input_layer = tf.keras.layers.ZeroPadding2D(((1, 0), (1, 0)))(input_layer) 35 | padding = 'valid' 36 | strides = 2 37 | else: 38 | strides = 1 39 | padding = 'same' 40 | 41 | conv = tf.keras.layers.Conv2D(filters=filters_shape[-1], kernel_size = filters_shape[0], strides=strides, padding=padding, 42 | use_bias=not bn, kernel_regularizer=tf.keras.regularizers.l2(0.0005), 43 | kernel_initializer=tf.random_normal_initializer(stddev=0.01), 44 | bias_initializer=tf.constant_initializer(0.))(input_layer) 45 | 46 | if bn: conv = BatchNormalization()(conv) 47 | if activate == True: conv = tf.nn.leaky_relu(conv, alpha=0.1) 48 | 49 | return conv 50 | 51 | def residual_block(input_layer, input_channel, filter_num1, filter_num2): 52 | short_cut = input_layer 53 | conv = convolutional(input_layer, filters_shape=(1, 1, input_channel, filter_num1)) 54 | conv = convolutional(conv , filters_shape=(3, 3, filter_num1, filter_num2)) 55 | 56 | residual_output = short_cut + conv 57 | return residual_output 58 | 59 | def upsample(input_layer): 60 | return tf.image.resize(input_layer, (input_layer.shape[1] * 2, input_layer.shape[2] * 2), method='nearest') -------------------------------------------------------------------------------- /code/YOLOV3/core/config.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : config.py 8 | # Author : YunYang1994 9 | # Created date: 2019-02-28 13:06:54 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | from easydict import EasyDict as edict 15 | 16 | 17 | __C = edict() 18 | # Consumers can get config by: from config import cfg 19 | 20 | cfg = __C 21 | 22 | # YOLO options 23 | __C.YOLO = edict() 24 | 25 | # Set the class name 26 | #__C.YOLO.CLASSES = "./data/classes/darkdata.names" 27 | __C.YOLO.CLASSES = "./data/classes/coco.names" 28 | __C.YOLO.ANCHORS = "./data/anchors/basline_anchors.txt" 29 | __C.YOLO.STRIDES = [8, 16, 32] 30 | __C.YOLO.ANCHOR_PER_SCALE = 3 31 | __C.YOLO.IOU_LOSS_THRESH = 0.5 32 | 33 | # Train options 34 | __C.TRAIN = edict() 35 | 36 | __C.TRAIN.ANNOT_PATH = "./data/dataset/exdark_train.txt" 37 | __C.TRAIN.BATCH_SIZE = 32 38 | # __C.TRAIN.INPUT_SIZE = [320, 352, 384, 416, 448, 480, 512, 544, 576, 608] 39 | __C.TRAIN.INPUT_SIZE = [416] 40 | __C.TRAIN.DATA_AUG = False 41 | __C.TRAIN.LR_INIT = 1e-3 42 | __C.TRAIN.LR_END = 1e-6 43 | __C.TRAIN.WARMUP_EPOCHS = 2 44 | __C.TRAIN.EPOCHS = 30 45 | 46 | 47 | 48 | # TEST options 49 | __C.TEST = edict() 50 | 51 | __C.TEST.ANNOT_PATH = "./data/dataset/test_benchmark.txt" 52 | __C.TEST.BATCH_SIZE = 16 53 | __C.TEST.INPUT_SIZE = 416 54 | __C.TEST.DATA_AUG = False 55 | __C.TEST.DECTECTED_IMAGE_PATH = "./data/detection/" 56 | __C.TEST.SCORE_THRESHOLD = 0.25 57 | __C.TEST.IOU_THRESHOLD = 0.5 58 | 59 | 60 | -------------------------------------------------------------------------------- /code/YOLOV3/core/dataset.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : dataset.py 8 | # Author : YunYang1994 9 | # Created date: 2019-03-15 18:05:03 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | import os 15 | import cv2 16 | import random 17 | import numpy as np 18 | import tensorflow as tf 19 | import core.utils as utils 20 | from core.config import cfg 21 | 22 | 23 | 24 | class Dataset(object): 25 | """implement Dataset here""" 26 | def __init__(self, dataset_type): 27 | self.annot_path = cfg.TRAIN.ANNOT_PATH if dataset_type == 'train' else cfg.TEST.ANNOT_PATH 28 | self.input_sizes = cfg.TRAIN.INPUT_SIZE if dataset_type == 'train' else cfg.TEST.INPUT_SIZE 29 | self.batch_size = cfg.TRAIN.BATCH_SIZE if dataset_type == 'train' else cfg.TEST.BATCH_SIZE 30 | self.data_aug = cfg.TRAIN.DATA_AUG if dataset_type == 'train' else cfg.TEST.DATA_AUG 31 | 32 | self.train_input_sizes = cfg.TRAIN.INPUT_SIZE 33 | self.strides = np.array(cfg.YOLO.STRIDES) 34 | self.classes = utils.read_class_names(cfg.YOLO.CLASSES) 35 | self.num_classes = len(self.classes) 36 | self.anchors = np.array(utils.get_anchors(cfg.YOLO.ANCHORS)) 37 | self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE 38 | self.max_bbox_per_scale = 150 39 | 40 | self.annotations = self.load_annotations(dataset_type) 41 | self.num_samples = len(self.annotations) 42 | self.num_batchs = int(np.ceil(self.num_samples / self.batch_size)) 43 | self.batch_count = 0 44 | 45 | 46 | def load_annotations(self, dataset_type): 47 | with open(self.annot_path, 'r') as f: 48 | txt = f.readlines() 49 | annotations = [line.strip() for line in txt if len(line.strip().split()[1:]) != 0] 50 | np.random.shuffle(annotations) 51 | return annotations 52 | 53 | def __iter__(self): 54 | return self 55 | 56 | def __next__(self): 57 | 58 | with tf.device('/cpu:0'): 59 | self.train_input_size = random.choice(self.train_input_sizes) 60 | self.train_output_sizes = self.train_input_size // self.strides 61 | #print(self.train_input_size,self.strides,self.train_output_sizes) 62 | 63 | 64 | batch_image = np.zeros((self.batch_size, self.train_input_size, self.train_input_size, 3), dtype=np.float32) 65 | 66 | batch_label_sbbox = np.zeros((self.batch_size, self.train_output_sizes[0], self.train_output_sizes[0], 67 | self.anchor_per_scale, 5 + self.num_classes), dtype=np.float32) 68 | batch_label_mbbox = np.zeros((self.batch_size, self.train_output_sizes[1], self.train_output_sizes[1], 69 | self.anchor_per_scale, 5 + self.num_classes), dtype=np.float32) 70 | batch_label_lbbox = np.zeros((self.batch_size, self.train_output_sizes[2], self.train_output_sizes[2], 71 | self.anchor_per_scale, 5 + self.num_classes), dtype=np.float32) 72 | 73 | batch_sbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4), dtype=np.float32) 74 | batch_mbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4), dtype=np.float32) 75 | batch_lbboxes = np.zeros((self.batch_size, self.max_bbox_per_scale, 4), dtype=np.float32) 76 | 77 | num = 0 78 | unhandled_image = [] 79 | if self.batch_count < self.num_batchs: 80 | while num < self.batch_size: 81 | try: 82 | index = self.batch_count * self.batch_size + num 83 | if index >= self.num_samples: index -= self.num_samples 84 | annotation = self.annotations[index] 85 | image, bboxes = self.parse_annotation(annotation) 86 | 87 | label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes = self.preprocess_true_boxes(bboxes) 88 | batch_image[num, :, :, :] = image 89 | batch_label_sbbox[num, :, :, :, :] = label_sbbox 90 | batch_label_mbbox[num, :, :, :, :] = label_mbbox 91 | batch_label_lbbox[num, :, :, :, :] = label_lbbox 92 | batch_sbboxes[num, :, :] = sbboxes 93 | batch_mbboxes[num, :, :] = mbboxes 94 | batch_lbboxes[num, :, :] = lbboxes 95 | num += 1 96 | 97 | except: 98 | unhandled_image.append(self.batch_count) 99 | num += 1 100 | 101 | self.batch_count += 1 102 | batch_smaller_target = batch_label_sbbox, batch_sbboxes 103 | batch_medium_target = batch_label_mbbox, batch_mbboxes 104 | batch_larger_target = batch_label_lbbox, batch_lbboxes 105 | 106 | 107 | 108 | return batch_image, (batch_smaller_target, batch_medium_target, batch_larger_target),unhandled_image 109 | else: 110 | self.batch_count = 0 111 | np.random.shuffle(self.annotations) 112 | raise StopIteration 113 | 114 | def random_horizontal_flip(self, image, bboxes): 115 | 116 | if random.random() < 0.5: 117 | _, w, _ = image.shape 118 | image = image[:, ::-1, :] 119 | bboxes[:, [0,2]] = w - bboxes[:, [2,0]] 120 | 121 | return image, bboxes 122 | 123 | def random_crop(self, image, bboxes): 124 | 125 | if random.random() < 0.5: 126 | h, w, _ = image.shape 127 | max_bbox = np.concatenate([np.min(bboxes[:, 0:2], axis=0), np.max(bboxes[:, 2:4], axis=0)], axis=-1) 128 | 129 | max_l_trans = max_bbox[0] 130 | max_u_trans = max_bbox[1] 131 | max_r_trans = w - max_bbox[2] 132 | max_d_trans = h - max_bbox[3] 133 | 134 | crop_xmin = max(0, int(max_bbox[0] - random.uniform(0, max_l_trans))) 135 | crop_ymin = max(0, int(max_bbox[1] - random.uniform(0, max_u_trans))) 136 | crop_xmax = max(w, int(max_bbox[2] + random.uniform(0, max_r_trans))) 137 | crop_ymax = max(h, int(max_bbox[3] + random.uniform(0, max_d_trans))) 138 | 139 | image = image[crop_ymin : crop_ymax, crop_xmin : crop_xmax] 140 | 141 | bboxes[:, [0, 2]] = bboxes[:, [0, 2]] - crop_xmin 142 | bboxes[:, [1, 3]] = bboxes[:, [1, 3]] - crop_ymin 143 | 144 | return image, bboxes 145 | 146 | def random_translate(self, image, bboxes): 147 | 148 | if random.random() < 0.5: 149 | h, w, _ = image.shape 150 | max_bbox = np.concatenate([np.min(bboxes[:, 0:2], axis=0), np.max(bboxes[:, 2:4], axis=0)], axis=-1) 151 | 152 | max_l_trans = max_bbox[0] 153 | max_u_trans = max_bbox[1] 154 | max_r_trans = w - max_bbox[2] 155 | max_d_trans = h - max_bbox[3] 156 | 157 | tx = random.uniform(-(max_l_trans - 1), (max_r_trans - 1)) 158 | ty = random.uniform(-(max_u_trans - 1), (max_d_trans - 1)) 159 | 160 | M = np.array([[1, 0, tx], [0, 1, ty]]) 161 | image = cv2.warpAffine(image, M, (w, h)) 162 | 163 | bboxes[:, [0, 2]] = bboxes[:, [0, 2]] + tx 164 | bboxes[:, [1, 3]] = bboxes[:, [1, 3]] + ty 165 | 166 | return image, bboxes 167 | 168 | def parse_annotation(self, annotation): 169 | 170 | line = annotation.split() 171 | image_path = line[0] 172 | if not os.path.exists(image_path): 173 | raise KeyError("%s does not exist ... " %image_path) 174 | image = cv2.imread(image_path) 175 | bboxes = np.array([list(map(int, box.split(','))) for box in line[1:]]) 176 | 177 | if self.data_aug: 178 | image, bboxes = self.random_horizontal_flip(np.copy(image), np.copy(bboxes)) 179 | image, bboxes = self.random_crop(np.copy(image), np.copy(bboxes)) 180 | image, bboxes = self.random_translate(np.copy(image), np.copy(bboxes)) 181 | 182 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 183 | image, bboxes = utils.image_preporcess(np.copy(image), [self.train_input_size, self.train_input_size], np.copy(bboxes)) 184 | return image, bboxes 185 | 186 | def bbox_iou(self, boxes1, boxes2): 187 | 188 | boxes1 = np.array(boxes1) 189 | boxes2 = np.array(boxes2) 190 | 191 | boxes1_area = boxes1[..., 2] * boxes1[..., 3] 192 | boxes2_area = boxes2[..., 2] * boxes2[..., 3] 193 | 194 | boxes1 = np.concatenate([boxes1[..., :2] - boxes1[..., 2:] * 0.5, 195 | boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1) 196 | boxes2 = np.concatenate([boxes2[..., :2] - boxes2[..., 2:] * 0.5, 197 | boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1) 198 | 199 | left_up = np.maximum(boxes1[..., :2], boxes2[..., :2]) 200 | right_down = np.minimum(boxes1[..., 2:], boxes2[..., 2:]) 201 | 202 | inter_section = np.maximum(right_down - left_up, 0.0) 203 | inter_area = inter_section[..., 0] * inter_section[..., 1] 204 | union_area = boxes1_area + boxes2_area - inter_area 205 | 206 | return inter_area / union_area 207 | 208 | def preprocess_true_boxes(self, bboxes): 209 | 210 | label = [np.zeros((self.train_output_sizes[i], self.train_output_sizes[i], self.anchor_per_scale, 211 | 5 + self.num_classes)) for i in range(3)] 212 | bboxes_xywh = [np.zeros((self.max_bbox_per_scale, 4)) for _ in range(3)] 213 | bbox_count = np.zeros((3,)) 214 | #print('label0',label[0].shape) 215 | #print('label1',label[1].shape) 216 | #print('label2',label[2].shape) 217 | 218 | for bbox in bboxes: 219 | bbox_coor = bbox[:4] 220 | bbox_class_ind = bbox[4] 221 | 222 | onehot = np.zeros(self.num_classes, dtype=np.float) 223 | onehot[bbox_class_ind] = 1.0 224 | uniform_distribution = np.full(self.num_classes, 1.0 / self.num_classes) 225 | deta = 0.01 226 | smooth_onehot = onehot * (1 - deta) + deta * uniform_distribution 227 | 228 | bbox_xywh = np.concatenate([(bbox_coor[2:] + bbox_coor[:2]) * 0.5, bbox_coor[2:] - bbox_coor[:2]], axis=-1) 229 | bbox_xywh_scaled = 1.0 * bbox_xywh[np.newaxis, :] / self.strides[:, np.newaxis] 230 | 231 | iou = [] 232 | exist_positive = False 233 | for i in range(3): 234 | anchors_xywh = np.zeros((self.anchor_per_scale, 4)) 235 | anchors_xywh[:, 0:2] = np.floor(bbox_xywh_scaled[i, 0:2]).astype(np.int32) + 0.5 236 | anchors_xywh[:, 2:4] = self.anchors[i] 237 | 238 | iou_scale = self.bbox_iou(bbox_xywh_scaled[i][np.newaxis, :], anchors_xywh) 239 | iou.append(iou_scale) 240 | iou_mask = iou_scale > 0.3 241 | 242 | if np.any(iou_mask): 243 | xind, yind = np.floor(bbox_xywh_scaled[i, 0:2]).astype(np.int32) 244 | #print('xind',xind) 245 | #print('yind',yind) 246 | 247 | label[i][yind, xind, iou_mask, :] = 0 248 | label[i][yind, xind, iou_mask, 0:4] = bbox_xywh 249 | label[i][yind, xind, iou_mask, 4:5] = 1.0 250 | label[i][yind, xind, iou_mask, 5:] = smooth_onehot 251 | 252 | bbox_ind = int(bbox_count[i] % self.max_bbox_per_scale) 253 | bboxes_xywh[i][bbox_ind, :4] = bbox_xywh 254 | bbox_count[i] += 1 255 | 256 | exist_positive = True 257 | 258 | if not exist_positive: 259 | best_anchor_ind = np.argmax(np.array(iou).reshape(-1), axis=-1) 260 | best_detect = int(best_anchor_ind / self.anchor_per_scale) 261 | best_anchor = int(best_anchor_ind % self.anchor_per_scale) 262 | xind, yind = np.floor(bbox_xywh_scaled[best_detect, 0:2]).astype(np.int32) 263 | 264 | label[best_detect][yind, xind, best_anchor, :] = 0 265 | label[best_detect][yind, xind, best_anchor, 0:4] = bbox_xywh 266 | label[best_detect][yind, xind, best_anchor, 4:5] = 1.0 267 | label[best_detect][yind, xind, best_anchor, 5:] = smooth_onehot 268 | 269 | bbox_ind = int(bbox_count[best_detect] % self.max_bbox_per_scale) 270 | bboxes_xywh[best_detect][bbox_ind, :4] = bbox_xywh 271 | bbox_count[best_detect] += 1 272 | label_sbbox, label_mbbox, label_lbbox = label 273 | sbboxes, mbboxes, lbboxes = bboxes_xywh 274 | return label_sbbox, label_mbbox, label_lbbox, sbboxes, mbboxes, lbboxes 275 | 276 | def __len__(self): 277 | return self.num_batchs 278 | -------------------------------------------------------------------------------- /code/YOLOV3/core/untitled: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/core/untitled -------------------------------------------------------------------------------- /code/YOLOV3/core/utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : utils.py 8 | # Author : YunYang1994 9 | # Created date: 2019-07-12 01:33:38 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | import cv2 15 | import random 16 | import colorsys 17 | import numpy as np 18 | import tensorflow as tf 19 | from core.config import cfg 20 | 21 | def load_weights(model, weights_file): 22 | """ 23 | I agree that this code is very ugly, but I don’t know any better way of doing it. 24 | """ 25 | wf = open(weights_file, 'rb') 26 | major, minor, revision, seen, _ = np.fromfile(wf, dtype=np.int32, count=5) 27 | 28 | j = 0 29 | for i in range(75): 30 | conv_layer_name = 'conv2d_%d' %i if i > 0 else 'conv2d' 31 | bn_layer_name = 'batch_normalization_%d' %j if j > 0 else 'batch_normalization' 32 | 33 | conv_layer = model.get_layer(conv_layer_name) 34 | filters = conv_layer.filters 35 | k_size = conv_layer.kernel_size[0] 36 | in_dim = conv_layer.input_shape[-1] 37 | 38 | if i not in [58, 66, 74]: 39 | # darknet weights: [beta, gamma, mean, variance] 40 | bn_weights = np.fromfile(wf, dtype=np.float32, count=4 * filters) 41 | # tf weights: [gamma, beta, mean, variance] 42 | bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]] 43 | bn_layer = model.get_layer(bn_layer_name) 44 | j += 1 45 | else: 46 | conv_bias = np.fromfile(wf, dtype=np.float32, count=filters) 47 | 48 | # darknet shape (out_dim, in_dim, height, width) 49 | conv_shape = (filters, in_dim, k_size, k_size) 50 | conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape)) 51 | # tf shape (height, width, in_dim, out_dim) 52 | conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0]) 53 | 54 | if i not in [58, 66, 74]: 55 | conv_layer.set_weights([conv_weights]) 56 | bn_layer.set_weights(bn_weights) 57 | else: 58 | conv_layer.set_weights([conv_weights, conv_bias]) 59 | 60 | assert len(wf.read()) == 0, 'failed to read all data' 61 | wf.close() 62 | 63 | 64 | 65 | def read_class_names(class_file_name): 66 | '''loads class name from a file''' 67 | names = {} 68 | with open(class_file_name, 'r') as data: 69 | for ID, name in enumerate(data): 70 | names[ID] = name.strip('\n') 71 | return names 72 | 73 | 74 | def get_anchors(anchors_path): 75 | '''loads the anchors from a file''' 76 | with open(anchors_path) as f: 77 | anchors = f.readline() 78 | anchors = np.array(anchors.split(','), dtype=np.float32) 79 | return anchors.reshape(3, 3, 2) 80 | 81 | 82 | def image_preporcess(image, target_size, gt_boxes=None): 83 | 84 | ih, iw = target_size 85 | h, w, _ = image.shape 86 | 87 | scale = min(iw/w, ih/h) 88 | nw, nh = int(scale * w), int(scale * h) 89 | image_resized = cv2.resize(image, (nw, nh)) 90 | 91 | image_paded = np.full(shape=[ih, iw, 3], fill_value=128.0) 92 | dw, dh = (iw - nw) // 2, (ih-nh) // 2 93 | image_paded[dh:nh+dh, dw:nw+dw, :] = image_resized 94 | image_paded = image_paded / 255. 95 | 96 | if gt_boxes is None: 97 | return image_paded 98 | 99 | else: 100 | gt_boxes[:, [0, 2]] = gt_boxes[:, [0, 2]] * scale + dw 101 | gt_boxes[:, [1, 3]] = gt_boxes[:, [1, 3]] * scale + dh 102 | return image_paded, gt_boxes 103 | 104 | 105 | def draw_bbox(image, bboxes, classes=read_class_names(cfg.YOLO.CLASSES), show_label=True): 106 | """ 107 | bboxes: [x_min, y_min, x_max, y_max, probability, cls_id] format coordinates. 108 | """ 109 | 110 | num_classes = len(classes) 111 | image_h, image_w, _ = image.shape 112 | hsv_tuples = [(1.0 * x / num_classes, 1., 1.) for x in range(num_classes)] 113 | colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) 114 | colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), colors)) 115 | 116 | random.seed(0) 117 | random.shuffle(colors) 118 | random.seed(None) 119 | 120 | for i, bbox in enumerate(bboxes): 121 | coor = np.array(bbox[:4], dtype=np.int32) 122 | fontScale = 0.5 123 | score = bbox[4] 124 | class_ind = int(bbox[5]) 125 | bbox_color = colors[class_ind] 126 | bbox_thick = int(0.6 * (image_h + image_w) / 600) 127 | c1, c2 = (coor[0], coor[1]), (coor[2], coor[3]) 128 | cv2.rectangle(image, c1, c2, bbox_color, bbox_thick) 129 | 130 | if show_label: 131 | bbox_mess = '%s: %.2f' % (classes[class_ind], score) 132 | t_size = cv2.getTextSize(bbox_mess, 0, fontScale, thickness=bbox_thick//2)[0] 133 | cv2.rectangle(image, c1, (c1[0] + t_size[0], c1[1] - t_size[1] - 3), bbox_color, -1) # filled 134 | 135 | cv2.putText(image, bbox_mess, (c1[0], c1[1]-2), cv2.FONT_HERSHEY_SIMPLEX, 136 | fontScale, (0, 0, 0), bbox_thick//2, lineType=cv2.LINE_AA) 137 | 138 | return image 139 | 140 | 141 | 142 | def bboxes_iou(boxes1, boxes2): 143 | 144 | boxes1 = np.array(boxes1) 145 | boxes2 = np.array(boxes2) 146 | 147 | boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1]) 148 | boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1]) 149 | 150 | left_up = np.maximum(boxes1[..., :2], boxes2[..., :2]) 151 | right_down = np.minimum(boxes1[..., 2:], boxes2[..., 2:]) 152 | 153 | inter_section = np.maximum(right_down - left_up, 0.0) 154 | inter_area = inter_section[..., 0] * inter_section[..., 1] 155 | union_area = boxes1_area + boxes2_area - inter_area 156 | ious = np.maximum(1.0 * inter_area / union_area, np.finfo(np.float32).eps) 157 | 158 | return ious 159 | 160 | 161 | def nms(bboxes, iou_threshold, sigma=0.3, method='nms'): 162 | """ 163 | :param bboxes: (xmin, ymin, xmax, ymax, score, class) 164 | 165 | Note: soft-nms, https://arxiv.org/pdf/1704.04503.pdf 166 | https://github.com/bharatsingh430/soft-nms 167 | """ 168 | classes_in_img = list(set(bboxes[:, 5])) 169 | best_bboxes = [] 170 | 171 | for cls in classes_in_img: 172 | cls_mask = (bboxes[:, 5] == cls) 173 | cls_bboxes = bboxes[cls_mask] 174 | 175 | while len(cls_bboxes) > 0: 176 | max_ind = np.argmax(cls_bboxes[:, 4]) 177 | best_bbox = cls_bboxes[max_ind] 178 | best_bboxes.append(best_bbox) 179 | cls_bboxes = np.concatenate([cls_bboxes[: max_ind], cls_bboxes[max_ind + 1:]]) 180 | iou = bboxes_iou(best_bbox[np.newaxis, :4], cls_bboxes[:, :4]) 181 | weight = np.ones((len(iou),), dtype=np.float32) 182 | 183 | assert method in ['nms', 'soft-nms'] 184 | 185 | if method == 'nms': 186 | iou_mask = iou > iou_threshold 187 | weight[iou_mask] = 0.0 188 | 189 | if method == 'soft-nms': 190 | weight = np.exp(-(1.0 * iou ** 2 / sigma)) 191 | 192 | cls_bboxes[:, 4] = cls_bboxes[:, 4] * weight 193 | score_mask = cls_bboxes[:, 4] > 0. 194 | cls_bboxes = cls_bboxes[score_mask] 195 | 196 | return best_bboxes 197 | 198 | 199 | def postprocess_boxes(pred_bbox, org_img_shape, input_size, score_threshold): 200 | 201 | valid_scale=[0, np.inf] 202 | pred_bbox = np.array(pred_bbox) 203 | 204 | pred_xywh = pred_bbox[:, 0:4] 205 | pred_conf = pred_bbox[:, 4] 206 | pred_prob = pred_bbox[:, 5:] 207 | 208 | # # (1) (x, y, w, h) --> (xmin, ymin, xmax, ymax) 209 | pred_coor = np.concatenate([pred_xywh[:, :2] - pred_xywh[:, 2:] * 0.5, 210 | pred_xywh[:, :2] + pred_xywh[:, 2:] * 0.5], axis=-1) 211 | # # (2) (xmin, ymin, xmax, ymax) -> (xmin_org, ymin_org, xmax_org, ymax_org) 212 | org_h, org_w = org_img_shape 213 | resize_ratio = min(input_size / org_w, input_size / org_h) 214 | 215 | dw = (input_size - resize_ratio * org_w) / 2 216 | dh = (input_size - resize_ratio * org_h) / 2 217 | 218 | pred_coor[:, 0::2] = 1.0 * (pred_coor[:, 0::2] - dw) / resize_ratio 219 | pred_coor[:, 1::2] = 1.0 * (pred_coor[:, 1::2] - dh) / resize_ratio 220 | 221 | # # (3) clip some boxes those are out of range 222 | pred_coor = np.concatenate([np.maximum(pred_coor[:, :2], [0, 0]), 223 | np.minimum(pred_coor[:, 2:], [org_w - 1, org_h - 1])], axis=-1) 224 | invalid_mask = np.logical_or((pred_coor[:, 0] > pred_coor[:, 2]), (pred_coor[:, 1] > pred_coor[:, 3])) 225 | pred_coor[invalid_mask] = 0 226 | 227 | # # (4) discard some invalid boxes 228 | bboxes_scale = np.sqrt(np.multiply.reduce(pred_coor[:, 2:4] - pred_coor[:, 0:2], axis=-1)) 229 | scale_mask = np.logical_and((valid_scale[0] < bboxes_scale), (bboxes_scale < valid_scale[1])) 230 | 231 | # # (5) discard some boxes with low scores 232 | classes = np.argmax(pred_prob, axis=-1) 233 | scores = pred_conf * pred_prob[np.arange(len(pred_coor)), classes] 234 | score_mask = scores > score_threshold 235 | mask = np.logical_and(scale_mask, score_mask) 236 | coors, scores, classes = pred_coor[mask], scores[mask], classes[mask] 237 | 238 | return np.concatenate([coors, scores[:, np.newaxis], classes[:, np.newaxis]], axis=-1) 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /code/YOLOV3/core/yolov3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : yolov3.py 8 | # Author : YunYang1994 9 | # Created date: 2019-07-12 13:47:10 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | import numpy as np 15 | import tensorflow as tf 16 | import core.utils as utils 17 | import core.common as common 18 | import core.backbone as backbone 19 | from core.config import cfg 20 | 21 | 22 | NUM_CLASS = len(utils.read_class_names(cfg.YOLO.CLASSES)) 23 | ANCHORS = utils.get_anchors(cfg.YOLO.ANCHORS) 24 | STRIDES = np.array(cfg.YOLO.STRIDES) 25 | IOU_LOSS_THRESH = cfg.YOLO.IOU_LOSS_THRESH 26 | 27 | def YOLOv3(input_layer): 28 | route_1, route_2, conv = backbone.darknet53(input_layer) 29 | 30 | conv = common.convolutional(conv, (1, 1, 1024, 512)) 31 | conv = common.convolutional(conv, (3, 3, 512, 1024)) 32 | conv = common.convolutional(conv, (1, 1, 1024, 512)) 33 | conv = common.convolutional(conv, (3, 3, 512, 1024)) 34 | conv = common.convolutional(conv, (1, 1, 1024, 512)) 35 | 36 | conv_lobj_branch = common.convolutional(conv, (3, 3, 512, 1024)) 37 | conv_lbbox = common.convolutional(conv_lobj_branch, (1, 1, 1024, 3*(NUM_CLASS + 5)), activate=False, bn=False) 38 | 39 | conv = common.convolutional(conv, (1, 1, 512, 256)) 40 | conv = common.upsample(conv) 41 | 42 | conv = tf.concat([conv, route_2], axis=-1) 43 | 44 | conv = common.convolutional(conv, (1, 1, 768, 256)) 45 | conv = common.convolutional(conv, (3, 3, 256, 512)) 46 | conv = common.convolutional(conv, (1, 1, 512, 256)) 47 | conv = common.convolutional(conv, (3, 3, 256, 512)) 48 | conv = common.convolutional(conv, (1, 1, 512, 256)) 49 | 50 | conv_mobj_branch = common.convolutional(conv, (3, 3, 256, 512)) 51 | conv_mbbox = common.convolutional(conv_mobj_branch, (1, 1, 512, 3*(NUM_CLASS + 5)), activate=False, bn=False) 52 | 53 | conv = common.convolutional(conv, (1, 1, 256, 128)) 54 | conv = common.upsample(conv) 55 | 56 | conv = tf.concat([conv, route_1], axis=-1) 57 | 58 | conv = common.convolutional(conv, (1, 1, 384, 128)) 59 | conv = common.convolutional(conv, (3, 3, 128, 256)) 60 | conv = common.convolutional(conv, (1, 1, 256, 128)) 61 | conv = common.convolutional(conv, (3, 3, 128, 256)) 62 | conv = common.convolutional(conv, (1, 1, 256, 128)) 63 | 64 | conv_sobj_branch = common.convolutional(conv, (3, 3, 128, 256)) 65 | conv_sbbox = common.convolutional(conv_sobj_branch, (1, 1, 256, 3*(NUM_CLASS +5)), activate=False, bn=False) 66 | 67 | return [conv_sbbox, conv_mbbox, conv_lbbox] 68 | 69 | def decode(conv_output, i=0): 70 | """ 71 | return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes] 72 | contains (x, y, w, h, score, probability) 73 | """ 74 | 75 | conv_shape = tf.shape(conv_output) 76 | batch_size = conv_shape[0] 77 | output_size = conv_shape[1] 78 | 79 | conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, 3, 5 + NUM_CLASS)) 80 | 81 | conv_raw_dxdy = conv_output[:, :, :, :, 0:2] 82 | conv_raw_dwdh = conv_output[:, :, :, :, 2:4] 83 | conv_raw_conf = conv_output[:, :, :, :, 4:5] 84 | conv_raw_prob = conv_output[:, :, :, :, 5: ] 85 | 86 | y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size]) 87 | x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1]) 88 | 89 | xy_grid = tf.concat([x[:, :, tf.newaxis], y[:, :, tf.newaxis]], axis=-1) 90 | xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, 3, 1]) 91 | xy_grid = tf.cast(xy_grid, tf.float32) 92 | 93 | pred_xy = (tf.sigmoid(conv_raw_dxdy) + xy_grid) * STRIDES[i] 94 | pred_wh = (tf.exp(conv_raw_dwdh) * ANCHORS[i]) * STRIDES[i] 95 | pred_xywh = tf.concat([pred_xy, pred_wh], axis=-1) 96 | 97 | pred_conf = tf.sigmoid(conv_raw_conf) 98 | pred_prob = tf.sigmoid(conv_raw_prob) 99 | 100 | return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1) 101 | 102 | def bbox_iou(boxes1, boxes2): 103 | 104 | boxes1_area = boxes1[..., 2] * boxes1[..., 3] 105 | boxes2_area = boxes2[..., 2] * boxes2[..., 3] 106 | 107 | boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5, 108 | boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1) 109 | boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5, 110 | boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1) 111 | 112 | left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2]) 113 | right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:]) 114 | 115 | inter_section = tf.maximum(right_down - left_up, 0.0) 116 | inter_area = inter_section[..., 0] * inter_section[..., 1] 117 | union_area = boxes1_area + boxes2_area - inter_area 118 | 119 | return 1.0 * inter_area / union_area 120 | 121 | def bbox_giou(boxes1, boxes2): 122 | 123 | boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5, 124 | boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1) 125 | boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5, 126 | boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1) 127 | 128 | boxes1 = tf.concat([tf.minimum(boxes1[..., :2], boxes1[..., 2:]), 129 | tf.maximum(boxes1[..., :2], boxes1[..., 2:])], axis=-1) 130 | boxes2 = tf.concat([tf.minimum(boxes2[..., :2], boxes2[..., 2:]), 131 | tf.maximum(boxes2[..., :2], boxes2[..., 2:])], axis=-1) 132 | 133 | boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1]) 134 | boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1]) 135 | 136 | left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2]) 137 | right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:]) 138 | 139 | inter_section = tf.maximum(right_down - left_up, 0.0) 140 | inter_area = inter_section[..., 0] * inter_section[..., 1] 141 | union_area = boxes1_area + boxes2_area - inter_area 142 | iou = inter_area / union_area 143 | 144 | enclose_left_up = tf.minimum(boxes1[..., :2], boxes2[..., :2]) 145 | enclose_right_down = tf.maximum(boxes1[..., 2:], boxes2[..., 2:]) 146 | enclose = tf.maximum(enclose_right_down - enclose_left_up, 0.0) 147 | enclose_area = enclose[..., 0] * enclose[..., 1] 148 | giou = iou - 1.0 * (enclose_area - union_area) / enclose_area 149 | 150 | return giou 151 | 152 | 153 | def compute_loss(pred, conv, label, bboxes, i=0): 154 | 155 | conv_shape = tf.shape(conv) 156 | batch_size = conv_shape[0] 157 | output_size = conv_shape[1] 158 | input_size = STRIDES[i] * output_size 159 | conv = tf.reshape(conv, (batch_size, output_size, output_size, 3, 5 + NUM_CLASS)) 160 | 161 | conv_raw_conf = conv[:, :, :, :, 4:5] 162 | conv_raw_prob = conv[:, :, :, :, 5:] 163 | 164 | pred_xywh = pred[:, :, :, :, 0:4] 165 | pred_conf = pred[:, :, :, :, 4:5] 166 | 167 | label_xywh = label[:, :, :, :, 0:4] 168 | respond_bbox = label[:, :, :, :, 4:5] 169 | label_prob = label[:, :, :, :, 5:] 170 | 171 | giou = tf.expand_dims(bbox_giou(pred_xywh, label_xywh), axis=-1) 172 | input_size = tf.cast(input_size, tf.float32) 173 | 174 | bbox_loss_scale = 2.0 - 1.0 * label_xywh[:, :, :, :, 2:3] * label_xywh[:, :, :, :, 3:4] / (input_size ** 2) 175 | giou_loss = respond_bbox * bbox_loss_scale * (1- giou) 176 | 177 | iou = bbox_iou(pred_xywh[:, :, :, :, np.newaxis, :], bboxes[:, np.newaxis, np.newaxis, np.newaxis, :, :]) 178 | max_iou = tf.expand_dims(tf.reduce_max(iou, axis=-1), axis=-1) 179 | 180 | respond_bgd = (1.0 - respond_bbox) * tf.cast( max_iou < IOU_LOSS_THRESH, tf.float32 ) 181 | 182 | conf_focal = tf.pow(respond_bbox - pred_conf, 2) 183 | 184 | conf_loss = conf_focal * ( 185 | respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf) 186 | + 187 | respond_bgd * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf) 188 | ) 189 | 190 | prob_loss = respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=label_prob, logits=conv_raw_prob) 191 | 192 | giou_loss = tf.reduce_mean(tf.reduce_sum(giou_loss, axis=[1,2,3,4])) 193 | conf_loss = tf.reduce_mean(tf.reduce_sum(conf_loss, axis=[1,2,3,4])) 194 | prob_loss = tf.reduce_mean(tf.reduce_sum(prob_loss, axis=[1,2,3,4])) 195 | 196 | return giou_loss, conf_loss, prob_loss 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /code/YOLOV3/data/anchors/basline_anchors.txt: -------------------------------------------------------------------------------- 1 | 1.25,1.625, 2.0,3.75, 4.125,2.875, 1.875,3.8125, 3.875,2.8125, 3.6875,7.4375, 3.625,2.8125, 4.875,6.1875, 11.65625,10.1875 2 | -------------------------------------------------------------------------------- /code/YOLOV3/data/classes/coco.names: -------------------------------------------------------------------------------- 1 | person 2 | bicycle 3 | car 4 | motorbike 5 | aeroplane 6 | bus 7 | train 8 | truck 9 | boat 10 | traffic light 11 | fire hydrant 12 | stop sign 13 | parking meter 14 | bench 15 | bird 16 | cat 17 | dog 18 | horse 19 | sheep 20 | cow 21 | elephant 22 | bear 23 | zebra 24 | giraffe 25 | backpack 26 | umbrella 27 | handbag 28 | tie 29 | suitcase 30 | frisbee 31 | skis 32 | snowboard 33 | sports ball 34 | kite 35 | baseball bat 36 | baseball glove 37 | skateboard 38 | surfboard 39 | tennis racket 40 | bottle 41 | wine glass 42 | cup 43 | fork 44 | knife 45 | spoon 46 | bowl 47 | banana 48 | apple 49 | sandwich 50 | orange 51 | broccoli 52 | carrot 53 | hot dog 54 | pizza 55 | donut 56 | cake 57 | chair 58 | sofa 59 | pottedplant 60 | bed 61 | diningtable 62 | toilet 63 | tvmonitor 64 | laptop 65 | mouse 66 | remote 67 | keyboard 68 | cell phone 69 | microwave 70 | oven 71 | toaster 72 | sink 73 | refrigerator 74 | book 75 | clock 76 | vase 77 | scissors 78 | teddy bear 79 | hair drier 80 | toothbrush 81 | -------------------------------------------------------------------------------- /code/YOLOV3/data/classes/darkdata.names: -------------------------------------------------------------------------------- 1 | bicycle 2 | boat 3 | bottle 4 | bus 5 | car 6 | cat 7 | chair 8 | cup 9 | dog 10 | motorbike 11 | person 12 | diningtable -------------------------------------------------------------------------------- /code/YOLOV3/data/dataset/annotations_preprocessing_methods.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import shutil, os\n", 11 | "import numpy as np\n", 12 | "import cv2\n", 13 | "import matplotlib.pyplot\n", 14 | "\n" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "coco_dict = {}\n", 24 | "with open(\"coco.names\", \"r\") as f:\n", 25 | " ls = f.readlines()\n", 26 | " i=0\n", 27 | " for cls in ls:\n", 28 | " coco_dict[cls.strip(\"\\n\")] = i\n", 29 | " i=i+1\n", 30 | "\n", 31 | " \n", 32 | " f.close()\n" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "imagespath = \"./ExDark/\"\n", 42 | "annotationspath = \"./ExDark_Annno/\"" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "classes = os.listdir(imagespath)" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "images_dir = []\n", 61 | "annot_dir= []\n", 62 | "for cls in classes:\n", 63 | " images_dir.append(imagespath+cls)\n", 64 | " annot_dir.append(annotationspath+cls)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "'''code to copy all original images test_benchmark folder'''\n", 74 | "for image in images_dir:\n", 75 | " #print(annot+\"/\"+file)\n", 76 | " for file in os.listdir(image):\n", 77 | " shutil.copy(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))], 'test_benchmark')\n", 78 | " #print(file)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": { 85 | "scrolled": true 86 | }, 87 | "outputs": [], 88 | "source": [ 89 | "darkimages_filepaths = []\n", 90 | "with open(\"test_benchmark.txt\",'w') as dataset:\n", 91 | " #print(image)\n", 92 | " for image in images_dir:\n", 93 | " #print(annot+\"/\"+file)\n", 94 | " for file in os.listdir(image):\n", 95 | " #f = open(annot+\"/\"+file,'r')\n", 96 | " darkimages_filepaths.append(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))])\n", 97 | " print(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))])\n", 98 | " #print(f.readlines()[1:])\n", 99 | " dataset.write(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))]+\"\\n\")\n", 100 | "dataset.close()" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "darkdata_groundtruth = []\n", 110 | "count=0\n", 111 | "for annot in annot_dir:\n", 112 | " #print(annot)\n", 113 | " \n", 114 | " for file in os.listdir(annot):\n", 115 | " f = open(annot+\"/\"+file,'r')\n", 116 | " #print(os.path.relpath(annot+\"/\"+file)[:len(os.path.relpath(annot+\"/\"+file))])\n", 117 | " darkdata_groundtruth.append(f.readlines()[1:])\n", 118 | " #print(f.readlines()[1:])\n", 119 | " count =count+1\n", 120 | " f.close()\n", 121 | " #dataset.write\n", 122 | "print(count) " 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "len(darkdata_groundtruth)==len(darkimages_filepaths)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": null, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "def image_size(filepath):\n", 141 | " height, width, channels = matplotlib.pyplot.imread(filepath).shape\n", 142 | " return height, width, channels" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "\n", 152 | "def draw_bbox(imagepath,top_left_xy,bottomright_xy,line_size=4):\n", 153 | " x_min = top_left_xy[0]\n", 154 | " y_min = top_left_xy[1]\n", 155 | " \n", 156 | " x_max = bottomright_xy[0]\n", 157 | " y_max = bottomright_xy[1]\n", 158 | " \n", 159 | " img = cv2.imread(imagepath,cv2.IMREAD_COLOR)\n", 160 | " cv2.rectangle(img,(x_min,y_min),(x_max,y_max),(0,0,255),line_size)\n", 161 | " cv2.imshow('image',img)\n", 162 | " cv2.waitKey(0)\n", 163 | " cv2.destroyAllWindows()\n", 164 | " " 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": null, 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "# get x_min,y_min and x_max,y_max\n", 174 | "def convert_coordinates(top_left_xy,width,height):\n", 175 | " x_min = top_left_xy[0]\n", 176 | " y_min = top_left_xy[1]\n", 177 | " \n", 178 | " x_max = x_min + width\n", 179 | " y_max = y_min + height\n", 180 | " \n", 181 | " return x_min, y_min, x_max, y_max\n" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "#Bicycle(1), Boat(2), Bottle(3), Bus(4), Car(5), Cat(6), Chair(7), Cup(8), Dog(9), Motorbike(10), People(11), Table(12)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "#maping dark data classes using coco ids\n", 200 | "def get_classid(class_name):\n", 201 | " if class_name==\"Bicycle\":\n", 202 | " class_id = 1\n", 203 | " if class_name == \"Boat\":\n", 204 | " class_id = 8\n", 205 | " if class_name==\"Bottle\":\n", 206 | " class_id = 39\n", 207 | " if class_name == \"Bus\":\n", 208 | " class_id = 5\n", 209 | " if class_name==\"Car\":\n", 210 | " class_id = 2\n", 211 | " if class_name == \"Cat\":\n", 212 | " class_id = 15\n", 213 | " if class_name==\"Chair\":\n", 214 | " class_id = 56\n", 215 | " if class_name == \"Cup\":\n", 216 | " class_id = 41\n", 217 | " if class_name==\"Dog\":\n", 218 | " class_id = 16\n", 219 | " if class_name == \"Motorbike\":\n", 220 | " class_id = 3\n", 221 | " if class_name==\"People\":\n", 222 | " class_id = 0\n", 223 | " if class_name == \"Table\":\n", 224 | " class_id = 60\n", 225 | " return class_id" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "#maping dark data classes using coco ids\n", 233 | "def get_classid(class_name):\n", 234 | " if class_name==\"Bicycle\":\n", 235 | " class_id = 0\n", 236 | " if class_name == \"Boat\":\n", 237 | " class_id = 1\n", 238 | " if class_name==\"Bottle\":\n", 239 | " class_id = 2\n", 240 | " if class_name == \"Bus\":\n", 241 | " class_id = 3\n", 242 | " if class_name==\"Car\":\n", 243 | " class_id = 4\n", 244 | " if class_name == \"Cat\":\n", 245 | " class_id = 5\n", 246 | " if class_name==\"Chair\":\n", 247 | " class_id = 6\n", 248 | " if class_name == \"Cup\":\n", 249 | " class_id = 7\n", 250 | " if class_name==\"Dog\":\n", 251 | " class_id = 8\n", 252 | " if class_name == \"Motorbike\":\n", 253 | " class_id = 9\n", 254 | " if class_name==\"People\":\n", 255 | " class_id = 10\n", 256 | " if class_name == \"Table\":\n", 257 | " class_id = 11\n", 258 | " return class_id" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": null, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "def flatten_bbox_id(groundtruth):\n", 268 | " '''return x_min,y_min,x_max,y_max,class_id'''\n", 269 | " space = ' '\n", 270 | " x_min = int(groundtruth[1])\n", 271 | " y_min = int(groundtruth[2])\n", 272 | " width = int(groundtruth[3])\n", 273 | " height = int(groundtruth[4])\n", 274 | " x_min, y_min, x_max, y_max = convert_coordinates((x_min,y_min),width,height)\n", 275 | " class_id = get_classid(groundtruth[0])\n", 276 | " return str(x_min)+','+str(y_min)+','+str(x_max)+','+str(y_max)+','+str(class_id)+space\n", 277 | "flatten_bbox_id(darkdata_groundtruth[1][0].split(' '))" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "metadata": {}, 284 | "outputs": [], 285 | "source": [ 286 | "ls = []\n", 287 | "for file in darkdata_groundtruth:\n", 288 | " #print(file)\n", 289 | " sub_ls = []\n", 290 | " for each in file:\n", 291 | " print(flatten_bbox_id(each.split(' ')))\n", 292 | " sub_ls.append(flatten_bbox_id(each.split(' ')))\n", 293 | " ls.append(sub_ls)\n", 294 | " \n", 295 | " " 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": null, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "gt_str_form = []\n", 305 | "for each in ls:\n", 306 | " gt_str_form.append(''.join(each))\n", 307 | "len(gt_str_form)" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": null, 313 | "metadata": {}, 314 | "outputs": [], 315 | "source": [ 316 | "gt_str_form" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": null, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "darkimages_trainpaths = []\n", 326 | "for file in os.listdir(r'C:\\Users\\navee\\Desktop\\MS_Data_Science\\Dark_Object_Detection_Research\\TensorFlow2.0-Examples\\4-Object_Detection\\YOLOV3\\data\\dataset\\test_benchmark'):\n", 327 | " print(r'C:\\Users\\navee\\Desktop\\MS_Data_Science\\Dark_Object_Detection_Research\\TensorFlow2.0-Examples\\4-Object_Detection\\YOLOV3\\data\\dataset\\test_benchmark'+'\\\\'+os.path.relpath(file))\n", 328 | " darkimages_trainpaths.append(r'C:\\Users\\navee\\Desktop\\MS_Data_Science\\Dark_Object_Detection_Research\\TensorFlow2.0-Examples\\4-Object_Detection\\YOLOV3\\data\\dataset\\test_benchmark'+'\\\\'+os.path.relpath(file))\n", 329 | " print(file)\n", 330 | "#len(darkimages_trainpaths)" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "with open(\"test_benchmark.txt\",'w') as dataset:\n", 340 | " for i in range(len(darkimages_filepaths)):\n", 341 | " print(darkimages_trainpaths[i]+' '+gt_str_form[i])\n", 342 | " dataset.write(darkimages_trainpaths[i]+' '+gt_str_form[i]+\"\\n\")\n", 343 | "dataset.close()" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": null, 349 | "metadata": {}, 350 | "outputs": [], 351 | "source": [ 352 | "check = []\n", 353 | "with open(\"test_benchmark.txt\",'r') as dataset:\n", 354 | " for each in dataset.readlines():\n", 355 | " check.append(each)\n", 356 | "dataset.close()" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": null, 362 | "metadata": {}, 363 | "outputs": [], 364 | "source": [ 365 | "with open(\"test_benchmark.txt\",'w') as dataset:\n", 366 | " for i in check:\n", 367 | " print(i.replace(\" \\n\",\"\\n\"))\n", 368 | " dataset.write(i.replace(\" \\n\",\"\\n\"))\n", 369 | "dataset.close()" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": null, 375 | "metadata": {}, 376 | "outputs": [], 377 | "source": [ 378 | "f = open('test_benchmark.txt','r')\n", 379 | "yy = f.readlines()\n", 380 | "yy" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "metadata": {}, 387 | "outputs": [], 388 | "source": [ 389 | "with open(\"test_he.txt\",'w') as dataset:\n", 390 | " for each in yy:\n", 391 | " print(each.replace('test_benchmark','test_he'))\n", 392 | " dataset.write(each.replace('test_benchmark','test_he'))\n", 393 | "dataset.close()\n", 394 | "with open(\"test_dhe.txt\",'w') as dataset:\n", 395 | " for each in yy:\n", 396 | " print(each.replace('test_benchmark','test_dhe'))\n", 397 | " dataset.write(each.replace('test_benchmark','test_dhe'))\n", 398 | "dataset.close()\n", 399 | "with open(\"test_ying.txt\",'w') as dataset:\n", 400 | " for each in yy:\n", 401 | " print(each.replace('test_benchmark','test_ying'))\n", 402 | " dataset.write(each.replace('test_benchmark','test_ying'))\n", 403 | "dataset.close()" 404 | ] 405 | }, 406 | { 407 | "cell_type": "code", 408 | "execution_count": null, 409 | "metadata": {}, 410 | "outputs": [], 411 | "source": [] 412 | } 413 | ], 414 | "metadata": { 415 | "kernelspec": { 416 | "display_name": "Python 3", 417 | "language": "python", 418 | "name": "python3" 419 | }, 420 | "language_info": { 421 | "codemirror_mode": { 422 | "name": "ipython", 423 | "version": 3 424 | }, 425 | "file_extension": ".py", 426 | "mimetype": "text/x-python", 427 | "name": "python", 428 | "nbconvert_exporter": "python", 429 | "pygments_lexer": "ipython3", 430 | "version": "3.6.7" 431 | } 432 | }, 433 | "nbformat": 4, 434 | "nbformat_minor": 2 435 | } 436 | -------------------------------------------------------------------------------- /code/YOLOV3/data/dataset/train_test_annotations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import numpy as np\n", 11 | "import cv2\n", 12 | "import random" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "coco_dict = {}\n", 22 | "with open(\"coco.names\", \"r\") as f:\n", 23 | " ls = f.readlines()\n", 24 | " i=0\n", 25 | " for cls in ls:\n", 26 | " coco_dict[cls.strip(\"\\n\")] = i\n", 27 | " i=i+1 \n", 28 | " f.close()" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "imagespath = \"./ExDark/\"\n", 38 | "annotationspath = \"./ExDark_Annno/\"" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "classes = os.listdir(imagespath)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "images_dir = []\n", 57 | "annot_dir= []\n", 58 | "for cls in classes:\n", 59 | " images_dir.append(imagespath+cls)\n", 60 | " annot_dir.append(annotationspath+cls)" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "''' Code to copy images from original Exdark tree to one single file\n", 70 | "#copy files into exdark_data\n", 71 | "import shutil, os\n", 72 | "for image in images_dir:\n", 73 | " for file in os.listdir(image):\n", 74 | " shutil.copy(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))], 'exdark_data')\n", 75 | "'''" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "scrolled": true 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "darkimages_filepaths = []\n", 87 | "for image in images_dir:\n", 88 | " for file in os.listdir(image):\n", 89 | " darkimages_filepaths.append(os.path.relpath(image+\"/\"+file)[:len(os.path.relpath(image+\"/\"+file))])" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "darkdata_groundtruth = []\n", 99 | "for annot in annot_dir:\n", 100 | " for file in os.listdir(annot):\n", 101 | " f = open(annot+\"/\"+file,'r')\n", 102 | " #print(os.path.relpath(annot+\"/\"+file)[:len(os.path.relpath(annot+\"/\"+file))])\n", 103 | " darkdata_groundtruth.append(f.readlines()[1:])\n", 104 | " f.close() " 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "len(darkdata_groundtruth)==len(darkimages_filepaths)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [ 122 | "import matplotlib.pyplot\n", 123 | "def image_size(filepath):\n", 124 | " height, width, channels = matplotlib.pyplot.imread(filepath).shape\n", 125 | " return height, width, channels" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": null, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "def draw_bbox(imagepath,top_left_xy,bottomright_xy,line_size=4):\n", 135 | " x_min = top_left_xy[0]\n", 136 | " y_min = top_left_xy[1]\n", 137 | " \n", 138 | " x_max = bottomright_xy[0]\n", 139 | " y_max = bottomright_xy[1]\n", 140 | " \n", 141 | " img = cv2.imread(imagepath,cv2.IMREAD_COLOR)\n", 142 | " cv2.rectangle(img,(x_min,y_min),(x_max,y_max),(0,0,255),line_size)\n", 143 | " cv2.imshow('image',img)\n", 144 | " cv2.waitKey(0)\n", 145 | " cv2.destroyAllWindows()\n", 146 | " " 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "# get x_min,y_min and x_max,y_max\n", 156 | "def convert_coordinates(top_left_xy,width,height):\n", 157 | " x_min = top_left_xy[0]\n", 158 | " y_min = top_left_xy[1]\n", 159 | " \n", 160 | " x_max = x_min + width\n", 161 | " y_max = y_min + height\n", 162 | " \n", 163 | " return x_min, y_min, x_max, y_max\n", 164 | "x_min, y_min, x_max, y_max = convert_coordinates((204,28),271,193)\n", 165 | "#darkfile = r\"ExDark\\Bicycle\\2015_00002.png\"\n", 166 | "#draw_bbox(darkfile,(136,190),(215,299))" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "#Bicycle(1), Boat(2), Bottle(3), Bus(4), Car(5), Cat(6), Chair(7), Cup(8), Dog(9), Motorbike(10), People(11), Table(12)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [ 184 | "#maping dark data classes using coco ids used for generating dataset when we use pre-trained YOLOv3 model trained on coco dataset\n", 185 | "def get_classid(class_name):\n", 186 | " if class_name==\"Bicycle\":\n", 187 | " class_id = 1\n", 188 | " if class_name == \"Boat\":\n", 189 | " class_id = 8\n", 190 | " if class_name==\"Bottle\":\n", 191 | " class_id = 39\n", 192 | " if class_name == \"Bus\":\n", 193 | " class_id = 5\n", 194 | " if class_name==\"Car\":\n", 195 | " class_id = 2\n", 196 | " if class_name == \"Cat\":\n", 197 | " class_id = 15\n", 198 | " if class_name==\"Chair\":\n", 199 | " class_id = 56\n", 200 | " if class_name == \"Cup\":\n", 201 | " class_id = 41\n", 202 | " if class_name==\"Dog\":\n", 203 | " class_id = 16\n", 204 | " if class_name == \"Motorbike\":\n", 205 | " class_id = 3\n", 206 | " if class_name==\"People\":\n", 207 | " class_id = 0\n", 208 | " if class_name == \"Table\":\n", 209 | " class_id = 60\n", 210 | " return class_id" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "# assigning class-ids, annotating for custom ExDark training\n", 218 | "def get_classid(class_name):\n", 219 | " if class_name==\"Bicycle\":\n", 220 | " class_id = 0\n", 221 | " if class_name == \"Boat\":\n", 222 | " class_id = 1\n", 223 | " if class_name==\"Bottle\":\n", 224 | " class_id = 2\n", 225 | " if class_name == \"Bus\":\n", 226 | " class_id = 3\n", 227 | " if class_name==\"Car\":\n", 228 | " class_id = 4\n", 229 | " if class_name == \"Cat\":\n", 230 | " class_id = 5\n", 231 | " if class_name==\"Chair\":\n", 232 | " class_id = 6\n", 233 | " if class_name == \"Cup\":\n", 234 | " class_id = 7\n", 235 | " if class_name==\"Dog\":\n", 236 | " class_id = 8\n", 237 | " if class_name == \"Motorbike\":\n", 238 | " class_id = 9\n", 239 | " if class_name==\"People\":\n", 240 | " class_id = 10\n", 241 | " if class_name == \"Table\":\n", 242 | " class_id = 11\n", 243 | " return class_id" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "def flatten_bbox_id(groundtruth):\n", 253 | " '''return x_min,y_min,x_max,y_max,class_id'''\n", 254 | " space = ' '\n", 255 | " x_min = int(groundtruth[1])\n", 256 | " y_min = int(groundtruth[2])\n", 257 | " width = int(groundtruth[3])\n", 258 | " height = int(groundtruth[4])\n", 259 | " x_min, y_min, x_max, y_max = convert_coordinates((x_min,y_min),width,height)\n", 260 | " class_id = get_classid(groundtruth[0])\n", 261 | " return str(x_min)+','+str(y_min)+','+str(x_max)+','+str(y_max)+','+str(class_id)+space\n", 262 | "flatten_bbox_id(darkdata_groundtruth[1][0].split(' '))" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": null, 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "ls = []\n", 272 | "for file in darkdata_groundtruth:\n", 273 | " #print(file)\n", 274 | " sub_ls = []\n", 275 | " for each in file:\n", 276 | " print(flatten_bbox_id(each.split(' ')))\n", 277 | " sub_ls.append(flatten_bbox_id(each.split(' ')))\n", 278 | " ls.append(sub_ls)\n", 279 | " \n", 280 | " " 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "gt_str_form = []\n", 290 | "for each in ls:\n", 291 | " gt_str_form.append(''.join(each))\n", 292 | "len(gt_str_form)" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": null, 298 | "metadata": {}, 299 | "outputs": [], 300 | "source": [ 301 | "darkimages_trainpaths = []\n", 302 | "for file in os.listdir(r'C:\\Users\\navee\\Desktop\\MS_Data_Science\\Dark_Object_Detection_Research\\TensorFlow2.0-Examples\\4-Object_Detection\\YOLOV3\\data\\dataset\\exdark_data'):\n", 303 | " darkimages_trainpaths.append(r'C:\\Users\\navee\\Desktop\\MS_Data_Science\\Dark_Object_Detection_Research\\TensorFlow2.0-Examples\\4-Object_Detection\\YOLOV3\\data\\dataset\\exdark_data'+'\\\\'+os.path.relpath(file))\n", 304 | "len(darkimages_trainpaths)" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "metadata": {}, 311 | "outputs": [], 312 | "source": [ 313 | "ls = range(len(darkimages_trainpaths))\n", 314 | "random.seed(1)\n", 315 | "train_rows = random.sample(ls, int(len(darkimages_trainpaths)*0.8)) ## making 80-20 split randomly\n", 316 | "assert(len(set(train_rows))==len(train_rows))## check for duplicates if any" 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": null, 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "test_rows=[]\n", 326 | "for ind in range(len(darkimages_trainpaths)):\n", 327 | " if ind not in train_rows:\n", 328 | " test_rows.append(ind)\n", 329 | "assert(len(set(test_rows))==len(test_rows))## check for duplicates if any\n", 330 | "assert(len(train_rows)+len(test_rows)==len(darkimages_trainpaths)) # check for any loss of data while splitting it" 331 | ] 332 | }, 333 | { 334 | "cell_type": "markdown", 335 | "metadata": {}, 336 | "source": [ 337 | "### Below we have generated annotation .txt files for train-test split to be used for creating a model on ExDark dataset" 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": null, 343 | "metadata": {}, 344 | "outputs": [], 345 | "source": [ 346 | "## copy train rows to exdark_train.txt\n", 347 | "with open(\"exdark_train.txt\",'w') as dataset:\n", 348 | " for i in train_rows:\n", 349 | " dataset.write(darkimages_trainpaths[i]+' '+gt_str_form[i]+\"\\n\")\n", 350 | "dataset.close()\n", 351 | "\n", 352 | "## copy test rows to exdark_test.txt\n", 353 | "with open(\"exdark_test.txt\",'w') as dataset:\n", 354 | " for i in test_rows:\n", 355 | " dataset.write(darkimages_trainpaths[i]+' '+gt_str_form[i]+\"\\n\")\n", 356 | "dataset.close()\n", 357 | "\n", 358 | "check_train = []\n", 359 | "with open(\"exdark_train.txt\",'r') as dataset:\n", 360 | " for each in dataset.readlines():\n", 361 | " check_train.append(each)\n", 362 | "dataset.close()\n", 363 | "\n", 364 | "check_test = []\n", 365 | "with open(\"exdark_test.txt\",'r') as dataset:\n", 366 | " for each in dataset.readlines():\n", 367 | " check_test.append(each)\n", 368 | "dataset.close()\n", 369 | "\n", 370 | "assert()\n", 371 | "## remove space at the end\n", 372 | "with open(\"exdark_train.txt\",'w') as dataset:\n", 373 | " for i in check_train:\n", 374 | " print(i.replace(\" \\n\",\"\\n\"))\n", 375 | " dataset.write(i.replace(\" \\n\",\"\\n\"))\n", 376 | "dataset.close()\n", 377 | "\n", 378 | "with open(\"exdark_test.txt\",'w') as dataset:\n", 379 | " for i in check_test:\n", 380 | " print(i.replace(\" \\n\",\"\\n\"))\n", 381 | " dataset.write(i.replace(\" \\n\",\"\\n\"))\n", 382 | "dataset.close()" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": null, 388 | "metadata": {}, 389 | "outputs": [], 390 | "source": [] 391 | } 392 | ], 393 | "metadata": { 394 | "kernelspec": { 395 | "display_name": "Python 3", 396 | "language": "python", 397 | "name": "python3" 398 | }, 399 | "language_info": { 400 | "codemirror_mode": { 401 | "name": "ipython", 402 | "version": 3 403 | }, 404 | "file_extension": ".py", 405 | "mimetype": "text/x-python", 406 | "name": "python", 407 | "nbconvert_exporter": "python", 408 | "pygments_lexer": "ipython3", 409 | "version": "3.6.7" 410 | } 411 | }, 412 | "nbformat": 4, 413 | "nbformat_minor": 2 414 | } 415 | -------------------------------------------------------------------------------- /code/YOLOV3/image_demo.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # coding=utf-8 3 | #================================================================ 4 | # Copyright (C) 2019 * Ltd. All rights reserved. 5 | # 6 | # Editor : VIM 7 | # File name : image_demo.py 8 | # Author : YunYang1994 9 | # Created date: 2019-07-12 13:07:27 10 | # Description : 11 | # 12 | #================================================================ 13 | 14 | import cv2 15 | import numpy as np 16 | import core.utils as utils 17 | import tensorflow as tf 18 | from core.yolov3 import YOLOv3, decode 19 | from PIL import Image 20 | from core.config import cfg 21 | 22 | input_size = 416 23 | NUM_CLASS = len(utils.read_class_names(cfg.YOLO.CLASSES)) 24 | image_path = "./docs/kite.jpg" 25 | 26 | input_layer = tf.keras.layers.Input([input_size, input_size, 3]) 27 | feature_maps = YOLOv3(input_layer) 28 | 29 | original_image = cv2.imread(image_path) 30 | original_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB) 31 | original_image_size = original_image.shape[:2] 32 | 33 | image_data = utils.image_preporcess(np.copy(original_image), [input_size, input_size]) 34 | image_data = image_data[np.newaxis, ...].astype(np.float32) 35 | 36 | bbox_tensors = [] 37 | for i, fm in enumerate(feature_maps): 38 | bbox_tensor = decode(fm, i) 39 | bbox_tensors.append(tf.reshape(bbox_tensor, (-1, 5+NUM_CLASS))) 40 | 41 | bbox_tensors = tf.concat(bbox_tensors, axis=0) 42 | model = tf.keras.Model(input_layer, bbox_tensors) 43 | utils.load_weights(model, "./yolov3.weights") 44 | model.summary() 45 | 46 | pred_bbox = model.predict(image_data) 47 | bboxes = utils.postprocess_boxes(pred_bbox, original_image_size, input_size, 0.3) 48 | bboxes = utils.nms(bboxes, 0.45, method='nms') 49 | 50 | image = utils.draw_bbox(original_image, bboxes) 51 | image = Image.fromarray(image) 52 | image.show() 53 | 54 | 55 | -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_00001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_00001.png -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_00049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_00049.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_00058.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_00058.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_00108.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_00108.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_03065.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_03065.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_03165.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_03165.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_04977.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_04977.jpg -------------------------------------------------------------------------------- /code/YOLOV3/predictions/detection_2015_05218.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/YOLOV3/predictions/detection_2015_05218.jpg -------------------------------------------------------------------------------- /code/YOLOV3/requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.7.1 2 | astor==0.8.0 3 | attrs==19.1.0 4 | backcall==0.1.0 5 | bleach==3.1.0 6 | certifi==2019.6.16 7 | colorama==0.4.1 8 | cycler==0.10.0 9 | Cython==0.29.13 10 | dask==2.2.0 11 | decorator==4.4.0 12 | defusedxml==0.5.0 13 | easydict==1.9 14 | entrypoints==0.3 15 | gast==0.2.2 16 | google-pasta==0.1.7 17 | grpcio==1.22.0 18 | h5py==2.9.0 19 | ipykernel==5.1.1 20 | ipython==7.7.0 21 | ipython-genutils==0.2.0 22 | jedi==0.14.1 23 | Jinja2==2.10.1 24 | joblib==0.13.2 25 | jsonschema==3.0.1 26 | jupyter-client==5.3.1 27 | jupyter-core==4.4.0 28 | Keras==2.2.4 29 | Keras-Applications==1.0.8 30 | Keras-Preprocessing==1.1.0 31 | kiwisolver==1.1.0 32 | Markdown==3.1.1 33 | MarkupSafe==1.1.1 34 | matplotlib==3.1.1 35 | mistune==0.8.4 36 | nbconvert==5.5.0 37 | nbformat==4.4.0 38 | notebook==6.0.0 39 | numpy==1.17.0 40 | opencv-python==4.1.0.25 41 | pandas==0.25.0 42 | pandocfilters==1.4.2 43 | parso==0.5.1 44 | pickleshare==0.7.5 45 | Pillow==5.3.0 46 | prometheus-client==0.7.1 47 | prompt-toolkit==2.0.9 48 | protobuf==3.9.0 49 | Pygments==2.4.2 50 | pyparsing==2.4.2 51 | pyrsistent==0.15.4 52 | python-dateutil==2.8.0 53 | pytz==2019.2 54 | pywinpty==0.5.5 55 | PyYAML==5.1.2 56 | pyzmq==18.0.2 57 | scikit-allel==1.2.1 58 | scikit-learn==0.21.3 59 | scipy==1.1.0 60 | seaborn==0.9.0 61 | Send2Trash==1.5.0 62 | six==1.12.0 63 | sklearn==0.0 64 | tb-nightly==1.14.0a20190603 65 | tensorflow==2.0.0b1 66 | termcolor==1.1.0 67 | terminado==0.8.2 68 | testpath==0.4.2 69 | tf-estimator-nightly==1.14.0.dev2019060501 70 | toolz==0.10.0 71 | tornado==6.0.3 72 | tqdm==4.32.2 73 | traitlets==4.3.2 74 | wcwidth==0.1.7 75 | webencodings==0.5.1 76 | Werkzeug==0.15.5 77 | wget==3.2 78 | wincertstore==0.2 79 | wrapt==1.11.2 80 | -------------------------------------------------------------------------------- /code/YOLOV3/test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### This python file will generate ground truth files and predicted files for the required subset of classes in ../mAP folder which will be used for calculation of mAP. We have explained the code in our portfolio." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": { 14 | "scrolled": true 15 | }, 16 | "outputs": [], 17 | "source": [ 18 | "import cv2\n", 19 | "import os\n", 20 | "import shutil\n", 21 | "import numpy as np\n", 22 | "import tensorflow as tf\n", 23 | "import core.utils as utils\n", 24 | "from core.config import cfg\n", 25 | "from core.yolov3 import YOLOv3, decode\n", 26 | "\n", 27 | "\n", 28 | "INPUT_SIZE = 416\n", 29 | "NUM_CLASS = len(utils.read_class_names(cfg.YOLO.CLASSES))\n", 30 | "CLASSES = utils.read_class_names(cfg.YOLO.CLASSES)\n", 31 | "\n", 32 | "\n", 33 | "predicted_dir_path = '../mAP/predicted'\n", 34 | "ground_truth_dir_path = '../mAP/ground-truth'\n", 35 | "if os.path.exists(predicted_dir_path): shutil.rmtree(predicted_dir_path)\n", 36 | "if os.path.exists(ground_truth_dir_path): shutil.rmtree(ground_truth_dir_path)\n", 37 | "if os.path.exists(cfg.TEST.DECTECTED_IMAGE_PATH): shutil.rmtree(cfg.TEST.DECTECTED_IMAGE_PATH)\n", 38 | "\n", 39 | "os.mkdir(predicted_dir_path)\n", 40 | "os.mkdir(ground_truth_dir_path)\n", 41 | "os.mkdir(cfg.TEST.DECTECTED_IMAGE_PATH)\n", 42 | "\n", 43 | "# Build Model\n", 44 | "input_layer = tf.keras.layers.Input([INPUT_SIZE, INPUT_SIZE, 3])\n", 45 | "feature_maps = YOLOv3(input_layer)\n", 46 | "\n", 47 | "bbox_tensors = []\n", 48 | "for i, fm in enumerate(feature_maps):\n", 49 | " bbox_tensor = decode(fm, i)\n", 50 | " bbox_tensors.append(tf.reshape(bbox_tensor, (-1, 5+NUM_CLASS)))\n", 51 | "\n", 52 | "bbox_tensors = tf.concat(bbox_tensors, axis=0)\n", 53 | "model = tf.keras.Model(input_layer, bbox_tensors)\n", 54 | "model.summary()\n", 55 | "utils.load_weights(model, \"./yolov3.weights\")\n", 56 | "\n", 57 | "with open(cfg.TEST.ANNOT_PATH, 'r') as annotation_file:\n", 58 | " for num, line in enumerate(annotation_file):\n", 59 | " annotation = line.strip().split()\n", 60 | " image_path = annotation[0]\n", 61 | " image_name = image_path.split('/')[-1]\n", 62 | " image = cv2.imread(image_path)\n", 63 | " image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n", 64 | " bbox_data_gt = np.array([list(map(int, box.split(','))) for box in annotation[1:]])\n", 65 | "\n", 66 | " if len(bbox_data_gt) == 0:\n", 67 | " bboxes_gt=[]\n", 68 | " classes_gt=[]\n", 69 | " else:\n", 70 | " bboxes_gt, classes_gt = bbox_data_gt[:, :4], bbox_data_gt[:, 4]\n", 71 | " ground_truth_path = os.path.join(ground_truth_dir_path, str(num) + '.txt')\n", 72 | "\n", 73 | " print('=> ground truth of %s:' % image_name)\n", 74 | " num_bbox_gt = len(bboxes_gt)\n", 75 | " with open(ground_truth_path, 'w') as f:\n", 76 | " for i in range(num_bbox_gt):\n", 77 | " class_name = CLASSES[classes_gt[i]]\n", 78 | " xmin, ymin, xmax, ymax = list(map(str, bboxes_gt[i]))\n", 79 | " bbox_mess = ' '.join([class_name, xmin, ymin, xmax, ymax]) + '\\n'\n", 80 | " f.write(bbox_mess)\n", 81 | " print('\\t' + str(bbox_mess).strip())\n", 82 | " print('=> predict result of %s:' % image_name)\n", 83 | " predict_result_path = os.path.join(predicted_dir_path, str(num) + '.txt')\n", 84 | " # Predict Process\n", 85 | " image_size = image.shape[:2]\n", 86 | " image_data = utils.image_preporcess(np.copy(image), [INPUT_SIZE, INPUT_SIZE])\n", 87 | " image_data = image_data[np.newaxis, ...].astype(np.float32)\n", 88 | "\n", 89 | " pred_bbox = model.predict(image_data)\n", 90 | " #bboxes = utils.postprocess_boxes(pred_bbox, image_size, INPUT_SIZE, cfg.TEST.SCORE_THRESHOLD)\n", 91 | " #bboxes = utils.nms(bboxes, cfg.TEST.IOU_THRESHOLD, method='nms')\n", 92 | " bboxes = utils.postprocess_boxes(pred_bbox, image_size, INPUT_SIZE, cfg.TEST.SCORE_THRESHOLD)\n", 93 | " bboxes = utils.nms(bboxes, cfg.TEST.IOU_THRESHOLD, method='nms')\n", 94 | "\n", 95 | "\n", 96 | " if cfg.TEST.DECTECTED_IMAGE_PATH is not None:\n", 97 | " image = utils.draw_bbox(image, bboxes)\n", 98 | " cv2.imwrite(cfg.TEST.DECTECTED_IMAGE_PATH+image_name, image)\n", 99 | " \n", 100 | " required_classes = ['person','bicycle','car','diningtable','boat','motorbike','chair','dog','cup','bottle','bus','cat']\n", 101 | "\n", 102 | " with open(predict_result_path, 'w') as f:\n", 103 | " for bbox in bboxes:\n", 104 | " coor = np.array(bbox[:4], dtype=np.int32)\n", 105 | " score = bbox[4]\n", 106 | " class_ind = int(bbox[5])\n", 107 | " class_name = CLASSES[class_ind]\n", 108 | " if class_name in required_classes:\n", 109 | " score = '%.4f' % score\n", 110 | " xmin, ymin, xmax, ymax = list(map(str, coor))\n", 111 | " bbox_mess = ' '.join([class_name, score, xmin, ymin, xmax, ymax]) + '\\n'\n", 112 | " f.write(bbox_mess)\n", 113 | " print('\\t' + str(bbox_mess).strip())" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": null, 119 | "metadata": {}, 120 | "outputs": [], 121 | "source": [] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": null, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [] 129 | } 130 | ], 131 | "metadata": { 132 | "kernelspec": { 133 | "display_name": "Python 3", 134 | "language": "python", 135 | "name": "python3" 136 | }, 137 | "language_info": { 138 | "codemirror_mode": { 139 | "name": "ipython", 140 | "version": 3 141 | }, 142 | "file_extension": ".py", 143 | "mimetype": "text/x-python", 144 | "name": "python", 145 | "nbconvert_exporter": "python", 146 | "pygments_lexer": "ipython3", 147 | "version": "3.6.7" 148 | } 149 | }, 150 | "nbformat": 4, 151 | "nbformat_minor": 2 152 | } 153 | -------------------------------------------------------------------------------- /code/YOLOV3/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ### This python file will generate ground truth files and predicted files for the required subset of classes in ../mAP folder which will be used for calculation of mAP. We have explained the code in our portfolio. 5 | 6 | # In[ ]: 7 | 8 | 9 | import cv2 10 | import os 11 | import shutil 12 | import numpy as np 13 | import tensorflow as tf 14 | import core.utils as utils 15 | from core.config import cfg 16 | from core.yolov3 import YOLOv3, decode 17 | 18 | 19 | INPUT_SIZE = 416 20 | NUM_CLASS = len(utils.read_class_names(cfg.YOLO.CLASSES)) 21 | CLASSES = utils.read_class_names(cfg.YOLO.CLASSES) 22 | 23 | 24 | predicted_dir_path = '../mAP/predicted' 25 | ground_truth_dir_path = '../mAP/ground-truth' 26 | if os.path.exists(predicted_dir_path): shutil.rmtree(predicted_dir_path) 27 | if os.path.exists(ground_truth_dir_path): shutil.rmtree(ground_truth_dir_path) 28 | if os.path.exists(cfg.TEST.DECTECTED_IMAGE_PATH): shutil.rmtree(cfg.TEST.DECTECTED_IMAGE_PATH) 29 | 30 | os.mkdir(predicted_dir_path) 31 | os.mkdir(ground_truth_dir_path) 32 | os.mkdir(cfg.TEST.DECTECTED_IMAGE_PATH) 33 | 34 | # Build Model 35 | input_layer = tf.keras.layers.Input([INPUT_SIZE, INPUT_SIZE, 3]) 36 | feature_maps = YOLOv3(input_layer) 37 | 38 | bbox_tensors = [] 39 | for i, fm in enumerate(feature_maps): 40 | bbox_tensor = decode(fm, i) 41 | bbox_tensors.append(tf.reshape(bbox_tensor, (-1, 5+NUM_CLASS))) 42 | 43 | bbox_tensors = tf.concat(bbox_tensors, axis=0) 44 | model = tf.keras.Model(input_layer, bbox_tensors) 45 | model.summary() 46 | utils.load_weights(model, "./yolov3.weights") 47 | 48 | with open(cfg.TEST.ANNOT_PATH, 'r') as annotation_file: 49 | for num, line in enumerate(annotation_file): 50 | annotation = line.strip().split() 51 | image_path = annotation[0] 52 | image_name = image_path.split('/')[-1] 53 | image = cv2.imread(image_path) 54 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 55 | bbox_data_gt = np.array([list(map(int, box.split(','))) for box in annotation[1:]]) 56 | 57 | if len(bbox_data_gt) == 0: 58 | bboxes_gt=[] 59 | classes_gt=[] 60 | else: 61 | bboxes_gt, classes_gt = bbox_data_gt[:, :4], bbox_data_gt[:, 4] 62 | ground_truth_path = os.path.join(ground_truth_dir_path, str(num) + '.txt') 63 | 64 | print('=> ground truth of %s:' % image_name) 65 | num_bbox_gt = len(bboxes_gt) 66 | with open(ground_truth_path, 'w') as f: 67 | for i in range(num_bbox_gt): 68 | class_name = CLASSES[classes_gt[i]] 69 | xmin, ymin, xmax, ymax = list(map(str, bboxes_gt[i])) 70 | bbox_mess = ' '.join([class_name, xmin, ymin, xmax, ymax]) + '\n' 71 | f.write(bbox_mess) 72 | print('\t' + str(bbox_mess).strip()) 73 | print('=> predict result of %s:' % image_name) 74 | predict_result_path = os.path.join(predicted_dir_path, str(num) + '.txt') 75 | # Predict Process 76 | image_size = image.shape[:2] 77 | image_data = utils.image_preporcess(np.copy(image), [INPUT_SIZE, INPUT_SIZE]) 78 | image_data = image_data[np.newaxis, ...].astype(np.float32) 79 | 80 | pred_bbox = model.predict(image_data) 81 | #bboxes = utils.postprocess_boxes(pred_bbox, image_size, INPUT_SIZE, cfg.TEST.SCORE_THRESHOLD) 82 | #bboxes = utils.nms(bboxes, cfg.TEST.IOU_THRESHOLD, method='nms') 83 | bboxes = utils.postprocess_boxes(pred_bbox, image_size, INPUT_SIZE, cfg.TEST.SCORE_THRESHOLD) 84 | bboxes = utils.nms(bboxes, cfg.TEST.IOU_THRESHOLD, method='nms') 85 | 86 | 87 | if cfg.TEST.DECTECTED_IMAGE_PATH is not None: 88 | image = utils.draw_bbox(image, bboxes) 89 | cv2.imwrite(cfg.TEST.DECTECTED_IMAGE_PATH+image_name, image) 90 | 91 | required_classes = ['person','bicycle','car','diningtable','boat','motorbike','chair','dog','cup','bottle','bus','cat'] 92 | 93 | with open(predict_result_path, 'w') as f: 94 | for bbox in bboxes: 95 | coor = np.array(bbox[:4], dtype=np.int32) 96 | score = bbox[4] 97 | class_ind = int(bbox[5]) 98 | class_name = CLASSES[class_ind] 99 | if class_name in required_classes: 100 | score = '%.4f' % score 101 | xmin, ymin, xmax, ymax = list(map(str, coor)) 102 | bbox_mess = ' '.join([class_name, score, xmin, ymin, xmax, ymax]) + '\n' 103 | f.write(bbox_mess) 104 | print('\t' + str(bbox_mess).strip()) 105 | 106 | 107 | # In[ ]: 108 | 109 | 110 | 111 | 112 | 113 | # In[ ]: 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /code/YOLOV3/train_yolov3_darkdata.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import cv2\n", 12 | "import numpy as np\n", 13 | "import random\n", 14 | "import core.utils as utils\n", 15 | "import tensorflow as tf\n", 16 | "from tqdm import tqdm\n", 17 | "import shutil\n", 18 | "from core.yolov3 import YOLOv3, decode, compute_loss\n", 19 | "from PIL import Image\n", 20 | "from core.config import cfg\n", 21 | "from core.dataset import Dataset\n", 22 | "import os\n", 23 | "import time\n", 24 | "assert(tf.__version__=='2.0.0-beta1')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "\"\"\"Training YoloV3 on dark data\"\"\"\n", 34 | "\n", 35 | "\"\"\"change __C.YOLO.CLASSES= \"./data/classes/coco.names\" to \"./data/classes/darkdata.names\" in core/config.py\"\"\"\n", 36 | "\n", 37 | "\n", 38 | "trainset = Dataset('train')\n", 39 | "logdir = \"./data/logdemo\"\n", 40 | "steps_per_epoch = len(trainset)\n", 41 | "global_steps = tf.Variable(1, trainable=False, dtype=tf.int64)\n", 42 | "warmup_steps = cfg.TRAIN.WARMUP_EPOCHS * steps_per_epoch\n", 43 | "total_steps = cfg.TRAIN.EPOCHS * steps_per_epoch\n", 44 | "\n", 45 | "input_tensor = tf.keras.layers.Input([416, 416,3])\n", 46 | "conv_tensors = YOLOv3(input_tensor)\n", 47 | "\n", 48 | "output_tensors = []\n", 49 | "for i, conv_tensor in enumerate(conv_tensors):\n", 50 | " pred_tensor = decode(conv_tensor, i)\n", 51 | " output_tensors.append(conv_tensor)\n", 52 | " output_tensors.append(pred_tensor)\n", 53 | "\n", 54 | "model = tf.keras.Model(input_tensor, output_tensors)\n", 55 | "\n", 56 | "'''load weights of subset of classes from coco trained yolov3 weights'''\n", 57 | "\n", 58 | "num = [0,1,2]\n", 59 | "weight_num = []\n", 60 | "for i in num:\n", 61 | " tmp=[(85*i),(85*i+1),(85*i+2),(85*i+3),(85*i+4),(85*i+4+2),(85*i+4+9),(85*i+4+40),(85*i+4+6),(85*i+4+3),(85*i+4+16),(85*i+4+57),(85*i+4+42),(85*i+4+17),(85*i+4+4),(85*i+4+1),(85*i+4+61)]\n", 62 | " weight_num.append(tmp)\n", 63 | "req_act = []\n", 64 | "for each in weight_num:\n", 65 | " for ea in each:\n", 66 | " req_act.append(ea)\n", 67 | " \n", 68 | "wf = open('./yolov3.weights', 'rb')\n", 69 | "major, minor, revision, seen, _ = np.fromfile(wf, dtype=np.int32, count=5)\n", 70 | "j = 0\n", 71 | "layer_bias = []\n", 72 | "layer_weights = []\n", 73 | "for i in range(75):\n", 74 | " conv_layer_name = 'conv2d_%d' %i if i > 0 else 'conv2d'\n", 75 | " bn_layer_name = 'batch_normalization_%d' %j if j > 0 else 'batch_normalization'\n", 76 | "\n", 77 | " conv_layer = model.get_layer(conv_layer_name)\n", 78 | " filters = conv_layer.filters\n", 79 | " k_size = conv_layer.kernel_size[0]\n", 80 | " in_dim = conv_layer.input_shape[-1]\n", 81 | " if i not in [58, 66, 74]:\n", 82 | " # darknet weights: [beta, gamma, mean, variance]\n", 83 | " bn_weights = np.fromfile(wf, dtype=np.float32, count=4 * filters)\n", 84 | " # tf weights: [gamma, beta, mean, variance]\n", 85 | " bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]]\n", 86 | " bn_layer = model.get_layer(bn_layer_name)\n", 87 | " j += 1\n", 88 | " \n", 89 | " else:\n", 90 | " conv_bias = np.fromfile(wf, dtype=np.float32, count=255)\n", 91 | " layer_bias.append(conv_bias)\n", 92 | " if i not in [58, 66, 74]: \n", 93 | " conv_shape = (filters, in_dim, k_size, k_size)\n", 94 | " conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))\n", 95 | " conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])\n", 96 | " conv_layer.set_weights([conv_weights])\n", 97 | " bn_layer.set_weights(bn_weights)\n", 98 | " elif i==58:\n", 99 | " conv_shape = (255,1024,1,1)\n", 100 | " conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))\n", 101 | " # tf shape (height, width, in_dim, out_dim)\n", 102 | " conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])\n", 103 | " layer_weights.append(conv_weights)\n", 104 | " conv_layer.set_weights([conv_weights[:,:,:,req_act], conv_bias[req_act]])\n", 105 | " elif i==66:\n", 106 | " conv_shape = (255,512,1,1)\n", 107 | " conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))\n", 108 | " # tf shape (height, width, in_dim, out_dim)\n", 109 | " conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])\n", 110 | " layer_weights.append(conv_weights)\n", 111 | " conv_layer.set_weights([conv_weights[:,:,:,req_act], conv_bias[req_act]])\n", 112 | " else:\n", 113 | " conv_shape = (255,256,1,1)\n", 114 | " conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))\n", 115 | " # tf shape (height, width, in_dim, out_dim)\n", 116 | " conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])\n", 117 | " layer_weights.append(conv_weights)\n", 118 | " conv_layer.set_weights([conv_weights[:,:,:,req_act], conv_bias[req_act]])\n", 119 | "#print(len(wf.read()))\n", 120 | "assert len(wf.read()) == 0, 'failed to read all data'\n", 121 | "wf.close()\n", 122 | "\n", 123 | "''' freezing all layers except output layers to fine tune on dark dataset'''\n", 124 | "for layer in model.layers:\n", 125 | " if layer.name in model.output_names:\n", 126 | " layer.trainable = True\n", 127 | " else:\n", 128 | " layer.trainable = False\n", 129 | " \n", 130 | "'''train the model from here'''\n", 131 | "model.summary()\n", 132 | "optimizer = tf.keras.optimizers.Adam()\n", 133 | "if os.path.exists(logdir): shutil.rmtree(logdir)\n", 134 | "writer = tf.summary.create_file_writer(logdir) ##\n", 135 | "\n", 136 | "def train_step(image_data, target):\n", 137 | " with tf.GradientTape() as tape:\n", 138 | " pred_result = model(image_data, training=True)\n", 139 | " giou_loss=conf_loss=prob_loss=0\n", 140 | "\n", 141 | " # optimizing process\n", 142 | " for i in range(3):\n", 143 | " conv, pred = pred_result[i*2], pred_result[i*2+1]\n", 144 | " loss_items = compute_loss(pred, conv, *target[i], i)\n", 145 | " giou_loss += loss_items[0]\n", 146 | " conf_loss += loss_items[1]\n", 147 | " prob_loss += loss_items[2]\n", 148 | "\n", 149 | " total_loss = giou_loss + conf_loss + prob_loss\n", 150 | "\n", 151 | " gradients = tape.gradient(total_loss, model.trainable_variables)\n", 152 | " optimizer.apply_gradients(zip(gradients, model.trainable_variables))\n", 153 | " tf.print(\"=> STEP %4d lr: %.6f giou_loss: %4.2f conf_loss: %4.2f \"\n", 154 | " \"prob_loss: %4.2f total_loss: %4.2f\" %(global_steps, optimizer.lr.numpy(),\n", 155 | " giou_loss, conf_loss,\n", 156 | " prob_loss, total_loss))\n", 157 | " # update learning rate\n", 158 | " global_steps.assign_add(1)\n", 159 | " if global_steps < warmup_steps:\n", 160 | " lr = global_steps / warmup_steps *cfg.TRAIN.LR_INIT\n", 161 | " else:\n", 162 | " lr = cfg.TRAIN.LR_END + 0.5 * (cfg.TRAIN.LR_INIT - cfg.TRAIN.LR_END) * (\n", 163 | " (1 + tf.cos((global_steps - warmup_steps) / (total_steps - warmup_steps) * np.pi))\n", 164 | " )\n", 165 | " optimizer.lr.assign(lr.numpy())\n", 166 | "\n", 167 | " # writing summary data to logs to display progress in tensor board\n", 168 | " with writer.as_default():\n", 169 | " tf.summary.scalar(\"lr\", optimizer.lr, step=global_steps)\n", 170 | " tf.summary.scalar(\"loss/total_loss\", total_loss, step=global_steps)\n", 171 | " tf.summary.scalar(\"loss/giou_loss\", giou_loss, step=global_steps)\n", 172 | " tf.summary.scalar(\"loss/conf_loss\", conf_loss, step=global_steps)\n", 173 | " tf.summary.scalar(\"loss/prob_loss\", prob_loss, step=global_steps)\n", 174 | " writer.flush()\n", 175 | "\n", 176 | "for epoch in range(cfg.TRAIN.EPOCHS):\n", 177 | " print(\"epoch %4d/%4d begins here:\"%(epoch+1,cfg.TRAIN.EPOCHS),\" steps per epoch: \",steps_per_epoch)\n", 178 | " for image_data, target,unhandled in trainset:\n", 179 | " #print(target)\n", 180 | " train_step(image_data, target)\n", 181 | " model.save_weights(\"./yolov3_dark\")" 182 | ] 183 | } 184 | ], 185 | "metadata": { 186 | "kernelspec": { 187 | "display_name": "Python 3", 188 | "language": "python", 189 | "name": "python3" 190 | }, 191 | "language_info": { 192 | "codemirror_mode": { 193 | "name": "ipython", 194 | "version": 3 195 | }, 196 | "file_extension": ".py", 197 | "mimetype": "text/x-python", 198 | "name": "python", 199 | "nbconvert_exporter": "python", 200 | "pygments_lexer": "ipython3", 201 | "version": "3.6.7" 202 | } 203 | }, 204 | "nbformat": 4, 205 | "nbformat_minor": 2 206 | } 207 | -------------------------------------------------------------------------------- /code/mAP/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaminaveen/YOLOv3_Low_Light_Object_Detection/d7c4681a386a884ab4254d2fc34b06abaa5ce9ca/code/mAP/__init__.py -------------------------------------------------------------------------------- /code/mAP/extra/README.md: -------------------------------------------------------------------------------- 1 | # Extra 2 | 3 | ## Ground-Truth: 4 | - ### convert `xml` to our format: 5 | 6 | 1) Insert ground-truth xml files into **ground-truth/** 7 | 2) Run the python script: `python convert_gt_xml.py` 8 | 9 | - ### convert YOLO to our format: 10 | 11 | 1) Add class list to the file `class_list.txt` 12 | 2) Insert ground-truth files into **ground-truth/** 13 | 3) Insert images into **images/** 14 | 4) Run the python script: `python convert_gt_yolo.py` 15 | 16 | - ### convert keras-yolo3 to our format: 17 | 18 | 1) Add or update the class list to the file `class_list.txt` 19 | 2) Use the parameter `--gt` to set the **ground-truth** source. 20 | 3) Run the python script: `python3 convert_keras-yolo3.py --gt ` 21 | 1) Supports only python 3. 22 | 2) This code can handle recursive annotation structure. Just use the `-r` parameter. 23 | 3) The converted annotation is placed by default in a new from_kerasyolo3 folder. You can change that with the parameter `-o`. 24 | 4) The format is defined according with github.com/qqwweee/keras-yolo3 25 | 26 | ## Predicted: 27 | - ### convert darkflow `json` to our format: 28 | 29 | 1) Insert result json files into **predicted/** 30 | 2) Run the python script: `python convert_pred_darkflow_json.py` 31 | 32 | - ### convert YOLO to our format: 33 | 34 | After runnuning darknet on a list of images, e.g.: `darknet.exe detector test data/voc.data yolo-voc.cfg yolo-voc.weights -dont_show -ext_output < data/test.txt > result.txt` 35 | 36 | 1) Copy the file `result.txt` to the folder `extra/` 37 | 2) Run the python script: `python convert_pred_yolo.py` 38 | 39 | - ### convert keras-yolo3 to our format: 40 | 41 | 1) Add or update the class list to the file `class_list.txt` 42 | 2) Use the parameter `--predicted` to set the **prediction** source. 43 | 3) Run the python script: `python3 convert_keras-yolo3.py --pred ` 44 | 1) Supports only python 3. 45 | 2) This code can handle recursive annotation structure. Just use the `-r` parameter. 46 | 3) The converted annotation is placed by default in a new from_kerasyolo3 folder. You can change that with the parameter `-o`. 47 | 4) The format is defined according with github.com/gustavovaliati/keras-yolo3 48 | 49 | ## Remove specific char delimiter from files 50 | 51 | E.g. remove `;` from: 52 | 53 | `;;;;` 54 | 55 | to: 56 | 57 | ` ` 58 | 59 | In the case you have the `--ground-truth` or `--predicted` files in the right format but with a specific char being used as a delimiter (e.g. `";"`), you can remove it by running: 60 | 61 | `python remove_delimiter_char.py --char ";" --ground-truth` 62 | 63 | ## Find the files that contain a specific class of objects 64 | 65 | 1) Run the `find_class.py` script and specify the **class** as argument, e.g. 66 | `python find_class.py chair` 67 | 68 | ## Remove all the instances of a specific class of objects 69 | 70 | 1) Run the `remove_class.py` script and specify the **class** as argument, e.g. 71 | `python remove_class.py chair` 72 | 73 | ## Rename a specific class of objects 74 | 75 | 1) Run the `rename_class.py` script and specify the `--current-class-name` and `--new-class-name` as arguments, e.g. 76 | 77 | `python rename_class.py --current-class-name Picture Frame --new-class-name PictureFrame` 78 | 79 | ## Rename all classes by replacing spaces with delimiters 80 | Use this option instead of the above option when you have a lot of classes with spaces. 81 | It's useful when renaming classes with spaces become tedious (because you have a lot of them). 82 | 83 | 1) Add class list to the file `class_list.txt` (the script will search this file for class names with spaces) 84 | 2) Run the `remove_space.py` script and specify the `--delimiter` (default: "-") and `--yes` if you want to force confirmation on all yes/no queries, e.g. 85 | 86 | `python remove_space.py --delimiter "-" --yes` 87 | 88 | ## Intersect ground-truth and predicted files 89 | This script ensures same number of files in ground-truth and predicted folder. 90 | When you encounter file not found error, it's usually because you have 91 | mismatched numbers of ground-truth and predicted files. 92 | You can use this script to move ground-truth and predicted files that are 93 | not in the intersection into a backup folder (backup_no_matches_found). 94 | This will retain only files that have the same name in both folders. 95 | 96 | 1) Prepare `.txt` files in your `ground-truth` and `predicted` folders. 97 | 2) Run the `intersect-gt-and-pred.py` script to move non-intersected files into a backup folder (default: `backup_no_matches_found`). 98 | 99 | `python intersect-gt-and-pred.py` 100 | -------------------------------------------------------------------------------- /code/mAP/extra/class_list.txt: -------------------------------------------------------------------------------- 1 | bed 2 | person 3 | pictureframe 4 | shirt 5 | lamp 6 | nightstand 7 | clock 8 | heater 9 | windowblind 10 | pillow 11 | robot 12 | cabinetry 13 | door 14 | doorhandle 15 | shelf 16 | pottedplant 17 | chair 18 | diningtable 19 | backpack 20 | whiteboard 21 | cup 22 | tvmonitor 23 | pen 24 | pencil 25 | wardrobe 26 | apple 27 | orange 28 | countertop 29 | tap 30 | banana 31 | bicyclehelmet 32 | book 33 | bookcase 34 | refrigerator 35 | wastecontainer 36 | tincan 37 | handbag 38 | sofa 39 | glasses 40 | vase 41 | coffeetable 42 | bowl 43 | remote 44 | candle 45 | bottle 46 | sink 47 | envelope 48 | doll 49 | -------------------------------------------------------------------------------- /code/mAP/extra/convert_gt_xml.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | import xml.etree.ElementTree as ET 5 | 6 | 7 | # change directory to the one with the files to be changed 8 | path_to_folder = '../ground-truth' 9 | #print(path_to_folder) 10 | os.chdir(path_to_folder) 11 | 12 | # old files (xml format) will be moved to a "backup" folder 13 | ## create the backup dir if it doesn't exist already 14 | if not os.path.exists("backup"): 15 | os.makedirs("backup") 16 | 17 | # create VOC format files 18 | xml_list = glob.glob('*.xml') 19 | if len(xml_list) == 0: 20 | print("Error: no .xml files found in ground-truth") 21 | sys.exit() 22 | for tmp_file in xml_list: 23 | #print(tmp_file) 24 | # 1. create new file (VOC format) 25 | with open(tmp_file.replace(".xml", ".txt"), "a") as new_f: 26 | root = ET.parse(tmp_file).getroot() 27 | for obj in root.findall('object'): 28 | obj_name = obj.find('name').text 29 | bndbox = obj.find('bndbox') 30 | left = bndbox.find('xmin').text 31 | top = bndbox.find('ymin').text 32 | right = bndbox.find('xmax').text 33 | bottom = bndbox.find('ymax').text 34 | new_f.write(obj_name + " " + left + " " + top + " " + right + " " + bottom + '\n') 35 | # 2. move old file (xml format) to backup 36 | os.rename(tmp_file, "backup/" + tmp_file) 37 | print("Conversion completed!") 38 | -------------------------------------------------------------------------------- /code/mAP/extra/convert_gt_yolo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | import cv2 5 | 6 | 7 | def convert_yolo_coordinates_to_voc(x_c_n, y_c_n, width_n, height_n, img_width, img_height): 8 | ## remove normalization given the size of the image 9 | x_c = float(x_c_n) * img_width 10 | y_c = float(y_c_n) * img_height 11 | width = float(width_n) * img_width 12 | height = float(height_n) * img_height 13 | ## compute half width and half height 14 | half_width = width / 2 15 | half_height = height / 2 16 | ## compute left, top, right, bottom 17 | ## in the official VOC challenge the top-left pixel in the image has coordinates (1;1) 18 | left = int(x_c - half_width) + 1 19 | top = int(y_c - half_height) + 1 20 | right = int(x_c + half_width) + 1 21 | bottom = int(y_c + half_height) + 1 22 | return left, top, right, bottom 23 | 24 | # read the class_list.txt to a list 25 | with open("class_list.txt") as f: 26 | obj_list = f.readlines() 27 | ## remove whitespace characters like `\n` at the end of each line 28 | obj_list = [x.strip() for x in obj_list] 29 | ## e.g. first object in the list 30 | #print(obj_list[0]) 31 | 32 | # change directory to the one with the files to be changed 33 | path_to_folder = '../ground-truth' 34 | #print(path_to_folder) 35 | os.chdir(path_to_folder) 36 | 37 | # old files (YOLO format) will be moved to a new folder (backup/) 38 | ## create the backup dir if it doesn't exist already 39 | if not os.path.exists("backup"): 40 | os.makedirs("backup") 41 | 42 | # create VOC format files 43 | txt_list = glob.glob('*.txt') 44 | if len(txt_list) == 0: 45 | print("Error: no .txt files found in ground-truth") 46 | sys.exit() 47 | for tmp_file in txt_list: 48 | #print(tmp_file) 49 | # 1. check that there is an image with that name 50 | ## get name before ".txt" 51 | image_name = tmp_file.split(".txt",1)[0] 52 | #print(image_name) 53 | ## check if image exists 54 | for fname in os.listdir('../images'): 55 | if fname.startswith(image_name): 56 | ## image found 57 | #print(fname) 58 | img = cv2.imread('../images/' + fname) 59 | ## get image width and height 60 | img_height, img_width = img.shape[:2] 61 | break 62 | else: 63 | ## image not found 64 | print("Error: image not found, corresponding to " + tmp_file) 65 | sys.exit() 66 | # 2. open txt file lines to a list 67 | with open(tmp_file) as f: 68 | content = f.readlines() 69 | ## remove whitespace characters like `\n` at the end of each line 70 | content = [x.strip() for x in content] 71 | # 3. move old file (YOLO format) to backup 72 | os.rename(tmp_file, "backup/" + tmp_file) 73 | # 4. create new file (VOC format) 74 | with open(tmp_file, "a") as new_f: 75 | for line in content: 76 | ## split a line by spaces. 77 | ## "c" stands for center and "n" stands for normalized 78 | obj_id, x_c_n, y_c_n, width_n, height_n = line.split() 79 | obj_name = obj_list[int(obj_id)] 80 | left, top, right, bottom = convert_yolo_coordinates_to_voc(x_c_n, y_c_n, width_n, height_n, img_width, img_height) 81 | ## add new line to file 82 | #print(obj_name + " " + str(left) + " " + str(top) + " " + str(right) + " " + str(bottom)) 83 | new_f.write(obj_name + " " + str(left) + " " + str(top) + " " + str(right) + " " + str(bottom) + '\n') 84 | print("Conversion completed!") 85 | -------------------------------------------------------------------------------- /code/mAP/extra/convert_keras-yolo3.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ABOUT THIS SCRIPT: 3 | Converts ground-truth from the annotation files 4 | according to the https://github.com/qqwweee/keras-yolo3 5 | or https://github.com/gustavovaliati/keras-yolo3 format. 6 | 7 | And converts the predicitons from the annotation files 8 | according to the https://github.com/gustavovaliati/keras-yolo3 format. 9 | ''' 10 | 11 | import argparse 12 | import datetime 13 | import os 14 | 15 | ''' 16 | Each time this script runs, it saves the output in a different path 17 | controlled by the following folder suffix: annotation_version. 18 | ''' 19 | annotation_version = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 20 | 21 | ap = argparse.ArgumentParser() 22 | 23 | ap.add_argument("-o", "--output_path", 24 | required=False, 25 | default='from_kerasyolo3/version_{}'.format(annotation_version), 26 | type=str, 27 | help="The dataset root path location.") 28 | ap.add_argument("-r", "--gen_recursive", 29 | required=False, 30 | default=False, 31 | action="store_true", 32 | help="Define if the output txt files will be placed in a \ 33 | recursive folder tree or to direct txt files.") 34 | group = ap.add_mutually_exclusive_group(required=True) 35 | group.add_argument('--gt', 36 | type=str, 37 | default=None, 38 | help="The annotation file that refers to ground-truth in (keras-yolo3 format)") 39 | group.add_argument('--pred', 40 | type=str, 41 | default=None, 42 | help="The annotation file that refers to predictions in (keras-yolo3 format)") 43 | 44 | ARGS = ap.parse_args() 45 | 46 | with open('class_list.txt', 'r') as class_file: 47 | class_map = class_file.readlines() 48 | print(class_map) 49 | annotation_file = ARGS.gt if ARGS.gt else ARGS.pred 50 | 51 | os.makedirs(ARGS.output_path, exist_ok=True) 52 | 53 | with open(annotation_file, 'r') as annot_f: 54 | for annot in annot_f: 55 | annot = annot.split(' ') 56 | img_path = annot[0].strip() 57 | if ARGS.gen_recursive: 58 | annotation_dir_name = os.path.dirname(img_path) 59 | # remove the root path to enable to path.join. 60 | if annotation_dir_name.startswith('/'): 61 | annotation_dir_name = annotation_dir_name.replace('/', '', 1) 62 | destination_dir = os.path.join(ARGS.output_path, annotation_dir_name) 63 | os.makedirs(destination_dir, exist_ok=True) 64 | # replace .jpg with your image format. 65 | file_name = os.path.basename(img_path).replace('.jpg', '.txt') 66 | output_file_path = os.path.join(destination_dir, file_name) 67 | else: 68 | file_name = img_path.replace('.jpg', '.txt').replace('/', '__') 69 | output_file_path = os.path.join(ARGS.output_path, file_name) 70 | os.path.dirname(output_file_path) 71 | 72 | with open(output_file_path, 'w') as out_f: 73 | for bbox in annot[1:]: 74 | if ARGS.gt: 75 | # Here we are dealing with ground-truth annotations 76 | # [] 77 | # todo: handle difficulty 78 | x_min, y_min, x_max, y_max, class_id = list(map(float, bbox.split(','))) 79 | out_box = '{} {} {} {} {}'.format( 80 | class_map[int(class_id)].strip(), x_min, y_min, x_max, y_max) 81 | else: 82 | # Here we are dealing with predictions annotations 83 | # 84 | x_min, y_min, x_max, y_max, class_id, score = list(map(float, bbox.split(','))) 85 | out_box = '{} {} {} {} {} {}'.format( 86 | class_map[int(class_id)].strip(), score, x_min, y_min, x_max, y_max) 87 | 88 | out_f.write(out_box + "\n") 89 | -------------------------------------------------------------------------------- /code/mAP/extra/convert_pred_darkflow_json.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | import json 5 | 6 | 7 | # change directory to the one with the files to be changed 8 | path_to_folder = '../predicted' 9 | #print(path_to_folder) 10 | os.chdir(path_to_folder) 11 | 12 | # old files (darkflow json format) will be moved to a "backup" folder 13 | ## create the backup dir if it doesn't exist already 14 | if not os.path.exists("backup"): 15 | os.makedirs("backup") 16 | 17 | # create VOC format files 18 | json_list = glob.glob('*.json') 19 | if len(json_list) == 0: 20 | print("Error: no .json files found in predicted") 21 | sys.exit() 22 | for tmp_file in json_list: 23 | #print(tmp_file) 24 | # 1. create new file (VOC format) 25 | with open(tmp_file.replace(".json", ".txt"), "a") as new_f: 26 | data = json.load(open(tmp_file)) 27 | for obj in data: 28 | obj_name = obj['label'] 29 | conf = obj['confidence'] 30 | left = obj['topleft']['x'] 31 | top = obj['topleft']['y'] 32 | right = obj['bottomright']['x'] 33 | bottom = obj['bottomright']['y'] 34 | new_f.write(obj_name + " " + str(conf) + " " + str(left) + " " + str(top) + " " + str(right) + " " + str(bottom) + '\n') 35 | # 2. move old file (darkflow format) to backup 36 | os.rename(tmp_file, "backup/" + tmp_file) 37 | print("Conversion completed!") 38 | -------------------------------------------------------------------------------- /code/mAP/extra/convert_pred_yolo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | IN_FILE = 'result.txt' 5 | OUTPUT_DIR = os.path.join('..', 'predicted') 6 | 7 | SEPARATOR_KEY = 'Enter Image Path:' 8 | IMG_FORMAT = '.jpg' 9 | 10 | outfile = None 11 | with open(IN_FILE) as infile: 12 | for line in infile: 13 | if SEPARATOR_KEY in line: 14 | if IMG_FORMAT not in line: 15 | break 16 | # get text between two substrings (SEPARATOR_KEY and IMG_FORMAT) 17 | image_path = re.search(SEPARATOR_KEY + '(.*)' + IMG_FORMAT, line) 18 | # get the image name (the final component of a image_path) 19 | # e.g., from 'data/horses_1' to 'horses_1' 20 | image_name = os.path.basename(image_path.group(1)) 21 | # close the previous file 22 | if outfile is not None: 23 | outfile.close() 24 | # open a new file 25 | outfile = open(os.path.join(OUTPUT_DIR, image_name + '.txt'), 'w') 26 | elif outfile is not None: 27 | # split line on first occurrence of the character ':' and '%' 28 | class_name, info = line.split(':', 1) 29 | confidence, bbox = info.split('%', 1) 30 | # get all the coordinates of the bounding box 31 | bbox = bbox.replace(')','') # remove the character ')' 32 | # go through each of the parts of the string and check if it is a digit 33 | left, top, width, height = [int(s) for s in bbox.split() if s.lstrip('-').isdigit()] 34 | right = left + width 35 | bottom = top + height 36 | outfile.write("{} {} {} {} {} {}\n".format(class_name, float(confidence)/100, left, top, right, bottom)) 37 | -------------------------------------------------------------------------------- /code/mAP/extra/find_class.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | 5 | if len(sys.argv) != 2: 6 | print("Error: wrong format.\nUsage: python find_class.py [class_name]") 7 | sys.exit(0) 8 | 9 | searching_class_name = sys.argv[1] 10 | 11 | def find_class(class_name): 12 | file_list = glob.glob('*.txt') 13 | file_list.sort() 14 | # iterate through the text files 15 | file_found = False 16 | for txt_file in file_list: 17 | # open txt file lines to a list 18 | with open(txt_file) as f: 19 | content = f.readlines() 20 | # remove whitespace characters like `\n` at the end of each line 21 | content = [x.strip() for x in content] 22 | # go through each line of eache file 23 | for line in content: 24 | class_name = line.split()[0] 25 | if class_name == searching_class_name: 26 | print(" " + txt_file) 27 | file_found = True 28 | break 29 | if not file_found: 30 | print(" No file found with that class") 31 | 32 | print("Ground-Truth folder:") 33 | os.chdir("../ground-truth") 34 | find_class(searching_class_name) 35 | print("\nPredicted folder:") 36 | os.chdir("../predicted") 37 | find_class(searching_class_name) 38 | -------------------------------------------------------------------------------- /code/mAP/extra/intersect-gt-and-pred.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | 5 | ## This script ensures same number of files in ground-truth and predicted folder. 6 | ## When you encounter file not found error, it's usually because you have 7 | ## mismatched numbers of ground-truth and predicted files. 8 | ## You can use this script to move ground-truth and predicted files that are 9 | ## not in the intersection into a backup folder (backup_no_matches_found). 10 | ## This will retain only files that have the same name in both folders. 11 | 12 | # change directory to the one with the files to be changed 13 | path_to_gt = '../ground-truth' 14 | path_to_pred = '../predicted' 15 | backup_folder = 'backup_no_matches_found' # must end without slash 16 | 17 | os.chdir(path_to_gt) 18 | gt_files = glob.glob('*.txt') 19 | if len(gt_files) == 0: 20 | print("Error: no .txt files found in", path_to_gt) 21 | sys.exit() 22 | os.chdir(path_to_pred) 23 | pred_files = glob.glob('*.txt') 24 | if len(pred_files) == 0: 25 | print("Error: no .txt files found in", path_to_pred) 26 | sys.exit() 27 | 28 | gt_files = set(gt_files) 29 | pred_files = set(pred_files) 30 | print('total ground-truth files:', len(gt_files)) 31 | print('total predicted files:', len(pred_files)) 32 | print() 33 | 34 | gt_backup = gt_files - pred_files 35 | pred_backup = pred_files - gt_files 36 | 37 | def backup(src_folder, backup_files, backup_folder): 38 | # non-intersection files (txt format) will be moved to a backup folder 39 | if not backup_files: 40 | print('No backup required for', src_folder) 41 | return 42 | os.chdir(src_folder) 43 | ## create the backup dir if it doesn't exist already 44 | if not os.path.exists(backup_folder): 45 | os.makedirs(backup_folder) 46 | for file in backup_files: 47 | os.rename(file, backup_folder + '/' + file) 48 | 49 | backup(path_to_gt, gt_backup, backup_folder) 50 | backup(path_to_pred, pred_backup, backup_folder) 51 | if gt_backup: 52 | print('total ground-truth backup files:', len(gt_backup)) 53 | if pred_backup: 54 | print('total predicted backup files:', len(pred_backup)) 55 | 56 | intersection = gt_files & pred_files 57 | print('total intersected files:', len(intersection)) 58 | print("Intersection completed!") 59 | -------------------------------------------------------------------------------- /code/mAP/extra/remove_class.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | 5 | if len(sys.argv) != 2: 6 | print("Error: wrong format.\nUsage: python remove_class.py [class_name]") 7 | sys.exit(0) 8 | 9 | searching_class_name = sys.argv[1] 10 | 11 | 12 | def query_yes_no(question, default="yes"): 13 | """Ask a yes/no question via raw_input() and return their answer. 14 | 15 | "question" is a string that is presented to the user. 16 | "default" is the presumed answer if the user just hits . 17 | It must be "yes" (the default), "no" or None (meaning 18 | an answer is required of the user). 19 | 20 | The "answer" return value is True for "yes" or False for "no". 21 | """ 22 | valid = {"yes": True, "y": True, "ye": True, 23 | "no": False, "n": False} 24 | if default is None: 25 | prompt = " [y/n] " 26 | elif default == "yes": 27 | prompt = " [Y/n] " 28 | elif default == "no": 29 | prompt = " [y/N] " 30 | else: 31 | raise ValueError("invalid default answer: '%s'" % default) 32 | 33 | while True: 34 | sys.stdout.write(question + prompt) 35 | if sys.version_info[0] == 3: 36 | choice = input().lower() # if version 3 of Python 37 | else: 38 | choice = raw_input().lower() 39 | if default is not None and choice == '': 40 | return valid[default] 41 | elif choice in valid: 42 | return valid[choice] 43 | else: 44 | sys.stdout.write("Please respond with 'yes' or 'no' " 45 | "(or 'y' or 'n').\n") 46 | 47 | 48 | def remove_class(class_name): 49 | # get list of txt files 50 | file_list = glob.glob('*.txt') 51 | file_list.sort() 52 | # iterate through the txt files 53 | for txt_file in file_list: 54 | class_found = False 55 | # open txt file lines to a list 56 | with open(txt_file) as f: 57 | content = f.readlines() 58 | # remove whitespace characters like `\n` at the end of each line 59 | content = [x.strip() for x in content] 60 | new_content = [] 61 | # go through each line of eache file 62 | for line in content: 63 | class_name = line.split()[0] 64 | if class_name == searching_class_name: 65 | class_found = True 66 | else: 67 | new_content.append(line) 68 | if class_found: 69 | # rewrite file 70 | with open(txt_file, 'w') as new_f: 71 | for line in new_content: 72 | new_f.write("%s\n" % line) 73 | 74 | if query_yes_no("Are you sure you want to remove the class \"" + searching_class_name + "\"?"): 75 | print(" Ground-Truth folder:") 76 | os.chdir("../ground-truth") 77 | remove_class(searching_class_name) 78 | print(" Done!") 79 | print(" Predicted folder:") 80 | os.chdir("../predicted") 81 | remove_class(searching_class_name) 82 | print(" Done!") 83 | -------------------------------------------------------------------------------- /code/mAP/extra/remove_delimiter_char.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import sys 4 | import argparse 5 | 6 | 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('-c', '--char', required=True, type=str, help='specific character to be removed (e.g. ";").') 9 | # mutually exclusive arguments (can't select both) 10 | group = parser.add_mutually_exclusive_group(required=True) 11 | group.add_argument('-g', '--ground-truth', help="if to remove that char from the ground-truth files.", action="store_true") 12 | group.add_argument('-p', '--predicted', help="if to remove that char from the predicted objects files.", action="store_true") 13 | args = parser.parse_args() 14 | 15 | def file_lines_to_list(path): 16 | # open txt file lines to a list 17 | with open(path) as f: 18 | content = f.readlines() 19 | # remove whitespace characters like `\n` at the end of each line 20 | content = [x.strip() for x in content] 21 | return content 22 | 23 | if len(args.char) != 1: 24 | print("Error: Please select a single char to be removed.") 25 | sys.exit(0) 26 | 27 | if args.predicted: 28 | os.chdir("../predicted/") 29 | else: 30 | os.chdir("../ground-truth/") 31 | 32 | ## create the backup dir if it doesn't exist already 33 | backup_path = "backup" 34 | if not os.path.exists(backup_path): 35 | os.makedirs(backup_path) 36 | 37 | # get a list with the predicted files 38 | files_list = glob.glob('*.txt') 39 | files_list.sort() 40 | 41 | for txt_file in files_list: 42 | lines = file_lines_to_list(txt_file) 43 | is_char_present = any(args.char in line for line in lines) 44 | if is_char_present: 45 | # move old file to backup 46 | os.rename(txt_file, backup_path + "/" + txt_file) 47 | # create new file 48 | with open(txt_file, "a") as new_f: 49 | for line in lines: 50 | #print(line) 51 | if args.predicted: 52 | class_name, confidence, left, top, right, bottom = line.split(args.char) 53 | # remove any white space if existent in the class name 54 | class_name = class_name.replace(" ", "") 55 | new_f.write(class_name + " " + confidence + " " + left + " " + top + " " + right + " " + bottom + '\n') 56 | else: 57 | # ground-truth has no "confidence" 58 | class_name, left, top, right, bottom = line.split(args.char) 59 | # remove any white space if existent in the class name 60 | class_name = class_name.replace(" ", "") 61 | new_f.write(class_name + " " + left + " " + top + " " + right + " " + bottom + '\n') 62 | print("Conversion completed!") 63 | -------------------------------------------------------------------------------- /code/mAP/extra/remove_space.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | import argparse 5 | 6 | # this script will load class_list.txt and find class names with spaces 7 | # then replace spaces with delimiters inside ground-truth/ and predicted/ 8 | 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument('-d', '--delimiter', type=str, help="delimiter to replace space (default: '-')", default='-') 11 | parser.add_argument('-y', '--yes', action='store_true', help="force yes confirmation on yes/no query (default: False)", default=False) 12 | args = parser.parse_args() 13 | 14 | def query_yes_no(question, default="yes", bypass=False): 15 | """Ask a yes/no question via raw_input() and return their answer. 16 | 17 | "question" is a string that is presented to the user. 18 | "default" is the presumed answer if the user just hits . 19 | It must be "yes" (the default), "no" or None (meaning 20 | an answer is required of the user). 21 | 22 | The "answer" return value is True for "yes" or False for "no". 23 | """ 24 | valid = {"yes": True, "y": True, "ye": True, 25 | "no": False, "n": False} 26 | if default is None: 27 | prompt = " [y/n] " 28 | elif default == "yes": 29 | prompt = " [Y/n] " 30 | elif default == "no": 31 | prompt = " [y/N] " 32 | else: 33 | raise ValueError("invalid default answer: '%s'" % default) 34 | 35 | while True: 36 | sys.stdout.write(question + prompt) 37 | if bypass: 38 | break 39 | if sys.version_info[0] == 3: 40 | choice = input().lower() # if version 3 of Python 41 | else: 42 | choice = raw_input().lower() 43 | if default is not None and choice == '': 44 | return valid[default] 45 | elif choice in valid: 46 | return valid[choice] 47 | else: 48 | sys.stdout.write("Please respond with 'yes' or 'no' " 49 | "(or 'y' or 'n').\n") 50 | 51 | 52 | def rename_class(current_class_name, new_class_name): 53 | # get list of txt files 54 | file_list = glob.glob('*.txt') 55 | file_list.sort() 56 | # iterate through the txt files 57 | for txt_file in file_list: 58 | class_found = False 59 | # open txt file lines to a list 60 | with open(txt_file) as f: 61 | content = f.readlines() 62 | # remove whitespace characters like `\n` at the end of each line 63 | content = [x.strip() for x in content] 64 | new_content = [] 65 | # go through each line of eache file 66 | for line in content: 67 | #class_name = line.split()[0] 68 | if current_class_name in line: 69 | class_found = True 70 | line = line.replace(current_class_name, new_class_name) 71 | new_content.append(line) 72 | if class_found: 73 | # rewrite file 74 | with open(txt_file, 'w') as new_f: 75 | for line in new_content: 76 | new_f.write("%s\n" % line) 77 | 78 | with open('class_list.txt') as f: 79 | for line in f: 80 | current_class_name = line.rstrip("\n") 81 | new_class_name = line.replace(' ', args.delimiter).rstrip("\n") 82 | if current_class_name == new_class_name: 83 | continue 84 | y_n_message = ("Are you sure you want " 85 | "to rename the class " 86 | "\"" + current_class_name + "\" " 87 | "into \"" + new_class_name + "\"?" 88 | ) 89 | 90 | if query_yes_no(y_n_message, bypass=args.yes): 91 | os.chdir("../ground-truth") 92 | rename_class(current_class_name, new_class_name) 93 | os.chdir("../predicted") 94 | rename_class(current_class_name, new_class_name) 95 | 96 | print('Done!') 97 | -------------------------------------------------------------------------------- /code/mAP/extra/rename_class.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import glob 4 | import argparse 5 | 6 | parser = argparse.ArgumentParser() 7 | # argparse current class name to a list (since it can contain more than one word, e.g."dining table") 8 | parser.add_argument('-c', '--current-class-name', nargs='+', type=str, help="current class name e.g.:\"dining table\".", required=True) 9 | # new class name (should be a single string without any spaces, e.g. "diningtable") 10 | parser.add_argument('-n', '--new-class-name', type=str, help="new class name.", required=True) 11 | args = parser.parse_args() 12 | 13 | current_class_name = " ".join(args.current_class_name) # join current name to single string 14 | new_class_name = args.new_class_name 15 | 16 | 17 | def query_yes_no(question, default="yes"): 18 | """Ask a yes/no question via raw_input() and return their answer. 19 | 20 | "question" is a string that is presented to the user. 21 | "default" is the presumed answer if the user just hits . 22 | It must be "yes" (the default), "no" or None (meaning 23 | an answer is required of the user). 24 | 25 | The "answer" return value is True for "yes" or False for "no". 26 | """ 27 | valid = {"yes": True, "y": True, "ye": True, 28 | "no": False, "n": False} 29 | if default is None: 30 | prompt = " [y/n] " 31 | elif default == "yes": 32 | prompt = " [Y/n] " 33 | elif default == "no": 34 | prompt = " [y/N] " 35 | else: 36 | raise ValueError("invalid default answer: '%s'" % default) 37 | 38 | while True: 39 | sys.stdout.write(question + prompt) 40 | if sys.version_info[0] == 3: 41 | choice = input().lower() # if version 3 of Python 42 | else: 43 | choice = raw_input().lower() 44 | if default is not None and choice == '': 45 | return valid[default] 46 | elif choice in valid: 47 | return valid[choice] 48 | else: 49 | sys.stdout.write("Please respond with 'yes' or 'no' " 50 | "(or 'y' or 'n').\n") 51 | 52 | 53 | def rename_class(current_class_name, new_class_name): 54 | # get list of txt files 55 | file_list = glob.glob('*.txt') 56 | file_list.sort() 57 | # iterate through the txt files 58 | for txt_file in file_list: 59 | class_found = False 60 | # open txt file lines to a list 61 | with open(txt_file) as f: 62 | content = f.readlines() 63 | # remove whitespace characters like `\n` at the end of each line 64 | content = [x.strip() for x in content] 65 | new_content = [] 66 | # go through each line of eache file 67 | for line in content: 68 | #class_name = line.split()[0] 69 | if current_class_name in line: 70 | class_found = True 71 | line = line.replace(current_class_name, new_class_name) 72 | new_content.append(line) 73 | if class_found: 74 | # rewrite file 75 | with open(txt_file, 'w') as new_f: 76 | for line in new_content: 77 | new_f.write("%s\n" % line) 78 | 79 | y_n_message = ("Are you sure you want " 80 | "to rename the class " 81 | "\"" + current_class_name + "\" " 82 | "into \"" + new_class_name + "\"?" 83 | ) 84 | 85 | if query_yes_no(y_n_message): 86 | print(" Ground-Truth folder:") 87 | os.chdir("../ground-truth") 88 | rename_class(current_class_name, new_class_name) 89 | print(" Done!") 90 | print(" Predicted folder:") 91 | os.chdir("../predicted") 92 | rename_class(current_class_name, new_class_name) 93 | print(" Done!") 94 | -------------------------------------------------------------------------------- /code/mAP/extra/result.txt: -------------------------------------------------------------------------------- 1 | Total BFLOPS 65.864 2 | 3 | seen 64 4 | Enter Image Path: data/horses.jpg: Predicted in 42.076185 seconds. 5 | horse: 88% (left_x: 3 top_y: 185 width: 150 height: 167) 6 | horse: 99% (left_x: 5 top_y: 198 width: 307 height: 214) 7 | horse: 96% (left_x: 236 top_y: 180 width: 215 height: 169) 8 | horse: 99% (left_x: 440 top_y: 209 width: 156 height: 142) 9 | Enter Image Path: data/person.jpg: Predicted in 41.767213 seconds. 10 | dog: 99% (left_x: 58 top_y: 262 width: 147 height: 89) 11 | person: 100% (left_x: 190 top_y: 95 width: 86 height: 284) 12 | horse: 100% (left_x: 394 top_y: 137 width: 215 height: 206) 13 | Enter Image Path: -------------------------------------------------------------------------------- /code/mAP/mAP.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import matplotlib\n", 10 | "%matplotlib inline" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 3, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "59.65% = bicycle AP \n", 23 | "24.69% = boat AP \n", 24 | "42.51% = bottle AP \n", 25 | "76.53% = bus AP \n", 26 | "38.38% = car AP \n", 27 | "0.33% = cat AP \n", 28 | "0.22% = chair AP \n", 29 | "1.63% = cup AP \n", 30 | "1.51% = diningtable AP \n", 31 | "0.31% = dog AP \n", 32 | "0.21% = motorbike AP \n", 33 | "10.25% = person AP \n", 34 | "mAP = 21.35%\n", 35 | "Figure(640x480)\n" 36 | ] 37 | } 38 | ], 39 | "source": [ 40 | "#original images results at iou=0.5 conf_thres=0.25\n", 41 | "!python main.py" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 4, 47 | "metadata": {}, 48 | "outputs": [ 49 | { 50 | "name": "stdout", 51 | "output_type": "stream", 52 | "text": [ 53 | "58.55% = bicycle AP \n", 54 | "23.77% = boat AP \n", 55 | "42.31% = bottle AP \n", 56 | "72.54% = bus AP \n", 57 | "36.09% = car AP \n", 58 | "0.24% = cat AP \n", 59 | "0.23% = chair AP \n", 60 | "1.57% = cup AP \n", 61 | "1.11% = diningtable AP \n", 62 | "0.28% = dog AP \n", 63 | "0.19% = motorbike AP \n", 64 | "8.96% = person AP \n", 65 | "mAP = 20.49%\n", 66 | "Figure(640x480)\n" 67 | ] 68 | } 69 | ], 70 | "source": [ 71 | "#ying images results at iou=0.5 conf_thres=0.25\n", 72 | "!python main.py" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 1, 78 | "metadata": {}, 79 | "outputs": [ 80 | { 81 | "name": "stdout", 82 | "output_type": "stream", 83 | "text": [ 84 | "57.30% = bicycle AP \n", 85 | "28.06% = boat AP \n", 86 | "37.26% = bottle AP \n", 87 | "69.00% = bus AP \n", 88 | "32.14% = car AP \n", 89 | "0.24% = cat AP \n", 90 | "0.23% = chair AP \n", 91 | "1.50% = cup AP \n", 92 | "1.56% = diningtable AP \n", 93 | "0.21% = dog AP \n", 94 | "0.22% = motorbike AP \n", 95 | "8.61% = person AP \n", 96 | "mAP = 19.69%\n", 97 | "Figure(640x480)\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "#histogram equalization results\n", 103 | "!python main.py" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 2, 109 | "metadata": {}, 110 | "outputs": [ 111 | { 112 | "name": "stdout", 113 | "output_type": "stream", 114 | "text": [ 115 | "59.20% = bicycle AP \n", 116 | "25.17% = boat AP \n", 117 | "43.34% = bottle AP \n", 118 | "73.63% = bus AP \n", 119 | "37.11% = car AP \n", 120 | "0.23% = cat AP \n", 121 | "0.25% = chair AP \n", 122 | "1.68% = cup AP \n", 123 | "1.25% = diningtable AP \n", 124 | "0.30% = dog AP \n", 125 | "0.19% = motorbike AP \n", 126 | "9.31% = person AP \n", 127 | "mAP = 20.97%\n", 128 | "Figure(640x480)\n" 129 | ] 130 | } 131 | ], 132 | "source": [ 133 | "#dynamic histogram equalization results\n", 134 | "!python main.py" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [] 143 | } 144 | ], 145 | "metadata": { 146 | "kernelspec": { 147 | "display_name": "Python 3", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.6.7" 162 | } 163 | }, 164 | "nbformat": 4, 165 | "nbformat_minor": 2 166 | } 167 | -------------------------------------------------------------------------------- /code/mAP/main.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import os 4 | import shutil 5 | import operator 6 | import sys 7 | import argparse 8 | 9 | if os.getcwd().split("\\")[-1]== 'YOLOV3': 10 | os.chdir('..\mAP') 11 | 12 | MINOVERLAP = 0.5 # default value (defined in the PASCAL VOC2012 challenge) 13 | 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument('-na', '--no-animation', help="no animation is shown.", action="store_true") 16 | parser.add_argument('-np', '--no-plot', help="no plot is shown.", action="store_true") 17 | parser.add_argument('-q', '--quiet', help="minimalistic console output.", action="store_true") 18 | # argparse receiving list of classes to be ignored 19 | parser.add_argument('-i', '--ignore', nargs='+', type=str, help="ignore a list of classes.") 20 | # argparse receiving list of classes with specific IoU 21 | parser.add_argument('--set-class-iou', nargs='+', type=str, help="set IoU for a specific class.") 22 | args = parser.parse_args() 23 | 24 | # if there are no classes to ignore then replace None by empty list 25 | if args.ignore is None: 26 | args.ignore = [] 27 | 28 | specific_iou_flagged = False 29 | if args.set_class_iou is not None: 30 | specific_iou_flagged = True 31 | 32 | # if there are no images then no animation can be shown 33 | img_path = 'images' 34 | if os.path.exists(img_path): 35 | for dirpath, dirnames, files in os.walk(img_path): 36 | if not files: 37 | # no image files found 38 | args.no_animation = True 39 | else: 40 | args.no_animation = True 41 | 42 | # try to import OpenCV if the user didn't choose the option --no-animation 43 | show_animation = False 44 | if not args.no_animation: 45 | try: 46 | import cv2 47 | show_animation = True 48 | except ImportError: 49 | print("\"opencv-python\" not found, please install to visualize the results.") 50 | args.no_animation = True 51 | 52 | # try to import Matplotlib if the user didn't choose the option --no-plot 53 | draw_plot = False 54 | if not args.no_plot: 55 | try: 56 | import matplotlib.pyplot as plt 57 | draw_plot = True 58 | except ImportError: 59 | print("\"matplotlib\" not found, please install it to get the resulting plots.") 60 | args.no_plot = True 61 | 62 | """ 63 | throw error and exit 64 | """ 65 | def error(msg): 66 | print(msg) 67 | sys.exit(0) 68 | 69 | """ 70 | check if the number is a float between 0.0 and 1.0 71 | """ 72 | def is_float_between_0_and_1(value): 73 | try: 74 | val = float(value) 75 | if val > 0.0 and val < 1.0: 76 | return True 77 | else: 78 | return False 79 | except ValueError: 80 | return False 81 | 82 | """ 83 | Calculate the AP given the recall and precision array 84 | 1st) We compute a version of the measured precision/recall curve with 85 | precision monotonically decreasing 86 | 2nd) We compute the AP as the area under this curve by numerical integration. 87 | """ 88 | def voc_ap(rec, prec): 89 | """ 90 | --- Official matlab code VOC2012--- 91 | mrec=[0 ; rec ; 1]; 92 | mpre=[0 ; prec ; 0]; 93 | for i=numel(mpre)-1:-1:1 94 | mpre(i)=max(mpre(i),mpre(i+1)); 95 | end 96 | i=find(mrec(2:end)~=mrec(1:end-1))+1; 97 | ap=sum((mrec(i)-mrec(i-1)).*mpre(i)); 98 | """ 99 | rec.insert(0, 0.0) # insert 0.0 at begining of list 100 | rec.append(1.0) # insert 1.0 at end of list 101 | mrec = rec[:] 102 | prec.insert(0, 0.0) # insert 0.0 at begining of list 103 | prec.append(0.0) # insert 0.0 at end of list 104 | mpre = prec[:] 105 | """ 106 | This part makes the precision monotonically decreasing 107 | (goes from the end to the beginning) 108 | matlab: for i=numel(mpre)-1:-1:1 109 | mpre(i)=max(mpre(i),mpre(i+1)); 110 | """ 111 | # matlab indexes start in 1 but python in 0, so I have to do: 112 | # range(start=(len(mpre) - 2), end=0, step=-1) 113 | # also the python function range excludes the end, resulting in: 114 | # range(start=(len(mpre) - 2), end=-1, step=-1) 115 | for i in range(len(mpre)-2, -1, -1): 116 | mpre[i] = max(mpre[i], mpre[i+1]) 117 | """ 118 | This part creates a list of indexes where the recall changes 119 | matlab: i=find(mrec(2:end)~=mrec(1:end-1))+1; 120 | """ 121 | i_list = [] 122 | for i in range(1, len(mrec)): 123 | if mrec[i] != mrec[i-1]: 124 | i_list.append(i) # if it was matlab would be i + 1 125 | """ 126 | The Average Precision (AP) is the area under the curve 127 | (numerical integration) 128 | matlab: ap=sum((mrec(i)-mrec(i-1)).*mpre(i)); 129 | """ 130 | ap = 0.0 131 | for i in i_list: 132 | ap += ((mrec[i]-mrec[i-1])*mpre[i]) 133 | return ap, mrec, mpre 134 | 135 | 136 | """ 137 | Convert the lines of a file to a list 138 | """ 139 | def file_lines_to_list(path): 140 | # open txt file lines to a list 141 | with open(path) as f: 142 | content = f.readlines() 143 | # remove whitespace characters like `\n` at the end of each line 144 | content = [x.strip() for x in content] 145 | return content 146 | 147 | """ 148 | Draws text in image 149 | """ 150 | def draw_text_in_image(img, text, pos, color, line_width): 151 | font = cv2.FONT_HERSHEY_PLAIN 152 | fontScale = 1 153 | lineType = 1 154 | bottomLeftCornerOfText = pos 155 | cv2.putText(img, text, 156 | bottomLeftCornerOfText, 157 | font, 158 | fontScale, 159 | color, 160 | lineType) 161 | text_width, _ = cv2.getTextSize(text, font, fontScale, lineType)[0] 162 | return img, (line_width + text_width) 163 | 164 | """ 165 | Plot - adjust axes 166 | """ 167 | def adjust_axes(r, t, fig, axes): 168 | # get text width for re-scaling 169 | bb = t.get_window_extent(renderer=r) 170 | text_width_inches = bb.width / fig.dpi 171 | # get axis width in inches 172 | current_fig_width = fig.get_figwidth() 173 | new_fig_width = current_fig_width + text_width_inches 174 | propotion = new_fig_width / current_fig_width 175 | # get axis limit 176 | x_lim = axes.get_xlim() 177 | axes.set_xlim([x_lim[0], x_lim[1]*propotion]) 178 | 179 | """ 180 | Draw plot using Matplotlib 181 | """ 182 | def draw_plot_func(dictionary, n_classes, window_title, plot_title, x_label, output_path, to_show, plot_color, true_p_bar): 183 | # sort the dictionary by decreasing value, into a list of tuples 184 | sorted_dic_by_value = sorted(dictionary.items(), key=operator.itemgetter(1)) 185 | # unpacking the list of tuples into two lists 186 | sorted_keys, sorted_values = zip(*sorted_dic_by_value) 187 | # 188 | if true_p_bar != "": 189 | """ 190 | Special case to draw in (green=true predictions) & (red=false predictions) 191 | """ 192 | fp_sorted = [] 193 | tp_sorted = [] 194 | for key in sorted_keys: 195 | fp_sorted.append(dictionary[key] - true_p_bar[key]) 196 | tp_sorted.append(true_p_bar[key]) 197 | plt.barh(range(n_classes), fp_sorted, align='center', color='crimson', label='False Predictions') 198 | plt.barh(range(n_classes), tp_sorted, align='center', color='forestgreen', label='True Predictions', left=fp_sorted) 199 | # add legend 200 | plt.legend(loc='lower right') 201 | """ 202 | Write number on side of bar 203 | """ 204 | fig = plt.gcf() # gcf - get current figure 205 | axes = plt.gca() 206 | r = fig.canvas.get_renderer() 207 | for i, val in enumerate(sorted_values): 208 | fp_val = fp_sorted[i] 209 | tp_val = tp_sorted[i] 210 | fp_str_val = " " + str(fp_val) 211 | tp_str_val = fp_str_val + " " + str(tp_val) 212 | # trick to paint multicolor with offset: 213 | # first paint everything and then repaint the first number 214 | t = plt.text(val, i, tp_str_val, color='forestgreen', va='center', fontweight='bold') 215 | plt.text(val, i, fp_str_val, color='crimson', va='center', fontweight='bold') 216 | if i == (len(sorted_values)-1): # largest bar 217 | adjust_axes(r, t, fig, axes) 218 | else: 219 | plt.barh(range(n_classes), sorted_values, color=plot_color) 220 | """ 221 | Write number on side of bar 222 | """ 223 | fig = plt.gcf() # gcf - get current figure 224 | axes = plt.gca() 225 | r = fig.canvas.get_renderer() 226 | for i, val in enumerate(sorted_values): 227 | str_val = " " + str(val) # add a space before 228 | if val < 1.0: 229 | str_val = " {0:.2f}".format(val) 230 | t = plt.text(val, i, str_val, color=plot_color, va='center', fontweight='bold') 231 | # re-set axes to show number inside the figure 232 | if i == (len(sorted_values)-1): # largest bar 233 | adjust_axes(r, t, fig, axes) 234 | # set window title 235 | fig.canvas.set_window_title(window_title) 236 | # write classes in y axis 237 | tick_font_size = 12 238 | plt.yticks(range(n_classes), sorted_keys, fontsize=tick_font_size) 239 | """ 240 | Re-scale height accordingly 241 | """ 242 | init_height = fig.get_figheight() 243 | # comput the matrix height in points and inches 244 | dpi = fig.dpi 245 | height_pt = n_classes * (tick_font_size * 1.4) # 1.4 (some spacing) 246 | height_in = height_pt / dpi 247 | # compute the required figure height 248 | top_margin = 0.15 # in percentage of the figure height 249 | bottom_margin = 0.05 # in percentage of the figure height 250 | figure_height = height_in / (1 - top_margin - bottom_margin) 251 | # set new height 252 | if figure_height > init_height: 253 | fig.set_figheight(figure_height) 254 | 255 | # set plot title 256 | plt.title(plot_title, fontsize=14) 257 | # set axis titles 258 | # plt.xlabel('classes') 259 | plt.xlabel(x_label, fontsize='large') 260 | # adjust size of window 261 | fig.tight_layout() 262 | # save the plot 263 | fig.savefig(output_path) 264 | # show image 265 | if to_show: 266 | plt.show() 267 | # close the plot 268 | plt.close() 269 | 270 | """ 271 | Create a "tmp_files/" and "results/" directory 272 | """ 273 | tmp_files_path = "tmp_files" 274 | if not os.path.exists(tmp_files_path): # if it doesn't exist already 275 | os.makedirs(tmp_files_path) 276 | results_files_path = "results" 277 | if os.path.exists(results_files_path): # if it exist already 278 | # reset the results directory 279 | shutil.rmtree(results_files_path) 280 | 281 | os.makedirs(results_files_path) 282 | if draw_plot: 283 | os.makedirs(results_files_path + "/classes") 284 | if show_animation: 285 | os.makedirs(results_files_path + "/images") 286 | os.makedirs(results_files_path + "/images/single_predictions") 287 | 288 | """ 289 | Ground-Truth 290 | Load each of the ground-truth files into a temporary ".json" file. 291 | Create a list of all the class names present in the ground-truth (gt_classes). 292 | """ 293 | # get a list with the ground-truth files 294 | ground_truth_files_list = glob.glob('ground-truth/*.txt') 295 | if len(ground_truth_files_list) == 0: 296 | error("Error: No ground-truth files found!") 297 | ground_truth_files_list.sort() 298 | # dictionary with counter per class 299 | gt_counter_per_class = {} 300 | 301 | for txt_file in ground_truth_files_list: 302 | #print(txt_file) 303 | file_id = txt_file.split(".txt",1)[0] 304 | file_id = os.path.basename(os.path.normpath(file_id)) 305 | # check if there is a correspondent predicted objects file 306 | if not os.path.exists('predicted/' + file_id + ".txt"): 307 | error_msg = "Error. File not found: predicted/" + file_id + ".txt\n" 308 | error_msg += "(You can avoid this error message by running extra/intersect-gt-and-pred.py)" 309 | error(error_msg) 310 | lines_list = file_lines_to_list(txt_file) 311 | # create ground-truth dictionary 312 | bounding_boxes = [] 313 | is_difficult = False 314 | for line in lines_list: 315 | try: 316 | if "difficult" in line: 317 | class_name, left, top, right, bottom, _difficult = line.split() 318 | is_difficult = True 319 | else: 320 | class_name, left, top, right, bottom = line.split() 321 | except ValueError: 322 | error_msg = "Error: File " + txt_file + " in the wrong format.\n" 323 | error_msg += " Expected: ['difficult']\n" 324 | error_msg += " Received: " + line 325 | error_msg += "\n\nIf you have a with spaces between words you should remove them\n" 326 | error_msg += "by running the script \"remove_space.py\" or \"rename_class.py\" in the \"extra/\" folder." 327 | error(error_msg) 328 | # check if class is in the ignore list, if yes skip 329 | if class_name in args.ignore: 330 | continue 331 | bbox = left + " " + top + " " + right + " " +bottom 332 | if is_difficult: 333 | bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False, "difficult":True}) 334 | is_difficult = False 335 | else: 336 | bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False}) 337 | # count that object 338 | if class_name in gt_counter_per_class: 339 | gt_counter_per_class[class_name] += 1 340 | else: 341 | # if class didn't exist yet 342 | gt_counter_per_class[class_name] = 1 343 | # dump bounding_boxes into a ".json" file 344 | with open(tmp_files_path + "/" + file_id + "_ground_truth.json", 'w') as outfile: 345 | json.dump(bounding_boxes, outfile) 346 | 347 | gt_classes = list(gt_counter_per_class.keys()) 348 | # let's sort the classes alphabetically 349 | gt_classes = sorted(gt_classes) 350 | n_classes = len(gt_classes) 351 | #print(gt_classes) 352 | #print(gt_counter_per_class) 353 | 354 | """ 355 | Check format of the flag --set-class-iou (if used) 356 | e.g. check if class exists 357 | """ 358 | if specific_iou_flagged: 359 | n_args = len(args.set_class_iou) 360 | error_msg = \ 361 | '\n --set-class-iou [class_1] [IoU_1] [class_2] [IoU_2] [...]' 362 | if n_args % 2 != 0: 363 | error('Error, missing arguments. Flag usage:' + error_msg) 364 | # [class_1] [IoU_1] [class_2] [IoU_2] 365 | # specific_iou_classes = ['class_1', 'class_2'] 366 | specific_iou_classes = args.set_class_iou[::2] # even 367 | # iou_list = ['IoU_1', 'IoU_2'] 368 | iou_list = args.set_class_iou[1::2] # odd 369 | if len(specific_iou_classes) != len(iou_list): 370 | error('Error, missing arguments. Flag usage:' + error_msg) 371 | for tmp_class in specific_iou_classes: 372 | if tmp_class not in gt_classes: 373 | error('Error, unknown class \"' + tmp_class + '\". Flag usage:' + error_msg) 374 | for num in iou_list: 375 | if not is_float_between_0_and_1(num): 376 | error('Error, IoU must be between 0.0 and 1.0. Flag usage:' + error_msg) 377 | 378 | """ 379 | Predicted 380 | Load each of the predicted files into a temporary ".json" file. 381 | """ 382 | # get a list with the predicted files 383 | predicted_files_list = glob.glob('predicted/*.txt') 384 | predicted_files_list.sort() 385 | 386 | for class_index, class_name in enumerate(gt_classes): 387 | bounding_boxes = [] 388 | for txt_file in predicted_files_list: 389 | #print(txt_file) 390 | # the first time it checks if all the corresponding ground-truth files exist 391 | file_id = txt_file.split(".txt",1)[0] 392 | file_id = os.path.basename(os.path.normpath(file_id)) 393 | if class_index == 0: 394 | if not os.path.exists('ground-truth/' + file_id + ".txt"): 395 | error_msg = "Error. File not found: ground-truth/" + file_id + ".txt\n" 396 | error_msg += "(You can avoid this error message by running extra/intersect-gt-and-pred.py)" 397 | error(error_msg) 398 | lines = file_lines_to_list(txt_file) 399 | for line in lines: 400 | try: 401 | tmp_class_name, confidence, left, top, right, bottom = line.split() 402 | except ValueError: 403 | error_msg = "Error: File " + txt_file + " in the wrong format.\n" 404 | error_msg += " Expected: \n" 405 | error_msg += " Received: " + line 406 | error(error_msg) 407 | if tmp_class_name == class_name: 408 | #print("match") 409 | bbox = left + " " + top + " " + right + " " +bottom 410 | bounding_boxes.append({"confidence":confidence, "file_id":file_id, "bbox":bbox}) 411 | #print(bounding_boxes) 412 | # sort predictions by decreasing confidence 413 | bounding_boxes.sort(key=lambda x:float(x['confidence']), reverse=True) 414 | with open(tmp_files_path + "/" + class_name + "_predictions.json", 'w') as outfile: 415 | json.dump(bounding_boxes, outfile) 416 | 417 | """ 418 | Calculate the AP for each class 419 | """ 420 | sum_AP = 0.0 421 | ap_dictionary = {} 422 | # open file to store the results 423 | with open(results_files_path + "/results.txt", 'w') as results_file: 424 | results_file.write("# AP and precision/recall per class\n") 425 | count_true_positives = {} 426 | for class_index, class_name in enumerate(gt_classes): 427 | count_true_positives[class_name] = 0 428 | """ 429 | Load predictions of that class 430 | """ 431 | predictions_file = tmp_files_path + "/" + class_name + "_predictions.json" 432 | predictions_data = json.load(open(predictions_file)) 433 | 434 | """ 435 | Assign predictions to ground truth objects 436 | """ 437 | nd = len(predictions_data) 438 | tp = [0] * nd # creates an array of zeros of size nd 439 | fp = [0] * nd 440 | for idx, prediction in enumerate(predictions_data): 441 | file_id = prediction["file_id"] 442 | if show_animation: 443 | # find ground truth image 444 | ground_truth_img = glob.glob1(img_path, file_id + ".*") 445 | #tifCounter = len(glob.glob1(myPath,"*.tif")) 446 | if len(ground_truth_img) == 0: 447 | error("Error. Image not found with id: " + file_id) 448 | elif len(ground_truth_img) > 1: 449 | error("Error. Multiple image with id: " + file_id) 450 | else: # found image 451 | #print(img_path + "/" + ground_truth_img[0]) 452 | # Load image 453 | img = cv2.imread(img_path + "/" + ground_truth_img[0]) 454 | # load image with draws of multiple detections 455 | img_cumulative_path = results_files_path + "/images/" + ground_truth_img[0] 456 | if os.path.isfile(img_cumulative_path): 457 | img_cumulative = cv2.imread(img_cumulative_path) 458 | else: 459 | img_cumulative = img.copy() 460 | # Add bottom border to image 461 | bottom_border = 60 462 | BLACK = [0, 0, 0] 463 | img = cv2.copyMakeBorder(img, 0, bottom_border, 0, 0, cv2.BORDER_CONSTANT, value=BLACK) 464 | # assign prediction to ground truth object if any 465 | # open ground-truth with that file_id 466 | gt_file = tmp_files_path + "/" + file_id + "_ground_truth.json" 467 | ground_truth_data = json.load(open(gt_file)) 468 | ovmax = -1 469 | gt_match = -1 470 | # load prediction bounding-box 471 | bb = [ float(x) for x in prediction["bbox"].split() ] 472 | for obj in ground_truth_data: 473 | # look for a class_name match 474 | if obj["class_name"] == class_name: 475 | bbgt = [ float(x) for x in obj["bbox"].split() ] 476 | bi = [max(bb[0],bbgt[0]), max(bb[1],bbgt[1]), min(bb[2],bbgt[2]), min(bb[3],bbgt[3])] 477 | iw = bi[2] - bi[0] + 1 478 | ih = bi[3] - bi[1] + 1 479 | if iw > 0 and ih > 0: 480 | # compute overlap (IoU) = area of intersection / area of union 481 | ua = (bb[2] - bb[0] + 1) * (bb[3] - bb[1] + 1) + (bbgt[2] - bbgt[0] 482 | + 1) * (bbgt[3] - bbgt[1] + 1) - iw * ih 483 | ov = iw * ih / ua 484 | if ov > ovmax: 485 | ovmax = ov 486 | gt_match = obj 487 | 488 | # assign prediction as true positive/don't care/false positive 489 | if show_animation: 490 | status = "NO MATCH FOUND!" # status is only used in the animation 491 | # set minimum overlap 492 | min_overlap = MINOVERLAP 493 | if specific_iou_flagged: 494 | if class_name in specific_iou_classes: 495 | index = specific_iou_classes.index(class_name) 496 | min_overlap = float(iou_list[index]) 497 | if ovmax >= min_overlap: 498 | if "difficult" not in gt_match: 499 | if not bool(gt_match["used"]): 500 | # true positive 501 | tp[idx] = 1 502 | gt_match["used"] = True 503 | count_true_positives[class_name] += 1 504 | # update the ".json" file 505 | with open(gt_file, 'w') as f: 506 | f.write(json.dumps(ground_truth_data)) 507 | if show_animation: 508 | status = "MATCH!" 509 | else: 510 | # false positive (multiple detection) 511 | fp[idx] = 1 512 | if show_animation: 513 | status = "REPEATED MATCH!" 514 | else: 515 | # false positive 516 | fp[idx] = 1 517 | if ovmax > 0: 518 | status = "INSUFFICIENT OVERLAP" 519 | 520 | """ 521 | Draw image to show animation 522 | """ 523 | if show_animation: 524 | height, widht = img.shape[:2] 525 | # colors (OpenCV works with BGR) 526 | white = (255,255,255) 527 | light_blue = (255,200,100) 528 | green = (0,255,0) 529 | light_red = (30,30,255) 530 | # 1st line 531 | margin = 10 532 | v_pos = int(height - margin - (bottom_border / 2)) 533 | text = "Image: " + ground_truth_img[0] + " " 534 | img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0) 535 | text = "Class [" + str(class_index) + "/" + str(n_classes) + "]: " + class_name + " " 536 | img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), light_blue, line_width) 537 | if ovmax != -1: 538 | color = light_red 539 | if status == "INSUFFICIENT OVERLAP": 540 | text = "IoU: {0:.2f}% ".format(ovmax*100) + "< {0:.2f}% ".format(min_overlap*100) 541 | else: 542 | text = "IoU: {0:.2f}% ".format(ovmax*100) + ">= {0:.2f}% ".format(min_overlap*100) 543 | color = green 544 | img, _ = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width) 545 | # 2nd line 546 | v_pos += int(bottom_border / 2) 547 | rank_pos = str(idx+1) # rank position (idx starts at 0) 548 | text = "Prediction #rank: " + rank_pos + " confidence: {0:.2f}% ".format(float(prediction["confidence"])*100) 549 | img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0) 550 | color = light_red 551 | if status == "MATCH!": 552 | color = green 553 | text = "Result: " + status + " " 554 | img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width) 555 | 556 | font = cv2.FONT_HERSHEY_SIMPLEX 557 | if ovmax > 0: # if there is intersections between the bounding-boxes 558 | bbgt = [ int(x) for x in gt_match["bbox"].split() ] 559 | cv2.rectangle(img,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2) 560 | cv2.rectangle(img_cumulative,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2) 561 | cv2.putText(img_cumulative, class_name, (bbgt[0],bbgt[1] - 5), font, 0.6, light_blue, 1, cv2.LINE_AA) 562 | bb = [int(i) for i in bb] 563 | cv2.rectangle(img,(bb[0],bb[1]),(bb[2],bb[3]),color,2) 564 | cv2.rectangle(img_cumulative,(bb[0],bb[1]),(bb[2],bb[3]),color,2) 565 | cv2.putText(img_cumulative, class_name, (bb[0],bb[1] - 5), font, 0.6, color, 1, cv2.LINE_AA) 566 | # show image 567 | cv2.imshow("Animation", img) 568 | cv2.waitKey(20) # show for 20 ms 569 | # save image to results 570 | output_img_path = results_files_path + "/images/single_predictions/" + class_name + "_prediction" + str(idx) + ".jpg" 571 | cv2.imwrite(output_img_path, img) 572 | # save the image with all the objects drawn to it 573 | cv2.imwrite(img_cumulative_path, img_cumulative) 574 | 575 | #print(tp) 576 | # compute precision/recall 577 | cumsum = 0 578 | for idx, val in enumerate(fp): 579 | fp[idx] += cumsum 580 | cumsum += val 581 | cumsum = 0 582 | for idx, val in enumerate(tp): 583 | tp[idx] += cumsum 584 | cumsum += val 585 | #print(tp) 586 | rec = tp[:] 587 | for idx, val in enumerate(tp): 588 | rec[idx] = float(tp[idx]) / gt_counter_per_class[class_name] 589 | #print(rec) 590 | prec = tp[:] 591 | for idx, val in enumerate(tp): 592 | prec[idx] = float(tp[idx]) / (fp[idx] + tp[idx]) 593 | #print(prec) 594 | 595 | ap, mrec, mprec = voc_ap(rec, prec) 596 | sum_AP += ap 597 | text = "{0:.2f}%".format(ap*100) + " = " + class_name + " AP " #class_name + " AP = {0:.2f}%".format(ap*100) 598 | """ 599 | Write to results.txt 600 | """ 601 | rounded_prec = [ '%.2f' % elem for elem in prec ] 602 | rounded_rec = [ '%.2f' % elem for elem in rec ] 603 | results_file.write(text + "\n Precision: " + str(rounded_prec) + "\n Recall :" + str(rounded_rec) + "\n\n") 604 | if not args.quiet: 605 | print(text) 606 | ap_dictionary[class_name] = ap 607 | 608 | """ 609 | Draw plot 610 | """ 611 | if draw_plot: 612 | plt.plot(rec, prec, '-o') 613 | # add a new penultimate point to the list (mrec[-2], 0.0) 614 | # since the last line segment (and respective area) do not affect the AP value 615 | area_under_curve_x = mrec[:-1] + [mrec[-2]] + [mrec[-1]] 616 | area_under_curve_y = mprec[:-1] + [0.0] + [mprec[-1]] 617 | plt.fill_between(area_under_curve_x, 0, area_under_curve_y, alpha=0.2, edgecolor='r') 618 | # set window title 619 | fig = plt.gcf() # gcf - get current figure 620 | fig.canvas.set_window_title('AP ' + class_name) 621 | # set plot title 622 | plt.title('class: ' + text) 623 | #plt.suptitle('This is a somewhat long figure title', fontsize=16) 624 | # set axis titles 625 | plt.xlabel('Recall') 626 | plt.ylabel('Precision') 627 | # optional - set axes 628 | axes = plt.gca() # gca - get current axes 629 | axes.set_xlim([0.0,1.0]) 630 | axes.set_ylim([0.0,1.05]) # .05 to give some extra space 631 | # Alternative option -> wait for button to be pressed 632 | #while not plt.waitforbuttonpress(): pass # wait for key display 633 | # Alternative option -> normal display 634 | #plt.show() 635 | # save the plot 636 | fig.savefig(results_files_path + "/classes/" + class_name + ".png") 637 | plt.cla() # clear axes for next plot 638 | 639 | if show_animation: 640 | cv2.destroyAllWindows() 641 | 642 | results_file.write("\n# mAP of all classes\n") 643 | mAP = sum_AP / n_classes 644 | text = "mAP = {0:.2f}%".format(mAP*100) 645 | results_file.write(text + "\n") 646 | print(text) 647 | 648 | # remove the tmp_files directory 649 | shutil.rmtree(tmp_files_path) 650 | 651 | """ 652 | Count total of Predictions 653 | """ 654 | # iterate through all the files 655 | pred_counter_per_class = {} 656 | #all_classes_predicted_files = set([]) 657 | for txt_file in predicted_files_list: 658 | # get lines to list 659 | lines_list = file_lines_to_list(txt_file) 660 | for line in lines_list: 661 | class_name = line.split()[0] 662 | # check if class is in the ignore list, if yes skip 663 | if class_name in args.ignore: 664 | continue 665 | # count that object 666 | if class_name in pred_counter_per_class: 667 | pred_counter_per_class[class_name] += 1 668 | else: 669 | # if class didn't exist yet 670 | pred_counter_per_class[class_name] = 1 671 | #print(pred_counter_per_class) 672 | pred_classes = list(pred_counter_per_class.keys()) 673 | 674 | 675 | """ 676 | Plot the total number of occurences of each class in the ground-truth 677 | """ 678 | if draw_plot: 679 | window_title = "Ground-Truth Info" 680 | plot_title = "Ground-Truth\n" 681 | plot_title += "(" + str(len(ground_truth_files_list)) + " files and " + str(n_classes) + " classes)" 682 | x_label = "Number of objects per class" 683 | output_path = results_files_path + "/Ground-Truth Info.png" 684 | to_show = False 685 | plot_color = 'forestgreen' 686 | draw_plot_func( 687 | gt_counter_per_class, 688 | n_classes, 689 | window_title, 690 | plot_title, 691 | x_label, 692 | output_path, 693 | to_show, 694 | plot_color, 695 | '', 696 | ) 697 | 698 | """ 699 | Write number of ground-truth objects per class to results.txt 700 | """ 701 | with open(results_files_path + "/results.txt", 'a') as results_file: 702 | results_file.write("\n# Number of ground-truth objects per class\n") 703 | for class_name in sorted(gt_counter_per_class): 704 | results_file.write(class_name + ": " + str(gt_counter_per_class[class_name]) + "\n") 705 | 706 | """ 707 | Finish counting true positives 708 | """ 709 | for class_name in pred_classes: 710 | # if class exists in predictions but not in ground-truth then there are no true positives in that class 711 | if class_name not in gt_classes: 712 | count_true_positives[class_name] = 0 713 | #print(count_true_positives) 714 | 715 | """ 716 | Plot the total number of occurences of each class in the "predicted" folder 717 | """ 718 | if draw_plot: 719 | window_title = "Predicted Objects Info" 720 | # Plot title 721 | plot_title = "Predicted Objects\n" 722 | plot_title += "(" + str(len(predicted_files_list)) + " files and " 723 | count_non_zero_values_in_dictionary = sum(int(x) > 0 for x in list(pred_counter_per_class.values())) 724 | plot_title += str(count_non_zero_values_in_dictionary) + " detected classes)" 725 | # end Plot title 726 | x_label = "Number of objects per class" 727 | output_path = results_files_path + "/Predicted Objects Info.png" 728 | to_show = False 729 | plot_color = 'forestgreen' 730 | true_p_bar = count_true_positives 731 | draw_plot_func( 732 | pred_counter_per_class, 733 | len(pred_counter_per_class), 734 | window_title, 735 | plot_title, 736 | x_label, 737 | output_path, 738 | to_show, 739 | plot_color, 740 | true_p_bar 741 | ) 742 | 743 | """ 744 | Write number of predicted objects per class to results.txt 745 | """ 746 | with open(results_files_path + "/results.txt", 'a') as results_file: 747 | results_file.write("\n# Number of predicted objects per class\n") 748 | for class_name in sorted(pred_classes): 749 | n_pred = pred_counter_per_class[class_name] 750 | text = class_name + ": " + str(n_pred) 751 | text += " (tp:" + str(count_true_positives[class_name]) + "" 752 | text += ", fp:" + str(n_pred - count_true_positives[class_name]) + ")\n" 753 | results_file.write(text) 754 | 755 | """ 756 | Draw mAP plot (Show AP's of all classes in decreasing order) 757 | """ 758 | if draw_plot: 759 | window_title = "mAP" 760 | plot_title = "mAP = {0:.2f}%".format(mAP*100) 761 | x_label = "Average Precision" 762 | output_path = results_files_path + "/mAP.png" 763 | to_show = True 764 | plot_color = 'royalblue' 765 | draw_plot_func( 766 | ap_dictionary, 767 | n_classes, 768 | window_title, 769 | plot_title, 770 | x_label, 771 | output_path, 772 | to_show, 773 | plot_color, 774 | "" 775 | ) 776 | --------------------------------------------------------------------------------