# Object-based filtering of pixel classifications 

Keywords data methods; image segmentation, data methods; GEOBIA

## Background

Geographic Object-Based Image Analysis (GEOBIA), aims to group pixels together into meaningful image-objects. There are two advantages to a GEOBIA workflow: one, we can reduce the ‘salt and pepper’ effect typical of classifying pixels; and two, we can increase the computational efficiency of our workflow by grouping pixels into fewer, larger, but more meaningful objects. A review of the emerging trends in GEOBIA can be found in Chen et al. (2017).

## Description

In this notebook, we take the pixel-based classifications generated in the 4_Classify_satellite_data.ipynb notebook, and filter the classifications by image-objects. To do this, we first need to conduct image segmentation using the function skimage.segmentation.quickshift. The image segmentation is conducted on the NDVI layer output in the previous notebook. To filter the pixel observations, we assign to each segment the majority (mode) pixel classification using the scipy.ndimage.measurements import _stats module.

1. Run the image segmentation

2. Visualize the segments

3. Calculate the mode statistic for each segment

4. Write the new object-based classification to disk as a COG

5. Plot the result

## Getting started

To run this analysis, run all the cells in the notebook, starting with the “Load packages” cell.

[1]:

import scipy
import xarray as xr
import numpy as np
import matplotlib.pyplot as plt
from datacube.utils.cog import write_cog
from scipy.ndimage.measurements import _stats
from skimage.segmentation import quickshift

/env/lib/python3.8/site-packages/geopandas/_compat.py:106: UserWarning: The Shapely GEOS version (3.8.0-CAPI-1.13.1 ) is incompatible with the GEOS version PyGEOS was compiled with (3.9.1-CAPI-1.14.2). Conversions between both will be slow.
warnings.warn(


## Analysis Parameters

• pred_tif: The path and name of the prediction GeoTIFF output in the previous notebook.

• tif_to_seg: The geotiff to use as an input to the image segmentation, in the default example this was an NDVI layer output in the last notebook.

• results: A folder location to store the classified GeoTIFFs.

[2]:

pred_tif = 'results/prediction.tif'

tif_to_seg = 'results/NDVI.tif'

results = 'results/'



## Generate an object-based classification

### Run image segmentation

[3]:

# Convert our mean NDVI xarray into a numpy array
ndvi = xr.open_rasterio(tif_to_seg).squeeze().values

# Calculate the segments
segments = quickshift(ndvi,
kernel_size=2,
convert2lab=False,
max_dist=4,
ratio=1.0)


### Visualise segments

To help us visualize the segments, we can calculate the zonal mean NDVI for each segment and then we’ll plot a zoomed in section of the region

[4]:

segments_zonal_mean_qs = scipy.ndimage.mean(input=ndvi,
labels=segments,
index=segments)

[5]:

fig,ax=plt.subplots(1,2, figsize=(16,8), sharey=True)
ax[0].imshow(ndvi[1000:2500,1000:2500], cmap='gist_earth_r', vmin=0.1, vmax=0.7)
ax[1].imshow(segments_zonal_mean_qs[1000:2500,1000:2500], cmap='gist_earth_r', vmin=0.1, vmax=0.7)
ax[0].set_title('Per pixel NDVI, zoomed in');
ax[1].set_title('Mean NDVI within segments, zoomed in')
plt.tight_layout();


### Open pixel-based predictions

[6]:

pred = xr.open_rasterio(pred_tif).squeeze().drop_vars('band')


### Calculate mode

Within each segment, the majority classification is calculated and assigned to that segment.

[7]:

count, _sum =_stats(pred, labels=segments, index=segments)
mode = _sum > (count/2)
mode = xr.DataArray(mode,  coords=pred.coords, dims=pred.dims, attrs=pred.attrs).astype(np.int16)


### Write result to disk

[8]:

write_cog(mode, results+ 'prediction_object.tif', overwrite=True)

/env/lib/python3.8/site-packages/pyproj/crs/crs.py:280: FutureWarning: '+init=<authority>:<code>' syntax is deprecated. '<authority>:<code>' is the preferred initialization method. When making the change, be mindful of axis order changes: https://pyproj4.github.io/pyproj/stable/gotchas.html#axis-order-changes-in-proj-6
projstring = _prepare_from_string(projparams)

[8]:

PosixPath('results/prediction_object_.tif')


### Plot result

Below we plot the the pixel-based classification alongside the newly created object-based classification. You can see the ‘salt and pepper’ effect of individual pixels being classified as crop has been removed in the object based classification, resulting in a ‘cleaner’ classification.

[9]:

fig, axes = plt.subplots(1, 2, sharey=True, figsize=(16, 8))
axes[0].set_title('Pixel-based Classification')
axes[1].set_title('Object-based Classification (mode)')
plt.tight_layout();


Contact: If you need assistance, please post a question on the Open Data Cube Slack channel or on the GIS Stack Exchange using the open-data-cube tag (you can view previously asked questions here). If you would like to report an issue with this notebook, you can file one on Github.

Last Tested:

[10]:

from datetime import datetime
datetime.today().strftime('%Y-%m-%d')

[10]:

'2021-08-17'