若該文爲原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105237807
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
目錄
OpenCV開發專欄
OpenCV開發筆記(三十七):紅胖子8分鐘帶你深入瞭解邊緣檢測和Canny算子邊緣檢測(圖文並茂+淺顯易懂+程序源碼)
前言
紅胖子來也!!!
本篇章開始講解一些邊緣檢測方面的內容,更貼近圖像處理和圖像識別。
有些圖像優化,其實是可以分層進行處理的(類似於摳圖),比如一張簽證,上面就分爲背景、表格框架、頭像、印章,整體處理是一種思路,將其識別封層後再進一步處理,那麼更加精準。
筆者對於算法的一點觀念
算法是與具體的個人很有關係的,算法本質都是基本操作(類似軟件API),但是怎麼組合基本操作,怎麼根據場景做各種基本操作外的一些額外優化操作,那就是人的思維方式並且沒有固定的套路(相對來說軟件是依據業務直接可以開發),這部分是需要花費大量的時間去研究算法嘗試效果,研究一個算法可能幾月幾年,達到目標效果的代碼也許就幾千幾百行,也許有幾萬行吧。
Demo
邊緣檢測
概述
圖像邊緣檢測大幅度地減少了數據量,並且剔除了可以認爲不相關的信息,保留了圖像重要的結構屬性。有許多方法用於邊緣檢測,它們的絕大部分可以劃分爲兩類:基於查找一類和基於零穿越的一類。基於查找的方法通過尋找圖像一階導數中的最大和最小值來檢測邊界,通常是將邊界定位在梯度最大的方向。基於零穿越的方法通過尋找圖像二階導數零穿越來尋找邊界,通常是Laplacian過零點或者非線性差分表示的過零點。
邊緣檢測的一般步驟
第一步:濾波(去噪)
根據圖像的特點做濾波,具體的濾波需要結合實際情況,使用濾波算法。
濾波算法前面講解到的有:方框濾波、均值濾波、高斯濾波、中值濾波、雙邊濾波,針對不同狀態下的去噪濾波,如高頻、低頻、一定頻率內等等。
特別注意,芝麻點的斑點這些不算噪聲,此處噪聲通俗的講爲高頻、低頻、一定範圍內的噪聲,是指變化的梯度,就是變化的速度,斑點內部是一樣的,那麼梯度爲0,既不是低頻、高頻,就是正常的圖像,所以濾不掉的,強制加大是可以濾掉,但是直接導致需要保留的部分也一起被濾掉了。
第二步:增強(強化邊緣特性)
強化邊緣特性就是增強邊緣,讓邊緣更加突出,使用增強算法將梯度值(變化度)大的部分凸顯出來,邊緣一般是背景與前景交界的地方,那麼其梯度值(變化度)一般情況下都大(特殊的情況是:背景有些地方融合或者顏色相似的情況)。
由上可知,用梯度值相關算法去實現即可,梯度相關算法前面講解到的有:膨脹、腐蝕、開運算、閉運算、形態學梯度、頂帽(禮帽)、黑帽。
第三步:檢測
增強後的圖像,會發現其實還是有很多點存在的梯度值比較大啊,而在應用中,這些點並不是邊緣點,所以此時會再進行一次操作進行過濾。常用的方法就是閾值化。
閾值化相關的算法,前面講解到的有:閾值化、自適應閾值、經典OTSU算法閾值化、雙閾值化、半閾值化,延伸的算法還有漫水填充。
算子邊緣檢測
算子邊緣檢測就是基於算子的邊緣檢測,是已經封裝好濾波、增強、檢測三個步驟的方法,可以直接使用,也可以自己先進行一些濾波、增強、去噪後再繼續使用邊緣檢測算子,那麼其效果會更好。
Canny邊緣檢測算子
概述
Canny邊緣檢測算子是John F.Canny與1986年開發出來的一個多級邊緣檢測算法。
Canny的目標是找到一個最優的邊緣檢測算法:
- 低錯誤率:標識出儘可能多的實際邊緣,同時儘可能減少噪聲產生的誤報;
- 高定位性:標識出的邊緣要與圖像中的實際邊緣儘可能接近;
- 最小響應:圖像中的邊緣只能標識一次,並且可能存在的圖像噪聲不應表示爲邊緣。
Canny 算法包含許多可以調整的參數,它們將影響到算法的計算的時間與實效。
高斯濾波器的大小:第一步所有的平滑濾波器將會直接影響 Canny 算法的結果。較小的濾波器產生的模糊效果也較少,這樣就可以檢測較小、變化明顯的細線。較大的濾波器產生的模糊效果也較多,將較大的一塊圖像區域塗成一個 特定點的顏色值。這樣帶來的結果就是對於檢測較大、平滑的邊緣更加有用,例如彩虹的邊緣。
雙閾值:使用兩個閾值比使用一個閾值更加靈活,但是它還是有閾值存在的共性問題。設置的閾值過高,可能會漏掉重要信息;閾值過低,將會把枝節信息看得很重要。很難給出一個適用於所有圖像的通用閾值。
原理
Canny邊緣檢測分爲四步:
第一步:消除噪聲
使用高斯濾波,使用size=5的高斯濾波進行濾波:
第二步:計算梯度幅值和方向
按照Sobel濾波器的步驟來操作。
先運用一堆卷積陣列分別作用於x和y方向:
使用公式計算梯度幅值和方向:
而梯度幅值一般取4個可能角度之一:0°、45°、90°、135°。
第三步:非極大值抑制.
進一步排除非邊緣像素,僅僅保留一些細線條,都作爲候選邊緣。
第四步:滯後閾值
滯後閾值需要兩個閾值:高閾值和地獄之;
- 若某一像素位置的幅值超過高閾值,該像素被保留爲邊緣像素;
- 若某一像素位置的幅值小於高閾值,該像素被排除;
- 若某一像素位置的幅值在兩個閾值之間,該像素僅僅在連接到一個高於高閾值的像素是被保留;
Canny檢測函數原型
void Canny( InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false );
- 參數一:InputArray類型的image,一般是cv::Mat,類型爲必須爲單通道;
- 參數二:OutputArray類型的edges;邊緣輸出邊緣圖;單通道8位圖像,與輸入的圖像大小相同;
- 參數三:double類型的threshold1,滯後過程的第一閾值;
- 參數四:double類型的threshold2,滯後過程的第二閾值;
- 參數五:int類型的額apertureSize,Sobel運算符的孔徑大小,默認爲3;
- 參數六:bool類型的L2gradient,計算梯度幅值的操作,默認爲false;
void Canny( InputArray dx,
InputArray dy,
OutputArray edges,
double threshold1,
double threshold2,
bool L2gradient = false );
- 參數一:InputArray類型的src,一般是cv::Mat,類型爲必須爲CV_16SC1 或者CV_16SC3;
- 參數二:OutputArray類型的dst;與src大小和類型一樣;
- 參數三:邊緣輸出邊緣圖;單通道8位圖像,與圖像大小相同;
- 參數四:double類型的threshold1,滯後過程的第一閾值;(注意:threshold1與threshold2的比值最好在2:1~3:1之間)
- 參數五:double類型的threshold2,滯後過程的第二閾值;(注意:threshold1與threshold2的比值最好在2:1~3:1之間)
- 參數六:bool類型的L2gradient,計算梯度幅值的操作,默認爲false。
Demo源碼
void OpenCVManager::testCanny()
{
QString fileName1 = "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/11.jpg";
cv::Mat srcMat = cv::imread(fileName1.toStdString());
int width = 400;
int height = 300;
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName);
cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 2),
srcMat.type());
cv::cvtColor(srcMat, srcMat, CV_BGR2GRAY);
int threshold1 = 200;
int threshold2 = 100;
int apertureSize = 1;
while(true)
{
windowMat = cv::Scalar(0, 0, 0);
cv::Mat mat;
cv::Mat dstMat;
cv::Mat grayMat;
// 轉換爲灰度圖像
// 原圖先copy到左邊
cv::Mat leftMat = windowMat(cv::Range(0, srcMat.rows),
cv::Range(0, srcMat.cols));
cv::cvtColor(srcMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(leftMat, 0.0f, grayMat, 1.0f, 0.0f, leftMat);
{
cvui::printf(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 20,
"threshold1");
cvui::trackbar(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 50,
200,
&threshold1,
0,
255);
cvui::printf(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 100, "threshold2");
cvui::trackbar(windowMat,
srcMat.rows * 1 + 100,
srcMat.cols * 0 + 130,
200,
&threshold2,
0,
255);
// 不能調整Sobel孔徑,否則宕機
// cvui::printf(windowMat,
// srcMat.rows * 1 + 100,
// srcMat.cols * 0 + 180, "apertureSize * 2 + 1");
// cvui::trackbar(windowMat,
// srcMat.rows * 1 + 100,
// srcMat.cols * 0 + 210,
// 200,
// &apertureSize,
// 0,
// 10);
// 使用邊緣檢測
cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
// copy
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::cvtColor(dstMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
}
// 更新
cvui::update();
// 顯示
cv::imshow(windowName, windowMat);
// esc鍵退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
工程模板:對應版本號v1.32.0
對應版本號v1.32.0
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105237807