web-dev-qa-db-ja.com

houghlines opencvを使用して描かれた2本の線の交点を見つける

Opencv Houghラインアルゴリズムを使用してラインの交点を取得するにはどうすればよいですか?

ここに私のコードがあります:

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

出力:

Output

すべての交差点を取得したい。

14
Nauman Umer

平行線の交点を取得する必要はありません。垂直線と水平線の交点のみ。また、垂直線があるため、勾配を計算すると爆発またはinf勾配が生じる可能性が高いため、_y = mx+b_方程式を使用しないでください。次の2つのことを行う必要があります。

  1. 角度に基づいて線を2つのクラスに分割します。
  2. 1つのクラスの各線と他のクラスの線の交点を計算します。

HoughLinesの場合、結果はすでに_rho, theta_であるため、thetaを使用して2つのクラスの角度に簡単にセグメント化できます。に使用できますcv2.kmeans()thetaを分割するデータとして。

次に、交点を計算するために、 各線から2点を与えて交点を計算する の式を使用できます。すでに各行から2つのポイントを計算しています:_(x1, y1), (x2, y2)_単に保存して使用することができます。編集:実際には、私のコードで以下に示すように、HoughLinesが与える_rho, theta_フォームとの行の交差を計算するために使用できる式があります。

私は 同様の質問 前にいくつかのpythonをチェックアウトできるコードで答えました。これは、線セグメントのみを提供するHoughLinesPを使用していたことに注意してください。


コード例

元の画像を提供しなかったため、使用できません。代わりに、OpenCVのハフ変換およびしきい値設定のチュートリアルで使用される標準の数独イメージを使用します。

Sudoku image

最初に、この画像を読み取り、 このOpenCVチュートリアル で使用されているような適応しきい値を使用して2値化します。

_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

次に、cv2.HoughLines()でハフ行を見つけます。

_rho, theta, thresh = 2, np.pi/180, 400
lines = cv2.HoughLines(bin_img, rho, theta, thresh)
_

Sudoku image with Hough lines

ここで、交差点を検索する場合、実際には垂直線のみの交差点を検索する必要があります。ほとんど平行な線の交点は望ましくありません。したがって、ラインをセグメント化する必要があります。この特定の例では、簡単なテストに基づいて、線が水平か垂直かを簡単に確認できます。垂直線のthetaは約0または約180です。水平線のthetaは約90です。ただし、角度を定義せずに、任意の数の角度に基づいて自動的にセグメント化する場合は、cv2.kmeans()を使用するのが最適だと思います。

正しくするために、1つの注意が必要です。 HoughLinesは、行を_rho, theta_形式( Hesse normal form )で返し、返されるthetaは0度と180度の間で、180度と0度の周りの行は似ています(両方とも水平に近い)行)、したがって、kmeansでこの周期性を取得する方法が必要です。

単位円に角度をプロットし、角度にtwoを掛けると、元々180度付近の角度は360度に近くなり、したがって、は、0の角度に対してほぼ同じ単位円に_x, y_値を持ちます。したがって、単位円上の座標で_2*angle_をプロットすることにより、ここでニースの「近さ」を得ることができます。次に、それらのポイントでcv2.kmeans()を実行し、必要な数の断片で自動的にセグメント化します。

それでは、セグメンテーションを行う関数を作成しましょう。

_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
_

これを使用するには、次のように呼び出すだけです。

_segmented = segment_by_angle_kmeans(lines)
_

ここで素晴らしいのは、オプションの引数k(デフォルトでは_k = 2_を指定することで任意の数のグループを指定できるため、ここでは指定しませんでした)。

各グループの線を異なる色でプロットする場合:

Segmented lines

そして、残っているのは、最初のグループの各線の交点と、2番目のグループの各線の交点を見つけることだけです。線はヘッセ標準形であるため、この形から線の交点を計算するためのニースの線形代数公式があります。 here を参照してください。ここで2つの関数を作成しましょう。 2行だけの交差点を見つけるものと、グループ内のすべての行をループし、2行でその単純な関数を使用する1つの関数:

_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
_

それを使用するために、それは単純です:

_intersections = segmented_intersections(segmented)
_

そして、すべての交差点をプロットすると、次のようになります:

Intersections


前述のように、このコードは線を2つ以上の角度のグループに分割することもできます。手描きの三角形で実行し、検出された行の交点を_k=3_で計算します:

Triangle intersections

34
alkasm

すでに線分セグメントがある場合は、それらを線分方程式で置き換えてください...

x = x1 + u * (x2-x1)
y = y1 + u * (y2-y1)

uは、次のいずれかを使用して見つけることができます...

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

まず、ハフ変換の出力を調整する必要があります(通常は、いくつかの基準(セグメントの勾配や重心など)に基づいてk-meansクラスタリングによってこれを行います)。たとえば、あなたの問題では、すべての線の勾配が通常0、180、90度の近くにあるように見えるため、これに基づいてクラスタリングを行うことができます。

次に、交差点を取得する方法は2つあります(技術的には同じです)。

  1. Bhupenの答えの方程式。
  2. Shapely または SymPy のようなジオメトリライブラリを使用します。ジオメトリライブラリを使用してこれを行うことの利点は、開発で後で必要になる可能性のあるさまざまなツール(交差、補間、凸包など)にアクセスできることです。

追伸Shapelyは強力なC++ジオメトリライブラリのラッパーですが、SymPyは純粋なPythonです。アプリケーションがタイムクリティカルである場合、これを検討する必要があります。

1
Wildhammer