废话少数,先上代码(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. 首先将激光图片高斯滤波一下,高斯方差,其中是光条的宽度
2. 求出图像每个点的一阶导数和二阶导数,,同时得到该点的Hessian矩阵
3. Hessian矩阵一共有两个特征值,其中最大的那个对应的特征向量就是光条的法线方向,用表示,以点为基准点,则光条中心的亚像素坐标为
其中
4. 判断,即一阶导数为0的点是否位于当前像素内,且方向的二阶导数大于指定的阈值,则该点为光条的中心点,为亚像素坐标。
下面是每一步的结果展示
原图
高斯滤波之后的图
提取结果
参考: