本帖最后由 正点原子01 于 2020-9-11 12:38 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊:
5)关注正点原子公众号,获取最新资料更新
第二十六章V7725摄像头Sobel边缘检测 边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。本次实验将使用对OV7725摄像头采集到的数据进行Sobel边缘检测处理,并且在LCD屏上显示。 1 1 1.1 Sobel边沿检测简介 1.2 实验任务 1.3 硬件设计 1.4 软件设计 1.5 下载验证 1.1 Sobel边缘检测简介所谓边缘是指其周围像素灰度急剧变化的那些象素的集合,它是图像最基本的特征。边缘存在于目标、背景和区域之间,所以,它是图像分割所依赖的最重要的依据。由于边缘是位置的标志,对灰度的变化不敏感,因此,边缘也是图像匹配的重要的特征。 边缘检测和区域划分是图像分割的两种不同的方法,二者具有相互补充的特点。在边缘检测中,是提取图像中不连续部分的特征,根据闭合的边缘确定区域。而在区 域划分中,是把图像分割成特征相同的区域,区域之间的边界就是边缘。由于边缘检测方法不需要将图像逐个像素地分割,因此更适合大图像的分割。边缘大致可以分为两种,一种是阶跃状边缘,边缘两边像素的灰度值明显不同;另一种为屋顶状边缘,边缘处于灰度值由小到大再到小的变化转折点处。边缘检测的主要工具是边缘检测模板。边缘检测的有很多,典型的有索贝尔算子、普里维特算子、罗伯茨交叉边缘检测等边缘检测技术,在本章设计中采用的是索贝尔算子。 索贝尔算子算法简介: 索贝尔算子(Sobel operator)主要用作边缘检测,在技术上,它是一离散性差分算子,用来运算图像亮度函数的灰度之近似值。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量。 Sobel卷积因子为: 图 24.1.1 Sobel卷积因子 该算子包含两组 3x3 的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以 A 代表原始图像, Gx 及 Gy 分别代表经横向及纵向边缘检测的图像灰度值,其公式如下: 图 24.1.2 横向及纵向的图像灰度值计算公式 图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小: 图 24.1.3 像素的灰度值计算公式 通常,为了提高效率 使用不开平方的近似值,但这样做会损失精度,迫不得已的时候可以如下这样子:
图 24.1.4 像素灰度值的近似计算公式 如果梯度G大于某一阀值,则认为该点(x,y)为边缘点。 1.2 实验任务本节实验任务是使用领航者开发板及OV7725摄像头进行图像采集,对采集到的图像进行Sobel边缘检测处理,并通过带有HDMI接口的显示器实时显示。 1.3 硬件设计本次实验的硬件电路、管脚分配与“OV7725摄像头HDMI显示”完全相同,有关这一部分内容请读者参考“OV7725摄像头HDMI显示”实验。PL端的硬件系统框架与“OV7725摄像头HDMI显示”实验基本相似,但不同点在于,在OV7725摄像头采集模块采集了YUV格式的图像数据之后,对数据进行Sobel边缘检测处理,然后再送入Video In to AXI4-Stream IP核。 系统架构如下图所示: 图 24.3.1 系统架构框图 我们将Sobel边缘检测处理模块封装成IP核,以便直接在IP Integrator设计画布中进行调用。有关在Vivado中自定义IP核的操作步骤,请参见“自定义IP核”实验。在IP Integrator设计画布中封装后的Sobel边缘检测IP核及其连接如下图所示: 图 24.3.2 Sobel边缘检测IP核 可以看到,OV7725摄像头采集模块的输出接口连接到Sobel边缘检测IP核的输入接口,Sobel边缘检测IP核的输出接口连接到Video In to AXI4-Stream IP核的输入接口。 Sobel边缘检测IP核主要包含三个源文件,层次结构如下图所示: 图 24.3.3 Sobel边缘检测IP核的层次结构 其中,VIP_sobel_edge_detector.v源文件是该IP核的顶层源文件,用于接收来自摄像头数据采集模块数输出数据,对图像矩阵进行计算,并且输出最终的经过Sobel边沿检测后的像素数据。VIP_matrix_generate_3x3_8bit.v源文件用于生成经过缓存后的图像矩阵,随数据同时输出的还有控制信号。line_shift_RAM_8bit.v源文件调用了两个伪双端口RAM,用于对当前的行数据进行两次缓存,以生成图像矩阵的3列数据,随数据同时输出的还有控制信号。 VIP_sobel_edge_detector.v源文件的代码如下所示: - module VIP_sobel_edge_detector
- (
- //global clock
- input clk, //cmos 像素时钟
- input rst_n, //复位
- //准备要进行处理的图像数据
- input per_frame_vsync,
- input per_frame_href,
- input per_frame_clken,
- input [23:0] per_frame_data,
- //处理完毕的图像数据
- output post_frame_vsync,
- output post_frame_href,
- output post_frame_clken,
- output [23:0] post_frame_data
- );
- //parameter define
- parameter SOBEL_THRESHOLD = 8'd55; //sobel边沿监测的阈值
- //wire define
- wire [7:0] per_img_Y;
- wire [7:0] matrix_p11;
- wire [7:0] matrix_p12;
- wire [7:0] matrix_p13; //3X3矩阵的输出数据
- wire [7:0] matrix_p21;
- wire [7:0] matrix_p22;
- wire [7:0] matrix_p23;
- wire [7:0] matrix_p31;
- wire [7:0] matrix_p32;
- wire [7:0] matrix_p33;
- wire matrix_frame_vsync; //与3X3矩阵的输出数据同步的vsync
- wire matrix_frame_href; //与3X3矩阵的输出数据同步的href
- wire matrix_frame_clken; //与3X3矩阵的输出数据同步的clken
- wire [10:0] pixel_sobel_value;
- //reg define
- reg [9:0] Gx_temp1;
- reg [9:0] Gx_temp2;
- reg [9:0] Gx_data;
- reg [9:0] Gy_temp1;
- reg [9:0] Gy_temp2;
- reg [9:0] Gy_data;
- reg [20:0] Gxy_square;
- reg pixel_edge_flag;
- reg [4:0] matrix_frame_vsync_dly;
- reg [4:0] matrix_frame_href_dly;
- reg [4:0] matrix_frame_clken_dly;
- //*****************************************************
- //** main code
- //*****************************************************
- //提取出YUV图像数据的高8位,来直接作为图像的灰度值
- assign per_img_Y = { per_frame_data[23:19] , per_frame_data[15:13] } ;
- //输出最后的经sebel边沿检测后的图像数据接口
- assign post_frame_vsync = matrix_frame_vsync_dly[4] ;
- assign post_frame_href = matrix_frame_href_dly[4] ;
- assign post_frame_clken = matrix_frame_clken_dly[4] ;
- assign post_frame_data = post_frame_href ? {24{pixel_edge_flag}} : 24'b0 ;
- //生成 8Bit 3X3 的图像矩阵
- VIP_matrix_generate_3x3_8bit u_VIP_Matrix_Generate_3X3_8Bit
- (
- .clk (clk),
- .rst_n (rst_n),
- //准备要进行处理的图像数据
- .per_frame_vsync (per_frame_vsync),
- .per_frame_href (per_frame_href),
- .per_frame_clken (per_frame_clken),
- .per_img_Y (per_img_Y),
- //矩阵化后的图像数据和控制信号
- .matrix_p11 (matrix_p11),
- .matrix_p12 (matrix_p12),
- .matrix_p13 (matrix_p13),
- .matrix_p21 (matrix_p21),
- .matrix_p22 (matrix_p22),
- .matrix_p23 (matrix_p23),
- .matrix_p31 (matrix_p31),
- .matrix_p32 (matrix_p32),
- .matrix_p33 (matrix_p33),
- .matrix_frame_vsync (matrix_frame_vsync),
- .matrix_frame_href (matrix_frame_href),
- .matrix_frame_clken (matrix_frame_clken)
- );
- //Sobel Parameter
- // Gx Gy Pixel
- // [ -1 0 +1 ] [ +1 +2 +1 ] [ P11 P12 P13 ]
- // [ -2 0 +2 ] [ 0 0 0 ] [ P21 P22 P23 ]
- // [ -1 0 +1 ] [ -1 -2 -1 ] [ P31 P32 P33 ]
- //步骤一:
- //计算 Gx * Pixel 的绝对值
- always@(posedge clk or negedge rst_n) begin
- if(!rst_n) begin
- Gx_temp1 <= 0;
- Gx_temp2 <= 0;
- Gx_data <= 0;
- end
- else begin
- Gx_temp1 <= matrix_p13 + (matrix_p23 << 1) + matrix_p33;
- Gx_temp2 <= matrix_p11 + (matrix_p21 << 1) + matrix_p31;
- Gx_data <= (Gx_temp1 >= Gx_temp2) ? (Gx_temp1 - Gx_temp2) : (Gx_temp2 - Gx_temp1) ;
- end
- end
- //步骤二:
- //计算 Gy * Pixel 的绝对值
- always@(posedge clk or negedge rst_n) begin
- if(!rst_n) begin
- Gy_temp1 <= 0;
- Gy_temp2 <= 0;
- Gy_data <= 0;
- end
- else begin
- Gy_temp1 <= matrix_p11 + (matrix_p12 << 1) + matrix_p13;
- Gy_temp2 <= matrix_p31 + (matrix_p32 << 1) + matrix_p33;
- Gy_data <= (Gy_temp1 >= Gy_temp2) ? Gy_temp1 - Gy_temp2 : Gy_temp2 - Gy_temp1;
- end
- end
- //步骤三:
- //计算 Gx^2 + Gy^2
- always@(posedge clk or negedge rst_n) begin
- if(!rst_n)
- Gxy_square <= 0;
- else
- Gxy_square <= Gx_data * Gx_data + Gy_data * Gy_data;
- end
- //步骤四:
- //计算 (Gx^2 + Gy^2)^0.5
- cordic_0 u_SQRT (
- .aclk ( clk ), // 输入时钟
- .s_axis_cartesian_tvalid ( 1'b1 ), // 输入数据有效信号
- .s_axis_cartesian_tdata ( Gxy_square ), // 输入数据
- .m_axis_dout_tvalid ( ), // 输出数据有效信号
- .m_axis_dout_tdata ( pixel_sobel_value ) // 输出数据
- );
- //步骤五:
- //与sobel边缘检测阈值进行比较,
- //以判定该像素点是否为边缘
- always@(posedge clk or negedge rst_n)
- begin
- if(!rst_n)
- pixel_edge_flag <= 1'b0;
- else if(pixel_sobel_value >= SOBEL_THRESHOLD)
- pixel_edge_flag <= 1'b1; //该像素点是边缘
- else
- pixel_edge_flag <= 1'b0; //该像素点不是边缘
- end
- //对图像矩阵数据的计算共耗时5个周期,
- //所以要将控制信号延时5个周期
- always@(posedge clk or negedge rst_n) begin
- if(!rst_n) begin
- matrix_frame_vsync_dly <= 0;
- matrix_frame_href_dly <= 0;
- matrix_frame_clken_dly <= 0;
- end
- else begin
- matrix_frame_vsync_dly <= { matrix_frame_vsync_dly[3:0] , matrix_frame_vsync };
- matrix_frame_href_dly <= { matrix_frame_href_dly[3:0] , matrix_frame_href };
- matrix_frame_clken_dly <= { matrix_frame_clken_dly[3:0] , matrix_frame_clken };
- end
- end
- endmodule
复制代码代码中的第67到91行例化了VIP_matrix_generate_3x3_8bit模块,用于生成3X3的图像矩阵。有关3x3矩阵的生成,我们在基于“基于OV5640的中值滤波实验”中有过介绍,本章节就不再赘述,有需要的朋友可以参考“基于OV5640的中值滤波实验”相关部分。接下来的第101-178行就是依次按照步骤,来计算出最后的经过Sobel边沿检测后的图像数据。 第101到112行的always语句是步骤一,用于计算出Pixel像素矩阵与X方向的算子矩阵Gx之间的乘积。这里要注意的是,由于在步骤三中要对X方向和Y方向上的乘积进行平方运算,所以在这里我们计算X方向的乘积时,只需计算出乘积的绝对值即可(因为一个数的平方与其相反数的平方相等)。算子矩阵Gx中带有负数,所以我们只需比较正数列相乘得到的数值和负数列相乘得到的数值的大小,然后取差值,就得到了Pixel像素矩阵与X方向的算子矩阵Gx之间的乘积,如代码第110行所示。 步骤二的原理与步骤一相同,只不过其计算的是Pixel像素矩阵与Y方向的算子矩阵Gy之间的乘积,如代码第117到128行。这里就不在赘述。 步骤三用于计算在步骤一和步骤二中计算出的乘积的平方和,如代码第133到138行所示。 步骤四用于对平方和进行开根号计算。如代码第142到148行所示。注意,在这里我们调用了CORDICIP核,该IP核是一个数字信号处理IP核,主要用于完成复杂的数学运算。我们要将其配置为所需的平方根计算,其配置参数如下图所示: 图 24.3.4 CORDIC IP核的配置参数 第二个页面“AXI4 Stream Options”保持默认即可。 步骤五就是最后的阈值判断。把在步骤四中计算出的数值与我们定义的Sobel边缘检测阈值进行比较。如果大于等于该阈值,则认为该像素点是边缘点;否则,则认为该像素点不是边缘点。如代码第153到161行所示。 最后,根据判断结果来对当前像素点的颜色数据进行赋值。如果该像素点是边缘点,则该像素点显示为白色,否则显示为黑色。如代码第60行所示。 当然,由于像素点的颜色数据的计算共耗时5个周期,所以也要对行场同步等控制信号延时5个周期,以与数据同步。如代码第165到176行的always块所示。 由于数据的缓存和读取需要一定的延迟,所以两个伪双端口块RAM各自的读写端口的输入地址也需要进行延迟打拍处理,以与数据进行同步,如代码第49行的always语句所述。 连线后的Block Design如下图所示: 图 24.3.5整体Block Design框图 接下来验证当前设计。验证完成后弹出对话框提示没有错误或者关键警告,点击“OK”。如果验证结果报出错误或者警告,则需要重新检查设计。 为工程添加的约束文件与“OV7725摄像头HDMI显示”完全相同,有关这一部分内容请读者参考“OV7725摄像头HDMI显示”实验。 最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“ Generate Bitstream”,对设计进行综合、实现、并生成Bitstream文件。 在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > Launch SDK,启动SDK软件。 1.4 软件设计PS端软件的设计与“OV7725摄像头HDMI显示”几乎相同。唯一的不同之处在于,在之前的“OV7725摄像头HDMI显示”实验中,我们将OV7725摄像头的输出数据格式设置为了RGB565;而本次实验我们要将其设置为YUV格式。在PS端软件中的ov7725_init()函数中,设置该格式的语句如下所示: - sccb_write_reg8 (0x12, 0x04) ; //COM7 输出VGA YUV格式
复制代码有关PS软件的其他部分的更详细介绍,请读者参考“OV7725摄像头HDMI显示”实验。 1.5 下载验证编译完工程之后我们就可以开始下载程序了。将OV7725摄像头模块插在领航者Zynq开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。 在SDK软件下方的SDK Terminal窗口中点击右上角的加号设置并连接串口。然后下载本次实验硬件设计过程中所生成的BIT文件,来对PL进行配置。最后下载软件程序,下载完成后,在下方的SDK Terminal中可以看到应用程序打印的信息,如下图所示: 图 24.5.1 串口打印的信息 此时表示ZYNQ成功检测到OV7725摄像头。同时,显示器上显示出OV7725摄像头采集的图像经过Sobel边缘检测处理之后的画面,即只有边缘为白色,非边缘为黑色。说明本次OV7725摄像头HDMI显示Sobel边缘检测的实验在领航者ZYQN开发板上验证成功,如下图所示: 图 24.5.2 实验结果 另外,读者可以尝试修改VIP_sobel_edge_detector IP核中的sobel边沿监测的阈值,默认是55,如下图所示: 图 24.5.3 sobel边沿监测的阈值 然后重新综合Block Design、生成比特流、并重新编译整个PS软件,将设计下载到ZYNQ,以观察不同阈值下的边沿监测的不同效果。
|