LSD线特征提取方法+Opencv实现C++

边缘检测: 传统的直线检测方法一般采用边缘检测算法提取二值边缘图像,然后利用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;
}

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值