【GEE笔记】最大类间方差法(otsu、大津法)算法实现——计算阈值、图像二值化分割

主要内容

1、最大类间方差法原理概述

2、GEE频率分布统计,直方图绘制

3、算法具体实现,以GEE JavaScript版本为例

4、目标像元提取,以遥感影像提取水体为示例

算法原理

概念

最大类间方差法(又名otsu、大津法)是由日本学者OTSU于1979年提出的一种对图像进行二值化的高效算法。算法假定该图像根据频率分布直方图把包含两类像元(前景像元和背景像元),计算能将两类分开的最佳阈值,使得它们的类内方差最小;由于两两平方距离恒定,所以即它们的类间方差最大。

优点:计算简单快速,不受图像亮度和对比度的影响。

缺点:对图像噪声敏感;如果图像中的前景像元和背景像元的面积相差很大,直方图没有明显的双峰,或者两个峰的大小相差很大,分割效果不佳。实际应用中,常结合其他方法。

原理

大津法借助穷举法搜索能使类内方差最小的阈值,计算两个类的方差的加权和,权值为两类各自的概率,前人证明了最小化类内方差和最大化类间方差是相同的。

图像的总平均灰度为:M=P1(t) * M0 + P2(t) * M1

前景与背景像元类间方差:S=P1(t) * (M1 - M) * (M1 - M) + P2(t) * (M2 - M) * (M2 - M)

t为前景与背景的分割阈值,前景像元占图像比例为P1(t),平均灰度为M1;背景像元占图像比例为P2(t),平均灰度为M1。

算法实现

数据准备

1、原始影像:定义示例矢量区域geometry(山东省潍坊市峡山水库周边,便于提取水体),时间范围,云量低于20%,筛选出符合条件的Landsat8影像集dataset,中值合成得到示例影像l8_image 

var geometry = 
    ee.Geometry.Polygon(
        [[[119.3140376290338, 36.559328749628065],
          [119.3140376290338, 36.263933411986294],
          [119.62234146204162, 36.263933411986294],
          [119.62234146204162, 36.559328749628065]]], null, false);

var dataset = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    .filterDate('2021-10-01', '2021-12-01')
    .filterBounds(geometry)
    .filter(ee.Filter.lt('CLOUD_COVER', 20));


function applyScaleFactors(image) {
    var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
    var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
    return image.addBands(opticalBands, null, true)
        .addBands(thermalBands, null, true);
}

//获取图像 中值合成
var l8_image = dataset
    .map(applyScaleFactors)
    .median().clip(geometry);

//可视化
var visualization = {
    bands: ['SR_B4', 'SR_B3', 'SR_B2'],
    min: 0.0,
    max: 0.3,
};
Map.centerObject(geometry);
Map.addLayer(l8_image, visualization, 'True Color (432)');

原始影像真彩色显示:

2、提取NDWI:后续用于计算阈值,识别水体和非水体

NDWI(Normalized Difference Water Index,归一化水指数),用遥感影像的特定波段进行归一化差值处理,以凸显影像中的水体信息。其表达式为:

NDWI =(p(Green)-p(NIR))/(p(Green)+p(NIR))

是基于绿波段与近红外波段的归一化比值指数,一般用来提取影像中的水体信息,效果较好。

var ndwi = l8_image.normalizedDifference(['SR_B3', 'SR_B5']).float().rename('l8_NDWI');
print("ndwi", ndwi)
var visParams1 = {
    min: 0, max: 1, palette: ['FFFFFF', 'CE7E45', 'DF923D', 'F1B555', 'FCD163', '99B718',
        '74A901', '66A000', '529400', '3E8601', '207401', '056201',
        '004C00', '023B01', '012E01', '011D01', '011301']
};
Map.addLayer(ndwi, visParams1, "l8_NDWI");

NDWI灰度显示:

数据查看

NDWI频数分布直方图计算并显示,可见有明显双峰,且两类没有重叠,非常便于运用大津法计算分割阈值,其中参数: 最大组数maxBuckets 最小组距minBucketWidth设置较为关键,具体其他参数参阅官方文档

GEE右上角“Download”可以将csv数据下载到本地进行分析

//频数分布直方图
var chart = ui.Chart.image.histogram({
    image: ndwi,
    region: geometry,
    scale: 30,
    maxBuckets: 1000,//最大组数
    minBucketWidth: 0.01, //最小组距
    // maxRaw, 
    maxPixels: 1e13
})
print(chart)

 计算阈值

首先计算频数分布数据,同前文注意参数设置,之后利用穷举法计算类内方差,得到类间方差结果表,按照方差排序,得到方差最大对应的值即为最佳阈值,

阈值=0.06508403641771252,可见符合频数分布直方图示意

详细过程见代码注释

//计算OTSU阈值 
var yuzhi = otsu(ndwi)
print("阈值", yuzhi)

//OTSU
function otsu1(histogram) {
    // 各组频数
    var counts = ee.Array(ee.Dictionary(histogram).get('histogram'))
    // 各组的值
    var means = ee.Array(ee.Dictionary(histogram).get('bucketMeans'))
    // 组数
    var size = means.length().get([0])
    // 总像元数量
    var total = counts.reduce(ee.Reducer.sum(), [0]).get([0])
    // 所有组的值之和
    var sum = means.multiply(counts).reduce(ee.Reducer.sum(), [0]).get([0])
    // 整幅影像的均值
    var mean = sum.divide(total)

    // 与组数相同长度的索引
    var indices = ee.List.sequence(1, size)
    // 穷举法计算类内方差
    var bss = indices.map(function (i) {
        // 当 i = 1, aCounts = [counts[0]], 当 i = 2, aCounts = [counts[0], counts[1]] 
        //从i分割为两类A、B 计算A方差
        var aCounts = counts.slice(0, 0, i)
        var aCount = aCounts.reduce(ee.Reducer.sum(), [0]).get([0])
        var aMeans = means.slice(0, 0, i)
        // 类别A均值
        var aMean = aMeans.multiply(aCounts)
            .reduce(ee.Reducer.sum(), [0]).get([0])
            .divide(aCount)

        var bCount = total.subtract(aCount)
        // 类别B均值
        var bMean = sum.subtract(aCount.multiply(aMean)).divide(bCount)
        //类间方差公式
        return aCount.multiply(aMean.subtract(mean).pow(2)).add(
            bCount.multiply(bMean.subtract(mean).pow(2)))
    })

    print('类间方差', ui.Chart.array.values(ee.Array(bss), 0, means))

    // 排序选出最适阈值
    return means
        .sort(bss)
        .get([-1])
}
function otsu(image) {
    var histogram = image.reduceRegion({
        reducer: ee.Reducer.histogram(1000, 0.01),// 自行修改合适的最大组数,最小组距
        geometry: geometry,
        scale: 30,
        bestEffort: true,
        // tileScale:16
    });
    print("频数分布", histogram)
    return otsu1(histogram.get(histogram.keys().get(0)));
}

分割图像

借助前文得到的的阈值进行影像二值化分割,得到水体示意图

var result = ndwi.gt(yuzhi)
Map.addLayer(result.randomVisualizer(), "", "water");

结果图:

代码附录

链接:https://code.earthengine.google.com/4456dc4c799a8673d0f1aec1431250f4

var geometry = 
    /* color: #d63000 */
    /* shown: false */
    /* displayProperties: [
      {
        "type": "rectangle"
      }
    ] */
    ee.Geometry.Polygon(
        [[[119.3140376290338, 36.559328749628065],
          [119.3140376290338, 36.263933411986294],
          [119.62234146204162, 36.263933411986294],
          [119.62234146204162, 36.559328749628065]]], null, false);

var dataset = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
    .filterDate('2021-10-01', '2021-12-01')
    .filterBounds(geometry)
    .filter(ee.Filter.lt('CLOUD_COVER', 20));


function applyScaleFactors(image) {
    var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
    var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
    return image.addBands(opticalBands, null, true)
        .addBands(thermalBands, null, true);
}

//获取图像 中值合成
var l8_image = dataset
    .map(applyScaleFactors)
    .median().clip(geometry);

//可视化
var visualization = {
    bands: ['SR_B4', 'SR_B3', 'SR_B2'],
    min: 0.0,
    max: 0.3,
};
Map.centerObject(geometry);
Map.addLayer(l8_image, visualization, 'True Color (432)');

var ndwi = l8_image.normalizedDifference(['SR_B3', 'SR_B5']).float().rename('l8_NDWI');
print("ndwi", ndwi)
var visParams1 = {
    min: 0, max: 1
};
Map.addLayer(ndwi, visParams1, "l8_NDWI");

//频数分布直方图
var chart = ui.Chart.image.histogram({
    image: ndwi,
    region: geometry,
    scale: 30,
    maxBuckets: 1000,//最大组数
    minBucketWidth: 0.01, //最小组距
    // maxRaw, 
    maxPixels: 1e13
})
print(chart)

//计算OTSU阈值 分割图像
var yuzhi = otsu(ndwi)
print("阈值", yuzhi)
var result = ndwi.gt(yuzhi)
Map.addLayer(result.randomVisualizer(), "", "water");
//
OTSU/

function otsu1(histogram) {
    // 各组频数
    var counts = ee.Array(ee.Dictionary(histogram).get('histogram'))
    // 各组的值
    var means = ee.Array(ee.Dictionary(histogram).get('bucketMeans'))
    // 组数
    var size = means.length().get([0])
    // 总像元数量
    var total = counts.reduce(ee.Reducer.sum(), [0]).get([0])
    // 所有组的值之和
    var sum = means.multiply(counts).reduce(ee.Reducer.sum(), [0]).get([0])
    // 整幅影像的均值
    var mean = sum.divide(total)

    // 与组数相同长度的索引
    var indices = ee.List.sequence(1, size)
    // 穷举法计算类内方差
    var bss = indices.map(function (i) {
        // 当 i = 1, aCounts = [counts[0]], 当 i = 2, aCounts = [counts[0], counts[1]] 
        //从i分割为两类A、B 计算A方差
        var aCounts = counts.slice(0, 0, i)
        var aCount = aCounts.reduce(ee.Reducer.sum(), [0]).get([0])
        var aMeans = means.slice(0, 0, i)
        // 类别A均值
        var aMean = aMeans.multiply(aCounts)
            .reduce(ee.Reducer.sum(), [0]).get([0])
            .divide(aCount)

        var bCount = total.subtract(aCount)
        // 类别B均值
        var bMean = sum.subtract(aCount.multiply(aMean)).divide(bCount)
        //类间方差公式
        return aCount.multiply(aMean.subtract(mean).pow(2)).add(
            bCount.multiply(bMean.subtract(mean).pow(2)))
    })

    print('类间方差', ui.Chart.array.values(ee.Array(bss), 0, means))

    // 排序选出最适阈值
    return means
        .sort(bss)
        .get([-1])
}
function otsu(image) {
    var histogram = image.reduceRegion({
        reducer: ee.Reducer.histogram(1000, 0.01),
            // .combine('mean', null, true)
            // .combine('variance', null, true),
        geometry: geometry,
        scale: 30,
        bestEffort: true,
        // tileScale:16
    });
    print("频数分布", histogram)
    return otsu1(histogram.get(histogram.keys().get(0)));
}

  • 20
    点赞
  • 152
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
以下是GEE基于滑动窗口法主成分分析合成遥感图像的JavaScript代码实现的一个示例: ```javascript // 设置滑动窗口的大小,这里设为3x3的窗口 var windowSize = 3; // 加载需要进行主成分分析的遥感图像 var image = ee.Image('LANDSAT/LC08/C01/T1_TOA/LC08_123032_20140515'); // 获取图像的波段数 var numBands = image.bandNames().size(); // 定义一个滑动窗口的核心函数 var slidingWindow = function(img) { // 将图像转换为一个数组 var arr = img.toArray(); // 获取图像的行和列数 var rows = img.size()[0]; var cols = img.size()[1]; // 定义一个空的结果数组 var out = ee.List([]); // 循环遍历每一个像素点 for (var i = windowSize; i <= rows - windowSize; i++) { for (var j = windowSize; j <= cols - windowSize; j++) { // 定义一个滑动窗口 var window = arr.slice(i - windowSize, j - windowSize, i + windowSize + 1, j + windowSize + 1); // 将滑动窗口转换为一个矩阵 var windowMatrix = ee.Array(window); // 对矩阵进行主成分分析 var pca = windowMatrix.reduceRegion({ reducer: ee.Reducer.pca(numBands), geometry: ee.Geometry.Rectangle(i - windowSize, j - windowSize, i + windowSize, j + windowSize), scale: 30 }); // 将主成分分析的结果添加到结果数组中 out = out.add(ee.Feature(null, pca)); } } // 将结果数组转换为一个特征集合 return ee.FeatureCollection(out); }; // 对图像进行滑动窗口主成分分析 var pcaImage = slidingWindow(image); // 将主成分分析的结果可视化 Map.addLayer(pcaImage, {}, 'PCA Image'); ``` 需要注意的是,这只是一个简单的示例,实际应用中可能需要对代码进行调整以满足不同的需求。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

runepic

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值