日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

OpenCV 【七】————边缘提取算子(图像边缘提取)——canny算法的原理及实现

發布時間:2023/11/27 生活经验 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OpenCV 【七】————边缘提取算子(图像边缘提取)——canny算法的原理及实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

canny邊緣檢測實現(C++、opencv)

1.作用:

? ? ? ?圖像邊緣信息主要集中在高頻段,通常說圖像銳化或檢測邊緣,實質就是高頻濾波。我們知道微分運算是求信號的變化率,具有加強高頻分量的作用。在空域運算中來說,對圖像的銳化就是計算微分。對于數字圖像的離散信號,微分運算就變成計算差分或梯度。圖像處理中有多種邊緣檢測(梯度)算子,常用的包括普通一階差分,Robert算子(交叉差分),Sobel算子等等,是基于尋找梯度強度。拉普拉斯算子(二階差分)是基于過零點檢測。通過計算梯度,設置閥值,得到邊緣圖像。

Canny邊緣檢測算子是一種多級檢測算法。1986年由John F. Canny提出,同時提出了邊緣檢測的三大準則:

  1. 低錯誤率的邊緣檢測:檢測算法應該精確地找到圖像中的盡可能多的邊緣,盡可能的減少漏檢和誤檢。
  2. 最優定位:檢測的邊緣點應該精確地定位于邊緣的中心。
  3. 圖像中的任意邊緣應該只被標記一次,同時圖像噪聲不應產生偽邊緣。

Canny的工作本質是,從數學上表達前面的三個準則。因此Canny的步驟如下:

  1. 對輸入圖像進行高斯平滑,降低錯誤率。
  2. 計算梯度幅度和方向來估計每一點處的邊緣強度與方向。
  3. 根據梯度方向,對梯度幅值進行非極大值抑制。本質上是對Sobel、Prewitt等算子結果的進一步細化。
  4. 用雙閾值處理和連接邊緣。

降噪

任何邊緣檢測算法都不可能在未經處理的原始數據上很好地處理,所以第一步是對原始數據與高斯平滑模板作卷積,得到的圖像與原始圖像相比有些輕微的模糊(blurred)。這樣,單獨的一個像素噪聲在經過高斯平滑的圖像上變得幾乎沒有影響。

尋找圖像中的亮度梯度

圖像中的邊緣可能會指向不同的方向,所以Canny算法使用4個mask檢測水平、垂直以及對角線方向的邊緣。原始圖像與每個mask所作的卷積都存儲起來。對于每個點我們都標識在這個點上的最大值以及生成的邊緣的方向。這樣我們就從原始圖像生成了圖像中每個點亮度梯度圖以及亮度梯度的方向。

在圖像中跟蹤邊緣

較高的亮度梯度比較有可能是邊緣,但是沒有一個確切的值來限定多大的亮度梯度是邊緣多大又不是,所以Canny使用了滯后閾值。

滯后閾值需要兩個閾值——高閾值與低閾值。假設圖像中的重要邊緣都是連續的曲線,這樣我們就可以跟蹤給定曲線中模糊的部分,并且避免將沒有組成曲線的噪聲像素當成邊緣。所以我們從一個較大的閾值開始,這將標識出我們比較確信的真實邊緣,使用前面導出的方向信息,我們從這些真正的邊緣開始在圖像中跟蹤整個的邊緣。在跟蹤的時候,我們使用一個較小的閾值,這樣就可以跟蹤曲線的模糊部分直到我們回到起點。

一旦這個過程完成,我們就得到了一個二值圖像,每點表示是否是一個邊緣點。

一個獲得亞像素精度邊緣的改進實現是在梯度方向檢測二階方向導數的過零點

?它在梯度方向的三階方向導數滿足符號條件

其中表示用高斯核平滑原始圖像得到的尺度空間表示L計算得到的偏導數。用這種方法得到的邊緣片斷是連續曲線,這樣就不需要另外的邊緣跟蹤改進。滯后閾值也可以用于亞像素邊緣檢測。?

?

2.實現:

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
sobel算子/
//階乘
int factorial(int n){int fac = 1;//0的階乘if (n == 0)return fac;for (int i = 1; i <= n; ++i){fac *= i;}return fac;
}//獲得Sobel平滑算子
cv::Mat getSobelSmoooth(int wsize){int n = wsize - 1;cv::Mat SobelSmooothoper = cv::Mat::zeros(cv::Size(wsize, 1), CV_32FC1);for (int k = 0; k <= n; k++){float *pt = SobelSmooothoper.ptr<float>(0);pt[k] = factorial(n) / (factorial(k)*factorial(n - k));}return SobelSmooothoper;
}//獲得Sobel差分算子
cv::Mat getSobeldiff(int wsize){cv::Mat Sobeldiffoper = cv::Mat::zeros(cv::Size(wsize, 1), CV_32FC1);cv::Mat SobelSmoooth = getSobelSmoooth(wsize - 1);for (int k = 0; k < wsize; k++){if (k == 0)Sobeldiffoper.at<float>(0, k) = 1;else if (k == wsize - 1)Sobeldiffoper.at<float>(0, k) = -1;elseSobeldiffoper.at<float>(0, k) = SobelSmoooth.at<float>(0, k) - SobelSmoooth.at<float>(0, k - 1);}return Sobeldiffoper;
}//卷積實現
void conv2D(cv::Mat& src, cv::Mat& dst, cv::Mat kernel, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat  kernelFlip;cv::flip(kernel, kernelFlip, -1);cv::filter2D(src, dst, ddepth, kernelFlip, anchor, delta, borderType);
}//可分離卷積———先垂直方向卷積,后水平方向卷積
void sepConv2D_Y_X(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_Y, cv::Mat kernel_X, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat dst_kernel_Y;conv2D(src, dst_kernel_Y, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷積conv2D(dst_kernel_Y, dst, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷積
}//可分離卷積———先水平方向卷積,后垂直方向卷積
void sepConv2D_X_Y(cv::Mat& src, cv::Mat& dst, cv::Mat kernel_X, cv::Mat kernel_Y, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat dst_kernel_X;conv2D(src, dst_kernel_X, kernel_X, ddepth, anchor, delta, borderType); //水平方向卷積conv2D(dst_kernel_X, dst, kernel_Y, ddepth, anchor, delta, borderType); //垂直方向卷積
}//Sobel算子邊緣檢測
//dst_X 垂直方向
//dst_Y 水平方向
void Sobel(cv::Mat& src, cv::Mat& dst_X, cv::Mat& dst_Y, cv::Mat& dst, int wsize, int ddepth, cv::Point anchor = cv::Point(-1, -1), int delta = 0, int borderType = cv::BORDER_DEFAULT){cv::Mat SobelSmooothoper = getSobelSmoooth(wsize); //平滑系數cv::Mat Sobeldiffoper = getSobeldiff(wsize); //差分系數//可分離卷積———先垂直方向平滑,后水平方向差分——得到垂直邊緣sepConv2D_Y_X(src, dst_X, SobelSmooothoper.t(), Sobeldiffoper, ddepth);//可分離卷積———先水平方向平滑,后垂直方向差分——得到水平邊緣sepConv2D_X_Y(src, dst_Y, SobelSmooothoper, Sobeldiffoper.t(), ddepth);//邊緣強度(近似)dst = abs(dst_X) + abs(dst_Y);cv::convertScaleAbs(dst, dst); //求絕對值并轉為無符號8位圖
}//確定一個點的坐標是否在圖像內
bool checkInRang(int r,int c, int rows, int cols){if (r >= 0 && r < rows && c >= 0 && c < cols)return true;elsereturn false;
}//從確定邊緣點出發,延長邊緣
void trace(cv::Mat &edgeMag_noMaxsup, cv::Mat &edge, float TL,int r,int c,int rows,int cols){if (edge.at<uchar>(r, c) == 0){edge.at<uchar>(r, c) = 255;for (int i = -1; i <= 1; ++i){for (int j = -1; j <= 1; ++j){float mag = edgeMag_noMaxsup.at<float>(r + i, c + j);if (checkInRang(r + i, c + j, rows, cols) && mag >= TL)trace(edgeMag_noMaxsup, edge, TL, r + i, c + j, rows, cols);}}}
}//Canny邊緣檢測
void Edge_Canny(cv::Mat &src, cv::Mat &edge, float TL, float TH, int wsize=3, bool L2graydient = false){int rows = src.rows;int cols = src.cols;//高斯濾波cv::GaussianBlur(src,src,cv::Size(5,5),0.8);//sobel算子cv::Mat dx, dy, sobel_dst;Sobel(src, dx, dy, sobel_dst, wsize, CV_32FC1);//計算梯度幅值cv::Mat edgeMag;if (L2graydient)   cv::magnitude(dx, dy, edgeMag); //開平方else  edgeMag = abs(dx) + abs(dy); //絕對值之和近似//計算梯度方向 以及 非極大值抑制cv::Mat edgeMag_noMaxsup = cv::Mat::zeros(rows, cols, CV_32FC1);for (int r = 1; r < rows - 1; ++r){for (int c = 1; c < cols - 1; ++c){float x = dx.at<float>(r, c);float y = dy.at<float>(r, c);float angle = std::atan2f(y, x) / CV_PI * 180; //當前位置梯度方向float mag = edgeMag.at<float>(r, c);  //當前位置梯度幅值//非極大值抑制//垂直邊緣--梯度方向為水平方向-3*3鄰域內左右方向比較if (abs(angle)<22.5 || abs(angle)>157.5){float left = edgeMag.at<float>(r, c - 1);float right = edgeMag.at<float>(r, c + 1);if (mag >= left && mag >= right)edgeMag_noMaxsup.at<float>(r, c) = mag;}//水平邊緣--梯度方向為垂直方向-3*3鄰域內上下方向比較if ((angle>=67.5 && angle<=112.5 ) || (angle>=-112.5 && angle<=-67.5)){float top = edgeMag.at<float>(r-1, c);float down = edgeMag.at<float>(r+1, c);if (mag >= top && mag >= down)edgeMag_noMaxsup.at<float>(r, c) = mag;}//+45°邊緣--梯度方向為其正交方向-3*3鄰域內右上左下方向比較if ((angle>112.5 && angle<=157.5) || (angle>-67.5 && angle<=-22.5)){float right_top = edgeMag.at<float>(r - 1, c+1);float left_down = edgeMag.at<float>(r + 1, c-1);if (mag >= right_top && mag >= left_down)edgeMag_noMaxsup.at<float>(r, c) = mag;}//+135°邊緣--梯度方向為其正交方向-3*3鄰域內右下左上方向比較if ((angle >=22.5 && angle < 67.5) || (angle >= -157.5 && angle < -112.5)){float left_top = edgeMag.at<float>(r - 1, c - 1);float right_down = edgeMag.at<float>(r + 1, c + 1);if (mag >= left_top && mag >= right_down)edgeMag_noMaxsup.at<float>(r, c) = mag;}}}//雙閾值處理及邊緣連接edge = cv::Mat::zeros(rows, cols, CV_8UC1);for (int r = 1; r < rows - 1; ++r){for (int c = 1; c < cols - 1; ++c){float mag = edgeMag_noMaxsup.at<float>(r, c);//大于高閾值,為確定邊緣點if (mag >= TH)trace(edgeMag_noMaxsup, edge, TL, r, c, rows, cols);else if (mag < TL)edge.at<uchar>(r, c) = 0;}}
}int main(){cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\lena.jpg");if (src.empty()){return -1;}if (src.channels() > 1) cv::cvtColor(src, src, CV_RGB2GRAY);cv::Mat edge,dst;//CannyEdge_Canny(src, edge, 20,60);//opencv自帶Cannycv::Canny(src, dst, 20, 80);cv::namedWindow("src", CV_WINDOW_NORMAL);imshow("src", src);cv::namedWindow("My_canny", CV_WINDOW_NORMAL);imshow("My_canny", edge);cv::namedWindow("Opencv_canny", CV_WINDOW_NORMAL);imshow("Opencv_canny", dst);cv::waitKey(0);return 0;
}
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/video/background_segm.hpp>
#include "opencv2/calib3d/calib3d.hpp"
#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;int main(int argc, char** argv) {cv::Mat temp_thin = cv::imread("../example/2.jpg", CV_LOAD_IMAGE_UNCHANGED);//cv::imshow("temp_thin",temp_thin);cv::Mat gray_image, dst, temp_thin_image, binary_image, edge_image;cvtColor(temp_thin, gray_image, CV_RGB2GRAY);GaussianBlur(gray_image, edge_image, Size(3, 3), 0, 0);//blur(gray, edge, Size(3, 3));Canny(edge_image, edge_image, 10, 150, 3, false);cv::imshow("edge_image", edge_image);std:; string anme_pic = "..\\example\\edge_image.bmp";cv::imwrite(anme_pic, edge_image);cv::waitKey(0);return 0;
}

3.效果

?

?

?

4.函數原型

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

5.原理

  • 須滿足條件:抑制噪聲;精確定位邊緣。
  • 從數學上表達了三個準則[信噪比準則(低錯誤率)、定位精度準則、單邊緣響應準則],并尋找表達式的最佳解。
  • 屬于先平滑后求導的方法。

?

?

1、高斯平滑濾波(略)

2、計算圖像梯度的幅值和方向

可選用的模板:soble算子、Prewitt算子、一階差分卷積模板等等;

在此選用Prewitt算子為例:

?

?

3、對幅值圖像進行非極大值抑制

首先將角度劃分成四個方向范圍 :水平(0°)、?45°、垂直(90°)、+45°。如下圖:

?

扇形區標號d1~d4,對應3*3領域的4種可能的組合,1-x-5 , 7-x-3 , 2-x-6 , 8-x-4。

在每一點上,領域中心 x 與沿著其對應的梯度方向的兩個像素相比,若中心像素為最大值,則保留,否則中心置0,這樣可以抑制非極大值,保留局部梯度最大的點,以得到細化的邊緣。

4、用雙閾值算法檢測和連接邊緣

選取系數TH和TL,比率為2:1或3:1。(一般取TH=0.3或0.2,TL=0.1);
取出非極大值抑制后的圖像中的最大梯度幅值,定義高低閾值。即:TH×Max,TL×Max (當然可以自己給定) ;
將小于低閾值的點拋棄,賦0;將大于高閾值的點立即標記(這些點就是邊緣點),賦1;
將小于高閾值,大于低閾值的點使用8連通區域確定(即:只有與TH像素連接時才會被接受,成為邊緣點,賦??1)。
?

6.參考

【1】?https://blog.csdn.net/weixin_40647819/article/details/91411424

【2】https://blog.csdn.net/likezhaobin/article/details/6892176

【3】https://docs.opencv.org/master/dd/d1a/group__imgproc__feature.html

【4】https://blog.csdn.net/weixin_40647819/article/details/80377343

【5】https://blog.csdn.net/liuzhuomei0911/article/details/51345591

?

總結

以上是生活随笔為你收集整理的OpenCV 【七】————边缘提取算子(图像边缘提取)——canny算法的原理及实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。