Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dmc5179/98011b5239e4a47aea34c5d75ba46a7f to your computer and use it in GitHub Desktop.
Save dmc5179/98011b5239e4a47aea34c5d75ba46a7f to your computer and use it in GitHub Desktop.
Python script for the Mask R-CNN Ship Detection Minimum Viable Model (Inference Only) (Python 3.6)
#!/usr/bin/env python
# coding: utf-8
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt # plot & image processing
from skimage.morphology import label
from skimage.data import imread
import os
import time
import sys
print('Setting up working area')
# Configurations
# Split x ratio of train dataset for validation
TRAINING_VALIDATION_RATIO = 0.2
WORKING_DIR = '/mnt/data/Airbus_Ship_Detection_Challenge/kaggle/working'
INPUT_DIR = '/mnt/data/Airbus_Ship_Detection_Challenge/kaggle/input'
OUTPUT_DIR = '/mnt/data/Airbus_Ship_Detection_Challenge/kaggle/output'
LOGS_DIR = os.path.join(WORKING_DIR, "logs")
TRAIN_DATA_PATH = os.path.join(INPUT_DIR, 'train_v2')
TEST_DATA_PATH = os.path.join(INPUT_DIR, 'test_v2')
SAMPLE_SUBMISSION_PATH = os.path.join(INPUT_DIR, 'sample_submission_v2.csv')
TRAIN_SHIP_SEGMENTATIONS_PATH = os.path.join(INPUT_DIR, 'train_ship_segmentations_v2.csv')
MASK_RCNN_PATH = os.path.join(WORKING_DIR, 'Mask_RCNN-master')
COCO_WEIGHTS_PATH = os.path.join(WORKING_DIR, "mask_rcnn_coco.h5")
SHIP_CLASS_NAME = 'ship'
IMAGE_WIDTH = 768
IMAGE_HEIGHT = 768
SHAPE = (IMAGE_WIDTH, IMAGE_HEIGHT)
test_ds = os.listdir(TEST_DATA_PATH)
train_ds = os.listdir(TRAIN_DATA_PATH)
print('Working Dir:', WORKING_DIR, os.listdir(WORKING_DIR))
print('Input Dir:', INPUT_DIR, os.listdir(INPUT_DIR))
print('train dataset from: {}, {}'.format(TRAIN_DATA_PATH, len(train_ds)))
print('test dataset from: {}, {}'.format(TRAIN_DATA_PATH, len(test_ds)))
print(TRAIN_SHIP_SEGMENTATIONS_PATH)
# Read mask encording from the input CSV file
masks = pd.read_csv(TRAIN_SHIP_SEGMENTATIONS_PATH)
masks.head()
# ref: https://www.kaggle.com/kmader/baseline-u-net-model-part-1
def multi_rle_encode(img):
labels = label(img[:, :, 0])
return [rle_encode(labels==k) for k in np.unique(labels[labels>0])]
# ref: https://www.kaggle.com/paulorzp/run-length-encode-and-decode
def rle_encode(img):
'''
img: numpy array, 1 - mask, 0 - background
Returns run length as string formated: [start0] [length0] [start1] [length1]... in 1d array
'''
# reshape to 1d array
pixels = img.T.flatten() # Needed to align to RLE direction
# pads the head & the tail with 0 & converts to ndarray
pixels = np.concatenate([[0], pixels, [0]])
# gets all start(0->1) & end(1->0) positions
runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
# transforms end positions to lengths
runs[1::2] -= runs[::2]
# converts to the string formated: '[s0] [l0] [s1] [l1]...'
return ' '.join(str(x) for x in runs)
def rle_decode(mask_rle, shape=SHAPE):
'''
mask_rle: run-length as string formated: [start0] [length0] [start1] [length1]... in 1d array
shape: (height,width) of array to return
Returns numpy array according to the shape, 1 - mask, 0 - background
'''
s = mask_rle.split()
# gets starts & lengths 1d arrays
starts, lengths = [np.asarray(x, dtype=int) for x in (s[0::2], s[1::2])]
starts -= 1
# gets ends 1d array
ends = starts + lengths
# creates blank mask image 1d array
img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
# sets mark pixles
for lo, hi in zip(starts, ends):
img[lo:hi] = 1
# reshape as a 2d mask image
return img.reshape(shape).T # Needed to align to RLE direction
def masks_as_image(in_mask_list, shape=SHAPE):
'''Take the individual ship masks and create a single mask array for all ships
in_mask_list: pd Series: [idx0] [RLE string0]...
Returns numpy array as (shape.h, sahpe.w, 1)
'''
all_masks = np.zeros(shape, dtype = np.int16)
# if isinstance(in_mask_list, list):
for mask in in_mask_list:
if isinstance(mask, str):
all_masks += rle_decode(mask)
return np.expand_dims(all_masks, -1)
def shows_decode_encode(image_id, path=TRAIN_DATA_PATH):
'''Show image, ship mask, and encoded/decoded result
'''
fig, axarr = plt.subplots(1, 3, figsize = (10, 5))
# image
img_0 = imread(os.path.join(path, image_id))
axarr[0].imshow(img_0)
axarr[0].set_title(image_id)
# input mask
rle_1 = masks.query('ImageId=="{}"'.format(image_id))['EncodedPixels']
img_1 = masks_as_image(rle_1)
# takes 2d array (shape.h, sahpe.w)
axarr[1].imshow(img_1[:, :, 0])
axarr[1].set_title('Ship Mask')
# encode & decode mask
rle_2 = multi_rle_encode(img_1)
img_2 = masks_as_image(rle_2)
axarr[2].imshow(img_0)
axarr[2].imshow(img_2[:, :, 0], alpha=0.3)
axarr[2].set_title('Encoded & Decoded Mask')
plt.show()
print(image_id , ' Check Decoding->Encoding',
'RLE_0:', len(rle_1), '->',
'RLE_1:', len(rle_2))
# check if a mask has a ship
masks['ships'] = masks['EncodedPixels'].map(lambda encoded_pixels: 1 if isinstance(encoded_pixels, str) else 0)
# sum ship# by ImageId and create the unique image id/mask list
start_time = time.time()
unique_img_ids = masks.groupby('ImageId').agg({'ships': 'sum'})
unique_img_ids['RleMaskList'] = masks.groupby('ImageId')['EncodedPixels'].apply(list)
unique_img_ids = unique_img_ids.reset_index()
end_time = time.time() - start_time
print("unique_img_ids groupby took: {}".format(end_time))
# Only care image with ships
unique_img_ids = unique_img_ids[unique_img_ids['ships'] > 0]
unique_img_ids['ships'].hist()
unique_img_ids.sample(3)
# split to training & validation sets
from sklearn.model_selection import train_test_split
train_ids, val_ids = train_test_split(unique_img_ids,
test_size = TRAINING_VALIDATION_RATIO,
stratify = unique_img_ids['ships'])
print(train_ids.shape[0], 'training masks')
print(val_ids.shape[0], 'validation masks')
train_ids['ships'].hist()
val_ids['ships'].hist()
# if to clone Mask_R-CNN git when it exists
#UPDATE_MASK_RCNN = False
os.chdir(WORKING_DIR)
#if UPDATE_MASK_RCNN:
# os.system('rm -rf {MASK_RCNN_PATH}')
# Downlaod Mask RCNN code to a local folder
#if not os.path.exists(MASK_RCNN_PATH):
# os.system(' wget https://github.com/samlin001/Mask_RCNN/archive/master.zip -O Mask_RCNN-master.zip')
# os.system(" unzip Mask_RCNN-master.zip 'Mask_RCNN-master/mrcnn/*'")
# os.system(' rm Mask_RCNN-master.zip')
print("MASK_RCNN module updated")
# Import Mask RCNN
sys.path.append(MASK_RCNN_PATH) # To find local version of the library
print("Imported Mask RCNN")
from mrcnn.config import Config
from mrcnn import utils
import mrcnn.model as modellib
from mrcnn import visualize
from mrcnn.model import log
class AirbusShipDetectionChallengeDataset(utils.Dataset):
"""Airbus Ship Detection Challenge Dataset
"""
def __init__(self, image_file_dir, ids, masks, image_width=IMAGE_WIDTH, image_height=IMAGE_HEIGHT):
super().__init__(self)
self.image_file_dir = image_file_dir
self.ids = ids
self.masks = masks
self.image_width = image_width
self.image_height = image_height
# Add classes
self.add_class(SHIP_CLASS_NAME, 1, SHIP_CLASS_NAME)
self.load_dataset()
def load_dataset(self):
"""Load dataset from the path
"""
# Add images
for index, row in self.ids.iterrows():
image_id = row['ImageId']
image_path = os.path.join(self.image_file_dir, image_id)
rle_mask_list = row['RleMaskList']
#print(rle_mask_list)
self.add_image(
SHIP_CLASS_NAME,
image_id=image_id,
path=image_path,
width=self.image_width, height=self.image_height,
rle_mask_list=rle_mask_list)
def load_mask(self, image_id):
"""Generate instance masks for shapes of the given image ID.
"""
info = self.image_info[image_id]
rle_mask_list = info['rle_mask_list']
mask_count = len(rle_mask_list)
mask = np.zeros([info['height'], info['width'], mask_count],
dtype=np.uint8)
i = 0
for rel in rle_mask_list:
if isinstance(rel, str):
np.copyto(mask[:,:,i], rle_decode(rel))
i += 1
# Return mask, and array of class IDs of each instance. Since we have
# one class ID only, we return an array of 1s
return mask.astype(np.bool), np.ones([mask.shape[-1]], dtype=np.int32)
def image_reference(self, image_id):
"""Return the path of the image."""
info = self.image_info[image_id]
if info['source'] == SHIP_CLASS_NAME:
return info['path']
else:
super(self.__class__, self).image_reference(image_id)
class AirbusShipDetectionChallengeGPUConfig(Config):
"""
Configuration of Airbus Ship Detection Challenge Dataset
Overrides values in the base Config class.
From https://github.com/samlin001/Mask_RCNN/blob/master/mrcnn/config.py
"""
# https://www.kaggle.com/docs/kernels#technical-specifications
NAME = 'ASDC_GPU'
# NUMBER OF GPUs to use.
GPU_COUNT = 1
IMAGES_PER_GPU = 2
NUM_CLASSES = 2 # ship or background
IMAGE_MIN_DIM = IMAGE_WIDTH
IMAGE_MAX_DIM = IMAGE_WIDTH
STEPS_PER_EPOCH = 300
VALIDATION_STEPS = 50
SAVE_BEST_ONLY = True
# Minimum probability value to accept a detected instance
# ROIs below this threshold are skipped
DETECTION_MIN_CONFIDENCE = 0.95
# Non-maximum suppression threshold for detection
# Keep it small to merge overlapping ROIs
DETECTION_NMS_THRESHOLD = 0.05
config = AirbusShipDetectionChallengeGPUConfig()
config.display()
start_time = time.time()
# Training dataset.
print("Preparing training dataset")
dataset_train = AirbusShipDetectionChallengeDataset(image_file_dir=TRAIN_DATA_PATH, ids=train_ids, masks=masks)
dataset_train.prepare()
# Validation dataset
print("Preparing validation dataset")
dataset_val = AirbusShipDetectionChallengeDataset(image_file_dir=TRAIN_DATA_PATH, ids=val_ids, masks=masks)
dataset_val.prepare()
# Load and display random samples
#image_ids = np.random.choice(dataset_train.image_ids, 3)
#for image_id in image_ids:
# image = dataset_train.load_image(image_id)
# mask, class_ids = dataset_train.load_mask(image_id)
# visualize.display_top_masks(image, mask, class_ids, dataset_train.class_names, limit=1)
end_time = time.time() - start_time
print("dataset prepare: {}".format(end_time))
class InferenceConfig(AirbusShipDetectionChallengeGPUConfig):
GPU_COUNT = 1
# 1 image for inference
IMAGES_PER_GPU = 1
inference_config = InferenceConfig()
# create a model in inference mode
infer_model = modellib.MaskRCNN(mode="inference",
config=inference_config,
model_dir=WORKING_DIR)
model_path = infer_model.find_last()
# Load trained weights
print("Loading weights for inference from ", model_path)
infer_model.load_weights(model_path, by_name=True)
# Test on a random image
image_id = np.random.choice(dataset_val.image_ids)
original_image, image_meta, gt_class_id, gt_bbox, gt_mask = modellib.load_image_gt(dataset_val, inference_config,
image_id, use_mini_mask=False)
log("original_image", original_image)
log("image_meta", image_meta)
log("gt_class_id", gt_class_id)
log("gt_bbox", gt_bbox)
log("gt_mask", gt_mask)
#visualize.display_instances(original_image, gt_bbox, gt_mask, gt_class_id,
# dataset_train.class_names, figsize=(8, 8))
results = infer_model.detect([original_image], verbose=1)
r = results[0]
#visualize.display_instances(original_image, r['rois'], r['masks'], r['class_ids'],
# dataset_val.class_names, r['scores'])
# Compute VOC-Style mean Average Precision @ IoU=0.5
# Running on a few images. Increase for better accuracy.
image_ids = np.random.choice(dataset_val.image_ids, 20)
APs = []
inference_start = time.time()
for image_id in image_ids:
# Load image and ground truth data
image, image_meta, gt_class_id, gt_bbox, gt_mask = modellib.load_image_gt(dataset_val, inference_config,
image_id, use_mini_mask=False)
molded_images = np.expand_dims(modellib.mold_image(image, inference_config), 0)
# Run object detection
results = infer_model.detect([image], verbose=1)
r = results[0]
#visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],
# dataset_val.class_names, r['scores'])
# Compute AP
AP, precisions, recalls, overlaps = utils.compute_ap(gt_bbox, gt_class_id, gt_mask,
r["rois"], r["class_ids"], r["scores"], r['masks'])
APs.append(AP)
inference_end = time.time()
print('Inference Time: %0.2f Minutes'%((inference_end - inference_start)/60))
print("mAP: ", np.mean(APs))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment