Detecção de borda inteligente para processamento de imagem (supressão não máxima e limites altos e baixos)

Método de detecção de arestas astutas

O operador Canny é o artigo "Canny J. Uma abordagem computacional para detecção de borda [J] publicado por John F. Canny em 1986. IEEE Transactions on Pattern Analysis and Machine Intelligence, 1986 (6): 679-698." proposto.

Alvo de detecção:

  • baixa taxa de erro. Todas as arestas devem ser encontradas e não deve haver respostas espúrias. Ou seja, as arestas detectadas devem ser as arestas mais reais possíveis.
  • Os pontos de borda devem estar bem posicionados. As arestas localizadas devem estar o mais próximo possível das arestas reais. Ou seja, a distância entre os pontos marcados como arestas pelo detector e o centro da aresta real deve ser mínima.
  • Resposta de ponto de borda única. Isso significa que o detector não deve apontar para vários pixels de borda onde existe apenas um único ponto de borda.

Etapas do algoritmo astuto

①Desfoque gaussiano - GaussianBlur
②Conversão em escala de cinza - cvtColor
③Gradiente de cálculo - Sobel/Scharr
④Supressão de sinal não máxima
⑤Imagem binária de saída de limite alto e baixo - a proporção de limite alto e baixo é 2:1 ou 3:1 é a melhor

1. Conversão em tons de cinza

Clique em imagem de processamento de imagem em escala de cinza

2. Desfoque gaussiano

Clique em Filtro gaussiano de processamento de imagem para visualizar

3. Calcule o gradiente

Clique em Gradient and Edge Detection Operators of Image Processing para visualizar

4. Supressão não máxima

A supressão de valor não máximo é um passo importante na detecção de bordas.No sentido popular, refere-se a encontrar o valor máximo local de pixels. Ao longo da direção do gradiente, compare o valor do gradiente antes e depois dele e remova-o se não for um máximo local.
insira a descrição da imagem aqui
No papel do operador de Canny proposto por John Canny, a supressão não máxima ocorre apenas em 0 ∘ , 9 0 ∘ , 4 5 ∘ , 13 5 ∘ 0^\circ, 90^\circ, 45^\circ, 135^ \circ09045135 É realizado em quatro direções de gradiente, e a direção do gradiente de cada pixel é substituída por essas quatro direções de acordo com a similaridade. Esses quatro casos também representam quatro gradientes diferentes, ou seja,
G y > ​​​​G x G_y>G_xGvocê>Gx, e ambos têm o mesmo número.
G e > ​​G x G_y>G_xGvocê>Gx, e os dois têm sinais diferentes.
G y < ​​​​G x G_y<G_xGvocê<Gx, e ambos têm o mesmo número.
G y < ​​​​G x G_y<G_xGvocê<Gx, e os dois têm sinais diferentes.
Conforme mostrado na figura acima, de acordo com o tamanho do gradiente na direção X e na direção Y, pode-se julgar se o ponto A está próximo do eixo X ou do eixo Y. O valor de subpixel do ponto A pode ser calculado através dos valores de pixel de A1 e A2. O mesmo vale para o ponto B, não repita mais. As duas imagens acima mostram que o gradiente próximo ao eixo Y é grande, e as duas imagens inferiores mostram que os pixels próximos ao eixo X são grandes.
Como as posições dos pontos A e B são determinadas pelo gradiente, os valores do gradiente dos pontos A e B também podem ser calculados de acordo com o gradiente do ponto Q, portanto, assume-se que os gradientes do ponto Q nas quatro direções são G 1 G_1G1G 2 G_2G2G 3 G_3G3G 4 G_4G4
G y > ​​G x G_y>G_xGvocê>Gx时,w = G x G y , G 1 = ( i − 1 , j ) , G 2 = ( i + 1 , j ) w=\frac{G_x}{G_y},G_1=(i-1,j) ,G_2=(i+1,j)c=GvocêGx,G1=( eu1 ,j ) ,G2=( eu+1 ,j )
Quando ambos têm o mesmo sinal:G 3 = ( i − 1 , j − 1 ) , G 4 = ( i + 1 , j + 1 ) G_3=(i-1,j-1),G_4=(i+ 1,j+1)G3=( eu1 ,j1 ) ,G4=( eu+1 ,j+1 )
Quando os dois têm sinais diferentes:G 3 = ( i − 1 , j + 1 ) , G 4 = ( i + 1 , j − 1 ) G_3=(i-1,j+1),G_4=(i+ 1,j-1)G3=( eu1 ,j+1 ) ,G4=( eu+1 ,j1 )
G y < ​​G x G_y<G_xGvocê<Gx时,w = G y G x , G 1 = ( i , j − 1 ) , G 2 = ( i , j + 1 ) w=\frac{G_y}{G_x},G_1=(i,j-1) ,G_2=(i,j+1)c=GxGvocê,G1=( eu ,j1 ) ,G2=( eu ,j+1 )
Quando ambos têm o mesmo sinal:G 3 = ( i + 1 , j − 1 ) , G 4 = ( i + 1 , j − 1 ) G_3=(i+1,j-1),G_4=(i+ 1,j-1)G3=( eu+1 ,j1 ) ,G4=( eu+1 ,j1 )
Quando os dois têm sinais diferentes:G 3 = ( i − 1 , j − 1 ) , G 4 = ( i + 1 , j + 1 ) G_3=(i-1,j-1),G_4=(i+ 1,j+1)G3=( eu1 ,j1 ) ,G4=( eu+1 ,j+1 )
Dessa forma, o valor do gradiente g A = w ∗ G 1 + ( 1 − w ) ∗ G 3 g B = w ∗ G 2 + ( 1 − w ) ∗ G 4 g_A de dois pontos de subpixel adjacentes pode ser calculado
=w*G_1+(1-w)*G_3\\ g_B=w*G_2+(1-w)*G_4gA=cG1+( 1w )G3gB=cG2+( 1w )G4
Compare os valores de pixel dos três, se o valor de pixel do ponto Q for maior que os outros dois, mantenha o ponto Q como um ponto na borda, caso contrário, considere o ponto Q como um ponto redundante.
código python:

ef NMS(gradients, direction):
    """ Non-maxima suppression

    Args:
        gradients: the gradients of each pixel
        direction: the direction of the gradients of each pixel

    Returns:
        the output image
    """
    W, H = gradients.shape
    nms = np.copy(gradients[1:-1, 1:-1])

    for i in range(1, W - 1):
        for j in range(1, H - 1):
            theta = direction[i, j]
            weight = np.tan(theta)
            if theta > np.pi / 4:
                d1 = [0, 1]
                d2 = [1, 1]
                weight = 1 / weight
            elif theta >= 0:
                d1 = [1, 0]
                d2 = [1, 1]
            elif theta >= - np.pi / 4:
                d1 = [1, 0]
                d2 = [1, -1]
                weight *= -1
            else:
                d1 = [0, -1]
                d2 = [1, -1]
                weight = -1 / weight

            g1 = gradients[i + d1[0], j + d1[1]]
            g2 = gradients[i + d2[0], j + d2[1]]
            g3 = gradients[i - d1[0], j - d1[1]]
            g4 = gradients[i - d2[0], j - d2[1]]

            grade_count1 = g1 * weight + g2 * (1 - weight)
            grade_count2 = g3 * weight + g4 * (1 - weight)

            if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:
                nms[i - 1, j - 1] = 0

    return nms

5. Limite de Rastreamento de Limiar Duplo

Defina dois limites, minVal e maxVal. Qualquer borda com um gradiente maior que maxVal é uma borda verdadeira, enquanto uma borda abaixo de minVal é uma não borda. As bordas situadas entre esses dois limites são classificadas como bordas ou não bordas com base em sua conectividade e são consideradas parte de uma borda se estiverem conectadas a um pixel de "borda confiável"; caso contrário, não são.
código mostra como abaixo:

def double_threshold(nms, threshold1, threshold2):
    """ Double Threshold
    Use two thresholds to compute the edge.

    Args:
        nms: the input image
        threshold1: the low threshold
        threshold2: the high threshold

    Returns:
        The binary image.
    """

    visited = np.zeros_like(nms)
    output_image = nms.copy()
    W, H = output_image.shape

    def dfs(i, j):
        if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:
            return
        visited[i, j] = 1
        if output_image[i, j] > threshold1:
            output_image[i, j] = 255
            dfs(i-1, j-1)
            dfs(i-1, j)
            dfs(i-1, j+1)
            dfs(i, j-1)
            dfs(i, j+1)
            dfs(i+1, j-1)
            dfs(i+1, j)
            dfs(i+1, j+1)
        else:
            output_image[i, j] = 0


    for w in range(W):
        for h in range(H):
            if visited[w, h] == 1:
                continue
            if output_image[w, h] >= threshold2:
                dfs(w, h)
            elif output_image[w, h] <= threshold1:
                output_image[w, h] = 0
                visited[w, h] = 1

    for w in range(W):
        for h in range(H):
            if visited[w, h] == 0:
                output_image[w, h] = 0
    return output_image

O código geral é o seguinte:

# -*- coding: utf-8 -*-
import numpy as np
import cv2
import imgShow as iS

def smooth(image, sigma = 1.4, length = 5):
    """ Smooth the image
    Compute a gaussian filter with sigma = sigma and kernal_length = length.
    Each element in the kernal can be computed as below:
        G[i, j] = (1/(2*pi*sigma**2))*exp(-((i-k-1)**2 + (j-k-1)**2)/2*sigma**2)
    Then, use the gaussian filter to smooth the input image.

    Args:
        image: array of grey image
        sigma: the sigma of gaussian filter, default to be 1.4
        length: the kernal length, default to be 5

    Returns:
        the smoothed image
    """
    # Compute gaussian filter
    k = length // 2
    gaussian = np.zeros([length, length])
    for i in range(length):
        for j in range(length):
            gaussian[i, j] = np.exp(-((i-k) ** 2 + (j-k) ** 2) / (2 * sigma ** 2))
    gaussian /= 2 * np.pi * sigma ** 2
    # Batch Normalization
    gaussian = gaussian / np.sum(gaussian)

    # Use Gaussian Filter
    W, H = image.shape
    new_image = np.zeros([W - k * 2, H - k * 2])

    for i in range(W - 2 * k):
        for j in range(H - 2 * k):
            new_image[i, j] = np.sum(image[i:i+length, j:j+length] * gaussian)

    new_image = np.uint8(new_image)

    return new_image


def get_gradient_and_direction(image):
    """ Compute gradients and its direction
    Use Sobel filter to compute gradients and direction.
         -1 0 1        -1 -2 -1
    Gx = -2 0 2   Gy =  0  0  0
         -1 0 1         1  2  1

    Args:
        image: array of grey image

    Returns:
        gradients: the gradients of each pixel
        direction: the direction of the gradients of each pixel
    """
    Gx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
    Gy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])

    W, H = image.shape
    gradients = np.zeros([W - 2, H - 2])
    direction = np.zeros([W - 2, H - 2])

    for i in range(W - 2):
        for j in range(H - 2):
            dx = np.sum(image[i:i+3, j:j+3] * Gx)
            dy = np.sum(image[i:i+3, j:j+3] * Gy)
            gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)
            if dx == 0:
                direction[i, j] = np.pi / 2
            else:
                direction[i, j] = np.arctan(dy / dx)

    gradients = np.uint8(gradients)

    return gradients, direction


def NMS(gradients, direction):
    """ Non-maxima suppression

    Args:
        gradients: the gradients of each pixel
        direction: the direction of the gradients of each pixel

    Returns:
        the output image
    """
    W, H = gradients.shape
    nms = np.copy(gradients[1:-1, 1:-1])

    for i in range(1, W - 1):
        for j in range(1, H - 1):
            theta = direction[i, j]
            weight = np.tan(theta)
            if theta > np.pi / 4:
                d1 = [0, 1]
                d2 = [1, 1]
                weight = 1 / weight
            elif theta >= 0:
                d1 = [1, 0]
                d2 = [1, 1]
            elif theta >= - np.pi / 4:
                d1 = [1, 0]
                d2 = [1, -1]
                weight *= -1
            else:
                d1 = [0, -1]
                d2 = [1, -1]
                weight = -1 / weight

            g1 = gradients[i + d1[0], j + d1[1]]
            g2 = gradients[i + d2[0], j + d2[1]]
            g3 = gradients[i - d1[0], j - d1[1]]
            g4 = gradients[i - d2[0], j - d2[1]]

            grade_count1 = g1 * weight + g2 * (1 - weight)
            grade_count2 = g3 * weight + g4 * (1 - weight)

            if grade_count1 > gradients[i, j] or grade_count2 > gradients[i, j]:
                nms[i - 1, j - 1] = 0

    return nms


def double_threshold(nms, threshold1, threshold2):
    """ Double Threshold
    Use two thresholds to compute the edge.

    Args:
        nms: the input image
        threshold1: the low threshold
        threshold2: the high threshold

    Returns:
        The binary image.
    """

    visited = np.zeros_like(nms)
    output_image = nms.copy()
    W, H = output_image.shape

    def dfs(i, j):
        if i >= W or i < 0 or j >= H or j < 0 or visited[i, j] == 1:
            return
        visited[i, j] = 1
        if output_image[i, j] > threshold1:
            output_image[i, j] = 255
            dfs(i-1, j-1)
            dfs(i-1, j)
            dfs(i-1, j+1)
            dfs(i, j-1)
            dfs(i, j+1)
            dfs(i+1, j-1)
            dfs(i+1, j)
            dfs(i+1, j+1)
        else:
            output_image[i, j] = 0


    for w in range(W):
        for h in range(H):
            if visited[w, h] == 1:
                continue
            if output_image[w, h] >= threshold2:
                dfs(w, h)
            elif output_image[w, h] <= threshold1:
                output_image[w, h] = 0
                visited[w, h] = 1

    for w in range(W):
        for h in range(H):
            if visited[w, h] == 0:
                output_image[w, h] = 0

    return output_image


if __name__ == "__main__":
    # code to read image
    img=cv2.imread('./originImg/Lena.tif')
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    smoothed_image = smooth(img)
    gradients, direction = get_gradient_and_direction(smoothed_image)
    nms = NMS(gradients, direction)
    output_image = double_threshold(nms, 40, 100)
    imageList = []
    origin_img = [img, 'origin_img']
    imageList.append(origin_img)
    # smoothed= [smoothed_image, ' smoothed_image']
    # imageList.append(smoothed)
    gradient = [gradients, 'gradients']
    imageList.append(gradient)
    nms = [nms, 'nms']
    imageList.append(nms)
    output_images = [output_image, 'output_image']
    imageList.append(output_images)
    iS.showMultipleimages(imageList, 25, 25, './ProcessedImg/canny.jpg')

Resultado dos testes:
insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/weixin_42491648/article/details/131348643
Recomendado
Clasificación