web-dev-qa-db-fra.com

trouver le point d'intersection de deux lignes tracées à l'aide de lignes de hough opencv

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:

Output

Je veux obtenir tous les points d'intersection.

14
Nauman Umer

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:

  1. Segmentez vos lignes en deux classes en fonction de leur angle.
  2. Calculez les intersections de chaque ligne d'une classe avec les lignes des autres classes.

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.


Exemple de code

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:

Sudoku image

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)

Sudoku image binarized

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)

Sudoku image with Hough lines

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:

Segmented lines

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:

Intersections


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:

Triangle intersections

34
alkasm

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))
4
Bhupen

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):

  1. Les équations dans la réponse de Bhupen.
  2. Utilisation d'une bibliothèque de géométrie comme Shapely ou SymPy . L'avantage de faire cela avec une bibliothèque de géométrie est que vous avez accès à une variété d'outils dont vous pourriez avoir besoin plus tard dans le développement (intersection, interpolation, coque convexe, etc., etc.)

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.

1
Wildhammer