边缘检测: 传统的直线检测方法一般采用边缘检测算法提取二值边缘图像,然后利用Hough变换将表示每条直线属性的参数投影到Hough空间中。该线检测方法是一种全局拟合算法。缺点是依赖于边缘检测算法的准确性,在边缘密集的地方容易出现异常检测
LSD检测 LSD算法是一种基于梯度信息的直线检测方法,具有检测速度快、参数自适应、精度可达到亚像素级的特点。其主要思想是将局部区域内具有相同梯度方向的像素进行合并,以达到直线检测的目的
下面有用到自己更改LBD的高级参数:
line_descriptor::LSDDetector::LSDOptions opts;
但是编译报错:
error: ‘LSDOptions’ is not a member of ‘cv::line_descriptor::LSDDetector’
经查找:这个错误通常是由于版本不兼容引起的。可能是你正在使用的OpenCV版本与你的代码中使用的OpenCV版本不同导致的。
在OpenCV 4.0版本及更高版本中,LSDDetector类的构造函数中没有LSDOptions参数。相反,它被重构为BinaryDescriptor::Params参数。因此,如果你的OpenCV版本高于4.0,那么在构造LSDDetector对象时,应该使用BinaryDescriptor::Params参数,而不是LSDOptions参数。
以下是在OpenCV 4.0版本及更高版本中使用BinaryDescriptor::Params参数的示例代码:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像
Mat img = imread("image.jpg");
// 创建BinaryDescriptor对象
Ptr<BinaryDescriptor> bd = BinaryDescriptor::createBinaryDescriptor();
// 设置计算参数
BinaryDescriptor::Params params;
params.numOfOctaves = 3;
params.numOfLevels = 4;
params.widthOfBand = 7;
params.extractSIFT = true;
// 初始化BinaryDescriptor对象
bd->init(params);
// 计算二进制描述子
vector<KeyPoint> keypoints;
Mat descriptors;
bd->compute(img, keypoints, descriptors);
// 输出计算结果
cout << "Number of keypoints: " << keypoints.size() << endl;
cout << "Descriptor size: " << descriptors.cols << endl;
return 0;
}
/*在这个示例中,我们首先读取了一个图像,然后创建了一个BinaryDescriptor对象bd。
接着,我们定义了一个BinaryDescriptor::Params对象params,并设置了三个计算参数:
numOfOctaves、numOfLevels和widthOfBand。这些参数可以影响二进制描述子的计算效果。
最后,我们使用bd->compute()函数计算了二进制描述子,并输出了计算结果。
在这个示例中,我们计算了SIFT特征点的二进制描述子。你可以根据自己的需求来设置
BinaryDescriptor::Params对象的计算参数。*/
原文链接地址:https://zhuanlan.zhihu.com/p/54126417
#include <chrono>
#include <cv.h>
#include <opencv2/core/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/line_descriptor/descriptor.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <iostream>
#include <sstream>
#include <sys/time.h>
#include <unistd.h>
#include<math.h>
using namespace cv;
using namespace std;
using namespace cv::line_descriptor;
struct sort_descriptor_by_queryIdx
{
inline bool operator()(const vector<DMatch> &a, const vector<DMatch> &b)
{
return (a[0].queryIdx < b[0].queryIdx);
}
};
struct sort_lines_by_response
{
inline bool operator()(const KeyLine &a, const KeyLine &b)
{
return (a.response > b.response);
}
};
void ExtractLineSegment(const Mat &img, const Mat &image2, vector<KeyLine> &keylines, vector<KeyLine> &keylines2);
// #define FILTER_LINES //用来验证线段长度的筛选效果
int main(int argc, char **argv)
{
if (argc != 3)
{
cerr << endl
<< "Usage: ./Line path_to_image1 path_to_image2" << endl;
return 1;
}
std::string window_name("LSD Detect");
string imagePath1 = string(argv[1]);
string imagePath2 = string(argv[2]);
cout << "import two images" << endl;
Mat image1 = imread(imagePath1);
Mat image2 = imread(imagePath2);
imshow("ima1", image1);
imshow("ima2", image2);
waitKey(0);
if (image1.data == NULL)
{
cout << "the path is wrong" << endl;
}
vector<KeyLine> keylines, keylines2;
ExtractLineSegment(image1, image2, keylines, keylines2);
return 0;
}
void ExtractLineSegment(const Mat &img, const Mat &image2, vector<KeyLine> &keylines, vector<KeyLine> &keylines2)
{
Mat mLdesc, mLdesc2;
vector<vector<DMatch>> lmatches;
Ptr<BinaryDescriptor> lbd = BinaryDescriptor::createBinaryDescriptor();
// // 创建LSDDetector对象,并且可以更改默认参数
// line_descriptor::LSDDetector::LSDOptions opts;
// opts.refine = line_descriptor::LSD_REFINE_STD; // 设置线段精化模式
// opts.scale = 1.2; // 设置线检测的尺度因子
// opts.sigma_scale = 0.6; // 设置线检测的高斯核参数
// opts.quant = 2.0; // 设置线检测的图像离散程度
// opts.ang_th = 22.5; // 设置线检测的最小角度
// opts.log_eps = 0; // 设置线检测的对数响应阈值
// opts.density_th = 0.7; // 设置线检测的密度阈值
// opts.n_bins = 1024; // 设置线检测的直方图bin数
// opts.min_length = 0; // 设置线检测的最小线段长度
// opts.max_line_gap = -1; // 设置线检测的最大线段间隔
// Ptr<line_descriptor::LSDDetector> lsd = line_descriptor::LSDDetector::createLSDDetector( opts );
Ptr<line_descriptor::LSDDetector> lsd = line_descriptor::LSDDetector::createLSDDetector();
struct timeval time1, time2, time3;
//记录两个时间差
unsigned long diff1, diff2;
cout << "extract lsd line segments" << endl;
//获取开始前的瞬间时间
gettimeofday(&time1, NULL);
lsd->detect(img, keylines, 1.2, 1); //后面俩个参数是scale (– scale factor used in pyramids generation)
// numOctaves (– number of octaves inside pyramid)
gettimeofday(&time2, NULL);
lsd->detect(image2, keylines2, 1.2, 1);
gettimeofday(&time3, NULL);
//计算开始和结束时间差
diff1 = (time2.tv_sec - time1.tv_sec) + (time2.tv_usec - time1.tv_usec) / 1000000.0;
diff2 = (time3.tv_sec - time2.tv_sec) + (time3.tv_usec - time2.tv_usec) / 1000000.0;
cout << " LSDDetector在图片1 中得到的线段数量" << keylines.size() << " ,,耗时为==" << diff1 << endl;
cout << " LSDDetector 在图片2中得到的线段数量" << keylines2.size() << " ,,耗时为==" << diff2 << endl;
double x1, y1, x2, y2;
double m, c, theta, len;
ostringstream os1, os2, os3;
string str1;
// 记得重复使用同一个stringstream对象(kp_depth_str)时要先继续清空对象里的内容,要不然会一直添加到kp_depth_str末尾(印象中是),不会像int那样简单的覆盖之前的内容
double x1, y1, x2, y2;
double m, c, theta, theta_standard,len,len_standard;
string str1;
Point2f standard_mid_pt;
// 记得重复使用同一个 ostringstream 对象(os1)时要先继续清空对象里的内容,要不然会一直添加到 os1 末尾,不会像int那样简单的覆盖之前的内容
ostringstream os1, os2, os3;
double text_size_ratio = 0.5; // text_size_ratio = font_scale 尺寸因子,可以小于1,值越大文字越大
int thickness = 1; // 线条宽度
int lineType = 2; // 线型(4邻域或8邻域,默认8邻域)
int itor = 0;
for (auto &kp : keylines)
{
x1 = kp.startPointX, y1 = kp.startPointY;
x2 = kp.endPointX, y2 = kp.endPointX;
cv::Point2d pt1(kp.startPointX, kp.startPointY);
cv::Point2d pt2(kp.endPointX, kp.endPointY);
// cout<< "第"<<itor<<"线段的x1=="<<x1<<",,y1=="<<y1<<endl;
// cout<< "第"<<itor<<"线段的x2=="<<x2<<",,y1=="<<y2<<endl;
// Calculating equation of the line : y = mx + c
if (x1 != x2)
m = (double)(y2 - y1) / (double)(x2 - x1);
else
m = 100000000.0;
c = y2 - m * x2;
// cout<< "第"<<itor<<"线段的斜率m=="<<m<<",,theta="<< theta<<endl;
// theta will contain values between - 90 -> + 90.
theta = atan(m) * (180.0 / M_PI);
theta_standard = kp.angle;
len_standard= kp.lineLength;
standard_mid_pt = kp.pt ;
len = pow((pow((y2 - y1), 2) + pow((x2 - x1), 2)), 0.5); // length of the line
len = round(len * pow(10, 3)) * pow(10, -3);//3位小数,函数round():四舍五入到最接近的整数值
// cout<< "第"<<itor<<"线段的长度为len=="<<len<<endl;
cv::circle(image1, cv::Point((x1 + x2) / 2, (y2 + y1) / 2) , (int)(5), cv::Scalar(250,0,250),2, 8, 0);
os1 << len;
str1 = os1.str();
putText(image1, str1, cv::Point((x1 + x2) / 2, (y2 + y1) / 2) , cv::FONT_HERSHEY_COMPLEX, text_size_ratio, cv::Scalar(100, 0, 0), thickness, lineType);
os1.str("");//清空对象 os1 里的内容
// cout<< "第"<<itor<<"线段的长度为str1=="<<str1<<endl;
itor++;
}
// 因此,在我们的方法中,我们将从之后获得的所有行中只选取最长的15行
// 在上一步中对它们进行过滤(只有当行数多于15时,否则小于15排队,我们会把他们都带走)。数字“15”只是一个我认为正确的参数
// 我们的计算消失点,但你可以改变它根据你的愿望。为了获得最长的行,我们首先使用
// sort函数,然后我们分割出前15行(最长的15行)。
// if (FinalLines.size() > 15)
// {
// std::sort(FinalLines.begin(), FinalLines.end(),
// [](const std::vector< double >& a,
// const std::vector< double >& b)
// { return a[6] > b[6]; });
// FilteredLines = std::vector<std::vector<double>>(FinalLines.begin(), FinalLines.begin() + 15);
// }
// 绘制直线,用紫色绘制
cv::Mat pre_image_output = img.clone();
cv::line_descriptor::drawKeylines(img, keylines, pre_image_output, cv::Scalar(255, 0, 255));
// 绘制直线,用green 色绘制
cv::Mat pre_image_output2 = image2.clone();
cv::line_descriptor::drawKeylines(image2, keylines2, pre_image_output2, cv::Scalar(255, 255, 0));
cv::imshow("pre_keylines", pre_image_output);
cv::imshow("pre_keylines2", pre_image_output2);
cv::waitKey(0);
int lsdNFeatures = 50;
cout << "filter lines" << endl;
if (keylines.size() > lsdNFeatures)
{
sort(keylines.begin(), keylines.end(), sort_lines_by_response());
keylines.resize(lsdNFeatures);
for (int i = 0; i < lsdNFeatures; i++)
keylines[i].class_id = i;
}
if (keylines2.size() > lsdNFeatures)
{
sort(keylines2.begin(), keylines2.end(), sort_lines_by_response());
keylines2.resize(lsdNFeatures);
for (int i = 0; i < lsdNFeatures; i++)
keylines2[i].class_id = i;
}
// 绘制直线,用蓝色绘制
cv::Mat image_output = img.clone();
cv::line_descriptor::drawKeylines(img, keylines, image_output, cv::Scalar(255, 0, 0));
// 绘制直线,用green 色绘制
cv::Mat image_output2 = image2.clone();
cv::line_descriptor::drawKeylines(image2, keylines2, image_output2, cv::Scalar(0, 255, 0));
cv::imshow("keylines", image_output);
cv::imshow("keylines2", image_output2);
cv::waitKey(0);
cout << "lbd describle" << endl;
lbd->compute(img, keylines, mLdesc);
lbd->compute(image2, keylines2, mLdesc2); //计算特征线段的描述子
BFMatcher *bfm = new BFMatcher(NORM_HAMMING, false);
bfm->knnMatch(mLdesc, mLdesc2, lmatches, 2);
vector<DMatch> matches;
for (size_t i = 0; i < lmatches.size(); i++)
{
const DMatch &bestMatch = lmatches[i][0];
const DMatch &betterMatch = lmatches[i][1];
float distanceRatio = bestMatch.distance / betterMatch.distance;
if (distanceRatio < 0.75)
matches.push_back(bestMatch);
}
cv::Mat outImg;
std::vector<char> mask(lmatches.size(), 1);
drawLineMatches(img, keylines, image2, keylines2, matches, outImg, Scalar::all(-1), Scalar::all(-1), mask,
DrawLinesMatchesFlags::DEFAULT);
imshow("Matches", outImg);
waitKey();
}
针对自己的Cmakelists.txt文件 的执行命令:./bin/HC++ /home/jyy/图片/test1.png /home/jyy/图片/test1.png
PROJECT(HC++)
cmake_minimum_required(VERSION 3.5)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HC++_SOURCE_DIR}/bin) #可执行文件的输出位置
# find required opencv
find_package(OpenCV REQUIRED)
# directory of opencv headers
link_directories(${OpenCV_LIBRARY_DIRS})
# opencv libr
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(${PROJECT_NAME} "./baselineExperiment.cpp" )
# directory of opencv libraryaries
target_link_libraries(HC++ ${OpenCV_LIBS} -lpthread -lm)
附上一个使用互动条动态调整线段长度的函数
#include <opencv2/opencv.hpp>
using namespace cv;
// 回调函数
void onChange(int pos, void* userdata)
{
Mat img = *(Mat*)userdata;
// 使用LSD算法提取线段
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
Ptr<LineSegmentDetector> lsd = createLineSegmentDetector(0);
std::vector<Vec4f> lines;
lsd->detect(gray, lines);
// 根据滑动条位置调整线段长度
float maxLength = pos / 10.0f;
for (Vec4f line : lines) {
float length = norm(Point2f(line[0], line[1]) - Point2f(line[2], line[3]));
if (length > maxLength) {
float dx = line[2] - line[0];
float dy = line[3] - line[1];
float scale = maxLength / length;
dx *= scale;
dy *= scale;
line[2] = line[0] + dx;
line[3] = line[1] + dy;
}
}
// 绘制线段并显示图像
Mat drawImg;
cvtColor(gray, drawImg, COLOR_GRAY2BGR);
lsd->drawSegments(drawImg, lines);
imshow("Image", drawImg);
}
int main()
{
Mat img = imread("image.jpg");
// 创建窗口并显示图像
namedWindow("Image");
imshow("Image", img);
// 创建滑动条并注册回调函数
int maxLength = 100;
createTrackbar("Max Length", "Image", &maxLength, 1000, onChange, &img);
waitKey(0);
return 0;
}
下面有用到自己更改LSD的高级参数(来源于chatgpt的方法):(但是在line_descriptor.hpp中并没有找到这俩个成员函数,难不成这东西也骗人??)
在OpenCV 4.0版本中,LSDOptions类已经从line_descriptor命名空间中移除,并被放置在line_descriptor::OptimizePose类中。因此,如果你在使用line_descriptor库时出现了“error: ‘LSDOptions’ is not a member of ‘cv::line_descriptor’”的错误,可能是因为你使用了过时的LSDOptions类。
为了解决这个问题,你可以使用line_descriptor::OptimizePose类来设置LSD算法的参数。请注意,在使用line_descriptor::OptimizePose类时,你需要包含<opencv2/line_descriptor.hpp>头文件。下面是一个示例代码,展示如何使用line_descriptor::OptimizePose来计算图像中的直线特征:
#include <opencv2/opencv.hpp>
#include <opencv2/line_descriptor.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像
Mat img = imread("image.jpg");
// 创建LSDDetector对象
Ptr<line_descriptor::LSDDetector> lsd = line_descriptor::LSDDetector::create();
///**********************参数设置一:报错***********************
// 创建LSDDetector对象,并且可以更改默认参数
// line_descriptor::LSDDetector::LSDOptions opts;
// line_descriptor::LSDOptions opts;
// opts.refine = line_descriptor::LSD_REFINE_STD; // 设置线段精化模式
// opts.scale = 1.2; // 设置线检测的尺度因子
// opts.sigma_scale = 0.6; // 设置线检测的高斯核参数
// opts.quant = 2.0; // 设置线检测的图像离散程度
// opts.ang_th = 22.5; // 设置线检测的最小角度
// opts.log_eps = 0; // 设置线检测的对数响应阈值
// opts.density_th = 0.7; // 设置线检测的密度阈值
// opts.n_bins = 1024; // 设置线检测的直方图bin数
// opts.min_length = 0; // 设置线检测的最小线段长度
// opts.max_line_gap = -1; // 设置线检测的最大线段间隔
// Ptr<line_descriptor::LSDDetector> lsd = line_descriptor::LSDDetector::createLSDDetector(opt );
///***********************************************************************************
Ptr<line_descriptor::LSDDetector> lsd = line_descriptor::LSDDetector::createLSDDetector();
//***************参数设置二
// // 创建OptimizePose对象
// cv::line_descriptor::OptimizePose pose;
// // 设置计算参数
// pose.setLSDDetector(lsd);
// pose.setLineLengthThresh(40);
// pose.setMinLineLength(10);
// pose.setNumOfOctave(1);
// pose.setRatioThreshold(0.9f);
// pose.setScale(1.2f);
//******************************
// 计算直线特征
vector<line_descriptor::KeyLine> keylines;
lsd->detect(img, keylines);
// 输出计算结果
cout << "Number of keylines: " << keylines.size() << endl;
return 0;
}