国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Canny算子邊緣檢測原理講解及其完整C語言實現(xiàn)(不使用opencv)

這篇具有很好參考價值的文章主要介紹了Canny算子邊緣檢測原理講解及其完整C語言實現(xiàn)(不使用opencv)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

寫在前面

  • 作者:隊友調(diào)車我吹空調(diào)
  • 日期:2023/05/17
  • 版權(quán):遵循CC 4.0 BY-SA版權(quán)協(xié)議

這里是后期的筆者,本文算是筆者的學(xué)習(xí)筆記,主要是在單片機(jī)中使用的,再者就是由于某些原因,筆者不想使用opencv,因此嘗試跟著原理手搓了這份代碼,筆者也盡力將代碼寫到最簡和效率最優(yōu)了。然而即使如此,Canny算子運算量依然非常大。高斯濾波的時候遍歷了一次圖像,計算梯度的時候遍歷了四次,非極大抑制的時候遍歷了一次。雙閾值和滯后邊界跟蹤總共遍歷一次。也就是說整套算法下來一共遍歷了圖像7次,完成跑下來爆內(nèi)存,筆者所使用的TC387都險些跑不下來。

所以在最開始,首先要對圖像做一些處理。

在進(jìn)行高斯濾波器前,將圖像裁剪到合適的大小,在這里筆者比較推薦30*80的大小。筆者在TC387上使用單獨一個核是可以較快運行的。如果對細(xì)節(jié)要求不高的同學(xué)可以再降采樣一次。

筆者在這里給大家提供一個圖像裁剪的函數(shù)。

/*************************************************************************
*  函數(shù)名稱:cropImage
*  功能說明:將大小為[MT9V03X_H][MT9V03X_W]的圖像裁剪到[PICTURE_H][PICTURE_W]
*  參數(shù)說明:picture_in[MT9V03X_H][MT9V03X_W] 輸入圖像
*  函數(shù)返回:void
*  修改時間:2023/05/17
*  備一一注:
*************************************************************************/
void cropImage(const uint8_t picture_in[MT9V03X_H][MT9V03X_W], uint8_t picture_out[PICTURE_H][PICTURE_W])
{
    int start_row = (MT9V03X_H - PICTURE_H) / 2;
    int start_col = (MT9V03X_W - PICTURE_W) / 2;

    for (int i = 0; i < PICTURE_H; i++) {
        for (int j = 0; j < PICTURE_W; j++) {
            picture_out[i][j] = picture_in[start_row + i][start_col + j];
        }
    }
}

筆者在正是內(nèi)容開始前,先談一談自己對Canny算子的理解。其實認(rèn)真看了我下邊的內(nèi)容的話,是能夠感覺到Canny算子的思想非常簡單的,先高斯濾波將圖像細(xì)節(jié)處理一下,接下來計算梯度幅值、梯度方向,梯度變化最劇烈的地方就是邊界;找到邊界以后,對邊界濾一下波,除去最不可能是邊界的地方,標(biāo)出最可能是邊界的地方,再用邊界跟蹤算法處理有一點可能是邊界的地方。

Canny算子的核心思想可以看成是使用了兩次Sobel算子,但是效果和計算量與Sobel算子都是不可同日而語的。

概覽

Canny邊緣檢測是一種非常流行的邊緣檢測算法,是John Canny在1986年提出的。它是一個多階段的算法,即由多個步驟構(gòu)成。本文主要講解了Canny算子的原理及實現(xiàn)過程。通常情況下邊緣檢測的目的是在保留原有圖像屬性的情況下,顯著減少圖像的數(shù)據(jù)規(guī)模。有多種算法可以進(jìn)行邊緣檢測,雖然Canny算法年代久遠(yuǎn),但可以說它是邊緣檢測的一種標(biāo)準(zhǔn)算法,而且仍在研究中廣泛使用。

步驟

  1. 應(yīng)用高斯濾波來平滑圖像,目的是去除噪聲
  2. 找尋圖像的強度梯度(intensity gradients)
  3. 應(yīng)用非最大抑制(non-maximum suppression)技術(shù)來消除邊誤檢(本來不是但檢測出來是)
  4. 應(yīng)用雙閾值的方法來決定可能的(潛在的)邊界
  5. 利用滯后技術(shù)來跟蹤邊界

詳解

高斯平滑濾波

濾波是為了去除噪聲,選用高斯濾波也是因為在眾多噪聲濾波器中,高斯表現(xiàn)最好(如果嘗試其他濾波器,如均值濾波、中值濾波等等,就會發(fā)現(xiàn)高斯的效果最好)。

一個大小為(2k+1)x(2k+1)的高斯濾波器核(核一般都是奇數(shù)尺寸的)的生成方程式由下式給出:
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

高斯濾波

高斯濾波是一種線性平滑濾波,適用于消除高斯噪聲,廣泛應(yīng)用于圖像處理的減噪過程。

通俗的講,高斯濾波就是對整幅圖像進(jìn)行加權(quán)平均的過程,每一個像素點的值,都由其本身和鄰域內(nèi)的其他像素值經(jīng)過加權(quán)平均后得到

高斯濾波的具體操作是:用一個模板(或稱卷積、掩模)掃描圖像中的每一個像素,用模板確定的鄰域內(nèi)像素的加權(quán)平均灰度值去替代模板中心像素點的值。

對于均值濾波和方框濾波來說,其鄰域內(nèi)每個像素的權(quán)重是相等的。而在高斯濾波中,會將中心點的權(quán)重值加大遠(yuǎn)離中心點的權(quán)重值減小,在此基礎(chǔ)上計算鄰域內(nèi)各個像素值不同權(quán)重的和。

針對最左側(cè)的圖像內(nèi)第4行第3列位置上像素值為226的像素點進(jìn)行高斯卷積,其運算規(guī)則為將該領(lǐng)域內(nèi)的像素點按照不同的權(quán)重計算和。
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

在實際使用中,高斯濾波使用的可能是不同大小的卷積核。例如, 3×3、5×5、7×7 大小的卷積核。在高斯濾波中,核的寬度和高度可以不相同,但是它們都必須是奇數(shù)。
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

每一種尺寸的卷積核都可以有多種不同形式的權(quán)重比例。例如,同樣是5×5的卷積核,可能是兩種不同的權(quán)重比。
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

在實際計算中,卷積核是歸一化處理的。

————————————————
版權(quán)聲明:本文為CSDN博主「半濠春水」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議。
原文鏈接:https://blog.csdn.net/weixin_51571728/article/details/121527964

代碼實現(xiàn)

#include <stdio.h>
#include <math.h>

#define PICTURE_H 120
#define PICTURE_W 188

void gaussianBlur(unsigned char picture_in[PICTURE_H][PICTURE_W], int ksize, double sigma, unsigned char picture_out[PICTURE_H][PICTURE_W])
{
    int i, j, m, n;
    int radius = ksize / 2;
    double weightSum;
    double weightTotal;
    double weightMatrix[ksize][ksize];

    // 計算高斯權(quán)重矩陣
    for (i = 0; i < ksize; i++)
    {
        for (j = 0; j < ksize; j++)
        {
            m = i - radius;
            n = j - radius;
            weightMatrix[i][j] = exp(-((m * m + n * n) / (2 * sigma * sigma)));
            weightSum += weightMatrix[i][j];
        }
    }

    // 高斯濾波處理
    for (i = 0; i < PICTURE_H; i++)
    {
        for (j = 0; j < PICTURE_W; j++)
        {
            double pixelValue = 0.0;
            weightTotal = 0.0;

            for (m = -radius; m <= radius; m++)
            {
                for (n = -radius; n <= radius; n++)
                {
                    if (i + m >= 0 && i + m < PICTURE_H && j + n >= 0 && j + n < PICTURE_W)
                    {
                        pixelValue += picture_in[i + m][j + n] * weightMatrix[m + radius][n + radius];
                        weightTotal += weightMatrix[m + radius][n + radius];
                    }
                }
            }

            picture_out[i][j] = (unsigned char)(pixelValue / weightTotal);
        }
    }
}

調(diào)用示例

int main()
{
    // 定義輸入和輸出圖像數(shù)組
    unsigned char picture_in[PICTURE_H][PICTURE_W];
    unsigned char picture_out[PICTURE_H][PICTURE_W];

    // 假設(shè)已經(jīng)讀取了輸入圖像到picture_in數(shù)組中

    int ksize = 3;   // 濾波核大小
    double sigma = 1.0;  // 水平方向上的標(biāo)準(zhǔn)差

    // 調(diào)用高斯濾波函數(shù)
    gaussianBlur(picture_in, ksize, sigma, picture_out);

    // 輸出濾波后的圖像或進(jìn)行其他處理

    return 0;
}

調(diào)用效果

結(jié)果是下面兩張圖片,可以看到圖像的邊緣被明顯地模糊了,圖像的抗噪能力變強了。
canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

總結(jié):

總結(jié)一下這一步:高斯濾波其實就是將所指像素用周圍的像素的某種均值代替(即卷積核),卷積核尺寸越大,去噪能力越強,因此噪聲越少,但圖片越模糊,canny檢測算法抗噪聲能力越強,但模糊的副作用也會導(dǎo)致定位精度不高。
高斯的卷積核大小推薦:一般情況下,尺寸5 * 5,3 * 3就行。

計算梯度的大小和方向

對于一張圖片來說,梯度能很好地反映其像素的變化情況,而梯度變化越大,說明相鄰像素之間存在著較大差異,放大到整張圖片來說,就是在某一塊區(qū)域存在邊緣,從視覺上來說就是用黑到白(灰度圖片讀入)。

梯度的計算分為大小方向,首先需要求出各個方向上的梯度,然后求平方根和切線。

以下是x、y方向上梯度的計算方式:

canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

計算梯度的過程包括以下幾個步驟:

  1. 首先,使用某種邊緣檢測算子(如Sobel算子)分別對圖像在水平和垂直方向進(jìn)行卷積運算。這將產(chǎn)生兩個梯度圖像,分別表示水平方向和垂直方向上的像素變化情況。
  2. 對于每個像素,可以使用求平方根的方法來計算梯度的大小。將水平方向和垂直方向上的梯度值平方,然后將它們相加,再對結(jié)果進(jìn)行開方運算。這將給出每個像素位置上的梯度大小。
  3. 同時,可以使用反正切函數(shù)(如atan2)來計算梯度的方向。通過將水平方向上的梯度值除以垂直方向上的梯度值,可以得到每個像素位置上的梯度方向。

使用Sobel算子計算圖像梯度幅值和梯度方向

Sobel算子可以分別在水平和垂直方向上對圖像進(jìn)行卷積操作,以捕捉圖像中像素值的變化情況。以下是使用Sobel算子計算梯度的詳細(xì)步驟:

灰度化:首先,將彩色圖像轉(zhuǎn)換為灰度圖像。這可以通過將彩色圖像的每個像素的紅、綠和藍(lán)通道值進(jìn)行加權(quán)平均來實現(xiàn),得到對應(yīng)的灰度值。

建立Sobel算子:Sobel算子是一個3x3的卷積核,分為水平和垂直方向上的兩個核。這兩個核分別用于檢測圖像中水平和垂直方向上的邊緣。下面是Sobel算子的示例:

水平方向Sobel算子:
-1  0  1
-2  0  2
-1  0  1

垂直方向Sobel算子:
-1 -2 -1
 0  0  0
 1  2  1

對圖像進(jìn)行卷積運算:將Sobel算子分別應(yīng)用于圖像的水平和垂直方向上,進(jìn)行卷積運算。對于每個像素位置,將算子與其周圍像素進(jìn)行乘法運算,并將乘積的結(jié)果相加,得到新的像素值。

計算梯度幅值和方向:對于每個像素位置,使用水平方向和垂直方向上的卷積結(jié)果,分別計算梯度的幅值和方向。幅值可以通過求平方根來得到,方向可以通過反正切函數(shù)(如atan2)計算得到。

建立Sobel算子,計算每個像素點在四個方向上的梯度幅值
代碼實現(xiàn)
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef signed short int int16;

/****************************************************************************************************
*函數(shù)簡介       基于soble算子計算四個方向的梯度幅值 
*參數(shù)說明       picture         輸入二維數(shù)組名
*參數(shù)說明		gradient_h		水平方向梯度幅值
				gradient_v		垂直方向梯度幅值
				gradient_d1		正對角線方向梯度幅值 
				gradient_d2		反對角線方向梯度幅值
*返回參數(shù)       void
****************************************************************************************************/
void sobel_gradient(uint8 picture[PICTURE_H][PICTURE_W], int16 gradient_h[PICTURE_H][PICTURE_W], int16 gradient_v[PICTURE_H][PICTURE_W], int16 gradient_d1[PICTURE_H][PICTURE_W], int16 gradient_d2[PICTURE_H][PICTURE_W])
{
    /*卷積核大小*/
    uint8 KERNEL_SIZE = 3;
    uint16 xStart = KERNEL_SIZE / 2;
    uint16 xEnd = PICTURE_W - KERNEL_SIZE / 2;
    uint16 yStart = KERNEL_SIZE / 2;
    uint16 yEnd = PICTURE_H - KERNEL_SIZE / 2;
    uint16 i, j;

    for (i = yStart; i < yEnd; i++)
    {
        for (j = xStart; j < xEnd; j++)
        {
            /*計算不同方向梯度幅值*/
            // 水平方向
            gradient_h[i][j] = -(int16)picture[i - 1][j - 1] + (int16)picture[i - 1][j + 1]
                               -(int16)picture[i][j - 1] + (int16)picture[i][j + 1]
                               -(int16)picture[i + 1][j - 1] + (int16)picture[i + 1][j + 1];

            // 垂直方向
            gradient_v[i][j] = -(int16)picture[i - 1][j - 1] + (int16)picture[i + 1][j - 1]
                               -(int16)picture[i - 1][j] + (int16)picture[i + 1][j]
                               -(int16)picture[i - 1][j + 1] + (int16)picture[i + 1][j + 1];

            // 對角線方向1
            gradient_d1[i][j] = -(int16)picture[i - 1][j] + (int16)picture[i][j - 1]
                                -(int16)picture[i][j + 1] + (int16)picture[i + 1][j]
                                -(int16)picture[i - 1][j + 1] + (int16)picture[i + 1][j - 1];

            // 對角線方向2
            gradient_d2[i][j] = -(int16)picture[i - 1][j] + (int16)picture[i][j + 1]
                                -(int16)picture[i][j - 1] + (int16)picture[i + 1][j]
                                -(int16)picture[i - 1][j - 1] + (int16)picture[i + 1][j + 1];
        }
    }
}
調(diào)用示例
#define PICTURE_H 120
#define PICTURE_W 188

uint8 picture[PICTURE_H][PICTURE_W];
int16 gradient_h[PICTURE_H][PICTURE_W];
int16 gradient_v[PICTURE_H][PICTURE_W];
int16 gradient_d1[PICTURE_H][PICTURE_W];
int16 gradient_d2[PICTURE_H][PICTURE_W];

// 填充輸入圖像數(shù)組 picture

SOBEL(picture, gradient_h, gradient_v, gradient_d1, gradient_d2);

// 使用計算得到的梯度幅值進(jìn)行后續(xù)處理
計算圖像的梯度幅值和梯度方向
代碼實現(xiàn)
/****************************************************************************************************
 * 計算圖像的梯度幅值和方向
 * @param picture 輸入圖像數(shù)組,類型為 uint8,大小為 [PICTURE_H][PICTURE_W]。
 * @param Gx 輸入圖像的水平方向梯度數(shù)組,類型為 int16,大小為 [PICTURE_H][PICTURE_W]。
 * @param Gy 輸入圖像的垂直方向梯度數(shù)組,類型為 int16,大小為 [PICTURE_H][PICTURE_W]。
 * @param G 輸出圖像的梯度幅值數(shù)組,類型為 int16,大小為 [PICTURE_H][PICTURE_W]。
 * @param theta 輸出圖像的梯度方向數(shù)組,類型為 float,大小為 [PICTURE_H][PICTURE_W]。
 ****************************************************************************************************/
void calculate_gradient(uint8 picture[PICTURE_H][PICTURE_W], int16 Gx[PICTURE_H][PICTURE_W], int16 Gy[PICTURE_H][PICTURE_W], int16 G[PICTURE_H][PICTURE_W], float theta[PICTURE_H][PICTURE_W])
{
    // 計算圖像梯度幅值和方向
    for (int i = 0; i < PICTURE_H; i++)
    {
        for (int j = 0; j < PICTURE_W; j++)
        {
            G[i][j] = sqrt(pow(Gx[i][j], 2) + pow(Gy[i][j], 2));
            theta[i][j] = atan2(Gy[i][j], Gx[i][j]) * 180 / PI;
        }
    }
}
調(diào)用示例
#include <stdio.h>

#define PICTURE_H 100 // 圖像的高度
#define PICTURE_W 100 // 圖像的寬度

void calculate_gradient(uint8 picture[PICTURE_H][PICTURE_W], int16 Gx[PICTURE_H][PICTURE_W], int16 Gy[PICTURE_H][PICTURE_W], int16 G[PICTURE_H][PICTURE_W], float theta[PICTURE_H][PICTURE_W]);

int main()
{
    // 假設(shè)有一個大小為 [PICTURE_H][PICTURE_W] 的圖像和梯度數(shù)組
    uint8 picture[PICTURE_H][PICTURE_W];
    int16 Gx[PICTURE_H][PICTURE_W];
    int16 Gy[PICTURE_H][PICTURE_W];
    int16 G[PICTURE_H][PICTURE_W];
    float theta[PICTURE_H][PICTURE_W];

    // 填充圖像和梯度數(shù)組的數(shù)據(jù)

    // 調(diào)用計算梯度函數(shù)
    calculate_gradient(picture, Gx, Gy, G, theta);

    // 打印梯度幅值和方向的示例
    for (int i = 0; i < PICTURE_H; i++)
    {
        for (int j = 0; j < PICTURE_W; j++)
        {
            printf("G[%d][%d] = %d\n", i, j, G[i][j]);
            printf("theta[%d][%d] = %f\n", i, j, theta[i][j]);
        }
    }

    return 0;
}
調(diào)用結(jié)果

canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

可以看到,[-1, 0, 1], [-1, 0, 1], [-1, 0, 1] 對應(yīng)了x方向上的邊緣檢測(在碰到x方向垂直的邊界的時候計算出來的數(shù)值更大,所以像素更亮),[1, 1, 1], [0, 0, 0], [-1, -1, -1] 同理對應(yīng)了y方向上的邊緣檢測。

非極大抑制

用Sobel算子或者是其他簡單的邊緣檢測方法,計算出來的邊緣太粗了,就是說原本的一條邊用幾條幾乎重疊的邊所代替了,導(dǎo)致視覺上看起來邊界很粗。非極大抑制是一種瘦邊經(jīng)典算法。它抑制那些梯度不夠大的像素點,只保留最大的梯度,從而達(dá)到瘦邊的目的。

a) 將其梯度方向近似為以下值中的一個[0,45,90,135,180,225,270,315](即上下左右和45度方向)這一步是為了方便使用梯度;
b) 比較該像素點,和其梯度方向正負(fù)方向的像素點的梯度強度,這里比較的范圍一般為像素點的八鄰域;
c) 如果該像素點梯度強度最大則保留,否則抑制(刪除,即置為0);

代碼實現(xiàn)

/****************************************************************************************************
 * 對圖像的梯度幅值進(jìn)行非極大值抑制,以細(xì)化邊緣。
 *
 * @param G 梯度幅值數(shù)組,類型為 int16,大小為 [PICTURE_H][PICTURE_W]。
 * @param theta 梯度方向數(shù)組,類型為 float,大小為 [PICTURE_H][PICTURE_W]。
 * @param edge_threshold 邊緣閾值,類型為 int16。
 * @param result 輸出的二值化邊緣圖像數(shù)組,類型為 uint8,大小為 [PICTURE_H][PICTURE_W]。
 ****************************************************************************************************/
void non_maximum_suppression(int16 G[PICTURE_H][PICTURE_W], float theta[PICTURE_H][PICTURE_W], int16 edge_threshold, uint8 result[PICTURE_H][PICTURE_W])
{
    // 對梯度幅值進(jìn)行非極大值抑制
    for (int i = 1; i < PICTURE_H - 1; i++)
    {
        for (int j = 1; j < PICTURE_W - 1; j++)
        {
            float angle = theta[i][j];

            // 將角度映射到 0 到 180 度之間
            if (angle < 0)
                angle += 180;

            // 判斷當(dāng)前像素點的梯度方向,并進(jìn)行非極大值抑制
            if (((angle >= 0 && angle < 22.5) || (angle >= 157.5 && angle < 180)) ||  // 水平方向
                ((angle >= 22.5 && angle < 67.5) || (angle >= 202.5 && angle < 247.5)) ||  // 主對角線方向
                ((angle >= 67.5 && angle < 112.5) || (angle >= 247.5 && angle < 292.5)) ||  // 垂直方向
                ((angle >= 112.5 && angle < 157.5) || (angle >= 292.5 && angle < 337.5)))  // 次對角線方向
            {
                if (G[i][j] > G[i][j - 1] && G[i][j] > G[i][j + 1])
                {
                    // 梯度幅值大于相鄰兩個像素的幅值,保留邊緣
                    result[i][j] = (G[i][j] >= edge_threshold) ? 255 : 0;
                }
                else
                {
                    result[i][j] = 0;
                }
            }
            else
            {
                if (G[i][j] > G[i - 1][j] && G[i][j] > G[i + 1][j])
                {
                    // 梯度幅值大于相鄰兩個像素的幅值,保留邊緣
                    result[i][j] = (G[i][j] >= edge_threshold) ? 255 : 0;
                }
                else
                {
                    result[i][j] = 0;
                }
            }
        }
    }

    // 將圖像邊緣的邊界像素置為 0
    for (int i = 0; i < PICTURE_H; i++)
    {
        result[i][0] = 0;
        result[i][PICTURE_W - 1] = 0;
    }
    for (int j = 0; j < PICTURE_W; j++)
    {
        result[0][j] = 0;
        result[PICTURE_H - 1][j] = 0;
    }
}

調(diào)用示例

// 假設(shè)有已計算好的梯度幅值和方向數(shù)組 G 和 theta
int16 G[PICTURE_H][PICTURE_W];
float theta[PICTURE_H][PICTURE_W];

// 定義邊緣閾值
int16 edge_threshold = 100;

// 定義二值化邊緣圖像數(shù)組
uint8 result[PICTURE_H][PICTURE_W];

// 執(zhí)行非極大值抑制
non_maximum_suppression(G, theta, edge_threshold, result);

示例中的邊緣閾值 edge_threshold 是一個示例值,需要根據(jù)實際情況調(diào)整該閾值以達(dá)到滿意的邊緣檢測效果。

該函數(shù)使用兩個嵌套的循環(huán)遍歷圖像像素,并根據(jù)梯度方向進(jìn)行非極大值抑制。代碼中對于不同的梯度方向使用了不同的判斷條件,并根據(jù)相鄰像素的梯度幅值決定是否保留邊緣。在循環(huán)結(jié)束后,代碼還會將圖像邊緣的邊界像素置為0。

調(diào)用結(jié)果

canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

雙閾值(Double Thresholding)和滯后邊界跟蹤

經(jīng)過非極大抑制后圖像中仍然有很多噪聲點。Canny算法中應(yīng)用了一種叫雙閾值的技術(shù)。

使用雙閾值是常見的邊緣檢測后續(xù)處理步驟之一。雙閾值處理可以更好地區(qū)分邊緣和非邊緣像素,并進(jìn)一步提升邊緣檢測的準(zhǔn)確性。

雙閾值處理包括以下步驟:

  1. 高閾值和低閾值的選擇:根據(jù)你的應(yīng)用需求和圖像特性,選擇適當(dāng)?shù)母唛撝岛偷烷撝?。高閾值用于定義強邊緣像素,低閾值用于定義弱邊緣像素。
  2. 邊緣分類:根據(jù)閾值,將像素分類為強邊緣像素、弱邊緣像素和非邊緣像素。一般來說,高于高閾值的像素被認(rèn)為是強邊緣,低于低閾值的像素被認(rèn)為是非邊緣,介于兩者之間的像素被認(rèn)為是弱邊緣。
  3. 邊緣連接:對于弱邊緣像素,如果其與強邊緣像素相鄰,則將其重新分類為強邊緣。這可以通過邊緣跟蹤算法(如霍夫變換或連通組件分析)來實現(xiàn)。

通過雙閾值處理,可以獲得更準(zhǔn)確的邊緣圖像,強邊緣像素代表明顯的邊界,而弱邊緣像素代表可能的邊界。這為后續(xù)的邊緣連接和過濾提供了更好的基礎(chǔ)。

雙閾值,即設(shè)定一個閾值上界和閾值下界(通常由人為指定的),圖像中的像素點如果大于閾值上界則認(rèn)為必然是邊界(稱為強邊界,strong edge),小于閾值下界則認(rèn)為必然不是邊界,兩者之間的則認(rèn)為是候選項(稱為弱邊界,weak edge),需進(jìn)行進(jìn)一步處理。我查閱資料,了解到上界一般是下界的2-3倍,實現(xiàn)出來的效果比較好。在Canny算法中,對于弱邊緣像素,通常采用邊緣跟蹤(如霍夫變換或連通組件分析)來連接它們與強邊緣像素,形成連續(xù)的邊界線段。

雙閾值技術(shù)

代碼實現(xiàn)
/****************************************************************************************************
 * 應(yīng)用雙閾值技術(shù)將梯度幅值圖像轉(zhuǎn)換為邊界圖像
 *
 * @param G 梯度幅值數(shù)組,二維數(shù)組,大小為 PICTURE_H × PICTURE_W
 * @param edges 邊界圖像數(shù)組,二維數(shù)組,大小為 PICTURE_H × PICTURE_W
 * @param high_threshold 高閾值,用于判斷強邊界的閾值
 * @param low_threshold 低閾值,用于判斷強邊界和弱邊界的閾值
 ****************************************************************************************************/
void apply_double_threshold(int16 G[PICTURE_H][PICTURE_W], uint8 edges[PICTURE_H][PICTURE_W], int16 high_threshold, int16 low_threshold)
{
    for (int i = 0; i < PICTURE_H; i++)
    {
        for (int j = 0; j < PICTURE_W; j++)
        {
            if (G[i][j] >= high_threshold)
            {
                // 像素強度大于等于高閾值,被認(rèn)為是強邊界
                edges[i][j] = 255; // 白色
            }
            else if (G[i][j] >= low_threshold)
            {
                // 像素強度在高閾值和低閾值之間,被認(rèn)為是弱邊界
                edges[i][j] = 128; // 灰色
            }
            else
            {
                // 像素強度小于低閾值,被認(rèn)為是非邊界
                edges[i][j] = 0; // 黑色
            }
        }
    }
}

這個函數(shù)接受梯度幅值數(shù)組 G、輸出的邊界數(shù)組 edges,以及高閾值 high_threshold 和低閾值 low_threshold 作為參數(shù)。它遍歷每個像素,根據(jù)其梯度幅值將其分類為強邊界、弱邊界或非邊界,并在邊界數(shù)組中相應(yīng)地設(shè)置像素值。

基于八鄰域的邊緣跟蹤算法

代碼實現(xiàn)
/****************************************************************************************************
 * 使用滯后邊界跟蹤對弱邊界進(jìn)行二次判斷
 *
 * @param edges 邊界圖像數(shù)組,二維數(shù)組,大小為 PICTURE_H × PICTURE_W
 * @param high_threshold 高閾值,用于判斷強邊界的閾值
 * @param low_threshold 低閾值,用于判斷強邊界和弱邊界的閾值
 ****************************************************************************************************/
void apply_hysteresis_threshold(uint8 edges[PICTURE_H][PICTURE_W], int16 high_threshold, int16 low_threshold)
{
    for (int i = 1; i < PICTURE_H - 1; i++)
    {
        for (int j = 1; j < PICTURE_W - 1; j++)
        {
            if (edges[i][j] == 128) // 如果當(dāng)前像素為弱邊界
            {
                // 檢查當(dāng)前像素周圍的8個像素
                if (edges[i - 1][j - 1] == 255 || edges[i - 1][j] == 255 || edges[i - 1][j + 1] == 255 ||
                    edges[i][j - 1] == 255 || edges[i][j + 1] == 255 ||
                    edges[i + 1][j - 1] == 255 || edges[i + 1][j] == 255 || edges[i + 1][j + 1] == 255)
                {
                    // 如果周圍任意一個像素是強邊界,則將當(dāng)前像素標(biāo)記為強邊界
                    edges[i][j] = 255;
                }
                else
                {
                    // 否則將當(dāng)前像素標(biāo)記為非邊界
                    edges[i][j] = 0;
                }
            }
        }
    }
}

雙閾值+邊緣跟蹤

使用雙閾值后再調(diào)用邊緣跟蹤,遍歷兩次似乎效率太低,所以我們可以將這兩個算法合成一個函數(shù)。

代碼實現(xiàn)
/****************************************************************************************************
 * 應(yīng)用雙閾值技術(shù)將梯度幅值圖像轉(zhuǎn)換為邊界圖像,并使用邊緣跟蹤算法處理弱邊界
 *
 * @param G 梯度幅值數(shù)組,二維數(shù)組,大小為 PICTURE_H × PICTURE_W
 * @param edges 邊界圖像數(shù)組,二維數(shù)組,大小為 PICTURE_H × PICTURE_W
 * @param high_threshold 高閾值,用于判斷強邊界的閾值
 * @param low_threshold 低閾值,用于判斷強邊界和弱邊界的閾值
 ****************************************************************************************************/
void apply_double_threshold(int16 G[PICTURE_H][PICTURE_W], uint8 edges[PICTURE_H][PICTURE_W], int16 high_threshold, int16 low_threshold)
{
    for (int i = 0; i < PICTURE_H-1; i++)
    {
        for (int j = 0; j < PICTURE_W-1; j++)
        {
            if (G[i][j] >= high_threshold)
            {
                // 像素強度大于等于高閾值,被認(rèn)為是強邊界
                edges[i][j] = 255; // 白色
            }
            else if (G[i][j] >= low_threshold)
            {
                // 像素強度在高閾值和低閾值之間,被認(rèn)為是弱邊界
                // 檢查當(dāng)前像素周圍的8個像素
                if (edges[i - 1][j - 1] == 255 || edges[i - 1][j] == 255 || edges[i - 1][j + 1] == 255 ||
                    edges[i][j - 1] == 255 || edges[i][j + 1] == 255 ||
                    edges[i + 1][j - 1] == 255 || edges[i + 1][j] == 255 || edges[i + 1][j + 1] == 255)
                {
                    // 如果周圍任意一個像素是強邊界,則將當(dāng)前像素標(biāo)記為強邊界
                    edges[i][j] = 255;
                }
                else
                {
                    // 否則將當(dāng)前像素標(biāo)記為非邊界
                    edges[i][j] = 0;
                }
            }
            else
            {
                // 像素強度小于低閾值,被認(rèn)為是非邊界
                edges[i][j] = 0; // 黑色
            }
        }
    }
}

在上述代碼中,白色方塊代表存在邊緣(包括強弱邊緣),遍歷弱邊緣中的每個像素,如果像素的八鄰域存在強邊緣對應(yīng)的像素,則將這個弱邊緣像素歸為真正的邊緣(從視覺上來理解,就是存在一條不確定的邊緣,如果這條不確定的邊緣旁存在真正的邊緣,則將這條邊歸為真邊,非則就將其刪除)

調(diào)用結(jié)果

canny算法的c語言實現(xiàn),單片機(jī),算法,c語言,圖像處理,自動駕駛,邊緣計算,視覺檢測

封裝

最后我們可以將以上代碼封裝成一個Canny函數(shù)。

其實讀者們依次將筆者上述的所有函數(shù)復(fù)制并封裝到源文件和頭文件中使用即可
由于筆者比較懶,所以懶得封裝了。

后續(xù)有空的時候,筆者會將所有內(nèi)容封裝成一個壓縮包,放到付費文件里。
最后會將連接更新在文章的開頭。

如果覺得這篇文章寫得不錯的話,不妨點贊并收藏,謝謝閱讀。

參考文獻(xiàn)

Canny算子的原理參考了這篇文章:計算機(jī)視覺中Canny算子詳解。在本篇文章中筆者僅參考了計算公式和整體思路。C語言代碼純靠手搓。文章來源地址http://www.zghlxwxcb.cn/news/detail-859416.html

到了這里,關(guān)于Canny算子邊緣檢測原理講解及其完整C語言實現(xiàn)(不使用opencv)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • python實現(xiàn)Canny算子邊緣檢測算法

    python實現(xiàn)Canny算子邊緣檢測算法

    ? ? 邊緣檢測是一種將圖片中關(guān)鍵信息表現(xiàn)出來的一種圖片技術(shù),它的結(jié)果并不是字面意思上的獲取圖片邊緣,而是將圖片有用的信息勾勒出來,類似素描的結(jié)果,但是已經(jīng)去掉了很多信息。如下所示,一張原始的圖片是這樣的: ? ?? ? ? 通過邊緣檢測算法,我們最終得到

    2024年02月08日
    瀏覽(23)
  • OpenCV 筆記(12):常用的邊緣檢測算子—— Canny

    OpenCV 筆記(12):常用的邊緣檢測算子—— Canny

    一階導(dǎo)數(shù)、二階導(dǎo)數(shù)的邊緣算子雖然簡單易用,但存在一些缺點。例如容易受噪聲影響,容易產(chǎn)生虛假邊緣。 John F. Canny 在 1986 年提出了 Canny 邊緣檢測算法。它是結(jié)合了梯度計算方法和非極大值抑制技術(shù)的一種邊緣檢測算法。該算法克服了之前的邊緣檢測算法在抑制噪聲和保

    2024年02月03日
    瀏覽(20)
  • 我在Vscode學(xué)OpenCV 圖像處理三(圖像梯度--邊緣檢測【圖像梯度、Sobel 算子、 Scharr 算子、 Laplacian 算子、Canny 邊緣檢測】)

    我在Vscode學(xué)OpenCV 圖像處理三(圖像梯度--邊緣檢測【圖像梯度、Sobel 算子、 Scharr 算子、 Laplacian 算子、Canny 邊緣檢測】)

    這里需要區(qū)分開邊緣檢測和輪廓檢測 邊緣檢測并非萬能,邊緣檢測雖然能夠檢測出邊緣,但邊緣是不連續(xù)的,檢測到的邊緣并不是一個整體。圖像輪廓是指將邊緣連接起來形成的一個整體,用于后續(xù)的計算。 OpenCV 提供了查找圖像輪廓的函數(shù) cv2.findContours(),該函數(shù)能夠查找圖

    2024年02月04日
    瀏覽(28)
  • Halcon邊緣檢測Sobel、Laplace和Canny算子

    Halcon邊緣檢測Sobel、Laplace和Canny算子

    提示:文章參考了網(wǎng)絡(luò)上其他作者的文章,以及相關(guān)書籍,如有侵權(quán),請聯(lián)系作者。 ???????除了閾值分割外,也可以通過檢測區(qū)域的邊緣得到目標(biāo)區(qū)域。區(qū)域的邊緣像素的灰度值往往會發(fā)生灰度上的突變,針對這些跳躍性的突變進(jìn)行檢測和計算,可以得到區(qū)域的邊緣輪廓

    2023年04月08日
    瀏覽(21)
  • OpenCV 入門教程:Laplacian算子和Canny邊緣檢測

    邊緣檢測在圖像處理和計算機(jī)視覺領(lǐng)域中起著重要的作用。 Laplacian 算子和 Canny 邊緣檢測是兩種常用的邊緣檢測方法,它們能夠幫助我們準(zhǔn)確地檢測圖像中的邊緣信息。 OpenCV 提供了這

    2024年02月13日
    瀏覽(21)
  • Halcon經(jīng)典的邊緣檢測算子Sobel/Laplace/Canny

    關(guān)于邊緣檢測,有許多經(jīng)典的算子,各大圖形處理庫都有各自的邊緣檢測算子,這里簡要介紹幾種。 Sobel算子結(jié)合了高斯平滑和微分求導(dǎo)。它是一階導(dǎo)數(shù)的邊緣檢測算子,使用卷積核對圖像中的每個像素點做卷積和運算,然后采用合適的閾值提取邊緣。Soble算子有兩個卷積核

    2024年01月22日
    瀏覽(21)
  • python --opencv圖像處理Canny算子邊緣檢測(Roberts算子、Prewitt算子、Sobel算子、Laplacian算子、Scharr 算子、 LOG 算子)

    python --opencv圖像處理Canny算子邊緣檢測(Roberts算子、Prewitt算子、Sobel算子、Laplacian算子、Scharr 算子、 LOG 算子)

    邊緣檢測是基于灰度突變來分割圖像的常用方法,其實質(zhì)是提取圖像中不連續(xù)部分的特征。目前常見邊緣檢測算子有差分算子、 Roberts 算子、 Sobel 算子、 Prewitt 算子、 Log 算子以及 Canny 算子等。 其中, Canny 算子是由計算機(jī)科學(xué)家 John F. Canny 于 1986 年提出的一種邊緣檢測算子

    2024年04月12日
    瀏覽(26)
  • 【canny邊緣檢測】canny邊緣檢測原理及代碼詳解

    【canny邊緣檢測】canny邊緣檢測原理及代碼詳解

    本文通過介紹canny邊緣檢測原理與代碼解析,希望能讓大家深入理解canny邊緣檢測 canny邊緣檢測主要分為4個部分,本文分別從每一個部分進(jìn)行解析并附代碼。 圖像降噪 梯度計算 非極大值抑制 雙閾值邊界跟蹤 圖像去噪是進(jìn)行邊緣檢測的第一步,通過去噪可以去除圖像中的一些

    2024年02月10日
    瀏覽(18)
  • OpenCV—拉普拉斯算子(Laplacian)邊緣檢測:原理與實現(xiàn)

    OpenCV—拉普拉斯算子(Laplacian)邊緣檢測:原理與實現(xiàn)

    目錄 介紹 拉普拉斯算子的作用 拉普拉斯算子的原理 使用OpenCV實現(xiàn)拉普拉斯算子 完整代碼展示 結(jié)論 拉普拉斯算子是一種常用于圖像處理的邊緣檢測技術(shù),它有助于識別圖像中的邊緣和紋理特征。本文將深入探討拉普拉斯算子的原理,以及如何使用OpenCV實現(xiàn)它。 ???????

    2024年02月04日
    瀏覽(90)
  • 使用c++視覺處理----canny 邊緣檢測、sobel邊緣檢測、scharr 濾波邊緣檢測

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包