OpenCV Practical Series - Fitting a Straight Line

OpenCV combat - fitting a straight line

0. Preface

In some computer vision applications, it is not only necessary to detect lines in an image, but also to accurately estimate the position and orientation of the lines. This section describes how to find the line that best fits a given set of points.

1. Straight line fitting

The first thing to do is to identify points in the image that are likely to align along a straight line, the line segments can be detected using the Hough transform . cv::HoughLinesPThe line segments detected using linesare contained in the vector std::vector<cv::Vec4i>.

(1) To extract the set of possible points, say, the 1line segment, we can draw a white line on the black image and Cannyintersect it with the contour image used to detect the line:

int n = 0;
// 提取探测到的第一条线段的轮廓像素
cv::Mat oneline(image.size(), CV_8U, cv::Scalar(0));
cv::line(oneline, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 3);
cv::bitwise_and(contours, oneline, oneline);

The above code can only contain points that can be associated with the specified line. In order to introduce some tolerance, we draw a line of a certain thickness (the line width is 3), so all points within the defined neighborhood can be accepted. The result is shown in the figure below (for ease of observation, the image pixel values ​​are reversed):

point associated with the line
(2) Insert the coordinates of the midpoints of this set into the cv::Pointof the function through a double loop std::vector(floating point coordinates can also be used, ie cv::Point2f):

std::vector<cv::Point> points;
//  迭代像素以获得所有点位置
for (int y=0; y<oneline.rows; y++) {
    
    
    uchar* rowPtr = oneline.ptr<uchar>(y);
    for (int x=0; x<oneline.cols; x++) {
    
    
        if (rowPtr[x]) {
    
    
            points.push_back(cv::Point(x, y));
        }
    }
}

(3)cv::fitLine It is easy to find the best fitting line segment by calling the function:

// 拟合直线
cv::Vec4f line;
cv::fitLine(points, line, cv::DIST_L2, 0, 0.01, 0.01);

The above code provides the parameters of the line segment equation in the form of a unit direction vector ( cv::Vec4fthe first two values) and the coordinates of a point on the line ( the last two values). cv::Vec4fFor the example results, the direction vector is (0.83, 0.55), and the point coordinates are (366.1, 289.1). cv::fitLineThe last two parameters of the function are used to specify the precision of the desired segment parameters.

(4) Line segment equations can be used to compute certain properties, and as an illustration, we can draw the fitted line on the image. For example, to draw a black line segment with length 100pixels and width pixels:3

int x0 = line[2];
int y0 = line[3];
int x1 = x0+100*line[0];
int y1 = y1+100*line[1];
// 绘制直线
cv::line(image, cv::Point(x0, y0), cv::Point(x1, y1), 0, 2);

The drawing result is shown in the figure below:

Fitting a straight line
Fitting a set of points to a line is a classic problem in mathematics, and cv::fitLinethe function fits a line by minimizing the sum of the distances from each point to the line.
Different distance functions can be used, the Euclidean distance ( CV_DIST_L2) is the easiest to compute and corresponds to a standard least squares line fit. When the point set contains outliers (i.e. points that do not belong to the line), a distance function that has less influence from other outliers can be selected. The minimization is based on Man estimation technique that iteratively solves a weighted least squares problem with weights that are inversely proportional to the distance from the line.
Using cv::fitLinethe function, you can also fit a set 3Dof points into a straight line. At this time, the input is a set of cv::Point3ior cv::Point3ftype data, and the output type is std::Vec6f. cv::fitEllipseThe function fits a set 2Dof points to an ellipse, which returns a rotated rectangle ( cv::RotatedRectinstance) within which the ellipse is inscribed:

cv::RotatedRect rrect= cv::fitEllipse(cv::Mat(points));
cv::ellipse(image,rrect,cv::Scalar(0));

cv::ellipsefunction to plot the computed elliptic function graph.

2. Complete code

Library files linefinder.hand edgedetector.hcan refer to the line detection section, the main function fitLine.cppcode is as follows:

#include <iostream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "linefinder.h"
#include "edgedetector.h"

#define PI 3.1415926

int main() {
    
    
    // 读取输入图像
    cv::Mat image = cv::imread("road.jpg", 0);
    if (!image.data) return 0;
    cv::namedWindow("Original Image");
    cv::imshow("Original Image", image);
    // 计算 Sobel
    EdgeDetector ed;
    ed.computeSobel(image);
    cv::namedWindow("Sobel (orientation)");
    cv::imshow("Sobel (orientation)", ed.getSobelOrientationImage());
    cv::imwrite("ori.png", ed.getSobelOrientationImage());
    // 低阈值 Sobel
    cv::namedWindow("Sobel (low threshold)");
    cv::imshow("Sobel (low threshold)", ed.getBinaryMap(125));
    // 高阈值 Sobel
    cv::namedWindow("Sobel (high threshold)");
    cv::imshow("Sobel (high threshold)", ed.getBinaryMap(350));
    // 应用 Canny 算法
    cv::Mat contours;
    cv::Canny(image,    // 灰度图像
            contours,   // 输出图像
            125,        // 低阈值
            350);       // 高阈值
    cv::namedWindow("Canny Contours");
    cv::imshow("Canny Contours", 255-contours);
    // 霍夫变换
    std::vector<cv::Vec2f> lines;
    cv::HoughLines(contours, lines, 1, PI/180, 50);
    // 绘制检测结果
    cv::Mat result(contours.rows, contours.cols, CV_8U, cv::Scalar(255));
    image.copyTo(result);
    std::cout << "Lines detected: " << lines.size() << std::endl;
    std::vector<cv::Vec2f>::const_iterator it = lines.begin();
    while (it!=lines.end()) {
    
    
        float rho = (*it)[0];
        float theta = (*it)[1];
        if (theta < PI/4. || theta > 3.*PI/4.) {
    
        // 竖线
            // 直线与图像第一行交点
            cv::Point pt1(rho/cos(theta), 0);
            // 直线与图像最后一行交点
            cv::Point pt2((rho-result.rows*sin(theta))/cos(theta), result.rows);
            cv::line(result, pt1, pt2, cv::Scalar(255), 1);
        } else {
    
            // 横线
            // 直线与图像第一列交点
            cv::Point pt1(0, rho/sin(theta));
            // 直线与图像最后一列交点
            cv::Point pt2(result.cols, (rho-result.cols*cos(theta))/sin(theta));
            cv::line(result, pt1, pt2, cv::Scalar(255), 1);
        }
        std::cout << "line: (" << rho << "," << theta << ")" << std::endl;
        ++it;
    }
    cv::namedWindow("Lines with Hough");
    cv::imshow("Lines with Hough", result);
    // 创建 LineFinder 实例
    LineFinder ld;
    // 设置概率霍夫变换
    ld.setLineLengthAndGap(100, 20);
    ld.setMinVote(60);
    // 直线检测
    std::vector<cv::Vec4i> li = ld.findLines(contours);
    ld.drawDetectedLines(image);
    cv::namedWindow("Lines with HoughP");
    cv::imshow("Lines with HoughP", image);
    std::vector<cv::Vec4i>::const_iterator it2 = li.begin();
    while (it2 != li.end()) {
    
    
        std::cout << "(" << (*it2)[0] << ", " << 
                (*it2)[1] << ") - (" << (*it2)[2] << 
                ", " << (*it2)[3] << ")" << std::endl;
        ++it2;
    }
    image = cv::imread("road.jpg", 0);
    int n = 0;
    cv::line(image, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 5);
    cv::namedWindow("One line of the Image");
    cv::imshow("One line of the Image", image);
    // 提取探测到的第一条线段的轮廓像素
    cv::Mat oneline(image.size(), CV_8U, cv::Scalar(0));
    cv::line(oneline, cv::Point(li[n][0], li[n][1]), cv::Point(li[n][2], li[n][3]), cv::Scalar(255), 3);
    cv::bitwise_and(contours, oneline, oneline);
    cv::namedWindow("One line");
    cv::imshow("One line", 255-oneline);
    std::vector<cv::Point> points;
    //  迭代像素以获得所有点位置
    for (int y=0; y<oneline.rows; y++) {
    
    
        uchar* rowPtr = oneline.ptr<uchar>(y);
        for (int x=0; x<oneline.cols; x++) {
    
    
            if (rowPtr[x]) {
    
    
                points.push_back(cv::Point(x, y));
            }
        }
    }
    // 拟合直线
    cv::Vec4f line;
    cv::fitLine(points, line, cv::DIST_L2, 0, 0.01, 0.01);
    std::cout << "line: (" << line[0] << ", " << line[1] << 
            ") (" << line[2] << ", " << line[3] << std::endl;
    // 直线上的点
    int x0 = line[2];
    int y0 = line[3];
    int x1 = x0+100*line[0];
    int y1 = y0+100*line[1];
    image = cv::imread("road.jpg", 0);
    // 绘制直线
    cv::line(image, cv::Point(x0, y0), cv::Point(x1, y1), 0, 2);
    cv::namedWindow("Fitted line");
    cv::imshow("Fitted line", image);
    cv::waitKey();
    return 0;
}

Transfer: https://blog.csdn.net/LOVEmy134611/article/details/128808279?spm=1001.2014.3001.5501

Guess you like

Origin blog.csdn.net/m0_58523831/article/details/129660972