簡體   English   中英

將一組線段近似為一條最佳擬合直線

[英]Approximate a group of line segments as a single best fit straight line

假設我有一組線段,如這張圖片中的紅線(或綠線)示例圖片

我想知道如何用最接近它們的線段替換它們。 或者,也許您可​​以建議要搜索的內容,因為這可能是統計中的常見問題。

問題背景:這其實來自於使用OpenCV的概率霍夫變換。 我想檢測一張紙的角。 當我將它應用於圖像時,我會在邊緣處得到一組線條,我想將這些線條轉換為單線連續段。

我想到的一種方法是從直線上取幾個點,然后使用直線回歸得到直線的方程。 一旦我有了它,我應該把它剪成一段。

這是一個潛在的解決方案:

  1. 獲取二值圖像。 加載圖像,轉換為灰度,以及大津閾值

  2. 進行形態學操作。 我們變形接近以將輪廓組合成單個輪廓

  3. 找到凸包。 我們創建一個空白掩碼,然后在二值圖像上找到凸包。 我們在蒙版上繪制線條,變形關閉,然后找到輪廓並填充輪廓以獲得實心圖像

  4. 執行線性回歸。 我們在二值圖像上找到最適合的線,然后將此結果線繪制到新的蒙版上

  5. 按位凸包和線掩碼在一起。 我們將兩個掩碼按位和合並在一起,並將這個結果輪廓繪制到原始圖像上。


這是每個步驟的可視化:

使用這個截圖輸入圖像

在此處輸入圖片說明

二進制圖像->變形關閉

在此處輸入圖片說明 在此處輸入圖片說明

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

凸包輪廓->凸包填充

在此處輸入圖片說明 在此處輸入圖片說明

# Create line mask and convex hull mask
line_mask = np.zeros(image.shape, dtype=np.uint8)
convex_mask = np.zeros(image.shape, dtype=np.uint8)

# Find convex hull on the binary image
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = cnts[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(convex_mask,start,end,[255,255,255],5)

# Morph close the convex hull mask, find contours, and fill in the outline
convex_mask = cv2.cvtColor(convex_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
convex_mask = cv2.morphologyEx(convex_mask, cv2.MORPH_CLOSE, kernel, iterations=3)
cnts = cv2.findContours(convex_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(convex_mask, cnts, (255,255,255))

線性回歸

在此處輸入圖片說明

# Perform linear regression on the binary image
[vx,vy,x,y] = cv2.fitLine(cnt,cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((image.shape[1]-x)*vy/vx)+y)
cv2.line(line_mask,(image.shape[1]-1,righty),(0,lefty),[255,255,255],2)

按位填充的凸包和線性回歸掩碼一起

在此處輸入圖片說明

# Bitwise-and the line and convex hull masks together
result = cv2.bitwise_and(line_mask, line_mask, mask=convex_mask)
result = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)

結果

在此處輸入圖片說明

# Find resulting contour and draw onto original image
cnts = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.drawContours(image, cnts, -1, (200,100,100), 3)

這是另一個輸入圖像的結果

在此處輸入圖片說明 在此處輸入圖片說明

完整代碼

import cv2
import numpy as np

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

# Create line mask and convex hull mask
line_mask = np.zeros(image.shape, dtype=np.uint8)
convex_mask = np.zeros(image.shape, dtype=np.uint8)

# Find convex hull on the binary image
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnt = cnts[0]
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv2.line(convex_mask,start,end,[255,255,255],5)

# Morph close the convex hull mask, find contours, and fill in the outline
convex_mask = cv2.cvtColor(convex_mask, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
convex_mask = cv2.morphologyEx(convex_mask, cv2.MORPH_CLOSE, kernel, iterations=3)
cnts = cv2.findContours(convex_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(convex_mask, cnts, (255,255,255))

# Perform linear regression on the binary image
[vx,vy,x,y] = cv2.fitLine(cnt,cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((image.shape[1]-x)*vy/vx)+y)
cv2.line(line_mask,(image.shape[1]-1,righty),(0,lefty),[255,255,255],2)

# Bitwise-and the line and convex hull masks together
result = cv2.bitwise_and(line_mask, line_mask, mask=convex_mask)
result = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)

# Find resulting contour and draw onto original image
cnts = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.drawContours(image, cnts, -1, (200,100,100), 3)

cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.imshow('line_mask', line_mask)
cv2.imshow('convex_mask', convex_mask)
cv2.imshow('result', result)
cv2.waitKey()

這是一個很好的問題。 看待這個問題的正確方法是沿每條線段對誤差進行積分。 而不是簡單的術語的(y[i] - y_hat)^2其中y_hat是從回歸線的預測值),你應該具有integral((y[i](t) - y_hat)^2, t, 0, 1)其中y[i](t)是線段的參數化, y[i](t) = t * y[i, 1] + (1 - t)*y[i, 0] (表示端點第i段的y[i, 0]y[i, 1] )。 我認為您會發現您可以准確地計算積分,因此您將獲得僅涉及端點的平方誤差總和的項。 我遺漏了一些細節,但我認為這足以讓您解決其余的問題。 編輯:誤差項應該平方; 我已經相應地調整了公式。

第二次編輯:我制定了一些公式(使用Maxima )。 對於由y = alpha*x + beta表示的回歸線,我得到alphabeta最小二乘估計:

alpha = (4*('sum(ll[k],k,1,n))*'sum(xx[k][2]*yy[k][2]*ll[k],k,1,n)
    +2*('sum(ll[k],k,1,n))*'sum(xx[k][1]*yy[k][2]*ll[k],k,1,n)
    +('sum(xx[k][2]*ll[k],k,1,n))*(-3*'sum(yy[k][2]*ll[k],k,1,n)
                                  -3*'sum(yy[k][1]*ll[k],k,1,n))
    +('sum(xx[k][1]*ll[k],k,1,n))*(-3*'sum(yy[k][2]*ll[k],k,1,n)
                                  -3*'sum(yy[k][1]*ll[k],k,1,n))
    +2*('sum(ll[k],k,1,n))*'sum(yy[k][1]*xx[k][2]*ll[k],k,1,n)
    +4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))
    /(4*('sum(ll[k],k,1,n))*'sum(xx[k][2]^2*ll[k],k,1,n)
     +4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*xx[k][2]*ll[k],k,1,n)
     -3*('sum(xx[k][2]*ll[k],k,1,n))^2
     -6*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
     +4*('sum(ll[k],k,1,n))*'sum(xx[k][1]^2*ll[k],k,1,n)
     -3*('sum(xx[k][1]*ll[k],k,1,n))^2)

  beta = -((2*'sum(xx[k][2]*ll[k],k,1,n)+2*'sum(xx[k][1]*ll[k],k,1,n))
   *'sum(xx[k][2]*yy[k][2]*ll[k],k,1,n)
   +('sum(xx[k][2]*ll[k],k,1,n)+'sum(xx[k][1]*ll[k],k,1,n))
    *'sum(xx[k][1]*yy[k][2]*ll[k],k,1,n)
   +('sum(xx[k][2]^2*ll[k],k,1,n))*(-2*'sum(yy[k][2]*ll[k],k,1,n)
                                   -2*'sum(yy[k][1]*ll[k],k,1,n))
   +('sum(xx[k][1]*xx[k][2]*ll[k],k,1,n))
    *(-2*'sum(yy[k][2]*ll[k],k,1,n)-2*'sum(yy[k][1]*ll[k],k,1,n))
   +('sum(xx[k][1]^2*ll[k],k,1,n))*(-2*'sum(yy[k][2]*ll[k],k,1,n)
                                   -2*'sum(yy[k][1]*ll[k],k,1,n))
   +('sum(xx[k][2]*ll[k],k,1,n)+'sum(xx[k][1]*ll[k],k,1,n))
    *'sum(yy[k][1]*xx[k][2]*ll[k],k,1,n)
   +2*('sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
   +2*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][1]*yy[k][1]*ll[k],k,1,n))
   /(4*('sum(ll[k],k,1,n))*'sum(xx[k][2]^2*ll[k],k,1,n)
    +4*('sum(ll[k],k,1,n))*'sum(xx[k][1]*xx[k][2]*ll[k],k,1,n)
    -3*('sum(xx[k][2]*ll[k],k,1,n))^2
    -6*('sum(xx[k][1]*ll[k],k,1,n))*'sum(xx[k][2]*ll[k],k,1,n)
    +4*('sum(ll[k],k,1,n))*'sum(xx[k][1]^2*ll[k],k,1,n)
    -3*('sum(xx[k][1]*ll[k],k,1,n))^2)

其中xxyy是值對列表,每個線段一對。 也就是說, xx[k]是第k段端點的 x 坐標, yy[k]是第k段端點的 y 坐標, ll[k]是長度sqrt((xx[k][2] - xx[k][1])^2 + (yy[k][2] - yy[k][1])^2)k個片段。

這是我推導出這些公式的程序。 可能還有其他合理的方法來設置這個問題,這些方法會產生相似但不同的公式。

y_hat[k](l) := alpha * x[k](l) + beta;
x[k](l) := (1 - l/ll[k]) * xx[k][1] + l/ll[k] * xx[k][2];
y[k](l) := (1 - l/ll[k]) * yy[k][1] + l/ll[k] * yy[k][2];
e[k]:= y[k](l) - y_hat[k](l);
foo : sum (integrate (e[k]^2, l, 0, ll[k]), k, 1, n);
declare (nounify (sum), linear);
[da, db] : [diff (foo, alpha), diff (foo, beta)];
map (expand, %);
bar : solve (%, [alpha, beta]);

下面是一些示例數據和我得到的結果。 我推遲定義dxdyll ,因為因為它們都是常數項,我不希望它們在alphabeta的解決方案中擴展。

dx[k] := xx[k][2] - xx[k][1];
dy[k] := yy[k][2] - yy[k][1];
ll[k] := sqrt (dx[k]^2 + dy[k]^2);

xx : [[1,2],[1.5,3.5],[5.5,10.5]]$
yy : [[1,2.2],[1.5,3.3],[5,12]]$

''bar, nouns, n=length(xx);
  => [[alpha = 1.133149837130799, beta = - 0.4809409869515073]]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM