【碳汇】区域尺度(县、市)森林植被碳储量估算

前言

单纯依赖样地估测法或机载激光雷达测量方法进行某个县、市的森林植被碳储量估算,仍面临时间过长的问题。
在区域尺度上进行森林植被碳储量的反演,许多研究已经开始利用多光谱卫星影像提取植被的光谱特征,并基于一些植被指数与实测样方的生物量进行建模,以估算区域尺度的碳储量。随着GEDI和GLAS激光雷达数据的公开,激光雷达数据能够提取冠层高度这一重要的结构性指标,但由于其分辨率较低,通常需要结合卫星影像才能进行区域尺度上森林冠层高度的连续分布反演,进而实现森林植被碳储量或生物量估算的反演。

Aboveground-carbon-stock-distribution-map-for-secondary-Andean-forests-in-the-Tabio-test_Q320.jpg

本文将基于Google Earth Engine(GEE)平台,利用Sentinel数据与GEDI数据,介绍讨如何进行森林碳储量的反演。

反演思路:

  1. 利用GEDI数据结合Landsat或Sentinel光学影像数据,生成区域尺度上连续分布的森林冠层高度模型;
  2. 获取目标县市内树种分布的矢量数据;若没有此类数据,可使用已公开的土地覆盖类型数据;
  3. 对不同森林类型的冠层高度与相应的实测碳储量或生物量数据进行建模,最终通过训练好的模型反演整个区域的森林植被碳储量。

实际操作:

1、森林冠层高度反演

代码思路:

  1. 提取光学影像中的重要指数和波段特征,常见的处理方法可参考相关文献。
  2. 对GEDI数据进行质量筛选,并考虑坡度等因素进行进一步的筛选。
  3. 因为GEDI光斑存在位置偏移,需对其进行重采样到更粗的分辨率。有一篇文章为了提高分辨率,筛选光斑中心位置与影像像元中心位置很近的光斑,以减小位置偏移的影响,并将其仅重采样至30m分辨率。并将GEDI提取的冠层高度指数与影像计算的指数进行图层叠加
  4. 根据筛选后的光斑位置进行样本提取,即提取改光斑位置对应的树高指数和光谱指数,用来构建森林冠层高度的反演模型。

Map.centerObject(boundary,9)

var startDate=ee.Date.fromYMD(2021,4,1)
var endDate=ee.Date.fromYMD(2022,10,30)

// 加载Sentinel-2光谱反射率数据
//这一部分是为了合成光学影像提取植被指数和光谱属性
// sentinal_2 data mask clouds

var s2 = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED");
var s2Filter=s2.filterDate(startDate,endDate)
              .filterBounds(boundary)
              .filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 20))
var s2projection=s2Filter.first().select("B3").projection()
function maskS2clouds(image) {
  var qa = image.select('QA60');
  var cloudBitMask = 1 << 10;
  var cirrusBitMask = 1 << 11;
  var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
      .and(qa.bitwiseAnd(cirrusBitMask).eq(0));
  return image.updateMask(mask).divide(10000)
  ;}
  
// Function to compute spectral indices
var addIndices = function(image) {
  var ndvi = image.normalizedDifference(['B8', 'B4']).rename('ndvi');
  var evi = image.expression(
    '2.5 * ((NIR - RED)/(NIR + 6*RED - 7.5*BLUE + 1))', {
      'NIR': image.select('B8'),
      'RED': image.select('B4'),
      'BLUE': image.select('B2')}).rename('evi');
  return image
    .addBands(ndvi)
    .addBands(evi);};
// remove cloudy and then add indices
var S2Processed = s2Filter
  .map(maskS2clouds)
  .map(addIndices);
 
// 坐标转换
var S2_composite = S2Processed.median().reproject({crs: 'EPSG:4326', scale: 100})


                
print('Projection, crs, and crs_transform:', S2_composite.projection());


Map.addLayer(S2_composite.clip(boundary), {bands: ['B11', 'B8', 'B3'], min: 0, max: 0.3});

// 这一部分加载DEM数据,并计算高程,坡度和坡向

// 加载 SRTM
var SRTM = ee.Image("USGS/SRTMGL1_003");
//裁剪研究区
var elevation = SRTM.clip(boundary);

//坐标转换                 
var elevation = elevation.reproject({crs: 'EPSG:4326',scale: 100}); 
  
print('Projection, crs, and crs_transform:', elevation.projection()); 

// 提取坡度
var slope = ee.Terrain.slope(SRTM).clip(boundary);

               
var slope = slope.reproject({crs: 'EPSG:4326',scale: 100}); 
  
print('Projection, crs, and crs_transform:', slope.projection());

// 加载土地覆盖类型数据,筛选出森林类别

森林
// 加载ESA World Cover数据
var dataset = ee.ImageCollection("ESA/WorldCover/v100").first();

// 裁剪研究区
var ESA_LC_2020 = dataset.clip(boundary);

// 从土地覆盖中提取森林区域
var forest_mask = ESA_LC_2020.updateMask(ESA_LC_2020.eq(10) );

// 显示森林
var visualization = {bands: ['Map'],};
Map.addLayer(forest_mask, visualization, "Trees");

// 合并预测变量,并导入GEDI中的RH95变量。

// 合并预测变量
var mergedCollection = S2_composite
  .addBands(elevation
  .addBands(slope));


// 准备训练数据集


// 定义mask
var qualityMask = function(im) {
  return im.updateMask(im.select('quality_flag').eq(1))
      .updateMask(im.select('degrade_flag').eq(0));
};

// 导入数据。筛选gedi光斑数据
var dataset = ee.ImageCollection('LARSE/GEDI/GEDI02_A_002_MONTHLY')
                  .filterDate(startDate, endDate)
                  .map(qualityMask)
                  .select('rh95').filterBounds(boundary);
var gediMosaic = dataset.mosaic().clip(boundary)

光斑

// var clippedmergedCollection=mergedCollection.addBands(gediMosaic)
//                           .clip(boundary)
var stacked=mergedCollection.addBands(gediMosaic).reproject({crs: 'EPSG:4326',scale: 100})
var predictors = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8',
'B11', 'B12','ndvi','evi', 'elevation', 'slope'];
var predicted =['rh95']
var predictorImage = stacked.select(predictors);
var predictedImage = stacked.select(predicted);
 
var classMask = predictedImage.mask().toInt().gt(0).rename('class').clip(boundary);
//有值为1无值为0,加上.gt(0)才会有问题
print(classMask);
// print(predictedImage);
// print(predictedImage.mask().toInt());
Map.addLayer(classMask, {min: 0, max: 1}, 'Class Mask');
var numSamples = 1000
//分层抽样,class必须为整数
var training = stacked.addBands(classMask)
  .stratifiedSample({
    numPoints: numSamples,
    classBand: 'class',
    region: boundary,
    scale: 100,
    classValues: [0,1],
    classPoints: [0, numSamples],  
    dropNulls: false,//这里我选择false,否则失败,采样为0
    tileScale: 16
});
 
// output mode to REGRESSION
var model = ee.Classifier.smileRandomForest(50)
  .setOutputMode('REGRESSION')
  .train({
    features: training,
    classProperty: 'rh95',
    inputProperties: predictors
  });
 
// Get model predictions for training samples
var predicted = training.classify({
  classifier: model,
  outputName: 'height_predicted'
});

// Calculate RMSE
var calculateRmse = function(input) {
    var observed = ee.Array(
      input.aggregate_array("rh95"));
    var predicted = ee.Array(
      input.aggregate_array('height_predicted'));
    var rmse = observed.subtract(predicted).pow(2)
      .reduce('mean', [0]).sqrt().get([0]);
    return rmse;
};
var rmse = calculateRmse(predicted);
print('RMSE', rmse)
 
// Create a plot of observed vs. predicted values
var chart = ui.Chart.feature.byFeature({
  features: predicted.select(['rh95', 'height_predicted']),
  xProperty: 'rh95',
  yProperties: ['height_predicted'],
}).setChartType('ScatterChart')
  .setOptions({
    title: 'Tree height (m)',
    dataOpacity: 0.8,
    hAxis: {'title': 'Observed'},
    vAxis: {'title': 'Predicted'},
    legend: {position: 'right'},
    series: {
      0: {
        visibleInLegend: false,
        color: '#525252',
        pointSize: 3,
        pointShape: 'triangle',
      },
    },
    trendlines: {
      0: {
        type: 'linear', 
        color: 'black', 
        lineWidth: 1,
        pointSize: 0,
        labelInLegend: 'Linear Fit',
        visibleInLegend: true,
        showR2: true
      }
    },
    chartArea: {left: 100, bottom:100, width:'50%'},
 
});
print(chart);
var predictedImage = stacked.classify({
  classifier: model,
  outputName: 'height'
});

var exportPath = 'users/foundation/gedi_map/';
Export.image.toAsset({
  image: predictedImage.updateMask(forest_mask).clip(boundary),
  description: 'Predicted_Image_Export',
  assetId: exportPath + 'predicted_height',
  region: boundary,
  scale: 100,
  maxPixels: 1e10
});


// // 导出图像,指定比例和区域
// Export.image.toDrive({
//   image: predictedImage.updateMask(forest_mask).clip(geometry),
//   description: 'height',
//   scale: 100,
//   crs: 'EPSG:4326', 
//   maxPixels: 6756353855,
//   region: boundary
// });

结果:我这里将结果导入到asset里,方便计算生物量时调用


2、估测森林生物量

导入生物量的实测数据,建模估测,这部分代码与上面估测冠层高度类似。

var col = ee.FeatureCollection(table);  // 实测生物量数据
var chm = image;  // 冠层高度模型数据
Map.centerObject(col, 9);
// 1.提取CHM值与生物量数据
// 使用`sample`方法从CHM图像中提取每个样地中心点的CHM值,并与生物量数据匹配
var samples = chm.sampleRegions({
  collection: col,
  properties: ['carbon'],  // 假设生物量数据存储在'biomass'属性中
  scale: 100  // CHM的分辨率
});

// 2.拆分训练和验证数据 
var split = 0.8;  // 80%数据用于训练,20%用于验证
var training = samples.filter(ee.Filter.lt('system:index', split));
var validation = samples.filter(ee.Filter.gte('system:index', split));

// 3. 构建回归模型
var model = ee.Classifier.smileLinear().train({
  features: training,
  classProperty: 'carbon',
  inputProperties: ['CHM']
});

// 4.用回归模型进行预测,用训练好的模型预测生物量
var carbonPrediction = chm.classify(model);
Map.addLayer(carbonPrediction, {min: 0, max: 100}, 'Predicted carbon');
// 5. 使用验证数据评估模型
var validationPredictions = validation.classify(model);
var rmse = validationPredictions
  .errorMatrix('carbon', 'classification')
  .accuracy();
print('RMSE:', rmse);

这里我没有生物量实测数据,也就没必要展示结果了

参考:
《全球陆地碳汇的遥感和优化计算方法》陈镜明
《GEDI L4A Footprint Level Aboveground Biomass Density, Version2.1》
https://spatialthoughts.com/2024/02/07/agb-regression-gee/
《Assessing Protected Area's Carbon Stocks and Ecological Structure at Regional-Scale Using Gedi Lidar》Mengyu Liang

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容