一、前言
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的转变。欢迎大家一起讨论交流。