在紫光PGL50H上简单实现图像Sobel边缘检测。
01 软硬件平台
软件平台:PDS_2022.1
硬件平台:小眼睛科技盘古50K开发板
02 Sobel边缘检测算法
Sobel是图像边缘检测中的一种算法,Sobel算子分为横向和纵向3×3的卷积核,两个卷积核分别与图像作平面卷积,提取水平边缘和垂直边缘,Gx和Gy分别代表横向和纵向边缘检测的图像数据,其公式如下:
![image.png](//file1.elecfans.com/web2/M00/A9/B8/wKgZomUooquANVy2AABwx5Esahk428.png)
最后每一个像素点的Gx和Gy使用以下公式结合,便得到边缘信息的图像。
![image.png](//file1.elecfans.com/web2/M00/A9/B9/wKgZomUopa-ADfOBAAAUYIS2NfA479.png)
由于Sobel算子是平面图像的滑动卷积,使用FPGA实现是相对容易的,且具有高效率。FPGA中实现复杂的数学运算是不好处理的,G采用不开平方的近似公式实现:
![image.png](//file1.elecfans.com/web2/M00/A7/F5/wKgaomUophWAK2yQAAANKo9WXkk482.png)
03 程序设计
本设计不涉及更复杂的图像处理,所以并没有将图像数据存入DDR后再输出,一般涉及复杂或者多任务的图像处理,将图像数据存入DDR是必要的。本设计基于小眼睛科技提供的 HDMI 回环实验,对输入的RGB像素值首先转灰度,转灰度后进行二值化。然后将数据存入FIFO1,在一帧中的第二行开始,将FIFO1的数据读出,并且存入FIFO2,在一帧中的第三行开始,将FIFO2的数据读出,流水实现,当场同步时,复位FIFO1和FIFO2,避免第1行和尾行像素无效的问题。之后,将输入的像素数据和FIFO1、FIFO2输出的数据同时流水寄存三拍,形成3×3的处理像素矩阵,按照Sobel公式实现即可。最后将vs、hs、de流水延迟,与最后的输出数据对齐就可以了。
module hdmi_sobel_test(
input wire sys_clk, // input system clock 50MHz
output rstn_out,
output iic_scl,
inout iic_sda,
output iic_tx_scl,
inout iic_tx_sda,
input pixclk_in,
input vs_in,
input hs_in,
input de_in,
input [7:0] r_in,
input [7:0] g_in,
input [7:0] b_in,
output pixclk_out,
output reg vs_out,
output reg hs_out,
output reg de_out,
output reg [7:0] r_out,
output reg [7:0] g_out,
output reg [7:0] b_out,
output led_int
);
reg [15:0] rstn_1ms ;
wire pix_clk ;
wire cfg_clk ;
wire locked ;
pll u_pll(
.clkin1 (sys_clk ), // input//50MHz
.pll_lock (locked ), // output
.clkout0 (cfg_clk ) // output//10MHz
);
ms72xx_ctl ms72xx_ctl(
.clk ( cfg_clk ), //input clk,
.rst_n ( rstn_out ), //input rstn,
.init_over ( init_over ), //output init_over,
.iic_tx_scl ( iic_tx_scl ), //output iic_scl,
.iic_tx_sda ( iic_tx_sda ), //inout iic_sda
.iic_scl ( iic_scl ), //output iic_scl,
.iic_sda ( iic_sda ) //inout iic_sda
);
assign led_int = init_over;
assign rstn_out = (rstn_1ms == 16'h2710);
assign pixclk_out = pixclk_in;
always @(posedge cfg_clk) begin
if(!locked) rstn_1ms <= 16'd0;
else if(rstn_1ms == 16'h2710) rstn_1ms <= rstn_1ms;
else rstn_1ms <= rstn_1ms + 1'b1;
end
reg [7:0] gray ;
reg [7:0] value ;
reg [7:0] vs_temp ;
reg [7:0] hs_temp ;
reg [7:0] de_temp ;
wire de_pos ;
wire de_neg ;
reg [10:0] de_pos_cnt ;
reg [7:0] data0_reg0 ;
reg [7:0] data0_reg1 ;
reg [7:0] data0_reg2 ;
reg [7:0] data1_reg0 ;
reg [7:0] data1_reg1 ;
reg [7:0] data1_reg2 ;
reg [7:0] data2_reg0 ;
reg [7:0] data2_reg1 ;
reg [7:0] data2_reg2 ;
reg [9:0] x_data_p ;
reg [9:0] x_data_n ;
reg [9:0] y_data_p ;
reg [9:0] y_data_n ;
reg [9:0] Gx ;
reg [9:0] Gy ;
reg [7:0] s_data ;
reg row1_we ;
reg row1_re ;
reg [7:0] row1_wdata ;
reg row2_we ;
reg row2_re ;
reg [7:0] row2_wdata ;
wire [7:0] row1_rdata ;
wire [7:0] row2_rdata ;
always @(posedge pixclk_in) begin
if(!init_over)
gray <= 8'd0;
else
gray <= (r_in*306 + g_in*601 + b_in*117)>>10;
end
always @(posedge pixclk_in) begin
if(!init_over)
value <= 8'h00;
else if(gray >= 8'd160)
value <= 8'hff;
else
value <= 8'h00;
end
always @(posedge pixclk_in) begin
vs_temp <= {vs_temp[6:0], vs_in};
hs_temp <= {hs_temp[6:0], hs_in};
de_temp <= {de_temp[6:0], de_in};
end
always @(posedge pixclk_in) begin
if(!init_over)begin
vs_out <= 1'b0 ;
hs_out <= 1'b0 ;
de_out <= 1'b0 ;
r_out <= 8'b0 ;
g_out <= 8'b0 ;
b_out <= 8'b0 ;
end
else begin
vs_out <= vs_temp[7] ;
hs_out <= hs_temp[7] ;
de_out <= de_temp[7] ;
r_out <= s_data ;
g_out <= s_data ;
b_out <= s_data ;
end
end
assign de_pos = de_in & !de_temp[0];
assign de_neg = !de_in & de_temp[0];
always @(posedge pixclk_in) begin
if(!init_over) de_pos_cnt <= 11'd0;
else if(vs_in) de_pos_cnt <= 11'd0;
else if(de_pos) de_pos_cnt <= de_pos_cnt + 1'b1;
end
always @(posedge pixclk_in) begin
data0_reg0 <= value ;
data0_reg1 <= data0_reg0;
data0_reg2 <= data0_reg1;
data1_reg0 <= row1_rdata;
data1_reg1 <= data1_reg0;
data1_reg2 <= data1_reg1;
data2_reg0 <= row2_rdata;
data2_reg1 <= data2_reg0;
data2_reg2 <= data2_reg1;
end
always @(posedge pixclk_in) begin
if(!init_over) begin
x_data_p <= 10'd0;
x_data_n <= 10'd0;
y_data_p <= 10'd0;
y_data_n <= 10'd0;
end
else if(de_temp[4]) begin
x_data_p <= data0_reg2 + (data1_reg2 << 1) + data2_reg2;
x_data_n <= data0_reg0 + (data1_reg0 << 1) + data2_reg0;
y_data_p <= data2_reg0 + (data2_reg1 << 1) + data2_reg2;
y_data_n <= data0_reg0 + (data0_reg1 << 1) + data0_reg2;
end
else begin
x_data_p <= 10'd0;
x_data_n <= 10'd0;
y_data_p <= 10'd0;
y_data_n <= 10'd0;
end
end
always @(posedge pixclk_in) begin
if(!init_over)begin
Gx <= 10'd0;
Gy <= 10'd0;
end
else begin
Gx <= (x_data_p >= x_data_n) ? (x_data_p - x_data_n) : (x_data_n - x_data_p);
Gy <= (y_data_p >= y_data_n) ? (y_data_p - y_data_n) : (y_data_n - y_data_p);
end
end
always @(posedge pixclk_in)
if(!init_over) s_data <= 8'h00;
else if((Gx+Gy) > 10'h1f0) s_data <= 8'hff;
else s_data <= 8'h00;
always @(posedge pixclk_in) begin
if(!init_over) begin
row1_we <= 1'b0;
row1_wdata <= 8'd0;
end
else if(de_temp[1]) begin
row1_we <= 1'b1;
row1_wdata <= value;
end
else begin
row1_we <= 1'b0;
row1_wdata <= 8'd0;
end
end
always @(posedge pixclk_in) begin
if(!init_over) begin
row2_we <= 1'b0;
row2_wdata <= 8'd0;
end
else if(de_temp[1] & de_pos_cnt > 1) begin
row2_we <= 1'b1;
row2_wdata <= row1_rdata;
end
else begin
row2_we <= 1'b0;
row2_wdata <= 8'd0;
end
end
always @(posedge pixclk_in)
if(!init_over) row1_re <= 1'b0;
else if(de_pos_cnt < 2) row1_re <= 1'b0;
else if(de_temp[0]) row1_re <= 1'b1;
else row1_re <= 1'b0;
always @(posedge pixclk_in)
if(!init_over) row2_re <= 1'b0;
else if(de_pos_cnt < 3) row2_re <= 1'b0;
else if(de_temp[0]) row2_re <= 1'b1;
else row2_re <= 1'b0;
fifo_8_2048 row1_fifo(
.clk (pixclk_in ),
.rst (vs_in ), //帧同步复位fifo
.wr_en (row1_we ),
.wr_data (row1_wdata ),
.wr_full ( ),
.almost_full ( ),
.rd_en (row1_re ),
.rd_data (row1_rdata ),
.rd_empty ( ),
.almost_empty ( )
);
fifo_8_2048 row2_fifo(
.clk (pixclk_in ),
.rst (vs_in ),
.wr_en (row2_we ),
.wr_data (row2_wdata ),
.wr_full ( ),
.almost_full ( ),
.rd_en (row2_re ),
.rd_data (row2_rdata ),
.rd_empty ( ),
.almost_empty ( )
);
endmodule
04 边缘检测效果
Sobel边缘检测效果如下视频所示。