Planesnet Classification Using MXNet Gluon

This notebook uses the Planesnet dataset, which can be found here: https://github.com/rhammell/planesnet The dataset contains 32,000 images 20x20 pixel RGB images. There are 8,000 images contain airplanes and 24,000 images with no airplanes. The goal of this notebook is to train a Convolutional Neural Network (CNN) using the MXNet Gluon framework to detect airplanes in overhead airport imagery.

To begin, import the necessary high-level libraries and get the Sagemaker execution role. Then we will point the inputs for the SageMaker training container to an S3 bucket. This will allow us to load data directly from S3 to our training instance without having to host the data locally on this notebook.

In [ ]:
import os
import boto3
import sagemaker
from sagemaker.mxnet import MXNet
from mxnet import gluon
from sagemaker import get_execution_role

sagemaker_session = sagemaker.Session()

role = get_execution_role()
In [2]:
#Loads planesnet data into S3 - only run this once!
inputs = sagemaker_session.upload_data(path='recordIOs', key_prefix='recfiles')
INFO:sagemaker:Created S3 bucket: sagemaker-us-east-1-951232522638

Below is a preview of the Gluon CNN python code. The lines of code actuall defining this CNN are:

def define_network():
    net = nn.Sequential()
    with net.name_scope():
        net.add(nn.Conv2D(32, 3, activation='relu'))
        net.add(nn.MaxPool2D(pool_size=(2,2)))
        net.add(nn.Conv2D(64, 3, activation='relu'))
        net.add(nn.Conv2D(64, 3, activation='relu'))
        net.add(nn.MaxPool2D(pool_size=(2,2)))
        net.add(nn.Dense(512, activation='relu'))
        net.add(nn.Dropout(0.5))
        net.add(nn.Dense(2))
    return net
So there are 3 Convolutional layers, 2 Pooling layers, 1 Fully connected layer, and 50% Dropout being used to prevent over-fitting. The Final Dense layer produces 2 outputs, making this a binary classification problem. This model with predict yes there is a plane, or no there is not a plane.

In [3]:
!cat 'planesnet-gluon.py'
from __future__ import print_function

import logging
import mxnet as mx
from mxnet import gluon, autograd
from mxnet.gluon import nn
import numpy as np
import json
import time


logging.basicConfig(level=logging.DEBUG)

# ------------------------------------------------------------ #
# Training methods                                             #
# ------------------------------------------------------------ #


def train(channel_input_dirs, hyperparameters, hosts, num_gpus, **kwargs):
    # SageMaker passes num_cpus, num_gpus and other args we can use to tailor training to
    # the current container environment, but here we just use simple cpu context.
    ctx = mx.cpu()

    # retrieve the hyperparameters we set in notebook (with some defaults)
    batch_size = hyperparameters.get('batch_size', 100)
    epochs = hyperparameters.get('epochs', 10)
    learning_rate = hyperparameters.get('learning_rate', 0.1)
    log_interval = hyperparameters.get('log_interval', 100)

    # load training and validation data
    # but point it at the location where SageMaker placed the data files, so it doesn't download them again.
    training_dir = channel_input_dirs['training']
    print(training_dir)
    train_data = get_train_data(training_dir + '/train.rec', batch_size)
    val_data = get_val_data(training_dir + '/validation.rec', batch_size)

    # define the network
    net = define_network()

    # Collect all parameters from net and its children, then initialize them.
    net.initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx)
    # Trainer is for updating parameters with gradient.

    if len(hosts) == 1:
        kvstore = 'device' if num_gpus > 0 else 'local'
    else:
        kvstore = 'dist_device_sync' if num_gpus > 0 else 'dist_sync'

    trainer = gluon.Trainer(net.collect_params(), 'adam',
                            optimizer_params={'learning_rate': learning_rate},
                            kvstore=kvstore)
    metric = mx.metric.Accuracy()
    loss = gluon.loss.SoftmaxCrossEntropyLoss()

    for epoch in range(epochs):
        # reset data iterator and metric at begining of epoch.
        metric.reset()
        btic = time.time()
        for i, (data, label) in enumerate(train_data):
            # Copy data to ctx if necessary
            data = data.as_in_context(ctx)
            label = label.as_in_context(ctx)
            # Start recording computation graph with record() section.
            # Recorded graphs can then be differentiated with backward.
            with autograd.record():
                output = net(data)
                L = loss(output, label)
                L.backward()
            # take a gradient step with batch_size equal to data.shape[0]
            trainer.step(data.shape[0])
            # update metric at last.
            metric.update([label], [output])

            if i % log_interval == 0 and i > 0:
                name, acc = metric.get()
                print('[Epoch %d Batch %d] Training: %s=%f, %f samples/s' %
                      (epoch, i, name, acc, batch_size / (time.time() - btic)))

            btic = time.time()

        name, acc = metric.get()
        print('[Epoch %d] Training: %s=%f' % (epoch, name, acc))

        name, val_acc = test(ctx, net, val_data)
        print('[Epoch %d] Validation: %s=%f' % (epoch, name, val_acc))

    return net


def save(net, model_dir):
    # save the model
    y = net(mx.sym.var('data'))
    y.save('%s/model.json' % model_dir)
    net.collect_params().save('%s/model.params' % model_dir)


def define_network():
    net = nn.Sequential()
    with net.name_scope():
        net.add(nn.Conv2D(32, 3, activation='relu'))
        net.add(nn.MaxPool2D(pool_size=(2,2)))
        net.add(nn.Conv2D(64, 3, activation='relu'))
        net.add(nn.Conv2D(64, 3, activation='relu'))
        net.add(nn.MaxPool2D(pool_size=(2,2)))
        net.add(nn.Dense(512, activation='relu'))
        net.add(nn.Dropout(0.5))
        net.add(nn.Dense(2))
    return net


def input_transformer(data, label):
    data = data.transpose([2, 1, 0]).astype(np.float32)
    return data, label


def get_train_data(data_dir, batch_size):
    return gluon.data.DataLoader(gluon.data.vision.ImageRecordDataset(data_dir, transform=input_transformer),
                                 batch_size=batch_size, shuffle=True, last_batch='discard')


def get_val_data(data_dir, batch_size):
    return gluon.data.DataLoader(gluon.data.vision.ImageRecordDataset(data_dir, transform=input_transformer), 
                                 batch_size=batch_size, shuffle=False)


def test(ctx, net, val_data):
    metric = mx.metric.Accuracy()
    for data, label in val_data:
        data = data.as_in_context(ctx)
        label = label.as_in_context(ctx)
        output = net(data)
        metric.update([label], [output])
    return metric.get()


# ------------------------------------------------------------ #
# Hosting methods                                              #
# ------------------------------------------------------------ #

def model_fn(model_dir):
    """
    Load the gluon model. Called once when hosting service starts.
    :param: model_dir The directory where model files are stored.
    :return: a model (in this case a Gluon network)
    """
    symbol = mx.sym.load('%s/model.json' % model_dir)
    outputs = mx.symbol.softmax(data=symbol, name='softmax_label')
    inputs = mx.sym.var('data')
    param_dict = gluon.ParameterDict('model_')
    net = gluon.SymbolBlock(outputs, inputs, param_dict)
    net.load_params('%s/model.params' % model_dir, ctx=mx.cpu())
    return net


def transform_fn(net, data, input_content_type, output_content_type):
    """
    Transform a request using the Gluon model. Called once per request.
    :param net: The Gluon model.
    :param data: The request payload.
    :param input_content_type: The request content type.
    :param output_content_type: The (desired) response content type.
    :return: response payload and content type.
    """
    # we can use content types to vary input/output handling, but
    # here we just assume json for both
    parsed = json.loads(data)
    nda = mx.nd.array(parsed)
    output = net(nda)
    prediction = mx.nd.argmax(output, axis=1)
    response_body = json.dumps(prediction.asnumpy().tolist()[0])
    return response_body, output_content_type

The following 3 blocks will first define the MXNet Model using the SageMaker API, then it fill train the model by called the fit function, and lastly it will deploy an endpoint that hosts the model.

In [4]:
m = MXNet("planesnet-gluon.py", 
          role=role, 
          train_instance_count=1, 
          train_instance_type="ml.c4.xlarge",
          hyperparameters={'batch_size': 128, 
                         'epochs': 50, 
                         'learning_rate': 0.001, 
                         'log_interval': 100})
In [5]:
m.fit(inputs)
INFO:sagemaker:Created S3 bucket: sagemaker-us-east-1-951232522638
INFO:sagemaker:Creating training-job with name: sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855
................................................................
executing startup script (first run)
2018-01-27 15:25:40,688 INFO - root - running container entrypoint
2018-01-27 15:25:40,689 INFO - root - starting train task
2018-01-27 15:25:41,907 INFO - mxnet_container.train - MXNetTrainingEnvironment: {'enable_cloudwatch_metrics': False, 'available_gpus': 0, 'channels': {u'training': {u'TrainingInputMode': u'File', u'RecordWrapperType': u'None', u'S3DistributionType': u'FullyReplicated'}}, '_ps_verbose': 0, 'resource_config': {u'current_host': u'algo-1', u'hosts': [u'algo-1']}, 'user_script_name': u'planesnet-gluon.py', 'input_config_dir': '/opt/ml/input/config', 'channel_dirs': {u'training': u'/opt/ml/input/data/training'}, 'code_dir': '/opt/ml/code', 'output_data_dir': '/opt/ml/output/data/', 'output_dir': '/opt/ml/output', 'model_dir': '/opt/ml/model', 'hyperparameters': {u'sagemaker_program': u'planesnet-gluon.py', u'learning_rate': 0.001, u'batch_size': 128, u'epochs': 50, u'log_interval': 100, u'sagemaker_region': u'us-east-1', u'sagemaker_enable_cloudwatch_metrics': False, u'sagemaker_job_name': u'sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855', u'sagemaker_container_log_level': 20, u'sagemaker_submit_directory': u's3://sagemaker-us-east-1-951232522638/sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855/source/sourcedir.tar.gz'}, 'hosts': [u'algo-1'], '_ps_port': 8000, 'user_script_archive': u's3://sagemaker-us-east-1-951232522638/sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855/source/sourcedir.tar.gz', '_scheduler_host': u'algo-1', 'sagemaker_region': u'us-east-1', 'input_dir': '/opt/ml/input', '_scheduler_ip': '10.32.0.4', 'current_host': u'algo-1', 'container_log_level': 20, 'available_cpus': 4, 'base_dir': '/opt/ml'}
Downloading s3://sagemaker-us-east-1-951232522638/sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855/source/sourcedir.tar.gz to /tmp/script.tar.gz
2018-01-27 15:25:42,024 INFO - botocore.vendored.requests.packages.urllib3.connectionpool - Starting new HTTP connection (1): 169.254.170.2
2018-01-27 15:25:42,121 INFO - botocore.vendored.requests.packages.urllib3.connectionpool - Starting new HTTPS connection (1): s3.amazonaws.com
2018-01-27 15:25:42,243 INFO - mxnet_container.train - Starting distributed training task
/opt/ml/input/data/training
MKL Build:20170720
[Epoch 0 Batch 100] Training: accuracy=0.765006, 759.383057 samples/s
[Epoch 0] Training: accuracy=0.794805
[Epoch 0] Validation: accuracy=0.871719
[Epoch 1 Batch 100] Training: accuracy=0.848082, 845.643122 samples/s
[Epoch 1] Training: accuracy=0.855625
[Epoch 1] Validation: accuracy=0.856250
[Epoch 2 Batch 100] Training: accuracy=0.871519, 765.710338 samples/s
[Epoch 2] Training: accuracy=0.870508
[Epoch 2] Validation: accuracy=0.869531
[Epoch 3 Batch 100] Training: accuracy=0.869817, 758.141263 samples/s
[Epoch 3] Training: accuracy=0.880703
[Epoch 3] Validation: accuracy=0.876875
[Epoch 4 Batch 100] Training: accuracy=0.905399, 739.773870 samples/s
[Epoch 4] Training: accuracy=0.897422
[Epoch 4] Validation: accuracy=0.865156
[Epoch 5 Batch 100] Training: accuracy=0.891863, 736.402982 samples/s
[Epoch 5] Training: accuracy=0.885039
[Epoch 5] Validation: accuracy=0.889687
[Epoch 6 Batch 100] Training: accuracy=0.896813, 832.725952 samples/s
[Epoch 6] Training: accuracy=0.903828
[Epoch 6] Validation: accuracy=0.911250
[Epoch 7 Batch 100] Training: accuracy=0.913521, 706.163796 samples/s
[Epoch 7] Training: accuracy=0.883164
[Epoch 7] Validation: accuracy=0.892188
[Epoch 8 Batch 100] Training: accuracy=0.901764, 814.544113 samples/s
[Epoch 8] Training: accuracy=0.906250
[Epoch 8] Validation: accuracy=0.914531
[Epoch 9 Batch 100] Training: accuracy=0.922881, 839.967194 samples/s
[Epoch 9] Training: accuracy=0.919961
[Epoch 9] Validation: accuracy=0.911875
[Epoch 10 Batch 100] Training: accuracy=0.920173, 825.439667 samples/s
[Epoch 10] Training: accuracy=0.922930
[Epoch 10] Validation: accuracy=0.913906
[Epoch 11 Batch 100] Training: accuracy=0.927831, 723.879351 samples/s
[Epoch 11] Training: accuracy=0.928633
[Epoch 11] Validation: accuracy=0.930937
[Epoch 12 Batch 100] Training: accuracy=0.935721, 693.529521 samples/s
[Epoch 12] Training: accuracy=0.934258
[Epoch 12] Validation: accuracy=0.931719
[Epoch 13 Batch 100] Training: accuracy=0.938892, 882.557026 samples/s
[Epoch 13] Training: accuracy=0.937070
[Epoch 13] Validation: accuracy=0.942344
[Epoch 14 Batch 100] Training: accuracy=0.944694, 823.765301 samples/s
[Epoch 14] Training: accuracy=0.940312
[Epoch 14] Validation: accuracy=0.935156
[Epoch 15 Batch 100] Training: accuracy=0.944694, 753.783389 samples/s
[Epoch 15] Training: accuracy=0.943242
[Epoch 15] Validation: accuracy=0.928438
[Epoch 16 Batch 100] Training: accuracy=0.940517, 834.474593 samples/s
[Epoch 16] Training: accuracy=0.942891
[Epoch 16] Validation: accuracy=0.948125
[Epoch 17 Batch 100] Training: accuracy=0.949180, 732.688511 samples/s
[Epoch 17] Training: accuracy=0.948750
[Epoch 17] Validation: accuracy=0.947031
[Epoch 18 Batch 100] Training: accuracy=0.954208, 794.118430 samples/s
[Epoch 18] Training: accuracy=0.950586
[Epoch 18] Validation: accuracy=0.949375
[Epoch 19 Batch 100] Training: accuracy=0.948252, 651.028025 samples/s
[Epoch 19] Training: accuracy=0.947617
[Epoch 19] Validation: accuracy=0.945156
[Epoch 20 Batch 100] Training: accuracy=0.952816, 750.552442 samples/s
[Epoch 20] Training: accuracy=0.954023
[Epoch 20] Validation: accuracy=0.944219
[Epoch 21 Batch 100] Training: accuracy=0.956064, 805.446113 samples/s
[Epoch 21] Training: accuracy=0.954766
[Epoch 21] Validation: accuracy=0.925312
[Epoch 22 Batch 100] Training: accuracy=0.961015, 728.287424 samples/s
[Epoch 22] Training: accuracy=0.956914
[Epoch 22] Validation: accuracy=0.950469
[Epoch 23 Batch 100] Training: accuracy=0.956374, 714.090263 samples/s
[Epoch 23] Training: accuracy=0.959883
[Epoch 23] Validation: accuracy=0.953125
[Epoch 24 Batch 100] Training: accuracy=0.961247, 831.400282 samples/s
[Epoch 24] Training: accuracy=0.960352
[Epoch 24] Validation: accuracy=0.951094
[Epoch 25 Batch 100] Training: accuracy=0.962252, 799.470632 samples/s
[Epoch 25] Training: accuracy=0.961172
[Epoch 25] Validation: accuracy=0.935781
[Epoch 26 Batch 100] Training: accuracy=0.954053, 761.868079 samples/s
[Epoch 26] Training: accuracy=0.959102
[Epoch 26] Validation: accuracy=0.955156
[Epoch 27 Batch 100] Training: accuracy=0.964573, 674.191607 samples/s
[Epoch 27] Training: accuracy=0.965938
[Epoch 27] Validation: accuracy=0.958438
[Epoch 28 Batch 100] Training: accuracy=0.965733, 797.647358 samples/s
[Epoch 28] Training: accuracy=0.967930
[Epoch 28] Validation: accuracy=0.962656
[Epoch 29 Batch 100] Training: accuracy=0.964650, 779.907132 samples/s
[Epoch 29] Training: accuracy=0.961406
[Epoch 29] Validation: accuracy=0.960625
[Epoch 30 Batch 100] Training: accuracy=0.961788, 768.749632 samples/s
[Epoch 30] Training: accuracy=0.966055
[Epoch 30] Validation: accuracy=0.960156
[Epoch 31 Batch 100] Training: accuracy=0.971767, 698.846836 samples/s
[Epoch 31] Training: accuracy=0.971484
[Epoch 31] Validation: accuracy=0.962031
[Epoch 32 Batch 100] Training: accuracy=0.970220, 747.096355 samples/s
[Epoch 32] Training: accuracy=0.970625
[Epoch 32] Validation: accuracy=0.962344
[Epoch 33 Batch 100] Training: accuracy=0.976253, 764.946636 samples/s
[Epoch 33] Training: accuracy=0.974883
[Epoch 33] Validation: accuracy=0.963125
[Epoch 34 Batch 100] Training: accuracy=0.972153, 836.164171 samples/s
[Epoch 34] Training: accuracy=0.970469
[Epoch 34] Validation: accuracy=0.966250
[Epoch 35 Batch 100] Training: accuracy=0.974706, 768.242508 samples/s
[Epoch 35] Training: accuracy=0.972695
[Epoch 35] Validation: accuracy=0.955000
[Epoch 36 Batch 100] Training: accuracy=0.977027, 760.524409 samples/s
[Epoch 36] Training: accuracy=0.973594
[Epoch 36] Validation: accuracy=0.959844
[Epoch 37 Batch 100] Training: accuracy=0.977413, 747.528056 samples/s
[Epoch 37] Training: accuracy=0.977695
[Epoch 37] Validation: accuracy=0.965000
[Epoch 38 Batch 100] Training: accuracy=0.981745, 717.674699 samples/s
[Epoch 38] Training: accuracy=0.980547
[Epoch 38] Validation: accuracy=0.967812
[Epoch 39 Batch 100] Training: accuracy=0.969910, 786.163292 samples/s
[Epoch 39] Training: accuracy=0.974219
[Epoch 39] Validation: accuracy=0.965313
[Epoch 40 Batch 100] Training: accuracy=0.973004, 797.308566 samples/s
[Epoch 40] Training: accuracy=0.976211
[Epoch 40] Validation: accuracy=0.965000
[Epoch 41 Batch 100] Training: accuracy=0.979038, 687.090110 samples/s
[Epoch 41] Training: accuracy=0.979688
[Epoch 41] Validation: accuracy=0.967500
[Epoch 42 Batch 100] Training: accuracy=0.980043, 652.801230 samples/s
[Epoch 42] Training: accuracy=0.979609
[Epoch 42] Validation: accuracy=0.965313
[Epoch 43 Batch 100] Training: accuracy=0.984530, 848.940644 samples/s
[Epoch 43] Training: accuracy=0.982187
[Epoch 43] Validation: accuracy=0.965156
[Epoch 44 Batch 100] Training: accuracy=0.979425, 705.016687 samples/s
[Epoch 44] Training: accuracy=0.980078
[Epoch 44] Validation: accuracy=0.969531
[Epoch 45 Batch 100] Training: accuracy=0.983060, 663.906828 samples/s
[Epoch 45] Training: accuracy=0.981523
[Epoch 45] Validation: accuracy=0.970469
[Epoch 46 Batch 100] Training: accuracy=0.980972, 711.672663 samples/s
[Epoch 46] Training: accuracy=0.982422
[Epoch 46] Validation: accuracy=0.965781
[Epoch 47 Batch 100] Training: accuracy=0.984220, 654.936274 samples/s
[Epoch 47] Training: accuracy=0.983164
[Epoch 47] Validation: accuracy=0.969531
[Epoch 48 Batch 100] Training: accuracy=0.987314, 860.672114 samples/s
[Epoch 48] Training: accuracy=0.984062
[Epoch 48] Validation: accuracy=0.961875
[Epoch 49 Batch 100] Training: accuracy=0.984530, 808.596547 samples/s
[Epoch 49] Training: accuracy=0.980664
[Epoch 49] Validation: accuracy=0.970625
===== Job Complete =====
In [ ]:
predictor = m.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')
INFO:sagemaker:Creating model with name: sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855
INFO:sagemaker:Creating endpoint with name sagemaker-mxnet-py2-cpu-2018-01-27-15-20-34-855
-----------------------------------------------------------------------------------------------------------------------------
Now that our endpoint is deployed, lets try to detect the planes in the following overhead image.

We will use a technique called a sliding window detection and apply it to a larger scene. The sliding window detection defines a window of a fixed size and moves that window across the image which a specified amount of overlap. The window size and overlap are specified by the win and step parameters. The open source detection algorithm has been adapted from https://github.com/bikz05/object-detector

In [82]:
file_name = 'test-images/planesnet/scene_2.png'
# test image
from IPython.display import Image
Image(file_name)
Out[82]:
In [135]:
import numpy as np
import cv2
import time
import os
import matplotlib.pyplot as plt
from scipy.misc import imsave, imread
from tqdm import tqdm


# Define a function that takes an image,
# start and stop positions in both x and y,
# window size (x and y dimensions),
# and overlap fraction (for both x and y)
def slide_window(img, x_start_stop, y_start_stop, xy_window, xy_overlap):

    # Compute the span of the region to be searched
    xspan = x_start_stop[1] - x_start_stop[0]
    yspan = y_start_stop[1] - y_start_stop[0]
    
    # Compute the number of pixels per step in x/y
    nx_pix_per_step = np.int(xy_window[0]*(1 - xy_overlap[0]))
    ny_pix_per_step = np.int(xy_window[1]*(1 - xy_overlap[1]))
    
    # Compute the number of windows in x/y
    nx_windows = np.int(xspan/nx_pix_per_step) - 1
    ny_windows = np.int(yspan/ny_pix_per_step) - 1
    # Initialize a list to append window positions to
    window_list = []
    for ys in range(ny_windows):
        for xs in range(nx_windows):
            # Calculate window position
            startx = xs*nx_pix_per_step + x_start_stop[0]
            endx = startx + xy_window[0]
            
            starty = ys*ny_pix_per_step + y_start_stop[0]
            endy = starty + xy_window[1]

            # Append window position to list
            window_list.append(((startx, starty), (endx, endy)))
    # Return the list of windows
    return window_list


# Define a function you will pass an image
# and the list of windows to be searched (output of slide_windows())
def search_windows(img, windows):

    #1) Create an empty list to receive positive detection windows
    on_windows = []
    test_features = []
    ct = 1
    #2) Iterate over all windows in the list
    for window in tqdm(windows):
        
        if window[1][0] > 1222 :
            print('error x out of bounds')
            print(window[1][0])
        elif window[0][1] > 558:
            print('error y out of bounds')
            print(window[0][1])
        elif window[0][0] > window[1][0] :
            print('x-cords messed up')
        elif window[0][1] > window[1][1]:
            print('y-cords messed up')
        
        #3) Extract the test window from original image        
        test_img = cv2.resize(img[window[0][1]:window[1][1], window[0][0]:window[1][0]], (20, 20))

        #4) Scale extracted features to be fed to classifier
        test_features = np.expand_dims(test_img, axis=1).transpose([1, 3, 0, 2])
        
        #5) Predict using your classifier
        prediction = predictor.predict(test_features)
        
        #6) If positive (prediction == 1) then save the window    
        if prediction == 1.0:
            on_windows.append(window) 

        ct = ct + 1
    #7 Return windows for positive detections
    return on_windows


# Define a function to draw bounding boxes
def draw_boxes(img, bboxes, color=(0, 0, 255), thick=6):
    # Make a copy of the image
    imcopy = np.copy(img)
    # Iterate through the bounding boxes
    for bbox in bboxes:
        # Draw a rectangle given bbox coordinates
        cv2.rectangle(imcopy, bbox[0], bbox[1], color, thick)
    # Return the image copy with boxes drawn
    return imcopy
In [136]:
%%time
from PIL import Image

in_fname = 'test-images/planesnet/scene_2.png'
img = imread(in_fname)
arr = np.array(img)[:,:,0:3]
shape = arr.shape

# Set output fname
out_fname = os.path.splitext(in_fname)[0] + '_detection.png'

# Sliding window parameters
win = 30
step = 0.8
draw_image = np.copy(img)

windows = slide_window(img, x_start_stop=(0, shape[1]-win), y_start_stop=(0, shape[0]-win),
                    xy_window=(win, win), xy_overlap=(step, step))

hot_windows = search_windows(img, windows)


window_img = draw_boxes(draw_image, hot_windows, color=(0, 0, 255), thick=2)

plt.imshow(window_img)
plt.show()

# Save output image
outIm = Image.fromarray(window_img)
outIm.save(out_fname)
/home/ec2-user/anaconda3/envs/mxnet_p27/lib/python2.7/site-packages/ipykernel/__main__.py:4: DeprecationWarning: `imread` is deprecated!
`imread` is deprecated in SciPy 1.0.0, and will be removed in 1.2.0.
Use ``imageio.imread`` instead.
100%|██████████| 24648/24648 [06:08<00:00, 66.82it/s]
CPU times: user 57.3 s, sys: 2.11 s, total: 59.4 s
Wall time: 6min 9s
The first image below used a 30x30 pixel window with 80% overlap. It took 6 minutes for the detection calculation to complete but it has a high number of false positive detections. The second image below took 58 minutes to for the detection calculation to complete. As you can see, it achieved a much higher accuracy. This is because the window used was 20x20 pixels with 90% overlap. Feel free to change the win and step parameters above to see how adjusting these values impact speed and accuracy.a
In [137]:
file_name = 'test-images/planesnet/scene_2_detection.png'

# test image
from IPython.display import Image
Image(file_name)
Out[137]:
In [118]:
file_name = 'test-images/planesnet/scene_2_detections_goldcopy.png'

# test image
from IPython.display import Image
Image(file_name)
Out[118]: