opencv|C++实现最简单的边缘连接(局部处理)

理想情况下,边缘检测应该仅产生位于边缘上的像素的集合.实际上,由于噪声,不均匀照明引起的边缘间断,以及其他引入灰度值虚假的不连续的影响,这些像素并不能完全描述边缘特性. 因此,一般在边缘检测后面会紧跟连接算法,将边缘像素组合成有意义的边缘或区域边界.
局部处理是连接边缘点最简单的方法之一,是在每个点(x,y)处的一个小邻域内分析像素的特点,根据特定的准则,将所有的相似点连接起来,以形成根据特定准则满足相同特性像素的一条边缘
用于确定边缘像素相似性的两个主要的性质:
(1)梯度向量的强度 opencv|C++实现最简单的边缘连接(局部处理)
文章图片

(2)梯度向量的方向 opencv|C++实现最简单的边缘连接(局部处理)
文章图片

(x,y)表示一个像素点 (s,t)表示以(x,y)为中心的所有的邻域点
如果(s,t)既满足幅度准则又满足角度准则,那么我们将(s,t)连接到(x,y),在图像的每个像素点重复这一操作,当邻域的中心从一个点到另一个点时必须将已连接的点记录下来,简单的记录过程就是将每组被连接的像素分配不同的灰度值
【opencv|C++实现最简单的边缘连接(局部处理)】根据以上原理得出步骤一:
1 计算图像x y方向的梯度 g_x,g_y
2 根据梯度矩阵计算幅度矩阵和角度矩阵 Mag angle
3 根据幅度准则和方向准则将图像赋予不同的灰度值

#include #include #include// void CalMag(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst1,cv::Mat &dst2){ for(int i=0; i(i,j); float gy=src2.at(i,j); dst1.at(i,j)=std::sqrt(std::pow(gx,2)+std::pow(gy,2)); dst2.at(i,j)=std::atan2(gy,gx); } } }void connect(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst){ for(int i=1; i(i,j); float anglex=src2.at(i,j); for(int m=i-1; m<=i+1; m++){ for(int n=j-1; n<=j+1; n++){ float magy=src1.at(m,n); float angley=src2.at(m,n); if(m!=i&&n!=j) { if (std::abs(magx - magy) <= 30 && std::abs(anglex - angley) <= 5) { int dd = (int) (magx / (1.4)); //原因是最大的值为sqrt(2)*255 dst.at(m, n) = dd; } } } } } } }int main() { cv::Mat src=https://www.it610.com/article/cv::imread("../3.png"); cv::Mat gray; cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY); cv::imshow("gray",gray); gray.convertTo(gray,CV_32FC1); cv::Mat gradient_x; gradient_x=(cv::Mat_(3,3)<<-1,0,1,-2,0,2,-1,0,1); cv::Mat gradient_y; gradient_y=(cv::Mat_(3,3)<<-1,-2,-1,0,0,0,1,2,1); cv::Mat g_x; g_x=cv::Mat::zeros(gray.size(),CV_32FC1); cv::Mat g_y; g_y=cv::Mat::zeros(gray.size(),CV_32FC1); //使用卷积计算x方向 y方向的梯度 cv::filter2D(gray,g_x,-1,gradient_x); cv::filter2D(gray,g_y,-1,gradient_y); cv::imshow("src",src); cv::imshow("x",g_x); cv::imshow("y",g_y); cv::Mat Mag(gray.size(),CV_32FC1); cv::Mat angle(gray.size(),CV_32FC1); //计算幅度与角度 CalMag(g_x,g_y,Mag,angle); cv::Mat dst; dst=cv::Mat::zeros(Mag.size(),CV_8U); //根据范围赋予不同的值 connect(Mag,angle,dst); cv::imshow("mag",Mag); cv::imwrite("../mag.png",Mag); cv::imshow("dst",dst); cv::imwrite("../dst.png",dst); cv::waitKey(0); return 0; }

opencv|C++实现最简单的边缘连接(局部处理)
文章图片

原图
opencv|C++实现最简单的边缘连接(局部处理)
文章图片

结果图
由于前面的计算代价很高,要计算每个像素的所有的邻域,通过将步骤一简化得到步骤二:
1 计算图像x y方向的梯度 g_x,g_y
2 根据梯度矩阵计算幅度矩阵和角度矩阵 Mag angle
3 根据以下公式计算图像g
opencv|C++实现最简单的边缘连接(局部处理)
文章图片

4 扫描g的行 并在不超过指定长度的K的每一行填充(置1)所有的缝隙(0),缝隙一定要在一个或多个1的两端
5 为在任何其他方向上theta检测缝隙,以角度theta旋转g 并应用步骤4中的水平扫描过程,然后将结果以-theta旋转回来
由于旋转图像比较复杂,要考虑图像的大小和灰度值的变换,所以我只旋转了90度 也就是只考虑水平边缘和垂直边缘的情况
#include #include #includevoid CalMag(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst1,cv::Mat &dst2){ for(int i=0; i(i,j); float gy=src2.at(i,j); dst1.at(i,j)=std::sqrt(std::pow(gx,2)+std::pow(gy,2)); dst2.at(i,j)=std::atan2(gy,gx); } } }void connect(cv::Mat &src1,cv::Mat &src2,cv::Mat &dst){ for(int i=1; i(i,j); float anglex=src2.at(i,j); if(magx>180&&anglex>-2&&anglex<2) dst.at(i,j)=255; } } }cv::Mat rotate_arbitrarily_angle1(cv::Mat matSrc, float angle, bool direction,int height,int width) { float theta = angle * CV_PI / 180.0; int nRowsSrc = https://www.it610.com/article/matSrc.rows; int nColsSrc = matSrc.cols; // 如果是顺时针旋转 if (!direction) theta = 2 * CV_PI - theta; // 全部以逆时针旋转来计算 // 逆时针旋转矩阵 float matRotate[3][3]{ { std::cos(theta), -std::sin(theta), 0}, {std::sin(theta), std::cos(theta), 0 }, {0, 0, 1} }; float pt[3][2]{ { 0, nRowsSrc }, {nColsSrc, nRowsSrc}, {nColsSrc, 0} }; for (int i = 0; i < 3; i++) { float x = pt[i][0] * matRotate[0][0] + pt[i][1] * matRotate[1][0]; float y = pt[i][0] * matRotate[0][1] + pt[i][1] * matRotate[1][1]; pt[i][0] = x; pt[i][1] = y; } // 计算出旋转后图像的极值点和尺寸 float fMin_x = std::min(std::min(std::min(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0); float fMin_y = std::min(std::min(std::min(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0); float fMax_x = std::max(std::max(std::max(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0); float fMax_y = std::max(std::max(std::max(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0); int nRows = cvRound(fMax_y - fMin_y + 0.5) + 1; int nCols = cvRound(fMax_x - fMin_x + 0.5) + 1; int nMin_x = cvRound(fMin_x + 0.5); int nMin_y = cvRound(fMin_y + 0.5); // 拷贝输出图像 cv::Mat matRet(nRows, nCols, matSrc.type(), cv::Scalar(0)); for (int j = 0; j < nRows; j++) { for (int i = 0; i < nCols; i++) { // 计算出输出图像在原图像中的对应点的坐标,然后复制该坐标的灰度值 // 因为是逆时针转换,所以这里映射到原图像的时候可以看成是,输出图像 // 到顺时针旋转到原图像的,而顺时针旋转矩阵刚好是逆时针旋转矩阵的转置 // 同时还要考虑到要把旋转后的图像的左上角移动到坐标原点。 int x = (i + nMin_x) * matRotate[0][0] + (j + nMin_y) * matRotate[0][1]; int y = (i + nMin_x) * matRotate[1][0] + (j + nMin_y) * matRotate[1][1]; if (x>= 0 && x < nColsSrc && y >= 0 && y < nRowsSrc) { matRet.at(j, i) = matSrc.at(y, x); } } } if(direction== false){ int x = (matRet.cols -width) / 2; int y = (matRet.rows -height) / 2; cv::Rect rect(x, y, width, height); matRet = cv::Mat(matRet,rect); } return matRet; }void fill(cv::Mat &src,cv::Mat &dst) { for(int i=0; i(i,j-1); int g2=src.at(i,j); int g3=src.at(i,j+1); if(g2==255) { dst.at(i,j)=255; if(g1==0){ dst.at(i,j-1)=255; } if(g3==0){ dst.at(i,j+1)=255; } } } } }int main() { cv::Mat src=https://www.it610.com/article/cv::imread("../3.png"); cv::Mat gray; cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY); //步骤1:计算梯度 gray.convertTo(gray,CV_32FC1); cv::Mat gradient_x; gradient_x=(cv::Mat_(3,3)<<-1,0,1,-2,0,2,-1,0,1); cv::Mat gradient_y; gradient_y=(cv::Mat_(3,3)<<-1,-2,-1,0,0,0,1,2,1); cv::Mat g_x; g_x=cv::Mat::zeros(gray.size(),CV_32FC1); cv::Mat g_y; g_y=cv::Mat::zeros(gray.size(),CV_32FC1); cv::filter2D(gray,g_x,-1,gradient_x); cv::filter2D(gray,g_y,-1,gradient_y); //步骤2 计算幅度角度 cv::Mat Mag(gray.size(),CV_32FC1); cv::Mat angle(gray.size(),CV_32FC1); CalMag(g_x,g_y,Mag,angle); //步骤3 赋值图像只有0 255 cv::Mat dst; dst=cv::Mat::zeros(Mag.size(),CV_8U); connect(Mag,angle,dst); cv::imshow("dst.png",dst); cv::imwrite("../dst.png",dst); //步骤4 5 扫描填充 int height=src.rows; int width=src.cols; cv::Mat dst1; dst1=cv::Mat::zeros(Mag.size(),CV_8U); fill(dst,dst1); dst1=rotate_arbitrarily_angle1(dst1,90,true,height,width); cv::imshow("dst2",dst1); cv::imwrite("../dst1.png",dst1); cv::Mat dst2; dst2=cv::Mat::zeros(dst1.size(),dst1.type()); fill(dst1,dst2); dst2=rotate_arbitrarily_angle1(dst2,90, false,height,width); cv::imshow("dst3",dst2); cv::imwrite("../dst3.png",dst2); cv::waitKey(0); return 0; }

opencv|C++实现最简单的边缘连接(局部处理)
文章图片

原图
opencv|C++实现最简单的边缘连接(局部处理)
文章图片

步骤3的结果
opencv|C++实现最简单的边缘连接(局部处理)
文章图片

扫描之后旋转90度的结果
opencv|C++实现最简单的边缘连接(局部处理)
文章图片

扫描填充旋转-90度的结果

    推荐阅读