“邊緣檢測是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。 這些包括(i)深度上的不連續、(ii)表面方向不連續、(iii)物質屬性變化和(iv)場景照明變化。 邊緣檢測是圖像處理和計算機視覺中,尤其是特征提取中的一個研究領域。圖像邊緣檢測大幅度地減少了數據量,並且剔除了可以認為不相關的信息,保留了圖像重要的結構屬性。常用的邊緣檢測模板有Laplacian算子、Roberts算子、Sobel算子、log(Laplacian-Gauss)算子、Kirsch算子和Prewitt算子等。”
——百度百科《邊緣檢測》
Sobel算子是常用的邊緣檢測模板,算法比較簡單,實際應用中效率比 canny 邊緣檢測效率要高,但是邊緣不如 Canny 檢測的准確,但是很多實際應用的場合,Sobel 邊緣卻是首選,尤其是對效率要求較高,而對細紋理不太關心的時候。在技術上,Sobel 算子是一離散性差分算子,用來運算圖像亮度函數的灰度之近似值。在圖像的任何一點使用此算子,將會產生對應的灰度矢量或是其法矢量。Sobel 邊緣檢測通常帶有方向性,可以只檢測豎直邊緣或垂直邊緣或都檢測。
Sobel邊緣檢測的核心在於像素矩陣的卷積,卷積對於數字圖像處理非常重要,很多圖像處理算法都是做卷積來實現的。卷積運算的本質就是對指定的圖像區域的像素值進行加權求和的過程,其計算過程為圖像區域中的每個像素值分別與卷積模板的每個元素對應相乘,將卷積的結果作求和運算,運算到的和就是卷積運算的結果。矩陣的卷積公式如下:
......我也沒怎么看明白這公式,繼續往下看吧。
一、實現步驟
1、Sobel 提供了水平方向和垂直方向兩個方向的濾波模板。設 x 方向和 y 方向的卷積因子分別為 Gx 和 Gy,模板如下所示,A為原圖像。
2、矩陣運算后,就得到橫向灰度值 Gx 和縱向灰度值 Gy,然后通過如下公式進行計算出該點的灰度值:
這個運算比較復雜,開方又開根的,可以取近視值替代,影響也不大:
3、最后,設置一個閾值 value,對數據進行比較然后輸出二值圖像:
很多書上說的計算梯度方向,其實就是上面的 |G|。
二、MATLAB實現
clc; clear all; close all; RGB = imread('Lenna.jpg'); %讀取圖片 gray = double(rgb2gray(RGB)); %灰度圖 [ROW,COL, DIM] = size(gray); %得到圖像行列數 value = 120; %閾值設置 Sobel_img = zeros(ROW,COL); for r = 2:ROW-1 for c = 2:COL-1 Gx = gray(r-1,c+1) + 2*gray(r,c+1) + gray(r+1,c+1) - gray(r-1,c-1) - 2*gray(r,c-1) - gray(r+1,c-1); Gy = gray(r-1,c-1) + 2*gray(r-1,c) + gray(r-1,c+1) - gray(r+1,c-1) - 2*gray(r+1,c) - gray(r+1,c+1); G = abs(Gx) + abs(Gy); %G = sqrt(Gx^2 + Gy^2); if(G > value) Sobel_img(r,c)=0; else Sobel_img(r,c)=255; end end end subplot(2,1,1); imshow(RGB); title('原圖'); subplot(2,1,2); imshow(Sobel_img);title('Sobel');
點擊運行,得到如下結果:
三、FPGA實現
1、形成3x3矩陣
這個在前面的博客花了3篇來解釋,就不多說了,我把3x3矩陣的代碼用一個專門的 .v 文件寫好,這里直接調用即可。輸入是灰度數據,即 YCbCr格式中的 8bit Y分量,輸出是矩陣數據。耗費 1 個時鍾周期。
//========================================================================== //== matrix_3x3_8bit,生成3x3矩陣,輸入和使能需對齊,耗費1clk //========================================================================== //--------------------------------------------------- 矩陣順序 // {matrix_11, matrix_12, matrix_13} // {matrix_21, matrix_22, matrix_23} // {matrix_31, matrix_32, matrix_33} //--------------------------------------------------- 模塊例化 matrix_3x3_8bit #( .COL (480 ), .ROW (272 ) ) u_matrix_3x3_8bit ( .clk (clk ), .rst_n (rst_n ), .din_vld (Y_de ), .din (Y_data ), .matrix_11 (matrix_11 ), .matrix_12 (matrix_12 ), .matrix_13 (matrix_13 ), .matrix_21 (matrix_21 ), .matrix_22 (matrix_22 ), .matrix_23 (matrix_23 ), .matrix_31 (matrix_31 ), .matrix_32 (matrix_32 ), .matrix_33 (matrix_33 ) );
2、Sobel算子
按上面步驟進行流水線計算即可,value的值可以直接寫參數表示,可以直接寫數字,也可以外部信號引入。耗費 3 個時鍾周期。
//sobel --------------------------------------------- reg [ 9:0] Gx1,Gx3,Gy1,Gy3,Gx,Gy ; reg [10:0] G ; //========================================================================== //== Sobel處理,耗費3clk //========================================================================== //clk1:Gx1、Gx3和Gy1、Gy3 //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin Gx1 <= 'd0; Gx3 <= 'd0; Gy1 <= 'd0; Gy3 <= 'd0; end else begin Gx1 <= matrix_11 + (matrix_21 << 1) + matrix_31; Gx3 <= matrix_13 + (matrix_23 << 1) + matrix_33; Gy1 <= matrix_11 + (matrix_12 << 1) + matrix_13; Gy3 <= matrix_31 + (matrix_32 << 1) + matrix_33; end end //clk2:Gx和Gy絕對值 //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin Gx <= 'd0; Gy <= 'd0; end else begin //也可判斷bit[7]來確定 Gx <= (Gx1 > Gx3) ? (Gx1 - Gx3) : (Gx3 - Gx1); Gy <= (Gy1 > Gy3) ? (Gy1 - Gy3) : (Gy3 - Gy1); end end //clk3:Gx+Gy //--------------------------------------------------- always @(posedge clk or negedge rst_n) begin if(!rst_n) begin G <= 'd0; end else begin G <= Gx + Gy; end end assign sobel_data = (G > value) ? 16'h0000 : 16'hffff;
3、信號同步
形成 3x3 矩陣耗費 1clk,Sobel 算子耗費 3clk,因此行場和使能信號都需要延遲 4 拍。
//========================================================================== //== 信號同步 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) begin Y_de_r <= 4'b0; Y_hsync_r <= 4'b0; Y_vsync_r <= 4'b0; end else begin Y_de_r <= {Y_de_r[2:0], Y_de}; Y_hsync_r <= {Y_hsync_r[2:0], Y_hsync}; Y_vsync_r <= {Y_vsync_r[2:0], Y_vsync}; end end assign sobel_de = Y_de_r[3]; assign sobel_hsync = Y_hsync_r[3]; assign sobel_vsync = Y_vsync_r[3];
四、上板驗證
我的 value 閾值由外部按鍵給出,當閾值為120時,效果如下:
視頻演示如下:
同樣的,可以移植到 OV7670 攝像頭工程中,如下所示:
[1] OpenS Lee:FPGA開源工作室(公眾號)
[2] CrazyBingo:基於VIP_Board Mini的FPGA視頻圖像算法(HDL-VIP)開發教程-V1.6
[3] NingHechuan:FPGA圖像處理教程
[4] 牟新剛、周曉、鄭曉亮.基於FPGA的數字圖像處理原理及應用[M]. 電子工業出版社,2017.
[5] 張錚, 王艷平, 薛桂香. 數字圖像處理與機器視覺[M]. 人民郵電出版社, 2010.