學習記錄…
概述
canny邊緣檢測是一種特別常用且性能優秀的邊緣檢測演算法,相比于普通的邊緣檢測演算法,canny獲得的邊緣較細且具有連續的邊緣輪廓,為之后的一系列影像處理帶來極大的便利,
canny邊緣檢測也是基于梯度影像的,通常在其區域最大值附近會包含一些寬脊,為了細化這些寬脊采用的方向就是非極大值抑制——梯度的本意是一個向量(矢量),函式在該點處沿著該方向(此梯度的方向)變化最快,變化率最大(為該梯度的模——即梯度影像像素值),梯度的方向是與邊緣的方向垂直的,那么在一個3x3范圍內,可以將梯度的方向進行磁區:

梯度方向(角度)在不同的磁區可以分別映射為水平方向(垂直邊緣)、+45方向、垂直方向(水平邊緣)、-45方向,
那么在確定某一點梯度方向所屬磁區所映射到的方向之后,就將該點梯度幅值與方向上的梯度幅值進行比較,若該點梯度幅值均大于方向上點的梯度幅值則保留,否則令為0,
改進
在canny邊緣檢測中,還有一個重要的步驟:雙閾值的滯后閾值處理,一個高閾值TH和一個低閾值TL,比例在2:1到3:1內,(至于為什么會這樣真不明白)這就帶來了canny邊緣檢測的一個很大的缺點,那就是需要輸入閾值引數,基于此,很多完全自適應閾值的canny演算法誕生,在這里僅提供一種較簡單和實用的思路——將經過非極大值抑制后的梯度影像利用Otsu演算法算出一個閾值,將其作為一個高閾值TH,高閾值的一半作為低閾值TL,
演算法步驟小結
- 使用一個高斯濾波器平滑輸入影像,
- 計算梯度幅值影像和角度影像,
- 對梯度幅值影像進行非極大值抑制,
- 將非極大值抑制獲得的影像利用Otsu演算法確定雙閾值,
- 使用雙閾值處理和連通域分析來檢測與連接邊緣,
具體內容可參照岡薩雷斯《數字影像處理》
具體代碼如下:
//確定一個點的坐標是否在影像內
bool checkInRang(int r, int c, int rows, int cols) {
if (r >= 0 && r < rows && c >= 0 && c < cols)
return true;
else
return false;
}
//從確定邊緣點出發,延長邊緣
void EdgePoint_Trace(cv::Mat& edgeMag_noMaxsup, cv::Mat& edge, unsigned 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)
EdgePoint_Trace(edgeMag_noMaxsup, edge, TL, r + i, c + j, rows, cols);
}
}
}
}
/********************************mian函式入口***************************************/
int main()
{
string path = "F:\\NoteImage\\lena.jpg";
Mat SrcImage = imread(path);
if (!SrcImage.data) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat grayImage, cannyImage;
cvtColor(SrcImage, grayImage, COLOR_BGR2GRAY);
//使影像連續并可導
GaussianBlur(grayImage, grayImage, Size(3, 3), 0, 0);
cv::Mat gx, gy;
cv::Mat mag, angle;
Sobel(grayImage, gx, CV_32F, 1, 0, 3);
Sobel(grayImage, gy, CV_32F, 0, 1, 3);
//計算梯度幅值和梯度的方向(角度)
cv::cartToPolar(gx, gy, mag, angle, true);
//定義全黑非極大值抑制影像
cv::Mat Non_maxImage = cv::Mat::zeros(grayImage.size(), CV_32FC1);
int height = grayImage.rows;
int width = grayImage.cols;
//獲得非極大值抑制影像
for (int i = 1; i < height - 1; ++i)
{
for (int j = 1; j < width - 1; ++j)
{
float g_angle = angle.at<float>(i, j);
float K_mag = mag.at<float>(i, j);
//梯度方向在垂直方向
if ((g_angle <= 112.5 && g_angle > 67.5) || (g_angle <= 292.5 && g_angle > 247.5))
{
if (K_mag >= mag.at<float>(i - 1, j) && K_mag >= mag.at<float>(i + 1, j))
Non_maxImage.at<float>(i, j) = K_mag;
}
//梯度方向在水平方向
else if (g_angle <= 22.5 || g_angle > 337.5 || (g_angle <= 202.5 && g_angle > 157.5))
{
if (K_mag >= mag.at<float>(i, j - 1) && K_mag >= mag.at<float>(i, j + 1))
Non_maxImage.at<float>(i, j) = K_mag;
}
//梯度方向在+45方向
else if ((g_angle <= 67.5 && g_angle > 22.5) || (g_angle <= 247.5 && g_angle > 202.5))
{
if (K_mag >= mag.at<float>(i - 1, j - 1) && K_mag >= mag.at<float>(i + 1, j + 1))
Non_maxImage.at<float>(i, j) = K_mag;
}
//梯度方向在-45方向
else if ((g_angle <= 337.5 && g_angle > 292.5) || (g_angle <= 157.5 && g_angle > 112.5))
{
if (K_mag >= mag.at<float>(i + 1, j - 1) && K_mag >= mag.at<float>(i - 1, j + 1))
Non_maxImage.at<float>(i, j) = K_mag;
}
}
}
//雙閾值處理--根據Otsu算出的閾值確定為高閾值,取高閾值的一半記為低閾值
unsigned TH = Otsu_threshold(Non_maxImage);
unsigned TL = TH * 0.5;
cv::Mat My_cannyImage = cv::Mat::zeros(grayImage.size(), grayImage.type());
for (int i = 1; i < height - 1; ++i)
{
for (int j = 1; j < width - 1; ++j)
{
float K_mag = Non_maxImage.at<float>(i, j);
//大于高閾值確定為邊緣點
if (K_mag > TH)
EdgePoint_Trace(Non_maxImage, My_cannyImage, TL, i, j, height, width);
else if (K_mag < TL)
My_cannyImage.at<uchar>(i, j) = 0;
}
}
//和OpenCV自帶函式做對比
Canny(grayImage, cannyImage, TH, TL, 3, true);
imshow("src", My_cannyImage);
cv::waitKey(0);
return 0;
雙閾值邊緣連接處理要點采用了大佬的方法:canny算子邊緣檢測原理與實作
試驗圖例:

差別還是很微小的…
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356708.html
標籤:AI
上一篇:GFL: Generalized Focal Loss
下一篇:元學習深度決議
