【计算机视觉系列实战教程 (实战03)】:提取两点之间的边缘点

发布于:2024-07-05 ⋅ 阅读:(18) ⋅ 点赞:(0)

1、目的

图像中任意两点(起点到终点)之间,提取由深色到浅色(或由浅色到深色)的第一个边缘点。这样有利于精确地提取指定区域内的图像边缘。

经实践证明:本算法能够有效地定位两点之间的边缘信息,在实际工程中只需修改候选边缘点的筛选即可。

2、实践步骤

(1)拟合直线

将两点之间的连续坐标提取出来存储在vector容器中

/* 向量之间的直线点拟合,获取起点到终点所在直线的所有坐标点(有序:从起点到终点) */
int GetLinePntsByVec(const cv::Point &PStart,
					const cv::Point &PEnd, 
					std::vector<cv::Point> &vPtsLine)
{
	//vPtsLine用于存放从起点到终点的连续坐标
	vPtsLine.push_back(PStart);
	double dDis = 
		cv::sqrt(cv::pow(PStart.x-PEnd.x,2) + cv::pow(PStart.y-PEnd.y, 2) );//向量的长度
	if(dDis = 0 )
	{
		return -1;
	}
	int iN = std::max(std::abs(PStart.x-PEnd.x),std::abs(PStart.y-PEnd.y));
	vPtsLine.reserve(iN); //直线点的数量
	double dDeta = dDis / iN; //取样步长
	cv::Vec2d v0 = cv::Vec2d(PEnd.x-PStart.x, PEnd.y-PStart.y) / dDis; //两点之间的单位向量
	for(int i = 1; i<iN; ++i)
	{
		vPtsLine.push_back(
			cv::Point(
				cvRound(double(vPtsLine[0].x)+i*dDeta*v0[0]),
				cvRound(double(vPtsLine[0].y)+i*dDeta*v0[1])
			)
		);
	}
	return 1;
}

(2)求直线所在像素点的梯度

计算直线上每一个像素点的梯度值

int GetGradFrmLine(const cv::Mat &imGrayBorder, 
				const std::vector<cv::Point> &vPtsLine,
				std::vector<double>& vGradLine)
{	
	//遍历直线上的像素,并得到每个像素的梯度(方向梯度)
	cv::Mat kernel_x = (cv::Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
	 cv::Mat kernel_y = (cv::Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
	 int iResX = 0;
	 int iResY = 0;
	 for (auto& pt : vPtsLine)
	 {
	     iResX = filter2D_Signal(imGrayBorder, pt, kernel_x);
	     iResY = filter2D_Signal(imGrayBorder, pt, kernel_y);
	     double dRes = cv::Vec2d(iResX, iResY).dot(v0); //得到直线上当前点方向梯度值
	     vGradLine.push_back(abs(dRes));
	 }
	 if(vGradLine.empty())
	 {
	 	return -1;
	 }
	 return 1;
}

(3)根据直线上的方向梯度值得到边缘点

筛选最佳的点作为直线经过的边缘点

本函数将梯度最大的值作为提取的结果,用户可根据自己的实际需求修改:


inline bool myCompareSec(const std::pair<int, double>& a, const std::pair<int, double>& b)
{
    return a.second > b.second;
}

inline bool myCompareFir(const std::pair<int, double>& a, const std::pair<int, double>& b)
{
    return a.first < b.first;
}

int GetEdgPntFrmLine(
	const cv::Mat &imGrayBorder, 
	cv::Point &PStart, 
	cv::Point &PEnd,
	cv::Point &Edg,
	bool bWhtToBlk)
{
	std::vector<cv::Point> vPntsLine;
	//直线拟合得到点集合vPntsLine
	GetLinePntsByVec(PStart, PEnd, vPntsLine);
	//计算直线上每一个像素点的梯度
	std::vector<double> vGradLine;
	GetGradFrmLine(imGrayBorder, vPtsLine,vGradLine);
	//对梯度值进行排序,并跟踪索引
	std::vector<std::pair<int, double>> vIdxGrad;
	for (int i = 0; i < vSobel.size(); ++i)
	{
	    vIdxGrad.push_back(std::pair<int, double>(i, vSobel[i]));
	}
	std::sort(vIdxGrad.begin(), vIdxGrad.end(), myCompareSec);
	//对梯度最大的诺干个点进行筛选(作为候选边缘点)
    std::vector<int> vIdxMax;
    for (int i = 0; i < std::max(int(vIdxSobel.size() / 15), 30); ++i)
    {
        if (i == 0) vIdxMax.push_back(vIdxGrad[0].first);
        else
        {
            int iCnt = 0;
            for (const int& idx : vIdxMax)
            {
                if (abs(idx - vIdxGrad[i].first) > 10) 
                    iCnt++;
            }
            if (iCnt == vIdxMax.size()) {
                if (vIdxGrad[i].second > 40.0)
                {
                    vIdxMax.push_back(vIdxGrad[i].first);
                }
            }
        }
    }
    std::sort(vIdxMax.begin(), vIdxMax.end());
	//这里的实例将梯度最大的点作为边缘点(可更具实际需求处理这些候选点)
	PEdg = vPtsLine[vIdxMax[vIdxMax.size()-1]];
	return 1;

	
}