OpenCV+python:Canny边缘检测算法
1,边缘处理
图像边缘信息主要集中在高频段,通常说图像锐化或检测边缘,实质就是高频滤波。我们知道微分运算是求信号的变化率,具有加强高频分量的作用。
在空域运算中来说,对图像的锐化就是计算微分。由于数字图像的离散信号,微分运算就变成计算差分或梯度。
图像处理中有多种边缘检测(梯度)算子,常用的包括普通一阶差分,Robert算子(交叉差分),Sobel算子等等,是基于寻找梯度强度。拉普拉斯算子(二阶差分)是基于过零点检测。通过计算梯度,设置阀值,得到边缘图像。
2,Canny边缘检测算法简介
Canny边缘检测算子是一种多级检测算法。1986年由John F. Canny提出,同时提出了边缘检测的三大准则:
(1) 低错误率的边缘检测:检测算法应该精确地找到图像中的尽可能多的边缘,尽可能的减少漏检和误检。
(2) 最优定位:检测的边缘点应该精确地定位于边缘的中心。
(3)图像中的任意边缘应该只被标记一次,同时图像噪声不应产生伪边缘。
为了满足这些要求,Canny使用了变分法。Canny检测器中的最优函数使用四个指数项的和来描述,它可以由高斯函数的一阶导数来近似。
3,Canny边缘检测算法的处理流程
(1)灰度转换:
该部分是按照Canny算法通常处理的图像为灰度图,如果获取的彩色图像,那首先就得进行灰度化。以RGB格式的彩图为例,通常灰度化采用的公式是:
Gray=0.299R+0.587G+0.114B
在OpenCV中也提供相关的API(cvtColor)
源代码:
///第一步:灰度化 2 IplImage * ColorImage=cvLoadImage("c:\\photo.bmp",1);3 if (ColorImage==NULL)4 {5 printf("image read error");6 return 0;7 }8 cvNamedWindow("Sourceimg",0); 9 cvShowImage("Sourceimg",ColorImage); //
10 IplImage * OpenCvGrayImage;
11 OpenCvGrayImage=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);
12 float data1,data2,data3;
13 for (int i=0;i<ColorImage->height;i++)
14 {
15 for (int j=0;j<ColorImage->width;j++)
16 {
17 data1=(uchar)(ColorImage->imageData[i*ColorImage->widthStep+j*3+0]);
18 data2=(uchar)(ColorImage->imageData[i*ColorImage->widthStep+j*3+1]);
19 data3=(uchar)(ColorImage->imageData[i*ColorImage->widthStep+j*3+2]);
20 OpenCvGrayImage->imageData[i*OpenCvGrayImage->widthStep+j]=(uchar)(0.07*data1 + 0.71*data2 + 0.21*data3);
21 }
22 }
23 cvNamedWindow("GrayImage",0);
24 cvShowImage("GrayImage",OpenCvGrayImage); //显示灰度图
25 cvWaitKey(0);
26 cvDestroyWindow("GrayImage");
(2) 使用高斯滤波器,以平滑图像,滤除噪声(高斯模糊):
了尽可能减少噪声对边缘检测结果的影响,所以必须滤除噪声以防止由噪声引起的错误检测。为了平滑图像,使用高斯滤波器与图像进行卷积,该步骤将平滑图像,以减少边缘检测器上明显的噪声影响。大小为(2k+1)x(2k+1)的高斯滤波器核的生成方程式由下式给出:
下面是一个sigma = 1.4,尺寸为3x3的高斯卷积核的例子(需要注意归一化):
若图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的亮度值为:
其中*为卷积符号,sum表示矩阵中所有元素相加求和。重要的是需要理解,高斯卷积核大小的选择将影响Canny检测器的性能。尺寸越大,检测器对噪声的敏感度越低,但是边缘检测的定位误差也将略有增加。一般5x5是一个比较不错的trade off。
源代码:
///第二步:高斯滤波
///double nSigma=0.2;int nWindowSize=1+2*ceil(3*nSigma);//通过sigma得到窗口大小int nCenter=nWindowSize/2;int nWidth=OpenCvGrayImage->width;int nHeight=OpenCvGrayImage->height;IplImage * pCanny;pCanny=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);//生成二维滤波核double *pKernel_2=new double[nWindowSize*nWindowSize];double d_sum=0.0;for(int i=0;i<nWindowSize;i++){for (int j=0;j<nWindowSize;j++){double n_Disx=i-nCenter;//水平方向距离中心像素距离double n_Disy=j-nCenter;pKernel_2[j*nWindowSize+i]=exp(-0.5*(n_Disx*n_Disx+n_Disy*n_Disy)/(nSigma*nSigma))/(2.0*3.1415926)*nSigma*nSigma; d_sum=d_sum+pKernel_2[j*nWindowSize+i];}}for(int i=0;i<nWindowSize;i++)//归一化处理{for (int j=0;j<nWindowSize;j++){pKernel_2[j*nWindowSize+i]=pKernel_2[j*nWindowSize+i]/d_sum;}}//输出模板for (int i=0;i<nWindowSize*nWindowSize;i++){if (i%(nWindowSize)==0){printf("\n");}printf("%.10f ",pKernel_2[i]);}//滤波处理for (int s=0;s<nWidth;s++){for (int t=0;t<nHeight;t++){double dFilter=0;double dSum=0;//当前坐标(s,t)//获取8邻域for (int x=-nCenter;x<=nCenter;x++){for (int y=-nCenter;y<=nCenter;y++){if ((x+s>=0)&&(x+s<nWidth)&&(y+t>=0)&&(y+t<nHeight))//判断是否越界{double currentvalue=(double)OpenCvGrayImage->imageData[(y+t)*OpenCvGrayImage->widthStep+x+s];dFilter+=currentvalue*pKernel_2[x+nCenter+(y+nCenter)*nCenter];dSum+=pKernel_2[x+nCenter+(y+nCenter)*nCenter];}}}pCanny->imageData[t*pCanny->widthStep+s]=(uchar)(dFilter/dSum);}}cvNamedWindow("GaussImage",0); cvShowImage("GaussImage",pCanny); //显示高斯图cvWaitKey(0); cvDestroyWindow("GaussImage");
(3)计算梯度强度和方向:
图像中的边缘可以指向各个方向,因此Canny算法使用四个算子来检测图像中的水平、垂直和对角边缘。边缘检测的算子(如Roberts,Prewitt,Sobel等)返回水平Gx和垂直Gy方向的一阶导数值,由此便可以确定像素点的梯度G和方向theta 。
其中G为梯度强度, theta表示梯度方向,arctan为反正切函数。具体参见《OpenCV+python:图像梯度》一文。
源代码:
第三步:计算梯度值和方向 3 //同样可以用不同的检测器加上把图像放到0-255之间// 4 / P[i,j]=(S[i+1,j]-S[i,j]+S[i+1,j+1]-S[i,j+1])/2 / 5 / Q[i,j]=(S[i,j]-S[i,j+1]+S[i+1,j]-S[i+1,j+1])/2 / 6 / 7 double *P=new double[nWidth*nHeight];8 double *Q=new double[nWidth*nHeight];9 int *M=new int[nWidth*nHeight];
10 //IplImage * M;//梯度结果
11 //M=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);
12 double *Theta=new double[nWidth*nHeight];
13 int nwidthstep=pCanny->widthStep;
14 for(int iw=0;iw<nWidth-1;iw++)
15 {
16 for (int jh=0;jh<nHeight-1;jh++)
17 {
18 P[jh*nWidth+iw]=(double)(pCanny->imageData[min(iw+1,nWidth-1)+jh*nwidthstep]-pCanny->imageData[iw+jh*nwidthstep]+
19 pCanny->imageData[min(iw+1,nWidth-1)+min(jh+1,nHeight-1)*nwidthstep]-pCanny->imageData[iw+min(jh+1,nHeight-1)*nwidthstep])/2;
20 Q[jh*nWidth+iw]=(double)(pCanny->imageData[iw+jh*nwidthstep]-pCanny->imageData[iw+min(jh+1,nHeight-1)*nwidthstep]+
21 pCanny->imageData[min(iw+1,nWidth-1)+jh*nwidthstep]-pCanny->imageData[min(iw+1,nWidth-1)+min(jh+1,nHeight-1)*nwidthstep])/2;
22 }
23 }
24 //计算幅值和方向
25 for(int iw=0;iw<nWidth-1;iw++)
26 {
27 for (int jh=0;jh<nHeight-1;jh++)
28 {
29 M[jh*nWidth+iw]=(int)sqrt(P[jh*nWidth+iw]*P[jh*nWidth+iw]+Q[jh*nWidth+iw]*Q[jh*nWidth+iw]+0.5);
30 Theta[jh*nWidth+iw]=atan2(Q[jh*nWidth+iw],P[jh*nWidth+iw])*180/3.1415;
31 if (Theta[jh*nWidth+iw]<0)
32 {
33 Theta[jh*nWidth+iw]=360+Theta[jh*nWidth+iw];
34 }
35 }
36 }
(4) 非极大值抑制:
非极大值抑制是一种边缘稀疏技术,通俗意义上是指寻找像素点局部最大值,非极大值抑制的作用在于“瘦”边。对图像进行梯度计算后,仅仅基于梯度值提取的边缘仍然很模糊。对于标准3,对边缘有且应当只有一个准确的响应。而非极大值抑制则可以帮助将局部最大值之外的所有梯度值抑制为0,对梯度图像中每个像素进行非极大值抑制的算法是:
- 将当前像素的梯度强度与沿正负梯度方向上的两个像素进行比较。
- 如果当前像素的梯度强度与另外两个像素相比最大,则该像素点保留为边缘点,否则该像素点将被抑制。
通常为了更加精确的计算,在跨越梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度,现举例如下:
上图中左右图:g1、g2、g3、g4都代表像素点,很明显它们是c的八领域中的4个,左图中c点是我们需要判断的点,蓝色的直线是它的梯度方向,也就是说c要是局部极大值,它的梯度幅值M需要大于直线与g1g2和g2g3的交点,dtmp1和dtmp2处的梯度幅值。
但是dtmp1和dtmp2不是整像素,而是亚像素,也就是坐标是浮点的,那怎么求它们的梯度幅值呢?线性插值,例如dtmp1在g1、g2之间,g1、g2的幅值都知道,我们只要知道dtmp1在g1、g2之间的比例,就能得到它的梯度幅值,而比例是可以靠夹角计算出来的,夹角又是梯度的方向。
写个线性插值的公式:设g1的幅值M(g1),g2的幅值M(g2),则dtmp1可以很得到:
M(dtmp1)=w*M(g2)+(1-w)*M(g1)
其中w=distance(dtmp1,g2)/distance(g1,g2)
distance(g1,g2) 表示两点之间的距离。实际上w是一个比例系数,这个比例系数可以通过梯度方向(幅角的正切和余切)得到。
右边图中的4个直线就是4个不同的情况,情况不同,g1、g2、g3、g4代表c的八领域中的4个坐标会有所差异,但是线性插值的原理都是一致的。
需要注意的是,如何标志方向并不重要,重要的是梯度方向的计算要和梯度算子的选取保持一致。
源代码:
第四步:非极大值抑制2 //注意事项 权重的选取,也就是离得近权重大3 / 4 IplImage * N;//非极大值抑制结果5 N=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);6 IplImage * OpencvCannyimg;//非极大值抑制结果7 OpencvCannyimg=cvCreateImage(cvGetSize(ColorImage),ColorImage->depth,1);8 int g1=0, g2=0, g3=0, g4=0; //用于进行插值,得到亚像素点坐标值 9 double dTmp1=0.0, dTmp2=0.0; //保存两个亚像素点插值得到的灰度数据
10 double dWeight=0.0; //插值的权重
11
12 for(int i=1;i<nWidth-1;i++)
13 {
14 for(int j=1;j<nHeight-1;j++)
15 {
16 //如果当前点梯度为0,该点就不是边缘点
17 if (M[i+j*nWidth]==0)
18 {
19 N->imageData[i+j*nwidthstep]=0;
20 }else
21 {
22 首先判断属于那种情况,然后根据情况插值///
23 第一种情况///
24 / g1 g2 /
25 / C /
26 / g3 g4 /
27 /
28 if((Theta[i+j*nWidth]>=90&&Theta[i+j*nWidth]<135)||(Theta[i+j*nWidth]>=270&&Theta[i+j*nWidth]<315))
29 {
30 g1=M[i-1+(j-1)*nWidth];
31 g2=M[i+(j-1)*nWidth];
32 g3=M[i+(j+1)*nWidth];
33 g4=M[i+1+(j+1)*nWidth];
34 dWeight=fabs(P[i+j*nWidth])/fabs(Q[i+j*nWidth]);
35 dTmp1=g1*dWeight+(1-dWeight)*g2;
36 dTmp2=g4*dWeight+(1-dWeight)*g3;
37 第二种情况///
38 / g1 /
39 / g2 C g3 /
40 / g4 /
41 /
42 }else if((Theta[i+j*nWidth]>=135&&Theta[i+j*nWidth]<180)||(Theta[i+j*nWidth]>=315&&Theta[i+j*nWidth]<360))
43 {
44 g1=M[i-1+(j-1)*nWidth];
45 g2=M[i-1+(j)*nWidth];
46 g3=M[i+1+(j)*nWidth];
47 g4=M[i+1+(j+1)*nWidth];
48 dWeight=fabs(Q[i+j*nWidth])/fabs(P[i+j*nWidth]);
49 dTmp1=g1*dWeight+(1-dWeight)*g2;
50 dTmp2=g4*dWeight+(1-dWeight)*g3;
51 第三种情况///
52 / g1 g2 /
53 / C /
54 / g4 g3 /
55 /
56 }else if((Theta[i+j*nWidth]>=45&&Theta[i+j*nWidth]<90)||(Theta[i+j*nWidth]>=225&&Theta[i+j*nWidth]<270))
57 {
58 g1=M[i+1+(j-1)*nWidth];
59 g2=M[i+(j-1)*nWidth];
60 g3=M[i+(j+1)*nWidth];
61 g4=M[i-1+(j+1)*nWidth];
62 dWeight=fabs(P[i+j*nWidth])/fabs(Q[i+j*nWidth]);
63 dTmp1=g1*dWeight+(1-dWeight)*g2;
64 dTmp2=g4*dWeight+(1-dWeight)*g3;
65 第四种情况///
66 / g1 /
67 / g4 C g2 /
68 / g3 /
69 /
70 }else if((Theta[i+j*nWidth]>=0&&Theta[i+j*nWidth]<45)||(Theta[i+j*nWidth]>=180&&Theta[i+j*nWidth]<225))
71 {
72 g1=M[i+1+(j-1)*nWidth];
73 g2=M[i+1+(j)*nWidth];
74 g3=M[i-1+(j)*nWidth];
75 g4=M[i-1+(j+1)*nWidth];
76 dWeight=fabs(Q[i+j*nWidth])/fabs(P[i+j*nWidth]);
77 dTmp1=g1*dWeight+(1-dWeight)*g2;
78 dTmp2=g4*dWeight+(1-dWeight)*g3;
79
80 }
81
82 }
83
84 if ((M[i+j*nWidth]>=dTmp1)&&(M[i+j*nWidth]>=dTmp2))
85 {
86 N->imageData[i+j*nwidthstep]=200;
87
88 }else N->imageData[i+j*nwidthstep]=0;
89
90 }
91 }
92
93
94 //cvNamedWindow("Limteimg",0);
95 //cvShowImage("Limteimg",N); //显示非抑制
96 //cvWaitKey(0);
97 //cvDestroyWindow("Limteimg");
(5) 双阈值检测
在施加非极大值抑制之后,剩余的像素可以更准确地表示图像中的实际边缘。然而,仍然存在由于噪声和颜色变化引起的一些边缘像素。为了解决这些杂散响应,必须用弱梯度值过滤边缘像素,并保留具有高梯度值的边缘像素,可以通过选择高低阈值来实现。如果边缘像素的梯度值高于高阈值,则将其标记为强边缘像素;如果边缘像素的梯度值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;如果边缘像素的梯度值小于低阈值,则会被抑制。阈值的选择取决于给定输入图像的内容。
双阈值检测的伪代码描写如下:
源代码:
///第五步:双阈值的选取2 //注意事项 注意是梯度幅值的阈值 3 / 4 int nHist[1024];//直方图5 int nEdgeNum;//所有边缘点的数目6 int nMaxMag=0;//最大梯度的幅值7 for(int k=0;k<1024;k++)8 {9 nHist[k]=0;
10 }
11 for (int wx=0;wx<nWidth;wx++)
12 {
13 for (int hy=0;hy<nHeight;hy++)
14 {
15 if((uchar)N->imageData[wx+hy*N->widthStep]==200)
16 {
17 int Mindex=M[wx+hy*nWidth];
18 nHist[M[wx+hy*nWidth]]++;//获取了梯度直方图
19
20 }
21 }
22 }
23 nEdgeNum=0;
24 for (int index=1;index<1024;index++)
25 {
26 if (nHist[index]!=0)
27 {
28 nMaxMag=index;
29 }
30 nEdgeNum+=nHist[index];//经过non-maximum suppression后有多少边缘点像素
31 }
32 //计算两个阈值 注意是梯度的阈值
33 int nThrHigh;
34 int nThrLow;
35 double dRateHigh=0.7;
36 double dRateLow=0.5;
37 int nHightcount=(int)(dRateHigh*nEdgeNum+0.5);
38 int count=1;
39 nEdgeNum=nHist[1];
40 while((nEdgeNum<=nHightcount)&&(count<nMaxMag-1))
41 {
42 count++;
43 nEdgeNum+=nHist[count];
44 }
45 nThrHigh=count;
46 nThrLow= (int)(nThrHigh*dRateLow+0.5);
47 printf("\n直方图的长度 %d \n ",nMaxMag);
48 printf("\n梯度的阈值幅值大 %d 小阈值 %d \n ",nThrHigh,nThrLow);
(6)抑制孤立低阈值点
到目前为止,被划分为强边缘的像素点已经被确定为边缘,因为它们是从图像中的真实边缘中提取出来的。然而,对于弱边缘像素,将会有一些争论,因为这些像素可以从真实边缘提取也可以是因噪声或颜色变化引起的。为了获得准确的结果,应该抑制由后者引起的弱边缘。通常,由真实边缘引起的弱边缘像素将连接到强边缘像素,而噪声响应未连接。为了跟踪边缘连接,通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘点就可以保留为真实的边缘。
抑制孤立边缘点的伪代码描述如下:
源代码:
第六步:边缘检测3 /4 5 for(int is=1;is<nWidth-1;is++)6 {7 for (int jt=1;jt<nHeight-1;jt++)8 {9 //CvScalar s=cvGet2D(N,jt,is);
10 //int currentvalue=s.val[0];
11 int currentvalue=(uchar)(N->imageData[is+jt*N->widthStep]);
12 if ((currentvalue==200)&&(M[is+jt*nWidth]>=nThrHigh))
13 //是非最大抑制后的点且 梯度幅值要大于高阈值
14 {
15 N->imageData[is+jt*nwidthstep]=255;
16 //邻域点判断
17 TraceEdge(is, jt, nThrLow, N, M);
18 }
19 }
20 }
21 for (int si=1;si<nWidth-1;si++)
22 {
23 for (int tj=1;tj<nHeight-1;tj++)
24 {
25 if ((uchar)N->imageData[si+tj*nwidthstep]!=255)
26 {
27 N->imageData[si+tj*nwidthstep]=0;
28 }
29 }
30
31 }
32
33 cvNamedWindow("Cannyimg",0);
34 cvShowImage("Cannyimg",N);
其中,邻域跟踪算法给出了两个,一种是递归,一种是非递归。 递归算法。解决了堆栈溢出问题。
int TraceEdge(int w, int h, int nThrLow, IplImage* pResult, int *pMag)2 {3 int n,m;4 char flag = 0;5 int currentvalue=(uchar)pResult->imageData[w+h*pResult->widthStep];6 if ( currentvalue== 0)7 {8 pResult->imageData[w+h*pResult->widthStep]= 255;9 flag=0;
10 for (n= -1; n<=1; n++)
11 {
12 for(m= -1; m<=1; m++)
13 {
14 if (n==0 && m==0) continue;
15 int curgrayvalue=(uchar)pResult->imageData[w+n+(h+m)*pResult->widthStep];
16 int curgrdvalue=pMag[w+n+(h+m)*pResult->width];
17 if (curgrayvalue==200&&curgrdvalue>nThrLow)
18 if (TraceEdge(w+n, h+m, nThrLow, pResult, pMag))
19 {
20 flag=1;
21 break;
22 }
23 }
24 if (flag) break;
25 }
26 return(1);
27 }
28 return(0);
29 }
非递归算法。如下:
void TraceEdge(int w, int h, int nThrLow, IplImage* pResult, int *pMag) 2 { 3 //对8邻域像素进行查询 4 int xNum[8] = {1,1,0,-1,-1,-1,0,1}; 5 int yNum[8] = {0,1,1,1,0,-1,-1,-1};6 int xx=0;7 int yy=0;8 bool change=true;9 while(change)
10 {
11 change=false;
12 for(int k=0;k<8;k++)
13 {
14 xx=w+xNum[k];
15 yy=h+yNum[k];
16 // 如果该象素为可能的边界点,又没有处理过
17 // 并且梯度大于阈值
18 int curgrayvalue=(uchar)pResult->imageData[xx+yy*pResult->widthStep];
19 int curgrdvalue=pMag[xx+yy*pResult->width];
20 if(curgrayvalue==200&&curgrdvalue>nThrLow)
21 {
22 change=true;
23 // 把该点设置成为边界点
24 pResult->imageData[xx+yy*pResult->widthStep]=255;
25 h=yy;
26 w=xx;
27 break;
28 }
29 }
30 }
31 }
到此,整个算法写完了,利用OpenCV的相关API进行处理的源代码如下:
import cv2 as cv
import numpy as npdef edge_demo(image):blurred = cv.GaussianBlur(image, (3, 3), 0)#高斯模糊gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY) #灰度转换# X Gradientxgrad = cv.Sobel(gray, cv.CV_16SC1, 1, 0)#计算梯度,(API要求不能为浮点数)# Y Gradientygrad = cv.Sobel(gray, cv.CV_16SC1, 0, 1) #edgeedge_output = cv.Canny(xgrad, ygrad, 50, 150) #调用cv.Canny,利用高低阈值求出图像边缘#edge_output = cv.Canny(gray, 50, 150) cv.imshow("Canny Edge", edge_output)dst = cv.bitwise_and(image, image, mask=edge_output)#得到彩色图像的边缘检测图像cv.imshow("Color Edge", dst)src = cv.imread("F:/images/lena.png")
cv.namedWindow("input image", cv.WINDOW_AUTOSIZE)
cv.imshow("input image", src)
edge_demo(src)
cv.waitKey(0)cv.destroyAllWindows()
运行结果:
OpenCV+python:Canny边缘检测算法相关推荐
- Python,Opencv cv2.Canny()边缘检测
Python,Opencv的Canny边缘检测 1. 效果图 2. 源码 参考 这篇博客将介绍Canny边缘检测的概念,并利用cv2.Canny()实现边缘检测: Canny边缘检测是一种流行的边缘检 ...
- 【图像处理】——Python图像分割边缘检测算法之一阶梯度算子(Roberts、Prewitt、Sobel、 Kirsch、Canny算子)
目录 前言 一.边缘检测算法 1.一阶算子 2.二阶算子 二.一阶算子 原图像lena 1.Roberts算子 不同方向的算子模板 梯度的计算 系统代码: 自定义函数代码 结果 2.Prewitt 不 ...
- Python+OpenCV:Canny边缘检测
Python+OpenCV:Canny边缘检测 理论 Canny边缘检测是目前比较流行的边缘检测算法,它由John F. Canny发明. 1. 这是一个多阶段的算法. 2. 降噪:由于边缘检测容易受 ...
- opencv 图像边缘检测 Canny边缘检测算法使用
图解边缘检测 opencv 应用Canny算法进行边缘检测 import cv2 as cv import numpy as npimg = cv.imread('baby_g.jpg', 0) # ...
- Canny边缘检测算法(python 实现)
文章目录 最优边缘准则 算法实现步骤 1. 应用高斯滤波来平滑(模糊)图像,目的是去除噪声 2. 计算梯度强度和方向 3. 应用非最大抑制技术NMS来消除边误检 4. 应用双阈值的方法来决定可能的(潜 ...
- opencv图片矩形网格边线_图像算法在数值计算中的应用(1):Canny边缘检测算法...
引言 有限差分方法(FDM)是计算机数值模拟最早采用的方法,至今仍在广泛应用.该方法将求解域划分为差分网格,用有限个网格节点代替连续的求解域.在直角坐标系下,求解域差分网格通常为均匀的矩形,在表达非矩 ...
- python canny算法_Python 实现 Canny 边缘检测算法
Canny 边缘检测算法由计算机科学家 John F. Canny 于 1986 年提出的.其不仅提供了算法,还带来了一套边缘检测的理论,分阶段的解释如何实现边缘检测.Canny 检测算法包含下面几个 ...
- OpenCV:用C++绘制彩色铅笔画(Canny边缘检测算法)
之前写的博客感觉太严肃了,学习应该是一件很开心,很有成就感的事情,所以我觉得我们可以利用所学知识,做一点有趣的事情. 在这里,我们需要用到的知识是Canny边缘检测算法.也就是通过边缘提取,外加一点小 ...
- python canny检测_【数字图像分析】基于Python实现 Canny Edge Detection(Canny 边缘检测算法)...
Canny 边缘检测算法 Steps: 高斯滤波平滑 计算梯度大小和方向 非极大值抑制 双阈值检测和连接 代码结构: Canny Edge Detection |Gaussian_Smoothing ...
最新文章
- c/c++的内存四区
- 铜川市计算机学校排名,铜川市计算机专业学校
- poj 3038 Children of the Candy Corn bfs dfs
- 百度定位SDK实现获取当前经纬度及位置
- org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for
- 那年大一在图书馆作死的大学高数笔记 | 函数和极限
- OO第三次博客总结作业
- 重庆大学校友会计算机,资环学院联谊会隆重举行
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_1_序列化和反序列化的概述...
- 电脑下载的准考证去哪里了
- 微信应用(小程序)开发最全整理
- 2018年的第一篇文章(福利篇)
- 金蝶开发中根据实体查找bostype,再根据bostype规则,生成对应的id
- Fence Repair-栅栏维修(优先队列)
- .NET 中关于ImageList 图片锯齿问题
- 智能出价策略如何影响广告效果?
- matlab导出图片背景_matlab美化图片之添加背景颜色
- macbook移动文件
- 百度有钱联盟邀请码有效期48小时
- 亚伟计算机速录测试软件,亚伟中文速录机