Segmentação de imagens utilizando técnicas deThresholding

Aprenda a Segmentação de Imagens com Thresholding Adaptive, Otsu e Riddler-Calvard usando OpenCV.

Jonys Arcanjo
Data Hackers
10 min readJul 24, 2023

--

Introdução

Thresholding é um processo de conversão de uma imagem em escala de cinza para uma imagem binária, onde os pixels são representados por apenas dois valores: 0 ou 255.

Um exemplo simples de thresholding envolve a seleção de um valor de pixel, denominado p, e a definição de todos os pixels com intensidades inferiores a p como zero (preto) e todos os pixels com intensidades superiores a p como 255 (branco). Essa abordagem nos permite criar uma representação binária da imagem, destacando as áreas de interesse com base no valor de threshold escolhido.

O thresholding é uma técnica comumente usada para segmentar objetos ou áreas específicas de uma imagem, facilitando a sua análise e extração de informações relevantes. A escolha adequada do valor de threshold é essencial para obter resultados precisos e satisfatórios, e isso muitas vezes requer experimentação e ajuste de acordo com a natureza da imagem e do problema em questão.

Simple Thresholding

A aplicação de métodos de thresholding simples envolve a intervenção humana, onde é necessário especificar um valor de threshold, denotado por T. Nesse processo, todas as intensidades de pixel abaixo de T são definidas como 0, enquanto todas as intensidades de pixel acima de T são definidas como 255.

Além disso, é possível realizar a inversão desse processo de binarização, onde todos os pixels abaixo de T são definidos como 255 e todas as intensidades de pixel acima de T são definidas como 0. Essa abordagem é útil em determinadas situações onde o interesse está nas áreas com intensidades mais altas.

Agora vamos analisar um codigo utilizando o Simple Thresholding.

import numpy as np
import argparse
import cv2

# Importar bibliotecas necessárias

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="Caminho para a imagem")
args = vars(ap.parse_args())

No código acima estamos Importando das bibliotecas necessárias: numpy, argparse e cv2.

Ele utiliza as bibliotecas numpy para manipulação de arrays multidimensionais, argparse para lidar com argumentos de linha de comando e cv2 (OpenCV) para realizar operações de processamento de imagem. Em seguida, é criado um objeto ArgumentParser para analisar os argumentos de linha de comando.

É definido um argumento -i ou --image que é obrigatório e representa o caminho para a imagem que será processada. Os argumentos são armazenados em um dicionário args retornado pelo método parse_args().

# Argumentos da linha de comando: caminho para a imagem

image = cv2.imread(args["image"]) # Carregar a imagem
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Converter para escala de cinza
blurred = cv2.GaussianBlur(image, (5, 5), 0) # Aplicar desfoque Gaussiano
cv2.imshow("Imagem", image) # Mostrar a imagem original

Nesta parte do código, o caminho da imagem fornecido como argumento de linha de comando é usado para carregar a imagem usando a função cv2.imread(). Em seguida, a imagem é convertida para escala de cinza usando cv2.cvtColor(), que aplica a conversão de espaço de cores de BGR (padrão do OpenCV) para escala de cinza.

O próximo passo é aplicar um desfoque Gaussiano na imagem em escala de cinza usando cv2.GaussianBlur(), onde uma janela de 5x5 é utilizada para calcular a média ponderada dos pixels vizinhos. Por fim, a imagem original é exibida em uma janela com o nome "Imagem" usando cv2.imshow().

# Limiarização binária

(T, thresh) = cv2.threshold(blurred, 155, 255, cv2.THRESH_BINARY)
cv2.imshow("Limiarização Binária", thresh)

# Limiarização binária inversa

(T, threshInv) = cv2.threshold(blurred, 155, 255, cv2.THRESH_BINARY_INV)
cv2.imshow("Limiarização Binária Inversa", threshInv)

# Aplicar máscara para revelar as moedas

cv2.imshow("Moedas", cv2.bitwise_and(image, image, mask=threshInv))
cv2.waitKey(0)

Nesta parte do código, são aplicadas técnicas de limiarização na imagem com Blur(embaçado). Primeiro, é feita a limiarização binária, onde a função cv2.threshold() é utilizada para separar os pixels em duas categorias: aqueles abaixo de um valor de limiar (no caso, 155) são definidos como 0, enquanto os acima desse valor são definidos como 255. O resultado é armazenado em thresh e exibido em uma janela com o nome "Limiarização Binária".

Em seguida, é realizada a limiarização binária inversa, onde os pixels abaixo do valor de limiar são definidos como 255 e os acima como 0.

O resultado é armazenado em threshInv e exibido em uma janela com o nome "Limiarização Binária Inversa".

Por fim, é aplicada uma máscara na imagem original para revelar apenas as regiões de interesse (no caso, as moedas), utilizando cv2.bitwise_and() e a máscara threshInv.

O resultado é exibido em uma janela com o nome "Moedas". A função cv2.waitKey(0) é utilizada para esperar até que uma tecla seja pressionada, mantendo as janelas abertas para visualização das imagens geradas.

1. Aprimeira imagem da esquerda e a imagem original das moedas é em escala de cinza.
2. Na segunda aplicamos limiarização binária simples, as moedas são mostradas em preto e o fundo em branco.
3. Na terceira imagem aplicamos a limiarização binária inversa, as moedas agora são brancas e o fundo é preto.
4. No lado direito, temos a limiarização binária inversa aplicada como uma máscara na imagem em escala de cinza. Agora estamos focados apenas nas moedas na imagem, ou seja, a nossa região de interesse.

Adaptive Thresholding

Uma das desvantagens do uso de métodos simples de thresholding é a necessidade de fornecer manualmente o valor de threshold T. Além disso, encontrar um valor adequado de T requer muitos experimentos manuais e ajustes de parâmetros, o que não é muito útil quando a imagem possui uma ampla gama de intensidades de pixels.

Simplificando, ter apenas um valor de T pode não ser o ideal. Para superar essa limitação, podemos recorrer ao thresholding adaptativo, que leva em consideração pequenas vizinhanças de pixels e encontra um valor de threshold ótimo T para cada vizinho. Esse método nos permite lidar com casos em que as intensidades de pixels apresentam variações significativas e o valor ideal de T pode variar em diferentes partes da imagem.

Resumindo, o thresholding adaptativo nos permite ajustar o valor de threshold de forma adaptativa, levando em conta o contexto local de cada pixel. Isso é especialmente útil quando a imagem exibe variações significativas nas intensidades de pixels e um único valor de threshold não é suficiente para segmentar corretamente as regiões de interesse.

Agora vamos analisar um codigo utilizando o Adaptive Thresholding.

import numpy as np
import argparse
import cv2

# Importar bibliotecas necessárias

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="Caminho para a imagem")
args = vars(ap.parse_args())

Primeiro vamos importar as bibliotecas necessárias (numpy, argparse e cv2) para o processamento de imagens. Ele também define um argumento de linha de comando para o caminho da imagem.

Em seguida, o objeto ArgumentParser é criado para analisar os argumentos de linha de comando. É definido um argumento -i ou --image que é obrigatório e representa o caminho para a imagem que será processada. Os argumentos são armazenados em um dicionário chamado args usando vars(ap.parse_args()).

# Argumentos da linha de comando: caminho para a imagem

image = cv2.imread(args["image"]) # Carregar a imagem
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# Converter para escala de cinza
blurred = cv2.GaussianBlur(image, (5, 5), 0)# Aplicar desfoque Gaussiano
cv2.imshow("Imagem", image) # Mostrar a imagem original

Nesta parte do código, é realizada a manipulação da imagem especificada no caminho fornecido como argumento de linha de comando. Primeiro, a imagem é carregada usando cv2.imread e armazenada na variável image.

Em seguida, a imagem é convertida para escala de cinza usando cv2.cvtColor, onde o espaço de cores BGR é convertido para escala de cinza. Em seguida, é aplicado um desfoque Gaussiano na imagem em escala de cinza usando cv2.GaussianBlur, onde uma máscara de desfoque de tamanho 5x5 é utilizada.

Por fim, a imagem original é exibida em uma janela chamada "Imagem" usando cv2.imshow.

# Limiarização adaptativa usando o método da média

thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 4)
cv2.imshow("Limiar_Media", thresh)

# Limiarização adaptativa usando o método gaussiano

thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 15, 3)
cv2.imshow("Limiar_Gaussiana", thresh)

cv2.waitKey(0)

Nesta parte do código, são aplicadas técnicas de limiarização adaptativa na imagem borrada. A primeira limiarização adaptativa é feita usando o método da média, onde a função cv2.adaptiveThreshold() é usada.

É passado o valor 255 como limite máximo para os pixels, cv2.ADAPTIVE_THRESH_MEAN_C como método de limiarização adaptativa baseado na média, cv2.THRESH_BINARY_INV para inverter a imagem binarizada, o tamanho da vizinhança como 11 e o valor C como 4. O resultado é armazenado em thresh e exibido em uma janela chamada "Limiar_Media".

Em seguida, é realizada uma segunda limiarização adaptativa usando o método gaussiano, onde os parâmetros são semelhantes, mas com o método cv2.ADAPTIVE_THRESH_GAUSSIAN_C e um tamanho de vizinhança de 15 e valor C de 3.

O resultado é armazenado em thresh e exibido em uma janela chamada "Limiar_Gaussiana". Por fim, a função cv2.waitKey(0) é usada para aguardar até que uma tecla seja pressionada, mantendo as janelas abertas para visualização das imagens geradas.

Otsu and Riddler-Calvard

O método de Otsu, também conhecido como limiarização de Otsu, é um algoritmo de limiarização automática amplamente utilizado. Ele assume que o histograma em escala de cinza de uma imagem contém dois picos, correspondendo a pixels de primeiro plano e de fundo.

O objetivo do método de Otsu é encontrar um valor de limiar ótimo que maximize a variância entre as classes, separando efetivamente os pixels de primeiro plano e de fundo. Ele realiza isso iterativamente, testando diferentes valores de limiar e calculando a variância ponderada para cada valor, selecionando aquele com a maior variância entre as classes.

Por outro lado, o método Riddler-Calvard, também conhecido como limiarização de Riddler-Calvard, é outro método de limiarização automática. Similar ao método de Otsu, ele também assume a existência de dois picos no histograma de uma imagem. O método de Riddler-Calvard calcula um valor de limiar ótimo baseado na minimização da entropia intraclasse, ou seja, busca um valor de limiar que minimize a incerteza ou desordem dentro de cada classe.

Agora vamos analisar um codigo utilizando o Otsu e o Riddler-Calvard.

from __future__ import print_function
import numpy as np
import argparse
import mahotas
import cv2

# Importar bibliotecas necessárias

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required=True,
help="Caminho para a imagem")
args = vars(ap.parse_args())

Primeiro importa as bibliotecas necessárias, incluindo numpy, argparse, mahotas e cv2. Em seguida, é criado um objeto ArgumentParser para lidar com os argumentos de linha de comando.

É definido um argumento -i ou --image que é obrigatório e representa o caminho para a imagem que será processada. Os argumentos são armazenados em um dicionário chamado args usando vars(ap.parse_args()).

Isso permite que o caminho da imagem seja acessado posteriormente no código para carregar e processar a imagem.

# Argumentos da linha de comando: caminho para a imagem

image = cv2.imread(args["image"]) # Carregar a imagem
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Converter para escala de cinza
blurred = cv2.GaussianBlur(image, (5, 5), 0) # Aplicar desfoque Gaussiano
cv2.imshow("Image", image) # Mostrar a imagem original

Nesta parte do código, é realizada a manipulação da imagem especificada no caminho fornecido como argumento de linha de comando. Primeiro, a imagem é carregada usando cv2.imread e armazenada na variável image.

Em seguida, a imagem é convertida para escala de cinza usando cv2.cvtColor, onde o espaço de cores BGR é convertido para escala de cinza. Em seguida, é aplicado um desfoque Gaussiano na imagem em escala de cinza usando cv2.GaussianBlur, onde uma máscara de desfoque de tamanho 5x5 é utilizada. Por fim, a imagem original é exibida em uma janela chamada "Image" usando cv2.imshow.

# Limiar de Otsu

T = mahotas.thresholding.otsu(blurred)
print("Limiar de Otsu: {}".format(T))

thresh = image.copy()
thresh[thresh > T] = 255
thresh[thresh < 255] = 0
thresh = cv2.bitwise_not(thresh)
cv2.imshow("Otsu", thresh)

# Limiar de Riddler-Calvard

T = mahotas.thresholding.rc(blurred)
print("Limiar de Riddler-Calvard: {}".format(T))
thresh = image.copy()
thresh[thresh > T] = 255
thresh[thresh < 255] = 0
thresh = cv2.bitwise_not(thresh)
cv2.imshow("Riddler-Calvard", thresh)

cv2.waitKey(0)

Nesta parte do código, são calculados e aplicados os limiares de Otsu e Riddler-Calvard na imagem blurred (imagem embaçada).

Para o limiar de Otsu, a função mahotas.thresholding.otsu() é utilizada para calcular o limiar ótimo com base na distribuição de intensidade dos pixels. O valor do limiar é armazenado na variável T e impresso na tela.

Em seguida, uma cópia da imagem é feita e a limiarização de Otsu é aplicada, onde os pixels acima do limiar são definidos como 255 (branco) e os pixels abaixo do limiar são definidos como 0 (preto). A imagem resultante é invertida usando cv2.bitwise_not e exibida em uma janela chamada "Otsu".

Para o limiar de Riddler-Calvard, a função mahotas.thresholding.rc() é utilizada para calcular o limiar com base na técnica Riddler-Calvard. O valor do limiar é armazenado em T e impresso na tela.

Da mesma forma que para o limiar de Otsu, uma cópia da imagem é feita e a limiarização de Riddler-Calvard é aplicada, com os pixels acima do limiar definidos como 255, os pixels abaixo do limiar definidos como 0 e a imagem resultante invertida. A imagem é então exibida em uma janela chamada "Riddler-Calvard".

Por fim, a função cv2.waitKey(0) é utilizada para aguardar até que uma tecla seja pressionada, mantendo as janelas abertas para visualização.

  1. A primeira imagem da esquerda é a imagem original.
  2. Na segunda imagem utilizamos a técnica de limiarização de Otsu.
  3. Na terceira imagem utilizamos a técina de limialização de Riddler-Calvard.

Conclusão

este artigo exploramos o conceito de thresholding e sua aplicação na segmentação de imagens. Concluiu-se que o thresholding é uma técnica valiosa para segmentar objetos e áreas de interesse, sendo essencial a escolha adequada do método e valor de threshold para obter resultados precisos na segmentação de imagens.

--

--