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)));
}