FPGA实现图像的bit平面分层

  对于灰度图而言,一个像素点由8个 bit 组成。代替突出灰度级范围,我们可以突出特定 bit 来为整个图像外观作出贡献,8 bit灰度图可考虑分层到 1-8 共8个比特平面。很容易理解的是,4个高阶比特平面,特别是最后两个比特平面,包含了在视觉上很重要的大多数数据。而低阶比特平面则在图像上贡献了更精细的灰度细节。

  本篇博客在前面实现了灰度图的基础上,进一步探究 bit 平面分层的 FPGA 实现。由于Verilog中都是从0开始算起,所以bit平面我们定义为 0-7 层。

 

一、MATLAB实现

 1 clear all;
 2 
 3 RGB = imread('dollar.jpg');   %读取图片文件
 4 
 5 gray = rgb2gray(RGB);         %转换为灰度图像
 6 [ROW,COL,N] = size(gray);     %获得图片尺寸[高度,长度,维度]
 7 
 8 subplot(3,3,1);
 9 imshow(gray,[]);
10 title('原始图像');
11 
12 for k=1:8
13     for i=1:ROW
14         for j=1:COL
15             tmp(i,j) = bitget(gray(i,j),k);%将RGB(i,j)灰度值分解为二进制串,取第k位
16         end
17     end
18     subplot(3,3,k+1);
19     imshow(tmp,[]);
20     ind = num2str(k-1);
21     imti = ['',ind,'个位平面'];
22     title(imti);
23 end

点击运行,得到如下结果:

 

二、FPGA实现

image

  当看到 MATLAB 实现的代码时,我们会觉得如果用 Verilog 来实现会非常困难,其实不然,Verilog 是非常适合做这种运算的,关键代码如下所示:

//**************************************************************************
// *** 名称 : bit_layer.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2020年3月
// *** 描述 : bit平面分层处理
//**************************************************************************

module bit_layer
//========================< 端口 >==========================================
(
input   wire                clk                     ,   //时钟
input   wire                rst_n                   ,   //复位
//Y分量 -------------------------------------------- 
input   wire                Y_de                    ,   //Y分量行同步
input   wire                Y_hsync                 ,   //Y分量场同步
input   wire                Y_vsync                 ,   //Y分量数据
input   wire    [ 7:0]      Y_data                  ,   //Y分量数据使能
//value ---------------------------------------------
input   wire    [ 7:0]      value                   ,   //阈值
//output --------------------------------------------
output  wire                bit_hsync               ,   //bit行同步
output  wire                bit_vsync               ,   //bit场同步
output  wire    [15:0]      bit_data                ,   //bit数据
output  wire                bit_de                      //bit数据使能             
);
//==========================================================================
//==    分层输出
//==========================================================================
assign bit_hsync = Y_hsync;
assign bit_vsync = Y_vsync;
assign bit_data  = {16{Y_data[value]}};
assign bit_de    = Y_de;



endmodule

  其中 Y_data 是我们获得 YCbCr 格式的图像数据之后取出的 8 bit Y分量,即灰度数据。

  由于FPGA编译综合、上板、传图,这一系列的操作实在过于繁琐,为了让实验更加简便,也为了能够更直观的对比各个层数的效果,我引入了一个外部按键。下载程序后初始层数为第0层,当按下按键时,层数会跟着转变,直到层数为第7层,之后按下按键又回到第0层,循环反复。

//**************************************************************************
// *** 名称 : key_value.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-08-10
// *** 描述 : 按键获得阈值
//**************************************************************************

module key_value
//========================< 端口 >==========================================
(
input   wire                clk                     ,
input   wire                rst_n                   ,
input   wire    [ 1:0]      key_vld                 ,
output  reg     [ 7:0]      value                  
);
//==========================================================================
//==    代码
//==========================================================================
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        value <= 8'd0;
    end
    else if(key_vld[0]) begin
        value <= value + 1;
    end
    else if(key_vld[1]) begin
        value <= value - 1;
    end
    else if(value>=8) begin         //value范围0-7
        value <= 8'd0;
    end
end



endmodule

  此外又引入了一个数码管显示模块,正好可以用来显示当前层数,不至于混淆,方便和MATLAB上的图案做对比。

//**************************************************************************
// *** 名称 : SEG_driver.v
// *** 描述 : 数码管控制器,进行位选和段选编码
//**************************************************************************

module SEG_driver
//========================< 端口 >==========================================
(
input                       clk             ,   //时钟
input                       rst_n           ,   //复位
//-------------------------------------------
input         [19:0]        data            ,   //6位数码管要显示的数值
input         [ 5:0]        point           ,   //小数点具体显示的位置,从高到低,高电平有效
input                       sign            ,   //符号位(高电平显示“-”号)
output   reg  [ 5:0]        seg_sel         ,   //数码管位选,最左侧数码管为最高位
output   reg  [ 7:0]        seg_led             //数码管段选
);
//========================< 参数 >==========================================
localparam  CLK_DIVIDE      = 4'd10         ;   // 时钟分频系数
localparam  MAX_NUM         = 13'd5000      ;   // 对数码管驱动时钟(5MHz)计数1ms所需的计数值
//========================< 信号 >==========================================
reg    [ 3:0]               clk_cnt         ;   // 时钟分频计数器
reg                         dri_clk         ;   // 数码管的驱动时钟,5MHz
reg    [23:0]               num             ;   // 24位bcd码寄存器
reg    [12:0]               cnt0            ;   // 数码管驱动时钟计数器
reg                         flag            ;   // 标志信号(标志着cnt0计数达1ms)
reg    [ 2:0]               cnt_sel         ;   // 数码管位选计数器
reg    [ 3:0]               num_disp        ;   // 当前数码管显示的数据
reg                         dot_disp        ;   // 当前数码管显示的小数点
wire   [ 3:0]               data0           ;   // 个位数
wire   [ 3:0]               data1           ;   // 十位数
wire   [ 3:0]               data2           ;   // 百位数
wire   [ 3:0]               data3           ;   // 千位数
wire   [ 3:0]               data4           ;   // 万位数
wire   [ 3:0]               data5           ;   // 十万位数
//==========================================================================
//==    提取显示数值所对应的十进制数的各个位
//==========================================================================
assign  data0 = data % 4'd10;               // 个位数
assign  data1 = data / 4'd10 % 4'd10;       // 十位数
assign  data2 = data / 7'd100 % 4'd10;      // 百位数
assign  data3 = data / 10'd1000 % 4'd10 ;   // 千位数
assign  data4 = data / 14'd10000 % 4'd10;   // 万位数
assign  data5 = data / 17'd100000;          // 十万位数
//==========================================================================
//==    对系统时钟10分频,得到的频率为5MHz的数码管驱动时钟dri_clk
//==========================================================================
always @(posedge clk or negedge rst_n) begin
   if(!rst_n) begin
       clk_cnt <= 4'd0;
       dri_clk <= 1'b1;
   end
   else if(clk_cnt == CLK_DIVIDE/2 - 1'd1) begin
       clk_cnt <= 4'd0;
       dri_clk <= ~dri_clk;
   end
   else begin
       clk_cnt <= clk_cnt + 1'b1;
       dri_clk <= dri_clk;
   end
end
//==========================================================================
//==    将20位2进制数转换为8421bcd码(即使用4位二进制数表示1位十进制数)
//==========================================================================
always @ (posedge dri_clk or negedge rst_n) begin
    if (!rst_n)
        num <= 24'b0;
    else begin
        if (data5 || point[5]) begin     //如果显示数据为6位十进制数,
            num[23:20] <= data5;         //则依次给6位数码管赋值
            num[19:16] <= data4;
            num[15:12] <= data3;
            num[11:8]  <= data2;
            num[ 7:4]  <= data1;
            num[ 3:0]  <= data0;
        end
        else begin                         
            if (data4 || point[4]) begin //如果显示数据为5位十进制数,则给低5位数码管赋值
                num[19:0] <= {data4,data3,data2,data1,data0};
                if(sign)                    
                    num[23:20] <= 4'd11; //如果需要显示负号,则最高位(第6位)为符号位
                else
                    num[23:20] <= 4'd10; //不需要显示负号时,则第6位不显示任何字符
            end
            else begin                   //如果显示数据为4位十进制数,则给低4位数码管赋值
                if (data3 || point[3]) begin
                    num[15: 0] <= {data3,data2,data1,data0};
                    num[23:20] <= 4'd10; //第6位不显示任何字符
                    if(sign)             //如果需要显示负号,则最高位(第5位)为符号位
                        num[19:16] <= 4'd11;
                    else                 //不需要显示负号时,则第5位不显示任何字符
                        num[19:16] <= 4'd10;
                end
                else begin               //如果显示数据为3位十进制数,则给低3位数码管赋值
                    if (data2 || point[2]) begin
                        num[11: 0] <= {data2,data1,data0};
                                         //第6、5位不显示任何字符
                        num[23:16] <= {2{4'd10}};
                        if(sign)         //如果需要显示负号,则最高位(第4位)为符号位
                            num[15:12] <= 4'd11;
                        else             //不需要显示负号时,则第4位不显示任何字符
                            num[15:12] <= 4'd10;
                    end
                    else begin           //如果显示数据为2位十进制数,则给低2位数码管赋值
                        if (data1 || point[1]) begin
                            num[ 7: 0] <= {data1,data0};
                                         //第6、5、4位不显示任何字符
                            num[23:12] <= {3{4'd10}};
                            if(sign)     //如果需要显示负号,则最高位(第3位)为符号位
                                num[11:8]  <= 4'd11;
                            else         //不需要显示负号时,则第3位不显示任何字符
                                num[11:8] <=  4'd10;
                        end
                        else begin       //如果显示数据为1位十进制数,则给最低位数码管赋值
                            num[3:0] <= data0;
                                         //第6、5位不显示任何字符
                            num[23:8] <= {4{4'd10}};
                            if(sign)     //如果需要显示负号,则最高位(第2位)为符号位
                                num[7:4] <= 4'd11;
                            else         //不需要显示负号时,则第2位不显示任何字符
                                num[7:4] <= 4'd10;
                        end
                    end
                end
            end
        end
    end
end
//==========================================================================
//==    每当计数器对数码管驱动时钟计数时间达1ms,输出一个时钟周期的脉冲信号
//==========================================================================
always @ (posedge dri_clk or negedge rst_n) begin
    if (rst_n == 1'b0) begin
        cnt0 <= 13'b0;
        flag <= 1'b0;
     end
    else if (cnt0 < MAX_NUM - 1'b1) begin
        cnt0 <= cnt0 + 1'b1;
        flag <= 1'b0;
     end
    else begin
        cnt0 <= 13'b0;
        flag <= 1'b1;
     end
end
//==========================================================================
//==    cnt_sel从0计数到5,用于选择当前处于显示状态的数码管
//==========================================================================
always @ (posedge dri_clk or negedge rst_n) begin
    if (rst_n == 1'b0)
        cnt_sel <= 3'b0;
    else if(flag) begin
        if(cnt_sel < 3'd5)
            cnt_sel <= cnt_sel + 1'b1;
        else
            cnt_sel <= 3'b0;
    end
    else
        cnt_sel <= cnt_sel;
end
//==========================================================================
//==    控制数码管位选信号,使6位数码管轮流显示
//==========================================================================
always @ (posedge dri_clk or negedge rst_n) begin
    if(!rst_n) begin
        seg_sel  <= 6'b111111;              //位选信号低电平有效
        num_disp <= 4'b0;           
        dot_disp <= 1'b1;                   //共阳极数码管,低电平导通
    end
    else begin
        case (cnt_sel)
            3'd0 :begin
                seg_sel  <= 6'b111110;  //显示数码管最低位
                num_disp <= num[3:0] ;  //显示的数据
                dot_disp <= ~point[0];  //显示的小数点
            end
            3'd1 :begin
                seg_sel  <= 6'b111101;  //显示数码管第1位
                num_disp <= num[7:4] ;
                dot_disp <= ~point[1];
            end
            3'd2 :begin
                seg_sel  <= 6'b111011;  //显示数码管第2位
                num_disp <= num[11:8];
                dot_disp <= ~point[2];
            end
            3'd3 :begin
                seg_sel  <= 6'b110111;  //显示数码管第3位
                num_disp <= num[15:12];
                dot_disp <= ~point[3];
            end
            3'd4 :begin
                seg_sel  <= 6'b101111;  //显示数码管第4位
                num_disp <= num[19:16];
                dot_disp <= ~point[4];
            end
            3'd5 :begin
                seg_sel  <= 6'b011111;  //显示数码管最高位
                num_disp <= num[23:20];
                dot_disp <= ~point[5];
            end
            default :begin
                seg_sel  <= 6'b111111;
                num_disp <= 4'b0;
                dot_disp <= 1'b1;
            end
        endcase
    end
end
//==========================================================================
//==    控制数码管段选信号,显示字符
//==========================================================================
always @ (posedge dri_clk or negedge rst_n) begin
    if (!rst_n)
        seg_led <= 8'hc0;
    else begin
        case (num_disp)
            4'd0 : seg_led <= {dot_disp,7'b1000000}; //显示数字 0
            4'd1 : seg_led <= {dot_disp,7'b1111001}; //显示数字 1
            4'd2 : seg_led <= {dot_disp,7'b0100100}; //显示数字 2
            4'd3 : seg_led <= {dot_disp,7'b0110000}; //显示数字 3
            4'd4 : seg_led <= {dot_disp,7'b0011001}; //显示数字 4
            4'd5 : seg_led <= {dot_disp,7'b0010010}; //显示数字 5
            4'd6 : seg_led <= {dot_disp,7'b0000010}; //显示数字 6
            4'd7 : seg_led <= {dot_disp,7'b1111000}; //显示数字 7
            4'd8 : seg_led <= {dot_disp,7'b0000000}; //显示数字 8
            4'd9 : seg_led <= {dot_disp,7'b0010000}; //显示数字 9
            4'd10: seg_led <= 8'b11111111;           //不显示任何字符
            4'd11: seg_led <= 8'b10111111;           //显示负号(-)
            default: 
                   seg_led <= {dot_disp,7'b1000000};
        endcase
    end
end



endmodule 

三、上板验证

  唉我的板卡还是有问题,但这个实验是灰度图为基础,总体效果还行,就是背景本来应该是黑色,显示成了红色,糟心。

  从视频可以看出,结果和我们的设计相符,各个层面的图像效果也和 MATLAB 做的一致,实验成功。

 

参考资料:

    [1] OpenS Lee:FPGA开源工作室(公众号)

    [2] https://blog.csdn.net/jzwong/article/details/52774393

 

posted @ 2020-03-06 10:12  咸鱼IC  阅读(1394)  评论(0)    收藏  举报