Comment puis-je obtenir les points d'intersection des lignes en utilisant l'algorithme de lignes Hough d'opencv?
Voici mon code:
import cv2
import numpy as np
import imutils
im = cv2.imread('../data/test1.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 60, 150, apertureSize=3)
img = im.copy()
lines = cv2.HoughLines(edges,1,np.pi/180,200)
for line in lines:
for rho,theta in line:
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 3000*(-b))
y1 = int(y0 + 3000*(a))
x2 = int(x0 - 3000*(-b))
y2 = int(y0 - 3000*(a))
cv2.line(img,(x1,y1),(x2,y2),(0,255,0),10)
cv2.imshow('houghlines',imutils.resize(img, height=650))
cv2.waitKey(0)
cv2.destroyAllWindows()
Sortie:
Je veux obtenir tous les points d'intersection.
Vous ne voulez pas obtenir les intersections des lignes parallèles; seules les intersections des lignes verticales avec celles des lignes horizontales. De plus, comme vous avez des lignes verticales, le calcul de la pente entraînera probablement une explosion ou des pentes inf, vous ne devez donc pas utiliser les équations y = mx+b
. Vous devez faire deux choses:
Avec HoughLines
, vous avez déjà le résultat sous la forme rho, theta
Afin que vous puissiez facilement segmenter en deux classes d'angle avec theta
. Vous pouvez utiliser par exemple pour cv2.kmeans()
avec theta
comme données que vous souhaitez diviser.
Ensuite, pour calculer les intersections, vous pouvez utiliser la formule pour calculer les intersections à deux points de chaque ligne . Vous calculez déjà deux points à partir de chaque ligne: (x1, y1), (x2, y2)
Afin que vous puissiez simplement les stocker et les utiliser. Edit: En fait, comme vu ci-dessous dans mon code, il existe une formule que vous pouvez utiliser pour calculer les intersections de lignes avec la forme rho, theta
Que HoughLines
donne.
J'ai répondu ne question similaire avant avec du code python que vous pouvez vérifier; notez que cela utilisait HoughLinesP
qui ne vous donne que des segments de ligne.
Vous n'avez pas fourni votre image d'origine, je ne peux donc pas l'utiliser. À la place, j'utiliserai l'image sudoku standard utilisée par OpenCV dans leurs didacticiels de transformation Hough et de seuillage:
Tout d'abord, nous allons simplement lire cette image et la binariser en utilisant un seuillage adaptatif comme ce qui est utilisé dans ce tutoriel OpenCV :
import cv2
import numpy as np
img = cv2.imread('sudoku.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray, 5)
adapt_type = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresh_type = cv2.THRESH_BINARY_INV
bin_img = cv2.adaptiveThreshold(blur, 255, adapt_type, thresh_type, 11, 2)
Ensuite, nous trouverons les lignes de Hough avec cv2.HoughLines()
:
rho, theta, thresh = 2, np.pi/180, 400
lines = cv2.HoughLines(bin_img, rho, theta, thresh)
Maintenant, si nous voulons trouver les intersections, nous voulons vraiment trouver les intersections uniquement des lignes perpendiculaires. Nous ne voulons pas les intersections de lignes principalement parallèles. Nous devons donc segmenter nos lignes. Dans cet exemple particulier, vous pouvez facilement vérifier si la ligne est horizontale ou verticale sur la base d'un simple test; les lignes verticales auront un theta
d'environ 0 ou environ 180; les lignes horizontales auront un theta
d'environ 90. Cependant, si vous voulez les segmenter en fonction d'un nombre arbitraire d'angles, automatiquement, sans que vous définissiez ces angles, je pense que la meilleure idée est d'utiliser cv2.kmeans()
.
Il y a une chose délicate à faire. HoughLines
renvoie des lignes sous la forme rho, theta
( forme normale Hesse ), et le theta
retourné est compris entre 0 et 180 degrés, et les lignes autour de 180 et 0 degrés sont similaires (ils sont tous deux proches des lignes horizontales), nous avons donc besoin d'un moyen d'obtenir cette périodicité dans kmeans
.
Si nous traçons l'angle sur le cercle unitaire, mais multiplions l'angle par deux , alors les angles à l'origine autour de 180 degrés deviendront proches de 360 degrés et donc aura des valeurs de x, y
sur le cercle unitaire presque identiques pour des angles à 0. Nous pouvons donc obtenir une belle "proximité" ici en traçant 2*angle
avec les coordonnées sur le cercle unitaire. Ensuite, nous pouvons exécuter cv2.kmeans()
sur ces points, et segmenter automatiquement avec autant de morceaux que nous voulons.
Construisons donc une fonction pour effectuer la segmentation:
from collections import defaultdict
def segment_by_angle_kmeans(lines, k=2, **kwargs):
"""Groups lines based on angle with k-means.
Uses k-means on the coordinates of the angle on the unit circle
to segment `k` angles inside `lines`.
"""
# Define criteria = (type, max_iter, epsilon)
default_criteria_type = cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER
criteria = kwargs.get('criteria', (default_criteria_type, 10, 1.0))
flags = kwargs.get('flags', cv2.KMEANS_RANDOM_CENTERS)
attempts = kwargs.get('attempts', 10)
# returns angles in [0, pi] in radians
angles = np.array([line[0][1] for line in lines])
# multiply the angles by two and find coordinates of that angle
pts = np.array([[np.cos(2*angle), np.sin(2*angle)]
for angle in angles], dtype=np.float32)
# run kmeans on the coords
labels, centers = cv2.kmeans(pts, k, None, criteria, attempts, flags)[1:]
labels = labels.reshape(-1) # transpose to row vec
# segment lines based on their kmeans label
segmented = defaultdict(list)
for i, line in Zip(range(len(lines)), lines):
segmented[labels[i]].append(line)
segmented = list(segmented.values())
return segmented
Maintenant, pour l'utiliser, nous pouvons simplement appeler:
segmented = segment_by_angle_kmeans(lines)
Ce qui est bien, c'est que nous pouvons spécifier un nombre arbitraire de groupes en spécifiant l'argument optionnel k
(par défaut, k = 2
Donc je ne l'ai pas spécifié ici).
Si nous traçons les lignes de chaque groupe avec une couleur différente:
Et maintenant tout ce qui reste à faire est de trouver les intersections de chaque ligne du premier groupe avec l'intersection de chaque ligne du deuxième groupe. Étant donné que les lignes sont sous forme normale de Hesse, il existe une belle formule d'algèbre linéaire pour calculer l'intersection des lignes de cette forme. Voir ici . Créons ici deux fonctions; une qui trouve l'intersection de seulement deux lignes et une fonction qui parcourt toutes les lignes des groupes et utilise cette fonction plus simple pour deux lignes:
def intersection(line1, line2):
"""Finds the intersection of two lines given in Hesse normal form.
Returns closest integer pixel locations.
See https://stackoverflow.com/a/383527/5087436
"""
rho1, theta1 = line1[0]
rho2, theta2 = line2[0]
A = np.array([
[np.cos(theta1), np.sin(theta1)],
[np.cos(theta2), np.sin(theta2)]
])
b = np.array([[rho1], [rho2]])
x0, y0 = np.linalg.solve(A, b)
x0, y0 = int(np.round(x0)), int(np.round(y0))
return [[x0, y0]]
def segmented_intersections(lines):
"""Finds the intersections between groups of lines."""
intersections = []
for i, group in enumerate(lines[:-1]):
for next_group in lines[i+1:]:
for line1 in group:
for line2 in next_group:
intersections.append(intersection(line1, line2))
return intersections
Ensuite, pour l'utiliser, c'est simplement:
intersections = segmented_intersections(segmented)
Et en traçant toutes les intersections, nous obtenons:
Comme mentionné ci-dessus, ce code peut également segmenter des lignes en plus de deux groupes d'angles. La voici en cours d'exécution sur un triangle dessiné à la main et en calculant les points d'intersection des lignes détectées avec k=3
:
Si vous avez déjà le segment de ligne, remplacez-les simplement dans une équation de ligne ...
x = x1 + u * (x2-x1)
y = y1 + u * (y2-y1)
u peut être trouvé en utilisant l'un des éléments suivants ...
u = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
u = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
Tout d'abord, vous devez affiner la sortie de la transformation de Hough (je le fais généralement par regroupement k-means basé sur certains critères, par exemple la pente et/ou les centroïdes des segments). Dans votre problème, par exemple, il semble que la pente de toutes les lignes se situe généralement à proximité de 0, 180, 90 degrés, vous pouvez donc effectuer un regroupement sur cette base.
Ensuite, il existe deux façons différentes d'obtenir les points d'intersection (qui sont techniquement les mêmes):
P.S. Shapely est un wrapper autour d'une puissante bibliothèque de géométrie C++ mais SymPy est du pur Python. Vous voudrez peut-être tenir compte de cela au cas où votre demande est critique en termes de temps.