Steger算法原理详解

看了好久,感觉明白一些,记录一下
Steger提取激光中心线的原理大致为:
首先将激光图片高斯模糊一下,卷积核大小要为激光线条的宽度,将激光的截面光强分布变为理想的正态函数。
实际的激光线条有一个宽度
Steger算法原理详解_第1张图片
要变为理想无宽度的
Steger算法原理详解_第2张图片
然后求出每一个点对应的二阶导数(hessian矩阵)、一阶导数的值,来构造一个该像素点附近的光强分布函数(泰勒展开)。
(一阶、二阶导数通过构造卷积核来计算,代码中体现)
求出二阶导数的同时,可以得到hessian矩阵。
在这里插入图片描述
Hessian矩阵的特征值就是形容其在该点附近特征向量方向的凹凸性,特征值越大,凸性越强。
所以hessian特征值绝对值最大的对应的特征向量就是光强变化最快的方向,我们可以向这个地方去寻找最强光强的像素点。(也就是中心线上的点)
构造出当前像素附近的光强分布的多项式(泰勒展开)如下图。
e指的是当前点坐标,I(u0,v0)代表当前像素的光强值,[Iu,Iv]代表一阶导数的值,H(u,v)代表二阶导数的值,t是一个未知参数
f(x+t*nx,y+t*ny)
现在已经知道了当前坐标(u0,v0)也知道了光强变化最快的方向(uu,vv),二阶导数(hessian矩阵)、一阶导数。
hessian矩阵特征值最大的对应的特征向量(uu,vv)表示梯度上升最快的方向(光强改变最快),要往这个方向改变
这个当前坐标附近的光强分布函数可以看作是关于t的一个分布函数
则可以对t求导,令导数为0的地方就是光强最强的地方(极值),也就是激光中心点。
在这里插入图片描述
可以改写成矩阵的形式
Steger算法原理详解_第3张图片
化简可以得到
在这里插入图片描述
计算出t后
(泰勒展开只能在很短的距离内适用)
如果teu和tev都小于0.5说明这个极值点就位于当前像素内。附近光强分布函数适用
如果teu和tev都很大,则说明这个光强最强点有点距离(同时附近光强分布函数不适用,不可以用t计算最强点),要跳过当前点,继续扫描距离激光中心更近的点。

代码如下

//path为一张图片的路径
std::vector<double> ProcessTool::StegerLine(std::string path)
{
	Mat img0 = imread(path, 1);
	//滤波去噪点,不需要可以注释掉,去掉之后灰度图要去注释
	cv::medianBlur(img0, img0, 5);
	cvtColor(img0, img0, CV_RGB2GRAY);
	threshold(img0, img0, 0, 255, THRESH_OTSU);
	
	Mat img;
	//灰度图
	//cvtColor(img0, img0, CV_BGR2GRAY);
	img = img0.clone();
	//高斯滤波
	img.convertTo(img, CV_32FC1);
	GaussianBlur(img, img, Size(5, 5), 2, 2);
	//高斯卷积,得到一个山峰强度的光条图
	//高斯卷积核实际上就是一个正态函数
	imshow("高斯滤波",img);
	waitKey(0);
	/**********************************/
	//构造了求导数的卷积核
	/***********************************/
	//这里使用的是数值计算的偏导数而非得到求导公式
	Mat m1, m2;
	m1 = (Mat_<float>(1, 2) << 1, -1);//x偏导一阶偏导 = f(x+1,y)-f(x,y)
	m2 = (Mat_<float>(2, 1) << 1, -1);//y偏导一阶偏导 = f(x,y+1)-f(x,y)
	Mat dx, dy;
	//使用m1卷积 当前坐标的灰度值-(x+1)的灰度值
	filter2D(img, dx, CV_32FC1, m1);//卷积
	filter2D(img, dy, CV_32FC1, m2);//卷积
	//二阶偏导数
	Mat m3, m4, m5;
	m3 = (Mat_<float>(1, 3) << 1, -2, 1);//求二阶x偏导的矩阵=f(x+1,y)+f(x-1,y)-2f(x,y)
	m4 = (Mat_<float>(3, 1) << 1, -2, 1);//二阶y偏导的矩阵
	m5 = (Mat_<float>(2, 2) << 1, -1, -1, 1);//二阶xy偏导矩阵=f(x+1,y+1)-f(x+1,y)-f(x,y+1)+f(x,y)
	Mat dxx, dyy, dxy;
	//求得对应点的偏导数值
	filter2D(img, dxx, CV_32FC1, m3);
	filter2D(img, dyy, CV_32FC1, m4);
	filter2D(img, dxy, CV_32FC1, m5);
	//hessian矩阵
	double maxD = -1;
	int imgcol = img.cols;
	int imgrow = img.rows;
	std::vector<double> Pt;
	for (int i = 0; i < imgcol; i++)
	{
		for (int j = 0; j < imgrow; j++)
		{
			//全黑的时候就不计算了,大于一个阈值的时候开始计算
			if (img0.at<uchar>(j, i) > 50)
			{
				Mat hessian(2, 2, CV_32FC1);
				//得到对应点的hessian矩阵
				//对每个点,构造出hessian矩阵
				hessian.at<float>(0, 0) = dxx.at<float>(j, i);
				hessian.at<float>(0, 1) = dxy.at<float>(j, i);
				hessian.at<float>(1, 0) = dxy.at<float>(j, i);
				hessian.at<float>(1, 1) = dyy.at<float>(j, i);
				//特征值和特征向量
				Mat eValue;
				Mat eVectors;
				// hessian矩阵2*2 对称矩阵, 有两个特征值
				// 获取当前点的特征值和特征向量
				// 存在两个特征值及对应特征向量
				eigen(hessian, eValue, eVectors);
				//std::cout << "eValue:" << eValue << std::endl << "eVectors:" << eVectors << std::endl;
				//nx为特征值最大对应的x,ny为特征值最大对应的y
				double nx, ny;
				double fmaxD = 0;
				//求特征值绝对值最大时对应的特征向量
				//Hessian矩阵的特征值就是形容其在该点附近特征向量方向的凹凸性,特征值越大,凸性越强。
				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);
					fmaxD = eValue.at<float>(0, 0);
				}
				else
				{
					nx = eVectors.at<float>(1, 0);
					ny = eVectors.at<float>(1, 1);
					fmaxD = eValue.at<float>(1, 0);
				}
				// 使用泰勒展开表示光强的分布函数f(x+t*nx,y+t*ny)可以表示当前坐标附近的光强分布
				// f(x+t*nx,y+t*ny) hessian矩阵特征值最大的对应的特征向量(nx,ny)表示梯度上升最快的方向(光强改变最快),要往这个方向改变  
				// 由于x,y,nx,ny已知,这个当前坐标附近的光强分布函数可以看作是关于t的一个分布函数
				// 则可以对t求导,令导数为0的地方就是光强最强的地方(极值),也就是激光中心点。
				// 如果t*nx和t*ny都小于0.5说明这个极值点就位于当前像素内,附近光强分布函数适用
				// 如果t*nx和t*ny都很大,附近光强分布函数不适用,则跳过这个点,继续扫描距离激光中心更近的点
				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));
				//t* nx和t* nx代表光条中心点距离当前像素的距离
				if (fabs(t * nx) <= 0.5 && fabs(t * ny) <= 0.5)
				{
					Pt.push_back(i);
					Pt.push_back(j);
				}
			}

		}
	}
	cvtColor(img0, img0, CV_GRAY2BGR);
	//显示中心点
	ShowLine(Pt, img0);
	return Pt;
}
//显示点的函数
ShowLine(std::vector<double> Points, Mat image)
{
	for (int k = 0; k < Points.size() / 2; k++)
	{
		Point rpt;
		rpt.x = Points[2 * k + 0];
		rpt.y = Points[2 * k + 1];
		image.at<Vec3b>(rpt.y, rpt.x) = Vec3b(0, 0, 255);
		//circle(img0, rpt, 1, Scalar(0, 0, 255));
	}
	cv::imshow("GetLine", image);
	//cv::imwrite("output.bmp", img0);
	cv::waitKey(1);
}

参考:
1.https://blog.csdn.net/jiangxing11/article/details/120056536
2.https://blog.csdn.net/Dangkie/article/details/78996761
3.Steger C. An unbiased detector of curvilinear structures. IEEE Trans Pattern Anal Mach Intell[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 1998, 20(2):113-125.

你可能感兴趣的:(计算机视觉,视觉检测,图像处理)