OpenCV2马拉松第17圈——边缘检测(Canny边缘检测)

计算机视觉讨论群162501053

转载请注明:http://blog.csdn.net/abcd1992719g

收入囊中

  • 利用OpenCV Canny函数进行边缘检测
  • 掌握Canny算法基本理论
  • 分享Java的实现

葵花宝典

在此之前,我们先阐述一下canny检测的算法.总共分为4部分.

(1)处理噪声

一般用高斯滤波.OpenCV使用如下核

(2)计算梯度幅值

先用如下Sobel算子计算出水平和竖直梯度

我在OpenCV2马拉松第14圈——边缘检测(Sobel,prewitt,roberts)详细介绍了Sobel算子

然后计算每个点的梯度幅值和方向

方向最后选4个 (0, 45, 90 or 135)

(3)非极大值抑制

(图片来自http://blog.csdn.net/likezhaobin/article/details/6892176)

下面的蓝色字体分析也来自http://blog.csdn.net/likezhaobin/article/details/6892176  非常感谢

图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。

根据上图可知,要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图1中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘,把C的灰度值改为0,如果C是极大值,可以设为128。这就是非极大值抑制的工作原理。

但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图1中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向,这是上文Canny算法中要求解梯度方向矩阵theta的原因。

我相信上面的解释非常明白了,这也是为什么我们要选0,45,90,135四个方向的原因

  1. ////////////////////第一种情况///////////////////////
  2. /////////       g1  g2                  /////////////
  3. /////////           C                   /////////////
  4. /////////           g3  g4              /////////////
  5. /////////////////////////////////////////////////////
  6. ////////////////////第二种情况///////////////////////
  7. /////////       g1                      /////////////
  8. /////////       g2  C   g3              /////////////
  9. /////////               g4              /////////////
  10. /////////////////////////////////////////////////////
  11. ////////////////////第三种情况///////////////////////
  12. /////////           g1  g2              /////////////
  13. /////////           C                   /////////////
  14. /////////       g4  g3                  /////////////
  15. /////////////////////////////////////////////////////
  16. ////////////////////第四种情况///////////////////////
  17. /////////               g1              /////////////
  18. /////////       g4  C   g2              /////////////
  19. /////////       g3                      /////////////
  20. /////////////////////////////////////////////////////

(4)双阀值检测

在上个步骤中,产生了梯度幅值, (upper and lower):

如果一个像素点的梯度值大于upper,则是边界

如果一个像素点的梯度值小于lower,则不是边界

如果介于两者之间,仅当这个点和边界点连通才会被认为是边界点

根据高阈值得到一个边缘图像,这样一个图像含有很少的假边缘,但是由于阈值较高,产生的图像边缘可能不闭合,为解决这样一个问题采用了另外一个低阈值。在高阈值图像中把边缘链接成轮廓,当到达轮廓的端点时,该算法会在断点的8邻域点中寻找满足低阈值的点,再根据此点收集新的边缘,直到整个图像边缘闭合。

Canny推荐upper:lower 的比例为  2:1 或者 3:1.

小提示:因为,所以最高阀值不是255而是360!还有处于[lower,upper]的点检测是否是边界肯定要放在最后一步,也就是[0,lower),(upper,360]的点都处理完再处理,这样才能判连通!

初识API

C++: void Canny(InputArray image,
OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false )
 
  • image – 单通道8比特图像
  • edges – 输出图像,和src有相同的大小类型
  • threshold1 – 低阀值
  • threshold2 – 高阀值
  • apertureSize – Sobel算子的大小
  • L2gradient – 一个标识位,一旦设置就启用更准确的  norm  去计算图像梯度,默认使用 norm 

荷枪实弹

我们就来看看很简单的样例程序吧

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;

/// Global variables
Mat src, src_gray;
Mat dst, detected_edges;

int lowThreshold;
int const max_lowThreshold = 100;
int ratio = 3;
int kernel_size = 3;
const char* window_name = "Edge Map";

static void CannyThreshold(int, void*)
{
    blur( src_gray, detected_edges, Size(3,3) );
    Canny( detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size );
    dst = Scalar::all(0);
    //detected_edges是mask,只有detected_edges被置上(边缘),才会从原始彩色图像copy到dot中,所以展示的是彩色边缘
    src.copyTo( dst, detected_edges);
    imshow( window_name, dst );
}

int main( int, char** argv )
{
  src = imread( argv[1] );
  dst.create( src.size(), src.type() );
  cvtColor( src, src_gray, CV_BGR2GRAY );

  namedWindow( window_name, CV_WINDOW_AUTOSIZE );
  createTrackbar( "Min Threshold:", window_name, &lowThreshold, max_lowThreshold, CannyThreshold );

  CannyThreshold(0, 0);

  waitKey(0);
  return 0;
}

效果图:

举一反三

C的实现http://blog.csdn.net/likezhaobin/article/details/6892629

下面是一个Java实现,以前是在windows上写的,现在在mac里怎么编码有问题了...

public int[][] CannyEdgeDetect(int[][] oldmat)
	{

		int[][] tempI = gaussFilter(oldmat); 

		//////////////////?¨—??…“‘”√≤a?¨μ?o?≤???/////////////////////////
		/////    P[i,j]=(S[i,j+1]-S[i,j]+S[i+1,j+1]-S[i+1,j])/2     /////
		/////    Q[i,j]=(S[i,j]-S[i+1,j]+S[i,j+1]-S[i+1,j+1])/2     /////
		/////////////////////////////////////////////////////////////////
		float[][] P = new float[height][width];                 //x?ú?′μo ?
		float[][] Q = new float[height][width];                 //y?ú?′μo ?
		int[][] M = new int[height][width];                       //????∑?÷μ
		float[][] Theta = new float[height][width];             //????∑Ω?ú
		//o?à?x,y∑Ω?úμ??′μo ?
		for(int i=0; i<(width-1); i++)
		{
			for(int j=0; j<(height-1); j++)
			{
				P[j][i] = (float)(tempI[j][Math.min(i+1, width-1)] - tempI[j][i] + tempI[Math.min(j+1, height-1)][Math.min(i+1, width-1)] - tempI[Math.min(j+1, height-1)][i])/2;
				Q[j][i] = (float)(tempI[j][i] - tempI[Math.min(j+1, height-1)][i] + tempI[j][Math.min(i+1, width-1)] - tempI[Math.min(j+1, height-1)][Math.min(i+1, width-1)])/2; 

			}
		}
		//o?à?????∑?÷μ∫?????μ?∑Ω?ú
		for(int i=0; i<width; i++)
		{
			for(int j=0; j<height; j++)
			{
				M[j][i] = (int)(Math.sqrt(P[j][i]*P[j][i] + Q[j][i]*Q[j][i])+0.5);
				//System.out.println(M[j][i]);
				Theta[j][i] = (float) (Math.atan2(Q[j][i], P[j][i]) * 57.3);
				if(Theta[j][i] < 0)
					Theta[j][i] += 360;              //Ω′’?∏?Ω?????aaμΩ0~360∑???
			}
		}  

		int[][] N = new int[height][width];  			//∑?o′¥?÷μ“÷÷?Ω·π?
		int g1=0, g2=0, g3=0, g4=0;                     //”√”?Ωˉ––≤?÷죨μ√μΩ—??òà?μ??ˉ±í÷μ
		double dTmp1=0.0, dTmp2=0.0;                    //±£¥ê?Ω∏?—??òà?μ?≤?÷μμ√μΩμ?a“?? ???
		double dWeight=0.0;                             //≤?÷μμ??®÷?  

		//±?Ωá≥? oa?
		for(int i=0; i<width; i++)
		{
		        N[0][i] = 0;
		        N[height-1][i] = 0;
		}
		for(int j=0; j<height; j++)
		{
		        N[j][0] = 0;
		        N[j][width-1] = 0;
		}  

		//Ωˉ––?÷≤??ó¥?÷μ—∞’“
		for(int i=1; i<(width-1); i++)
		{
		    for(int j=1; j<(height-1); j++)
		    {
		        if(M[j][i] == 0)
		            N[j][i] = 0;         //?áπ?μ±?∞????∑?÷μ??0£¨‘ú≤a ??÷≤??ó¥??‘∏√μ?∏≥??0
		        else
		        {
		        //////// ???≈–?? ù”???÷÷?è??£¨?a∫?∏????è??≤?÷μ///////
		        ////////////////////μ?“a÷÷?è??///////////////////////
		        /////////       g1  g2                  /////////////
		        /////////           C                   /////////////
		        /////////           g3  g4              /////////////
		        /////////////////////////////////////////////////////
		        if( ((Theta[j][i]>=90)&&(Theta[j][i]<135)) ||
		                ((Theta[j][i]>=270)&&(Theta[j][i]<315)))
		            {
		                //////∏???–±? ∫?à?∏?÷–o‰÷μΩˉ––≤?÷μ??Ω?
		                g1 = M[j-1][i-1];
		                g2 = M[j-1][i];
		                g3 = M[j+1][i];
		                g4 = M[j+1][i+1];
		                dWeight = Math.abs(P[j][i])/Math.abs(Q[j][i]);   //∑¥’??–
		                dTmp1 = g1*dWeight+g2*(1-dWeight);
		                dTmp2 = g4*dWeight+g3*(1-dWeight);
		            }
		        ////////////////////μ???÷÷?è??///////////////////////
		        /////////       g1                      /////////////
		        /////////       g2  C   g3              /////////////
		        /////////               g4              /////////////
		        /////////////////////////////////////////////////////
		            else if( ((Theta[j][i]>=135)&&(Theta[j][i]<180)) ||
		                ((Theta[j][i]>=315)&&(Theta[j][i]<360)))
		            {
		                g1 = M[j-1][i-1];
		                g2 = M[j][i-1];
		                g3 = M[j][i+1];
		                g4 = M[j+1][i+1];
		                dWeight = Math.abs(Q[j][i])/Math.abs(P[j][i]);   //’??–
		                dTmp1 = g2*dWeight+g1*(1-dWeight);
		                dTmp2 = g4*dWeight+g3*(1-dWeight);
		            }
		        ////////////////////μ???÷÷?è??///////////////////////
		        /////////           g1  g2              /////////////
		        /////////           C                   /////////////
		        /////////       g4  g3                  /////////////
		        /////////////////////////////////////////////////////
		            else if( ((Theta[j][i]>=45)&&(Theta[j][i]<90)) ||
		                ((Theta[j][i]>=225)&&(Theta[j][i]<270)))
		            {
		                g1 = M[j-1][i];
		                g2 = M[j-1][i+1];
		                g3 = M[j+1][i];
		                g4 = M[j+1][i-1];
		                dWeight = Math.abs(P[j][i])/Math.abs(Q[j][i]);   //∑¥’??–
		                dTmp1 = g2*dWeight+g1*(1-dWeight);
		                dTmp2 = g3*dWeight+g4*(1-dWeight);
		            }
		            ////////////////////μ?à?÷÷?è??///////////////////////
		            /////////               g1              /////////////
		            /////////       g4  C   g2              /////////////
		            /////////       g3                      /////////////
		            /////////////////////////////////////////////////////
		            else if( ((Theta[j][i]>=0)&&(Theta[j][i]<45)) ||
		                ((Theta[j][i]>=180)&&(Theta[j][i]<225)))
		            {
		                g1 = M[j-1][i+1];
		                g2 = M[j][i+1];
		                g3 = M[j+1][i-1];
		                g4 = M[j][i-1];
		                dWeight = Math.abs(Q[j][i])/Math.abs(P[j][i]);   //’??–
		                dTmp1 = g1*dWeight+g2*(1-dWeight);
		                dTmp2 = g3*dWeight+g4*(1-dWeight);
		            }
		        }
		        //////////Ωˉ––?÷≤??ó¥?÷μ≈–??£¨≤¢–¥??o?≤?Ω·π?////////////////
		        if((M[j][i]>=dTmp1) && (M[j][i]>=dTmp2))
		            N[j][i] = 128;
		        else
		            N[j][i] = 0;  

		        //System.out.println(N[j][i]);
		        }
		}  

		//à′∑?÷μo?≤? μ?÷
		int []nHist = new int[1024];
		int nEdgeNum;             //?…??±?Ωá ?
		int nMaxMag = 0;          //?ó¥????? ?
		int nHighCount;  

		//?≥o?÷±∑Ω?o
		for(int i=0;i<1024;i++)
	        nHist[i] = 0;
		for(int i=0; i<width; i++)
		{
	        for(int j=0; j<height; j++)
	        {
	              if(N[j][i]==128)
	                   nHist[M[j][i]]++;
	        }
		}  

		//aò?°?ó¥?????∑?÷μo∞?±‘?±?‘μμ?∏? ?
		nEdgeNum = nHist[0];
		nMaxMag = 0;                    //aò?°?ó¥?μ?????÷μ
		for(int i=1; i<1024; i++)           //?≥o??≠π?°∞∑??ó¥?÷μ“÷÷?°±∫?”–??…??òà?
		{
		    if(nHist[i] != 0)       //??????0μ?μ? ?≤a?…????±?Ωáμ?μ?
		    {
		        nMaxMag = i;
		    }
		    nEdgeNum += nHist[i];   //?≠π?non-maximum suppression∫?”–??…??òà?
		}  

		//o?à??Ω∏?∑?÷μ
		float  dRatHigh = 0.79f;
		float  dThrHigh;
		float  dThrLow;
		float  dRatLow = 0.5f;
		nHighCount = (int)(dRatHigh * nEdgeNum + 0.5);
		int k = 1;
		nEdgeNum = nHist[1];
		while((k<(nMaxMag-1)) && (nEdgeNum < nHighCount))
		{
		       k++;
		       nEdgeNum += nHist[k];
		}
		dThrHigh = k;                                   //∏??–÷μ
		dThrLow = (int)((dThrHigh) * dRatLow + 0.5);    //μ??–÷μ
		System.out.println("high = " + dThrHigh + "low = " + dThrLow);
		//dThrHigh = 10;
		//dThrLow = 5;

		//Ωˉ––±?‘μo?≤?
		int cx = width;
		int cy = height;
		for(int i=0; i<width; i++)
		{
		    for(int j=0; j<height; j++)
		    {
		        if((N[j][i]==128) && (M[j][i] >= dThrHigh))
		        {
		            N[j][i] = 255;
		            TraceEdge(j, i, (int)(dThrLow+0.5), N, M);
		        }
		    }
		}  

		//Ω′aπ√a”–…?÷√??±?Ωáμ?μ?…?÷√??∑?±?Ωáμ?
		for(int i=0; i<width; i++)
		{
		    for(int j=0; j<height; j++)
		    {
		        if(N[j][i] != 255)
		        {
		            N[j][i]  = 0 ;   // …?÷√??∑?±?Ωáμ?
		        }
		        N[j][i] = N[j][i] + (N[j][i] << 8) + (N[j][i] << 16);
		    }
		}  

		return N;

	}

	/*
	 * cancy ∏®÷˙∫? ?
	 */
	private void TraceEdge(int y, int x, int nThrLow, int[][] pResult, int[][] pMag)
	{
	    //?‘8??”ú?òà?Ωˉ––≤è—?
	    int[] xNum = {1,1,0,-1,-1,-1,0,1};
	    int[] yNum = {0,1,1,1,0,-1,-1,-1};
	    int yy,xx,k;
	    for(k=0;k<8;k++)
	    {
	        yy = y+yNum[k];
	        xx = x+xNum[k];
	        if(pResult[yy][xx]==128 && pMag[yy][xx]>=nThrLow )
	        {
	            //∏√μ?…???±?Ωáμ?
	            pResult[yy][xx] = 255;
	            //“‘∏√μ???÷––?‘?Ωˉ––∏˙??
	            TraceEdge(yy,xx,nThrLow,pResult,pMag);
	        }
	    }
	}  

初识API

OpenCV2马拉松第17圈——边缘检测(Canny边缘检测),布布扣,bubuko.com

时间: 2024-08-01 10:45:35

OpenCV2马拉松第17圈——边缘检测(Canny边缘检测)的相关文章

OpenCV2马拉松第17圈——边缘检測(Canny边缘检測)

计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g 收入囊中 利用OpenCV Canny函数进行边缘检測 掌握Canny算法基本理论 分享Java的实现 葵花宝典 在此之前,我们先阐述一下canny检測的算法.总共分为4部分. (1)处理噪声 一般用高斯滤波.OpenCV使用例如以下核 (2)计算梯度幅值 先用例如以下Sobel算子计算出水平和竖直梯度 我在OpenCV2马拉松第14圈--边缘检測(Sobel,prewitt,ro

OpenCV2马拉松第16圈——边缘检测(形态学梯度)

计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g 收入囊中 利用OpenCV函数进行形态学梯度操作 自定义结构矩阵进行形态学梯度操作 葵花宝典 在此之前,如果你还没接触过灰度图像形态学膨胀与腐蚀,希望你能仔细阅读灰度图像形态学膨胀与腐蚀 本质上,灰度与二值并不差异,二值不过是0与255,膨胀与腐蚀的操作都是一样的 形态学梯度的定义如下: 形态梯度 dst=morph_grad(src,element)=dilate(src,ele

OpenCV2马拉松第14圈——边缘检测(Sobel,prewitt,roberts)

收入囊中 差分在边缘检测的角色 Sobel算子 OpenCV sobel函数 OpenCV Scharr函数 prewitt算子 Roberts算子 葵花宝典 差分在边缘检测到底有什么用呢?先看下面的图片 作为人,我们可以很容易发现图中红圈有边界,边界处肯定是非常明显,变化陡峭的,在数学中,什么可以表示变化的快慢,自然就是导数,微分了. 想像有如下的一维图片. 红圈处变化最陡峭,再看导数图 红圈在最高值,也就是导数可以很好表示边缘,因为变化很剧烈 图像中的Sobel算子 是离散差分算子. 结合了

OpenCV2马拉松第15圈——边缘检测(Laplace算子,LOG算子)

收入囊中 拉普拉斯算子 LOG算子(高斯拉普拉斯算子) OpenCV Laplacian函数 构建自己的拉普拉斯算子 利用拉普拉斯算子进行图像的锐化 葵花宝典 在OpenCV2马拉松第14圈--边缘检测(Sobel,prewitt,roberts)  我们已经认识了3个一阶差分算子 拉普拉斯算子是二阶差分算子,为什么要加入二阶的算子呢?试想一下,如果图像中有噪声,噪声在一阶导数处也会取得极大值从而被当作边缘.然而求解这个极大值也不方便,采用二阶导数后,极大值点就为0了,因此值为0的地方就是边界.

openCV2马拉松第19圈——Harris角点检测(自己实现)

计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/26824529 收入囊中 使用OpenCV的connerHarris实现角点检测 自己实现Harris算法 下面是自己实现的一个效果图 因为阀值设置比较高,所以房屋周围没有找出来 葵花宝典 在此之前,我们讲过边缘的检测,边缘检测的基本原理就是x方向或者y方向梯度变化很大,角点,顾名思义,就是两个方向的梯度变化都很大. 左1,平滑区域,没有边缘和角点

OpenCV2马拉松第22圈——Hough变换直线检测原理与实现

计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/27220445 收入囊中 Hough变换 概率Hough变换 自己实现Hough变换直线检测 葵花宝典 先看一下我实现的效果图 下面,我们进入Hough变换的原理讲解. 看上图,我们知道,经过一点(x0,y0)的直线可以表示成y0 = mox + b0 反过来看方程,b = –x0m + y0 ,于是我们从原来的坐标系转移到了Hough空间,m是横

OpenCV2马拉松第10圈——直方图反向投影(back project)

收入囊中 灰度图像的反向投影 彩色图像的反向投影 利用反向投影做object detect 葵花宝典 什么是反向投影?事实上没有那么高大上! 在上一篇博文学到,图像能够获得自己的灰度直方图. 反向投影差点儿相同是逆过程,由直方图得到我们的投影图. 步骤例如以下: 依据模版图像,得到模版图像的灰度直方图. 对灰度直方图对归一化,归一化后是个概率分布,直方图的积分是1 依据概率分布的直方图,求输入图像的投影图,也就是对每个像素点,我们依据灰度值,能够得到其概率 得到的投影图是介于[0,1]之间的,为

OpenCV2马拉松第12圈——直方图比較

收入囊中 使用4种不同的方法进行直方图比較 葵花宝典 要比較两个直方图, 首先必需要选择一个衡量直方图相似度的对照标准.也就是先说明要在哪个方面做对照. 我们能够想出非常多办法,OpenCV採用了下面4种 公式也都不难,我们自己就能实现. d越小,表示差异越低,两幅图像越接近,越相似 初识API C++: double compareHist(InputArray H1, InputArray H2, int method) C++: double compareHist(const Spars

OpenCV2马拉松第13圈——模版匹配

收入囊中 在http://blog.csdn.net/abcd1992719g/article/details/25505315这里,我们已经学习了如何利用反向投影和meanshift算法来在图像中查找给定模版图片的位置.meanshift针对的是单张图像,在连续图像序列的跟踪中,camshift(Continuously Adaptive Mean-SHIFT)是一种著名的算法.但在这里,我们先不讨论camshift,而是先讨论最简单的模版匹配. 模版匹配算法 opencv normalize