计算机视觉讨论群162501053
转载请注明:http://blog.csdn.net/abcd1992719g

收入囊中

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


葵花宝典
在此之前,我们先阐述一下canny检測的算法.总共分为4部分.
(1)处理噪声
一般用高斯滤波.OpenCV使用例如以下核


(2)计算梯度幅值
先用例如以下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 


荷枪实弹
我们就来看看非常easy的例子程序吧
#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;
}

效果图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjZDE5OTI3MTln/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" width="454" height="528" alt="">



举一反三



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

以下是一个Java实现,曾经是在windows上写的。如今在mac里怎么编码有问题了...

public int[][] CannyEdgeDetect(int[][] oldmat)
{ int[][] tempI = gaussFilter(oldmat); //////////////////Õ¨—˘ø…“‘”√≤ªÕ¨µƒºÏ≤‚∆˜/////////////////////////
///// 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œÚ∆´µº ˝
float[][] Q = new float[height][width]; //yœÚ∆´µº ˝
int[][] M = new int[height][width]; //Û∂»∑˘÷µ
float[][] Theta = new float[height][width]; //Û∂»∑ΩœÚ
//º∆À„x,y∑ΩœÚµƒ∆´µº ˝
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; }
}
//º∆À„Û∂»∑˘÷µ∫ÕÛ∂»µƒ∑ΩœÚ
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; //Ω´’‚∏ˆΩ«∂»◊™ªªµΩ0~360∑∂Œß
}
} int[][] N = new int[height][width]; //∑«º´¥Û÷µ“÷÷∆Ω·π˚
int g1=0, g2=0, g3=0, g4=0; //”√”⁄Ω¯––≤Â÷µ£¨µ√µΩ—«œÒÀÿµ„◊¯±Í÷µ
double dTmp1=0.0, dTmp2=0.0; //±£¥Ê¡Ω∏ˆ—«œÒÀÿµ„≤Â÷µµ√µΩµƒª“∂» ˝æ›
double dWeight=0.0; //≤Â÷µµƒ»®÷ÿ //±flΩÁ≥ı ºªØ
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£¨‘Ú≤ª «æ÷≤ø◊Ó¥Û∂‘∏√µ„∏≥Œ™0
else
{
//////// ◊œ»≈–∂œ Ù”⁄ƒ«÷÷«Èøˆ£¨»ª∫Û∏˘æ›«Èøˆ≤Â÷µ///////
////////////////////µ⁄“ª÷÷«Èøˆ///////////////////////
///////// g1 g2 /////////////
///////// C /////////////
///////// g3 g4 /////////////
/////////////////////////////////////////////////////
if( ((Theta[j][i]>=90)&&(Theta[j][i]<135)) ||
((Theta[j][i]>=270)&&(Theta[j][i]<315)))
{
//////∏˘æ›–±¬ ∫ÕÀƒ∏ˆ÷–º‰÷µΩ¯––≤Â÷µ«ÛΩ‚
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);
}
}
//////////Ω¯––æ÷≤ø◊Ó¥Û÷µ≈–∂œ£¨≤¢–¥»ÎºÏ≤‚Ω·π˚////////////////
if((M[j][i]>=dTmp1) && (M[j][i]>=dTmp2))
N[j][i] = 128;
else
N[j][i] = 0; //System.out.println(N[j][i]);
}
} //À´∑ß÷µºÏ≤‚ µœ÷
int []nHist = new int[1024];
int nEdgeNum; //ø…ƒ‹±flΩÁ ˝
int nMaxMag = 0; //◊Ó¥ÛÛ∂» ˝
int nHighCount; //Õ≥º∆÷±∑ΩÕº
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]]++;
}
} //ªÒ»°◊Ó¥ÛÛ∂»∑˘÷µº∞«±‘⁄±fl‘µµ„∏ˆ ˝
nEdgeNum = nHist[0];
nMaxMag = 0; //ªÒ»°◊Ó¥ÛµƒÃ›∂»÷µ
for(int i=1; i<1024; i++) //Õ≥º∆æ≠π˝°∞∑«◊Ó¥Û÷µ“÷÷∆°±∫Û”–∂‡…ŸœÒÀÿ
{
if(nHist[i] != 0) //Û∂»Œ™0µƒµ„ «≤ªø…ƒ‹Œ™±flΩÁµ„µƒ
{
nMaxMag = i;
}
nEdgeNum += nHist[i]; //æ≠π˝non-maximum suppression∫Û”–∂‡…ŸœÒÀÿ
} //º∆À„¡Ω∏ˆ∑ß÷µ
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; //∏fl„–÷µ
dThrLow = (int)((dThrHigh) * dRatLow + 0.5); //µÕ„–÷µ
System.out.println("high = " + dThrHigh + "low = " + dThrLow);
//dThrHigh = 10;
//dThrLow = 5; //Ω¯––±fl‘µºÏ≤‚
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);
}
}
} //Ω´ªπ√ª”–…Ë÷√Œ™±flΩÁµƒµ„…Ë÷√Œ™∑«±flΩÁµ„
for(int i=0; i<width; i++)
{
for(int j=0; j<height; j++)
{
if(N[j][i] != 255)
{
N[j][i] = 0 ; // …Ë÷√Œ™∑«±flΩÁµ„
}
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 )
{
//∏√µ„…ËŒ™±flΩÁµ„
pResult[yy][xx] = 255;
//“‘∏√µ„Œ™÷––ƒ‘ŸΩ¯––∏˙◊Ÿ
TraceEdge(yy,xx,nThrLow,pResult,pMag);
}
}
}

初识API

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

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

    收入囊中 拉普拉斯算子 LOG算子(高斯拉普拉斯算子) OpenCV Laplacian函数 构建自己的拉普拉斯算子 利用拉普拉斯算子进行图像的锐化 葵花宝典 在OpenCV2马拉松第14圈--边缘检 ...

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

    收入囊中 差分在边缘检測的角色 Sobel算子 OpenCV sobel函数 OpenCV Scharr函数 prewitt算子 Roberts算子 葵花宝典 差分在边缘检測究竟有什么用呢?先看以下的 ...

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

    计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/26824529 收入囊中 使用OpenCV的con ...

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

    计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/27220445 收入囊中 Hough变换 概率Ho ...

  5. OpenCV2马拉松第24圈——轮廓提取

    计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/27979267 收入囊中 在图片中找到轮廓而且描绘 ...

  6. OpenCV2马拉松第5圈——线性滤波

    收入囊中 这里的非常多内容事实上在我的Computer Vision: Algorithms and ApplicationsのImage processing中都有讲过 相关和卷积工作原理 边界处理 ...

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

    收入囊中 在http://blog.csdn.net/abcd1992719g/article/details/25505315这里,我们已经学习了怎样利用反向投影和meanshift算法来在图像中查 ...

  8. OpenCV2马拉松第2圈——读写图片

    收入囊中 用imread读取图片 用nameWindow和imshow展示图片 cvtColor彩色图像灰度化 imwrite写图像 Luv色彩空间转换 初识API 图像读取接口 image = im ...

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

    收入囊中 灰度图像的反向投影 彩色图像的反向投影 利用反向投影做object detect 葵花宝典 什么是反向投影?事实上没有那么高大上! 在上一篇博文学到,图像能够获得自己的灰度直方图. 反向投影 ...

随机推荐

  1. (三)Mybatis总结之动态sql

    动态sql 为何需要动态sql?因为简单的sql语句已经不能满足复杂的业务需求 动态sql相当于sql语句拼接 1.if语句 if语句:判断,如果执行多条件查询,如果中间某个条件变量为空,就跳过当前判 ...

  2. kill 8080 port on windows

    1. 查找PID netstat -ano | findstr :yourPortNumber 2. kill进程 taskkill /PID typeyourPIDhere /F

  3. 60查找nanopim1plus的HDMI为720p输出的问题

    60查找nanopim1plus的HDMI为720p输出的问题 大文实验室/大文哥 壹捌陆捌零陆捌捌陆捌贰 21504965 AT qq.com 完成时间:2017/12/5 17:51 版本:V1. ...

  4. activity生命周期知识点整理

    activity生命周期知识点整理 Activity: 是一个应用组件,用户可与其提供的屏幕进行交互.窗口通常会充满屏幕,但也可以小于屏幕并浮动在其他窗口之上. 一个activity的什么周期: 启动 ...

  5. Python学习日记之读取中文目录

    unicode # -*- coding:utf-8 -*- import os import shutil ins="E:\\学习资料" dir=unicode(ins,'utf ...

  6. Pro ASP.NET Core MVC 第6版翻译 目录页

    Pro ASP.NET Core MVC 第6版 目录 第一部分 第一章 ASP.NET Core MVC 的前世今生 第二章 第一个MVC应用程序(上) 第二章 第一个MVC应用程序(下) 第三章 ...

  7. map 用法

    map 是一种关联容器,  提供一对一的关联, 关联的形式为: KEY----VALUE     关键字不重复.multimap与map类似,但是允许关键字重复 即:关键字和与之对应的值 关键字起到索 ...

  8. 【6.24校内test】T1 江城唱晚

    [题目背景] 墙角那株海棠,是你种下的思念. 生死不能忘,高烛照容颜. 一曲江城唱晚,重忆当年坐灯前, 青衫中绣着你留下的线. ——银临<江城唱晚> [问题描述] 扶苏是个喜欢一边听古风歌 ...

  9. JAVA基础——集合类汇总

    一.集合与数组 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用. ...

  10. Bat 脚本(常用命令)

    Bat 批处理脚本 (常用) Bat 批处理脚本 === Content === 1. Rem 和 :: Rem 为注释命令,能回显. :: 为符号注释,不能回显. %行内注释内容% ===== (不 ...