所谓边缘是指其周围像素灰度急剧变化的那些象素的集合,它是图像最基本的特征。边缘存在于目标、背景和区域之间,所以,它是图像分割所依赖的最重要的依据。由于边缘是位置的标志,对灰度的变化不敏感,,因此,边缘也是图像匹配的重要的特征。边缘检测和区域划分是图像分割的两种不同的方法,二者具有相互补充的特点。在边缘检测中,是提取图像中不连续部分的特征,根据闭合的边缘确定区域。而在区 域划分中,是把图像分割成特征相同的区域,区域之间的边界就是边缘。由于边缘检测方法不需要将图像逐个像素地分割,因此更适合大图像的分割。边缘大致可以分为两种,一种是阶跃状边缘,边缘两边像素的灰度值明显不同;另一种为屋顶状边缘,边缘处于灰度值由小到大再到小的变化转折点处。边缘检测的主要工具是边缘检测模板。sobel算子是边缘检测有效的算子,主要是以下这两个矩阵,Gx=AxP,Gy=AyP。意思就是说,P矩阵是图像的3*3矩阵,然后用图像矩阵的每个像素分别去乘Ax和Ay的每个像素。Ax是行边缘检测模块,Gx代表的意思就是P矩阵右边行减去左边行,得到一个阈值,Gy也是同理。
得到了Gx和Gy之后,相当于是得到了行边缘阈值以及列边缘阈值,计算两者的平方和G,如果G是大于设定的阈值,则表示这是边缘,否则就不是。
在对图像进行边缘检测之前需要将图像转换成灰度的,这在前面以净实现。此外,sobel边缘检测的前提同样也是需要对3×3图像矩阵进行操作,对于如何得到3×3图像的矩阵,在上一节中也实现了。接下来是实现sobel边缘检测的步骤。
sobel边缘检测的第一步是计算竖直方向梯度和水平方向梯度,实现的代码如下图所示。其实就是对像素进行运算,之后判断正负,然后相减得到梯度。这一步骤消耗了一个时钟周期。
//---------------------------------------------------
// sobel edge function
//---------------------------------------------------
// step1:calculate the Gx and Gy
// [-1 0 1] [P1 P2 P3] [ 1 2 1] [P1 P2 P3]
//Gx=[-2 0 2]*[P4 P5 P6] GY=[ 0 0 0]*[P4 P5 P6]
// [-1 0 1] [P7 P8 P9] [-1 -2 -1] [P7 P8 P9]
//a clock to finish
reg [9:0] Gx1;
reg [9:0] Gx2;
reg [9:0] Gy1;
reg [9:0] Gy2;
wire [9:0] Gx;
wire [9:0] Gy;
assign Gx=(Gx1>Gx2)? (Gx1-Gx2):(Gx2-Gx1);
assign Gy=(Gy1>Gy2)? (Gy1-Gy2):(Gy2-Gy1);
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
Gx1<=10'd0;
Gx2<=10'd0;
Gy1<=10'd0;
Gy2<=10'd0;
end
else begin
Gx1<=matrix_p11+matrix_p21+matrix_p21+matrix_p31;
Gx2<=matrix_p13+matrix_p23+matrix_p23+matrix_p33;
Gy1<=matrix_p11+matrix_p12+matrix_p12+matrix_p13;
Gy2<=matrix_p31+matrix_p32+matrix_p32+matrix_p33;
end
end
在得到了梯度后,需要计算其平方和,相关代码如下很简单,消耗一个时钟周期的时间。
// step2:cal Gx^2 and Gy^2 and add all
//G2=Gx^2+Gy^2
//a clk delay to finish
reg [19:0] G2;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
G2<=20'd0;
end
else begin
G2<=Gx*Gx+Gy*Gy;
end
end
得到了梯度的平方和之后,需要对其开平方运算,开平方运算这里使用到了vivado的一个ip核cordic。相关的配置信息如下图所示。一个比较关键的信息是latency延迟了6个周期。
创建好了开平方模块后,对其进行例化操作,如下所示。
// step3:cal sqrt G
//G=sqrt(G2)
//input width 20
//output width 11
//delay 6 clk
wire [10:0] G;
wire dout_valid;
cordic_0 u_cordic_0 (
.aclk(clk),
.aresetn(rst_n),
.s_axis_cartesian_tvalid(1'b1),
.s_axis_cartesian_tdata(G2),
.m_axis_dout_tvalid(dout_valid),
.m_axis_dout_tdata(G)
);
在得到了梯度G之后,通过G与阈值进行比较,当G大于阈值那么输出1,当G小于阈值则输出0。代码如下,消耗了一个时钟周期。
// step4:compare with threshold data
//if G<SOBEL_THRESHOLD then 0
//if G>SOBEL_THRESHOLD then 1
//a clk to finish
reg data_sobel;
parameter SOBEL_THRESHOLD=8'd50;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
data_sobel<=1'b0;
end
else begin
if(G>SOBEL_THRESHOLD)begin
data_sobel<=1'b1;
end
else data_sobel<=1'b0;
end
end
由于完成整个sobel边缘检测消耗了10个时钟周期,因此,这里将其延迟了9拍来做信号的同步。
//-----------------------------
// signal synchronization
//-----------------------------
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
post_clken_dy<=9'd0;
post_href_dy<=9'd0;
post_vsync_dy<=9'd0;
end
else begin
post_clken_dy<={post_clken_dy[7:0],matrix_frame_clken};
post_href_dy<={post_href_dy[7:0],matrix_frame_href};
post_vsync_dy<={post_vsync_dy[7:0],matrix_frame_vsync};
end
end
assign post_clken=post_clken_dy[8];
assign post_href=post_href_dy[8];
assign post_vsync=post_vsync_dy[8];
assign post_data={24{data_sobel}};
下面这张图是sobel边缘检测的仿真图,左边上面是当前周期的3×3图像矩阵,然后一个时钟周期后计算得到Gx1=773、Gx2=885、Gy1=543以及Gy2=1019。之后Gx=112和Gy=476同步得到。在一个时钟周期之后,梯度平方和G2=239120被得到。之后在经过6个周期后,平方根G=488被得到。可见,sobel边缘检测代码是对的。
下图给出了sobel边缘检测前的图和之后的图。可以看到,仿真结果成功提取了图片的边缘信息。通信行程卡居然提示违规hhh,只好纯色填充了。
相关的block design如下图所示,只是在原来的灰度显示基础上添加了sobel边缘检测模块。之后进行综合布局布线生成比特流文件。验证结果表明可以检测图片的边缘。后期我会整理下代码然后开源这部分的vivado工程和代码。