verilog实现rgb2gray

前言

   项目算法需求,需要将RGB彩色图像转换为灰度图像,算法原理是很简单的,但是对于刚接触FPGA的宝宝来说,进行时序的设计和调试还是不那么容易的,为了省事儿,就按照上一篇中值滤波(http://www.cnblogs.com/happyamyhope/p/5577898.html)的结构进行设计。开始的开始,只能根据已经做好的设计照葫芦画瓢,否则调试还是很繁琐的,主要是因为目前还是掌握不了时序设计的精髓和思路,慢慢来吧。

实验步骤:

1.实验原理介绍;

2.编写各模块的代码;

3.调试仿真,并与matlab中rgb2gray函数的结果进行比较;

实验过程:

1.实验原理介绍;

matlab中rgb2gray函数的原理还是比较简单的,最后输出的灰度图像是RGB三种颜色通道的加权和;

rgb2gray converts RGB values to grayscale values by forming a weighted sum of the R, G, and B components:

0.2989 * R + 0.5870 * G + 0.1140 * B

上面就是MATLAB中rgb2gray函数的算法原理;

本模块输入的是8bits的三通道彩色图像数据,输出的也是8bits的数据,至于整体算法模块的小数,有待到时候进行一下整合,改变数据的位数。

为了不涉及到小数,我们将数据整体左移16位即乘以65536,根据matlab中的算法原理进行设计:

gray <= 19589 * red + 38469 * green + 7471 * blue;
//gray <= 19595 * red + 38469 * green + 7472 * blue;

查看了一些资料,比如: http://www.cnblogs.com/diewcs/archive/2010/10/03/1841744.html

2.编写个模块的代码;

先将计数器控制模块和rgb2gray进行调试仿真,再将两个模块综合到一起进行调试仿真,得到最终的结果。

主要是计数器控制模块,如何正确得到正确时序的地址数据。

1)计数器控制模块:

module counter_ctrl(
  
  CLK,
  RSTn,
  iCall,
  iNxt_pix,
  oAddr,
  oDone
  
    );

input CLK;
input RSTn;
input iCall;
input iNxt_pix;
output [17:0] oAddr;
output oDone;

reg [17:0] imk;
reg isDone ;

reg start_sig_d;
  
wire start_sig_rising_vld;
  
always @ (posedge CLK or negedge RSTn)   //Asynchronous reset  
  if ( !RSTn )
     start_sig_d <= 0;
  else 
     start_sig_d <= iCall;
  
assign start_sig_rising_vld = iCall & (~start_sig_d);
  
always @ ( posedge CLK or negedge RSTn )
  if ( !RSTn ) begin
    imk <= 18'd0;
     isDone <= 1'b0;
  end
  else if ( start_sig_rising_vld )
     begin  
        imk <= 18'b1; 
        isDone <= 1'b1;    
    end    
  else if ( iNxt_pix )  // & ( imk != 166222 )
       begin  
          imk <= imk + 1'b1;
        isDone <= 1'b1; 
        end
     else isDone <= 1'b0;    
     
assign oAddr = imk;
assign oDone = isDone;

endmodule
View Code

模块通过计数控制得到将要读取图像数据的地址,需要注意的是何时开始获取数据初始地址,何时开始地址开始进行加一计数。

testbench模块:

module ctrl_tb;

    // Inputs
    reg CLK;
    reg RSTn;
    reg iCall;

    // Outputs
    wire [17:0] oAddr;
    wire oDone;

    // Instantiate the Unit Under Test (UUT)
    counter_ctrl uut (
        .CLK(CLK), 
        .RSTn(RSTn), 
        .iCall(iCall), 
        .oAddr(oAddr), 
        .oDone(oDone)
    );

    initial begin
        // Initialize Inputs
        CLK = 0;
        RSTn = 0;
        iCall = 0;

        // Wait 100 ns for global reset to finish
        #100;
        RSTn = 1;
        iCall = 1;
        
        // Add stimulus here

    end
 
 always #10 CLK = ~CLK;
 
 always @ ( posedge CLK or RSTn )
   if ( oDone )
      $display("%d
", oAddr );

 always @ ( posedge CLK or RSTn )
   if ( oAddr == 18'd200 )
      begin
        iCall <= 0;
        $stop;
      end
 
endmodule

2)rgb2gray算法模块;

// matlab algorithm:gray = 0.2989 * R + 0.5870 * G + 0.1140 * B;
// move left by 16bits: gray = 19589 * R + 38469 * G + 7471 * B;
module rgb2gray(

  CLK,
  RSTn,
  iCall,
  iRed,
  iGreen,
  iBlue,
  oGray,
  oDone
  
    );

input CLK;
input RSTn;
input iCall;
//input [23:0] iRGB;
input [7:0] iRed;
input [7:0] iGreen;
input [7:0] iBlue;

output [7:0] oGray;
output oDone;

reg [7:0] red;
reg [7:0] green;
reg [7:0] blue;

reg [23:0] gray;
reg [2:0] i;
reg isDone;

/********************************************************************************/ 

reg get_pix_vld;

always @ ( posedge CLK or negedge RSTn )
    if (!RSTn)
           get_pix_vld <= 1'b0;
     else if ( iCall )  
            get_pix_vld <= 1'b1;
     else if ( i==3'd2 ) 
            get_pix_vld <= 1'b0;

always @ ( posedge CLK or negedge RSTn )   
  if ( !RSTn ) begin                       
    red   <= 8'd0;                         
    green <= 8'd0;                         
     blue  <= 8'd0;                         
  end                                      
  else if ( iCall ) begin                  
    red   <= iRed  ;                       
    green <= iGreen;                       
     blue  <= iBlue ;                       
 end                                       
 
always @ ( posedge CLK or negedge RSTn )
  if ( !RSTn ) begin
    i <= 3'd0;
    gray <= 24'd0;
     isDone <= 1'b0;
  end
  else if ( get_pix_vld ) 
    case ( i ) 
       0: begin
            //gray <= 19595 * iRGB[23:16] + 38469 * iRGB[15:8] + 7472 * iRGB[7:0];
            gray <= 19589 * red + 38469 * green + 7471 * blue;
           //gray <= 19595 * red + 38469 * green + 7472 * blue;
          i <= i + 1'b1; 
            end
        1: begin isDone <= 1'b1; i <= i + 1'b1; end
        2: begin isDone <= 1'b0; i <= 3'd0; end
    endcase
     
assign oGray = ( gray >> 16 ) ;
assign oDone = isDone ;      

endmodule

将输入的三色通道图像数据转换为灰度图像。

3)创建IP ROM核,将彩色图象数据分通道储存在ROM中,具体操作步骤请参考    

    http://www.cnblogs.com/happyamyhope/p/5498745.html

图像大小是383*434,共R、G、B三个通道。

4)顶层模块:

将低层各个模块联系起来,得到rgb2gray的整体模块。

module rgb_gray(
  CLK,
  RSTn,
  Start,
  Gray,
  Done
    );

input CLK;
input RSTn;
input Start;

output [7:0] Gray;
output Done;

/*****************************************************************************/

wire [17:0]  addra ;
wire [7:0 ]  red   ;
wire [7:0 ]  green ;
wire [7:0 ]  blue  ;

imLr imLr_inst(
  .clka  ( CLK   ),
  .addra ( addra ),
  .douta ( red )
);

imLg imLg_inst(
  .clka  ( CLK   ),
  .addra ( addra ),
  .douta ( green )
);

imLb imLb_inst(
  .clka  ( CLK   ),
  .addra ( addra ),
  .douta ( blue  )
);

/*****************************************************************************/

wire done_ctrl;
wire done_gray;    

counter_ctrl counter_ctrl_inst( 

  .CLK     ( CLK       ), 
  .RSTn    ( RSTn      ),
  .iCall   ( Start     ),
  .iNxt_pix( done_gray ),
  .oAddr   ( addra     ),
  .oDone   ( done_ctrl ) 
    );
     
wire [7:0] gray;

rgb2gray rgb2gray_inst(

  .CLK    ( CLK        ) ,
  .RSTn   ( RSTn       ) ,
  .iCall  ( done_ctrl  ) ,
  .iRed   ( red        ) ,
  .iGreen ( green      ) ,
  .iBlue  ( blue       ) ,
  .oGray  ( gray       ) ,
  .oDone  ( done_gray  )  
    );

/*****************************************************************************/

assign Gray = gray;
assign Done = done_gray;

endmodule
View Code

顶层模块需要注意不同模块之间数据的连接,以数据线的形式相连,故要使用wire类型。

5)testbench模块:

module rgb_gray_tb;

    // Inputs
    reg CLK;
    reg RSTn;
    reg Start;
   reg [17:0] pix_cnt;
    
    // Outputs
    wire [7:0] Gray;
    wire Done ;

   integer fout ;
    
    // Instantiate the Unit Under Test (UUT)
    rgb_gray rgb_gray_inst (
        .CLK(CLK), 
        .RSTn(RSTn), 
        .Start(Start), 
        .Gray(Gray), 
        .Done(Done)
    );

    initial begin
        // Initialize Inputs
        CLK = 0;
        RSTn = 1;
        Start = 0;
        pix_cnt = 0;
        
        fout = $fopen( "rgb2gray_re.txt" );
        
        // Wait 100 ns for global reset to finish
        #100;
        RSTn = 0;
        Start = 1;
      pix_cnt = 0;
        
        // Add stimulus here
        #100;   // To start the system
        // Add stimulus here
        RSTn = 1;
        pix_cnt = 1;

    end
    
always #10 CLK = ~CLK;

always @ ( posedge CLK )
  if ( Done )
    pix_cnt <= pix_cnt + 1'b1;

always @ ( posedge CLK )
  if ( pix_cnt == 18'd166223 ) begin
    Start <= 0;
     $display("Image rgb2gray Completed!
");
     $display("The all time is %d 
",$time);
     $stop;
  end
 
always @ ( posedge CLK )
  if ( Done ) begin
     $fwrite(fout, "%d", Gray, "
");
     $display("%d,   %d ",pix_cnt, Gray);
  end
     
endmodule

结果图片:

        

前一张图片,在最后两个像素的时候代码出现了警告,不知道是什么原因,不过最后的结果没有错误。

后一张图片可以看出前几个像素点得到的算法结果。

3.调试仿真,并与matlab中rgb2gray函数的结果进行比较:

matlab的比较代码:

% code to create image data from rgb2gray txt file
clc; 
clear all; 
close all;

I_rgb = imread('imL.png');
subplot(2, 2, 1), imshow(I_rgb), title('imL-rgb')

I_gray = rgb2gray(I_rgb);
[m, n] = size(I_gray);
subplot(2, 2, 3), imshow(I_gray), title('imL-rgb2gray-mfunc')

rgb2gray_v_load = load('.
gb2gray_re.txt'); % verilog 产生的rgb2gray数据
rgb2gray_v = reshape(rgb2gray_v_load, n, m);
rgb2gray_v = uint8(rgb2gray_v');

diff = I_gray - rgb2gray_v;
subplot(2, 2, 4), imshow(rgb2gray_v), title('rgb2gray-verilog');

最后的数据结果显示,verilog的算法结果与matlab函数的处理结果相比较,差值在1个像素之内。

显示结果:

可以看出,最后的结果相差无几,Good!

实验结论:

因为有中值滤波的基础,基于此,实现rgb2gray的算法设计还是比较简单的,不过也还是弄得有点久,最重要的是时序的设计,在调试仿真的过程中让宝宝比较烦恼的就是时序问题,是在是搞不懂FPGA的时序到底是怎么回事儿,就算明白了时序的问题还会有一些该有的延时或者该考虑的时序没有考虑到,还有,就算考虑到了,可是编写代码还是硬伤。

加油吧,少年!

原文地址:https://www.cnblogs.com/happyamyhope/p/5725741.html