15

I'm using OpenCV to fit a line from a set of points using cvFitLine()

cvFitLine() returns a normalized vector that is co-linear to the line and a point on the line. See details here

Using this information how can I get the equation of a line so that I can draw the line?

Sunny
  • 1,973
  • 2
  • 17
  • 22

6 Answers6

13

If cvFitLine() returns normalized vector (vx,vy) and point (x0,y0), then the equation of the line is

(x,y) = (x0,y0) + t*(vx,vy)

where t runs from −∞ to +∞.

This is what you asked for, but probably isn't immediately helpful in drawing the line. You would want to clip it either to the screen boundaries, or perhaps the bounding box of the the original set of points. To clip a line to a rectangle, just solve for values of t where the line crosses the boundary of the rectangle.

brainjam
  • 18,863
  • 8
  • 57
  • 82
  • I think this is the most analytically correct answer (vs. e.g. making a huge line and clipping). I expanded on this in an answer below, spelling out how to get line endpoints at an image boundary in python. – Hendy Feb 27 '20 at 16:04
10

Just draw a big line instead of solving for the boundaries. eg:

cv.Line(img, (x0-m*vx[0], y0-m*vy[0]), (x0+m*vx[0], y0+m*vy[0]), (0,0,0))

will do it for example.. for m large enough :)

karpathy
  • 1,393
  • 1
  • 15
  • 19
5

This just spells out @brainjam's answer in python for any passers by.

The formula for a line using a unit vector (vx, vy) and some point on the line (x0, y0) is:

(x, y) = (x0, y0) + t*(vx, vy)

The return from cv2.fitLine() is:

np.array([vx, vy, x0, y0])

In the example case, I have a line spanning the height of my image, so I want to find the t0 and t1 that intersect with y=0 and y=img.shape[0] (the top/bottom boundaries).

# get the fitLine for your set of points in the array, `line`
fit_line = cv2.fitLine(line, cv2.DIST_L2, 0, 0.01, 0.01)

# compute t0 for y=0 and t1 for y=img.shape[0]: (y-y0)/vy
t0 = (0-fit_line[3])/fit_line[1]
t1 = (img.shape[0]-fit_line[3])/fit_line[1]

# plug into the line formula to find the two endpoints, p0 and p1
# to plot, we need pixel locations so convert to int
p0 = (fit_line[2:4] + (t0 * fit_line[0:2])).astype(np.uint32)
p1 = (fit_line[2:4] + (t1 * fit_line[0:2])).astype(np.uint32)

# draw the line. For my version of opencv, it wants tuples so we
# flatten the arrays and convert
# args: cv2.line(image, p0, p1, color, thickness)
cv2.line(img, tuple(p0.ravel()), tuple(p1.ravel()), (0, 255, 0), 10)
zardosht
  • 3,014
  • 2
  • 24
  • 32
Hendy
  • 10,182
  • 15
  • 65
  • 71
4

I used a strategy similar to Karpathy up there but used an extra function. As you can see, I'm using cvClipLine to trim the line to the size of the image, which is unnecessary but does add a little niceness.

Also the multiplier here is defined as theMult = max(img->height,img->width) so we dont get numbers that might one day overflow or something.

void drawLine(IplImage * img, float line[4], int thickness,CvScalar color)
{
    double theMult = max(img->height,img->width);
    // calculate start point
    CvPoint startPoint;
    startPoint.x = line[2]- theMult*line[0];// x0
    startPoint.y = line[3] - theMult*line[1];// y0
    // calculate end point
    CvPoint endPoint;
    endPoint.x = line[2]+ theMult*line[0];//x[1]
    endPoint.y = line[3] + theMult*line[1];//y[1]

    // draw overlay of bottom lines on image
    cvClipLine(cvGetSize(img), &startPoint, &endPoint);
    cvLine(img, startPoint, endPoint, color, thickness, 8, 0);
}
Denis
  • 664
  • 9
  • 24
2

Adding to @brainjam answer:

To clip to the bounding box of original set of points:

// std::vector<Point2i> points = ...

//lineParams: [vx,vy, x0,y0]: (normalized vector, point on our contour)
Vec4f lineParams; fitLine(points, lineParams, CV_DIST_L2, 0, 0.01, 0.01);

// derive the bounding xs of points
decltype(points)::iterator minXP, maxXP;
std::tie(minXP, maxXP) = std::minmax_element(points.begin(), points.end(), [](const Point2i& p1, const Point2i& p2){ return p1.x < p2.x; });

// derive y coords of fitted line
float m = lineParams[1] / lineParams[0];
int y1 = ((minXP->x - lineParams[2]) * m) + lineParams[3];
int y2 = ((maxXP->x - lineParams[2]) * m) + lineParams[3];
line(clearTarget, Point(minXP->x, y1), Point(maxXP->x, y2), Scalar(255, 255, 255), 2);

To clip to the entire image boundaries substitute minXP->x to 0 and maxXP->x to image.cols - 1, which was originally answered in https://stackoverflow.com/a/14192660/2380455

ambientlight
  • 7,212
  • 3
  • 49
  • 61
1

we use a " Vec4f fitedLine;" for fitted Line in fitLine we have 4 parameters if we consider Line relation az bellow: Y - Y0 = M (X - X0)

we have Y0 = FitedLine[3]; X0 = FitedLine[2]; m = FitedLine[1]/FitedLine[0];

so we have a Line equation we can find other points on it.

ABSSH
  • 110
  • 5