Verilog和Matlab实现RGB888互转YUV444
文章目录
- 一、色彩空间
- 1.1 RGB色彩空间
- 1.2 CMYK色彩空间
- 1.3 YUV色彩空间
- 二、色彩空间转换公式
- 2.1 RGB转CMYK
- 2.2 CMYK转RGB
- 2.3 RGB888转YUV444
- 2.4 YUV444转RGB888
- 三、MATLAB实现RGB888转YUV444
- 3.1 matlab代码
- 3.2 matlab结果
- 3.3 保存原始图像数据和转换成yuv后的图像数据
- 四、Verilog实现RGB888转YUV444
- 4.1 verilog代码
- 4.2 仿真观察
- 五、MATLAB实现YUV444转RGB888
- 5.1 matlab代码
- 5.2 matlab结果
- 六、Verilog实现YUV444转RGB888
- 6.1 Verilog代码
- 6.2 仿真观察
一、色彩空间
色彩空间就是显示一幅图像所使用的特定颜色组合,不同的应用场景会使用不同的色彩空间。常见的色彩空间有RGB、CMYK、HSV、LAB以及YUV等等。
1.1 RGB色彩空间
RGB色彩空间最常用的用途就是显示器领域,利用物理中光的三原色可以叠加成不同颜色的原理;因此一个像素由R、G、B三种颜色分量组成,在RGB色彩空间中,R、G、B三个分量的属性是独立的,每个分量数字越大,对应的颜色占比就越大。常见的RGB格式有RGB888、RGB565、RGB555等等,其中RGB888表示每种颜色分量都有256级,所以RGB888能表示256 * 256 * 256=1677w种颜色。RGB色彩空间应用十分广泛,但不适合做图像处理,因为人眼视网膜上存在两种视敏细胞:锥状细胞和杆状细胞这两种细胞对颜色和亮度的感知程度不一样(具体可以去了解以下人眼系统构成),总之就是人眼对亮度的感知大于对颜色的感知。而RGB三种分量都与亮度有关系,因此做图像处理时,改变任意分量对亮度都会产生影响,因此RGB色彩空间通常只是用来显示。
1.2 CMYK色彩空间
CMYK色彩空间的使用场景是印刷、打印等领域,当光线照射到一个物体上时,物体将吸收一部分光,并将剩下的光进行反射,反射的光线就是我们所看见的物体颜色,这也是与RGB色彩空间的根本不同之处。CMYK颜色模型使用青、品红、黄、黑四个通道来表示颜色,青、品红、黄三个通道分别对应RGB的补色,K通道表示黑色墨水的量
因此RGB色域更广,CMYK相较于RGB色域有限,所以存在一些RGB里的颜色在印刷时无法显示的情况,这些CMYK色域不包含的颜色在印刷时会丢失。
1.3 YUV色彩空间
YUV是指亮度分量和色度分量都分开表示的像素格式,在 YUV空间中,每一个颜色有一个亮度信号 Y,和两个色度信号 U 和V。亮度信号是强度的感觉,它和色度信号断开,这样的话强度就可以在不影响颜色的情况下改变,是一种模拟信号,最初用于模拟电视广播。
YCbCr 则是在世界数字组织视频标准研制过程中作为ITU - R BT.601 建议的一部分,其实是YUV经过缩放和偏移的翻版,YCbCr 是 YUV 的一种数字形式,专为数字图像和视频处理而设计。它通常用于数字视频压缩(如 JPEG、MPEG 等)。YCbCr其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。
- YUV是一种模拟信号,其色彩模型源于RGB,常用于模拟广播电视中;在某些情况下,YUV 的分量可以超出 0-255 的范围,尤其是在模拟信号中。
- YCbCr是一种数字信号,是YUV压缩和偏移的结果,广泛用于数字视频编码、图像压缩和传输(例如,JPEG、MPEG、H.264 等);分量的范围通常被限制在 0-255(对于 8 位图像)。在某些情况下,Cb 和 Cr 可能会在 -128 到 127 的范围内(有符号值)。
一般人们所讲的YUV大多是指YCbCr。YCbCr 有许多采样格式,是在获取原始图像数据时采用的策略。如YUV444,YUV422,YUV420 。
- YUV 4:4:4表示每一个 Y 分量对应一对 UV 分量,每像素占用 (Y + U + V = 8 + 8 + 8 = 24bits)
- YUV 4:2:2表示每两个 Y 分量对应一对 UV 分量,每像素占用 (Y + 0.5U + 0.5V = 8 + 4 + 4 = 16bits)
- YUV 4:2:0表示每四个 Y 分量对应一对 UV 分量,每像素占用 (Y + 0.25U + 0.25V = 8 + 2 + 2 = 12bits)
二、色彩空间转换公式
2.1 RGB转CMYK
第一步:将RGB色彩空间中的颜色映射到CMY色彩空间中R,G,B值除以255,将范围从0…255更改为0~1
R ′ = R / 255 R^{'}=R/255 R′=R/255
G ′ = G / 255 G^{'}=G/255 G′=G/255
B ′ = B / 255 B^{'}=B/255 B′=B/255
第二步:计算出黑色K的量值:
K = 1 − m a x ( R ′ , G ′ , B ′ ) K=1 - max(R^{'},G^{'},B^{'}) K=1−max(R′,G′,B′)
第三步:计算出C(青色),M(品红),Y(红色)的值:
C = ( 1 − R ′ − K ) / ( 1 − K ) C =(1 - R^{'} - K)/ (1 - K) C=(1−R′−K)/(1−K)
M = ( 1 − G ′ − K ) / ( 1 − K ) M =(1 - G^{'} - K)/ (1 - K) M=(1−G′−K)/(1−K)
Y = ( 1 − B ′ − K ) / ( 1 − K ) Y =(1 - B^{'} - K)/ (1 - K) Y=(1−B′−K)/(1−K)
例如R、G、 B = 88、137、142转换成CMYK就等于C、M、Y、K=38、4、0、44(单位%)
2.2 CMYK转RGB
R = 255 ∗ ( 1 − C ) ∗ ( 1 − K ) R=255 * (1-C)*(1-K) R=255∗(1−C)∗(1−K)
G = 255 ∗ ( 1 − M ) ∗ ( 1 − K ) G=255 * (1-M)*(1-K) G=255∗(1−M)∗(1−K)
B = 255 ∗ ( 1 − Y ) ∗ ( 1 − K ) B=255 * (1-Y)*(1-K) B=255∗(1−Y)∗(1−K)
例如C、M、Y、K=38、4、0、44(单位%)等于R、G、 B = 89、137、143
2.3 RGB888转YUV444
不同的视频协议标准中,公式有所区别:
- 标准数字电视(SDTV)约定使用BT.601中规定的色彩空间,按照BT.656中规定的方法传输数据。
- 高清数字电视(HDTV)约定使用BT.709中规定的色彩空间,按照BT.1120中规定的方法传输数据。
- 超高清数字电视(UHDTV)约定使用BT.2020中规定的色彩空间,按照BT.2020中规定的方法传输数据。
不同的协议中,YCbCr也有不同的形式分为:TV range、full range。TV range 主要是广播电视采用的标准, full range主要是pc端采用的标准,所以full range 有时也叫 pc range。不同的形式,YCbCr的分量取值范围也不同:
- TV range 的各个分量的范围为: YUV Y∈[16,235] Cb∈[16-240] Cr∈[16-240]
- full range 的各个分量的范围均为:[0-255]
本文采用BT.601规范中的full range形式,因此RGB转换YUV公式为:
Y = 0.299 ∗ R + 0.587 ∗ G + 0.114 ∗ B Y=0.299 *R + 0.587*G + 0.114 * B Y=0.299∗R+0.587∗G+0.114∗B
U = − 0.169 ∗ R − 0.331 ∗ G + 0.5 ∗ B + 128 U=-0.169 *R -0.331*G + 0.5 * B + 128 U=−0.169∗R−0.331∗G+0.5∗B+128
V = 0.5 ∗ R − 0.419 ∗ G − 0.081 ∗ B + 128 V=0.5 *R - 0.419*G -0.081 * B + 128 V=0.5∗R−0.419∗G−0.081∗B+128
2.4 YUV444转RGB888
R = Y + 1.402 ∗ V − 1.402 ∗ 128 R=Y + 1.402*V - 1.402 * 128 R=Y+1.402∗V−1.402∗128
G = Y − 0.344 ∗ U − 0.714 ∗ V + 1.058 ∗ 128 G=Y -0.344*U - 0.714 * V + 1.058*128 G=Y−0.344∗U−0.714∗V+1.058∗128
B = Y + 1.772 ∗ U − 1.772 ∗ 128 B=Y + 1.772*U -1.772*128 B=Y+1.772∗U−1.772∗128
三、MATLAB实现RGB888转YUV444
3.1 matlab代码
clear all; close all; clc;
% 读取 BMP 图像
bmpImage = imread('...\...\...\...\....\....\....\yuanyang.bmp');% 确保图像是 RGB 格式
if size(bmpImage, 3) ~= 3error('输入图像必须是 RGB 格式');
end% 将 RGB 图像转换为 YUV
R = double(bmpImage(:,:,1));
G = double(bmpImage(:,:,2));
B = double(bmpImage(:,:,3));Y = 0.299 * R + 0.587 * G + 0.114 * B;
U = -0.169 * R - 0.331 * G + 0.5 * B + 128; % 加上128以偏移
V = 0.5 * R - 0.419 * G - 0.081 * B + 128; % 加上128以偏移% 将 YUV 转换为 uint8 类型
Y = uint8(Y);
U = uint8(U);
V = uint8(V);% 创建一个 YUV 整体图像(将 Y、U、V 通道堆叠在一起)
YUV_image = cat(3, Y, U, V);% 显示图像
figure;% 显示原图像
subplot(2, 4, 1);
imshow(bmpImage);
title('原始图像');% 显示 R 通道
subplot(2, 4, 2);
imshow(bmpImage(:,:,1)); % 显示 R 通道
title('R 通道');% 显示 G 通道
subplot(2, 4, 3);
imshow(bmpImage(:,:,2)); % 显示 G 通道
title('G 通道');% 显示 B 通道
subplot(2, 4, 4);
imshow(bmpImage(:,:,3)); % 显示 B 通道
title('B 通道');% 显示 YUV 整体图像
subplot(2, 4, 5); % 合并多个子图用于显示 YUV 整体图像
imshow(YUV_image);
title('YUV 整体图像');% 显示 Y 通道
subplot(2, 4, 6);
imshow(Y);
title('Y 通道');% 显示 U 通道
subplot(2, 4, 7);
imshow(U);
title('U 通道');% 显示 V 通道
subplot(2, 4, 8);
imshow(V);
title('V 通道');
3.2 matlab结果
这是RGB转YUV后的图片,我们还可以把每个通道单独显示出来,如下所示:
3.3 保存原始图像数据和转换成yuv后的图像数据
% 保存原始图像的 RGB 数据
fileID = fopen('original_image_data.dat', 'w');
for row = 1:size(bmpImage, 1)fprintf(fileID, '%s\n', sprintf('%02X %02X %02X ', bmpImage(row, :, 1), bmpImage(row, :, 2), bmpImage(row, :, 3)));
end
fclose(fileID);% 保存 YUV 图像的数据
fileID = fopen('yuv_image_data.dat', 'w');
for row = 1:size(YUV_image, 1)fprintf(fileID, '%s\n', sprintf('%02X %02X %02X ', YUV_image(row, :, 1), YUV_image(row, :, 2), YUV_image(row, :, 3)));
end
fclose(fileID);
打开两个数据文件,原始图像RGB数据如下:
原始图像RGB数值如上所示,排列方式为R、G、B,例如第一个像素点的RGB值就是 0xe1e1e3,接下来打开转换后的YUV数据文件:
转换后的图像YUV数值如上所示,排列方式为Y、U、V,例如第一个像素点的YUV值就是 0xe18180,我们根据公式手动验证一下,转换后的数值是否正确:
- Y = 0.299 * 225 + 0.587225 + 0.114227 = 225.228;四舍五入后 Y = 225 = 0xe1
- U = -0.169 * 225 - 0.331 * 225 + 0.5 * 227 + 128 = 129 = 0x81
- V=0.5 225 - 0.419225 -0.081 * 227 + 128 = 127.838;四舍五入后 V = 128 = 0x80
说明转换后的YUV数值正确,接下来我们用Verilog实现转换
四、Verilog实现RGB888转YUV444
因为转换公式里有小数,而FPGA不能直接对小数进行运算,一般的处理方式就是将浮点数转成定点数来计算,例如0.299可以写成306(0.2991024)然后再进行运算,运算完成后的结果右移10位就行。例如0.299 * 248 = 74.152,浮点数转成定点数运算就是 306248 /1024 = 74.109。计算结果有误差,所以要选择合适的扩大位宽使得计算误差在可接受范围内,Verilog代码如下:
4.1 verilog代码
`timescale 1ns / 1ps
module rgb2yuv(input clk ,input rst ,//输入RGB原始信号input i_data_valid ,input [7:0] i_data_r ,input [7:0] i_data_g ,input [7:0] i_data_b ,//输出转行后的yuv信号output o_data_valid ,output [7:0] o_data_y ,output [7:0] o_data_u ,output [7:0] o_data_v
);//Y= 0.299*R + 0.587*G + 0.114*B
//U=-0.169*R - 0.331*G + 0.500*B + 128
//V= 0.500*R - 0.419*G - 0.081*B + 128/****************parameter********************/
parameter Y_PARA_R = 306, // 0.299*1024Y_PARA_G = 601, // 0.587*1024Y_PARA_B = 117; // 0.114*1024parameter U_PARA_R = 173, // 0.169*1024U_PARA_G = 339, // 0.331*1024U_PARA_B = 512; // 0.500*1024parameter V_PARA_R = 512, // 0.500*1024V_PARA_G = 429, // 0.419*1024V_PARA_B = 83 ; // 0.081*1024parameter BASE = 131072;// 128*1024/*******************reg***********************/
reg [1:0] ro_data_valid ;
reg [17:0] ro_data_y ;
reg [17:0] ro_data_u ;
reg [17:0] ro_data_v ;
reg [17:0] r_y1 ;
reg [17:0] r_y2 ;
reg [17:0] r_y3 ;
reg [17:0] r_u1 ;
reg [17:0] r_u2 ;
reg [17:0] r_u3 ;
reg [17:0] r_v1 ;
reg [17:0] r_v2 ;
reg [17:0] r_v3 ;/******************wire***********************/
/******************assign*********************/
assign o_data_valid = ro_data_valid[1] ;
assign o_data_y = ro_data_y[17:10] ;
assign o_data_u = ro_data_u[17:10] ;
assign o_data_v = ro_data_v[17:10] ;//1 clock
always @(posedge clk) beginr_y1 = (i_data_r * Y_PARA_R);r_y2 = (i_data_g * Y_PARA_G);r_y3 = (i_data_b * Y_PARA_B);
endalways @(posedge clk) beginr_u1 = (i_data_r * U_PARA_R);r_u2 = (i_data_g * U_PARA_G);r_u3 = (i_data_b * U_PARA_B);
endalways @(posedge clk) beginr_v1 = (i_data_r * V_PARA_R);r_v2 = (i_data_g * V_PARA_G);r_v3 = (i_data_b * V_PARA_B);
end//2 clock
always @(posedge clk) beginro_data_y = r_y1 + r_y2 + r_y3;ro_data_u = r_u3 - r_u1 - r_u2 + BASE;ro_data_v = r_v1 - r_v2 - r_v3 + BASE;
end//sync_validalways @(posedge clk) beginro_data_valid <= {ro_data_valid[0],i_data_valid};
endendmodule
整体转换周期只有两个时钟周期,所以valid信号要打两拍。
4.2 仿真观察
整体仿真环境如《详解BMP图片格式以及关于Verilog图像处理的仿真环境搭建》里的一样,直接把本模块例化进去就可以:
rgb2yuv u_rgb2yuv(.clk ( clk ),.rst ( reset ),.i_data_valid ( valid_i ),.i_data_r ( img_data_i[23:16] ),.i_data_g ( img_data_i[15:8] ),.i_data_b ( img_data_i[7:0] ),.o_data_valid ( valid_o ),.o_data_y ( img_data_o[23:16] ),.o_data_u ( img_data_o[15:8] ),.o_data_v ( img_data_o[7:0] )
);
运行仿真:
从打印信息来看,文件初始化完成。
我们可以看到,输入的第一个RGB是 0xe1e1e3和matlab的数值一样,输出的第一个YUV像素值是0xe1817f和matlab给的0xe18180不一致,这是因为matlab采取的四舍五入,fpga只能取整,所以这就是fpga计算小数带来的误差,我们打开输出文件夹:
我们可以看到原始图像已经转换成了yuv格式,图片和matlab显示的一致,至此rgb转yuv已完成。
五、MATLAB实现YUV444转RGB888
5.1 matlab代码
clear all; close all; clc;
% 读取 YCbCr 图像
% 假设 YCbCr 图像文件名为 'input_ycbcr.png'
ycbcr_image = imread('...\...\...\...\....\....\....\new_img.bmp');% 显示原始 YCbCr 图像
figure;
subplot(1, 2, 1);
imshow(ycbcr_image);
title('Original YCbCr Image');% 将 YCbCr 转换为 RGB
Y = double(ycbcr_image(:,:,1));
Cb = double(ycbcr_image(:,:,2));
Cr = double(ycbcr_image(:,:,3));% 计算 RGB
R = Y + 1.402 * (Cr - 128);
G = Y - 0.344136 * (Cb - 128) - 0.714136 * (Cr - 128);
B = Y + 1.772 * (Cb - 128);% 限制 RGB 值在 [0, 255] 范围内
R = min(max(R, 0), 255);
G = min(max(G, 0), 255);
B = min(max(B, 0), 255);% 合并 RGB 分量
RGB = cat(3, uint8(R), uint8(G), uint8(B));% 显示转换后的 RGB 图像
subplot(1, 2, 2);
imshow(RGB);
title('Converted RGB Image');
5.2 matlab结果
我们可以看到将已经转成YUV格式的图片通过这个公式有转会了RGB格式,与原图一致。
六、Verilog实现YUV444转RGB888
YUV转RGB和上面一样,使用浮点数转定点数就好,但是注意溢出的问题,最后还要将RGB数值限幅到[0-255],代码如下:
6.1 Verilog代码
`timescale 1ns / 1ps
module yuv2rgb(input clk ,input rst ,//输入YUV原始信号input i_data_valid ,input [7:0] i_data_y ,input [7:0] i_data_u ,input [7:0] i_data_v ,//输出转行后的rgb信号output o_data_valid ,output reg [7:0] o_data_r ,output reg [7:0] o_data_g ,output reg [7:0] o_data_b
);//R = Y + 1.402 * (V - 128) = Y + 1.402*V - 1.402 * 128
//G = Y - 0.344 * (U - 128) - 0.714 * (V - 128) = Y - 0.344*U - 0.714*V + 1.058*128
//B = Y + 1.772 * (U - 128) = Y + 1.772*U - 1.772 * 128/****************parameter********************/
parameter R_PARA_V = 1436; // 1.402*1024parameter G_PARA_U = 352, // 0.344*1024G_PARA_V = 731; // 0.714*1024parameter B_PARA_U = 1815; // 1.772*1024parameter BASE1 = 183763, // 1.402*128*1024BASE2 = 138674, // 1.058*128*1024BASE3 = 223260; // 1.772*128*1024/*******************reg***********************/
reg [19:0] r_r1 ;
reg [19:0] r_r2 ;
reg [19:0] r_g1 ;
reg [19:0] r_g2 ;
reg [19:0] r_b1 ;
reg [2:0] ro_data_valid ;
reg [19:0] ro_data_r ;
reg [19:0] ro_data_g ;
reg [19:0] ro_data_b ;
/******************assign*********************/
assign o_data_valid = ro_data_valid[2] ;//1clock
always @(posedge clk) beginr_r1 <= {i_data_y,10'b0};r_r2 <= i_data_v * R_PARA_V;
endalways @(posedge clk) beginr_g1 <= i_data_u * G_PARA_U;r_g2 <= i_data_v * G_PARA_V;
endalways @(posedge clk) beginr_b1 <= i_data_u * B_PARA_U;
end//2clock
always @(posedge clk) beginro_data_r <= r_r1 + r_r2 - BASE1;ro_data_g <= r_r1 - r_g1 - r_g2 + BASE2;ro_data_b <= r_r1 + r_b1 - BASE3;
end//3clock
always @(posedge clk) beginif(ro_data_r[19]==1'b1)begin //若结果为负数,则变为0o_data_r <= 8'd0;end else beginif(ro_data_r[18]==1'b1) //如果超过了255,则限幅到255o_data_r <= 8'd255;elseo_data_r <= ro_data_r[17:10];end
endalways @(posedge clk) beginif(ro_data_g[19]==1'b1)begin //若结果为负数,则变为0o_data_g <= 8'd0;end else beginif(ro_data_g[18]==1'b1) //如果超过了255,则限幅到255o_data_g <= 8'd255;elseo_data_g <= ro_data_g[17:10];end
endalways @(posedge clk) beginif(ro_data_b[19]==1'b1)begin //若结果为负数,则变为0o_data_b <= 8'd0;end else beginif(ro_data_b[18]==1'b1) //如果超过了255,则限幅到255o_data_b <= 8'd255;elseo_data_b <= ro_data_b[17:10];end
endalways @(posedge clk) beginro_data_valid <= {ro_data_valid[1:0],i_data_valid};
endendmodule
6.2 仿真观察
打开仿真,先看打印信息:
图片初始化完成,我们再来观察数值:
输入的第一个YUV数值是0xe1817f,与前文RGB转出的YUV数值一致。我们可以看到输出的RGB数值为0xdfe1eb,转换成十进制就是(223,225,235),而最原始的图片第一个RGB数值是(225,225,227)这也是转换两次后的误差导致。我们打开输出文件夹:
可以看到new_img_1就是我们从RGB转到YUV再转到RGB的图片,可以看出图片是一致的,仔细看颜色会有看出有一点点区别,这就是精度的问题;如果想要更高的精度,可以试着把浮点数扩到4096倍再计算。