def generate_new_anchors(self, new_anchors_conf): """ Create new anchors according to given configuration. Args: new_anchors_conf: A dictionary containing the following keys: - anchors_no and one of the following: - relative_labels - from_xml - adjusted_frame Returns: None """ anchor_no = new_anchors_conf.get('anchor_no') if not anchor_no: raise ValueError(f'No "anchor_no" found in new_anchors_conf') labels_frame = self.get_adjusted_labels(new_anchors_conf) relative_dims = np.array( list( zip( labels_frame['Relative Width'], labels_frame['Relative Height'], ))) centroids, _ = k_means(relative_dims, anchor_no, frame=labels_frame) self.anchors = ( generate_anchors(self.image_width, self.image_height, centroids) / self.input_shape[0]) default_logger.info('Changed default anchors to generated ones')
def create_new_dataset(self, new_dataset_conf): """ Build new dataset and respective TFRecord(s). Args: new_dataset_conf: A dictionary containing the following keys: one of the following: - relative_labels - from_xml - adjusted_frame - coordinate_labels(optional) and: - sequences - workers(optional, defaults to 32) - batch_size(optional, defaults to 64) - new_size(optional, defaults to None) Returns: None """ default_logger.info(f'Generating new dataset ...') test_size = new_dataset_conf.get('test_size') labels_frame = self.generate_new_frame(new_dataset_conf) save_tfr( labels_frame, os.path.join('..', 'Data', 'TFRecords'), new_dataset_conf['dataset_name'], test_size, self, )
def create_new_dataset(self, new_dataset_conf): """ Create a new TFRecord dataset. Args: new_dataset_conf: A dictionary containing the following keys: - dataset_name(required) str representing a name for the dataset - test_size(optional) ex: 0.1 - augmentation(optional) True or False - sequences(required if augmentation is True) - aug_workers(optional if augmentation is True) defaults to 32. - aug_batch_size(optional if augmentation is True) defaults to 64. And one of the following is required: - relative_labels: Path to csv file with the following columns: ['Image', 'Object Name', 'Object Index', 'bx', 'by', 'bw', 'bh'] - coordinate_labels: Path to csv file with the following columns: ['Image Path', 'Object Name', 'Image Width', 'Image Height', 'X_min', 'Y_min', 'X_max', 'Y_max', 'Relative Width', 'Relative Height', 'Object ID'] - from_xml: True or False to parse from XML Labels folder. """ default_logger.info(f'Generating new dataset ...') test_size = new_dataset_conf.get('test_size') labels_frame = self.generate_new_frame(new_dataset_conf) save_tfr( labels_frame, os.path.join('..', 'Data', 'TFRecords'), new_dataset_conf['dataset_name'], test_size, self, )
def relative_to_coordinates(self, out_file=None): """ Convert relative coordinates in self.mapping to coordinates. Args: out_file: path to new converted csv. Returns: pandas DataFrame with the new coordinates. """ items_to_save = [] for index, data in self.mapping.iterrows(): image_name, object_name, object_index, bx, by, bw, bh = data x1, y1, x2, y2 = ratios_to_coordinates(bx, by, bw, bh, self.image_width, self.image_height) items_to_save.append([ image_name, x1, y1, x2, y2, object_name, object_index, bx, by, bw, bh, ]) new_data = pd.DataFrame( items_to_save, columns=[ 'image', 'x1', 'y1', 'x2', 'y2', 'object_type', 'object_id', 'bx', 'by', 'bw', 'bh', ], ) new_data[['x1', 'y1', 'x2', 'y2']] = new_data[['x1', 'y1', 'x2', 'y2']].astype('int64') if out_file: new_data.to_csv(out_file, index=False) default_logger.info( f'Converted labels in {self.labels_file} to coordinates') return new_data
def create_models(self): """ Create training and inference yolo models. Returns: training, inference models """ input_initial = self.apply_func(Input, shape=self.input_shape) cfg_out = self.read_dark_net_cfg() cfg_parser = configparser.ConfigParser() cfg_parser.read_file(cfg_out) self.output_indices = [] self.previous_layer = input_initial for section in cfg_parser.sections(): self.create_section(section, cfg_parser) if len(self.output_indices) == 0: self.output_indices.append(len(self.model_layers) - 1) self.output_layers.extend( [self.model_layers[i] for i in self.output_indices]) if '4' in self.model_configuration: self.output_layers.reverse() self.training_model = Model(inputs=input_initial, outputs=self.output_layers) output_0, output_1, output_2 = self.output_layers boxes_0 = self.apply_func( Lambda, output_0, lambda item: get_boxes(item, self.anchors[self.masks[0]], self. classes), ) boxes_1 = self.apply_func( Lambda, output_1, lambda item: get_boxes(item, self.anchors[self.masks[1]], self. classes), ) boxes_2 = self.apply_func( Lambda, output_2, lambda item: get_boxes(item, self.anchors[self.masks[2]], self. classes), ) outputs = self.apply_func( Lambda, (boxes_0[:3], boxes_1[:3], boxes_2[:3]), lambda item: self.get_nms(item), ) self.inference_model = Model(input_initial, outputs, name='inference_model') default_logger.info('Training and inference models created') return self.training_model, self.inference_model
def parse_voc_folder(folder_path, voc_conf): """ Parse a folder containing voc xml annotation files. Args: folder_path: Folder containing voc xml annotation files. voc_conf: Path to voc json configuration file. Returns: pandas DataFrame with the annotations. """ assert os.path.exists(folder_path) cache_path = os.path.join('..', 'Output', 'Data', 'parsed_from_xml.csv') if os.path.exists(cache_path): frame = pd.read_csv(cache_path) print( f'Labels retrieved from cache:' f'\n{frame["Object Name"].value_counts()}' ) return frame image_data = [] frame_columns = [ 'Image Path', 'Object Name', 'Image Width', 'Image Height', 'X_min', 'Y_min', 'X_max', 'Y_max', ] xml_files = [ file_name for file_name in os.listdir(folder_path) if file_name.endswith('.xml') ] for file_name in xml_files: annotation_path = os.path.join(folder_path, file_name) image_labels = parse_voc_file(annotation_path, voc_conf) image_data.extend(image_labels) frame = pd.DataFrame(image_data, columns=frame_columns) classes = frame['Object Name'].drop_duplicates() default_logger.info(f'Read {len(xml_files)} xml files') default_logger.info( f'Received {len(frame)} labels containing ' f'{len(classes)} classes' ) if frame.empty: raise ValueError( f'No labels were found in {os.path.abspath(folder_path)}' ) frame = adjust_frame(frame, 'parsed_from_xml.csv') return frame
def clear_outputs(): """ Clear Output folder. Returns: None """ for file_name in os.listdir(os.path.join('..', 'Output')): if not file_name.startswith('.'): full_path = (Path(os.path.join( '..', 'Output', file_name)).absolute().resolve()) if os.path.isdir(full_path): shutil.rmtree(full_path) else: os.remove(full_path) default_logger.info(f'Deleted old output: {full_path}')
def detect_video(self, video, trained_weights, codec='mp4v', display=False): """ Perform detection on a video, stream(optional) and save results. Args: video: Path to video file. trained_weights: .tf or .weights file codec: str ex: mp4v display: If True, detections will be displayed during the detection operation. Returns: None """ self.create_models() self.load_weights(trained_weights) vid = cv2.VideoCapture(video) length = int(vid.get(cv2.CAP_PROP_FRAME_COUNT)) width = int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)) fps = int(vid.get(cv2.CAP_PROP_FPS)) current = 1 codec = cv2.VideoWriter_fourcc(*codec) out = os.path.join('..', 'Output', 'Detections', 'predicted_vid.mp4') writer = cv2.VideoWriter(out, codec, fps, (width, height)) while vid.isOpened(): _, frame = vid.read() detections, adjusted = self.detect_image(frame, f'frame_{current}') self.draw_on_image(adjusted, detections) writer.write(adjusted) completed = f'{(current / length) * 100}% completed' print( f'\rframe {current}/{length}\tdetections: ' f'{len(detections)}\tcompleted: {completed}', end='', ) if display: cv2.imshow(f'frame {current}', adjusted) current += 1 if cv2.waitKey(1) == ord('q'): default_logger.info( f'Video detection stopped by user {current}/{length} ' f'frames completed') break
def predict_photos(self, photos, trained_weights, batch_size=32, workers=16): """ Predict a list of image paths and save results to output folder. Args: photos: A list of image paths. trained_weights: .weights or .tf file batch_size: Prediction batch size. workers: Parallel predictions. Returns: None """ self.create_models() self.load_weights(trained_weights) to_predict = photos.copy() with ThreadPoolExecutor(max_workers=workers) as executor: predicted = 1 done = [] total_photos = len(photos) while to_predict: current_batch = [ to_predict.pop() for _ in range(batch_size) if to_predict ] future_predictions = { executor.submit(self.predict_on_image, image): image for image in current_batch } for future_prediction in as_completed(future_predictions): future_prediction.result() completed = f'{predicted}/{total_photos}' current_image = future_predictions[future_prediction] percent = (predicted / total_photos) * 100 print( f'\rpredicting {os.path.basename(current_image)} ' f'{completed}\t{percent}% completed', end='', ) predicted += 1 done.append(current_image) for item in done: default_logger.info(f'Saved prediction: {item}')
def save_fig(title, save_figures=True): """ Save generated figures to Output folder. Args: title: Figure title also the image to save file name. save_figures: If True, figure will be saved Returns: None """ if save_figures: saving_path = str( Path(os.path.join('..', 'Output', 'Plots', f'{title}.png')).absolute().resolve()) if os.path.exists(saving_path): return plt.savefig(saving_path) default_logger.info(f'Saved figure {saving_path}') plt.close()
def save_tfr(data, output_folder, dataset_name, test_size=None, trainer=None): """ Transform and save dataset into TFRecord format. Args: data: pandas DataFrame with adjusted labels. output_folder: Path to folder where TFRecord(s) will be saved. dataset_name: str name of the dataset. test_size: relative test subset size. trainer: Main.Trainer object Returns: None """ data['Object Name'] = data['Object Name'].apply( lambda x: x.encode('utf-8') ) data['Object ID'] = data['Object ID'].astype(int) data[data.dtypes[data.dtypes == 'int64'].index] = data[ data.dtypes[data.dtypes == 'int64'].index ].apply(abs) data.to_csv( os.path.join('..', 'Data', 'TFRecords', 'full_data.csv'), index=False ) groups = np.array(data.groupby('Image Path')) np.random.shuffle(groups) if test_size: assert ( 0 < test_size < 1 ), f'test_size must be 0 < test_size < 1 and {test_size} is given' separation_index = int((1 - test_size) * len(groups)) training_set = groups[:separation_index] test_set = groups[separation_index:] training_frame = pd.concat([item[1] for item in training_set]) test_frame = pd.concat([item[1] for item in test_set]) training_frame.to_csv( os.path.join('..', 'Data', 'TFRecords', 'training_data.csv'), index=False, ) test_frame.to_csv( os.path.join('..', 'Data', 'TFRecords', 'test_data.csv'), index=False, ) training_path = str( Path(os.path.join(output_folder, f'{dataset_name}_train.tfrecord')) .absolute() .resolve() ) test_path = str( Path(os.path.join(output_folder, f'{dataset_name}_test.tfrecord')) .absolute() .resolve() ) write_tf_record(training_path, training_set, data, trainer) default_logger.info(f'Saved training TFRecord: {training_path}') write_tf_record(test_path, test_set, data, trainer) default_logger.info(f'Saved validation TFRecord: {test_path}') return tf_record_path = os.path.join(output_folder, f'{dataset_name}.tfrecord') write_tf_record(tf_record_path, groups, data, trainer) default_logger.info(f'Saved TFRecord {tf_record_path}')
def create_sequences(self, sequences): """ Create sequences for imgaug.augmenters.Sequential(). Args: sequences: A list of dictionaries with each dictionary containing the following: -sequence_group: str, one of self.augmentation_map keys including: ['meta', 'arithmetic', 'artistic', 'blend', 'gaussian_blur', 'color', 'contrast', 'convolution', 'edges', 'flip', 'geometric', 'corrupt_like', 'pi_like', 'pooling', 'segmentation', 'size'] -no: augmentation number ('no' key in self.augmentation_map > chosen sequence_group) Example: sequences = ( [[{'sequence_group': 'meta', 'no': 5}, {'sequence_group': 'arithmetic', 'no': 3}], [{'sequence_group': 'arithmetic', 'no': 2}]] ) Returns: The list of augmentation sequences that will be applied over images. """ total_target = (len(sequences) * len(self.image_paths)) + len( self.image_paths) default_logger.info(f'Total images(old + augmented): {total_target}') for group in sequences: some_ofs = [ self.augmentation_map[item['sequence_group']][item['no'] - 1] for item in group[0] ] one_ofs = [ self.augmentation_map[item['sequence_group']][item['no'] - 1] for item in group[1] ] some_of_aug = [item['augmentation'] for item in some_ofs] one_of_aug = [item['augmentation'] for item in one_ofs] some_of_seq = iaa.SomeOf((0, 5), [eval(item) for item in some_of_aug]) one_of_seq = iaa.OneOf([eval(item) for item in one_of_aug]) self.augmentation_sequences.append( iaa.Sequential([some_of_seq, one_of_seq], random_order=True)) return self.augmentation_sequences
def k_means(relative_sizes, k, distance_func=np.median, frame=None): """ Calculate optimal anchor relative sizes. Args: relative_sizes: 2D array of relative box sizes. k: int, number of clusters. distance_func: function to calculate distance. frame: pandas DataFrame with the annotation data(for visualization purposes). Returns: Optimal relative sizes. """ box_number = relative_sizes.shape[0] last_nearest = np.zeros((box_number,)) centroids = relative_sizes[np.random.randint(0, box_number, k)] old_distances = np.zeros((relative_sizes.shape[0], k)) iteration = 0 while True: distances = 1 - iou(relative_sizes, centroids, k) print( f'Iteration: {iteration} Loss: ' f'{np.sum(np.abs(distances - old_distances))}' ) old_distances = distances.copy() iteration += 1 current_nearest = np.argmin(distances, axis=1) if (last_nearest == current_nearest).all(): default_logger.info( f'Generated {len(centroids)} anchors in ' f'{iteration} iterations' ) return centroids, frame for anchor in range(k): centroids[anchor] = distance_func( relative_sizes[current_nearest == anchor], axis=0 ) last_nearest = current_nearest
def read_tfr( tf_record_file, classes_file, feature_map, max_boxes, classes_delimiter='\n', new_size=None, get_features=False, ): """ Read and load dataset from TFRecord file. Args: tf_record_file: Path to TFRecord file. classes_file: file containing classes. feature_map: A dictionary of feature names mapped to tf.io objects. max_boxes: Maximum number of boxes per image. classes_delimiter: delimiter in classes_file. new_size: w, h new image size get_features: If True, features will be returned. Returns: MapDataset object. """ tf_record_file = str(Path(tf_record_file).absolute().resolve()) text_init = tf.lookup.TextFileInitializer( classes_file, tf.string, 0, tf.int64, -1, delimiter=classes_delimiter ) class_table = tf.lookup.StaticHashTable(text_init, -1) files = tf.data.Dataset.list_files(tf_record_file) dataset = files.flat_map(tf.data.TFRecordDataset) default_logger.info(f'Read TFRecord: {tf_record_file}') return dataset.map( lambda x: read_example( x, feature_map, class_table, max_boxes, new_size, get_features ) )
def train( self, epochs, batch_size, learning_rate, new_anchors_conf=None, new_dataset_conf=None, dataset_name=None, weights=None, evaluate=True, merge_evaluation=True, evaluation_workers=8, shuffle_buffer=512, min_overlaps=None, display_stats=True, plot_stats=True, save_figs=True, clear_outputs=False, n_epoch_eval=None, ): """ Train on the dataset. Args: epochs: Number of training epochs. batch_size: Training batch size. learning_rate: non-negative value. new_anchors_conf: A dictionary containing anchor generation configuration. new_dataset_conf: A dictionary containing dataset generation configuration. dataset_name: Name of the dataset for model checkpoints. weights: .tf or .weights file evaluate: If False, the trained model will not be evaluated after training. merge_evaluation: If False, training and validation maps will be calculated separately. evaluation_workers: Parallel predictions. shuffle_buffer: Buffer size for shuffling datasets. min_overlaps: a float value between 0 and 1, or a dictionary containing each class in self.class_names mapped to its minimum overlap display_stats: If True and evaluate=True, evaluation statistics will be displayed. plot_stats: If True, Precision and recall curves as well as comparative bar charts will be plotted save_figs: If True and plot_stats=True, figures will be saved clear_outputs: If True, old outputs will be cleared n_epoch_eval: Conduct evaluation every n epoch. Returns: history object, pandas DataFrame with statistics, mAP score. """ min_overlaps = min_overlaps or 0.5 if clear_outputs: self.clear_outputs() activate_gpu() default_logger.info(f'Starting training ...') if new_anchors_conf: default_logger.info(f'Generating new anchors ...') self.generate_new_anchors(new_anchors_conf) self.create_models() if weights: self.load_weights(weights) if new_dataset_conf: self.create_new_dataset(new_dataset_conf) self.check_tf_records() training_dataset = self.initialize_dataset(self.train_tf_record, batch_size, shuffle_buffer) valid_dataset = self.initialize_dataset(self.valid_tf_record, batch_size, shuffle_buffer) optimizer = tf.keras.optimizers.Adam(learning_rate) loss = [ calculate_loss(self.anchors[mask], self.classes, self.iou_threshold) for mask in self.masks ] self.training_model.compile(optimizer=optimizer, loss=loss) checkpoint_name = os.path.join( '..', 'Models', f'{dataset_name or "trained"}_model.tf') callbacks = self.create_callbacks(checkpoint_name) if n_epoch_eval: mid_train_eval = MidTrainingEvaluator( self.input_shape, self.classes_file, self.image_width, self.image_height, self.train_tf_record, self.valid_tf_record, self.anchors, self.masks, self.max_boxes, self.iou_threshold, self.score_threshold, n_epoch_eval, merge_evaluation, evaluation_workers, shuffle_buffer, min_overlaps, display_stats, plot_stats, save_figs, checkpoint_name, ) callbacks.append(mid_train_eval) history = self.training_model.fit( training_dataset, epochs=epochs, callbacks=callbacks, validation_data=valid_dataset, ) default_logger.info('Training complete') if evaluate: evaluations = self.evaluate( checkpoint_name, merge_evaluation, evaluation_workers, shuffle_buffer, min_overlaps, display_stats, plot_stats, save_figs, ) return evaluations, history return history
def evaluate( self, weights_file, merge, workers, shuffle_buffer, min_overlaps, display_stats=True, plot_stats=True, save_figs=True, ): """ Evaluate on training and validation datasets. Args: weights_file: Path to trained .tf file. merge: If False, training and validation datasets will be evaluated separately. workers: Parallel predictions. shuffle_buffer: Buffer size for shuffling datasets. min_overlaps: a float value between 0 and 1, or a dictionary containing each class in self.class_names mapped to its minimum overlap display_stats: If True evaluation statistics will be printed. plot_stats: If True, evaluation statistics will be plotted including precision and recall curves and mAP save_figs: If True, resulting plots will be save to Output folder. Returns: stats, map_score. """ default_logger.info('Starting evaluation ...') evaluator = Evaluator( self.input_shape, self.train_tf_record, self.valid_tf_record, self.classes_file, self.anchors, self.masks, self.max_boxes, self.iou_threshold, self.score_threshold, ) predictions = evaluator.make_predictions(weights_file, merge, workers, shuffle_buffer) if isinstance(predictions, tuple): training_predictions, valid_predictions = predictions if any([training_predictions.empty, valid_predictions.empty]): default_logger.info( 'Aborting evaluations, no detections found') return training_actual = pd.read_csv( os.path.join('..', 'Data', 'TFRecords', 'training_data.csv')) valid_actual = pd.read_csv( os.path.join('..', 'Data', 'TFRecords', 'test_data.csv')) training_stats, training_map = evaluator.calculate_map( training_predictions, training_actual, min_overlaps, display_stats, 'Train', save_figs, plot_stats, ) valid_stats, valid_map = evaluator.calculate_map( valid_predictions, valid_actual, min_overlaps, display_stats, 'Valid', save_figs, plot_stats, ) return training_stats, training_map, valid_stats, valid_map actual_data = pd.read_csv( os.path.join('..', 'Data', 'TFRecords', 'full_data.csv')) if predictions.empty: default_logger.info('Aborting evaluations, no detections found') return stats, map_score = evaluator.calculate_map( predictions, actual_data, min_overlaps, display_stats, save_figs=save_figs, plot_results=plot_stats, ) return stats, map_score
def load_weights(self, weights_file): """ Load DarkNet weights or checkpoint/pre-trained weights. Args: weights_file: .weights or .tf file path. Returns: None """ assert weights_file.split('.')[-1] in [ 'tf', 'weights', ], 'Invalid weights file' assert ( self.classes == 80 if weights_file.endswith('.weights') else 1 ), f'DarkNet model should contain 80 classes, {self.classes} is given.' if weights_file.endswith('.tf'): self.training_model.load_weights(weights_file) default_logger.info(f'Loaded weights: {weights_file} ... success') return with open(weights_file, 'rb') as weights_data: default_logger.info(f'Loading pre-trained weights ...') major, minor, revision, seen, _ = np.fromfile(weights_data, dtype=np.int32, count=5) all_layers = [ layer for layer in self.training_model.layers if 'output' not in layer.name ] output_models = [ layer for layer in self.training_model.layers if 'output' in layer.name ] output_layers = [item.layers for item in output_models] for output_item in output_layers: all_layers.extend(output_item) all_layers.sort(key=lambda layer: int(layer.name.split('_')[1])) for i, layer in enumerate(all_layers): current_read = weights_data.tell() total_size = os.fstat(weights_data.fileno()).st_size print( f'\r{round(100 * (current_read / total_size))}%\t{current_read}/{total_size}', end='', ) if 'conv2d' not in layer.name: continue next_layer = all_layers[i + 1] b_norm_layer = (next_layer if 'batch_normalization' in next_layer.name else None) filters = layer.filters kernel_size = layer.kernel_size[0] input_dimension = layer.get_input_shape_at(-1)[-1] convolution_bias = (np.fromfile( weights_data, dtype=np.float32, count=filters) if b_norm_layer is None else None) bn_weights = (np.fromfile( weights_data, dtype=np.float32, count=4 * filters).reshape( (4, filters))[[1, 0, 2, 3]] if (b_norm_layer is not None) else None) convolution_shape = ( filters, input_dimension, kernel_size, kernel_size, ) convolution_weights = (np.fromfile( weights_data, dtype=np.float32, count=np.product(convolution_shape), ).reshape(convolution_shape).transpose([2, 3, 1, 0])) if b_norm_layer is None: try: layer.set_weights( [convolution_weights, convolution_bias]) except ValueError: pass if b_norm_layer is not None: layer.set_weights([convolution_weights]) b_norm_layer.set_weights(bn_weights) assert len(weights_data.read()) == 0, 'failed to read all data' default_logger.info(f'Loaded weights: {weights_file} ... success') print()
def create_models(self): """ Create training and inference yolov3 models. Args: layer configuration. Returns: training, inference models """ input_initial = self.apply_func(Input, shape=self.input_shape) x = self.convolution_block(input_initial, 32, 3, 1, True) x = self.convolution_block(x, 64, 3, 2, True) x = self.convolution_block(x, 32, 1, 1, True, 'append') x = self.convolution_block(x, 64, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 3, 2, True) x = self.convolution_block(x, 64, 1, 1, True, 'append') x = self.convolution_block(x, 128, 3, 1, True, 'add') # x = self.convolution_block(x, 64, 1, 1, True, 'append') x = self.convolution_block(x, 128, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 3, 2, True) x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # x = self.convolution_block(x, 128, 1, 1, True, 'append') x = self.convolution_block(x, 256, 3, 1, True, 'add') # skip_36 = x x = self.convolution_block(x, 512, 3, 2, True) x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # x = self.convolution_block(x, 256, 1, 1, True, 'append') x = self.convolution_block(x, 512, 3, 1, True, 'add') # skip_61 = x x = self.convolution_block(x, 1024, 3, 2, True) x = self.convolution_block(x, 512, 1, 1, True, 'append') x = self.convolution_block(x, 1024, 3, 1, True, 'add') # x = self.convolution_block(x, 512, 1, 1, True, 'append') x = self.convolution_block(x, 1024, 3, 1, True, 'add') # x = self.convolution_block(x, 512, 1, 1, True, 'append') x = self.convolution_block(x, 1024, 3, 1, True, 'add') # x = self.convolution_block(x, 512, 1, 1, True, 'append') x = self.convolution_block(x, 1024, 3, 1, True, 'add') # x = self.convolution_block(x, 512, 1, 1, True) x = self.convolution_block(x, 1024, 3, 1, True) x = self.convolution_block(x, 512, 1, 1, True) x = self.convolution_block(x, 1024, 3, 1, True) x = self.convolution_block(x, 512, 1, 1, True) # detection_0 = x output_0 = self.output(detection_0, 512) x = self.convolution_block(x, 256, 1, 1, True) # x = self.apply_func(UpSampling2D, x, size=2) x = self.apply_func(Concatenate, [x, skip_61]) x = self.convolution_block(x, 256, 1, 1, True) x = self.convolution_block(x, 512, 3, 1, True) x = self.convolution_block(x, 256, 1, 1, True) x = self.convolution_block(x, 512, 3, 1, True) x = self.convolution_block(x, 256, 1, 1, True) # detection_1 = x output_1 = self.output(detection_1, 256) x = self.convolution_block(x, 128, 1, 1, True) # x = self.apply_func(UpSampling2D, x, size=2) x = self.apply_func(Concatenate, [x, skip_36]) x = self.convolution_block(x, 128, 1, 1, True) x = self.convolution_block(x, 256, 3, 1, True) x = self.convolution_block(x, 128, 1, 1, True) x = self.convolution_block(x, 256, 3, 1, True) x = self.convolution_block(x, 128, 1, 1, True) detection_2 = x output_2 = self.output(detection_2, 128) self.training_model = Model( input_initial, [output_0, output_1, output_2], name='training_model', ) boxes_0 = self.apply_func( Lambda, output_0, lambda item: get_boxes(item, self.anchors[self.masks[0]], self. classes), ) boxes_1 = self.apply_func( Lambda, output_1, lambda item: get_boxes(item, self.anchors[self.masks[1]], self. classes), ) boxes_2 = self.apply_func( Lambda, output_2, lambda item: get_boxes(item, self.anchors[self.masks[2]], self. classes), ) outputs = self.apply_func( Lambda, (boxes_0[:3], boxes_1[:3], boxes_2[:3]), lambda item: self.get_nms(item), ) self.inference_model = Model(input_initial, outputs, name='inference_model') default_logger.info('Training and inference models created') return self.training_model, self.inference_model
def adjust_non_voc_csv(csv_file, image_path, image_width, image_height): """ Read relative data and return adjusted frame accordingly. Args: csv_file: .csv file containing the following columns: [Image, Object Name, Object Index, bx, by, bw, bh] image_path: Path prefix to be added. image_width: image width. image_height: image height Returns: pandas DataFrame with the following columns: ['Image Path', 'Object Name', 'Image Width', 'Image Height', 'X_min', 'Y_min', 'X_max', 'Y_max', 'Relative Width', 'Relative Height', 'Object ID'] """ image_path = Path(image_path).absolute().resolve() coordinates = [] old_frame = pd.read_csv(csv_file) new_frame = pd.DataFrame() new_frame['Image Path'] = old_frame['Image'].apply( lambda item: os.path.join(image_path, item) ) new_frame['Object Name'] = old_frame['Object Name'] new_frame['Image Width'] = image_width new_frame['Image Height'] = image_height new_frame['Relative Width'] = old_frame['bw'] new_frame['Relative Height'] = old_frame['bh'] new_frame['Object ID'] = old_frame['Object Index'] + 1 for index, row in old_frame.iterrows(): image, object_name, object_index, bx, by, bw, bh = row co = ratios_to_coordinates(bx, by, bw, bh, image_width, image_height) coordinates.append(co) ( new_frame['X_min'], new_frame['Y_min'], new_frame['X_max'], new_frame['Y_max'], ) = np.array(coordinates).T new_frame[['X_min', 'Y_min', 'X_max', 'Y_max']] = new_frame[ ['X_min', 'Y_min', 'X_max', 'Y_max'] ].astype('int64') print(f'Parsed labels:\n{new_frame["Object Name"].value_counts()}') classes = new_frame['Object Name'].drop_duplicates() default_logger.info( f'Adjustment from existing received {len(new_frame)} labels containing ' f'{len(classes)} classes' ) default_logger.info(f'Added prefix to images: {image_path}') return new_frame[ [ 'Image Path', 'Object Name', 'Image Width', 'Image Height', 'X_min', 'Y_min', 'X_max', 'Y_max', 'Relative Width', 'Relative Height', 'Object ID', ] ]
def augment_photos_folder(self, batch_size=64, new_size=None): """ Augment photos in Data/Photos/ Args: batch_size: Size of each augmentation batch. new_size: tuple, new image size. Returns: None """ default_logger.info( f'Started augmentation with {self.workers} workers') default_logger.info(f'Total images to augment: {self.total_images}') default_logger.info(f'Session assigned id: {self.session_id}') with ThreadPoolExecutor(max_workers=self.workers) as executor: while self.image_paths_copy: current_batch, current_paths = self.load_batch( new_size, batch_size) future_augmentations = { executor.submit(self.augment_image, image, path): path for image, path in zip(current_batch, current_paths) } for future_augmented in as_completed(future_augmentations): future_augmented.result() default_logger.info(f'Augmentation completed') augmentation_frame = pd.DataFrame(self.augmentation_data, columns=self.mapping.columns) saving_path = os.path.join('..', 'Output', 'Data', f'augmented_data_plus_original.csv') combined = pd.concat([self.mapping, augmentation_frame]) for item in ['bx', 'by', 'bw', 'bh']: combined = combined.drop(combined[combined[item] > 1].index) combined.to_csv(saving_path, index=False) default_logger.info(f'Saved old + augmented labels to {saving_path}') adjusted_combined = adjust_non_voc_csv(saving_path, self.image_folder, self.image_width, self.image_height) adjusted_saving_path = saving_path.replace('augmented', 'adjusted_aug') adjusted_combined.to_csv(adjusted_saving_path, index=False) default_logger.info( f'Saved old + augmented (adjusted) labels to {adjusted_saving_path}' ) return adjusted_combined