• FPGA之旅设计99例之第二十二例----Sobel算法边沿检测


    一. 简介

    本例将在上例的基础上,添加一个简单的图像处理算法—边缘检测(Sobel算法)。串口助手发送图片过来之后,结果边缘检测算法处理之后再输出到VGA进行显示。

    边沿检测算法主要是针对灰度图进行处理的,所以模块内部还需要添加个RGB565转灰度的功能。边缘检测算法有很多种,例如Sobel、Canny、Prewitt等等,各有其优缺点。本次选择Sobel进行学习,主要是因为其相对而言比较简单,FPGA易于实现。

    二. Sobel算法

    Sobel主要是利用图像两个方向的梯度来与阈值进行对比,大于阈值的点,被认为是边缘点。算法步骤大体如下

    1. 求x,y方向的梯度dx,dy
    2. 求出近似梯度G = dx2 + dy2开根号,也可以近似为 G = |dx| + |dy|,方便FPGA处理
    3. 与阈值进行对比,大于阈值则为边缘,将该像素赋值为255,否则为0

    可以看出算法的核心是如何计算图像两个方向的梯度。

    (一). 梯度计算

    梯度计算主要是用到了两个3x3大小的卷积核(如下图)。上图负责x方向,下图负责y方向。

    在这里插入图片描述

    卷积核在图像上进行移动,每次都会覆盖3x3大小的图像区域。卷积核与图像上对于位置上的元素相乘然后求和(矩阵的内积),就得到该点的梯度值(3x3中心位置的像素点)。

    这样又会引入两个问题

    1. 计算过程需要使用三行图像数据,如何进行存储
    2. 图像边缘位置的像素如何进行处理
    (二). 如何缓存三行数据

    实际上,只需要存储两行数据即可,第三行为当前输入的那行。具体的实现方式有两种,一种是调用现成IP,另外一种就是自己实现啦。目是要学习Sobel算法,偷个懒直接调用IP也是可以的。在Quartus中可以调用Shift Register IP实现,Vivado中也有,名字忘记了。

    IP工作原理如下,该图是缓存了4行数据。第一行输入一个数据,第一行就会输出一个数据,然后第一行输出的数据又会作为第二行的输入,第二行也会输出一个数据,作为第三行的输入,以此类推。一个数据输入,那么将会得到每一行的一个数据,共四个数据。
    在这里插入图片描述

    IP创建

    1. 选择Shift Register

    请添加图片描述

    1. 选择输入数据的位宽,缓存行数,每行的数据个数和时钟使能信号。通过框图可以看出输入的位宽为8bit,缓存的行数为两行(第三行为当前输入,不需要缓存),每一行的个数为480,刚好为图像的列数。

    请添加图片描述

    这样缓存三行的问题就得到了解决。

    (三). 边缘像素如何处理

    如果按照算法流程来的话,处理完成后,图像就会变小一圈,尺寸就变了,这是我们不希望得到了。一般采用的方法为先将图片扩大一圈,扩大一圈对应的像素值可以为0,或者255,也可以图像最外圈的保持一致,这样结果算法处理后,图像大小就和输入的大小保持一致了。但是这样在FPGA中处理不是很方便,考虑到图片边缘一般不会包含重要的信息,同样也是本次将采取的方便,外部输入一个像素,Sobel算法就会输出一个像素,这样边缘像素是否是边缘点,就是一个随机的了,跟初始化的值和上一帧图像有关,这样就大大简化了问题。

    三. 算法实现

    (1). 图片转灰

    这里采用最简单的方法,对图像的三个通道取平均值的方法进行转换。这里采取了两个周期来处理,最终得到灰度值

    //计算rgb三个值
    always@(posedge clk or negedge rst)
    begin
    	if(rst == 1'b0)
    	begin
    		reg_R <= 'd0;
    		reg_G	<= 'd0;
    		reg_B <= 'd0;
    	end
    	else	if(data_en == 1'b1)
    	begin
    		reg_R <= {data_in[15:11],3'b0};
    		reg_G <= {data_in[10:5],2'b0};
    		reg_B <= {data_in[4:0],3'b0};
    	end
    	else
    	begin
    		reg_R <= reg_R;
    		reg_G	<= reg_G;
    	   reg_B <= reg_B;
    	end
    end
    
    //计算regR regB  regG之和取平均
    always@(posedge clk or negedge rst)
    begin
    	if(rst == 1'b0)
    		reg_RGB <= 'd0;
    	else
    		reg_RGB <= (reg_R + reg_G + reg_B)/3;  //取平均值
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    (二). Sobel算法实现

    本次实现的Sobel算法一共需要花费两个时钟周期完成,

    第一个时钟周期主要完成Sobel核与图像的卷积操作将负数和正数分开计算

    always@(posedge clk or negedge rst)
    begin
    	if(rst == 1'b0)
    		begin
    			Sobel_px <= 'd0;
    			Sobel_nx <= 'd0;
    		end
    	else if(data_en==1'b1)
    		begin
    			Sobel_nx <= martix_11 + martix_21 + martix_21 + martix_31;
    			Sobel_px <= martix_13 + martix_23 + martix_23 + martix_33;
    		end
    	else
    		begin
    		Sobel_nx <= 'd0;
    		Sobel_px <= 'd0;
    		end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第二个时钟周期主要完成负数和正数的比较,求得梯度的绝对值,并求和比较输出结果

    assign Sobel_data = (Sobel_x + Sobel_y > Sobel_thresh) ? 8'd0 : 8'd255;
    always@(posedge clk)
    begin
    	Sobel_x <= (Sobel_px > Sobel_nx) ? (Sobel_px - Sobel_nx) : (Sobel_nx - Sobel_px);
        Sobel_y <= (Sobel_py > Sobel_ny) ? (Sobel_py - Sobel_ny) : (Sobel_ny - Sobel_py);
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最终实现效果如图,搞错了图像宽度为640,不是480,所以最右边出现重复,尴尬

    回复 FPGA之旅设计99例之第二十二例 获取完整工程 ,Sobel模块放在了sdram_read模块下面。
    请添加图片描述

  • 相关阅读:
    玩转SQLite7:基本语法与数据类型
    数据结构与算法设计分析——常用搜索算法
    直播预告 | 敏捷教练送外卖无限游戏
    P2824 [HEOI2016/TJOI2016] 排序
    Selenium alert 弹窗处理!
    Druid实现SQL监控、慢SQL记录、Spring监控、去广告
    你好~是APP需要接入广告吗?
    1.每天进步一点点------爬虫应用场景
    Loj#3320-「CCO 2020」旅行商问题
    通过语言模型奖励实现视频大型多模态模型的直接偏好优化
  • 原文地址:https://blog.csdn.net/weixin_44678052/article/details/127712187