邊緣檢測(cè)及擴(kuò)展學(xué)習(xí)(OpenCV學(xué)習(xí)筆記之三)

在圖像處理中經(jīng)常有一個(gè)需求就是要知道圖像中物體的邊緣,以此來(lái)做物體區(qū)分或作其他處理,有時(shí)還可實(shí)現(xiàn)某些濾鏡效果例如我們可以將圖片轉(zhuǎn)化成簡(jiǎn)筆畫的效果。今天就用OpenCV的方法來(lái)實(shí)現(xiàn)以上提到的效果。OpenCV中可以實(shí)現(xiàn)邊緣檢測(cè)的方法有以下幾種:

一、Canny算子

Canny邊緣檢測(cè)算子是John F.Canny于 1986 年開發(fā)出來(lái)的一個(gè)多級(jí)邊緣檢測(cè)算法。更為重要的是 Canny 創(chuàng)立了邊緣檢測(cè)計(jì)算理論,解釋了這項(xiàng)技術(shù)是如何工作的。Canny邊緣檢測(cè)算法以Canny的名字命名,被很多人推崇為當(dāng)今最優(yōu)的邊緣檢測(cè)的算法。其內(nèi)部已經(jīng)做了降噪處理防止噪聲干擾。

@param image 輸入圖像,即源圖像,填Mat類的對(duì)象即可,且需為單通道8位圖像。
@param edges 輸出的邊緣圖,需要和源圖片有一樣的尺寸和類型。
@param threshold1 第一個(gè)滯后性閾值。
@param threshold2 第二個(gè)滯后性閾值。
@param apertureSize 表示應(yīng)用Sobel算子的孔徑大小,其有默認(rèn)值3。
@param L2gradient 一個(gè)計(jì)算圖像梯度幅值的標(biāo)識(shí),有默認(rèn)值false。

CV_EXPORTS_W void Canny( InputArray image, OutputArray edges,
                         double threshold1, double threshold2,
                         int apertureSize = 3, bool L2gradient = false );

上面就是Canny方法的聲明和參數(shù)說(shuō)明,對(duì)于Canny算子的算法規(guī)則這里不進(jìn)行講解(后續(xù)可能會(huì)加上),這里只做用法的介紹。下面是一段邊緣檢測(cè)代碼:

- (UIImage *)testCannyImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    
    cv::Mat cannyImage;
    // 檢測(cè)圖像邊緣
    cv::Canny(testImage, cannyImage, 90, 270);
    
    // 轉(zhuǎn)化色彩空間,因數(shù)OpenCV中圖片默認(rèn)是BGR而我們常用的是RGB.
    cv::cvtColor(cannyImage, cannyImage, cv::COLOR_BGR2RGB);
    
    return MatToUIImage(cannyImage);
}
屏幕快照.png

上圖的左邊是原圖右邊是獲取到的邊緣圖。從邊緣圖我們可以看出Canny算子處理后的效果像一幅鋼筆畫,線條有強(qiáng)烈的明暗對(duì)比且很硬朗。我們做一下簡(jiǎn)單處理就可以出現(xiàn)很好的藝術(shù)效果,我們先將原圖作一定的模糊處理,再將邊緣圖和原圖合在一起就可以實(shí)現(xiàn)漫畫的藝術(shù)效果,可以將其作為一種濾鏡來(lái)使用(好像在各大短視頻應(yīng)用中已經(jīng)有這種濾鏡了,但實(shí)現(xiàn)方法應(yīng)該不是用的OpenCV因?yàn)镺penCV在iOS設(shè)置上無(wú)法使用GPU加速)代碼下如。

- (UIImage *)testCannyImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    
    cv::Mat cannyImage;
    // 獲取圖像邊緣
    cv::Canny(testImage, cannyImage, 90, 270);
    
    cv::Mat colorImage;
    // 設(shè)置圖片的大小和類型的圖片
    colorImage.create(testImage.rows, testImage.cols, testImage.type());
    // 將圖片內(nèi)容設(shè)為的值都設(shè)置為0(即黑色)
    colorImage = cv::Scalar(0);
    // 將圖像反轉(zhuǎn)(即黑變白、白變黑)
    cv::bitwise_not(cannyImage , cannyImage);
    // 加入高斯模糊
    cv::GaussianBlur(testImage, testImage, cv::Size(7,7), 1);
    // 將邊緣圖做為掩碼把原圖拷貝到colorImage上
    testImage.copyTo(colorImage, cannyImage);
    // 轉(zhuǎn)化色彩空間,因數(shù)OpenCV中圖片默認(rèn)是BGR而我們常用的是RGB.
    cv::cvtColor(colorImage, colorImage, cv::COLOR_BGR2RGB);
    
    return MatToUIImage(colorImage);
}
藝術(shù)效果圖.png

對(duì)于邊緣圖能實(shí)現(xiàn)的藝術(shù)效果不限于此,通過(guò)參數(shù)和步驟的不同可以實(shí)現(xiàn)不同的藝術(shù)效果,這里就先介紹這么多,如果有想法可以試試實(shí)現(xiàn)其他效果。

二、Sobel算子

Sobel算子結(jié)合了高斯平滑和微分求導(dǎo),用來(lái)計(jì)算圖像灰度函數(shù)的近似梯度。該方法是存在方向的,一般要求出X方向和Y方向的兩個(gè)結(jié)果圖來(lái)合成一個(gè)最終的結(jié)果圖。方法及參數(shù)如下:

@param src 為輸入圖像,填Mat類型即可。
@param dst 即目標(biāo)圖像,函數(shù)的輸出參數(shù),需要和源圖片有一樣的尺寸和類型。
@param ddepth 輸出圖像的深度。
@param dx x 方向上的差分階數(shù)。
@param dy y方向上的差分階數(shù)。
@param ksize 有默認(rèn)值3,表示Sobel核的大小;必須取1,3,5或7。
@param scale 計(jì)算導(dǎo)數(shù)值時(shí)可選的縮放因子,默認(rèn)值是1,表示默認(rèn)情況下是沒有應(yīng)用縮放的。我們可以在文檔中查閱getDerivKernels的相關(guān)介紹,來(lái)得到這個(gè)參數(shù)的更多信息。
@param delta 表示在結(jié)果存入目標(biāo)圖(第二個(gè)參數(shù)dst)之前可選的delta值,有默認(rèn)值0。
@param borderType 我們的老朋友了(萬(wàn)年是最后一個(gè)參數(shù)),邊界模式,默認(rèn)值為BORDER_DEFAULT。這個(gè)參數(shù)可以在官方文檔中borderInterpolate處得到更詳細(xì)的信息。
 
CV_EXPORTS_W 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 );

Soble算子出來(lái)的邊緣圖是不明暗變化的,它更像是一個(gè)素描畫有明暗的變化。我們先做一個(gè)邊緣畫,實(shí)現(xiàn)代碼如下。

- (UIImage *)testSobelImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    // 聲明三個(gè)圖像頭信息
    cv::Mat sobelX;
    cv::Mat sobelY;
    cv::Mat sobelImage;
    // 獲取X和Y方向的邊緣圖
    cv::Sobel(testImage, sobelX, CV_8U, 1, 0);
    cv::Sobel(testImage, sobelY, CV_8U, 0, 1);
    // 合并X和Y方向上的邊緣圖
    cv::addWeighted(sobelX, 0.5, sobelY, 0.5, 0, sobelImage);
    // 轉(zhuǎn)化色彩空間,因數(shù)OpenCV中圖片默認(rèn)是BGR而我們常用的是RGB.
    cv::cvtColor(sobelImage, sobelImage, cv::COLOR_BGR2RGB);
    
    return MatToUIImage(sobelImage);
}

sobel.png

我們可以看到Sobel算子處理后的圖與原圖有很大的關(guān)系,原圖是彩色的結(jié)果就是彩色的這與Canny算子不同,Canny算子不管原圖是什么結(jié)果都是灰圖。從結(jié)果圖中我們還可以看到其線條并不是那么明暗分明,有漸變過(guò)渡更像是一幅鉛筆的素描畫。我們將其中的色彩去掉并反轉(zhuǎn)彩色讓其變成一幅素描圖,實(shí)現(xiàn)代碼如下。

- (UIImage *)testSobelImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"test" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    // 將新圖轉(zhuǎn)成灰圖
    cv::cvtColor(testImage, testImage, cv::COLOR_BGR2GRAY);
    // 聲明三個(gè)圖像頭信息
    cv::Mat sobelX;
    cv::Mat sobelY;
    cv::Mat sobelImage;
    // 獲取X和Y方向的邊緣圖
    cv::Sobel(testImage, sobelX, CV_8U, 1, 0);
    cv::Sobel(testImage, sobelY, CV_8U, 0, 1);
    // 合并X和Y方向上的邊緣圖
    cv::addWeighted(sobelX, 0.5, sobelY, 0.5, 0, sobelImage);
    // 將圖像反轉(zhuǎn)(即黑變白、白變黑)
    cv::bitwise_not(sobelImage, sobelImage);
    
    return MatToUIImage(sobelImage);
}
黑白圖.png

上圖就是一個(gè)素描圖,不過(guò)看上去卻有點(diǎn)浮雕效果。

三、Laplacian算子

Laplacian函數(shù)可以計(jì)算出圖像經(jīng)過(guò)拉普拉斯變換后的結(jié)果。Laplacian函數(shù)其實(shí)主要是利用sobel算子的運(yùn)算。它通過(guò)加上sobel算子運(yùn)算出的圖像x方向和y方向上的導(dǎo)數(shù),來(lái)得到我們載入圖像的拉普拉斯變換結(jié)果。其他方法及參數(shù)如下:

@param src 輸入圖像,即源圖像,填Mat類的對(duì)象即可,且需為單通道8位圖像。
@param dst 輸出的邊緣圖,需要和源圖片有一樣的尺寸和通道數(shù)。
@param ddepth 目標(biāo)圖像的深度。
@param ksize 用于計(jì)算二階導(dǎo)數(shù)的濾波器的孔徑尺寸,大小必須為正奇數(shù),且有默認(rèn)值1。
@param scale 計(jì)算拉普拉斯值的時(shí)候可選的比例因子,有默認(rèn)值1。
@param delta 表示在結(jié)果存入目標(biāo)圖(第二個(gè)參數(shù)dst)之前可選的delta值,有默認(rèn)值0。
@param borderType 邊界模式,默認(rèn)值為BORDER_DEFAULT。這個(gè)參數(shù)可以在官方文檔中borderInterpolate()處得到更詳細(xì)的信息。

CV_EXPORTS_W void Laplacian( InputArray src, OutputArray dst, int ddepth,
                             int ksize = 1, double scale = 1, double delta = 0,
                             int borderType = BORDER_DEFAULT );

我們實(shí)現(xiàn)一個(gè)邊緣圖,代碼如下:

- (UIImage *)testLaplacianImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"pp.jpg" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    
    cv::Mat laplacianImage;
    // 獲取邊緣圖
    cv::Laplacian(testImage, laplacianImage, CV_8U);
    // 轉(zhuǎn)化色彩空間,因數(shù)OpenCV中圖片默認(rèn)是BGR而我們常用的是RGB.
    cv::cvtColor(laplacianImage, laplacianImage, cv::COLOR_BGR2RGB);
    
    return MatToUIImage(laplacianImage);
}
laplacian.png

可以看到,經(jīng)過(guò)Laplacian處理的邊緣圖比Sobel算子處理的邊緣圖弱了很多,如果不注意很難發(fā)現(xiàn)圖中的內(nèi)容。我們同樣將其轉(zhuǎn)成一個(gè)素描圖看看效果是什么樣,代碼如下:

- (UIImage *)testLaplacianImage{
    
    // 獲取測(cè)試用的圖片路徑
    NSString * path = [[NSBundle mainBundle] pathForResource:@"pp.jpg" ofType:nil];
    // 讀取圖片
    cv::Mat testImage = cv::imread([path cStringUsingEncoding:NSUTF8StringEncoding]);
    
    cv::Mat laplacianImage;
    // 獲取邊緣圖
    cv::Laplacian(testImage, laplacianImage, CV_8U);
    // 轉(zhuǎn)化色彩空間,因數(shù)OpenCV中圖片默認(rèn)是BGR而我們常用的是RGB.
    cv::cvtColor(laplacianImage, laplacianImage, cv::COLOR_BGR2GRAY);
    // 將圖像反轉(zhuǎn)(即黑變白、白變黑)
    cv::bitwise_not(laplacianImage, laplacianImage);
    
    return MatToUIImage(laplacianImage);
}
素描圖.png

上圖是拉普拉斯處理的素描圖我們可以看到其比sobel算子處理的效果也弱很多。

四、Scharr

scharr一般我們稱它為濾波器,而不是算子。通常其是配合Sobel算子一起使用的。scharr和Sobel一樣也是有方向的,其實(shí)它的參數(shù)變量和Sobel基本上是一樣的,除了沒有ksize核的大小??梢詤⒄誗obel學(xué)習(xí)效果沒有Sobel,這里就不再重復(fù)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 不同圖像灰度不同,邊界處一般會(huì)有明顯的邊緣,利用此特征可以分割圖像。需要說(shuō)明的是:邊緣和物體間的邊界并不等同,邊緣...
    大川無(wú)敵閱讀 14,113評(píng)論 0 29
  • 1、閾值分割 1.1 簡(jiǎn)介 圖像閾值化分割是一種傳統(tǒng)的最常用的圖像分割方法,因其實(shí)現(xiàn)簡(jiǎn)單、計(jì)算量小、性能較穩(wěn)定而成...
    木夜溯閱讀 22,942評(píng)論 9 15
  • 閑話 這里的學(xué)習(xí)筆記和其他人學(xué)習(xí)OpenCV的流程可能不一樣,我是根據(jù)我的一個(gè)項(xiàng)目來(lái)學(xué)習(xí)的。項(xiàng)目已經(jīng)做出來(lái)了但個(gè)人...
    wosicuanqi閱讀 7,055評(píng)論 2 10
  • 這篇文章總結(jié)比較全面:http://blog.csdn.net/timidsmile/article/detail...
    rogerwu1228閱讀 2,016評(píng)論 0 3
  • 邊緣檢測(cè)的一般步驟 (1)濾波 邊緣檢測(cè)的算法主要是基于圖像強(qiáng)度的一階和二階導(dǎo)數(shù),但導(dǎo)數(shù)通常對(duì)噪聲很是敏感,因此必...
    傻傻小蘿卜閱讀 2,380評(píng)論 0 2

友情鏈接更多精彩內(nèi)容