基于SparkRoad FPGA开发板的Sobel算子边缘检测模块设计
1. 电路架构介绍:
这里我们将电路主要分为三个部分,分别是OV2640的摄像头读取模块以及图像存储、VGA发生器、Sobel实时处理模块这三个部分:
图 1:边缘检测模块的整体架构图
其中第一个部分用于图片数据的采集,我们首先通过I2C 协议对OV2640的工作状态寄存器进行配置,最终实现输出像素大小为200×164的视频,并将读取的视频流存储在双端口BRAM中方便对图片进行处理和读取,然后我们将VGA发生器以及通过时钟的组合逻辑和时序逻辑实现对图片数据信息的读取,再利用读取的数据在行场信号的配合下进行输出,将输出的信号,最终通过Sobel算子卷积核进行卷积滤波,最后进行VGA时序的输出。
2. 原理介绍和Verilog代码实现:
这里主要描述有关于Sobel算子以及卷积核的代码:
由于输入的信号为RGB888信号因此我们首先需要对原始的RGB信号进行处理,首先将其转化为YCbCr信号,然后我们在取其中的Y信号分量进行信号的处理,Y信号变为了灰度的图像视频,其每个像素点值的大小,反映了对应RGB像素对应的亮度,也可以通过色彩空间的映射来理解这一过程,这里我们配图如下:
图 2 :颜色状态空间的示意图(图片来源知乎-章萧醇)
从映射的角度就是将取原来图片每个像素的向量取0A向量的投影,实现对RGB像素亮度信息的提取。
其具体转化的RGB_YCbCr的数学公式可以由下面的表达式表示:
Y = 0.257*R + 0.564*G + 0.098*B + 16
Cb = -0.148*R - 0.291*G + 0.439*B + 128
Cr = 0.439*R - 0.368*G - 0.071*B + 128
转化的代码:
//====================================================================
// RGB888转YCbCr
//====================================================================
// 第一个时钟周期
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
{R1,G1,B1} <= {16'd0, 16'd0, 16'd0};
{R2,G2,B2} <= {16'd0, 16'd0, 16'd0};
{R3,G3,B3} <= {16'd0, 16'd0, 16'd0};
end
else begin
{R1,G1,B1} <= { {R0 * 16'd77}, {G0 * 16'd150}, {B0 * 16'd29 } };
{R2,G2,B2} <= { {R0 * 16'd43}, {G0 * 16'd85}, {B0 * 16'd128} };
{R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
end
end
// 第二个时钟周期
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
Y1 <= 16'd0;
Cb1 <= 16'd0;
Cr1 <= 16'd0;
end
else begin
Y1 <= R1 + G1 + B1;
Cb1 <= B2 - R2 - G2 + 16'd32768; //128扩大256倍
Cr1 <= R3 - G3 - B3 + 16'd32768; //128扩大256倍
end
end
在得到了灰度图片之后,我们便可以正式开始设计Sobel算子的卷积模块了,由于这里我们使用了3x3的卷积核,因此我们首先需要将图片的数据读入Linebuffer以移位的方式中进行缓存,再将数据以三个一列的方式输入至卷积核矩阵中,然后每一次卷积核都将输出一个值,这个值便是通过Sobel算子计算得到的数据,我们对行同步信号和场同步信号进行同步,再最后将这些数据一起进行输出实现边缘检测的功能。
图3:实现Sobel算子的简洁示意图
Sobel算子由两3x3的卷积核组成,一个用于检测水平边缘,另一个用于检测垂直边缘。这两个卷积核分别是:
[-1 0 1] [-1 -2 -1]
[-2 0 2] 水平边缘检测算子 [ 0 0 0] 垂直边缘检测算子
[-1 0 1] [ 1 2 1]
然后通过卷积运算便可以得到某一个像素点的水平梯度大小和垂直梯度大小,再将这两个大小取绝对值相加,便可以获得该像素点的线性梯度值,利用边缘往往取梯度的较大值,在此基础上对得到的梯度图像进行阈值二值化,便可以得到边缘检测的图像。
//==========================================================================
// Sobel处理,耗费3个时钟周期
//==========================================================================
//第一个时钟周期:Gx1、Gx3和Gy1、Gy3
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
Gx1 <= 'd0;
Gx3 <= 'd0;
Gy1 <= 'd0;
Gy3 <= 'd0;
end
else begin
Gx1 <= matrix_11 + (matrix_21 << 1) + matrix_31;
Gx3 <= matrix_13 + (matrix_23 << 1) + matrix_33;
Gy1 <= matrix_11 + (matrix_12 << 1) + matrix_13;
Gy3 <= matrix_31 + (matrix_32 << 1) + matrix_33;
end
end
//第二个时钟周期:Gx和Gy绝对值
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
Gx <= 'd0;
Gy <= 'd0;
end
else begin //也可判断bit[7]来确定
Gx <= (Gx1 > Gx3) ? (Gx1 - Gx3) : (Gx3 - Gx1);
Gy <= (Gy1 > Gy3) ? (Gy1 - Gy3) : (Gy3 - Gy1);
end
end
//第三个时钟周期:Gx+Gy
//---------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
G <= 'd0;
end
else begin
G <= Gx + Gy;
end
end
assign sobel_data = (G > value) ? 8'h00 : 8'hff;
这里我们是用了三级流水线实现算子核和绝对值的运算,第一级用于实现两个矩阵乘法运算,第二级用于x方向矩阵和y方向矩阵绝对值的运算,第三级流水线用于实现两个绝对值相加的运算,由于运算消耗了4个时钟周期,最后应该对输入控制信号的时钟进行打拍的同步最后将行同步信号Hsync 和场同步信号Vsync与输出的数据进行同步,由于这部分代码比较简单,便省略了部分代码^-^ 。
图 4:仿真验证平台示意图
最后我们搭建了验证平台,为了在行为级对Sob算子的处理过程进行验证,我们选取了一张200x164的图片将像素通过python代码进行灰度的预处理再保存为16进制文本的形式,再通过再以读取文本$readmemh将数据读入testbench的ram中给vga发生器进行输出,最后利用 modelsim将行为级仿真将输出的数据在存入文本中,通过python代码进行文本转图片的逆运算,观察前后处理图片的效果,实现Sobel算子的功能性验证。
最终演示和效果图像如下图所示:
图 5:最终sob边缘检测结果以及对比原图像
3. 总结:
通过这次Sobel算子的边缘检测项目,让我对I2C通讯协议以及使用FPGA进行图像处理都有了更深的理解,这为后续的学习具有很大的帮助。