OpenCV C++(八)----邊緣檢測

圖像的邊緣是指灰度值發(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算子

image.png

注意搞疗,為了方便嗓蘑,這里把錨點(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ì)角方向上的差分逻澳。

image.png

反映的是在垂直方向和水平方向上的邊緣。

通常有四種方式來衡量最后多次卷積后輸出的邊緣強(qiáng)度:

image.png

其中取絕對(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算子

image.png

圖像與prewittx卷積后可以反映圖像垂直方向上的邊緣,與prewitty卷積后可以反映圖像水平方向上的邊緣刮便。 而且空猜,這兩個(gè)卷積核均是可分離的,其中

image.png

從分離的結(jié)果可以看出恨旱,prewittx算子實(shí)際上先對(duì)圖像進(jìn)行垂直方向上的非歸一化的均值平滑辈毯,然后進(jìn)行水平方向上的差分;而prewitty算子實(shí)際上先對(duì)圖像進(jìn)行水平方向上的非歸一化的均值平滑搜贤,然后進(jìn)行垂直方向上的差分谆沃。

image.png

反映的是在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算子

image.png
image.png

這兩個(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è)卷積核組成:

image.png

圖像與每個(gè)卷積核進(jìn)行卷積趾痘,然后取絕對(duì)值作為對(duì)應(yīng)方向上的邊緣強(qiáng)度的量化。對(duì)8個(gè)卷積結(jié)果取絕對(duì)值蔓钟,然后在對(duì)應(yīng)值位置取最大值作為最后輸出的邊緣強(qiáng)度永票。

8.5.2、Robinson算子

與Kirsch類似

image.png

因?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核痹束。

image.png

第二步:利用第一步計(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面哥。

image.png

總結(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]

image.png

接下來介紹常用的非極大值抑制的第二種方式,它可以彌補(bǔ)這一點(diǎn)外里,沒有舍棄任何信息怎爵, 而是用插值法擬合梯度方向上的邊緣強(qiáng)度,這樣會(huì)更加準(zhǔn)確地衡量梯度方向上的邊緣強(qiáng)度盅蝗。

image.png

一般將梯度方向離散化為以下四種情況:

· 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)

image.png

如果angle(r鳖链, c)∈(45, 90]∪(-135风科, -90]撒轮,那么兩個(gè)插值分別為:

image.png

其余不贅述
第四步: 雙閾值的滯后閾值處理。
(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ì)算公式定義:

image.png
image.png

圖像矩陣與拉普拉斯核的卷積本質(zhì)上是計(jì)算任意位置的值與其在水平方向和垂直方向上四個(gè)相鄰點(diǎn)平均值之間的差值(只是相差一個(gè)4的倍數(shù))甜孤。

注意:沒有平滑處理,對(duì)噪聲敏感畏腕,誤將噪聲作為邊緣缴川,并且得不到有方向的邊緣。

顯然描馅, 它無法像Sobel和Prewitt算子那樣單獨(dú)得到水平方向把夸、垂直方向或者其他固定方向上的邊緣。拉普拉斯算子的優(yōu)點(diǎn)是它只有一個(gè)卷積核铭污,所以其計(jì)算成本比其他算子要低恋日。

其他形式如下

image.png

拉普拉斯核內(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)算子

image.png
image.png

步驟:
第一步: 構(gòu)建窗口大小為H×W、 標(biāo)準(zhǔn)差為σ的LoG卷積核闷营。

image.png

其中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)算子

image.png

當(dāng) k=0.95時(shí)绿语, 高斯拉普拉斯和高斯差分的值是近似相等的 。

高斯差分邊緣檢測的步驟如下:
第一步: 構(gòu)建窗口大小為H×W 的高斯差分卷積核候址。

image.png

其中H吕粹、W 均為奇數(shù),卷積核錨點(diǎn)的位置在
image.png

第二步: 圖像矩陣與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)位置即為邊緣位置镊靴。

image.png

尋找過零點(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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末党晋,一起剝皮案震驚了整個(gè)濱河市谭胚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌未玻,老刑警劉巖灾而,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扳剿,居然都是意外死亡旁趟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門庇绽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锡搜,“玉大人橙困,你說我怎么就攤上這事「停” “怎么了凡傅?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肠缔。 經(jīng)常有香客問我夏跷,道長,這世上最難降的妖魔是什么明未? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任槽华,我火速辦了婚禮,結(jié)果婚禮上亚隅,老公的妹妹穿的比我還像新娘硼莽。我一直安慰自己,他們只是感情好煮纵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布懂鸵。 她就那樣靜靜地躺著,像睡著了一般行疏。 火紅的嫁衣襯著肌膚如雪匆光。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天酿联,我揣著相機(jī)與錄音终息,去河邊找鬼。 笑死贞让,一個(gè)胖子當(dāng)著我的面吹牛周崭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喳张,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼续镇,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了销部?” 一聲冷哼從身側(cè)響起摸航,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舅桩,沒想到半個(gè)月后酱虎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡擂涛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年读串,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爹土,死狀恐怖甥雕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胀茵,我是刑警寧澤社露,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站琼娘,受9級(jí)特大地震影響峭弟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脱拼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一瞒瘸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熄浓,春花似錦情臭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娃惯,卻和暖如春跷乐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背趾浅。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工愕提, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皿哨。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓浅侨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親证膨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仗颈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容