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::HoughLinesP
Los segmentos de línea detectados usando lines
están contenidos en el vector std::vector<cv::Vec4i>
.
(1) Para extraer el conjunto de puntos posibles, digamos, el 1
segmento de línea, podemos dibujar una línea blanca en la imagen negra y Canny
cruzarla 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 3
dentro 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):
(2) Inserte las coordenadas de los puntos medios de este conjunto en el cv::Point
de 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::Vec4f
los dos primeros valores) y las coordenadas de un punto en la línea ( los dos últimos valores). cv::Vec4f
Para 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::fitLine
Los 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 100
pí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:
Ajustar un conjunto de puntos a una recta es un problema clásico de las matemáticas, y cv::fitLine
la 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 M
una 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::fitLine
la función, también puede encajar un conjunto 3D
de puntos en una línea recta. En este momento, la entrada es un conjunto de cv::Point3i
o cv::Point3f
tipo de datos, y el tipo de salida es std::Vec6f
. cv::fitEllipse
La función ajusta un conjunto 2D
de puntos a una elipse, lo que devuelve un rectángulo rotado ( cv::RotatedRect
instancia) dentro del cual se inscribe la elipse:
cv::RotatedRect rrect= cv::fitEllipse(cv::Mat(points));
cv::ellipse(image,rrect,cv::Scalar(0));
cv::ellipse
función para trazar el gráfico de función elíptica calculado.
2. Código completo
Archivos de biblioteca linefinder.h
y edgedetector.h
puede referirse a la sección de detección de línea, el código de función principal fitLine.cpp
es 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