一, 前言
最近学习牟新刚编著《基于 FPGA 的数字图像处理原理及应用》的第六章直方图操作, 由于需要将捕获的图像转换为灰度图像, 因此在之前代码的基础上加入了 RGB 图像转灰度图像的算法实现.
2020-02-29 10:38:40
二, RGB 图像转灰度图像算法原理
将彩色图像转换为灰度图像的方法有两种, 一个是令 RGB 三个分量的数值相等. 输出后便可以得到灰度图像, 另一种是转换为 YCbCr 格式, 将 Y 分量提取出来, YCbCr 格式中的 Y 分量表示的是图
像的亮度和浓度, 所以只输出 Y 分量, 得到图像就是灰度图像.
YCbCr 是通过有序的三元组来表示的, 三元由 Y(Luminance),Cb(Chrominace-Blue) 和 Cr(Chrominace-Red) 组成, 其中 Y 表示颜色的明亮度和浓度, 而 Cb 和 Cr 则分别表示颜色的蓝色浓度
偏移量和红色浓度偏移量. 人的肉眼对由 YCbCr 色彩空间编码的视频中 Y 分量更敏感, 而 Cb 和 Cr 的微小变换不会引起视觉上的不同. 根据该原理, 通过对 Cb 和 Cr 进行子采样来减小图像的数据量. 使得
图像对存储需求和传输带宽的要求大大降低, 从而达到完成图像压缩的同时, 也保证了视觉上几乎没有损失的效果, 进而使得图像的传输速度更快, 存储更加方便.
官方给的 RGB888 转 YCrCb 的算法公式:
- Y = 0.299R + 0.587G + 0.114B
- Cb = 0.568(B-Y) + 128 = -0.172R -0.339G + 0.511B + 128
- Cr = 0.713(R -Y) + 128 = 0.511R - 0.428G - 0.083B + 128
扩大 256 倍 →
- Y = ((77*R + 150*G + 29*B)>>8)
- Cb = ((-43*B - 85*G + 128*B)>>8) + 128
- Cr = ((128*R - 107*G - 21*B)>>8) + 128
三, 代码实现
代码分为三部分, 包括视频码流生成 image_src.v, 视频捕获 video_cap.v, 彩色图像转灰度图像 RGB2YCbCr.v 及顶层文件 rgb2gray.v; 同时为了仿真及测试结果分析提供相应的 matlab 文件及用于
Modelsim 仿真的 rgb2gray.do 文件.
(1) 频码流生成 image_src.v, 生成 640*512 的 24Bit RGB 图像数据流;
- /*
- ***********************************************************************************************************
- ** Input file: None
- ** Component name: image_src.v
- ** Author: zhengXiaoliang
- ** Company: WHUT
- ** Description: to simulate dvd stream
- ***********************************************************************************************************
- */
- `timescale 1ps/1ps
- `define SEEK_SET 0
- `define SEEK_CUR 1
- `define SEEK_END 2
- module image_src(
- reset_l, // 全局复位
- clk, // 同步时钟
- src_sel, // 数据源通道选择
- test_vsync, // 场同步输出
- test_dvalid, // 像素有效输出
- test_data, // 像素数据输出
- clk_out // 像素时钟输出
- );
- parameter iw = 640; // 默认视频宽度
- parameter ih = 512; // 默认视频高度
- parameter dw = 8; // 默认像素数据位宽
- parameter h_total = 1440; // 行总数
- parameter v_total = 600; // 垂直总数
- parameter sync_b = 5; // 场前肩
- parameter sync_e = 55; // 场同步脉冲
- parameter vld_b = 65; // 场后肩
- //port decleared
- input reset_l,clk;
- input [3:0] src_sel; //to select the input file
- output test_vsync, test_dvalid,clk_out;
- output [dw-1:0] test_data;
- //variable decleared
- reg [dw-1:0] test_data_reg;
- reg test_vsync_temp;
- reg test_dvalid_tmp;
- reg [1:0] test_dvalid_r;
- reg [10:0] h_cnt;
- reg [10:0] v_cnt;
- integer fp_r;
- integer cnt = 0;
- // 输出像素时钟
- assign clk_out = clk; //output the dv clk
- // 输出像素数据
- assign test_data = test_data_reg; //test data output
- // 当行同步有效时, 从文件读取像素数据输出到数据线上
- always@(posedge clk or posedge test_vsync_temp)begin
- if(((~(test_vsync_temp))) == 1'b0) // 场同步清零文件指针
- cnt <= 0; //clear file pointer when a new frame comes
- else begin
- if(test_dvalid_tmp == 1'b1)begin // 行同步有效, 说明当前时钟数据有效
- case(src_sel) // 选择不同的数据源
- 4'b0000: fp_r = $fopen("E:/Modelsim/rgb2gray/sim/rgb_image.txt","r");
- 4'b0001: fp_r = $fopen("txt_source/test_scr1.txt","r");
- 4'b0010: fp_r = $fopen("txt_source/test_scr2.txt","r");
- 4'b0011: fp_r = $fopen("txt_source/test_scr3.txt","r");
- 4'b0100: fp_r = $fopen("txt_source/test_scr4.txt","r");
- 4'b0101: fp_r = $fopen("txt_source/test_scr5.txt","r");
- 4'b0110: fp_r = $fopen("txt_source/test_scr6.txt","r");
- 4'b0111: fp_r = $fopen("txt_source/test_scr7.txt","r");
- 4'b1000: fp_r = $fopen("txt_source/test_scr8.txt","r");
- 4'b1001: fp_r = $fopen("txt_source/test_scr9.txt","r");
- 4'b1010: fp_r = $fopen("txt_source/test_scr10.txt","r");
- 4'b1011: fp_r = $fopen("txt_source/test_scr11.txt","r");
- 4'b1100: fp_r = $fopen("txt_source/test_scr12.txt","r");
- 4'b1101: fp_r = $fopen("txt_source/test_scr13.txt","r");
- 4'b1110: fp_r = $fopen("txt_source/test_scr14.txt","r");
- 4'b1111: fp_r = $fopen("txt_source/test_scr15.txt","r");
- default: fp_r = $fopen("txt_source/test_src3.txt","r");
- endcase
- $fseek(fp_r,cnt,0); // 查找当前需要读取的文件位置
- $fscanf(fp_r,"%02x\n",test_data_reg); // 将数据按指定格式读入 test_data_reg 寄存器
- cnt <= cnt + 4; // 移动文件指针到下一个数据
- $fclose(fp_r); // 关闭文件
- $display("h_cnt = %d,v_cnt = %d, pixdata = %d",h_cnt,v_cnt,test_data_reg); //for debug use
- end
- end
- end
- // 水平计数器, 每来一个时钟就 + 1, 加到 h_total 置零重新计数
- always@(posedge clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- h_cnt <= #1 {11{1'b0}};
- else begin
- if(h_cnt == ((h_total -1)))
- h_cnt <= #1 {11{1'b0}};
- else
- h_cnt <= #1 h_cnt + 11'b00000000001;
- end
- end
- // 垂直计数器: 水平计数器计满后 + 1, 计满后清零
- always@(posedge clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- v_cnt <= #1 {11{1'b0}};
- else begin
- if(h_cnt == ((h_total - 1)))begin
- if(v_cnt == ((v_total - 1)))
- v_cnt <= #1 {11{1'b0}};
- else
- v_cnt <= #1 v_cnt + 11'b00000000001;
- end
- end
- end
- // 场同步信号生成
- always@(posedge clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- test_vsync_temp <= #1 1'b1;
- else begin
- if(v_cnt>= sync_b & v_cnt <= sync_e)
- test_vsync_temp <= #1 1'b1;
- else
- test_vsync_temp <= #1 1'b0;
- end
- end
- assign test_vsync = (~test_vsync_temp);
- // 水平同步信号生成
- always@(posedge clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- test_dvalid_tmp <= #1 1'b0;
- else begin
- if(v_cnt>= vld_b & v_cnt <((vld_b + ih)))begin
- if(h_cnt == 10'b0000000000)
- test_dvalid_tmp <= #1 1'b1;
- else if(h_cnt == iw)
- test_dvalid_tmp <= #1 1'b0;
- end
- else
- test_dvalid_tmp <= #1 1'b0;
- end
- end
- // 水平同步信号输出
- assign test_dvalid = test_dvalid_tmp;
- always@(posedge clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- test_dvalid_r <= #1 2'b00;
- else
- test_dvalid_r <= #1 ({test_dvalid_r[0],test_dvalid_tmp});
- end
- endmodule
(2) 视频捕获 video_cap.v, 捕获 RGB 图像数据, 并输出 RGB888 格式的 24bit 数据码流;
- //2020-02-17
- //Huang.Wei
- `timescale 1ps/1ps
- module video_cap(
- reset_l, // 异步复位信号
- DVD, // 输入视频流
- DVSYN, // 输入场同步信号
- DHSYN, // 输入行同步
- DVCLK, // 输入 DV 时钟
- cap_dat, // 输出 RGB 通道像素流, 24 位
- cap_dvalid, // 输出数据有效
- cap_vsync, // 输出场同步
- cap_clk, // 本地逻辑时钟
- img_en,
- cmd_rdy, // 命令行准备好, 代表可以读取
- cmd_rdat, // 命令行数据输出
- cmd_rdreq // 命令行读取请求
- );
- parameter TRIG_VALUE = 250; // 读触发值, 也即行消隐时间
- parameter IW = 640; // 图像宽度
- parameter IH = 512; // 图像高度
- parameter DW_DVD = 8; // 输入像素宽度
- parameter DVD_CHN = 3; // 输入像素通道: RGB 3 通道
- parameter DW_LOCAL = 24; // 本地捕获的数据宽度 24 位
- parameter DW_CMD = 24; // 命令行数据宽度
- parameter VSYNC_WIDTH = 100; //9 // 场同步宽度, 9 个时钟
- parameter CMD_FIFO_DEPTH = 1024; // 行缓存位宽
- parameter CMD_FIFO_DW_DEPTH = 10;
- parameter IMG_FIFO_DEPTH = 512; // 异步 fifo 深度, 选 512
- parameter IMG_FIFO_DW_DEPTH = 9;
- //Port Declared
- input reset_l;
- input [DW_DVD-1:0] DVD;
- input DVSYN;
- input DHSYN;
- input DVCLK;
- output reg [DW_LOCAL-1:0] cap_dat;
- output reg cap_dvalid;
- output cap_vsync;
- input cap_clk;
- output img_en;
- output reg cmd_rdy;
- output [DW_CMD-1:0] cmd_rdat;
- input cmd_rdreq;
- // 首先完成数据位宽转换
- wire pixel_clk;
- reg [1:0] vc_reset;
- reg dv_enable;
- reg [9:0] count_lines;
- reg cmd_en;
- reg cmd_wrreq;
- reg cmd_wrreq_r;
- reg rst_cmd_fifo;
- wire [DW_CMD-1:0] cmd_din;
- reg [DW_CMD-1:0] cmd_dat;
- assign pixel_clk = DVCLK;
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- begin
- vc_reset <= 2'b00;
- dv_enable <= 1'b0;
- end
- else
- begin
- dv_enable <= #1 1'b1;
- if((~(DVSYN)) == 1'b1 & dv_enable == 1'b1)
- vc_reset <= #1 ({vc_reset[0],1'b1});
- end
- end
- reg [DW_DVD-1:0] vd_r[0:DVD_CHN-1];
- reg [DVD_CHN*DW_DVD-1:0] data_merge;
- reg vsync;
- reg [DVD_CHN:0] hsync_r;
- reg mux;
- reg mux_r;
- // 缓存场同步和行同步信号
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- begin
- vsync <= 1'b0;
- hsync_r <= {DVD_CHN+1{1'b0}};
- end
- else
- begin
- vsync <= #1 DVSYN;
- hsync_r <= #1 {hsync_r[DVD_CHN-1:0],DHSYN};
- end
- end
- // 像素通道计算, 指示当前像素属于 RGB 那个通道
- reg [DVD_CHN:0] pixel_cnt;
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- begin
- pixel_cnt <= {DVD_CHN+1{1'b1}};
- end
- else
- begin
- if(hsync_r[1] == 1'b0)
- pixel_cnt <= #1 {DVD_CHN+1{1'b1}};
- else
- if(pixel_cnt == DVD_CHN -1)
- pixel_cnt <= #1 {DVD_CHN+1{1'b0}};
- else
- pixel_cnt <= #1 pixel_cnt + 1'b1;
- end
- end
- integer i;
- integer j;
- // 缓存输入 DV, 获得 3 个 RGB 通道值
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l)))==1'b1)
- for(i=0;i<DVD_CHN;i=i+1)
- vd_r[i] <= {DW_DVD{1'b0}};
- else
- begin
- vd_r[0] <= #1 DVD;
- for(j=1;j<DVD_CHN;j=j+1)
- vd_r[j] <= vd_r[j-1];
- end
- end
- //RGB 合并有效信号
- wire mux_valid;
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- mux <= 1'b0;
- else begin
- if(hsync_r[DVD_CHN-2] == 1'b0)
- mux <= #1 1'b1;
- else
- if(mux_valid == 1'b1)
- mux <= #1 1'b1;
- else
- mux <= #1 1'b0;
- end
- end
- always@(posedge pixel_clk)
- mux_r <= mux;
- wire [DVD_CHN*DW_DVD-1:0] dvd_temp;
- wire mux_1st;
- assign mux_1st = (~hsync_r[DVD_CHN]) & (hsync_r[DVD_CHN-1]);
- // 一个颜色通道
- generate
- if(DVD_CHN == 1)
- begin: xhdl1
- assign mux_valid = hsync_r[0];
- assign dvd_temp = vd_r[0];
- end
- endgenerate
- // 两个颜色通道
- generate
- if(DVD_CHN == 2)
- begin: xhdl2
- assign mux_valid = mux_1st | (pixel_cnt == DVD_CHN - 1);
- assign dvd_temp = {vd_r[0],vd_r[1]};
- end
- endgenerate
- // 三个颜色通道, 将三路 RBG 数据合并到 dvd_temp 信号中
- generate
- if(DVD_CHN == 3)
- begin: xhdl3
- assign mux_valid = mux_1st | (pixel_cnt == 0);
- assign dvd_temp = {vd_r[2],vd_r[1],vd_r[0]};
- end
- endgenerate
- // 四个颜色通道
- generate
- if(DVD_CHN == 4)
- begin: xhdl4
- assign mux_valid = mux_1st | (pixel_cnt == 1);
- assign dvd_temp = {vd_r[0],vd_r[1],vd_r[2],vd_r[3]};
- end
- endgenerate
- // 将合并后的数据存入寄存器
- always@(posedge pixel_clk or negedge reset_l)begin
- if(((~(reset_l))) == 1'b1)
- data_merge <= {DVD_CHN*DW_DVD{1'b0}};
- else
- begin
- if(hsync_r[DVD_CHN] == 1'b1 & mux == 1'b1)
- data_merge <= #1 dvd_temp;
- end
- end
- // 将合并后的数据打入异步 fifo
- wire [DW_DVD*DVD_CHN-1:0] fifo_din;
- wire [DW_DVD*DVD_CHN-1:0] fifo_dout;
- wire [IMG_FIFO_DW_DEPTH-1:0] rdusedw;
- reg [9:0] trig_cnt;
- wire fifo_empty;
- reg fifo_wrreq;
- reg fifo_wrreq_r;
- //wire fifo_wrreq;
- //assign fifo_wrreq = mux & hsync_r[DVD_CHN];
- reg fifo_rdreq;
- reg fifo_rdreq_r1;
- reg rst_fifo;
- // 实例化异步 fifo
- cross_clock_fifo img_fifo(
- .data(fifo_din),
- .rdclk(cap_clk),
- .rdreq(fifo_rdreq),
- .wrclk(pixel_clk),
- .wrreq(fifo_wrreq),
- .q(fifo_dout),
- .rdempty(fifo_empty),
- .rdusedw(rdusedw),
- .aclr(rst_fifo)
- );
- /*
- defparam img_fifo.DW = DW_DVD*DVD_CHN;
- defparam img_fifo.DEPTH = IMG_FIFO_DEPTH;
- defparam img_fifo.DW_DEPTH = IMG_FIFO_DW_DEPTH;
- */
- assign fifo_din = data_merge;
- //RGB 合并时写入 fifo
- always@(posedge pixel_clk or negedge reset_l)begin
- if(reset_l == 1'b0)begin
- fifo_wrreq <= #1 1'b0;
- fifo_wrreq_r <= #1 1'b0;
- end
- else begin
- fifo_wrreq <= hsync_r[DVD_CHN] & mux_r;
- fifo_wrreq_r <= fifo_wrreq;
- end
- end
- //fifo 中数据大于触发值时开始读, 读完一行停止
- always@(posedge cap_clk or negedge reset_l)begin
- if(reset_l == 1'b0)
- fifo_rdreq <= #1 1'b0;
- else
- begin
- if((rdusedw>= TRIG_VALUE) & (fifo_empty == 1'b0))
- fifo_rdreq <= #1 1'b1;
- else if(trig_cnt == (IW - 1))
- fifo_rdreq <= #1 1'b0;
- end
- end
- // 读计数
- always@(posedge cap_clk or negedge reset_l)begin
- if(reset_l == 1'b0)
- trig_cnt <= #1 {10{1'b0}};
- else
- begin
- if(fifo_rdreq == 1'b0)
- trig_cnt <= #1 {10{1'b0}};
- else
- if(trig_cnt == (IW - 1))
- trig_cnt <= #1 {10{1'b0}};
- else
- trig_cnt <= #1 trig_cnt + 10'b0000000001;
- end
- end
- wire [DW_LOCAL-1:0] img_din;
- assign img_din = ((cmd_en == 1'b0)) ? fifo_dout[DW_LOCAL-1:0] : {DW_LOCAL{1'b0}};
- assign cmd_din = ((cmd_en == 1'b1)) ? fifo_dout[DW_CMD-1:0] : {DW_CMD{1'b0}};
- // 生成场同步信号, 数据有效信号及像素数据输出
- reg vsync_async;
- reg vsync_async_r1;
- reg [VSYNC_WIDTH:0] vsync_async_r;
- reg cap_vsync_tmp;
- always@(posedge cap_clk or negedge reset_l)begin
- if(reset_l == 1'b0)
- begin
- vsync_async <= #1 1'b0;
- vsync_async_r1 <= #1 1'b0;
- vsync_async_r <= {VSYNC_WIDTH+1{1'b0}};
- cap_vsync_tmp <= #1 1'b0;
- end
- else
- begin
- vsync_async <= #1 (~vsync);
- vsync_async_r1 <= #1 vsync_async;
- vsync_async_r <= {vsync_async_r[VSYNC_WIDTH-1:0], vsync_async_r1};
- if(vsync_async_r[1] == 1'b1 & vsync_async_r[0] == 1'b0)
- cap_vsync_tmp <= #1 1'b1;
- else if(vsync_async_r[VSYNC_WIDTH] == 1'b0 & vsync_async_r[0] == 1'b0)
- cap_vsync_tmp <= #1 1'b0;
- end
- end
- assign cap_vsync = cap_vsync_tmp;
- always@(posedge cap_clk or negedge reset_l)begin
- if(reset_l==1'b0)
- begin
- cap_dat <= #1 {DW_LOCAL{1'b0}};
- fifo_rdreq_r1 <= #1 1'b0;
- cap_dvalid <= #1 1'b0;
- cmd_dat <= #1 {DW_CMD{1'b0}};
- cmd_wrreq <= #1 1'b0;
- cmd_wrreq_r <= #1 1'b0;
- end
- else
- begin
- cap_dat <= #1 img_din;
- fifo_rdreq_r1 <= #1 fifo_rdreq;
- cap_dvalid <= #1 fifo_rdreq_r1 & (~(cmd_en));
- cmd_dat <= #1 cmd_din;
- cmd_wrreq <= #1 fifo_rdreq_r1 & cmd_en;
- cmd_wrreq_r <= cmd_wrreq;
- end
- end
- //frame count and img_en signal
- reg [1:0] fr_cnt;
- reg img_out_en;
- always@(posedge cap_clk)begin
- if(vc_reset[1] == 1'b0)
- begin
- img_out_en <= 1'b0;
- fr_cnt <= {2{1'b0}};
- end
- else
- begin
- if(vsync_async_r1 == 1'b0 & vsync_async == 1'b1)
- begin
- fr_cnt <= fr_cnt + 2'b01;
- if(fr_cnt == 2'b11)
- img_out_en <= 1'b1;
- end
- end
- end
- assign img_en = img_out_en;
- // 行计数, 确定 cmd 数据到来时刻
- always@(posedge cap_clk)begin
- if(cap_vsync_tmp == 1'b1)
- begin
- count_lines <= {10{1'b0}};
- cmd_en <= 1'b0;
- cmd_rdy <= 1'b0;
- end
- begin
- if(fifo_rdreq_r1 == 1'b1 & fifo_rdreq == 1'b0)
- count_lines <= #1 count_lines + 4'h1;
- if(count_lines == (IH - 2))
- rst_cmd_fifo <= 1'b1;
- else
- rst_cmd_fifo <= 1'b0;
- if(count_lines>= IH)
- cmd_en <= #1 1'b1;
- if(cmd_wrreq_r == 1'b1 & cmd_wrreq == 1'b0)
- cmd_rdy <= 1'b1;
- if(cmd_wrreq_r == 1'b1 & cmd_wrreq == 1'b0)
- rst_fifo <= 1'b1;
- else
- rst_fifo <= 1'b0;
- end
- end
- //Instance a line buffer to store the cmd line
- line_buffer_new
- cmd_buf(
- .aclr(rst_cmd_fifo),
- .clock(cap_clk),
- .data(cmd_dat),
- .rdreq(cmd_rdreq),
- .wrreq(cmd_wrreq),
- .empty(),
- .full(),
- .q(cmd_rdat),
- .usedw()
- );
- /*
- defparam cmd_buf.DW = DW_CMD;
- defparam cmd_buf.DEPTH = CMD_FIFO_DEPTH;
- defparam cmd_buf.DW_DEPTH = CMD_FIFO_DW_DEPTH;
- defparam cmd_buf.IW = IW;
- */
- endmodule
(3) 彩色图像转灰度图像 RGB2YCbCr.v, 实现 RGB888 到 YCbCr 图像格式的转换, 并输出三个通道的数据;
- //==================================================================================================//
- //FileName: RGB2YCrCb.v
- /*
- 官方给的 RGB888 to YCbCr 的计算公式:
- Y = 0.299R + 0.587G + 0.114B
- Cb = 0.568(B - Y) + 128 = -0.172R - 0.339G + 0.511B + 128
- Cr = 0.713(R -Y) + 128 = 0.511R - 0.428G - 0.083B + 128
- =>
- Y = ((77*R + 150*G + 29*B)>>8);
- Cb = ((-43*R - 85*G + 128*B)>>8) + 128;
- Cr = ((128*R - 107*G - 21*B)>>8) + 128;
- */
- //Date: 2020-02-28
- //==================================================================================================//
- `timescale 1ps/1ps
- module RGB2YCrCb(
- RESET, // 异步复位信号
- RGB_CLK, // 输入像素时钟
- RGB_VSYNC, // 输入场同步信号
- RGB_DVALID, // 输入数据有信号
- RGB_DAT, // 输入 RGB 通道像素流, 24 位
- YCbCr_CLK, // 输出像素时钟
- YCbCr_VSYNC, // 输出场同步信号
- YCbCr_DVALID, // 输出数据有效信号
- Y_DAT, // 输出 Y 分量
- Cb_DAT, // 输出 Cb 分量
- Cr_DAT // 输出 Cr 分量
- );
- parameter RGB_DW = 24; // 输入像素宽度
- parameter YCbCr_DW = 8; // 输出像素宽度
- //Port Declared
- input RESET;
- input RGB_CLK;
- input RGB_VSYNC;
- input RGB_DVALID;
- input [RGB_DW-1:0]RGB_DAT;
- output YCbCr_CLK;
- output YCbCr_VSYNC;
- output YCbCr_DVALID;
- output reg [YCbCr_DW-1:0] Y_DAT;
- output reg [YCbCr_DW-1:0] Cb_DAT;
- output reg [YCbCr_DW-1:0] Cr_DAT;
- reg [2*YCbCr_DW-1:0] RGB_R1,RGB_R2,RGB_R3;
- reg [2*YCbCr_DW-1:0] RGB_G1,RGB_G2,RGB_G3;
- reg [2*YCbCr_DW-1:0] RGB_B1,RGB_B2,RGB_B3;
- reg [2*YCbCr_DW-1:0] IMG_Y,IMG_Cb,IMG_Cr;
- reg [2:0] VSYNC_R;
- reg [2:0] DVALID_R;
- //Step1: Consume 1Clk
- always@(posedge RGB_CLK or negedge RESET)begin
- if(!RESET)begin
- RGB_R1 <= {2*YCbCr_DW{1'b0}};
- RGB_R2 <= {2*YCbCr_DW{1'b0}};
- RGB_R3 <= {2*YCbCr_DW{1'b0}};
- RGB_G1 <= {2*YCbCr_DW{1'b0}};
- RGB_G2 <= {2*YCbCr_DW{1'b0}};
- RGB_G3 <= {2*YCbCr_DW{1'b0}};
- RGB_B1 <= {2*YCbCr_DW{1'b0}};
- RGB_B2 <= {2*YCbCr_DW{1'b0}};
- RGB_B3 <= {2*YCbCr_DW{1'b0}};
- end
- else begin
- RGB_R1 <= RGB_DAT[23:16] * 8'd77;
- RGB_G1 <= RGB_DAT[15:8] * 8'd150;
- RGB_B1 <= RGB_DAT[7:0] * 8'd29;
- RGB_R2 <= RGB_DAT[23:16] * 8'd43;
- RGB_G2 <= RGB_DAT[15:8] * 8'd85;
- RGB_B2 <= RGB_DAT[7:0] * 8'd128;
- RGB_R3 <= RGB_DAT[23:16] * 8'd128;
- RGB_G3 <= RGB_DAT[15:8] * 8'd107;
- RGB_B3 <= RGB_DAT[7:0] * 8'd21;
- end
- end
- //Step2: Consume 1Clk
- always@(posedge RGB_CLK or negedge RESET)begin
- if(!RESET)begin
- IMG_Y <= {2*YCbCr_DW{1'b0}};
- IMG_Cr <= {2*YCbCr_DW{1'b0}};
- IMG_Cb <= {2*YCbCr_DW{1'b0}};
- end
- else begin
- IMG_Y <= RGB_R1 + RGB_G1 + RGB_B1;
- IMG_Cb <= RGB_B2 - RGB_R2 - RGB_G2 + 16'd32768;
- IMG_Cr <= RGB_R3 - RGB_G3 - RGB_B3 + 16'd32768;
- end
- end
- //Step3: Consume 1Clk
- always@(posedge RGB_CLK or negedge RESET)begin
- if(!RESET)begin
- Y_DAT <= {YCbCr_DW{1'b0}};
- Cb_DAT <= {YCbCr_DW{1'b0}};
- Cr_DAT <= {YCbCr_DW{1'b0}};
- end
- else begin
- Y_DAT <= IMG_Y[15:8];
- Cr_DAT <= IMG_Cr[15:8];
- Cb_DAT <= IMG_Cb[15:8];
- end
- end
- assign YCbCr_CLK = RGB_CLK;
- always@(posedge RGB_CLK or negedge RESET)begin
- if(!RESET)begin
- VSYNC_R <= 4'd0;
- DVALID_R <= 4'd0;
- end
- else begin
- VSYNC_R <= {VSYNC_R[1:0],RGB_VSYNC};
- DVALID_R <= {DVALID_R[1:0],RGB_DVALID};
- end
- end
- assign YCbCr_DVALID = DVALID_R[2];
- assign YCbCr_VSYNC = VSYNC_R[2];
- endmodule
(4) 顶层文件 rgb2gray.v;
- //===============================================================================================//
- //FileName: rgb2gray.v
- //Date:2020-02-28
- //===============================================================================================//
- `timescale 1ps/1ps
- module rgb2gray(
- RSTn, // 全局复位
- CLOCK, // 系统时钟
- IMG_CLK, // 像素时钟
- IMG_DVD, // 像素值
- IMG_DVSYN, // 输入场信号
- IMG_DHSYN, // 输入数据有效信号
- GRAY_CLK, // 输出灰度图像时钟
- GRAY_VSYNC, // 输出灰度图像场信号
- GRAY_DVALID, // 输出灰度图像数据有效信号
- Y_DAT, // 输出图像数据 Y 分量
- Cb_DAT, // 输出图像数据 Cb 分量
- Cr_DAT // 输出图像数据 Cr 分量
- );
- /*image parameter*/
- parameter iw = 640; //image width
- parameter ih = 512; //image height
- parameter trig_value = 400; //250
- /*data width*/
- parameter dvd_dw = 8; //image source data width
- parameter dvd_chn = 3; //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr
- parameter local_dw = dvd_dw * dvd_chn; //local algorithem process data width
- parameter cmd_dw = dvd_dw * dvd_chn; //local algorithem process data width
- //Port Declared
- input RSTn;
- input CLOCK;
- input IMG_CLK;
- input [dvd_dw-1:0] IMG_DVD;
- input IMG_DVSYN;
- input IMG_DHSYN;
- output GRAY_CLK;
- output GRAY_VSYNC;
- output GRAY_DVALID;
- output [dvd_dw-1:0] Y_DAT;
- output [dvd_dw-1:0] Cb_DAT;
- output [dvd_dw-1:0] Cr_DAT;
- //Variable Declared
- wire [local_dw-1:0] RGB_DAT;
- wire RGB_DVALID;
- wire RGB_VSYNC;
- video_cap u1(
- .reset_l(RSTn), // 异步复位信号
- .DVD(IMG_DVD), // 输入视频流
- .DVSYN(IMG_DVSYN), // 输入场同步信号
- .DHSYN(IMG_DHSYN), // 输入行同步
- .DVCLK(IMG_CLK), // 输入 DV 时钟
- .cap_dat(RGB_DAT), // 输出 RGB 通道像素流, 24 位
- .cap_dvalid(RGB_DVALID), // 输出数据有效
- .cap_vsync(RGB_VSYNC), // 输出场同步
- .cap_clk(CLOCK), // 本地逻辑时钟
- .img_en(),
- .cmd_rdy(), // 命令行准备好, 代表可以读取
- .cmd_rdat(), // 命令行数据输出
- .cmd_rdreq() // 命令行读取请求
- );
- defparam u1.DW_DVD = dvd_dw;
- defparam u1.DW_LOCAL = local_dw;
- defparam u1.DW_CMD = cmd_dw;
- defparam u1.DVD_CHN = dvd_chn;
- defparam u1.TRIG_VALUE = trig_value;
- defparam u1.IW = iw;
- defparam u1.IH = ih;
- RGB2YCrCb u2(
- .RESET(RSTn), // 异步复位信号
- .RGB_CLK(CLOCK), // 输入像素时钟
- .RGB_VSYNC(RGB_VSYNC), // 输入场同步信号
- .RGB_DVALID(RGB_DVALID), // 输入数据有信号
- .RGB_DAT(RGB_DAT), // 输入 RGB 通道像素流, 24 位
- .YCbCr_CLK(GRAY_CLK), // 输出像素时钟
- .YCbCr_VSYNC(GRAY_VSYNC), // 输出场同步信号
- .YCbCr_DVALID(GRAY_DVALID), // 输出数据有效信号
- .Y_DAT(Y_DAT), // 输出 Y 分量
- .Cb_DAT(Cb_DAT), // 输出 Cb 分量
- .Cr_DAT(Cr_DAT) // 输出 Cr 分量
- );
- defparam u2.RGB_DW = local_dw;
- defparam u2.YCbCr_DW = dvd_dw;
- endmodule
(5)Maltab 文件用显示仿真结果;
clc;
clear;
%% 数据获取
RGBImg = imread('lena_512x512.jpg'); %rgb 原始图像
- RGBImg = imresize(RGBImg,[512 640]);
GRAYImg = rgb2gray(RGBImg); %Matlab 变换灰度图像
fid = fopen('gray_image_Y.txt','r'); %FPGA 转换灰度图像
- data = fscanf(fid,'%2x');
- data = uint8(data);
- gray_data = reshape(data,640,512);
- gray_data = gray_data';
%% 画图显示
- figure(1);
- subplot(1,3,1);
- imshow(RGBImg);
- title('lena 原始图像');
- subplot(1,3,2);
- imshow(GRAYImg);
- title('Matlab 变换灰度图像');
- subplot(1,3,3);
- imshow(gray_data);
- title('FPGA 变换灰度图像');
(6) 用于 Modelsim 测试的 Testbench 文件 rgb2gray_tb.v;
- `timescale 1ps/1ps
- module rgb2gray_tb;
- /*image para*/
- parameter iw = 640; //image width
- parameter ih = 512; //image height
- parameter trig_value = 400; //250
- /*video parameter*/
- parameter h_total = 2000;
- parameter v_total = 600;
- parameter sync_b = 5;
- parameter sync_e = 55;
- parameter vld_b = 65;
- parameter clk_freq = 72;
- /*data width*/
- parameter dvd_dw = 8; //image source data width
- parameter dvd_chn = 3; //channel of the dvd data: when 3 it's rgb or 4:4:YCbCr
- parameter local_dw = dvd_dw * dvd_chn; //local algorithem process data width
- parameter cmd_dw = dvd_dw * dvd_chn; //local algorithem process data width
- /*test module enable*/
- parameter cap_en = 1;
- /*signal group*/
- reg clk = 1'b0;
- reg reset_l;
- reg [3:0] src_sel;
- /*input dv group*/
- wire dv_clk;
- wire dvsyn;
- wire dhsyn;
- wire [dvd_dw-1:0] dvd;
- /*dvd source data generated for simulation*/
- image_src //#(iw*dvd_chn, ih+1, dvd_dw, h_total, v_total, sync_b, sync_e, vld_b)
- u1(
- .clk(clk),
- .reset_l(reset_l),
- .src_sel(src_sel),
- .test_data(dvd),
- .test_dvalid(dhsyn),
- .test_vsync(dvsyn),
- .clk_out(dv_clk)
- );
- defparam u1.iw = iw*dvd_chn;
- defparam u1.ih = ih + 1;
- defparam u1.dw = dvd_dw;
- defparam u1.h_total = h_total;
- defparam u1.v_total = v_total;
- defparam u1.sync_b = sync_b;
- defparam u1.sync_e = sync_e;
- defparam u1.vld_b = vld_b;
- /*local clk: also clk of all local modules*/
- reg cap_clk = 1'b0;
- /*output data*/
- wire GRAY_CLK;
- wire GRAY_VSYNC;
- wire GRAY_DVALID;
- wire [dvd_dw-1:0] Y_DAT;
- wire [dvd_dw-1:0] Cb_DAT;
- wire [dvd_dw-1:0] Cr_DAT;
- /*video capture: capture image src and transfer it into local timing*/
- rgb2gray u2(
- .RSTn(reset_l),
- .CLOCK(cap_clk),
- .IMG_CLK(dv_clk),
- .IMG_DVD(dvd),
- .IMG_DVSYN(dvsyn),
- .IMG_DHSYN(dhsyn),
- .GRAY_CLK(GRAY_CLK),
- .GRAY_VSYNC(GRAY_VSYNC),
- .GRAY_DVALID(GRAY_DVALID),
- .Y_DAT(Y_DAT),
- .Cb_DAT(Cb_DAT),
- .Cr_DAT(Cr_DAT)
- );
- initial
- begin: init
- reset_l <= 1'b1;
- src_sel <= 4'b0000;
- #(100); //reset the system
- reset_l <= 1'b0;
- #(100);
- reset_l <= 1'b1;
- end
- //dv_clk generate
- always@(reset_l or clk)begin
- if((~(reset_l)) == 1'b1)
- clk <= 1'b0;
- else
- begin
- if(clk_freq == 48) //48MHz
- clk <= #10417 (~(clk));
- else if(clk_freq == 51.84) //51.84MHz
- clk <= #9645 (~(clk));
- else if(clk_freq == 72) //72MHz
- clk <= #6944 (~(clk));
- end
- end
- //cap_clk generate: 25MHz
- always@(reset_l or cap_clk)begin
- if((~(reset_l)) == 1'b1)
- cap_clk <= 1'b0;
- else
- cap_clk <= #20000 (~(cap_clk));
- end
- generate
- if(cap_en != 0) begin :capture_operation
- integer fid1, fid2, fid3, cnt_cap=0;
- always@(posedge GRAY_CLK or posedge GRAY_VSYNC)begin
- if(((~(GRAY_VSYNC))) == 1'b0)
- cnt_cap = 0;
- else
- begin
- if(GRAY_DVALID == 1'b1)
- begin
- //Y
- fid1 = $fopen("E:/Modelsim/rgb2gray/sim/gray_image_Y.txt","r+");
- $fseek(fid1,cnt_cap,0);
- $fdisplay(fid1,"%02x\n",Y_DAT);
- $fclose(fid1);
- //Cb
- fid2 = $fopen("E:/Modelsim/rgb2gray/sim/gray_image_Cb.txt","r+");
- $fseek(fid2,cnt_cap,0);
- $fdisplay(fid2,"%02x\n",Cb_DAT);
- $fclose(fid2);
- //Cr
- fid3 = $fopen("E:/Modelsim/rgb2gray/sim/gray_image_Cr.txt","r+");
- $fseek(fid3,cnt_cap,0);
- $fdisplay(fid3,"%02x\n",Cr_DAT);
- $fclose(fid3);
- cnt_cap<=cnt_cap+4;
- end
- end
- end
- end
- endgenerate
- endmodule
(7) 用于 Modelsim 仿真的. do 文件 rgb2gray.do.
- # 切换至工程目录
- cd E:/Modelsim/rgb2gray/sim
- #打开工程
- project open E:/Modelsim/rgb2gray/sim/rgb2gray
- #添加指定设计文件
- project addfile E:/Modelsim/rgb2gray/src/cross_clock_fifo.v
- project addfile E:/Modelsim/rgb2gray/src/image_src.v
- project addfile E:/Modelsim/rgb2gray/src/line_buffer_new.v
- project addfile E:/Modelsim/rgb2gray/src/rgb2gray.v
- project addfile E:/Modelsim/rgb2gray/src/RGB2YCbCr.v
- project addfile E:/Modelsim/rgb2gray/src/video_cap.v
- project addfile E:/Modelsim/rgb2gray/sim/rgb2gray_tb.v
- #编译工程内所有文件
- project compileall
- #仿真 work 库下面的 rgb2gray_tb 实例, 同时调用 altera_lib 库, 不进行任何优化
- vsim -t 1ps -novopt -L altera_lib work.rgb2gray_tb
- #添加输入信号
- add wave -divider RGBImg
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/dv_clk
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/dvsyn
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/dhsyn
- add wave -radix hex -position insertpoint sim:/rgb2gray_tb/dvd
- #添加输出信号
- add wave -divider GRAYImg
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/GRAY_CLK
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/GRAY_VSYNC
- add wave -radix binary -position insertpoint sim:/rgb2gray_tb/GRAY_DVALID
- add wave -radix hex -position insertpoint sim:/rgb2gray_tb/Y_DAT
- add wave -radix hex -position insertpoint sim:/rgb2gray_tb/Cb_DAT
- add wave -radix hex -position insertpoint sim:/rgb2gray_tb/Cr_DAT
- #复位
- restart
- #取消警告
- set StdArithNoWarnings 1
- #开始
- run 17ms
四, 仿真结果
如下图所示, 整个转换消耗 3 个时钟, 因此相应的行 / 场信号延迟 3 个时钟, 保持时钟的同步性.
如下图所示, 将 FPGA 运算处理结果与 Matlab 自带 rgb2gray 函数处理结果对比如下.
来源: https://www.cnblogs.com/huangwei0521/p/12382238.html