RGB转YCbCr模块设计
RGB转YCbCr模块设计

RGB转YCbCr模块设计

Tags
基础知识
IC设计
FPGA
Published
July 31, 2024
Author
内容摘要
标签
是否记得
module rgb2ycbcr ( //module clock input clk , // 模块驱动时钟 input rst_n , // 复位信号 //图像处理前的数据接口 input rgb_vsync , // vsync信号 input rgb_clken , // 时钟使能信号 input rgb_valid , // 数据有效信号 input [23:0] rgb_data , // 输入图像数据RGB //图像处理后的数据接口 output ycbcb_vsync , // vsync信号 output ycbcbr_clken , // 时钟使能信号 output ycbcr_valid , // 数据有效信号 output [7:0] gray_data // 输出图像Y数据 ); //reg define reg [15:0] rgb_r_m0, rgb_r_m1, rgb_r_m2; reg [15:0] rgb_g_m0, rgb_g_m1, rgb_g_m2; reg [15:0] rgb_b_m0, rgb_b_m1, rgb_b_m2; reg [15:0] img_y0 ; reg [15:0] img_cb0; reg [15:0] img_cr0; reg [ 7:0] img_y1 ; reg [ 7:0] img_cb1; reg [ 7:0] img_cr1; reg [ 2:0] rgb_vsync_d; reg [ 2:0] rgb_clken_d; reg [ 2:0] rgb_valid_d ; //wire define wire [ 7:0] rgb888_r; wire [ 7:0] rgb888_g; wire [ 7:0] rgb888_b; wire [ 7:0] img_y; wire [ 7:0] img_cb; wire [ 7:0] img_cr; //***************************************************** //** main code //***************************************************** //RGB565 to RGB 888 assign rgb888_r = rgb_data[23:16]; assign rgb888_g = rgb_data[15:8]; assign rgb888_b = rgb_data[7:0]; //同步输出数据接口信号 assign ycbcb_vsync = rgb_vsync_d[2] ; assign ycbcbr_clken = rgb_clken_d[2] ; assign ycbcr_valid = rgb_valid_d[2] ; assign img_y = ycbcbr_clken ? img_y1 : 8'd0; assign img_cb = ycbcbr_clken ? img_cb1: 8'd0; assign img_cr = ycbcbr_clken ? img_cr1: 8'd0; assign gray_data = img_y; //-------------------------------------------- //RGB 888 to YCbCr /******************************************************** 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 Y = (77 *R + 150*G + 29 *B )>>8 Cb = (-43*R - 85 *G + 128*B + 32768)>>8 Cr = (128*R - 107*G - 21 *B + 32768)>>8 *********************************************************/ //step1 计算括号内的各乘法项 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin rgb_r_m0 <= 16'd0; rgb_r_m1 <= 16'd0; rgb_r_m2 <= 16'd0; rgb_g_m0 <= 16'd0; rgb_g_m1 <= 16'd0; rgb_g_m2 <= 16'd0; rgb_b_m0 <= 16'd0; rgb_b_m1 <= 16'd0; rgb_b_m2 <= 16'd0; end else begin rgb_r_m0 <= rgb888_r * 8'd77 ; rgb_r_m1 <= rgb888_r * 8'd43 ; rgb_r_m2 <= rgb888_r * 8'd128; rgb_g_m0 <= rgb888_g * 8'd150; rgb_g_m1 <= rgb888_g * 8'd85 ; rgb_g_m2 <= rgb888_g * 8'd107; rgb_b_m0 <= rgb888_b * 8'd29 ; rgb_b_m1 <= rgb888_b * 8'd128; rgb_b_m2 <= rgb888_b * 8'd21 ; end end //step2 括号内各项相加 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin img_y0 <= 16'd0; img_cb0 <= 16'd0; img_cr0 <= 16'd0; end else begin img_y0 <= rgb_r_m0 + rgb_g_m0 + rgb_b_m0; img_cb0 <= rgb_b_m1 - rgb_r_m1 - rgb_g_m1 + 16'd32768; img_cr0 <= rgb_r_m2 - rgb_g_m2 - rgb_b_m2 + 16'd32768; end end //step3 括号内计算的数据右移8位 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin img_y1 <= 8'd0; img_cb1 <= 8'd0; img_cr1 <= 8'd0; end else begin img_y1 <= img_y0 [15:8]; img_cb1 <= img_cb0[15:8]; img_cr1 <= img_cr0[15:8]; end end //延时3拍以同步数据信号 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin rgb_vsync_d <= 3'd0; rgb_clken_d <= 3'd0; rgb_valid_d <= 3'd0; end else begin rgb_vsync_d <= {rgb_vsync_d[1:0], rgb_vsync}; rgb_clken_d <= {rgb_clken_d[1:0], rgb_clken}; rgb_valid_d <= {rgb_valid_d[1:0] , rgb_valid }; end end endmodule
这个模块实现起来并不难,在数学上来说就是运用几个公式将RGB转换一下,值得参考的一点是如何将这些方程用硬件语言实现。
以Y的转换为例:
在FPGA中计算小数乘法是很不方便,当然也不是不能,比如使用DSP或者使用“定点小数”计算。定点小数就是保证计算前后小数点离最后一位的距离是不变的,如果发生改变则要进行舍弃。为了尽可能使用整数进行计算,程序中做了一个讨巧的设计,那就是变成计算:
虽然在精确度上有点差,不过对于FPGA来说是可以接受的。
verilog中实现乘法可以直接相乘,即
a * b;
也可以手动设置乘法器,例如:
reg [7:0] a, b, product; integer i; always @(a or b) begin product = 0; for (i = 0; i < 8; i = i + 1) begin if (b[i]) begin product = product + (a << i); end end end
或者直接使用乘法IP,效果更好。
但注意,不是所有的FPGA都支持直接使用*乘号,在我自己的实验项目中,安路开发板支持29个18x18的乘法器,即输入的数值最大只能有18位;zynq更加支持,并且具备专用DSP乘法模块。使用*意味着让内核自动映射到开发板内部的LUT或寄存器中实现计算,或依赖IP模块。
但注意,不是所有的FPGA都支持直接使用*乘号,在我自己的实验项目中,安路开发板支持29个18x18的乘法器,即输入的数值最大只能有18位;zynq更加支持,并且具备专用DSP乘法模块。使用*意味着让内核自动映射到开发板内部的LUT或寄存器中实现计算,或依赖IP模块。
一个简单的乘法器模块仿真
乘法器部分
module multiplication( input [3:0] a, input [3:0] b, output [7:0] product ); integer i; reg [7:0] multiplication; always @(a or b) begin multiplication = 0; for (i = 0; i < 4; i = i + 1) begin if (b[i]) begin multiplication = multiplication + (a << i); end end end assign product = multiplication; endmodule
仿真模块
`timescale 1ns/1ps module multiplication_tb; reg [3:0] a; reg [3:0] b; wire [7:0] product; multiplication uut( .a(a), .b(b), .product(product) ); initial begin $dumpfile("multiplier_tb.vcd"); $dumpvars(0, multiplication_tb); a = 4'b0000; b = 4'b0000; // 应用测试向量 #10 a = 4'b0011; b = 4'b0010; // 3 * 2 = 6 #10 a = 4'b0101; b = 4'b0011; // 5 * 3 = 15 #10 a = 4'b1111; b = 4'b1111; // 15 * 15 = 225 #10 a = 4'b0110; b = 4'b0100; // 6 * 4 = 24 #10 a = 4'b1001; b = 4'b0010; // 9 * 2 = 18 #10 $finish; end endmodule
仿真结果
notion image
在Verilog中实现除法并不像加法、减法或乘法那样简单。虽然Verilog支持使用/%操作符进行除法和取模运算,但这些操作符在综合时通常不会被直接使用,因为它们会生成复杂且资源消耗大的电路。因此,通常需要手动实现除法算法。
左边的电路很重要的特点就是,乘法,加法并不是一次性完成的,而是分步计算,首先得到乘法,之后再加法,然后再右移实现除法。