Serie práctica de OpenCV: ajuste de una línea recta

Combate OpenCV: ajustando una línea recta

0. Prefacio

En algunas aplicaciones de visión por computadora , no solo es necesario detectar líneas en una imagen, sino también estimar con precisión la posición y orientación de las líneas. Esta sección describe cómo encontrar la línea que mejor se ajusta a un conjunto dado de puntos.

1. Montaje en línea recta

Lo primero que debe hacer es identificar puntos en la imagen que probablemente se alineen a lo largo de una línea recta, los segmentos de línea se pueden detectar usando la transformada de Hough . cv::HoughLinesPLos segmentos de línea detectados usando linesestán contenidos en el vector std::vector<cv::Vec4i>.

(1) Para extraer el conjunto de puntos posibles, digamos, el 1segmento de línea, podemos dibujar una línea blanca en la imagen negra y Cannycruzarla con la imagen de contorno utilizada para detectar la línea:

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

El código anterior solo puede contener puntos que se pueden asociar con la línea especificada. Para introducir cierta tolerancia, dibujamos una línea de cierto grosor (el ancho de la línea es ), de modo que se puedan aceptar todos los puntos 3dentro de la vecindad definida. El resultado se muestra en la siguiente figura (para facilitar la observación, los valores de píxel de la imagen están invertidos):

punto asociado a la línea
(2) Inserte las coordenadas de los puntos medios de este conjunto en el cv::Pointde la función a través de un bucle doble std::vector(también se pueden usar coordenadas de punto flotante, es decir 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 Es fácil encontrar el segmento de línea que mejor se ajusta llamando a la función:

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

El código anterior proporciona los parámetros de la ecuación del segmento de línea en forma de vector de dirección unitaria ( cv::Vec4flos dos primeros valores) y las coordenadas de un punto en la línea ( los dos últimos valores). cv::Vec4fPara los resultados del ejemplo, el vector de dirección es (0.83, 0.55)y las coordenadas del punto son (366.1, 289.1). cv::fitLineLos dos últimos parámetros de la función se utilizan para especificar la precisión de los parámetros del segmento deseado.

(4) Las ecuaciones de segmento de línea se pueden usar para calcular ciertas propiedades y, como ilustración, podemos dibujar la línea ajustada en la imagen. Por ejemplo, para dibujar un segmento de línea negra con 100píxeles de largo y píxeles de ancho :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);

El resultado del dibujo se muestra en la siguiente figura:

Ajuste de una línea recta
Ajustar un conjunto de puntos a una recta es un problema clásico de las matemáticas, y cv::fitLinela función ajusta una recta minimizando la suma de las distancias de cada punto a la recta.
Se pueden usar diferentes funciones de distancia, la distancia euclidiana ( CV_DIST_L2) es la más fácil de calcular y corresponde a un ajuste de línea de mínimos cuadrados estándar. Cuando el conjunto de puntos contiene valores atípicos (es decir, puntos que no pertenecen a la línea), se puede seleccionar una función de distancia que tenga menos influencia de otros valores atípicos. La minimización se basa en Muna técnica de estimación que resuelve iterativamente un problema de mínimos cuadrados ponderados con pesos que son inversamente proporcionales a la distancia desde la línea.
Con cv::fitLinela función, también puede encajar un conjunto 3Dde puntos en una línea recta. En este momento, la entrada es un conjunto de cv::Point3io cv::Point3ftipo de datos, y el tipo de salida es std::Vec6f. cv::fitEllipseLa función ajusta un conjunto 2Dde puntos a una elipse, lo que devuelve un rectángulo rotado ( cv::RotatedRectinstancia) dentro del cual se inscribe la elipse:

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

cv::ellipsefunción para trazar el gráfico de función elíptica calculado.

2. Código completo

Archivos de biblioteca linefinder.hy edgedetector.hpuede referirse a la sección de detección de línea, el código de función principal fitLine.cppes el siguiente:

#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;
}

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

Supongo que te gusta

Origin blog.csdn.net/m0_58523831/article/details/129660972
Recomendado
Clasificación