使用steger算法提取线激光,用于口内扫描(c++)

废话少数,先上代码(C++)

#include <iostream>
#include <opencv2/opencv.hpp>

int main(int argc, char** argv)
{
    // 以灰度模式读取图像 
    cv::Mat src_img = cv::imread("1-spec-l.png", cv::IMREAD_GRAYSCALE);
    if (src_img.empty()) {
        std::cerr << "Error: Unable to open image file." << std::endl;
        return EXIT_FAILURE;
    }


    // 创建目标图像
    cv::Mat dst_img = src_img.clone();
    cv::cvtColor(dst_img, dst_img, cv::COLOR_GRAY2RGB);

    // 存储特征点
    std::vector<cv::Point2f> pts;

    // 高斯模糊去噪
    cv::GaussianBlur(src_img, src_img, cv::Size(5, 5), 2, 2);


    // 一阶偏导数
    cv::Mat m1 = (cv::Mat_<float>(1, 3) << 1, 0, -1);  // 水平滤波器,1行3列,初始化为1,0,-1
    cv::Mat m2 = (cv::Mat_<float>(3, 1) << 1, 0, -1);  // 垂直滤波器,3行1列
    cv::Mat dx, dy;
    cv::filter2D(src_img, dx, CV_32FC1, m1);  // 得到水平滤波后的图像,即x方向的梯度dx
    cv::filter2D(src_img, dy, CV_32FC1, m2);  // 得到垂直滤波后的图像,即y方向的梯度dy

    // 二阶偏导数dxx, dyy, dxy
    cv::Mat m3 = (cv::Mat_<float>(1, 3) << 1, -2, 1);
    cv::Mat m4 = (cv::Mat_<float>(3, 1) << 1, -2, 1);
    cv::Mat m5 = (cv::Mat_<float>(2, 2) << 1, -1, -1, 1);
    cv::Mat dxx, dyy, dxy;
    cv::filter2D(src_img, dxx, CV_32FC1, m3);
    cv::filter2D(src_img, dyy, CV_32FC1, m4);
    cv::filter2D(src_img, dxy, CV_32FC1, m5);

    // 得到 Hessian 矩阵和特征点提取
    for (int i = 0; i < src_img.cols; ++i)
    {
        for (int j = 0; j < src_img.rows; ++j)
        {
            if (src_img.at<uchar>(j, i) > 50)
            {
                cv::Mat hessian = (cv::Mat_<float>(2, 2) << dxx.at<float>(j, i), dxy.at<float>(j, i),
                    dxy.at<float>(j, i), dyy.at<float>(j, i));

                cv::Mat eValue, eVectors;

                // eigen 函数计算Hessian矩阵的特征值(2个)和特征向量
                cv::eigen(hessian, eValue, eVectors);

                //最大特征值对应的特征向量,即为对应于光条的法线方向,用(nx,ny)表示
                double nx, ny;
                if (fabs(eValue.at<float>(0, 0)) >= fabs(eValue.at<float>(1, 0)))
                {
                    nx = eVectors.at<float>(0, 0);
                    ny = eVectors.at<float>(0, 1);
                }
                else
                {
                    nx = eVectors.at<float>(1, 0);
                    ny = eVectors.at<float>(1, 1);
                }

                double t = -(nx * dx.at<float>(j, i) + ny * dy.at<float>(j, i)) /
                    (nx * nx * dxx.at<float>(j, i) + 2 * nx * ny * dxy.at<float>(j, i) + ny * ny * dyy.at<float>(j, i));

                if (fabs(t * nx) <= 0.5 && fabs(t * ny) <= 0.5)
                {
                    pts.push_back(cv::Point2f(i + t * nx, j + t * ny));
                }
            }
        }
    }

    // 绘制轮廓点  使用 cv::circle 在 dst_img 上绘制一个半径为 1 像素的圆形:
    for (const auto& pt : pts)
    {
        cv::circle(dst_img, cv::Point(cvRound(pt.x), cvRound(pt.y)), 1, cv::Scalar(0, 0, 255), -1);
    }

    // 保存结果图像
    cv::imwrite("processed.jpg", dst_img);

    // 显示结果图像(可选)
    cv::imshow("Processed Image", dst_img);
    cv::waitKey(0);

    return EXIT_SUCCESS;
}

steger算法是基于Hessian矩阵,能够实现光条中心的亚像素精度定位,提取激光线的大致流程:通过Hessian矩阵能够得到光条的法线方向,然后在法线方向利用泰勒展开得到亚像素位置。

1. 首先将激光图片高斯滤波一下,高斯方差\sigma <\frac{\omega }{\sqrt{3}},其中\omega是光条的宽度

2. 求出图像每个点的一阶导数和二阶导数,,同时得到该点的Hessian矩阵

H ( x , y ) = \begin{bmatrix} d _ { x x } \ \ d _ { x y } \\ d _ { x y } \ \ d _ { y y } \end{bmatrix}

3. Hessian矩阵一共有两个特征值,其中最大的那个对应的特征向量就是光条的法线方向,用( n _ { x } , n _ { y } )表示,以点( x _ { 0 } , y _ { 0 } )为基准点,则光条中心的亚像素坐标为( p _ { x } , p _ { y } ) = ( x _ { 0 } + t n _ { x } , y _ { 0 } + t n _ { y } )

其中t = - \frac { n _ { x } d _ { x } + n _ { y } d _ { y } } { n _ { x } ^ { 2 } d _ { x x } + 2 n _ { x } n _ { y } d _ { x y } + n _ { y } ^ { 2 } d _ { y y } }

4. 判断( t n _ { x } , t n _ { y } ) \in \left[ - 0 . 5 , 0 . 5 \right] \times \left[ - 0 . 5 , 0 . 5 \right],即一阶导数为0的点是否位于当前像素内,且( n _ { x } , n _ { y } )方向的二阶导数大于指定的阈值,则该点( x _ { 0 } , y _ { 0 } )为光条的中心点,( p _ { x } , p _ { y } )为亚像素坐标。

下面是每一步的结果展示

原图

高斯滤波之后的图

提取结果

参考:

激光条纹中心线提取算法总结和复现_激光中心条纹提取-CSDN博客

Steger算法原理详解-CSDN博客

以下是一个基本的Python实现Steger算法提取激光线中心的例子: ```python import cv2 import numpy as np def steger_algorithm(img): # 进行高斯平滑以减少噪声 img = cv2.GaussianBlur(img, (5, 5), 0) # 求出图像的梯度 dx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5) dy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5) mag = np.sqrt(dx ** 2 + dy ** 2) # 二值化图像 thresh = cv2.threshold(mag, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1] # 进行形态学操作 kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)) thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel) # 计算距离变换 dist = cv2.distanceTransform(thresh, cv2.DIST_L2, 5) dist = cv2.normalize(dist, None, 0, 1.0, cv2.NORM_MINMAX) # 计算Hessian矩阵的特征值 hxx = cv2.Sobel(dx, cv2.CV_64F, 1, 0, ksize=5) hyy = cv2.Sobel(dy, cv2.CV_64F, 0, 1, ksize=5) hxy = cv2.Sobel(dx, cv2.CV_64F, 0, 1, ksize=5) hessian = np.zeros(img.shape[:2] + (2, 2)) hessian[..., 0, 0] = hxx hessian[..., 0, 1] = hxy hessian[..., 1, 0] = hxy hessian[..., 1, 1] = hyy lambda1, lambda2 = cv2.eigen(hessian) # 计算线中心 center = np.zeros_like(img) center[dist > 0.5] = 255 center[lambda2 < 0] = 0 center[lambda1 > 0.01] = 0 return center ``` 以上代码假设输入的图像是灰度图像。它首先进行高斯平滑以减少噪声,然后计算图像的梯度,并将其平方和开方以获得梯度幅值。接下来,它对梯度幅值进行二值化,并通过形态学操作来消除噪声。然后,它计算距离变换,并归一化到0到1之间。接下来,它计算Hessian矩阵的特征值,并使用它们来确定激光线的中心。最后,它返回一个二值图像,其中1表示激光线的中心。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值