|
| 1 | +#!/usr/bin/env python |
| 2 | +# coding: utf-8 |
| 3 | +# # Railway obstacle detector with Tensorflow and Keras |
| 4 | +''' |
| 5 | +Last modified on 2019.09.03 |
| 6 | +Author - Kim Minseok (Software department in Soongsil University) |
| 7 | +''' |
| 8 | + |
| 9 | +# ## Import modules |
| 10 | +import numpy as np |
| 11 | +import tensorflow as tf |
| 12 | +import cv2 |
| 13 | +import os |
| 14 | +import matplotlib.image as mpimg |
| 15 | +from tensorflow.python.keras import layers |
| 16 | +from tensorflow.python.keras import losses |
| 17 | +from tensorflow.python.keras import models |
| 18 | +#Util modules |
| 19 | +from object_detection.utils import label_map_util |
| 20 | +import visualization_utils as vis_util |
| 21 | +print('UTILITY MODULE PATH :: vis_util=', vis_util.__file__, ', label_map_util=', label_map_util.__file__) |
| 22 | + |
| 23 | +# ## Set properties |
| 24 | +INPUT_SHAPE = (256, 256, 3) |
| 25 | +IMAGE_PATH = None |
| 26 | +VIDEO_PATH = './complex1.mp4' |
| 27 | +WEIGHT_PATH = './railway_detection/weights.hdf5' |
| 28 | +MODEL_PATH = './railway_detection/model.h5' |
| 29 | +MODEL_NAME = 'inference_graph' |
| 30 | +BATCH_SIZE = 1 |
| 31 | +PATH_TO_CKPT = os.path.join(MODEL_NAME, 'frozen_inference_graph.pb') |
| 32 | +PATH_TO_LABELS = os.path.join('training', 'labelmap.pbtxt') |
| 33 | +NUM_CLASSES = 4 |
| 34 | + |
| 35 | +# ## Model build function |
| 36 | +def conv_block(input_tensor, num_filters): |
| 37 | + encoder = layers.Conv2D(num_filters, (3, 3), padding='same')(input_tensor) |
| 38 | + encoder = layers.BatchNormalization()(encoder) |
| 39 | + encoder = layers.Activation('relu')(encoder) |
| 40 | + encoder = layers.Conv2D(num_filters, (3, 3), padding='same')(encoder) |
| 41 | + encoder = layers.BatchNormalization()(encoder) |
| 42 | + encoder = layers.Activation('relu')(encoder) |
| 43 | + return encoder |
| 44 | + |
| 45 | + |
| 46 | +def encoder_block(input_tensor, num_filters): |
| 47 | + encoder = conv_block(input_tensor, num_filters) |
| 48 | + encoder_pool = layers.MaxPooling2D((2, 2), strides=(2, 2))(encoder) |
| 49 | + return encoder_pool, encoder |
| 50 | + |
| 51 | + |
| 52 | +def decoder_block(input_tensor, concat_tensor, num_filters): |
| 53 | + decoder = layers.Conv2DTranspose(num_filters, (2, 2), strides=(2, 2), padding='same')(input_tensor) |
| 54 | + decoder = layers.concatenate([concat_tensor, decoder], axis=-1) |
| 55 | + decoder = layers.BatchNormalization()(decoder) |
| 56 | + decoder = layers.Activation('relu')(decoder) |
| 57 | + decoder = layers.Conv2D(num_filters, (3, 3), padding='same')(decoder) |
| 58 | + decoder = layers.BatchNormalization()(decoder) |
| 59 | + decoder = layers.Activation('relu')(decoder) |
| 60 | + decoder = layers.Conv2D(num_filters, (3, 3), padding='same')(decoder) |
| 61 | + decoder = layers.BatchNormalization()(decoder) |
| 62 | + decoder = layers.Activation('relu')(decoder) |
| 63 | + return decoder |
| 64 | + |
| 65 | + |
| 66 | +# ## Model compile function |
| 67 | +def dice_coeff(y_true, y_pred): |
| 68 | + smooth = 1. |
| 69 | + # Flatten |
| 70 | + y_true_f = tf.reshape(y_true, [-1]) |
| 71 | + y_pred_f = tf.reshape(y_pred, [-1]) |
| 72 | + intersection = tf.reduce_sum(y_true_f * y_pred_f) |
| 73 | + score = (2. * intersection + smooth) / (tf.reduce_sum(y_true_f) + tf.reduce_sum(y_pred_f) + smooth) |
| 74 | + return score |
| 75 | + |
| 76 | + |
| 77 | +def dice_loss(y_true, y_pred): |
| 78 | + loss = 1 - dice_coeff(y_true, y_pred) |
| 79 | + return loss |
| 80 | + |
| 81 | + |
| 82 | +def bce_dice_loss(y_true, y_pred): |
| 83 | + loss = losses.binary_crossentropy(y_true, y_pred) + dice_loss(y_true, y_pred) |
| 84 | + return loss |
| 85 | + |
| 86 | + |
| 87 | +# ## Build or load model |
| 88 | +if not os.path.exists(MODEL_PATH): |
| 89 | + inputs = layers.Input(shape=INPUT_SHAPE) # 256 |
| 90 | + encoder0_pool, encoder0 = encoder_block(inputs, 32) # 128 |
| 91 | + encoder1_pool, encoder1 = encoder_block(encoder0_pool, 64) # 64 |
| 92 | + encoder2_pool, encoder2 = encoder_block(encoder1_pool, 128) # 32 |
| 93 | + encoder3_pool, encoder3 = encoder_block(encoder2_pool, 256) # 16 |
| 94 | + encoder4_pool, encoder4 = encoder_block(encoder3_pool, 512) # 8 |
| 95 | + center = conv_block(encoder4_pool, 1024) # center |
| 96 | + decoder4 = decoder_block(center, encoder4, 512) # 16 |
| 97 | + decoder3 = decoder_block(decoder4, encoder3, 256) # 32 |
| 98 | + decoder2 = decoder_block(decoder3, encoder2, 128) # 64 |
| 99 | + decoder1 = decoder_block(decoder2, encoder1, 64) # 128 |
| 100 | + decoder0 = decoder_block(decoder1, encoder0, 32) # 256 |
| 101 | + outputs = layers.Conv2D(1, (1, 1), activation='sigmoid')(decoder0) |
| 102 | + model = models.Model(inputs=[inputs], outputs=[outputs]) |
| 103 | + print('New model built') |
| 104 | +else: |
| 105 | + model = models.load_model(MODEL_PATH, custom_objects={'bce_dice_loss': bce_dice_loss, 'dice_loss': dice_loss}) |
| 106 | + print('Model loaded :: ', MODEL_PATH) |
| 107 | + |
| 108 | + |
| 109 | +# ## Compile model |
| 110 | +model.compile(optimizer='adam', loss=bce_dice_loss, metrics=[dice_loss]) |
| 111 | +model.summary() |
| 112 | +print('Model compiled :: opimizer=adam') |
| 113 | + |
| 114 | + |
| 115 | +# ## Show image result |
| 116 | +if IMAGE_PATH is not None: |
| 117 | + test_img =mpimg.imread(IMAGE_PATH) |
| 118 | + print('Image read :: ', IMAGE_PATH) |
| 119 | + squared_img = cv2.resize(test_img, dsize=(256, 256), interpolation=cv2.INTER_AREA) / 255 |
| 120 | + input_img = np.expand_dims(squared_img, axis=0) |
| 121 | + predicted_label = model.predict(input_img)[0] |
| 122 | + print('predicted_Iabel image made') |
| 123 | + resize_img = cv2.resize(predicted_label,(test_img.shape[1], test_img.shape[0])) |
| 124 | + cv2.imshow('image', resize_img) |
| 125 | + cv2.waitKey(0) |
| 126 | + |
| 127 | + |
| 128 | +# # Obstacle Detection Model |
| 129 | +# ## Load lable map |
| 130 | +# Load the label map. |
| 131 | +label_map = label_map_util.load_labelmap(PATH_TO_LABELS) |
| 132 | +categories = label_map_util.convert_label_map_to_categories( |
| 133 | + label_map, |
| 134 | + max_num_classes=NUM_CLASSES, |
| 135 | + use_display_name=True) |
| 136 | +category_index = label_map_util.create_category_index(categories) |
| 137 | + |
| 138 | + |
| 139 | +# ## Load the tf model into memory |
| 140 | +detection_graph = tf.Graph() |
| 141 | +with detection_graph.as_default(): |
| 142 | + od_graph_def = tf.GraphDef() |
| 143 | + with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid: |
| 144 | + serialized_graph = fid.read() |
| 145 | + od_graph_def.ParseFromString(serialized_graph) |
| 146 | + tf.import_graph_def(od_graph_def, name='') |
| 147 | + |
| 148 | + sess = tf.Session(graph=detection_graph) |
| 149 | + |
| 150 | +# Input tensor is the image. Output tensors are the detection boxes, scores, and classes |
| 151 | +image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') |
| 152 | + |
| 153 | +# Each box represents a part of the image where a particular object was detected |
| 154 | +detection_boxes = detection_graph.get_tensor_by_name('detection_boxes:0') |
| 155 | + |
| 156 | +# Each score represents level of confidence for each of the objects. |
| 157 | +# The score is shown on the result image, together with the class label. |
| 158 | +detection_scores = detection_graph.get_tensor_by_name('detection_scores:0') |
| 159 | +detection_classes = detection_graph.get_tensor_by_name('detection_classes:0') |
| 160 | + |
| 161 | +# Number of objects detected |
| 162 | +num_detections = detection_graph.get_tensor_by_name('num_detections:0') |
| 163 | + |
| 164 | + |
| 165 | +# ## Run session |
| 166 | +# Perform the actual detection by running the model with the image as input |
| 167 | +# Danger index coloring function |
| 168 | +def box_to_color_map(boxes=None, scores=None, final_img=None, min_score_thresh=0.75, danger_tresh=0.7, caution_tresh=0.3): |
| 169 | + ''' |
| 170 | + This function makes colormap for boxes and labels. |
| 171 | + If there are boxes that have score over min_score_thersh, this function evalutes its danger measure. |
| 172 | + Also, It classifies danger measure to 3 parts |
| 173 | + 1. Danger (over danger_tresh) 2. Caution (over caution_tresh) 3. Fine |
| 174 | +
|
| 175 | + :param boxes: Detection boxes |
| 176 | + :param scores: Detection score |
| 177 | + :param final_img: Mask image predicted railway |
| 178 | + :param min_score_thresh: Showing boxes that have scores over min_score_thresh |
| 179 | + :param danger_tresh: Classifying to danger object |
| 180 | + :param caution_tresh: Classifying to caution object |
| 181 | + :return: Colormap by boxes |
| 182 | + ''' |
| 183 | + if final_img is None: |
| 184 | + print('ERROR::frame is None') |
| 185 | + return None |
| 186 | + if boxes is None: |
| 187 | + print('ERROR::boxes is None') |
| 188 | + return None |
| 189 | + if scores is None: |
| 190 | + print('ERROR::scores is None') |
| 191 | + return None |
| 192 | + im_height, im_width = final_img.shape[0], final_img.shape[1] |
| 193 | + color_map = ['white'] * np.squeeze(boxes).__len__() |
| 194 | + for i in range(np.squeeze(boxes).__len__()): |
| 195 | + if np.squeeze(scores)[i] > min_score_thresh: |
| 196 | + (ymin, xmin, ymax, xmax) = np.squeeze(boxes)[i] |
| 197 | + (top, left, bottom, right) = (ymin * im_height, xmin * im_width, ymax * im_height, xmax * im_width) |
| 198 | + (top, left, bottom, right) = (top.astype(np.int32), left.astype(np.int32), |
| 199 | + bottom.astype(np.int32), right.astype(np.int32)) |
| 200 | + pixel_sum = 0.0 |
| 201 | + for bar_iter in range(left, right): |
| 202 | + try: |
| 203 | + pixel_sum += final_img[bottom][bar_iter] |
| 204 | + except IndexError: |
| 205 | + print('index Error') |
| 206 | + print('danger_score : ', pixel_sum / (right - left + 1), |
| 207 | + ' / bottom and left to right : ', bottom, ' and ', left, ' to ', right) |
| 208 | + danger_score = pixel_sum / (right - left) # Average value |
| 209 | + if danger_score > danger_tresh: |
| 210 | + danger_color = 'blue' # Danger color |
| 211 | + elif danger_score > caution_tresh: |
| 212 | + danger_color = 'deepskyblue' # Caution color |
| 213 | + else: |
| 214 | + danger_color = 'cyan' # Fine color |
| 215 | + color_map[i] = danger_color |
| 216 | + return color_map |
| 217 | + |
| 218 | + |
| 219 | +# # Video result |
| 220 | +cap = cv2.VideoCapture(VIDEO_PATH) |
| 221 | +print('Video loaded :: ', VIDEO_PATH) |
| 222 | +frame_count = 0 |
| 223 | + |
| 224 | +while cap.isOpened(): |
| 225 | + # Frame read |
| 226 | + ret, frame = cap.read() |
| 227 | + print('Frame Number :: ', frame_count) |
| 228 | + frame_count = frame_count + 1 |
| 229 | + if not ret: |
| 230 | + break |
| 231 | + if frame.shape[0] > 1000: # Downsizing |
| 232 | + print('Frame size is too large, Frame will be downsized :: ', frame.shape[1]/2, ', ', frame.shape[0]/2) |
| 233 | + frame = cv2.resize(frame, dsize=(int(frame.shape[1]/2), int(frame.shape[0]/2)), interpolation=cv2.INTER_AREA) |
| 234 | + im_width, im_height = frame.shape[1], frame.shape[0] |
| 235 | + |
| 236 | + # Convert frame to input data |
| 237 | + squared_img = cv2.resize(frame, dsize=(256, 256), interpolation=cv2.INTER_AREA) / 255 |
| 238 | + input_img = np.expand_dims(squared_img, axis=0) |
| 239 | + predicted_label = model.predict(input_img)[0] |
| 240 | + final_img = cv2.resize(predicted_label, dsize=(im_width, im_height), interpolation=cv2.INTER_AREA) |
| 241 | + backtorgb = cv2.cvtColor(final_img, cv2.COLOR_GRAY2RGB) |
| 242 | + backtorgb = (backtorgb * 255).astype(np.uint8) |
| 243 | + |
| 244 | + # Mix original image and predicted segmentation mask |
| 245 | + mixed_img = cv2.add(backtorgb, frame) |
| 246 | + |
| 247 | + # Run obstacle detection session |
| 248 | + frame_expanded = np.expand_dims(frame, axis=0) |
| 249 | + (boxes, scores, classes, num) = sess.run( |
| 250 | + [detection_boxes, detection_scores, detection_classes, num_detections], |
| 251 | + feed_dict={image_tensor: frame_expanded}) |
| 252 | + |
| 253 | + # Display original image and segmentation image |
| 254 | + cv2.imshow('original', frame) |
| 255 | + cv2.imshow('segmentation', mixed_img) |
| 256 | + |
| 257 | + # Visualize detection boxes |
| 258 | + vis_util.visualize_boxes_and_labels_on_image_array( |
| 259 | + mixed_img, |
| 260 | + np.squeeze(boxes), |
| 261 | + np.squeeze(classes).astype(np.int32), |
| 262 | + np.squeeze(scores), |
| 263 | + category_index, |
| 264 | + color=box_to_color_map(boxes, scores, final_img), |
| 265 | + use_normalized_coordinates=True, |
| 266 | + line_thickness=3, |
| 267 | + min_score_thresh=0.75); |
| 268 | + |
| 269 | + cv2.imshow('mixed', mixed_img) |
| 270 | + cv2.waitKey(1) |
| 271 | +cv2.destroyAllWindows() |
0 commit comments