Laboratório 2#
Estatísticas, histogramas, contraste e composições de imagens no GEE
Objetivos:
Operações para calcular estatísticas dos valores dos pixels nas bandas de um raster e gerar gráficos de suas distribuições
Extrair valores de um pixel em uma imagem
Ajustar o contraste de uma imagem adicionada ao mapa e como formar composições coloridas com as bandas dos dados
Introdução#
[ ]:
import ee
import geemap.geemap as geemap
import matplotlib.pyplot as plt
[ ]:
# Ref: https://developers.google.com/earth-engine/apidocs/ee-authenticate
# Para inicializar a sessão para execução insira o id do projeto em ee.Initialize().
ee.Authenticate()
ee.Initialize(project='id_projeto')
[ ]:
%matplotlib inline
Desenvolvimento#
Definir um ponto na cidade de São Paulo
[ ]:
lat, lon = -23.5546721, -46.7318389
poli_usp_point = ee.Geometry.Point(coords=[lon, lat], proj="EPSG:4326")
Definir um bbox na cidade de São Paulo
Observação: Fazer a seleção de um polígono retangular pequeno na área de São Paulo para que o processamento fique mais rápido.
[ ]:
sao_paulo_box = ee.Geometry.BBox(
west=-46.81,
south=-23.5,
east=-46.654,
north=-23.7,
)
Preparando a coleção#
Importar a coleção de images “Sentinel-2 MSI: MultiSpectral Instrument, Level-1C”
[ ]:
colecao_sentinel2 = ee.ImageCollection("COPERNICUS/S2_SR")
# Veja a documentação em: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2_SR#bands
Selecionar apenas as imagens que contém o ponto selecionado
[ ]:
img_histograma = colecao_sentinel2.filterBounds(poli_usp_point)
Filtrar apenas imagens em um intervalo de datas desejado
[ ]:
start_date = ee.Date("2019-01-01")
end_date = ee.Date("2020-01-01")
img_histograma = img_histograma.filterDate(start_date, end_date)
Filtrar apenas as imagens com menos de 20% de cobertura de nuvens
[ ]:
img_histograma = img_histograma.filterMetadata(
"CLOUDY_PIXEL_PERCENTAGE", "less_than", 20
)
Selecionar apenas a primeira imagem da coleção, pois precisamos de somente uma imagem para o restante da prática
[ ]:
img_histograma = img_histograma.first()
Selecionar somente algumas das bandas para análise.
[ ]:
img_histograma = img_histograma.select(["B2", "B4", "B3"])
Fazer um recorte (clip) da imagem para a área o retângulo que definimos anteriormente
[ ]:
img_histograma = img_histograma.clip(sao_paulo_box)
Criar um mapa através do geemap
, biblioteca de visualização de dados geoespaciais
[ ]:
my_map = geemap.Map(center=[lat, lon], zoom=11)
Vamos adicionar uma banda da imagem ao mapa para visualização, isso gerará uma imagem monocromática
[ ]:
my_map.addLayer(
ee_object=img_histograma.select("B2"),
vis_params={},
name="Recorte São Paulo - Banda B2",
)
# O segundo parâmetro da função, que ajusta a visualização, está vazio para manter os valores do padrão.
# O terceiro parâmetro é o título para a camada que é criada no mapa pela função.
Para visualizar o mapa
[ ]:
my_map
Podemos checar o valor da banda B2 para o ponto que criarmos anteriormente:
[ ]:
value = img_histograma.reduceRegion(
reducer=ee.Reducer.mean(), geometry=poli_usp_point, scale=10
)
print("Valor de cada banda no ponto selecionado:")
print(value.getInfo())
Estatísticas dos dados de uma imagem no GEE#
Calcular o desvio padrão dos valores dos pixels de uma imagem com o Reducer.stdDev()
[ ]:
dp_pixels_bandas = img_histograma.reduceRegion(
reducer=ee.Reducer.stdDev(), maxPixels=1e9
)
print("Desvio Padrão: ", dp_pixels_bandas.getInfo())
# OBS: O parâmetro maxPixels é necessário para evitar erros de memória.
Calcular a média dos valores dos pixels de uma imagem com o Reducer.mean()
[ ]:
media_pixels_bandas = img_histograma.reduceRegion(
reducer=ee.Reducer.mean(), maxPixels=1e9
)
print("Média: ", media_pixels_bandas.getInfo())
Calcular a variância dos valores dos pixels de uma imagem com o Reducer.variance()
[ ]:
variancia_pixels_bandas = img_histograma.reduceRegion(
reducer=ee.Reducer.variance(), maxPixels=1e9
)
print("Variância: ", variancia_pixels_bandas.getInfo())
Escalonando os valores da imagem#
Os valores das coleções do Sentinel-2 disponíveis no GEE são os valores da reflectância multiplicados por 10.000 (dez mil). Precisamos dividir os valores por 10.000 para obter os valores de reflectância, que devem obrigatoriamente estar entre 0 e 1.
[ ]:
img_histograma_nao_escalonada = img_histograma.divide(ee.Image.constant(10000))
Gerando histogramas#
Vamos gerar histogramas para visualizar a distribuição dos valores dos pixels de uma imagem. Porém antes de gerar o histograma, vamos recalcular os valores das estatísticas para a imagem escalonada.
[ ]:
dp_pixels_bandas = img_histograma_nao_escalonada.reduceRegion(
reducer=ee.Reducer.stdDev(), maxPixels=1e9
)
print("Desvio Padrão Imagem Não Escalonada: ")
for key, value in dp_pixels_bandas.getInfo().items():
print(f"\tBanda {key}: {value:.4f}")
[ ]:
media_pixels_bandas = img_histograma_nao_escalonada.reduceRegion(
reducer=ee.Reducer.mean(), maxPixels=1e9
)
print("Média Imagem Não Escalonada: ")
for key, value in media_pixels_bandas.getInfo().items():
print(f"\tBanda {key}: {value:.4f}")
[ ]:
variancia_pixels_bandas = img_histograma_nao_escalonada.reduceRegion(
reducer=ee.Reducer.variance(), maxPixels=1e9
)
print("Variância Imagem Não Escalonada: ")
for key, value in variancia_pixels_bandas.getInfo().items():
print(f"\tBanda {key}: {value:.4f}")
Agora, para criação do histograma, vamos selecionar somente a banda B2 da imagem
[ ]:
banda_B2 = img_histograma_nao_escalonada.select("B2")
Utilizamos o ee.Reducer.histogram()
para gerar o histograma.
[ ]:
# maxBuckets: é o número máximo de buckets (colunas) no histograma. Esse valor deve ser uma potência de 2, caso não seja, será arredondado para a potência de 2 mais próxima.
# minBucketWidth: é a largura mínima de cada bucket (coluna) do histograma.
# maxPixels: é o número máximo de pixels que serão usados para calcular o histograma.
histograma_b2 = banda_B2.reduceRegion(
reducer=ee.Reducer.histogram(maxBuckets=1000, minBucketWidth=0.00001), maxPixels=1e9
).getInfo()["B2"]
A variável histograma
é um dicionário, e dela vamos extrair as listas necessárias para realizar o gráfico
[ ]:
frequencias_b2 = histograma_b2["histogram"]
bins_b2 = histograma_b2["bucketMeans"]
Finalmente, vamos plotar o histograma com o módulo matplotlib.pyplot
(ou plt
) que foi importado no início do notebook.
[ ]:
fig, ax = plt.subplots(figsize=(9, 5))
ax.bar(bins_b2, frequencias_b2, width=bins_b2[1] - bins_b2[0], color="gray", alpha=0.7)
ax.plot(bins_b2, frequencias_b2, color="blue", linewidth=2, alpha=1, label="B2")
ax.set_title("Histograma para reflectância em B2")
ax.set_xlabel("Reflectância")
ax.set_ylabel("Frequência")
ax.set_xlim(0, 1)
ax.grid(True, alpha=0.3)
ax.legend()
plt.show()
Gerando histograma para várias bandas#
Primeiro vamos obter o histograma para todas as bandas da imagem de uma vez só. Para isso, basta alterar a imagem que estamos utilizando para a imagem original (sem filtrar a banda B2).
[ ]:
histograma = img_histograma_nao_escalonada.reduceRegion(
reducer=ee.Reducer.histogram(maxBuckets=2**9, minBucketWidth=0.001), maxPixels=1e9
).getInfo()
Utilizar um for
loop do Python para gerar diferentes histogramas para cada banda da imagem.
[ ]:
fig, ax = plt.subplots(figsize=(9, 5))
for i, banda in enumerate(["B2", "B4", "B3"]):
frequencias = histograma[banda]["histogram"]
bins = histograma[banda]["bucketMeans"]
ax.bar(bins, frequencias, width=bins[1] - bins[0], alpha=0.3)
ax.plot(bins, frequencias, linewidth=2, label=banda)
ax.set_title(f"Histograma da reflectância em diferentes bandas")
ax.set_xlabel("Reflectância")
ax.set_ylabel("Frequência")
ax.set_xlim([0, 1])
plt.grid(True, alpha=0.3)
plt.legend(loc="upper right")
plt.show()
Ajustando o contraste de uma imagem#
A imagem que visualizamos anteriormente está com o contraste muito baixo, pois foi adicionada com os valores padrões. Para ajustar, podemos seguir dois caminhos:
Editar o parâmetro «Range» no
control_layer
do mapa, que fica no canto superior direito do mapaRemover o layer e adicionar novamente com os parâmetros desejados
Vamos seguir o caminho 2 na célula a seguir, porém o resultado é o mesmo.
[ ]:
# Remove a camada que tenha o nome "Recorte São Paulo - Banda B2"
# TODO: deve ter uma forma melhor de fazer isso sem usar comprehension list
my_map.remove_layer(
x for x in my_map.layers if x.name == "Recorte São Paulo - Banda B2"
)
# Define parâmetros de visualização
vis_params = {
"bands": ["B2"],
"min": 0,
"max": 1,
"gamma": 1,
}
# Adiciona a imagem novamente ao mapa
my_map.addLayer(
ee_object=img_histograma_nao_escalonada.select("B2"),
vis_params=vis_params,
name="Recorte São Paulo - Banda B2",
)
Finalmente, para visualizar o mapa com o contraste ajustado, basta executar a célula abaixo.
[ ]:
my_map
Composição colorida com ajuste de contraste simples#
Eu sei que vocês estão pensando: «Mas eu quero ver uma imagem colorida, não uma imagem em tons de cinza!». Para isso vamos utilizar o método addLayer()
novamente, porém agora vamos adicionar as bandas RGB da imagem.
As bandas RGB são as bandas 4, 3 e 2, respectivamente. Essa informação é retirada da documentação da coleção Sentinel-2 no GEE: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2_SR
[ ]:
vis_params = {
"bands": ["B4", "B3", "B2"],
"min": 0.04,
"max": 0.4,
"gamma": 1,
}
# Para escolher os valores de “min” e “max”, uma alternativa é utilizar os
# histogramas gerados anteriormente e escolher visualmente os pontos de corte
my_map.addLayer(
ee_object=img_histograma_nao_escalonada.select(["B4", "B3", "B2"]),
vis_params=vis_params,
name="RGB",
)
[ ]:
my_map
Indo além#
Composição colorida com ajustes de contraste individuais#
É possível fazer um mapeamento mais preciso de máximos e mínimos para cada banda, e assim ajustar de forma mais apropriada o contraste da imagem que será exibida na interface do GEE.
Além disso, vamos utilizar os histogramas que geramos anteriormente para definir os valores de máximos e mínimos para cada banda de forma automática.
Para começar, vamos calcular os valores de máximos para exibição de cada banda. Vamos definir que o valor máximo de cada banda será a média mais uma vez o desvio padrão.
[ ]:
max_intervalos = []
media_values = media_pixels_bandas.getInfo().values()
dp_values = dp_pixels_bandas.getInfo().values()
for mean, dp in zip(media_values, dp_values):
max_intervalos.append(mean + 2.5 * dp)
# Obs.: A função zip() retorna um iterador de tuplas, onde a i-ésima tupla contém
# o i-ésimo elemento de cada um dos argumentos sequenciais ou iteráveis.
max_intervalos
Também precisamos definir os valores mínimos para cada banda. Apenas para nos familiarizarmos com a sintaxe, dessa vez vamos evitar utilizar o for
loop do Python, em vez disso, vamos utilizar compreensão de listas (list comprehension). Note que o resultado é o mesmo, apesar de ser um pouco mais difícil de ler, mas é uma forma de escrever código mais «pythônica».
[ ]:
min_intervalos = [
mean - 2.5 * dp
for mean, dp in zip(
media_pixels_bandas.getInfo().values(), dp_pixels_bandas.getInfo().values()
)
]
min_intervalos
Agora adicionamos a imagem ao mapa com os valores de máximos e mínimos definidos para cada banda.
[ ]:
vis_params = {
"bands": ["B4", "B3", "B2"],
"min": min_intervalos,
"max": max_intervalos,
"gamma": 1.4,
}
my_map.addLayer(
ee_object=img_histograma_nao_escalonada.select(["B4", "B3", "B2"]),
vis_params=vis_params,
name="RGB advanced",
)
E finalmente podemos visualizar o mapa com a imagem colorida e com o contraste ajustado.
[ ]:
my_map
Atividade#
Assim como no laboratório anterior, preencha os campos abaixo e submeta este notebook para avaliação.
[ ]:
p1 = "1 - Qual o seu número USP?"
r1 = int() # preencha com um número inteiro, ex: int(12345678)
p2 = "2 - Qual foi o valor médio de pixel na banda 2?"
r2 = int() # preencha com um número inteiro, ex: int(12345678)
p3 = "3 - Calcule o desvio padrão da banda 5."
r3 = float() # preencha com um número real
p4 = "4 - O que a banda 5 representa no conjunto de dados Sentinel-2? (ver a documentação)"
r4 = int() # preencha com um número inteiro
Não altere a célula abaixo, apenas execute-a para carregar o formulário de submissão.
[ ]:
MY_FINAL_RESULT = {
"p1": r1,
"p2": r2,
"p3": r3,
"p4": r4,
}
MY_FINAL_RESULT