Clevere Kantenerkennungsmethode
Der Canny-Operator wird in der Arbeit „Canny J. A computational Approach to Edge Detection [J] veröffentlicht von John F. Canny im Jahr 1986. IEEE Transactions on Pattern Analysis and Machine Intelligence, 1986 (6): 679–698.“ vorgeschlagen.
Erkennungsziel:
- geringe Fehlerquote. Alle Kanten sollten gefunden werden und es sollte keine falschen Reaktionen geben. Das heißt, die erkannten Kanten müssen möglichst echte Kanten sein.
- Kantenpunkte sollten gut positioniert sein. Die gefundenen Kanten müssen den realen Kanten möglichst nahe kommen. Das heißt, der Abstand zwischen einem vom Detektor als Kante markierten Punkt und der Mitte der wahren Kante sollte minimal sein.
- Einzelkantenpunktreaktion. Das bedeutet, dass der Detektor nicht auf mehrere Kantenpixel zeigen sollte, bei denen nur ein einziger Kantenpunkt vorhanden ist.
Clevere Algorithmusschritte
①Gaußsche Unschärfe – GaussianBlur
②Graustufenkonvertierung – cvtColor
③Gradient berechnen – Sobel/Scharr
④Nicht maximale Signalunterdrückung
⑤Binärbild mit hoher und niedriger Schwelle ausgeben – das Verhältnis von hoher und niedriger Schwelle beträgt 2:1 oder 3:1 ist am besten
1. Graustufenkonvertierung
Klicken Sie auf Bildverarbeitungsbild- Graustufenansicht
2. Gaußsche Unschärfe
Klicken Sie zum Anzeigen auf den Gaußschen Filter der Bildverarbeitung
3. Berechnen Sie den Gradienten
Klicken Sie zum Anzeigen auf „Gradienten- und Kantenerkennungsoperatoren der Bildverarbeitung“.
4. Nicht maximale Unterdrückung
Die Unterdrückung nicht maximaler Werte ist ein wichtiger Schritt bei der Kantenerkennung. Im allgemeinen Sinne bezieht sie sich auf das Ermitteln des lokalen Maximalwerts von Pixeln. Vergleichen Sie entlang der Gradientenrichtung den Gradientenwert davor und danach und entfernen Sie ihn, wenn es sich nicht um ein lokales Maximum handelt.
In dem von John Canny vorgeschlagenen Canny-Operatorpapier liegt die nicht maximale Unterdrückung nur bei 0 ∘ , 9 0 ∘ , 4 5 ∘ , 13 5 ∘ 0^\circ, 90^\circ, 45^\circ, 135^ \circ0∘、90∘、45∘、135∘ Es wird in vier Gradientenrichtungen ausgeführt, und die Gradientenrichtung jedes Pixels wird entsprechend der Ähnlichkeit durch diese vier Richtungen ersetzt. Diese vier Fälle stellen auch vier verschiedene Gradienten dar, nämlich
G y > G x G_y>G_xGy>Gx, und beide haben die gleiche Nummer.
G y > G x G_y>G_xGy>Gx, und die beiden haben unterschiedliche Vorzeichen.
G y < G x G_y<G_xGy<Gx, und beide haben die gleiche Nummer.
G y < G x G_y<G_xGy<Gx, und die beiden haben unterschiedliche Vorzeichen.
Wie in der Abbildung oben gezeigt, kann anhand der Größe des Gradienten in X-Richtung und Y-Richtung beurteilt werden, ob Punkt A nahe an der X-Achse oder der Y-Achse liegt. Der Subpixelwert von Punkt A kann berechnet werden durch die Pixelwerte von A1 und A2. Dasselbe gilt für Punkt B, nicht mehr wiederholen. Die beiden Bilder oben zeigen, dass der Farbverlauf in der Nähe der Y-Achse groß ist, und die beiden Bilder unten zeigen, dass die Pixel in der Nähe der X-Achse groß sind.
Da die Positionen der Punkte A und B durch den Gradienten bestimmt werden, können die Gradientenwerte der Punkte A und B auch anhand des Gradienten des Punkts Q berechnet werden, sodass davon ausgegangen wird, dass die Gradienten des Punkts Q in den vier Richtungen liegen sind G 1 G_1G1、G 2 G_2G2、G 3 G_3G3、G 4 G_4G4。
当G y > G x G_y>G_xGy>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)w=GyGx,G1=( ich−1 ,j ) ,G2=( ich+1 ,j )
Wenn beide das gleiche Vorzeichen haben: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=( ich−1 ,J−1 ) ,G4=( ich+1 ,J+1 )
Wenn die beiden unterschiedliche Vorzeichen haben: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=( ich−1 ,J+1 ) ,G4=( ich+1 ,J−1 )
当G y < G x G_y<G_xGy<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)w=GxGy,G1=( ich ,J−1 ) ,G2=( ich ,J+1 )
Wenn beide das gleiche Vorzeichen haben: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=( ich+1 ,J−1 ) ,G4=( ich+1 ,J−1 )
Wenn die beiden unterschiedliche Vorzeichen haben: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=( ich−1 ,J−1 ) ,G4=( ich+1 ,J+1 )
Auf diese Weise kann der Gradientenwert g A = w ∗ G 1 + ( 1 − w ) ∗ G 3 g B = w ∗ G 2 + ( 1 − w ) ∗ G 4 g_A zweier benachbarter Subpixelpunkte ermittelt werden berechnet werden
=w*G_1+(1-w)*G_3\\ g_B=w*G_2+(1-w)*G_4GA=w∗G1+( 1−w )∗G3GB=w∗G2+( 1−w )∗G4
Vergleichen Sie die Pixelwerte der drei. Wenn der Pixelwert von Punkt Q größer als der der anderen beiden ist, behalten Sie Punkt Q als Punkt am Rand bei, andernfalls betrachten Sie Punkt Q als redundanten Punkt.
Python-Code:
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. Duale Schwellenwert-Tracking-Grenze
Legen Sie zwei Schwellenwerte fest: minVal und maxVal. Jede Kante mit einem Gradienten größer als maxVal ist eine echte Kante, während eine Kante unter minVal keine Kante ist. Kanten, die zwischen diesen beiden Schwellenwerten liegen, werden aufgrund ihrer Konnektivität als Kanten oder Nichtkanten klassifiziert und gelten als Teil einer Kante, wenn sie mit einem „zuverlässigen Kanten“-Pixel verbunden sind. Andernfalls ist dies nicht der Fall.
Code wie folgt anzeigen:
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
Der Gesamtcode lautet wie folgt:
# -*- 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')
Testergebnisse: