基于 Sobel 算法的边缘检测设计与实现

概述

边缘检测, 针对的是灰度图像, 顾名思义,检测图像的边缘, 是针对图像像素点的一种计算, 目的是标识数字图像中灰度变化明显的点。(周围灰度急剧变化的像素的集合,这个突变的就是变化率最大的地方,即一阶导数最大的地方)图像的边缘检测,在保留了图像的重要结构信息的同时,剔除了可以认为不相关的信息,大幅度减少了数据量,便于图像的传输和处理。
  sobel边缘检测算子、Robert边缘检测算子等为典型的一阶微分算子,可以较好的捕捉图像灰度变化的边缘。对应的二阶边缘算子,如Laplacian边缘检测算子,可以更好的捕捉图像灰度变化的边缘,而对缓慢变化的区域不敏感。
image.png

算法阈值设置是关键,不同阈值下的边缘检测效果不同,阈值设置越小(64),则越容易引起脏点,但是边缘保持较粗,反之,阈值设置越大(96),则能屏蔽一些较小梯度的点,但是边缘相应会变得更细。

Matlab实现

function Q=sobel_detector(IMG,thresh) 

[h,w] = size(IMG); 
Q = ones(h,w);

% -------------------------------------------------------------------------
%         Gx                  Gy				  Pixel
% [   -1  0   +1  ]   [   -1  -2   -1 ]     [   P1  P2   P3 ]
% [   -2  0   +2  ]   [   0   0    0  ]     [   P4  P5   P6 ]
% [   -1  0   +1  ]   [   +1  +2   +1 ]     [   P7  P8   P9 ]
Sobel_X = [-1, 0, 1, -2, 0, 2, -1, 0, 1];   % Weight x
Sobel_Y = [-1,-2,-1,  0, 0, 0,  1, 2, 1];   % Weight y

IMG_Gray = double(IMG);    
IMG_Sobel = ones(h,w);    

n=3;
for i=1 : h
    for j=1:w
        if(i<1 || i>h-1 || j<1 || j>w-1)
            IMG_Sobel(i,j) = 0; 	 %边缘像素不处理
        else
            temp1 = Sobel_X(1) * IMG_Gray(i-1,j-1) 	+ Sobel_X(2) * IMG_Gray(i-1,j)	+ Sobel_X(3) * IMG_Gray(i-1,j+1) +...
                    Sobel_X(4) * IMG_Gray(i,j-1) 	+ Sobel_X(5) * IMG_Gray(i,j) 	+ Sobel_X(6) * IMG_Gray(i,j+1) +...
                    Sobel_X(7) * IMG_Gray(i+1,j-1) 	+ Sobel_X(8) * IMG_Gray(i+1,j)	+ Sobel_X(9) * IMG_Gray(i+1,j+1);
            temp2 = Sobel_Y(1) * IMG_Gray(i-1,j-1)	+ Sobel_Y(2) * IMG_Gray(i-1,j)	+ Sobel_Y(3) * IMG_Gray(i-1,j+1) +...
                    Sobel_Y(4) * IMG_Gray(i,j-1) 	+ Sobel_Y(5) * IMG_Gray(i,j) 	+ Sobel_Y(6) * IMG_Gray(i,j+1) +...
                    Sobel_Y(7) * IMG_Gray(i+1,j-1) 	+ Sobel_Y(8) * IMG_Gray(i+1,j)	+ Sobel_Y(9) * IMG_Gray(i+1,j+1);
            temp3 = sqrt(temp1^2 + temp2^2);
            if(uint8(temp3) > thresh)
                IMG_Sobel(i,j) = 1;
            else
                IMG_Sobel(i,j) = 0; 
            end
        end
    end
end

Q=IMG_Sobel;

算法阈值设置是关键,不同阈值下的边缘检测效果不同,阈值设置越小(64),则越容易引起脏点,但是边缘保持较粗,反之,阈值设置越大(96),则能屏蔽一些较小梯度的点,但是边缘相应会变得更细。
image.png
image.png

FPGA实现

目的

串口接受图像数据,实现Sobel算法的边缘检测,并且将处理过的图像显示在RGB屏幕中

流程框图

image.png

模块作用

时钟生成模块clk_gen:生成驱动RGB屏幕所需要的时钟信号,这里需要注意的是,使用PLL IP核所的复位信号为高有效,当locked引脚为高点平时表示生成了稳定的时钟信号,依次来作为后续系统的复位信号。
  uart_rx uart_tx为串口的发送和接受模块,用于接受和发送串口数据。
  sobel_ctrl实现sobel算法的核心模块。
  RGB模块,将图片数据在RGB屏幕上显示

代码

代码部分主要提供sobel_ctrlRGB模块,其余请查看工程源码。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/01/21 14:52:15
// Design Name: 
// Module Name: sobel_ctrl
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module  sobel_ctrl
(
    input   wire          sys_clk     ,   
    input   wire          sys_rst_n   ,   
    input   wire  [7:0]   pi_data     ,   
    input   wire          pi_flag     ,   

    output  reg   [7:0]   po_data      ,   
    output  reg           po_flag         
);

//parameter define
parameter   P_LENGTH = 10'd100 ,        //行计数最大值
            P_WIDTH  = 10'd75 ;         //列计数最大值
parameter   THRESHOLD = 8'b000_011_00 ; //比较阈值
parameter   BLACK = 8'b0000_0000 ,      //黑色
            WHITE = 8'b1111_1111 ;      //白色
//wire  define
wire  [7:0]   data_out1   ;   //fifo1数据输出
wire  [7:0]   data_out2   ;   //fifo2数据输出

//reg   define 
reg   [9:0]   cnt_row     ;   //行计数,计数一行数据个数
reg   [9:0]   cnt_col     ;   //场计数,计数数据行数
reg           wr_en1      ;   //fifo1写使能
reg           wr_en2      ;   //fifo2写使能
reg   [7:0]   data_in1    ;   //fifo1写数据输入  
reg   [7:0]   data_in2    ;   //fifo2写数据输入
reg           rd_en       ;   //fifo1、fifo2共用的读使能
reg           dout_flag   ;   //控制fifo1,2~84行的写使能
reg           po_flag_reg ;   //输出标志位缓存,rd_en延后一拍得到,控制计算po_sum
reg   [9:0]   cnt_rd;         //读取数据计数器
reg   [7:0]   pi_data_delay ;     //pi_data 数据寄存
reg   [7:0]   data_out1_delay ;   //fifo1 数据输出寄存
reg   [7:0]   data_out2_delay ;   //fifo2 数据输出寄存
reg           rd_en_delay1 ; //输出数据标志信号,延后 rd_en 一拍
reg           rd_en_delay2 ; //a,b,c 赋值标志信号
reg           gx_gy_flag ; //gx,gy 计算标志信号
reg           gxy_flag ; //gxy 计算标志信号
reg           gxy_compare_flag; //阈值比较标志信号
reg   [7:0]   a1 ;
reg   [7:0]   a2 ;
reg   [7:0]   a3 ;
reg   [7:0]   b1 ;
reg   [7:0]   b2 ;
reg   [7:0]   b3 ;
reg   [7:0]   c1 ;
reg   [7:0]   c2 ;
reg   [7:0]   c3 ; //图像数据
reg   [8:0]   gx ;
reg   [8:0]   gy ; //gx,gy
reg   [7:0]   gxy ; //gxy

//*********************************************//
//******************main code******************//
//*********************************************//

//cnt_row:行计数器,计数一行数据个数
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        cnt_row <=  10'd0;
    else if((cnt_row == P_LENGTH - 1'd1) && (pi_flag == 1'b1))
        cnt_row <=  10'd0;
    else if(pi_flag == 1'b1)
        cnt_row <=  cnt_row + 1'b1;
end

//cnt_col:列计数器,计数数据行数
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        cnt_col <=  10'd0;
    else if((cnt_col == P_WIDTH - 1'd1) && (pi_flag == 1'b1) && (cnt_row == P_LENGTH - 1'd1))
        cnt_col <=  10'd0;
    else if((cnt_row == P_LENGTH - 1'd1) && (pi_flag == 1'b1))
        cnt_col <=  cnt_col + 1'b1;
end

//wr_en1:fifo1写使能信号,高电平有效
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        wr_en1  <=  1'b0;
    else if((cnt_col == 7'd0) && (pi_flag == 1'b1))
        wr_en1  <=  1'b1;          //第0行写入fifo1
    else
        wr_en1  <=  dout_flag;  //2-84行写入fifo1
end

//dout_flag:控制2~P_WIDTH - 1'd1-1行wr_en1信号
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        dout_flag <=  0;
    else if((wr_en2 == 1'b1) && (rd_en == 1'b1))
        dout_flag <=  1'b1;
    else
        dout_flag <=  1'b0;
end

//wr_en2:fifo2写使能信号,高电平有效
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        wr_en2  <=  1'b0;
    else if((cnt_col >= 7'd1) && (cnt_col <= P_WIDTH - 1'd1 - 1'b1) && (pi_flag == 1'b1))
        wr_en2  <=  1'b1;          //2~P_WIDTH - 1'd1行写入fifo2
    else
      wr_en2  <=  1'b0;
end

//data_in1:fifo1数据输入
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        data_in1  <=  8'b0;
    else if((pi_flag == 1'b1) && (cnt_col == 7'd0))
        data_in1  <=  pi_data;  //第0行数据暂存fifo1中
    else if(dout_flag == 1'b1)
      data_in1  <=  data_out2;//第2~P_WIDTH - 1'd1-1行时,fifo2读出数据存入fifo1
    else
        data_in1  <=  data_in1;
end

//data_in2:fifo2数据输入
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        data_in2  <=  8'b0;
    else    if((pi_flag == 1'b1)&&(cnt_col >= 7'd1)&&(cnt_col <= (P_WIDTH - 1'd1 - 1'b1)))
        data_in2  <=  pi_data;
    else
        data_in2  <=  data_in2;
end

//rd_en:fifo1和fifo2的共用读使能信号
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        rd_en <=  1'b0;
    else    if((pi_flag == 1'b1)&&(cnt_col >= 7'd2)&&(cnt_col <= P_WIDTH - 1'd1)) //第二行之后开始读取数据
        rd_en <=  1'b1;
    else
        rd_en <=  1'b0;
end

//cnt_rd:读取数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        cnt_rd <= 1'd0;
    else if((rd_en == 1'b1)&&(cnt_rd == P_LENGTH - 1'b1)) //读取一行数据后计数器清零
        cnt_rd <= 1'd0;
    else if(rd_en == 1'b1)
        cnt_rd <= cnt_rd + 1'b1;
end
//rd_en_delay1:读使能延后一拍
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        rd_en_delay1 <= 8'b0;
    else if(rd_en == 1'b1)
        rd_en_delay1 <= 1'b1;
    else
        rd_en_delay1<= 1'b0;
end
//rd_en_delay2:读使能再延后一拍
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        rd_en_delay2 <= 8'b0;
    else if(rd_en_delay1 == 1'b1)
        rd_en_delay2 <= 1'b1;
    else
        rd_en_delay2 <= 1'b0;
end
//data_out1_delay:fifo1数据输出延后一拍 a1
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        data_out1_delay <= 8'b0;
    else if(rd_en_delay1 == 1'b1)
        data_out1_delay <= data_out1;
end

//data_out2_delay:fifo2数据输出延后一拍 b1
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        data_out2_delay <= 8'b0;
    else if(rd_en_delay1 == 1'b1)
        data_out2_delay <= data_out2;
    
end

//pi_data_delay:pi_data数据输出延后一拍 c1
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        pi_data_delay <= 8'b0;
    else if(rd_en_delay1 == 1'b1)
        pi_data_delay <= pi_data;
end
//gx_gy_flag:gx_gy计算标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gx_gy_flag <= 1'b0;
    else if(rd_en_delay2 == 1'b1&& ((cnt_rd >= 8'd3)|| (cnt_rd == 8'd0)))//第三行开始计算gx,gy
        gx_gy_flag <= 1'b1;
    else
        gx_gy_flag <= 1'b0;
end
//gxy_flag:gxy计算标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gxy_flag <= 1'b0;
    else if(gx_gy_flag == 1'b1)
        gxy_flag <= 1'b1;
    else
        gxy_flag <= 1'b0;
end
//gxy比较标志位 gxy_compare_flag
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gxy_compare_flag <= 1'b0;
    else if(gxy_flag == 1'b1)
        gxy_compare_flag <= 1'b1;
    else
        gxy_compare_flag <= 1'b0;
end

//a1、a2、a3等赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)begin
        a1 <= 8'b0;
        a2 <= 8'b0;
        a3 <= 8'b0;
        b1 <= 8'b0;
        b2 <= 8'b0;
        b3 <= 8'b0;
        c1 <= 8'b0;
        c2 <= 8'b0;
        c3 <= 8'b0;
    end
    else if(rd_en_delay2 == 1'b1)begin
        a1 <= data_out1_delay;
        a2 <= a1;
        a3 <= a2;
        b1 <= data_out2_delay;
        b2 <= b1;
        b3 <= b2;
        c1 <= pi_data_delay;
        c2 <= c1;
        c3 <= c2;
    end
    else begin
        a1 <= a1;
        a2 <= a2;
        a3 <= a3;
        b1 <= b1;
        b2 <= b2;
        b3 <= b3;
        c1 <= c1;
        c2 <= c2;
        c3 <= c3;
    end
end
//gx计算
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gx <= 8'b0;
    else if(gx_gy_flag == 1'b1)
        gx <= a3 - a1 +((b3 - b1)<<1) + c3 - c1;
    else
        gx <= gx;
end
//gy计算
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gy <= 8'b0;
    else if(gx_gy_flag == 1'b1)
        gy <= a1 - c1 +((a2 - c2)<<1) + a3 - c3;
    else
        gy <= gy;
end
//gxy计算
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        gxy <= 8'b0;
    else if((gx[8] == 1'b1) && (gy[8] == 1'b1) && (gxy_flag == 1'b1))
        gxy <= (~gx[7:0] + 1'b1) + (~gy[7:0] + 1'b1);
    else if((gx[8] == 1'b1) && (gy[8] == 1'b0) && (gxy_flag == 1'b1))
        gxy <= (~gx[7:0] + 1'b1) + (gy[7:0]);
    else if((gx[8] == 1'b0) && (gy[8] == 1'b1) && (gxy_flag == 1'b1))
        gxy <= (gx[7:0]) + (~gy[7:0] + 1'b1);
    else if((gx[8] == 1'b0) && (gy[8] == 1'b0) && (gxy_flag == 1'b1))
        gxy <= (gx[7:0]) + (gy[7:0]);
    else
        gxy <= gxy;
end

//po_data:gxy与阈值比较
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        po_data <= 1'b0;
    else if((gxy >= THRESHOLD) && (gxy_compare_flag == 1'b1))
        po_data <= BLACK;
    else if(gxy_compare_flag == 1'b1)
        po_data <= WHITE;
end

//po_flag:输出标志信号,po_data同步输出
always@(posedge sys_clk or  negedge sys_rst_n)
begin
    if(sys_rst_n == 1'b0)
        po_flag <=  1'b0;
    else if(gxy_compare_flag == 1'b1)
        po_flag <=  1'b1;
    else    
        po_flag <=  1'b0;
end

//------------- fifo_data_inst1 --------------
fifo1   fifo_data_inst1
(
    .clk    (sys_clk    ),  //input clk
    .din    (data_in1   ),  //input [7:0] din
    .wr_en  (wr_en1     ),  //input wr_en
    .rd_en  (rd_en      ),  //input rd_en
    .full   ( ),            // output full
    .empty  ( ),            // output empty
    .dout   (data_out1   )   //output [7:0] dout
);

//------------- fifo_data_inst2 --------------
fifo2   fifo_data_inst2
(
    .clk    (sys_clk    ),  //input clk
    .din    (data_in2   ),  //input [7:0] din
    .wr_en  (wr_en2     ),  //input wr_en
    .rd_en  (rd_en      ),  //input rd_en
    .full   ( ),            // output full
    .empty  ( ),            // output empty
    .dout   (data_out2   )   //output [7:0] dout
);

endmodule

这段代码在FIFO三项求和代码的基础上增加了阈值计算与比较代码。在代码最开始,我们定义了P_LENGTH P_WIDTH这两个参数作为输入图片的长度与宽度。sobel算法是计算三行三列的数据,因此,我们定义了一个行计数器与列计数器,计算输入数据的行数与列数。由FIFO三项求和的原理我们可以知道,第一行数据输入FIFO1中,第二行数据输入FIFO2中,第三行数据由串口输入,直接与第一行、第二行中的数据计算,结果直接输出,计算过程中,已经参与过计算的第三行数据输入FIFO2中,已经参与过计算的第二行数据输入FIFO1中,原本在FIFO1中的第一行数据被直接舍弃。(FIFO先入先出)。这就是FIFO三项计算的基本原理。
  详细讲解:
       FIFO1:开始时,第一行数据直接输入FIFO1中,以(cnt_col == 7'd0)作为标志信号使能FIFO1的写使能信号,即输入的数据开始输入第一行数据,(pi_flag == 1'b1)为串口输入的标志信号,默认加上。当三行数据开始计算后,将计算过的第二行数据传入FIFO1中,以dout_flag作为标志信号使能FIFO1的写使能信号。
       FIFO2: FIFO2的流程比较简单,当(cnt_col >= 7'd1)(cnt_col <= P_WIDTH - 1'd1 - 1'b1)时,即输入的数据开始输入第二行数据,使能使能FIFO2的写使能信号,之所以(cnt_col <= P_WIDTH - 1'd1 - 1'b1)是因为最后一行不参与计算。
       当前两行数据都已经缓存到对应的FIFO中之后,就可以开始FIFO三项计算,首先将FIFO中的数据读取出来,rd_en:fifo1和fifo2的共用读使能信号,当(cnt_col >= 7'd2)&&(cnt_col <= P_WIDTH - 1'd1))即数据存储至第三行以后,就使能rd_en开始数据读取并且计算。当计算开始之后,将计算过的第二行数据存至FIFO1中,则以(wr_en2 == 1'b1) && (rd_en == 1'b1)为约束信号dout_flag,即FIFO2开始写入数据,且FIFO1、2 已经开始读取数据开始计算,此时我们就可以使能wr_en1将第二行的数据存至FIFO1中此时各个FIFO中读取的为最开始输入的数据即第一行第一个数据与第二行第一个数据。
       根据算法,需要三行三列的数据,但是每次只能读取一列的数据,因为我们将读取到的数据打一拍,缓存起来。给一段波形图,大家自己理解。
image.png

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/01/23 11:13:22
// Design Name: 
// Module Name: lcd_display
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module lcd_display(
    input   wire [7:0]          pi_data,
    input   wire                pi_flag,
    input   wire                sys_clk,
    input   wire                lcd_pclk, //时钟
    input   wire                sys_rst_n, //复位,低电平有效
    input   wire [10:0]         pixel_xpos, //当前像素点横坐标
    input   wire [10:0]         pixel_ypos, //当前像素点纵坐标
    input   wire [10:0]         h_disp, //LCD 屏水平分辨率
    input   wire [10:0]         v_disp, //LCD 屏垂直分辨率

    output  wire [23:0]         pixel_data //像素数据
    );
    wire rd_en;
    wire ena;
    wire enb;
    wire    [23:0]  pic_data;
    reg     [23:0]  rgb_data; 
    reg     [23:0]  pix_data;    
    reg     [17:0]  wr_addr     ;   //ram写地址
    reg     [17:0]  rd_addr     ;   //ram读地址
    reg             pic_valid;
    reg             pi_flag_delay;   
    parameter   H_PIC   =   10'd98     ,   //sobel算法处理后的图片长度
                W_PIC   =   10'd73     ,   //sobel算法处理后的图片宽度
                PIC_SIZE =   18'd7154   ,  //sobel算法处理后的图片像素个数
                baby_blue =   24'hADD8E6 ;   //浅蓝色
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

assign ena = sys_rst_n;
assign enb = sys_rst_n;
//将rgb332转换成rgb888
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        rgb_data <= 24'hFFFFFF;
    else if((pi_data == 8'b0000_0000)&& (pi_flag == 1'b1))
        rgb_data <= 24'h000000;
    else if(pi_flag == 1'b1)
        rgb_data <= 24'hFFFFFF;
end
//pi_flag打一拍。对齐时序
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        pi_flag_delay <= 1'b0;
    else if(pi_flag == 1'b1)
        pi_flag_delay <= 1'b1;
    else 
        pi_flag_delay <= 1'b0;
end

//wr_addr:ram写地址
always@(posedge sys_clk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        wr_addr <=  14'd0;
    else    if((wr_addr == (PIC_SIZE - 1'b1)) && (pi_flag_delay == 1'b1))
        wr_addr <=  14'd0;
    else    if(pi_flag == 1'b1)
        wr_addr <=  wr_addr + 1'b1;

//rd_addr:ram读地址
always@(posedge lcd_pclk or  negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_addr <=  14'd0;
    else    if(rd_addr == (PIC_SIZE - 1'b1))
        rd_addr <=  14'd0;
    else    if(rd_en == 1'b1)
        rd_addr <=  rd_addr + 1'b1;
    else
        rd_addr <=  rd_addr;

//rd_en:ROM读使能
assign  rd_en = (((pixel_xpos >= (((h_disp - H_PIC)/2) - 1'b1))
               && (pixel_xpos < (((h_disp - H_PIC)/2) + H_PIC - 1'b1))) 
              &&((pixel_ypos >= ((v_disp - W_PIC)/2))
              && ((pixel_ypos < (((v_disp - W_PIC)/2) + W_PIC)))));

//pic_valid:图片数据有效信号                
always@(posedge lcd_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        pic_valid   <=  1'b0;
    else
        pic_valid   <=  rd_en;
//pixel_data:输出VGA显示图像数据
assign  pixel_data = (pic_valid == 1'b1) ? pic_data : pix_data;

always@(posedge lcd_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        pix_data    <=  8'd0;
    else    
        pix_data    <=  baby_blue;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//

//------------- ram_pic_inst -------------
ram_pic ram_pic_inst
(
    .clka    (sys_clk    ),    //输入RAM写时钟,50MHz,1bit
    .ena     (ena),
    .wea     (pi_flag_delay ),    //输入RAM写使能,1bit
    .addra   (wr_addr    ),    //输入RAM写地址,14bit
    .dina    (rgb_data    ),    //输入写入RAM的图片数据,8bit
    .clkb    (lcd_pclk    ),    //输入RAM读时钟,25MHz,1bit
    .addrb   (rd_addr    ),    //输入RAM读地址,14bit
    .enb     (enb),
    .doutb   (pic_data   )     //输出读取RAM的图片数据,8bit
);
endmodule
RGB模块代码也只放其中的一个模块代码。很简单,大家自己理解

效果验证

07acc7df64074f0729ea656b8dab9ed.jpg

工程源码

链接: https://pan.baidu.com/s/1UpxbxtBpMlphn2I-iXmaEA?pwd=dimx 提取码: dimx 复制这段内容后打开百度网盘手机App,操作更方便哦

写在最后

第一次认真的写,写的比较潦草,说一下初学的坑,一定要认真的仿真,看每一个波形,不能感觉好像对了,就急匆匆的烧录到板子里,最后大概率还是错的。😭

参考文献

[1] 正点原子. 达芬奇之FPGA开发指南
  [2] 野火. FPGA+Verilog开发实战指南——基于Xilinx+Spartan6
  [3] 韩彬.基于MATLAB与FPGA的图像处理教程

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值