FPGA 实现色彩空间转换RGB—YCbCr

一、前言

        RGB、YUV 和 YCbCr 都是人为规定的色彩空间。RGB色彩空间是基于人眼对红(Red)、绿(Green)、蓝(Blue)三种颜色的感知来定义的。通过这三种颜色的不同组合,可以表示出大部分可见颜色。YUV 色彩空间将颜色信息分为亮度(Y)和色度(U、V)两个部分。亮度表示图像的明暗程度,色度表示颜色信息。YCbCr 是在世界数字组织视频标准研制过程中作为 ITU - R BT1601 建议的一部分,是 YUV 经过缩放和偏移的翻版。其中 Y 与 YUV 中的 Y 含义一致, Cb , Cr 同样都指色彩, 只是在表示方法上不同而已。在 YUV 家族中, YCbCr 是在计算机系统中应用最多的成员,其应用领域很广泛,一般人们所讲的 YUV 大多是指 YCbCr。

二、程序设计

        设计Verilog程序实现RGB到YCbCr的转变,以及YCbCr到RGB的转变。首先我们要知道YCbCr与RGB相互转换公式,如下图所示。

        在MATLAB中编写代码读取一幅1280*720的lena RGB 图实现RGB到YCbCr的转变。将RGB lena 图像以及生成的YCbCr lena图像保存为16进制的 txt 文本,用于Verilog程序仿真时使用。主要MATLAB转变代码如下。

 %RGB到YCbCr转变
 ycbcr0 = zeros(size(image_in), 'like', image_in);
 ycbcr0(:,:,1)  =  0.2568*image_in_r + 0.5041*image_in_g + 0.0979*image_in_b + 16; 
 ycbcr0(:,:,2)  = -0.1482*image_in_r - 0.2910*image_in_g + 0.4392*image_in_b + 128;
 ycbcr0(:,:,3)  =  0.4392*image_in_r - 0.3678*image_in_g - 0.0714*image_in_b + 128;
 image0_in_y    = ycbcr0(:,:,1);          
 image0_in_cb   = ycbcr0(:,:,2);          
 image0_in_cr   = ycbcr0(:,:,3);

        可以发现公式中有好多小数的乘除,FPGA是不能直接计算小数的,调用IP计算又感觉有些小题大做。这时就需要引入移位扩充的思想了,例如想要计算0.2568*123,可以先将0.2568*256再和123相乘,最后再除256。为什么是乘256而不是更容易计算的1000呢,这是因为256是8位二进制数,在FPGA中实现乘256就是将数据左移8位,除256就是将数据右移8位。这样很容易实现数据的计算,又避免了小数系数的问题。但是这样会损失一些精度,这些精度对于本次程序设计是可以忽略不计的。而对于一些精度要求高的项目还是老老实实用IP核计算吧。这时就要修改一下对应的MATLAB程序了,代码如下。

 ycbcr0(:,:,1)  =  0.2568*image_in_r + 0.5041*image_in_g + 0.0979*image_in_b + 16; 
 ycbcr0(:,:,2)  = -0.1482*image_in_r - 0.2910*image_in_g + 0.4392*image_in_b + 128;
 ycbcr0(:,:,3)  =  0.4392*image_in_r - 0.3678*image_in_g - 0.0714*image_in_b + 128;

 ycbcr0(:,:,1)  = 256*( 0.2568*image_in_r + 0.5041*image_in_g + 0.0979*image_in_b + 16 )>>8; 
 ycbcr0(:,:,2)  = 256*(-0.1482*image_in_r - 0.2910*image_in_g + 0.4392*image_in_b + 128)>>8;
 ycbcr0(:,:,3)  = 256*( 0.4392*image_in_r - 0.3678*image_in_g - 0.0714*image_in_b + 128)>>8;

 ycbcr0(:,:,1)  = (66*image_in_r + 129*image_in_g + 25*image_in_b + 4096  )>>8; 
 ycbcr0(:,:,2)  = (-38*image_in_r - 74*image_in_g + 112*image_in_b + 32768)>>8;
 ycbcr0(:,:,3)  = (112*image_in_r - 94*image_in_g - 18*image_in_b + 32768 )>>8;

        这样就可以根据MATLAB代码实现Verilog代码了,程序设计代码如下。

`timescale 1ns / 1ps

module rgb2ycbcr
(
	input   wire				i_clk,
	input   wire				i_rst_n,	
	input   wire				i_hsyn,		
	input   wire				i_vsyn,		
	input	wire [7:0]			i_r,
	input	wire [7:0] 			i_g,
	input	wire [7:0] 			i_b,	
	input   wire				i_de,		
	output	wire				o_hsyn,	
	output  wire				o_vsyn,	
	output  wire [7:0]			o_y,
	output  wire [7:0]			o_cb,
	output  wire [7:0]			o_cr,
	output  wire				o_de
);


reg [15:0] r_d0;
reg [15:0] g_d0;
reg [15:0] b_d0;

reg [15:0] r_d1;
reg [15:0] g_d1;
reg [15:0] b_d1;

reg [15:0] r_d2;
reg [15:0] g_d2;
reg [15:0] b_d2;

reg [15:0] y_d0;
reg [15:0] cb_d0;
reg [15:0] cr_d0;

reg [1:0] hsyn;
reg [1:0] vsyn;
reg [1:0] de;

always@(posedge i_clk or negedge i_rst_n) 
begin
    if(!i_rst_n)
	begin
        r_d0 <= 16'd0;
        g_d0 <= 16'd0;
        b_d0 <= 16'd0;
		r_d1 <= 16'd0;
		g_d1 <= 16'd0;
		b_d1 <= 16'd0;
		r_d2 <= 16'd0;
		g_d2 <= 16'd0;
		b_d2 <= 16'd0;	
    end
    else 
	begin
        r_d0 <= 66  * i_r;
        g_d0 <= 129 * i_g;
        b_d0 <= 25  * i_b;
		r_d1 <= 38  * i_r;
		g_d1 <= 74  * i_g;
		b_d1 <= 112 * i_b;
		r_d2 <= 112 * i_r;
		g_d2 <= 94  * i_g;
		b_d2 <= 18  * i_b;	        	
    end
end

always@(posedge i_clk or negedge i_rst_n) 
begin
    if(!i_rst_n)
	begin
		y_d0  <= 16'd0;
		cb_d0 <= 16'd0;
		cr_d0 <= 16'd0;
    end
    else 
	begin
		y_d0  <= r_d0 + g_d0 + b_d0 + 4096 ;
		cb_d0 <= b_d1 - r_d1 - g_d1 + 32768;
		cr_d0 <= r_d2 - g_d2 - b_d2 + 32768;      	
    end
end

always@(posedge i_clk ) 
begin
	hsyn	<= {hsyn[0],i_hsyn};
	vsyn	<= {vsyn[0],i_vsyn};
	de		<= {de[0],i_de};
end

assign 	o_hsyn	= hsyn[1];
assign 	o_vsyn	= vsyn[1];
assign 	o_y		= y_d0 [15:8];
assign 	o_cb	= cb_d0[15:8];
assign 	o_cr	= cr_d0[15:8];
assign 	o_de	= de[1];

endmodule

三、仿真验证

        用MATLAB生成的原始RGB lena 图像.txt文件作为算法的输入,将FPGA仿真生成的数据和MATLAB生成的YCbCr lena图像.txt文件数据进行比较,初步验证Verilog代码是否正确。仿真文件代码比较杂乱,这里就不粘贴仿真源码了。截取部分仿真波形如图所示,第一个红色框中的数据就是一个RGB 像素,第二个红色框中的数据就是一个YCbCr 像素。根据公式计算可以确定输入输出符合预期,对比MATLAB生成的.txt文件也可以验证数据正确。

        将 Verilog代码输出的YCbCr数据保存,用MATLAB将数据还原成图像,确定代码设计符合预期。

        YCbCr到RGB的转变这里就不多墨迹了,流程是一样的,直接上传代码。

        原始公式MATLAB代码

%YCbCr到RGB转变
 image0_out_rgb_r = 1.1644*(image0_in_y - 16) + 1.5960*(image0_in_cr - 128);
 image0_out_rgb_g = 1.1644*(image0_in_y - 16) - 0.3918*(image0_in_cb - 128) 
                                              - 0.8130*(image0_in_cr - 128);         
 image0_out_rgb_b = 1.1644*(image0_in_y - 16) + 2.0172*(image0_in_cb - 128);

        移位处理后的MATLAB代码 

 image0_out_rgb_r = 1.1644*(ycbcr0(:,:,1) - 16) + 1.5960*(ycbcr0(:,:,3) - 128);
 image0_out_rgb_g = 1.1644*(ycbcr0(:,:,1) - 16) - 0.3918*(ycbcr0(:,:,2) - 128) -0.8130*(ycbcr0(:,:,3) - 128);         
 image0_out_rgb_b = 1.1644*(ycbcr0(:,:,1) - 16) + 2.0172*(ycbcr0(:,:,2) - 128);

 image0_out_rgb_r = 1.1644*ycbcr0(:,:,1) - 18.6304 + 1.5960*ycbcr0(:,:,3) - 204.288;
 image0_out_rgb_g = 1.1644*ycbcr0(:,:,1) - 18.6304 - 0.3918*ycbcr0(:,:,2) + 50.1504 + -0.8130*ycbcr0(:,:,3) + 104.063;         
 image0_out_rgb_b = 1.1644*ycbcr0(:,:,1) - 18.6304 + 2.0172*ycbcr0(:,:,2) - 258.2016;

 image0_out_rgb_r = 1.1644*ycbcr0(:,:,1) + 1.5960*ycbcr0(:,:,3) - 223;
 image0_out_rgb_g = 1.1644*ycbcr0(:,:,1) - 0.3918*ycbcr0(:,:,2) - 0.8130*ycbcr0(:,:,3) + 136;         
 image0_out_rgb_b = 1.1644*ycbcr0(:,:,1) + 2.0172*ycbcr0(:,:,2) - 277;

 image0_out_rgb_r = 256*(1.1644*ycbcr0(:,:,1) + 1.5960*ycbcr0(:,:,3) - 223)>>8;
 image0_out_rgb_g = 256*(1.1644*ycbcr0(:,:,1) - 0.3918*ycbcr0(:,:,2) - 0.8130*ycbcr0(:,:,3) + 136)>>8;         
 image0_out_rgb_b = 256*(1.1644*ycbcr0(:,:,1) + 2.0172*ycbcr0(:,:,2) - 277)>>8;

 image0_out_rgb_r = (298*ycbcr0(:,:,1) + 408*ycbcr0(:,:,3) - 57088)>>8;
 image0_out_rgb_g = (298*ycbcr0(:,:,1) - 100*ycbcr0(:,:,2) - 208*ycbcr0(:,:,3) + 34816)>>8;         
 image0_out_rgb_b = (298*ycbcr0(:,:,1) + 516*ycbcr0(:,:,2) - 70912)>>8;

        Verilog代码 

`timescale 1ns / 1ps

module uiycbcr2rgb_algorithm
(
	input   wire				i_clk,
	input   wire				i_rst_n,	
	input   wire				i_hsyn,		
	input   wire				i_vsyn,		
	input	wire [7:0]			i_y, 
	input	wire [7:0] 			i_cb,
	input	wire [7:0] 			i_cr,	
	input   wire				i_de,		
	output	wire				o_hsyn,	
	output  wire				o_vsyn,	
	output  wire [7:0]			o_r,
	output  wire [7:0]			o_g,
	output  wire [7:0]			o_b,
	output  wire				o_de
);


reg [15:0] y_d0;
reg [15:0] cr_d0;

reg [15:0] y_d1;
reg [15:0] cb_d1;
reg [15:0] cr_d1;

reg [15:0] y_d2;
reg [15:0] cb_d2;

reg [15:0] r_d0;
reg [15:0] g_d0;
reg [15:0] b_d0;

reg [1:0] hsyn;
reg [1:0] vsyn;
reg [1:0] de;

always@(posedge i_clk or negedge i_rst_n) 
begin
    if(!i_rst_n)
	begin
        y_d0  <= 16'd0;
        cr_d0 <= 16'd0;
		y_d1  <= 16'd0;
		cb_d1 <= 16'd0;
		cr_d1 <= 16'd0;
		y_d2  <= 16'd0;
		cb_d2 <= 16'd0;
    end
    else 
	begin
        y_d0   <= 298 * i_y;
        cr_d0  <= 408 * i_cr;
		y_d1   <= 298 * i_y;
		cb_d1  <= 100 * i_cb;
		cr_d1  <= 208 * i_cr;
		y_d2   <= 298 * i_y;
		cb_d2  <= 516 * i_cb;        	
    end
end

always@(posedge i_clk or negedge i_rst_n) 
begin
    if(!i_rst_n)
	begin
		r_d0  <= 16'd0;
		g_d0  <= 16'd0;
		b_d0  <= 16'd0;
    end
    else 
	begin
		r_d0  <= y_d0 + cr_d0 - 57088;
		g_d0  <= y_d1 - cb_d1 - cr_d1 + 34816;
		b_d0  <= y_d2 + cb_d2 - 70912;      	
    end
end

always@(posedge i_clk ) 
begin
	hsyn	<= {hsyn[0],i_hsyn};
	vsyn	<= {vsyn[0],i_vsyn};
	de		<= {de[0],i_de};
end

assign 	o_hsyn	= hsyn[1];
assign 	o_vsyn	= vsyn[1];
assign 	o_r		= r_d0[15:8];
assign 	o_g		= g_d0[15:8];
assign 	o_b		= b_d0[15:8];
assign 	o_de	= de[1];

endmodule

四、总结

        本文讲述了如何通过FPGA使用Verilog语言实现RGB到YCbCr的转变,以及YCbCr到RGB的转变。欢迎大家一起讨论交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值