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()
出力:
すべての交差点を取得したい。
平行線の交点を取得する必要はありません。垂直線と水平線の交点のみ。また、垂直線があるため、勾配を計算すると爆発またはinf勾配が生じる可能性が高いため、_y = mx+b
_方程式を使用しないでください。次の2つのことを行う必要があります。
HoughLines
の場合、結果はすでに_rho, theta
_であるため、theta
を使用して2つのクラスの角度に簡単にセグメント化できます。に使用できますcv2.kmeans()
とtheta
を分割するデータとして。
次に、交点を計算するために、 各線から2点を与えて交点を計算する の式を使用できます。すでに各行から2つのポイントを計算しています:_(x1, y1), (x2, y2)
_単に保存して使用することができます。編集:実際には、私のコードで以下に示すように、HoughLines
が与える_rho, theta
_フォームとの行の交差を計算するために使用できる式があります。
私は 同様の質問 前にいくつかのpythonをチェックアウトできるコードで答えました。これは、線セグメントのみを提供するHoughLinesP
を使用していたことに注意してください。
元の画像を提供しなかったため、使用できません。代わりに、OpenCVのハフ変換およびしきい値設定のチュートリアルで使用される標準の数独イメージを使用します。
最初に、この画像を読み取り、 この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)
_
次に、cv2.HoughLines()
でハフ行を見つけます。
_rho, theta, thresh = 2, np.pi/180, 400
lines = cv2.HoughLines(bin_img, rho, theta, thresh)
_
ここで、交差点を検索する場合、実際には垂直線のみの交差点を検索する必要があります。ほとんど平行な線の交点は望ましくありません。したがって、ラインをセグメント化する必要があります。この特定の例では、簡単なテストに基づいて、線が水平か垂直かを簡単に確認できます。垂直線の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
_を指定することで任意の数のグループを指定できるため、ここでは指定しませんでした)。
各グループの線を異なる色でプロットする場合:
そして、残っているのは、最初のグループの各線の交点と、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)
_
そして、すべての交差点をプロットすると、次のようになります:
前述のように、このコードは線を2つ以上の角度のグループに分割することもできます。手描きの三角形で実行し、検出された行の交点を_k=3
_で計算します:
すでに線分セグメントがある場合は、それらを線分方程式で置き換えてください...
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))
まず、ハフ変換の出力を調整する必要があります(通常は、いくつかの基準(セグメントの勾配や重心など)に基づいてk-meansクラスタリングによってこれを行います)。たとえば、あなたの問題では、すべての線の勾配が通常0、180、90度の近くにあるように見えるため、これに基づいてクラスタリングを行うことができます。
次に、交差点を取得する方法は2つあります(技術的には同じです)。
追伸Shapelyは強力なC++ジオメトリライブラリのラッパーですが、SymPyは純粋なPythonです。アプリケーションがタイムクリティカルである場合、これを検討する必要があります。