Individual Tree Detection with lidR
This project explored methods for detecting individual trees from airborne LiDAR data using both point-cloud and canopy height model (CHM) approaches. Plot-level LiDAR subsets from the Malcolm Knapp Research Forest were analyzed in R using the lidR package to segment tree crowns and identify tree locations.
Interactive!
Project Workflow
Step 01
Extract and Prepare Plot-Level LiDAR Point Clouds
LiDAR data from the Malcolm Knapp Research Forest were subset into four circular plots (154 m diameter) using plot coordinates provided in a reference dataset. Outlier points below ground level and above 65 m were removed to ensure that the normalized point cloud represented realistic vegetation structure before further analysis.
#read in plot table
plot_table <- read.csv("data/Lab4_Plots.csv")
#define plot radius
radius <- 77
#for loop to extract multiple plots
for(i in 1:nrow(plot_table)){ #run the loop until i = the number of rows in 'Lab4_plots'
plot_cent <- c(plot_table$X[i], plot_table$Y[i]) #extract plot center
plot_las <- clip_circle(norm_cat_mkrf, plot_cent[1], plot_cent[2], radius) #clip plot from norm_cat_mkrf
output_file <- paste("data/Plots/MKRF_Plot_", i, ".las", sep = "") #output directory as string
writeLAS(assign(paste("MKRF_Plot_", i, sep = ""), plot_las), output_file) #write'MKRF_Plot_i' to output dir.
}
Step 02
Detect Individual Trees Using a Point-Cloud Segmentation Algorithm
Individual trees were first detected directly from the LiDAR point cloud using the Li et al. (2012) segmentation algorithm implemented in the lidR package in R. The algorithm identifies tree tops and groups nearby points into individual crowns using adaptive distance thresholds based on tree height.
#combine plots into a list
plots <- list(
MKRF_Plot_1,
MKRF_Plot_2,
MKRF_Plot_3,
MKRF_Plot_4)
#create empty list with same length #as number of plots
li2012_trees <- vector("list", length(plots))
for (i in seq_along(plots)) {
#segment each plot in the list using the Li 2012 algorithm
li2012_trees[[i]] <- segment_trees(plots[[i]], li2012())
#plot so each colour corresponds to treeID/one tree
plot(li2012_trees[[i]], color = "treeID")
}
Step 03
Generate a Canopy Height Model and Locate Tree Tops
A canopy height model (CHM) was generated from the LiDAR data using the pit-free algorithm to create a continuous representation of canopy height. Tree tops were then detected from the CHM using a local maximum filter, which identifies peaks in canopy height that likely represent individual trees.
#just working with Plot 1 for this step
#first create a CHM for Plot 1
chm_0.5_plot1 <- rasterize_canopy(MKRF_Plot_1,
res = 0.5, #spatial resolution
pitfree(
thresholds = c(0, 10, 20, 30), #height thresholds
subcircle = 0.2, #subcircle with 20cm radius
max_edge = c(0,1) #classical triangulation threshold = 0, maximum edge length 1
))
#determine tree tops for Plot 1
#using lmf algorithm specifying a circular moving window of 5m and minimum tree height of 2m
treetops_plot1 <- locate_trees(MKRF_Plot_1, lmf(ws = 5, hmin = 2, shape = "circular"))
Step 04
Segment Tree Crowns Using CHM-Based Analysis
Tree crown segmentation was then performed using the Dalponte and Coomes (2016) algorithm, which expands individual tree crowns outward from detected tree tops based on canopy height thresholds. Segmentation results were compared across multiple CHM spatial resolutions to evaluate how raster resolution affects the accuracy of tree detection and crown delineation.
#use Dalponte algorithm for segmentation with a CHM of 4m resolution
dalponte_trees_4 <- segment_trees(MKRF_Plot_2,
dalponte2016(chm = chm_4_plot2,
treetops = treetops_plot2))
Project Visuals
CHM with treetops
One LiDAR tile for Malcolm Knapp Research Forest
LiDAR point-cloud for one plot
3-D plot of segmented trees