圖像的邊緣是指灰度值發(fā)生急劇變換的位置疑务。在某種程度上宜鸯,邊緣不隨光照和視角的變化而變化。
邊緣檢測的目的是制作一個(gè)線圖仆邓,在不會(huì)損害圖像內(nèi)容的情況下鲜滩,同時(shí)又大大減少圖像的數(shù)據(jù)量,提供了對(duì)圖像數(shù)據(jù)的合理概述。
邊緣通過檢查每個(gè)像素的領(lǐng)域并對(duì)其灰度變化進(jìn)行量化的节值,相當(dāng)于微積分里連續(xù)函數(shù)中方向?qū)?shù)或者離散數(shù)列的查分徙硅。
8.1、Roberts算子
注意搞疗,為了方便嗓蘑,這里把錨點(diǎn)的位置標(biāo)注在了卷積核上须肆,這是不合適的,應(yīng)該標(biāo)注在卷積核逆時(shí)針翻轉(zhuǎn)180°的結(jié)果上桩皿。這里標(biāo)注的地方只是一個(gè)位置說明豌汇,即Roberts135翻轉(zhuǎn)180°后,錨點(diǎn)的位置在第0行第0列泄隔,Roberts45翻轉(zhuǎn)180°后拒贱,錨點(diǎn)的位置 在第0行第1列。
與Roberts核卷積佛嬉,本質(zhì)上是兩個(gè)對(duì)角方向上的差分逻澳。
反映的是在垂直方向和水平方向上的邊緣。
通常有四種方式來衡量最后多次卷積后輸出的邊緣強(qiáng)度:
其中取絕對(duì)值的最大值的方式暖呕,對(duì)邊緣的走向有些敏感斜做,而其他幾種方式可以獲得性能更一致的全方位響應(yīng)。當(dāng)然湾揽,取平方和的開方的方式效果一般是最好的瓤逼,但是同時(shí)會(huì)更加耗時(shí)。
void roberts(InputArray src, OutputArray dst, int ddepth, int x, int y, int borderType)
{
if (x == 0 && y == 0)
{
return;
}
//roberts135=|1 0| roberts45=|0 1|
// |0 -1| |-1 0|
Mat roberts_1 = ( Mat_<float>(2, 2) << 1, 0, 0, -1 );
Mat roberts_2 = ( Mat_<float>(2, 2) << 0, 1, -1, 0 );
//當(dāng)x不等于0時(shí)库物,src與robert_1卷積,135°卷積核,檢測的是45°的邊緣
if (x != 0 && y == 0)
{
conv2D(src, roberts_1, dst, ddepth, Point(0, 0), borderType);
}
//當(dāng)y不等于0時(shí)霸旗,src與robert_2卷積,45°卷積核艳狐,檢測的是135°的邊緣
if (x == 0 && y != 0)
{
conv2D(src, roberts_2, dst, ddepth, Point(0, 0), borderType);
}
}
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
//圖像矩陣和robert算子卷積
Mat roberts_1;
roberts(img, roberts_1, CV_32FC1, 1, 0);
Mat roberts_2;
roberts(img, roberts_2, CV_32FC1, 0, 1);
//兩個(gè)卷積結(jié)果的灰度級(jí)顯示
Mat abs_roberts_1, abs_roberts_2;
convertScaleAbs(roberts_1, abs_roberts_1, 1, 0);
convertScaleAbs(roberts_2, abs_roberts_2, 1, 0);
//邊緣強(qiáng)度,這里采用平方根的方式
Mat roberts_1_2, roberts_2_2;
pow(roberts_1, 2.0, roberts_1_2);
pow(roberts_2, 2.0, roberts_2_2);
Mat edge;
sqrt(roberts_1_2 + roberts_2_2, edge);
edge.convertTo(edge, CV_8UC1);
Roberts邊緣檢測因?yàn)槭褂昧撕苌俚泥徲蛳袼貋斫七吘墢?qiáng)度定硝, 因此對(duì)圖像中的噪聲具有高度敏感性『聊浚可以先對(duì)圖像進(jìn)行平滑處理蔬啡, 然后再進(jìn)行Roberts邊緣檢測。 下面介紹幾種具有平滑作用 的邊緣提取卷積核镀虐。
8.2箱蟆、Prewitt算子
圖像與prewittx卷積后可以反映圖像垂直方向上的邊緣,與prewitty卷積后可以反映圖像水平方向上的邊緣刮便。 而且空猜,這兩個(gè)卷積核均是可分離的,其中
從分離的結(jié)果可以看出恨旱,prewittx算子實(shí)際上先對(duì)圖像進(jìn)行垂直方向上的非歸一化的均值平滑辈毯,然后進(jìn)行水平方向上的差分;而prewitty算子實(shí)際上先對(duì)圖像進(jìn)行水平方向上的非歸一化的均值平滑搜贤,然后進(jìn)行垂直方向上的差分谆沃。
反映的是在45°和135°方向上的邊緣。遺憾的是仪芒,這兩個(gè)卷積核是不可分離的唁影。
void prewitt(InputArray src, OutputArray dst, int ddepth, int x = 1, int y = 0, int borderType )
{
if (x == 0 && y == 0)
{
return;
}
//prewitt_x=|1 0 -1| 分離 prewitt_x= | 1 | *| 1 0 -1|
// |1 0 -1| | 1 |
// |1 0 -1| | 1 |
//prewitt_y=| 1 1 1| 分離 prewitt_y=| 1 1 1|*| 1 |
// | 0 0 0| | 0 |
// |-1 -1 -1| | -1 |
Mat prewitt_x_y = (Mat_<float>(3,1) << 1, 1, 1);
Mat prewitt_x_x = (Mat_<float>(1,3) << 1, 0, -1);
Mat prewitt_y_y = prewitt_x_y.t();
Mat prewitt_y_x = prewitt_x_x.t();
//x 不為零的話耕陷,執(zhí)行x方向的prewitt卷積
if (x != 0 && y == 0)
{
sepConv2D_Y_X(src, dst, ddepth, prewitt_x_y, prewitt_x_x, Point(-1, -1), borderType);
}
// y 不為零的話,執(zhí)行y方向的prewitt卷積
if (x == 0 && y != 0)
{
sepConv2D_Y_X(src, dst, ddepth, prewitt_y_y, prewitt_y_x, Point(-1, -1), borderType);
}
}
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
//圖像矩陣和prewitt算子卷積
Mat prewitt_1;
prewitt(img, prewitt_1, CV_32FC1, 1, 0);
Mat prewitt_2;
prewitt(img, prewitt_2, CV_32FC1, 0, 1);
//兩個(gè)卷積結(jié)果的灰度級(jí)顯示
Mat abs_prewitt_1, abs_prewitt_2;
convertScaleAbs(prewitt_1, abs_prewitt_1, 1, 0);
convertScaleAbs(prewitt_2, abs_prewitt_2, 1, 0);
//邊緣強(qiáng)度,這里采用平方根的方式
Mat prewitt_1_2, prewitt_2_2;
pow(prewitt_1, 2.0, prewitt_1_2);
pow(prewitt_2, 2.0, prewitt_2_2);
Mat edge;
sqrt(prewitt_1_2 + prewitt_2_2, edge);
edge.convertTo(edge, CV_8UC1);
從Roberts和Prewitt邊緣檢測的效果圖可以清晰地理解差分方向(或稱梯度方向)與得到的邊緣方向是垂直的据沈,如水平差分方向上的卷積反映的是垂直方向上的邊緣哟沫。
在圖像的平滑處理中,高斯平滑的效果往往比均值平滑要好锌介,因此把Prewitt算子的非歸一化的均值卷積核替換成非歸一化的高斯卷積核嗜诀,就可以構(gòu)建3階的Sobel邊緣檢測算子。
8.3掏湾、Sobel算子
sobelx=| 1 |*|1 0 -1| = | 1 0 -1|
| 2 | | 2 0 -2|
| 1 | | 1 0 -1|
sobely=|1 2 1|*| 1 | = | 1 2 1|
| 0 | | 0 0 0|
|-1 | | -1 -2 -1|
Sobel算子是在一個(gè)坐標(biāo)軸方向上進(jìn)行非歸一化的高斯平滑玻侥,在另一個(gè)坐標(biāo)軸方向上 進(jìn)行差分處理贷岸。
n×n的Sobel算子是由平滑算子和差分算子full卷積而得到的,其中n為奇 數(shù)吓歇。對(duì)于窗口大小為n的非歸一化的高斯平滑算子等于n-1階的二項(xiàng)式展開式的系數(shù)雳窟,
窗口大小為n的差分算子是在n-2階的二 項(xiàng)式展開式的系數(shù)兩側(cè)補(bǔ)零尊浪, 然后后向差分得到的。
//階乘
int factorial(int n)
{
int fac = 1;
if (n == 0)
{
return fac;
}
for (int i = 1; i <= n; i++)
{
fac *= i;
}
return fac;
}
//獲取平滑算子
Mat getPascalSmooth(int n)
{
Mat pascalSmooth = Mat::zeros(Size(n, 1), CV_32FC1);
for (int i = 0; i < n; i++)
{
pascalSmooth.at<float>(0, i) = factorial(n - 1) / (factorial(i)*factorial(n - 1 - i));
}
return pascalSmooth;
}
//獲取差分算子
Mat getPascalDiff(int n)
{
Mat pascalDiff = Mat::zeros(Size(n, 1), CV_32FC1);
Mat pascalSmooth_privious = getPascalSmooth(n - 1);
for (int i = 0; i < n; i++)
{
if (i == 0)
{
pascalDiff.at<float>(0, i) = 1;
}
else if (i == n - 1)
{
pascalDiff.at<float>(0, i) = -1;
}
else
{
pascalDiff.at<float>(0, i) = pascalSmooth_privious.at<float>(0, i) - pascalSmooth_privious.at<float>(0, i - 1);
}
}
return pascalDiff;
}
//sobel算子卷積運(yùn)算
Mat sobel(Mat image, int x_flag, int y_flag, int winSize, int borderType)
{
//卷積核窗口大小應(yīng)該為大于3的奇數(shù)
if (winSize < 3 || winSize % 2 == 1)
{
printf("error winSize!");
}
//平滑系數(shù)
Mat pascalSmooth = getPascalSmooth(winSize);
//差分系數(shù)
Mat pascalDiff = getPascalDiff(winSize);
//sobel卷積
Mat image_con_sobel;
if (x_flag != 0)
{
sepConv2D_Y_X(image, image_con_sobel, CV_32FC1, pascalSmooth.t(), pascalDiff, Point(-1, -1), borderType);
}
if(x_flag == 0 && y_flag != 0)
{
sepConv2D_Y_X(image, image_con_sobel, CV_32FC1, pascalSmooth, pascalDiff.t(), Point(-1, -1), borderType);
}
return image_con_sobel;
}
//main.cpp
Mat img = imread("Koala.jpg", IMREAD_GRAYSCALE);
//圖像矩陣和sobel算子卷積
Mat sobel_x;
sobel_x = sobel(img, 1, 0, 5, BORDER_DEFAULT);
Mat sobel_y;
sobel_y = sobel(img, 0, 1, 5, BORDER_DEFAULT);
//兩個(gè)卷積結(jié)果的灰度級(jí)顯示
Mat abs_sobel_x, abs_sobel_y;
convertScaleAbs(sobel_x, abs_sobel_x, 1, 0);
convertScaleAbs(sobel_y, abs_sobel_y, 1, 0);
//邊緣強(qiáng)度,這里采用平方根的方式
Mat sobel_x_1, sobel_y_2;
pow(sobel_x, 2.0, sobel_x_1);
pow(sobel_y, 2.0, sobel_y_2);
Mat edge;
sqrt(sobel_x_1 + sobel_y_2, edge);
edge.convertTo(edge, CV_8UC1);
一個(gè)有趣的應(yīng)用封救, 就是對(duì)邊緣檢測的結(jié)果進(jìn)行反色處理會(huì)呈現(xiàn)出鉛筆素描的效果拇涤。
OpenCV提供的接口:
void Sobel( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, int ksize = 3,
double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
8.4、Scharr算子
這兩個(gè)卷積核均是不可分離的誉结。
scharrx反映垂直方向的邊緣強(qiáng)度鹅士,scharry反映水平方向的邊緣強(qiáng)度。
scharr45反映135°方向的邊緣強(qiáng)度惩坑,scharr135反映45°方向的邊緣強(qiáng)度掉盅。
void scharr(InputArray src, OutputArray dst, int ddepth, int x, int y, int borderType)
{
if (x == 0 && y == 0)
{
printf("error x or y input");
}
Mat scharr_x = (Mat_<float>(3, 3) << 3, 0, -3, 10, 0, -10, 3, 0, -3);
Mat scharr_y = (Mat_<float>(3, 3) << 3, 10, 3, 0, 0, 0, -3, -10, -3);
if (x != 0 && y == 0)
{
conv2D(src, scharr_x, dst, ddepth, Point(-1, -1), borderType);
}
if (x == 0 && y != 0)
{
conv2D(src, scharr_y, dst, ddepth, Point(-1, -1), borderType);
}
}
OpenCV中提供的接口
void Scharr( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
8.5、Kirsch算子和Bobinson算子
8.5.1以舒、Krisch算子
Kirsch算子由以下8個(gè)卷積核組成:
圖像與每個(gè)卷積核進(jìn)行卷積趾痘,然后取絕對(duì)值作為對(duì)應(yīng)方向上的邊緣強(qiáng)度的量化。對(duì)8個(gè)卷積結(jié)果取絕對(duì)值蔓钟,然后在對(duì)應(yīng)值位置取最大值作為最后輸出的邊緣強(qiáng)度永票。
8.5.2、Robinson算子
與Kirsch類似
因?yàn)镵irsch算子和Robinson算子使用了8個(gè)方向上的卷積核滥沫, 所以其檢測到的邊緣比標(biāo)準(zhǔn)的Prewitt算子和Sobel算子檢測到的邊緣會(huì)顯得更加豐富侣集。
8.6、Canny算子
基于卷積運(yùn)算的邊緣檢測算法兰绣, 比如Sobel世分、 Prewitt等, 有如下兩個(gè)缺點(diǎn):
(1) 沒有充分利用邊緣的梯度方向狭魂。
(2) 最后輸出的邊緣二值圖罚攀,只是簡單地利用閾值進(jìn)行處理党觅,顯然如果閾值過大,則會(huì)損失很多邊緣信息斋泄; 如果閾值過小杯瞻,則會(huì)有很多噪聲。
而Canny邊緣檢測基于這兩點(diǎn)做了改進(jìn)炫掐,提出了:
(1) 基于邊緣梯度方向的非極大值抑制魁莉。
(2) 雙閾值的滯后閾值處理。
算法步驟如下:
第一步:圖像矩陣I分別與水平方向上的卷積核sobelx和垂直方向上的卷積核sobely卷積得到dx和dy募胃,然后利用平方和的開方得到邊緣強(qiáng)度旗唁。這一步的過程和Sobel邊緣檢測一樣,這里也可以將卷積核換為Prewitt核痹束。
第二步:利用第一步計(jì)算出的dx和dy检疫,計(jì)算出梯度方向angle=arctan2(dy,dx)祷嘶,即對(duì)每一個(gè)位置(r屎媳, c) ,angle(r论巍, c)=arctan 2(dy(r烛谊, c),dx(r嘉汰, c))代表該 位置的梯度方向丹禀,一般用角度表示,即angle(r鞋怀, c)∈[0双泪,180]∪[-180,0]接箫。
第三步: 對(duì)每一個(gè)位置進(jìn)行非極大值抑制的處理攒读。magnitude(1, 1)分別與其右上方和左下方的值做比較辛友, 這里912>292且912>276薄扁,如果它的值均大于梯度方向上鄰域的值,則可看作極大值废累,令 nonMaxSup(1邓梅,1) =magni tude(1,1)邑滨;如果它的值不全大于梯度方向上鄰域的值日缨,則可看作非極大值,就需要抑制掖看,令nonMaxSup(1匣距,1) =0面哥。
總結(jié)上述非極大值抑制的過程:
如果magnitude(r, c)在沿著梯度方向angle(r毅待,c)上的鄰域內(nèi)是最大的則為極大值尚卫;否則,設(shè)置為0尸红。 對(duì)于非極大值抑制的實(shí)現(xiàn)吱涉,將梯 度方向一般離散化為以下四種情況:
· angle(r,c)∈[0,22.5)∪(-22.5,0]∪(157.5,180]∪(-180,157.5)
· angle(r,c)∈[22.5,67.5)∪[-157.5,-112.5)
· angle(r,c)∈[67.5,112.5]∪[-112.5,-67.5]
· angle(r,c)∈(112.5,157.5]∪[-67.5.-22.5]
接下來介紹常用的非極大值抑制的第二種方式,它可以彌補(bǔ)這一點(diǎn)外里,沒有舍棄任何信息怎爵, 而是用插值法擬合梯度方向上的邊緣強(qiáng)度,這樣會(huì)更加準(zhǔn)確地衡量梯度方向上的邊緣強(qiáng)度盅蝗。
一般將梯度方向離散化為以下四種情況:
· angle(r,c)∈(45,90]∪(-135,-90]
· angle(r,c)∈(90,135]∪(-90,-45]
· angle(r,c)∈[0,45]∪[-180,-135]
· angle(r,c)∈(135,180]∪(-45,0)
如果angle(r鳖链, c)∈(45, 90]∪(-135风科, -90]撒轮,那么兩個(gè)插值分別為:
其余不贅述
第四步: 雙閾值的滯后閾值處理。
(1) 邊緣強(qiáng)度大于高閾值的那些點(diǎn)作為確定邊緣點(diǎn)贼穆。
(2) 邊緣強(qiáng)度比低閾值小的那些點(diǎn)立即被剔除。
(3) 邊緣強(qiáng)度在低閾值和高閾值之間的那些點(diǎn)兰粉, 按照以下原則進(jìn)行處理——只有這 些點(diǎn)能按某一路徑與確定邊緣點(diǎn)相連時(shí)故痊,才可以作為邊緣點(diǎn)被接受。組成這一路徑的所 有點(diǎn)的邊緣強(qiáng)度都比低閾值要大玖姑。對(duì)這一過程可以理解為愕秫,首先選定邊緣強(qiáng)度大于高閾 值的所有確定邊緣點(diǎn),然后在邊緣強(qiáng)度大于低閾值的情況下盡可能延長邊緣焰络。
OpenCV提供的接口:
void Canny( InputArray image, OutputArray edges,
double threshold1, double threshold2,
int apertureSize = 3, bool L2gradient = false );
8.7戴甩、Laplacian算子
二維函數(shù)f (x, y)的Laplacian(拉普拉斯)變換闪彼, 由以下計(jì)算公式定義:
圖像矩陣與拉普拉斯核的卷積本質(zhì)上是計(jì)算任意位置的值與其在水平方向和垂直方向上四個(gè)相鄰點(diǎn)平均值之間的差值(只是相差一個(gè)4的倍數(shù))甜孤。
注意:沒有平滑處理,對(duì)噪聲敏感畏腕,誤將噪聲作為邊緣缴川,并且得不到有方向的邊緣。
顯然描馅, 它無法像Sobel和Prewitt算子那樣單獨(dú)得到水平方向把夸、垂直方向或者其他固定方向上的邊緣。拉普拉斯算子的優(yōu)點(diǎn)是它只有一個(gè)卷積核铭污,所以其計(jì)算成本比其他算子要低恋日。
其他形式如下
拉普拉斯核內(nèi)所有值的和必須等于0膀篮,這樣就使得在恒等灰度值區(qū)域不會(huì)產(chǎn)生錯(cuò)誤的邊緣,而且上述幾種形式的拉普拉斯算子均是不可分離的岂膳。
void Laplacian( InputArray src, OutputArray dst, int ddepth,
int ksize = 1, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
8.8各拷、高斯拉普拉斯(LoG)算子
步驟:
第一步: 構(gòu)建窗口大小為H×W、 標(biāo)準(zhǔn)差為σ的LoG卷積核闷营。
其中H烤黍、 W 均為奇數(shù)且一般H=W, 卷積核錨點(diǎn)的位置在
第二步: 圖像矩陣與LoGH×W 核卷積傻盟, 結(jié)果記為I_Cov_LoG速蕊。
第三步: 邊緣二值化顯示。
//構(gòu)建分離的高斯卷積核,kernelX:水平方向娘赴;kernelY:垂直方向
void getSepLoGKernel(float sigma, int length, Mat &kernelX, Mat &kernelY)
{
//分配內(nèi)存
kernelX.create(Size(length, 1), CV_32FC1);
kernelY.create(Size(1, length), CV_32FC1);
int center = (length - 1) / 2;
double sigma2 = pow(sigma, 2);
//構(gòu)建可分離的高斯拉普拉斯核
for (int c = 0; c < length; c++)
{
float norm2 = pow(c - center, 2.0);
kernelY.at<float>(c, 0) = exp(-norm2 / (2 * sigma));
kernelX.at<float>(0, c) = (norm2 / sigma2 - 1.0)*kernelY.at<float>(c, 0);
}
}
//LoG算子
Mat LoG(InputArray image, float sigma, int win)
{
Mat kernelX, kernelY;
//得到兩個(gè)分離核
getSepLoGKernel(sigma, win, kernelX, kernelY);
//先進(jìn)行水平卷積规哲,再進(jìn)行垂直卷積
Mat convXY;
sepConv2D_Y_X(image, convXY, CV_32FC1, kernelX, kernelY, Point(-1, -1), BORDER_DEFAULT);
//卷積核轉(zhuǎn)置
Mat kernelX_T = kernelX.t();
Mat kernelY_T = kernelY.t();
//先進(jìn)行垂直卷積,再進(jìn)行水平卷積
Mat convYX;
sepConv2D_Y_X(image, convYX, CV_32FC1, kernelX_T, kernelY_T, Point(-1, -1), BORDER_DEFAULT);
//計(jì)算兩個(gè)卷積的結(jié)果和诽表,得到高斯拉普拉斯卷積
Mat LoG;
add(convXY, convYX, LoG);
return LoG;
}
雖然高斯拉普拉斯核可分離唉锌,但是當(dāng)核的尺寸較大時(shí),計(jì)算量仍然很大竿奏,下面通過 高斯差分近似高斯拉普拉斯袄简,從而進(jìn)一步減少計(jì)算量。
8.9泛啸、高斯差分(DoG)算子
當(dāng) k=0.95時(shí)绿语, 高斯拉普拉斯和高斯差分的值是近似相等的 。
高斯差分邊緣檢測的步驟如下:
第一步: 構(gòu)建窗口大小為H×W 的高斯差分卷積核候址。
第二步: 圖像矩陣與DoGH×W 核卷積岗仑, 結(jié)果記為I_Cov_DoG匹耕。
第三步: 與拉普拉斯邊緣檢測相同的二值化或者水墨效果的顯示。
注意:為了減少計(jì)算量荠雕,可以不用創(chuàng)建高斯差分核稳其,而是根據(jù)卷積的加法分配律和結(jié)合律的性質(zhì),圖像矩陣分別與兩個(gè)高斯核卷積舞虱,然后做差即可欢际。
//高斯卷積
Mat gaussConv(Mat I, float sigma, int s)
{
//構(gòu)建水平方向上的非歸一化的高斯核
Mat xkernel = Mat::zeros(1, s, CV_32FC1);
//中心位置
int cs = (s - 1) / 2;
//方差
float sigma2 = pow(sigma, 2);
for (int c = 0; c < s; c++)
{
float norm2 = pow(float(c - cs), 2);
xkernel.at<float>(0, c) = exp(-norm2 / (2 * sigma2));
}
//將xkernel轉(zhuǎn)置,得到垂直方向上的卷積核
Mat ykernel = xkernel.t();
//分離卷積核的卷積運(yùn)算
Mat gauConv;
sepConv2D_Y_X(I, gauConv, CV_32F, xkernel, ykernel, Point(-1, -1), BORDER_DEFAULT);
gauConv.convertTo(gauConv, CV_32F, 1.0 / sigma2);
return gauConv;
}
//DoG 算子
Mat DoG(Mat I, float sigma, int s, float k)
{
//與標(biāo)準(zhǔn)差為sigma的非歸一化的高斯核卷積
Mat Ig = gaussConv(I, sigma, s);
//與標(biāo)準(zhǔn)差為k*sigma的非歸一化的高斯核卷積
Mat Igk = gaussConv(I, k*sigma, s);
//兩個(gè)高斯卷積結(jié)果做差
Mat doG = Igk - Ig;
return doG;
}
8.10矾兜、Marr-Hildreth算子
對(duì)于LoG和DoG邊緣檢測损趋,最后一步只是簡單地進(jìn)行閾值化處理,顯然得到的邊緣很粗略。那么Marr-Hildreth邊緣檢測可以簡單地理解為對(duì)高斯差分和高斯拉普拉斯檢 測到的邊緣的細(xì)化浑槽,就像Canny對(duì)Sobel蒋失、Prewitt檢測到的邊緣的細(xì)化一樣。
算法步驟如下:
第一步: 構(gòu)建窗口大小為H×W 的高斯拉普拉斯或者高斯差分卷積核桐玻。
第二步: 圖像矩陣與LoGH×W 核或者DoGH×W 核卷積篙挽。
第三步: 通過第二步得到的卷積結(jié)果尋找過零點(diǎn)位置,過零點(diǎn)位置即為邊緣位置镊靴。
尋找過零點(diǎn)的方法
方法1:針對(duì)圖像矩陣與高斯差分核(或者高斯拉普拉斯核)的卷積結(jié)果铣卡,對(duì)每一個(gè)位置判斷以該位置為中心的3×3鄰域內(nèi)的上/下方向、左/右方向偏竟、左上/右下方向煮落、右上/ 左下方向的值是否有異號(hào)出現(xiàn)。對(duì)于這四種情況踊谋,只要有一種情況出現(xiàn)異號(hào)蝉仇,該位置(r, c)就是過零點(diǎn)殖蚕,即為邊 緣點(diǎn)轿衔。
方法2:與第一種方式類似,只是首先計(jì)算左上睦疫、右上害驹、左下、右下的4個(gè)2×2 鄰域內(nèi)的均值對(duì)于這四個(gè)鄰域內(nèi)的均值笼痛,只要任意兩個(gè)均值是異號(hào)的裙秋,該位置就是過零點(diǎn),即為邊緣點(diǎn)缨伊。
//第一種方法計(jì)算過零點(diǎn)
void zero_cross_default(InputArray _src, OutputArray _dst)
{
Mat src = _src.getMat();
_dst.create(src.size(), CV_8UC1);
Mat dst = _dst.getMat();
int rows = src.rows;
int cols = src.cols;
//零交叉點(diǎn)
for (int r = 1; r < rows - 2; r++)
{
for (int c = 1; c < cols - 2; c++)
{
//左上、右下
if (src.at<float>(r - 1, c - 1)*src.at<float>(r + 1, c + 1) < 0)
{
dst.at<uchar>(r, c) = 255;
continue;
}
//上进宝、下
if (src.at<float>(r - 1, c)*src.at<float>(r + 1, c) < 0)
{
dst.at<uchar>(r, c) = 255;
continue;
}
//左刻坊、右
if (src.at<float>(r, c - 1)*src.at<float>(r, c + 1) < 0)
{
dst.at<uchar>(r, c) = 255;
continue;
}
//右上、左下
if (src.at<float>(r - 1, c + 1)*src.at<float>(r + 1, c - 1) < 0)
{
dst.at<uchar>(r, c) = 255;
continue;
}
}
}
}
//第二種方法計(jì)算過零點(diǎn):均值
void zero_cross_mean(InputArray _src, OutputArray _dst)
{
Mat src = _src.getMat();
_dst.create(src.size(), CV_8UC1);
Mat dst = _dst.getMat();
int rows = src.rows;
int cols = src.cols;
double minValue;
double maxValue;
Mat temp(1, 4, CV_32FC1);
//零交叉點(diǎn)
for (int r = 1; r < rows - 1; r++)
{
for (int c = 1; c < cols - 1; c++)
{
//左上領(lǐng)域
Mat left_top(src, Rect(c - 1, r - 1, 2, 2));
temp.at<float>(0, 0) = mean(left_top)[0];
//右上領(lǐng)域
Mat right_top(src, Rect(c, r - 1, 2, 2));
temp.at<float>(0, 1) = mean(right_top)[0];
//左下領(lǐng)域
Mat left_bottom(src, Rect(c - 1, r, 2, 2));
temp.at<float>(0, 2) = mean(left_bottom)[0];
//右下領(lǐng)域
Mat right_bottom(src, Rect(c, r, 2, 2));
temp.at<float>(0, 3) = mean(right_bottom)[0];
//找出四個(gè)方向領(lǐng)域的最值
minMaxLoc(temp, &minValue, &maxValue);
if (minValue*minValue < 0)
{
dst.at<uchar>(r, c) = 255;
}
}
}
}
//Marr_Hildreth算子
Mat Marr_Hildreth(InputArray image, int win, float sigma, ZERO_CROSS_TYPE type)
{
//高斯拉普拉斯
Mat loG = LoG(image, sigma, win);
//過零點(diǎn)
Mat zeroCrossImage;
switch (type)
{
case ZERO_CROSS_DEFALUT:
zero_cross_default(loG, zeroCrossImage);
break;
case ZERO_CROSS_MEAN:
zero_cross_mean(loG, zeroCrossImage);
break;
default:
break;
}
return zeroCrossImage;
}