Canny-Edge-Detection-Algorithmus-Python-Implementierung (mit Code)


Zusammenfassung : Der Canny-Kantenerkennungsalgorithmus wurde 1986 vom Informatiker John F. Canny vorgeschlagen. Es stellt nicht nur Algorithmen bereit, sondern bringt auch eine Reihe von Kantenerkennungstheorien mit, in denen erläutert wird, wie die Kantenerkennung schrittweise implementiert wird. Der Canny-Erkennungsalgorithmus besteht aus den folgenden Phasen:

  • Bild Graustufen
  • Gaußsche Unschärfe
  • Bildgradient, Gradientengröße, Berechnung der Gradientenrichtung
  • NMS (Nicht-Maximale Unterdrückung)
  • Grenzauswahl mit doppelten Schwellenwerten

1. Rufen Sie opencv für eine raffinierte Kantenerkennung auf

Wenn Sie Canny nur anwenden möchten, um den Überblick zu behalten, ist es nicht nötig, die spezifischen Prinzipien und die Implementierung von Canny zu lesen. Weil die OpenCV-Bibliothek von Python eine gute Funktion bietet, um diese Funktion zu erreichen. Lassen Sie uns anhand eines Beispiels veranschaulichen, wie man opencv zur Erkennung raffinierter Kanten verwendet:

Dies ist das Originalbild und das Ergebnisbild der raffinierten Kantenerkennung mit opencv:

Fügen Sie hier eine Bildbeschreibung ein

Hier ist die Code-Implementierung:

import cv2 #导入opencv库
 #读取图片
img = cv2.imread("images/2007\_000032.jpg")
#进行canny边缘检测
edge = cv2.Canny(img,50,150)
#保存结果
cv2.imwrite('test.jpg',edge)

Der Schlüssel zu diesen vier Codezeilen ist die Funktion cv2.Canny . Wir erklären seine Parameter im Detail. Ich hoffe, es wird Ihnen helfen. Der Prototyp der Canny-Funktion in OpenCV-Python ist:

cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])

Erforderliche Parameter:

  • Der erste Parameter ist das zu verarbeitende Originalbild, bei dem es sich um ein Einkanal-Graustufenbild handeln muss.
  • Der zweite Parameter ist Schwellenwert 1;
  • Der dritte Parameter ist Threshold2.

Unter diesen wird der größere Schwellenwert 2 verwendet, um offensichtliche Kanten im Bild zu erkennen. Im Allgemeinen ist der Erkennungseffekt jedoch nicht so perfekt und die Kantenerkennung erfolgt intermittierend. Daher wird zu diesem Zeitpunkt ein kleinerer erster Schwellenwert verwendet, um diese diskontinuierlichen Kanten zu verbinden.

Die „apertureSize“ im optionalen Parameter ist die Größe des Sobel-Operators. Der L2gradient-Parameter ist ein boolescher Wert. Wenn er wahr ist, wird die genauere L2-Norm zur Berechnung verwendet (d. h. die Summe der Quadrate der Kehrwerte der beiden Richtungen wird geöffnet), andernfalls wird die L1-Norm verwendet (direkt). der absolute der beiden Richtungsableitungen Wertschöpfung).

An diesem Punkt können Sie tatsächlich eine raffinierte Kantenerkennung auf Ihr Projekt anwenden. Wenn Sie das Prinzip der intelligenten Kantenerkennung verstehen möchten, lesen Sie bitte weiter.

2. Bild-Graustufen

Wenn wir uns bei einem Bild nur um seine Grenze kümmern, reicht ein Einkanalbild aus, um Informationen zur Erkennung der Grenze bereitzustellen. Daher können wir die 3-Kanal-Bilder von R, G und B und sogar höherdimensionale hyperspektrale Fernerkundungsbilder grauskalieren. Bei Graustufen handelt es sich eigentlich um eine Operation zur Dimensionsreduzierung, die redundante Daten reduziert und den Rechenaufwand verringert. Das Folgende ist die Methode für Graustufen-RGB-Bilder:

# 灰度化
def gray(self, img\_path):
	"""
	计算公式:
	Gray(i,j) = [R(i,j) + G(i,j) + B(i,j)] / 3
	or :
	Gray(i,j) = 0.299 \* R(i,j) + 0.587 \* G(i,j) + 0.114 \* B(i,j)
	"""
	
	# 读取图片
	img = plt.imread(img_path)
	# BGR 转换成 RGB 格式
	img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
	# 灰度化
	img_gray = np.dot(img_rgb[...,:3], [0.299, 0.587, 0.114])
	return img_gray

3. Gaußsche Unschärfeverarbeitung

Die Gaußsche Unschärfe entrauscht tatsächlich das Graustufenbild. Aus mathematischer Sicht ist der Gaußsche Unschärfeprozess des Bildes die Faltung des Bildes mit der Normalverteilung. Bevor die Gaußsche Filterung durchgeführt wird, muss zunächst ein Gaußscher Filter (Kernel) erstellt werden. Wie bekomme ich einen Gauß-Filter? Tatsächlich wird die Gaußsche Funktion diskretisiert und die entsprechenden horizontalen und vertikalen Koordinatenindizes im Filter werden in die Gaußsche Funktion eingesetzt, um den entsprechenden Wert zu erhalten. Filter unterschiedlicher Größe haben unterschiedliche Werte. Das Folgende ist die Berechnungsformel der zweidimensionalen Gaußschen Funktion und des (2k+1)x(2k+1)-Filters:

Die häufig verwendete Größe des Gaußschen Filters ist 5x5, σ = 1,4. Das Folgende ist der Implementierungscode des 5x5-Gauß-Filters:

# 去除噪音 - 使用 5x5 的高斯滤波器
def smooth(self, img\_gray):
	# 生成高斯滤波器
	"""
	要生成一个 (2k+1)x(2k+1) 的高斯滤波器,滤波器的各个元素计算公式如下:
	H[i, j] = (1/(2\*pi\*sigma\*\*2))\*exp(-1/2\*sigma\*\*2((i-k-1)\*\*2 + (j-k-1)\*\*2))
	"""
	sigma1 = sigma2 = 1.4
	gau_sum = 0
	gaussian = np.zeros([5, 5])
	for i in range(5):
		for j in range(5):
			gaussian[i, j] = math.exp((-1/(2*sigma1*sigma2))*(np.square(i-3)+ np.square(j-3)))/(2*math.pi*sigma1*sigma2)
			gau_sum = gau_sum + gaussian[i, j]
	
	# 归一化处理
	gaussian = gaussian / gau_sum
	
	# 高斯滤波
	W, H = img_gray.shape
	new_gray = np.zeros([W-5, H-5])
	
	for i in range(W-5):
		for j in range(H-5):
			new_gray[i, j] = np.sum(img_gray[i:i+5, j:j+5] * gaussian)
	
	return new_gray

4. Berechnung des Bildgradienten, der Gradientengröße und der Gradientenrichtung

Die Bedeutung dieses Schrittes kann nicht genug betont werden. Intuitiv wissen wir, dass sich die Pixelwerte in der Nähe der Grenze eines Bildes stark ändern. Die meisten Pixelwerte innerhalb des Objekts sind ähnlich. Auf diese Weise können wir die Differenz zwischen dem Pixelwert des aktuellen Pixels und den Pixeln in der Nähe berechnen, um festzustellen, ob sich das Pixel innerhalb oder an der Grenze des Objekts befindet. Dieser Unterschied wird als Bildgradient bezeichnet. Der Gradientenbetrag und die Gradientenrichtung werden aus dem Bildgradienten berechnet.
Konkret verwenden wir die erste Ableitung, um den Gradienten zu berechnen:

Fügen Sie hier eine Bildbeschreibung ein

Für die obige Formel besteht die eigentliche Operation darin, das aktuelle Pixel vom nächsten Pixel des aktuellen Pixels zu subtrahieren. Zu diesem Zeitpunkt ist Δ x = 1 \Updelta x=1Δx _=1 ;

Der Gradient umfasst den Gradienten in x-Richtung und den Gradienten in y-Richtung. Es sind zwei Vektoren. Die Gradientengröße ist die Vektorsumme dieser beiden Vektoren:
Fügen Sie hier eine Bildbeschreibung ein

Da die Gradientengröße nun ein Vektor ist, müssen wir ihre Richtung berechnen:

[

Wir verwenden den folgenden Code, um Folgendes zu erreichen:

# 计算梯度幅值
def gradients(self, new_gray):
	"""
	:type: image which after smooth
	:rtype:
		dx: gradient in the x direction
		dy: gradient in the y direction
		M: gradient magnitude
		theta: gradient direction
	"""
	W, H = new_gray.shape
	dx = np.zeros([W-1, H-1])
	dy = np.zeros([W-1, H-1])
	M = np.zeros([W-1, H-1])
	theta = np.zeros([W-1, H-1])
	
	for i in range(W-1):
		for j in range(H-1):
		dx[i, j] = new\_gray[i+1, j] - new\_gray[i, j]
		dy[i, j] = new\_gray[i, j+1] - new\_gray[i, j]
		# 图像梯度幅值作为图像强度值
		M[i, j] = np.sqrt(np.square(dx[i, j]) + np.square(dy[i, j]))
		# 计算 θ - artan(dx/dy)
		theta[i, j] = math.atan(dx[i, j] / (dy[i, j] + 0.000000001))
	return dx, dy, M, theta

In der berechneten Gradientengröße haben wir tatsächlich die Grenze des Bildes erhalten (dh M im Rückgabewert der Funktion). folgendermaßen:
Fügen Sie hier eine Bildbeschreibung ein

Es ist jedoch leicht zu erkennen, dass es bei dieser Kante zwei Probleme gibt:

  • dickere Kanten;
  • Viele abgehackte Kanten.

Für diese beiden Probleme gibt es die folgenden zwei Schritte der NMS- und Doppelschwellengrenzenauswahl.

5. NMS (nicht maximale Unterdrückung)

Idealerweise sollten die resultierenden Kanten sehr dünn sein. Daher muss eine nicht maximale Unterdrückung durchgeführt werden, um die Kanten auszudünnen. Das Prinzip ist einfach: Durchlaufen Sie alle Punkte auf der Gradientenmatrix und behalten Sie die Pixel mit Maximalwerten in Kantenrichtung bei. Genau wie das Bild unten. Das Schwarz und Grau in der Abbildung stellen die Grenzen dar. Wir verwenden NMS, um das lokale Maximum (dh das Schwarz im Bild) zu finden und den Wert anderer Positionen (dh das Grau im Bild) auf 0 zu setzen.

Fügen Sie hier eine Bildbeschreibung ein

Lassen Sie uns über die Details von NMS sprechen. NMS wird in acht Feldern ausgeführt: oben, unten, links, rechts, oben links, unten links, oben rechts, unten rechts (natürlich muss dieser Punkt beim Vergleich nicht mit den anderen acht Punkten verglichen werden. Vergleichen Sie ihn einfach mit seiner Gradientenrichtung Dies ist leicht zu verstehen. Da wir nur benötigen, dass der aktuelle Wert ein lokales Maximum an der Kante ist, zu der er gehört, und nicht, dass er an anderen Kanten ein lokales Maximum ist.) Wie in der folgenden Abbildung gezeigt, Die 8 Punkte sind die acht benachbarten Felder.

Fügen Sie hier eine Bildbeschreibung ein

NMS dient dazu, das lokale Maximum zu finden. Daher ist es erforderlich, den Gradienten des aktuellen Pixels mit anderen Richtungen zu vergleichen. Wie in der folgenden Abbildung gezeigt, sind g1, g2, g3 und g4 4 Punkte in den acht Domänen von C, und die blaue Linie ist die Gradientenrichtung von C. Wenn C ein lokales Maximum ist, ist die Gradientenamplitude von Punkt C größer als die Gradientenamplitude der beiden Schnittpunkte der Gradientenrichtungslinie und g1g2, g4g3, dh größer als die Gradientenamplitude der Punkte dTemp1 und dTemp2. Wie oben erwähnt, kann diese Methode nicht den besten Effekt erzielen, da dTemp1 und dTemp2 keine ganzzahligen Pixel, sondern Subpixel sind. Subpixel bedeutet, dass zwischen zwei physischen Pixeln Pixel liegen. Wie findet man also die Gradientengröße des Subpixels? Mit der linearen Interpolationsmethode kann das Gewicht von dTemp1 zwischen g1 und g2 berechnet und anschließend die Gradientengröße ermittelt werden. Berechnet wie folgt:

weight = |gx| / |gy| or |gy| / |gx|
dTemp1 = weight*g1 + (1-weight)*g2
dTemp2 = weight*g3 + (1-weight)*g4

Bei der Berechnung gibt es zwei Situationen (das aktuelle Pixel wird mit der Größe von dtemp1 und dtemp2 verglichen; wenn es größer als diese beiden Werte ist, wird es beibehalten, wenn es kleiner als einer dieser beiden Werte ist, ist sein Wert 0 ):

  • In den folgenden beiden Abbildungen ist der Gradientenwert in y-Richtung relativ groß, dh die Gradientenrichtung liegt nahe an der y-Achse. Daher befinden sich g2 und g4 an der oberen und unteren Position von C und das Gewicht ist zu diesem Zeitpunkt = |gy| / |gx|. Die Abbildung links zeigt den Fall, bei dem die Vorzeichen der Gradienten in x- und y-Richtung gleich sind, und das Bild rechts zeigt den Fall, bei dem die Vorzeichen der Gradienten in x- und y-Richtung entgegengesetzt sind.
    Fügen Sie hier eine Bildbeschreibung ein

  • In den folgenden beiden Abbildungen ist der Gradientenwert in x-Richtung relativ groß, dh die Gradientenrichtung liegt nahe an der x-Achse. Daher befinden sich g2 und g4 an der linken und rechten Position von C und das Gewicht ist zu diesem Zeitpunkt = |gy| / |gx|. Die Abbildung links zeigt den Fall, bei dem die Vorzeichen der Gradienten in x- und y-Richtung gleich sind, und das Bild rechts zeigt den Fall, bei dem die Vorzeichen der Gradienten in x- und y-Richtung entgegengesetzt sind.

Der Code ist wie folgt implementiert:

def NMS(self, M, dx, dy):
	d = np.copy(M)
	W, H = M.shape
	NMS = np.copy(d)
	NMS[0, :] = NMS[W-1, :] = NMS[:, 0] = NMS[:, H-1] = 0
	for i in range(1, W-1):
		for j in range(1, H-1):
			# 如果当前梯度为0,该点就不是边缘点
			if M[i, j] == 0:
				NMS[i, j] = 0
			else:
				gradX = dx[i, j] # 当前点 x 方向导数
				gradY = dy[i, j] # 当前点 y 方向导数
				gradTemp = d[i, j] # 当前梯度点
				
				# 如果 y 方向梯度值比较大,说明导数方向趋向于 y 分量
				if np.abs(gradY) > np.abs(gradX):
					weight = np.abs(gradX) / np.abs(gradY) # 权重
					grad2 = d[i-1, j]
					grad4 = d[i+1, j]

					# 如果 x, y 方向导数符号一致
					# 像素点位置关系
					# g1  g2
					#     c
					#     g4  g3

					if gradX * gradY > 0:
						grad1 = d[i-1, j-1]
						grad3 = d[i+1, j+1]

					# 如果 x,y 方向导数符号相反
					# 像素点位置关系
					#     g2  g1
					#     c
					# g3  g4
					
					else:
						grad1 = d[i-1, j+1]
						grad3 = d[i+1, j-1]

				# 如果 x 方向梯度值比较大
				else:
					weight = np.abs(gradY) / np.abs(gradX)
					grad2 = d[i, j-1]
					grad4 = d[i, j+1]
					
					# 如果 x, y 方向导数符号一致
					# 像素点位置关系
					#      g3
					# g2 c g4
					# g1
					if gradX * gradY > 0:
						grad1 = d[i+1, j-1]
						grad3 = d[i-1, j+1]
					
					# 如果 x,y 方向导数符号相反
					# 像素点位置关系
					# g1
					# g2 c g4
					#      g3
					else:
						grad1 = d[i-1, j-1]
						grad3 = d[i+1, j+1]

				# 利用 grad1-grad4 对梯度进行插值
				gradTemp1 = weight \* grad1 + (1 - weight) \* grad2
				gradTemp2 = weight \* grad3 + (1 - weight) \* grad4
				
				# 当前像素的梯度是局部的最大值,可能是边缘点
				if gradTemp >= gradTemp1 and gradTemp >= gradTemp2:
					NMS[i, j] = gradTemp
				else:
					# 不可能是边缘点
					NMS[i, j] = 0

	return NMS

6. Grenzauswahl der doppelten Schwelle

In dieser Phase wird entschieden, welche Kanten echte Kanten sind und welche nicht. Hierzu müssen zwei Schwellenwerte festgelegt werden, minVal und maxVal. Jede Kante mit einem Gradienten größer als maxVal ist definitiv eine echte Kante, während eine Kante unter minVal definitiv keine Kante ist und daher verworfen wird. 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 wird es ebenfalls verworfen. Der Code sieht so aus:

def double\_threshold(self, NMS):
	W, H = NMS.shape
	DT = np.zeros([W, H])
	
	# 定义高低阈值
	TL = 0.1 \* np.max(NMS)
	TH = 0.3 \* np.max(NMS)
	
	for i in range(1, W-1):
		for j in range(1, H-1):
			# 双阈值选取
			if (NMS[i, j] < TL):
				DT[i, j] = 0
			elif (NMS[i, j] > TH):
				DT[i, j] = 1
			# 连接
			elif (NMS[i-1, j-1:j+1] < TH).any() or (NMS[i+1, j-1:j+1].any() or (NMS[i, [j-1, j+1]] < TH).any()):
				DT[i, j] = 1	
	return DT

Nach Abschluss aller Schritte ist das Ergebnis in der folgenden Abbildung dargestellt:

Acho que você gosta

Origin blog.csdn.net/xijuezhu8128/article/details/129856373
Recomendado
Clasificación