canny原理 传输门
滤波可以用高斯滤波、中值滤波。
第一步, 使用高斯滤波器(或中值滤波),以平滑图像,滤除噪声。
卷积核及假设像素如下
高斯滤波也分成3级流水线完成。
第一级流水线,计算每一行3个单元的加法,耗费一个时钟。
第二级流水线,计算3行的总和,耗费一个时钟。
其实接下来的canny算法应该称作伪canny算法,因为在计算过程中做了几个近似。
第二步,计算图像中每个像素点的梯度强度和方向。
参照Sobel边缘检测计算梯度的公式,得到梯度的幅值。梯度方向的求取需要反三角函数,公式如下
1 对梯度方向的近似
Arctan(x) 对FPGA来说并不友好,在非极大值抑制的环节中只需要知道该像素的梯度方向在哪块区域就可以,并不需要判断出实际的角度。除非在非极大值抑制的环节需要对比较像素进行插值计算。本设计直接的区域做法就不会对比较像素进行插值计算了,直接比较该区域方向两端的梯度值。如水平、垂直、斜对角的两个像素,只要中心像素大于两端的两个像素,那么就保留中心像素。
图 1 表示从左上角第一个像素定义的X、Y坐标。原点定义在左上角
图2 表示 4个梯度方向区域分布
180度分成四个区域,每个区域占45°,另外的180°都是相对应的
对于梯度方向的近似,通过以下公式来观察近似区域和实际区域的差距。
在第1区域,以 21.8代替22.5 ,它们只相差0.7 。
在第3区域,以68.2 代替 67.5,它们只相差 0.7 。
G_x 、G_y为Sobel算子对x y方向做的差分和。
梯度方向有以下规律。
在第一个区域里G_x 、G_y不论正负比较绝对值,符合G_x> G_y * 2.414 ,那么缩小一点1区域的范围,做一点近似符合G_x> G_y2.5。
在第三个区域里G_x 、G_y不论正负比较绝对值,符合G_y> G_x * 2.414 ,那么缩小一点3区域的范围,做一点近似符合G_y> G_x2.5。
在第二个区域里,G_x 、G_y的绝对值不符合第一、三区域,但G_x 、G_y同号。
在第四个区域里,G_x 、G_y的绝对值不符合第一、三区域,但G_x 、G_y异号。
通过上述规律可以简单的通过G_x 、G_y的倍数关系以及符号去判断梯度方向。
1,通过在Sobel求G_x 、G_y 并判断它们的正负。
2,判断G_x 、G_y的倍数关系以及同异号情况。
3,通过第二步的结果去判断梯度方向落在哪块区域里。
第三步, 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
第一种做法
将梯度方向信息与梯度幅值合成,一起进入另一个由Shift Ram 构成的矩阵中,用中心像素梯度幅值去比较梯度方向两端像素的幅值,全部大于的标记该像素为真,否则标记为假。同时用中心像素的梯度幅值去与设定的高低阈值做比较,公式如下
H_T为高阈值,L_T为低阈值,G为梯度幅值。
第二种做法
采取近似将第三步与第四步融为一步
在双阈值和连通域中的近似
本来应该判断完极大值之后在进行双阈值的处理,而我为了不再用一个shift_ram构成3 X 3的矩阵,直接在进行非极大值抑制的那一步进行了双阈值处理,前提是认为一个像素的八连通域中有梯度值大于高阈值的像素,就认为这个像素有效(当然这个像素必须符合大于低阈值的要求)。
假定图像已经经历了灰度化和滤波处理,那么下一步就是取像素的梯度(幅值和方向),梯度算子用的是平常的sobel算子,梯度方向的求取按上文的近似判断。插一句嘴,fpga实现矩阵有几种方法,我用的是altera的Ip核 shift_ram
从图3中可知,缓存两行有两个tap,每一行有1024个单元,每个单元是8位。
图 4为Shift Ram 工作示意图,假定每行6个单元,缓存2行。每个时钟移位一次,Taps的输出和当前的输入构成矩阵的一列,将每个列向前移动,构成 3 X 3 的矩阵。
既然已经求出了像素梯度的幅值和方向了,为了实现第二个近似,还得去判断梯度幅值的高低情况,一个像素梯度经过开方器本来是10位,为了能够记录该像素的其他几个信息,再加4位表示4个方向,2位表示幅值的高低。这些信息必须和梯度的幅值融为一体能进入下面的环节,之后就要进行非极大值抑制的环节了,再次建立一个shift_ram构成一个3X3矩阵,一个时钟处理一个像素,根据梯度的值是否大于高阈值,是否大于低阈值且周围有高阈值的像素,以及该像素的方向,与当前矩阵的不同值相比较,去求得极大值。这种做法会有误判点,如果对边缘的要求很高的话,可以正常的双阈值处理。(也就是使极大值的点保持原值,非极大值全部置0,数据流在进入一个shift_ram,构成矩阵在去判断阈值大小)
实际效果如下,方法简单,结果也可以接受。
第四步,应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
借助第三步求得的像素真假与高低阈值比较的结果,将其合成再进入一个Shift Ram构成的矩阵,只判断像素为真的情况,在像素为真的条件下,考察Compare变量的值,像素为边缘点满足以下任意一个条件即可。
1、中心像素的Compare == 2 ’ b10 。
2、中心像素的Compare == 2’ b01 ,同时在其八连域中存在像素的Compare == 2’ b10 。
在rom里存200*200的图像,canny处理后在LCD上显示(fpga的M9K真的太少了…)
这是摄像头视频的canny取边缘,VGA显示 。30fps
猜想:这是用灰度值进行处理,如果是取多颜色聚合体的边缘,如魔方的边缘是用HSV空间的HS 与 V 分量的融合算法效果会好点呢,或者直接用RGB、HSV 3维向量的梯度。
新人小白,如有错误,恳请指出。
第一个代码有两个近似处理,第二个代码只做了第一个近似(梯度方向),完整的双域值效果要好些。
module canny
(
input clk,
input rst_s,
input [7:0] filter_out,
output reg [7:0] canny_out,
input filter_de,
input filter_hs,
input filter_vs,
output canny_hs,
output canny_vs,
output canny_de
);
//双阈值的高低阈值
parameter THRESHOLD_LOW = 10'd50;
parameter THRESHOLD_HIGH = 10'd100;
// shift_ram移位出口
wire [7:0] tap_0;
wire [7:0] tap_1;//输出端口
reg[9:0] Gx_1;//GX第一列计数
reg[9:0] Gx_3;
reg[9:0] Gy_1;
reg[9:0] Gy_3;
reg[10:0] Gx;//Gx Gy 做差分 求偏导
reg[10:0] Gy;
reg[20:0] sqrt_in;//计算梯度值的两个平方和
reg[9:0] sqrt_out;//开平方得到的梯度
reg[10:0] sqrt_rem;//开平方的余数
wire [20:0] sqrt_in_n;
wire [9:0] sqrt_out_n;
wire [10:0] sqrt_rem_n;
//对filter——de hs vs延迟
reg [5:0]hs_buf;
reg [5:0]vs_buf;
reg [5:0]de_buf;
wire sobel_de;
wire sobel_hs;
wire sobel_vs;
//9X9矩阵 sobel算子用
reg [7:0] ma1_1;
reg [7:0] ma1_2;
reg [7:0] ma1_3;
reg [7:0] ma2_1;
reg [7:0] ma2_2;
reg [7:0] ma2_3;
reg [7:0] ma3_1;
reg [7:0] ma3_2;
reg [7:0] ma3_3;
//记录行上升沿,可以设置前两行全为8'h00,也可以随其自然
reg edge_de_a;
reg edge_de_b;
wire edge_de;
reg [9:0] row_cnt;
//-----非极大值抑制----
reg[1:0] sign;//Gx Gy 正 负
reg type; // Gx Gy 异号 同号
reg path_one;
wire path_two;
reg path_thr;
wire path_fou;//四个梯度方向
wire start;//判断,;xy轴方向有没有选中
reg [15:0] gra_path;//梯度幅值+方向+高低阈值状态
//--非极大值的ram出口
wire [15:0] tap_2;
wire [15:0] tap_3;
// 9x9矩阵,非极大值抑制用
reg [15:0] max1_1;
reg [15:0] max1_2;
reg [15:0] max1_3;
reg [15:0] max2_1;
reg [15:0] max2_2;
reg [15:0] max2_3;
reg [15:0] max3_1;
reg [15:0] max3_2;
reg [15:0] max3_3;
//对sobel de hs vs 延迟3拍
reg [2:0]de_buf_n;
reg [2:0]hs_buf_n;
reg [2:0]vs_buf_n;
//case四个方向选择
wire [3:0] path_se;
wire search;//八连通域判断是否有大于高阈值的点
shift_ram shift_ram_inst (
.aclr ( ~filter_vs),
.clock ( clk),
.clken ( filter_de),
.shiftin ( filter_out ),//输入端口 第三行
.shiftout (),//和tap——1一样的输出
.taps0x ( tap_0 ),//第二行
.taps1x ( tap_1 )//第一行
);
//对矩阵第一行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma1_1,ma1_2,ma1_3} <= 24'd0;
else if (filter_de)
{ma1_1,ma1_2,ma1_3} <= {ma1_2,ma1_3,tap_1};
else
{ma1_1,ma1_2,ma1_3} <= {ma1_1,ma1_2,ma1_3};
end
//对矩阵第二行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma2_1,ma2_2,ma2_3} <= 24'd0;
else if (filter_de)
{ma2_1,ma2_2,ma2_3} <= {ma2_2,ma2_3,tap_0};
else
{ma2_1,ma2_2,ma2_3} <= {ma2_1,ma2_2,ma2_3};
end
//对矩阵第3行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma3_1,ma3_2,ma3_3} <= 24'd0;
else if (filter_de)
{ma3_1,ma3_2,ma3_3} <= {ma3_2,ma3_3,filter_out};
else
{ma3_1,ma3_2,ma3_3} <= {ma3_1,ma3_2,ma3_3};
end
//----------------Sobel Parameter--------------------------------------------
// Gx Gy Pixel
// [+1 0 -1] [+1 +2 +1] [ma1_1 ma1_2 ma1_3]
// [+2 0 -2] [ 0 0 0] [ma2_1 ma2_2 ma2_3]
// [+1 0 -1] [-1 -2 -1] [ma3_1 ma3_2 ma3_3]
//-------------------------------------------------------------
//将GX两列Gy 2列行先加 第一级流水线
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gx_1 <= 10'd0;
Gx_3 <= 10'd0;
end
else
begin
Gx_1 <= {2'b00,ma1_1} + {1'b0,ma2_1,1'b0} +{2'b0,ma3_1};
Gx_3 <= {2'b00,ma1_3} + {1'b0,ma2_3,1'b0} +{2'b0,ma3_3};
end
end
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gy_1 <= 10'd0;
Gy_3 <= 10'd0;
end
else
begin
Gy_1 <= {2'b00,ma1_1} + {1'b0,ma1_2,1'b0} +{2'b0,ma1_3};
Gy_3 <= {2'b00,ma3_1} + {1'b0,ma3_2,1'b0} +{2'b0,ma3_3};
end
end
//---Gx1 Gx3;Gy1 Gy3 做差 差分 xy方向的偏导 再判断GX GY的正负 第二级
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gx <= 11'd0;
Gy <= 11'd0;
sign <= 2'b00;
end
else
begin
Gx <= (Gx_1 >= Gx_3)? Gx_1 - Gx_3 : Gx_3 - Gx_1;
Gy <= (Gy_1 >= Gy_3)? Gy_1 - Gy_3 : Gy_3 - Gy_1;
sign[0] <= (Gx_1 >= Gx_3)? 1'b1 : 1'b0;//判断GX Gy 正负,1 正 0 负
sign[1] <= (Gy_1 >= Gy_3)? 1'b1 : 1'b0;
end
end
//第三级 平方和 + GX、GY异同号?+ GX GY 大小级别 + 梯度方向
//求 Gx^2 Gy^2,提供给开方Ip计算梯度, //梯度的方向就是函数f(x,y)在这点增长最快的方向,梯度的模为方向导数的最大值。
// 梯度的摸 = (Gx^2 + Gy^2)开平方
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
sqrt_in <= 21'd0;
else
sqrt_in <= Gx*Gx + Gy*Gy;
end
assign sqrt_in_n = sqrt_in;
//对Gx Gy 正负的情况做分类 两类 异号 1 同号 0
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
type <= 1'b0;
else if (sign[0]^sign[1])
type <= 1'b1;
else
type <= 1'b0;
end
// 对 GX GY 大小级别做判断,也就是 GX > GY*2.5 ? Gy > GX*2.5?
// 符合 GX > GY*2.5 必定为x轴方向
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
path_one <= 1'b0;
else if(Gx > (Gy + Gy + Gy[10:1]))
path_one <= 1'b1;
//这里有个失误点,本来Gx Gy是10位,但对于GY*2.5 超过1023时,只取低10位,进位消失,该if成立,就会出现XY轴同时为1
else
path_one <= 1'b0;
end
// 符合 Gy > Gx*2.5 必定为y轴方向
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
path_thr <= 1'b0;
else if(Gy > (Gx + Gx + Gx[10:1]))
path_thr <= 1'b1;
else
path_thr <= 1'b0;
end
// 判断完 x y 轴方向 再判断两个对角方向
// 由于坐标轴原点在左上角 -------> x
// |
// |
// y|
// 同号 为 \ 异号为 / (当然得在 X Y 轴 都不是的情况下)
assign start = (path_one | path_thr)? 1'b0 : 1'b1;
assign path_two = (start) ? type : 1'b0;
assign path_fou = (start) ? ~type: 1'b0;
//开方IP组合逻辑,花的时间很少,送进去马上就得出数据,在下一个时钟赋给输出
sqrt sqrt_inst (
.radical ( sqrt_in_n ),
.q ( sqrt_out_n ),
.remainder ( sqrt_rem_n )
);
//第四级
//开方得到梯度,再加上4个方向gra_path[13:10]
//提前对梯度进行双域值判断,以防进行非极大值抑制后又要来一个3X3的shift_ram
//意图在进行非极大值抑制的时候进行八连通域分析
//其实是伪双阈值,不再去判断大于高阈值的是不是极大值点,只要中间像素(小于高,大于低)周围
//有大于高阈值的点,就认为中间像素有效
//gra_path[15:14]高低阈值,gra_path[13:10]四个方向,gra_path[9:0]梯度幅值
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
gra_path <= 16'd0;
else if (sqrt_out_n > THRESHOLD_HIGH)
gra_path <= {1'b1,1'b0,path_fou,path_thr,path_two,path_one,sqrt_out_n};
else if (sqrt_out_n > THRESHOLD_LOW)
gra_path <= {1'b0,1'b1,path_fou,path_thr,path_two,path_one,sqrt_out_n};
else
gra_path <= 16'd0;
end
//对 hs vs de 进行适当的延迟,匹配VGA的时钟
always@(posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
hs_buf <= 6'd0 ;
vs_buf <= 6'd0 ;
de_buf <= 6'd0 ;
end
else
begin
hs_buf <= {hs_buf[4:0], filter_hs} ;
vs_buf <= {vs_buf[4:0], filter_vs} ;
de_buf <= {de_buf[4:0], filter_de} ;
end
end
assign sobel_hs = hs_buf[5] ;
assign sobel_vs = vs_buf[5] ;
assign sobel_de = de_buf[5] ;
//在计算一个像素的梯度和方向后,开始非极大值抑制
shift_ram_maximum shift_ram_maximum_m0 (
.aclr (~sobel_vs),
.clock ( clk),
.clken ( sobel_de),
.shiftin ( gra_path ),//输入端口 第三行
.shiftout (),//和tap——1一样的输出
.taps0x ( tap_2 ),//第二行
.taps1x ( tap_3 )//第一行
);
//对矩阵第一行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max1_1,max1_2,max1_3} <= 48'd0;
else if (sobel_de)
{max1_1,max1_2,max1_3} <= {max1_2,max1_3,tap_3};
else
{max1_1,max1_2,max1_3} <= {max1_1,max1_2,max1_3};
end
//对矩阵第二行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max2_1,max2_2,max2_3} <= 48'd0;
else if (sobel_de)
{max2_1,max2_2,max2_3} <= {max2_2,max2_3,tap_2};
else
{max2_1,max2_2,max2_3} <= {max2_1,max2_2,max2_3};
end
//对矩阵第3行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max3_1,max3_2,max3_3} <= 48'd0;
else if (sobel_de)
{max3_1,max3_2,max3_3} <= {max3_2,max3_3,gra_path};
else
{max3_1,max3_2,max3_3} <= {max3_1,max3_2,max3_3};
end
//进行非极大值抑制
assign path_se = max2_2[13:10];//对于目标像素的梯度方向进行分配
assign search = max1_1[15] | max1_2[15] | max1_3[15] | max2_1[15] | max2_2[15] | max2_3[15] | max3_1[15] | max3_2[15] | max3_3[15];//搜寻目标像素周边是否包含梯度值大于高阈值的点,当然自身是高于的话,那么肯定为1
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
canny_out <= 8'd0;
else if (search &(row_cnt > 10'd5))
begin
case (path_se)
4'b0001:
canny_out <=((max2_2[9:0]>= max2_1[9:0])&(max2_2[9:0]>= max2_3[9:0]))?8'hff:8'h00;
4'b0010:
canny_out <=((max2_2[9:0]>= max1_3[9:0])&(max2_2[9:0]>= max3_1[9:0]))?8'hff:8'h00;
4'b0100:
canny_out <=((max2_2[9:0]>= max1_2[9:0])&(max2_2[9:0]>= max3_2[9:0]))?8'hff:8'h00;
4'b1000:
canny_out <=((max2_2[9:0]>= max1_1[9:0])&(max2_2[9:0]>= max3_3[9:0]))?8'hff:8'h00;default:
canny_out <= 8'h00;
endcase
end
else
canny_out <= 8'h00;
end
always@(posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
hs_buf_n <= 3'd0 ;
vs_buf_n <= 3'd0 ;
de_buf_n <= 3'd0 ;
end
else
begin
hs_buf_n <= {hs_buf_n[1:0], sobel_hs} ;
vs_buf_n <= {vs_buf_n[1:0], sobel_vs} ;
de_buf_n <= {de_buf_n[1:0], sobel_de} ;
end
end
assign canny_hs = hs_buf_n[2] ;
assign canny_vs = vs_buf_n[2] ;
assign canny_de = de_buf_n[2] ;
//检测行标志信号上升沿
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
edge_de_a <= 1'b0;
edge_de_b <= 1'b0;
end
else
begin
edge_de_a <= filter_de;
edge_de_b <= edge_de_a;
end
end
assign edge_de = edge_de_a & ~edge_de_b;
//记录行数,对前4行进行特殊处理
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
row_cnt <= 10'd0;
else if(~canny_vs)
row_cnt <= 10'd0;
else if(edge_de)
row_cnt <= row_cnt + 1'b1;
else
row_cnt <= row_cnt;
end
endmodule
第二个,取消了双域值近似
module canny
(
input clk,
input rst_s,
input [7:0] filter_out,
output reg [7:0] canny_out,
input filter_de,
input filter_hs,
input filter_vs,
output canny_hs,
output canny_vs,
output canny_de
);
//双阈值的高低阈值
parameter THRESHOLD_LOW = 10'd50;
parameter THRESHOLD_HIGH = 10'd100;
// shift_ram移位出口
wire [7:0] tap_0;
wire [7:0] tap_1;//输出端口
reg[9:0] Gx_1;//GX第一列计数
reg[9:0] Gx_3;
reg[9:0] Gy_1;
reg[9:0] Gy_3;
reg[10:0] Gx;//Gx Gy 做差分 求偏导
reg[10:0] Gy;
reg[20:0] sqrt_in;//计算梯度值的两个平方和
reg[9:0] sqrt_out;//开平方得到的梯度
reg[10:0] sqrt_rem;//开平方的余数
wire [20:0] sqrt_in_n;
wire [9:0] sqrt_out_n;
wire [10:0] sqrt_rem_n;
//对filter——de hs vs延迟
reg [5:0]hs_buf;
reg [5:0]vs_buf;
reg [5:0]de_buf;
wire sobel_de;
wire sobel_hs;
wire sobel_vs;
//9X9矩阵 sobel算子用
reg [7:0] ma1_1;
reg [7:0] ma1_2;
reg [7:0] ma1_3;
reg [7:0] ma2_1;
reg [7:0] ma2_2;
reg [7:0] ma2_3;
reg [7:0] ma3_1;
reg [7:0] ma3_2;
reg [7:0] ma3_3;
//记录行上升沿,可以设置前两行全为8'h00,也可以随其自然
reg edge_de_a;
reg edge_de_b;
wire edge_de;
reg [9:0] row_cnt;
//-----非极大值抑制----
reg[1:0] sign;//Gx Gy 正 负
reg type; // Gx Gy 异号 同号
reg path_one;
wire path_two;
reg path_thr;
wire path_fou;//四个梯度方向
wire start;//判断,;xy轴方向有没有旋中
reg [15:0] gra_path;//梯度幅值+方向+高低阈值状态
//--非极大值的ram出口
wire [15:0] tap_2;
wire [15:0] tap_3;
// 9x9矩阵,非极大值抑制用
reg [15:0] max1_1;
reg [15:0] max1_2;
reg [15:0] max1_3;
reg [15:0] max2_1;
reg [15:0] max2_2;
reg [15:0] max2_3;
reg [15:0] max3_1;
reg [15:0] max3_2;
reg [15:0] max3_3;
//对sobel de hs vs 延迟3拍
reg [3:0]de_buf_n;
reg [3:0]hs_buf_n;
reg [3:0]vs_buf_n;
wire max_de;
wire max_vs;
wire max_hs;
reg [1:0] max_g;
//case四个方向选择
wire [3:0] path_se;
wire high_low;
//--双阈值出口
wire [1:0] tap_4;
wire [1:0] tap_5;
// 9x9矩阵,非极大值抑制用
reg [1:0] mag1_1;
reg [1:0] mag1_2;
reg [1:0] mag1_3;
reg [1:0] mag2_1;
reg [1:0] mag2_2;
reg [1:0] mag2_3;
reg [1:0] mag3_1;
reg [1:0] mag3_2;
reg [1:0] mag3_3;
//对max de hs vs 延迟3拍
reg [3:0]de_buf_m;
reg [3:0]hs_buf_m;
reg [3:0]vs_buf_m;
wire search;
shift_ram shift_ram_inst (
.aclr ( ~filter_vs),
.clock ( clk),
.clken ( filter_de),
.shiftin ( filter_out ),//输入端口 第三行
.shiftout (),//和tap——1一样的输出
.taps0x ( tap_0 ),//第二行
.taps1x ( tap_1 )//第一行
);
//对矩阵第一行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma1_1,ma1_2,ma1_3} <= 24'd0;
else if (filter_de)
{ma1_1,ma1_2,ma1_3} <= {ma1_2,ma1_3,tap_1};
else
{ma1_1,ma1_2,ma1_3} <= {ma1_1,ma1_2,ma1_3};
end
//对矩阵第二行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma2_1,ma2_2,ma2_3} <= 24'd0;
else if (filter_de)
{ma2_1,ma2_2,ma2_3} <= {ma2_2,ma2_3,tap_0};
else
{ma2_1,ma2_2,ma2_3} <= {ma2_1,ma2_2,ma2_3};
end
//对矩阵第3行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{ma3_1,ma3_2,ma3_3} <= 24'd0;
else if (filter_de)
{ma3_1,ma3_2,ma3_3} <= {ma3_2,ma3_3,filter_out};
else
{ma3_1,ma3_2,ma3_3} <= {ma3_1,ma3_2,ma3_3};
end
//----------------Sobel Parameter--------------------------------------------
// Gx Gy Pixel
// [+1 0 -1] [+1 +2 +1] [ma1_1 ma1_2 ma1_3]
// [+2 0 -2] [ 0 0 0] [ma2_1 ma2_2 ma2_3]
// [+1 0 -1] [-1 -2 -1] [ma3_1 ma3_2 ma3_3]
//-------------------------------------------------------------
//将GX两列Gy 2列行先加 第一级流水线
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gx_1 <= 10'd0;
Gx_3 <= 10'd0;
end
else
begin
Gx_1 <= {2'b00,ma1_1} + {1'b0,ma2_1,1'b0} +{2'b0,ma3_1};
Gx_3 <= {2'b00,ma1_3} + {1'b0,ma2_3,1'b0} +{2'b0,ma3_3};
end
end
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gy_1 <= 10'd0;
Gy_3 <= 10'd0;
end
else
begin
Gy_1 <= {2'b00,ma1_1} + {1'b0,ma1_2,1'b0} +{2'b0,ma1_3};
Gy_3 <= {2'b00,ma3_1} + {1'b0,ma3_2,1'b0} +{2'b0,ma3_3};
end
end
//---Gx1 Gx3;Gy1 Gy3 做差 差分 xy方向的偏导 再判断GX GY的正负 第二级
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
begin
Gx <= 11'd0;
Gy <= 11'd0;
sign <= 2'b00;
end
else
begin
Gx <= (Gx_1 >= Gx_3)? Gx_1 - Gx_3 : Gx_3 - Gx_1;
Gy <= (Gy_1 >= Gy_3)? Gy_1 - Gy_3 : Gy_3 - Gy_1;
sign[0] <= (Gx_1 >= Gx_3)? 1'b1 : 1'b0;//判断GX Gy 正负,1 正 0 负
sign[1] <= (Gy_1 >= Gy_3)? 1'b1 : 1'b0;
end
end
//第三级 平方和 + GX、GY异同号?+ GX GY 大小级别 + 梯度方向
//求 Gx^2 Gy^2,提供给开方Ip计算梯度, //梯度的方向就是函数f(x,y)在这点增长最快的方向,梯度的模为方向导数的最大值。
// 梯度的摸 = (Gx^2 + Gy^2)开平方
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
sqrt_in <= 21'd0;
else
sqrt_in <= Gx*Gx + Gy*Gy;
end
assign sqrt_in_n = sqrt_in;
//对Gx Gy 正负的情况做分类 两类 异号 1 同号 0
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
type <= 1'b0;
else if (sign[0]^sign[1])
type <= 1'b1;
else
type <= 1'b0;
end
// 对 GX GY 大小级别做判断,也就是 GX > GY*2.5 ? Gy > GX*2.5?
// 符合 GX > GY*2.5 必定为x轴方向
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
path_one <= 1'b0;
else if(Gx > (Gy + Gy + Gy[10:1]))
path_one <= 1'b1;
//这里有个失误点,本来Gx Gy是10位,但对于GY*2.5 超过1023时,只取低10位,进位消失,该if成立,就会出现XY轴同时为1
else
path_one <= 1'b0;
end
// 符合 Gy > Gx*2.5 必定为y轴方向
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
path_thr <= 1'b0;
else if(Gy > (Gx + Gx + Gx[10:1]))
path_thr <= 1'b1;
else
path_thr <= 1'b0;
end
// 判断完 x y 轴方向 再判断两个对角方向
// 由于坐标轴原点在左上角 -------> x
// |
// |
// y|
// 同号 为 \ 异号为 / (当然得在 X Y 轴 都不是的情况下)
assign start = (path_one | path_thr)? 1'b0 : 1'b1;
assign path_two = (start) ? type : 1'b0;
assign path_fou = (start) ? ~type: 1'b0;
//开方IP组合逻辑,花的时间很少,送进去马上就得出数据,在下一个时钟赋给输出
sqrt sqrt_inst (
.radical ( sqrt_in_n ),
.q ( sqrt_out_n ),
.remainder ( sqrt_rem_n )
);
//第四级
//开方得到梯度,再加上4个方向gra_path[13:10]
//gra_path[15:14]高低阈值,gra_path[13:10]四个方向,gra_path[9:0]梯度幅值
always @(posedge clk or negedge rst_s)
begin
if(!rst_s)
gra_path <= 16'd0;
else if (sqrt_out_n > THRESHOLD_HIGH)
gra_path <= {1'b1,1'b0,path_fou,path_thr,path_two,path_one,sqrt_out_n};
else if (sqrt_out_n > THRESHOLD_LOW)
gra_path <= {1'b0,1'b1,path_fou,path_thr,path_two,path_one,sqrt_out_n};
else
gra_path <= 16'd0;
end
//对 hs vs de 进行适当的延迟,匹配VGA的时钟
always@(posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
hs_buf <= 6'd0 ;
vs_buf <= 6'd0 ;
de_buf <= 6'd0 ;
end
else
begin
hs_buf <= {hs_buf[4:0], filter_hs} ;
vs_buf <= {vs_buf[4:0], filter_vs} ;
de_buf <= {de_buf[4:0], filter_de} ;
end
end
assign sobel_hs = hs_buf[5] ;
assign sobel_vs = vs_buf[5] ;
assign sobel_de = de_buf[5] ;
//在计算一个像素的梯度和方向后,开始非极大值抑制
shift_ram_maximum shift_ram_maximum_m0 (
.aclr (~sobel_vs),
.clock ( clk),
.clken ( sobel_de),
.shiftin ( gra_path ),//输入端口 第三行
.shiftout (),//和tap——1一样的输出
.taps0x ( tap_2 ),//第二行
.taps1x ( tap_3 )//第一行
);
//对矩阵第一行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max1_1,max1_2,max1_3} <= 48'd0;
else if (sobel_de)
{max1_1,max1_2,max1_3} <= {max1_2,max1_3,tap_3};
else
{max1_1,max1_2,max1_3} <= {max1_1,max1_2,max1_3};
end
//对矩阵第二行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max2_1,max2_2,max2_3} <= 48'd0;
else if (sobel_de)
{max2_1,max2_2,max2_3} <= {max2_2,max2_3,tap_2};
else
{max2_1,max2_2,max2_3} <= {max2_1,max2_2,max2_3};
end
//对矩阵第3行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{max3_1,max3_2,max3_3} <= 48'd0;
else if (sobel_de)
{max3_1,max3_2,max3_3} <= {max3_2,max3_3,gra_path};
else
{max3_1,max3_2,max3_3} <= {max3_1,max3_2,max3_3};
end
//进行非极大值抑制
assign path_se = max2_2[13:10];//对于目标像素的梯度方向进行分配
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
max_g <= 2'd0;
else if (row_cnt > 10'd5)
begin
case (path_se)
4'b0001:
max_g <=((max2_2[9:0]> max2_1[9:0])&(max2_2[9:0]> max2_3[9:0]))?{max2_2[15:14]}:2'd0;
4'b0010:
max_g <=((max2_2[9:0]> max1_3[9:0])&(max2_2[9:0]> max3_1[9:0]))?{max2_2[15:14]}:2'd0;
4'b0100:
max_g <=((max2_2[9:0]> max1_2[9:0])&(max2_2[9:0]> max3_2[9:0]))?{max2_2[15:14]}:2'd0;
4'b1000:
max_g <=((max2_2[9:0]> max1_1[9:0])&(max2_2[9:0]> max3_3[9:0]))?{max2_2[15:14]}:2'd0;
default:
max_g <= 2'd0;
endcase
end
else
max_g <= 2'd0;
end
always@(posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
hs_buf_n <= 4'd0 ;
vs_buf_n <= 4'd0 ;
de_buf_n <= 4'd0 ;
end
else
begin
hs_buf_n <= {hs_buf_n[2:0], sobel_hs} ;
vs_buf_n <= {vs_buf_n[2:0], sobel_vs} ;
de_buf_n <= {de_buf_n[2:0], sobel_de} ;
end
end
assign max_hs = hs_buf_n[3] ;
assign max_vs = vs_buf_n[3] ;
assign max_de = de_buf_n[3] ;
//检测行标志信号上升沿
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
edge_de_a <= 1'b0;
edge_de_b <= 1'b0;
end
else
begin
edge_de_a <= filter_de;
edge_de_b <= edge_de_a;
end
end
assign edge_de = edge_de_a & ~edge_de_b;
//记录行数,对前4行进行特殊处理
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
row_cnt <= 10'd0;
else if(~canny_vs)
row_cnt <= 10'd0;
else if(edge_de)
row_cnt <= row_cnt + 1'b1;
else
row_cnt <= row_cnt;
end
//双阈值,8连通域连接像素
shift_ram_two shift_ram_two_m0 (
.aclr (~max_vs),
.clock ( clk),
.clken ( max_de),
.shiftin ( max_g ),//输入端口 第三行
.shiftout (),//和tap——1一样的输出
.taps0x ( tap_4 ),//第二行
.taps1x ( tap_5 )//第一行
);
//对矩阵第一行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{mag1_1,mag1_2,mag1_3} <= 6'd0;
else if (max_de)
{mag1_1,mag1_2,mag1_3} <= {mag1_2,mag1_3,tap_5};
else
{mag1_1,mag1_2,mag1_3} <= {mag1_1,mag1_2,mag1_3};
end
//对矩阵第二行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{mag2_1,mag2_2,mag2_3} <= 6'd0;
else if (max_de)
{mag2_1,mag2_2,mag2_3} <= {mag2_2,mag2_3,tap_4};
else
{mag2_1,mag2_2,mag2_3} <= {mag2_1,mag2_2,mag2_3};
end
//对矩阵第3行进行移位赋值
always @ (posedge clk or negedge rst_s)
begin
if (!rst_s)
{mag3_1,mag3_2,mag3_3} <= 6'd0;
else if (max_de)
{mag3_1,mag3_2,mag3_3} <= {mag3_2,mag3_3,max_g};
else
{mag3_1,mag3_2,mag3_3} <= {mag3_1,mag3_2,mag3_3};
end
assign search = mag1_1[1] | mag1_2[1] | mag1_3[1] | mag2_1[1] | mag2_2[1] | mag2_3[1] | mag3_1[1] | mag3_2[1] | mag3_3[1];//搜寻目标像素周边是否包含梯度值大于高阈值的点,当然自身是高于的话,那么肯定为1
assign high_low = mag2_2[1] | mag2_2[0];//排除小于低阈值的点
always @ (posedge clk or negedge rst_s)
begin
if(!rst_s)
canny_out <= 8'd0;
else if (~canny_de)
canny_out <= 8'd0;
else if(high_low)
canny_out <= (search) ? 8'hff : 8'h00;
else
canny_out <= 8'd0;
end
always@(posedge clk or negedge rst_s)
begin
if (!rst_s)
begin
hs_buf_m <= 4'd0 ;
vs_buf_m <= 4'd0 ;
de_buf_m <= 4'd0 ;
end
else
begin
hs_buf_m <= {hs_buf_n[2:0], max_hs} ;
vs_buf_m <= {vs_buf_n[2:0], max_vs} ;
de_buf_m <= {de_buf_n[2:0], max_de} ;
end
end
assign canny_hs = hs_buf_m[3] ;
assign canny_vs = vs_buf_m[3] ;
assign canny_de = de_buf_m[3] ;
endmodule