Here is an EE OBIA example based on Noel Gorelick's recent image segmentation presentation and example code (available here). Note that you'll need to adapt this to your particular input dataset and use case. (I feel justified making this generalization because the OP is asking about 'OBIA', which is not what the originally posted code represents). If all you care about are segments, then you can stop with clusters
. If you care about segment properties, and using those properties to classify the segments, see everything after clusters
:
var imageCollection = ee.ImageCollection('USDA/NAIP/DOQQ');
var geometry = /* color: #0b4a8b */ee.Geometry.Polygon(
[[[-121.89511299133301, 38.98496606984683],
[-121.89511299133301, 38.909335196675435],
[-121.69358253479004, 38.909335196675435],
[-121.69358253479004, 38.98496606984683]]], null, false);
var cdl2016 = ee.Image('USDA/NASS/CDL/2016');
var bands = ['R', 'G', 'B', 'N']
var img = imageCollection
.filterDate('2015-01-01', '2017-01-01')
.filterBounds(geometry)
.mosaic()
img = ee.Image(img).clip(geometry).divide(255).select(bands)
Map.centerObject(geometry, 13)
Map.addLayer(img, {gamma: 0.8}, 'RGBN', false)
var seeds = ee.Algorithms.Image.Segmentation.seedGrid(36);
// Run SNIC on the regular square grid.
var snic = ee.Algorithms.Image.Segmentation.SNIC({
image: img,
size: 32,
compactness: 5,
connectivity: 8,
neighborhoodSize:256,
seeds: seeds
}).select(['R_mean', 'G_mean', 'B_mean', 'N_mean', 'clusters'], ['R', 'G', 'B', 'N', 'clusters'])
var clusters = snic.select('clusters')
Map.addLayer(clusters.randomVisualizer(), {}, 'clusters')
Map.addLayer(snic, {bands: ['R', 'G', 'B'], min:0, max:1, gamma: 0.8}, 'means', false)
// Compute per-cluster stdDev.
var stdDev = img.addBands(clusters).reduceConnectedComponents(ee.Reducer.stdDev(), 'clusters', 256)
Map.addLayer(stdDev, {min:0, max:0.1}, 'StdDev', false)
// Area, Perimeter, Width and Height
var area = ee.Image.pixelArea().addBands(clusters).reduceConnectedComponents(ee.Reducer.sum(), 'clusters', 256)
Map.addLayer(area, {min:50000, max: 500000}, 'Cluster Area', false)
var minMax = clusters.reduceNeighborhood(ee.Reducer.minMax(), ee.Kernel.square(1));
var perimeterPixels = minMax.select(0).neq(minMax.select(1)).rename('perimeter');
Map.addLayer(perimeterPixels, {min: 0, max: 1}, 'perimeterPixels');
var perimeter = perimeterPixels.addBands(clusters)
.reduceConnectedComponents(ee.Reducer.sum(), 'clusters', 256);
Map.addLayer(perimeter, {min: 100, max: 400}, 'Perimeter size', false);
var sizes = ee.Image.pixelLonLat().addBands(clusters).reduceConnectedComponents(ee.Reducer.minMax(), 'clusters', 256)
var width = sizes.select('longitude_max').subtract(sizes.select('longitude_min')).rename('width')
var height = sizes.select('latitude_max').subtract(sizes.select('latitude_min')).rename('height')
Map.addLayer(width, {min:0, max:0.02}, 'Cluster width', false)
Map.addLayer(height, {min:0, max:0.02}, 'Cluster height', false)
var objectPropertiesImage = ee.Image.cat([
snic.select(bands),
stdDev,
area,
perimeter,
width,
height
]).float();
var training = objectPropertiesImage.addBands(cdl2016.select('cropland'))
.updateMask(seeds)
.sample(geometry, 5);
var classifier = ee.Classifier.smileRandomForest(10).train(training, 'cropland')
Map.addLayer(objectPropertiesImage.classify(classifier), {min:0, max:254}, 'Classified objects')
The primary issue is that you're calculating NDVI incorrectly. The NDVI bands for Landsat 8 are B5 and B4; the NDVI bands for Landsat 5 are B4 and B3. In Earth Engine, the .normalizedDifference
method subtracts the second band from the first band and divides the result by the sum of the two bands. You've provided the arguments as .normalizedDifference('B4', 'B5')
instead of .normalizedDifference('B5', 'B4')
. As a result, all of your NDVI scores are negative and the VerDET output is nonsensical. So for Landsat 8, you'd want something like:
// NDVI image collection from Landsat OLI
var collection8 = ee.ImageCollection('LANDSAT/LC08/C01/T1_SR')
.filter(ee.Filter.eq('WRS_PATH', 219))
.filter(ee.Filter.eq('WRS_ROW', 72))
.filterDate('2013-01-01', '2017-12-31')
.filterMetadata('CLOUD_COVER', 'equals', 0)
.map(function(image) {
return image.normalizedDifference(['B5', 'B4'])
});
The other issue is the source of the internal server error. The exact cause of this is not clear, but it's probably due an out-of-memory error. You'll notice that some tiles finish, while other tiles do not. It appears that the edge tiles, with less imagery in them, are completing, which suggests that its a memory error. Furthermore, it seems like the algorithm is able to complete only when running on fewer images. You might try running the code on a collection of annual composites to reduce the number of images that its running on, but the solution is primarily going to have to come from Google; it seems like sometimes this works and sometimes it doesn't.
The following code runs the VerDET analysis on Landsat 8 only:
https://code.earthengine.google.com/25b7db815d4f9c40e56b145b340f4de8
Best Answer
SNIC is just an algorithm, so it can be applied to the layer you want to segment. The "Crop Data Layer" is just an example layer.
I give you a simple example: