分類: C/C++
void cvCanny( const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 ); image單通道輸入圖像.edges單通道存儲邊緣的輸出圖像threshold1第一個閾值threshold2第二個閾值aperture_sizeSobel 算子內核大小 (見 cvSobel).
函數 cvCanny 采用 CANNY 算法發現輸入圖像的邊緣而且在輸出圖像中標識這些邊緣。threshold1和threshold2 當中的小閾值用來控制邊緣連接,大的閾值用來控制強邊緣的初始分割。
- 注意事項:cvCanny只接受單通道圖像作為輸入。
- 外部鏈接:經典的canny自調整閾值算法的一個opencv的實現見在OpenCV中自適應確定canny算法的分割門限
- 參考OpenCV中文官網:http://www.opencv.org.cn/index.php/Cv%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86#Canny
說明:OpenCV中cvCanny函數用到了cvSobel的差分計算。下圖為OpenCV的cvCanny函數效果
點擊(此處)折疊或打開
#include "stdafx.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <cmath>
using namespace std;
using namespace cv;
int main(int argc ,char ** argv)
{
IplImage * pImg=NULL;
IplImage * pCannyImg=NULL;
if (argc ==2&&(pImg=cvLoadImage(argv[1],0))!=0)
{
pCannyImg=cvCreateImage(cvGetSize(pImg),IPL_DEPTH_8U,1);
cvCanny(pImg,pCannyImg,50,150,3);
//創建窗口
cvNamedWindow("src", 1);
cvNamedWindow("canny",1);
//顯示圖像
cvShowImage( "src", pImg );
cvShowImage( "canny", pCannyImg );
cvWaitKey(0); //等待按鍵
//銷毀窗口
cvDestroyWindow( "src" );
cvDestroyWindow( "canny" );
//釋放圖像
cvReleaseImage( &pImg );
cvReleaseImage( &pCannyImg );
return 0;
}
return -1;
}
重要:Canny原理:鏈接及內容:http://blog.csdn.net/likezhaobin/article/details/6892176
opencv2版本:
// canny邊緣檢測.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include "stdafx.h"
#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <cmath>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#pragma comment(lib,"opencv_core2410d.lib")
#pragma comment(lib,"opencv_highgui2410d.lib")
#pragma comment(lib,"opencv_imgproc2410d.lib")
using namespace std;
using namespace cv;
int main(int argc ,char ** argv)
{
IplImage * pImg=NULL;
IplImage * pCannyImg=NULL;
cv::Mat src = cv::imread("swan.jpg");
if (src.empty())
return -1;
cv::Mat bw;
cv::cvtColor(src, bw, CV_BGR2GRAY);
Mat canny_mat(src.size(),CV_8U);
//cvCanny(pImg,pCannyImg,50,150,3);
cv::Canny(bw,canny_mat,50,150,3);
imshow("canny",canny_mat);
cvWaitKey(0); //等待按鍵
return 0;
}
邊緣檢測后:
圖象的邊緣是指圖象局部區域亮度變化顯著的部分,該區域的灰度剖面一般可以看作是一個階躍,既從一個灰度值在很小的緩沖區域內急劇變化到另一個灰度相差較大的灰度值。圖象的邊緣部分集中了圖象的大部分信息,圖象邊緣的確定與提取對於整個圖象場景的識別與理解是非常重要的,同時也是圖象分割所依賴的重要特征,邊緣檢測主要是圖象的灰度變化的度量、檢測和定位,自從1959提出邊緣檢測以來,經過五十多年的發展,已有許多中不同的邊緣檢測方法。根據作者的理解和實踐,本文對邊緣檢測的原理進行了描述,在此基礎上着重對Canny檢測算法的實現進行詳述。
本文所述內容均由編程驗證而來,在實現過程中,有任何錯誤或者不足之處大家共同討論(本文不講述枯燥的理論證明和數學推導,僅僅從算法的實現以及改進上進行原理性和工程化的描述)。
1、邊緣檢測原理及步驟
在之前的博文中,作者從一維函數的躍變檢測開始,循序漸進的對二維圖像邊緣檢測的基本原理進行了通俗化的描述。結論是:實現圖像的邊緣檢測,就是要用離散化梯度逼近函數根據二維灰度矩陣梯度向量來尋找圖像灰度矩陣的灰度躍變位置,然后在圖像中將這些位置的點連起來就構成了所謂的圖像邊緣(圖像邊緣在這里是一個統稱,包括了二維圖像上的邊緣、角點、紋理等基元圖)。
在實際情況中理想的灰度階躍及其線條邊緣圖像是很少見到的,同時大多數的傳感器件具有低頻濾波特性,這樣會使得階躍邊緣變為斜坡性邊緣,看起來其中的強度變化不是瞬間的,而是跨越了一定的距離。這就使得在邊緣檢測中首先要進行的工作是濾波。
1)濾波:邊緣檢測的算法主要是基於圖像強度的一階和二階導數,但導數通常對噪聲很敏感,因此必須采用濾波器來改善與噪聲有關的邊緣檢測器的性能。常見的濾波方法主要有高斯濾波,即采用離散化的高斯函數產生一組歸一化的高斯核(具體見“高斯濾波原理及其編程離散化實現方法”一文),然后基於高斯核函數對圖像灰度矩陣的每一點進行加權求和(具體程序實現見下文)。
2)增強:增強邊緣的基礎是確定圖像各點鄰域強度的變化值。增強算法可以將圖像灰度點鄰域強度值有顯著變化的點凸顯出來。在具體編程實現時,可通過計算梯度幅值來確定。
3)檢測:經過增強的圖像,往往鄰域中有很多點的梯度值比較大,而在特定的應用中,這些點並不是我們要找的邊緣點,所以應該采用某種方法來對這些點進行取舍。實際工程中,常用的方法是通過閾值化方法來檢測。
2、Canny邊緣檢測算法原理
JohnCanny於1986年提出Canny算子,它與Marr(LoG)邊緣檢測方法類似,也屬於是先平滑后求導數的方法。本節對根據上述的邊緣檢測過程對Canny檢測算法的原理進行介紹。
2.1 對原始圖像進行灰度化
Canny算法通常處理的圖像為灰度圖,因此如果攝像機獲取的是彩色圖像,那首先就得進行灰度化。對一幅彩色圖進行灰度化,就是根據圖像各個通道的采樣值進行加權平均。以RGB格式的彩圖為例,通常灰度化采用的方法主要有:
方法1:Gray=(R+G+B)/3;
方法2:Gray=0.299R+0.587G+0.114B;(這種參數考慮到了人眼的生理特點)
注意1:至於其他格式的彩色圖像,可以根據相應的轉換關系轉為RGB然后再進行灰度化;
注意2:在編程時要注意圖像格式中RGB的順序通常為BGR。
2.2 對圖像進行高斯濾波
圖像高斯濾波的實現可以用兩個一維高斯核分別兩次加權實現,也可以通過一個二維高斯核一次卷積實現。
1)高斯核實現
上式為離散化的一維高斯函數,確定參數就可以得到一維核向量。
上式為離散化的二維高斯函數,確定參數就可以得到二維核向量。
注意1:關於參數Sigma的取值詳見上篇博文。
注意2:在求的高斯核后,要對整個核進行歸一化處理。
2)圖像高斯濾波
對圖像進行高斯濾波,聽起來很玄乎,其實就是根據待濾波的像素點及其鄰域點的灰度值按照一定的參數規則進行加權平均。這樣可以有效濾去理想圖像中疊加的高頻噪聲。
通常濾波和邊緣檢測是矛盾的概念,抑制了噪聲會使得圖像邊緣模糊,這回增加邊緣定位的不確定性;而如果要提高邊緣檢測的靈敏度,同時對噪聲也提高了靈敏度。實際工程經驗表明,高斯函數確定的核可以在抗噪聲干擾和邊緣檢測精確定位之間提供較好的折衷方案。這就是所謂的高斯圖像濾波,具體實現代碼見下文。
2.3 用一階偏導的有限差分來計算梯度的幅值和方向
關於圖像灰度值得梯度可使用一階有限差分來進行近似,這樣就可以得圖像在x和y方向上偏導數的兩個矩陣。常用的梯度算子有如下幾種:
1)Roberts算子
上式為其x和y方向偏導數計算模板,可用數學公式表達其每個點的梯度幅值為:
2)Sobel算子
上式三個矩陣分別為該算子的x向卷積模板、y向卷積模板以及待處理點的鄰域點標記矩陣,據此可用數學公式表達其每個點的梯度幅值為:
3)Prewitt算子
和Sobel算子原理一樣,在此僅給出其卷積模板。
4)Canny算法所采用的方法
在本文實現的Canny算法中所采用的卷積算子比較簡單,表達如下:
其x向、y向的一階偏導數矩陣,梯度幅值以及梯度方向的數學表達式為:
求出這幾個矩陣后,就可以進行下一步的檢測過程。
2.4 對梯度幅值進行非極大值抑制 圖像梯度幅值矩陣中的元素值越大,說明圖像中該點的梯度值越大,但這不不能說明該點就是邊緣(這僅僅是屬於圖像增強的過程)。在Canny算法中,非極大值抑制是進行邊緣檢測的重要步驟,通俗意義上是指尋找像素點局部最大值,將非極大值點所對應的灰度值置為0,這樣可以剔除掉一大部分非邊緣的點(這是本人的理解)。
圖1 非極大值抑制原理
根據圖1 可知,要進行非極大值抑制,就首先要確定像素點C的灰度值在其8值鄰域內是否為最大。圖1中藍色的線條方向為C點的梯度方向,這樣就可以確定其局部的最大值肯定分布在這條線上,也即出了C點外,梯度方向的交點dTmp1和dTmp2這兩個點的值也可能會是局部最大值。因此,判斷C點灰度與這兩個點灰度大小即可判斷C點是否為其鄰域內的局部最大灰度點。如果經過判斷,C點灰度值小於這兩個點中的任一個,那就說明C點不是局部極大值,那么則可以排除C點為邊緣。這就是非極大值抑制的工作原理。
作者認為,在理解的過程中需要注意以下兩點:
1)中非最大抑制是回答這樣一個問題:“當前的梯度值在梯度方向上是一個局部最大值嗎?” 所以,要把當前位置的梯度值與梯度方向上兩側的梯度值進行比較;
2)梯度方向垂直於邊緣方向。
但實際上,我們只能得到C點鄰域的8個點的值,而dTmp1和dTmp2並不在其中,要得到這兩個值就需要對該兩個點兩端的已知灰度進行線性插值,也即根據圖1中的g1和g2對dTmp1進行插值,根據g3和g4對dTmp2進行插值,這要用到其梯度方向,這是上文Canny算法中要求解梯度方向矩陣Thita的原因。
完成非極大值抑制后,會得到一個二值圖像,非邊緣的點灰度值均為0,可能為邊緣的局部灰度極大值點可設置其灰度為128。根據下文的具體測試圖像可以看出,這樣一個檢測結果還是包含了很多由噪聲及其他原因造成的假邊緣。因此還需要進一步的處理。
2.5 用雙閾值算法檢測和連接邊緣
Canny算法中減少假邊緣數量的方法是采用雙閾值法。選擇兩個閾值(關於閾值的選取方法在擴展中進行討論),根據高閾值得到一個邊緣圖像,這樣一個圖像含有很少的假邊緣,但是由於閾值較高,產生的圖像邊緣可能不閉合,未解決這樣一個問題采用了另外一個低閾值。
在高閾值圖像中把邊緣鏈接成輪廓,當到達輪廓的端點時,該算法會在斷點的8鄰域點中尋找滿足低閾值的點,再根據此點收集新的邊緣,直到整個圖像邊緣閉合。