Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Approximate a group of line segments as a single best fit straight line

Assuming I have a group of lines segments like the red lines (or green lines) in this picture Sample picture

I want to know how can I replace them with just one line segment that approximates them best. Or maybe you can advise what to search for since this might be a common problem in statistics.

Problem background: This comes actually from using OpenCV's probabilistic Hough Transform. I want to detect the corners of a piece of paper. When I apply it to an image I get on the edges a group of lines that I want to convert to a single line continuous segment.

One way to do it that I was thinking of, is to take from the line a couple of points and then to use line regression to get the equation of the line. Once I have that I should just cut it to a segment.

like image 720
Adi Avatar asked Aug 18 '14 17:08

Adi


People also ask

How to find the line of best fit of ordered pairs?

A more accurate way of finding the line of best fit is the least square method . Use the following steps to find the equation of line of best fit for a set of ordered pairs ( x 1 , y 1 ) , ( x 2 , y 2 ) , ... ( x n , y n ) . Step 1: Calculate the mean of the x -values and the mean of the y -values.

How to approximate curves by broken line segments?

lems has been to approximate the given curves by a series of broken line segments [1]. The usual method of finding such an approximation has been, by visual examina- tion of the graph, first to decide on the number of line segments, second, to select the intervals over which each line segment is to apply, and third, to draw in what

What is line of best fit in statistics?

Line of Best Fit (Least Square Method) A line of best fit is a straight line that is the best approximation of the given set of data. It is used to study the nature of the relation between two variables. (We're only considering the two-dimensional case, here.)

What is an example of a line segment?

Examples of Line Segments 1 A triangle is made up of three line segments joined end to end 2 A square is made up of four-line segments 3 A pentagon is made up of five-line segments More ...


1 Answers

Here's a potential solution:

  1. Obtain binary image. Load image, convert to grayscale, and Otsu's threshold

  2. Perform morphological operations. We morph close to combine the contours into a single contour

  3. Find convex hull. We create a blank mask then find the convex hull on the binary image. We draw the lines onto the mask, morph close, then find contours and fill in the outline to get a solid image

  4. Perform linear regression. We find the line of best fit on the binary image then draw this resulting line onto a new mask

  5. Bitwise convex hull and line mask together. We bitwise-and both masks together and draw this resulting contour onto the original image.


Here's a visualization of each step:

Using this screenshotted input image

enter image description here

Binary image -> Morph close

enter image description here enter image description here

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

Convex hull outline -> convex hull filled

enter image description here enter image description here

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

Linear regression

enter image description here

# 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 filled convex hull and linear regression masks together

enter image description here

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

Result

enter image description here

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

Here's the result with the other input image

enter image description here enter image description here

Full code

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()
like image 71
nathancy Avatar answered Oct 19 '22 01:10

nathancy