Umas das maiores ameaças dentro de um laboratório são as tarefas monótonas. Repetitivas e longas, são as mais propensas ao erro humano. Entretanto, essas atividades são as mais importantes pois estruturam os dados e garantem a consistência de experimentos.
A contagem de sementes é um exemplo clássico de tarefa repetitiva. Feita manualmente, consome muito tempo, atenção e energia. Felizmente, com Python, é possível transformar esse processo em algo mais rápido, preciso e menos cansativo.
Visão Computacional
Essa é uma área da computação que permite que os computadores “vejam” e interpretem o nosso mundo. Essa é uma tecnologia aplicada em diversos segmentos industriais - é, por exemplo, a base dos carros autônomos.
A tecnologia LiDAR (Light Detection and Ranging) utiliza feixes de laser para medir distâncias e objetos em movimentos em tempo real.
Ela funciona emitindo pulsos de infravermelho, que ricocheteiam nos objetos ao redor e retornam para o sensor. O sensor então mede o tempo que cada pulso demorou para retornar.
Mas não precisamos de todo esse aparato tecnológico para contar e medir sementes. Precisamos apenas fotografar nossas sementes e aplicar algoritmos de processamento de imagem.
Príncipios da análise de imagens
Tudo começa com a fotografia. Para garantir resultados confiáveis, precisamos ter imagens de alta resolução e um certo controle do ambiente. A iluminação deve ser o mais uniforme possível para evitar sombras que possam confundir nosso algoritmo. O fundo deve ter contraste suficiente para destacar as sementes.
Idealmente, escolhemos uma superfície neutra, de cor branca ou preta, e reta. Dependendo do tamanho dos objetos que queremos selecionar, uma câmera de smartphone é suficiente. Mas podemos utilizar imagens de microscopia também.
Tamanho digital e tamanho real
Calcular as dimensões de um objeto a partir de uma imagem é possível, mas há dois desafios: primeiro, as medidas iniciais serão dadas em pixels, não em unidades reais como centímetros ou milímetros. Segundo, a dimensão calculada pode variar com o ângulo e a distância da câmera em relação ao objeto.
Convertendo Pixels para Unidades Reais
A conversão de pixels para unidades físicas requer uma escala de referência, que pode ser criada usando um objeto de dimensões conhecidas, como uma régua ou marcador, posicionado no mesmo plano do objeto a ser medido. Geralmente, criamos essa referência desenhando um quadrado de 1x1 cm em um dos cantos do fundo. Com isso, podemos calcular a relação entre o número de pixels e a unidade real.
Onde:
Corrigindo Variações de Ângulo e Distância
A posição da câmera em relação ao objeto pode causar distorções nas dimensões calculadas. Para resolver isso, utilizam-se duas abordagens: calibração da câmera e correção de perspectiva.
Calibração da Câmera. A calibração determina a geometria da câmera ao medir fatores como distância focal e distorções da lente. O processo usa padrões conhecidos, como um tabuleiro de xadrez, para estabelecer a relação entre o espaço tridimensional (mundo real) e bidimensional (imagem). Ferramentas como OpenCV simplificam essa etapa, corrigindo as distorções e garantindo medições precisas.
Correção de Perspectiva. Quando o objeto não está paralelo ao plano da câmera, a perspectiva altera suas dimensões aparentes. A solução é aplicar a transformação de homografia, que ajusta a imagem como se o objeto estivesse sendo visto perpendicularmente. Este processo requer pontos de referência ou o conhecimento da posição relativa entre câmera e objeto.
Para obter medições mais precisas, o ideal é tirar a foto com a câmera alinhada diretamente acima do objeto, sem inclinações. Isso evita distorções e facilita o cálculo das dimensões, reduzindo a necessidade de ajustes complicados depois. No final das contas, sua imagem deve parecer mais ou menos com a de baixo.
Biblioteca PlantCV
PlantCV é um pacote de software de análise de imagem de código aberto, desenvolvido especificamente para ciência vegetal. Esta biblioteca permite medir características de plantas (fenótipos) a partir de imagens. Para melhor aproveitamento, recomenda-se utilizar o PlantCV com Jupyter Notebooks, embora funcione em scripts tradicionais.
A instalação é direta e pode ser feita com o PyPi.
python -m pip install plantcv
Escrevendo o código
Após instalar a biblioteca, crie uma nova pasta para armazenar todos os arquivos, o que tornará a execução do programa mais simples. Salve tanto a imagem quanto o script Python nesta pasta. Iremos começar importanto a biblioteca e criando uma classe de opções.
from plantcv import plantcv as pcv
class Options:
def __init__(self):
self.image = "images/seed_003.jpeg" # a localização da imagem
self.debug = "plot" # utilizamos "plot" para gerar os gráficos de visualização do Jupyter Notebook
args = Options()
Com a classe de opções carregada, definimos essas variáveis na biblioteca. Em seguida, modificamos alguns parâmetros de visualização dos gráficos para melhorar sua apresentação.
pcv.params.debug = args.debug
pcv.params.dpi = 300
pcv.params.text_size = 2
pcv.params.text_thickness = 2
pcv.params.line_thickness = 10
Começaremos carregando nossa imagem, e para isso, utilizaremos a função pcv.readimage()
img, path, filename = pcv.readimage(
filename=args.image, mode="rgba"
)
A variável img
guarda nossa imagem no espaço de cores RGB. Ao executar essa linha de código, a imagem carregada será exibida. Os eixos x e y do gráfico representam a posição de cada pixel na imagem.
Quando visualizamos a imagem, percebemos que ela ainda é apenas uma matriz de valores representando a intensidade das cores vermelho, verde e azul (RGB) em cada pixel. Para iniciar o processamento e a contagem das sementes, o primeiro passo é simplificar esses dados.
Convertendo para Tons de Cinza
Uma imagem em RGB contém três canais de cor, mas para muitas análises, esses detalhes não são necessários. Podemos converter a imagem para tons de cinza, onde cada pixel é representado por um único valor de intensidade. Isso facilita o processamento sem perder informações importantes sobre os contornos e as formas das sementes.
O código para realizar essa conversão é simples:
gray_img = pcv.rgb2gray_lab(rgb_img=img, channel="b")
A função pcv.rgb2gray_lab
permite converter uma imagem no espaço de cores RGB para o espaço de cores LAB e extrair apenas um dos canais LAB: L (luminosidade), A (componentes vermelho-verde) ou B (componentes azul-amarelo). O segundo argumento, channel
, define qual desses canais será retornado.
Como podemos observar, o melhor contraste entre as sementes e o fundo é obtido escolhendo o canal B.
Segmentando a imagem
Com a imagem convertida para escala de cinza, onde cada pixel possui um valor entre 0 (preto) e 255 (branco), podemos avançar para a segmentação. A segmentação mais simples é feita por meio de limiarização (thresholding). Aqui, definimos um valor-limite: pixels com valores acima desse limite são considerados parte das sementes, enquanto os demais são descartados como fundo.
A função pcv.visualize.histogram
é especialmente útil para analisar a distribuição dos valores de intensidade de uma imagem ou de um canal específico. Ela gera um histograma que mostra a frequência de ocorrência de cada valor de pixel, o que ajuda a entender o contraste, a iluminação e a segmentação da imagem.
hist = pcv.visualize.histogram(gray_img)
No histograma mostrado, os picos mais claros (referentes às sementes) estão em torno de 130 a 135, enquanto os pixels mais escuros estão abaixo disso.
Assim, um threshold próximo a 133 seria um bom ponto de corte.
Aplicaremos a limiarização utilizando a função pcv.threshold.binary
, que realiza a separação entre fundo e objeto com base em um valor-limite definido. Essa função converte a imagem em uma binária, onde os pixels abaixo do limiar serão pretos (0), e os acima, brancos (255), destacando as sementes.
img_threshold = pcv.threshold.binary(
gray_img, # imagem em escala de cinza que será processada
threshold=133, # O valor-limite para separar fundo e objeto (133 neste caso, com base no histograma)
object_type="light" # Define se o objeto é "light" (claro em relação ao fundo) ou "dark" (escuro em relação ao fundo).
)
Limpando a imagem
Após realizar a limiarização, podem surgir buracos ou regiões pretas no interior das sementes devido a imperfeições na imagem ou variações de intensidade. Para corrigir isso, utilizaremos a função pcv.fill_holes
, garantindo que cada semente seja representada como uma região sólida.
filled_mask = pcv.fill_holes(img_threshold)
Após preencher os buracos internos com pcv.fill_holes
, é comum encontrar pequenas imperfeições nas bordas ou partículas desconectadas que não fazem parte das sementes. Para suavizar essas irregularidades, utilizaremos a função pcv.fill
, que ajuda a eliminar pequenas lacunas e a unificar áreas de interesse.
filtered_mask = pcv.fill(
bin_img=filled_mask, # imagem binária que será processada
size=100 # Partículas com uma área menor que 100 pixels serão apagadas
)
Contando as sementes
Agora que a imagem está refinada, com sementes bem definidas e ruídos eliminados, podemos aplicar a função pcv.create_labels
. Essa função identifica e rotula cada objeto na imagem binária, permitindo a contagem e o acompanhamento individual de cada semente.
seeds_label, n_seeds = pcv.create_labels(mask=filtered_mask)
print(f"Número total de sementes: {n_seeds}")
# >>> Número total de sementes: 58
Analisando tamanho de sementes
Agora que obtivemos os objetos presentes na imagem, podemos utilizar algumas funções do PlantCV para obter informações interessantes. Uma dessas funções é a pcv.analyze.size
, que calcula métricas relacionadas ao tamanho e à forma de cada objeto rotulado.
pcv.params.sample_label = "seed" # definimos sample_label para organizar nossas observações. Teremos seed_1, seed_2, ..., seed_58
shape_img = pcv.analyze.size(
img=img,
labeled_mask=seeds_label, # utilizamos a máscara que obtivemos com pcv.create_labels
n_labels=n_seeds # definimos também a quantidade de objetos na máscara
)
Os valores calculados pela função pcv.analyze.size
, assim como por outras análises do PlantCV, são armazenados no dicionário pcv.outputs.observations
. Esse dicionário organiza as métricas extraídas com base no rótulo de cada objeto analisado, o sample_label (por exemplo, seed_1
).
Acessando os Resultados
Podemos obter a área, ainda em pixels, de cada semente identificada, iterando sobre os rótulos atribuídos a cada uma. Isso nos dá uma visão detalhada do tamanho de cada semente na imagem.
Aqui está um exemplo prático usando um loop:
for i in range(1, n_seeds + 1):
seed_area_px = pcv.outputs.observations[f"seed_{i}"]["area"]["value"]
print(f"Semente {i} - Área: {seed_area_px} px²")
# >>> Semente 1 - Área: 532.0 px²
# >>> Semente 2 - Área: 650.0 px²
# >>> Semente 3 - Área: 644.0 px²
# >>> ...
# >>> Semente 58 - Área: 612.0 px
A função pcv.analyze.size
fornece várias métricas além da área, todas acessíveis pelo dicionário de observações.
area
- área total ocupada pelo objeto, medida em pixels;center_of_mass
- coordenadas do centro de massa do objeto;convex_hull_area
- área do menor polígono convexo que pode envolver o objeto;convex_hull_vertices
- posição dos vértices que formam o envoltório convexo;ellipse_angle
- ângulo de rotação da elipse que melhor se ajusta ao objeto;ellipse_center
- coordenadas do centro da elipse ajustada;ellipse_eccentricity
- excentricidade da elipse, indica a deformação de um círculo perfeito;ellipse_major_axis
- comprimento do eixo maior da elipse ajustada;ellipse_minor_axis
- comprimento do eixo menor da elipse ajustada;height
- altura do objeto em pixels;in_bounds
- indica se o objeto está completamente dentro da imagem;longest_path
- o comprimento do caminho mais longo dentro do objeto;object_in_frame
- indica se o objeto está visível na imagem;perimeter
- comprimento total das bordas do objeto;solidity
- proporção entre a área do objeto e a área do seu envoltório convexo;width
- largura do objeto em pixels.Convertendo pixels para centimetros com um marcador
As dimensões obtidas pelas análises até aqui estão em pixels, uma unidade relativa que depende da resolução e da distância da câmera. Para convertê-las para centímetros, utilizamos um marcador de tamanho conhecido presente na imagem. Este marcador serve como referência para calcular a relação entre pixels e a unidade de medida desejada.
Para isso, começaremos definindo uma área de interesse (region of interest, ou ROI) na nossa imagem original. Esta área é um retângulo que deve encapsular por completo o marcador que adicionamos.
pcv.params.sample_label = "marker" # sinalizamos que o objeto a ser analisado não é mais uma semente, mas um marcador
roi = pcv.roi.rectangle(
img=img,
x=0, # a posição horizontal inicial do ROI
y=0, # a posição vertical inicial do ROI
h=200, # o comprimento do ROI
w=200 # a largura do ROI
)
Após definir o ROI, utilizamos a função pcv.report_size_marker_area
para identificar o marcador e calcular sua área em pixels. Essa função detecta o marcador baseado na cor ou brilho (canal escolhido) e um limiar de segmentação:
image = pcv.report_size_marker_area(
img=img,
roi=roi,
marker='detect', # Detecta o marcador automaticamente
bg_color='dark', # Especifica se o objeto é mais claro (light) ou escuro (dark) que o fundo
thresh_channel='v', # Escolhemos o canal para a limiarização. opções:
# h - hue (matiz): útil para distinguir cores específicas
# s - saturation (saturação): destaca áreas com cores vibrantes
# v - value (brilho): enfatiza áreas claras ou escuras
thresh=130 # Limiar para segmentação. Similar ao que fizemos para detectar as sementes
)
Calculando a Relação de Conversão
A partir da área detectada do marcador e seu tamanho físico conhecido, calculamos a relação entre pixels e centímetros. Isso nos permitirá converter dimensões de quaisquer objetos na imagem.
marker_area_pixels = pcv.outputs.observations['marker']['marker_area']['value']
marker_real_area_cm2 = 1 # No nosso exemplo, o marcador é um retângulo com 1x1cm
pixels_per_cm2 = marker_area_pixels / marker_real_area_cm2
# Relacionando áreas em pixels para cm²
for i in range(1, n_seeds + 1):
seed_area_px = pcv.outputs.observations[f"seed_{i}"]["area"]["value"]
seed_area_cm2 = seed_area_px / pixels_per_cm2
print(f"Semente {i} - Área: {seed_area_cm2:.3f} cm²")
# >>> Semente 1 - Área: 0.025 cm²
# >>> Semente 2 - Área: 0.030 cm²
# >>> Semente 3 - Área: 0.030 cm²
# >>> ...
# >>> Semente 58 - Área: 0.029 cm²
Com isso, transformamos dados relativos em informações precisas e utilizáveis em análises biológicas ou agrícolas. A automação da contagem e medição de sementes torna-se, assim, uma ferramenta essencial para otimizar o trabalho no laboratório, aumentando a eficiência e a confiabilidade dos resultados.
Salvando os Resultados
Após calcular as áreas das sementes e realizar outras medições, você pode salvar os resultados diretamente em um arquivo CSV utilizando a função do próprio PlantCV. Isso facilita o registro e a análise dos dados em ferramentas externas, como o Excel.
# Salvar os resultados das observações em um arquivo CSV
pcv.outputs.save_observations(filename='seeds_results.csv')
A função save_observations()
do PlantCV salva todas as medições e observações feitas, armazenando-as em um arquivo CSV de forma prática e automatizada.
Assim, você garante que os dados coletados estejam bem organizados e prontos para análises futuras ou relatórios. Mas atenção: as dimensões ainda estarão em pixels! A transformação que fizemos não modificou os resultados no dicionário de observações.
A automação da contagem e medição de sementes com visão computacional torna o processo mais rápido, preciso e menos propenso a erros. Usando o PlantCV, aprendemos a carregar, segmentar e medir sementes, convertendo dimensões de pixels para unidades reais com um marcador de referência.
Esse método, embora técnico, oferece uma maneira mais eficiente e confiável de coletar dados, facilitando o nosso trabalho!