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;//直线拟合得到点集合vPntsLineGetLinePntsByVec(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;}