1、RGB转YUV的基本原理
YUV(YCbCr)是另一种色彩空间,Y是指饱和度、U和V是指色度,YUV在图像处理领域有着比RGB更广泛的应用。需要灰度图时,只需要输出Y值即可,下面是转换的公式。
2、RGB转YUV的matlab实现
考虑到FPGA只能进行整点数的操作,因此在matlab仿真中先把数值放大256倍,然后移8位,如下图所示,并且为了防止负数产生,所以加上32768。这样子我们的数据就都是在0到255之间了,可以正常显示。
matlab的代码如下所示,代码比较基础,就是对上述的几个公式的复现。
clc;
clear all;
img_rgb=imread('dog.png');
%high and width
h=size(img_rgb,1);
w=size(img_rgb,2);
%rgb dog show
subplot(221);
imshow(img_rgb);
title('rgb dog');
% Relized method 2:myself Algorithm realized
% Y = ( R*77 + G*150 + B*29) >>8
% Cb = (-R*44 - G*84 + B*128) >>8
% Cr = ( R*128 - G*108 - B*20) >>8
img_rgb=double(img_rgb);
img_y=zeros(h,w);
img_u=zeros(h,w);
img_v=zeros(h,w);
for i = 1 : h
for j = 1 : w
img_y(i,j) = bitshift(( img_rgb(i,j,1)*77 + img_rgb(i,j,2)*150 + img_rgb(i,j,3)*29),-8);
img_u(i,j) = bitshift((-img_rgb(i,j,1)*44 - img_rgb(i,j,2)*84 + img_rgb(i,j,3)*128 + 32678),-8);
img_v(i,j) = bitshift(( img_rgb(i,j,1)*128 - img_rgb(i,j,2)*108 - img_rgb(i,j,3)*20 + 32678),-8);
end
end
img_y = uint8(img_y);
img_u = uint8(img_u);
img_v = uint8(img_v);
%dog show
subplot(222);
imshow(img_y);
title('gray dog');
subplot(223);
imshow(img_u);
title('u dog');
subplot(224);
imshow(img_v);
title('v dog');
最后显示的效果如下图所示,第一张是原图;第二张是Y分量图也就是灰度图;第三张是U分量的图;第四张是V分量的图。
3、RGB转YUV的FPGA实现
FPGA实现RGB转YUV,这边可以在ov5640显示工程的基础上进行修改,前面两节已经很清楚的讲了ov5640显示环境的搭建。为了实现rgb2yuv,需要自己编写一个转换的模块,转换的verilog代码如下图。
我这边的verilog代码是采用了流水线设计,而不是直接用组合逻辑去实现,也是为了方便打拍子。代码整个流程分为三步:第一步是将RGB分量分开并且扩大256倍;第二步是将各分量转换为YUV分量;第三步是将YUV分量向右移8位。还有比较重要的一点是,时钟使能信号、场同步信号和数据有效信号需要延时三个时钟周期来达到同步的目的。此外,最终输出是只取了Y分量来达到输出灰度图的目的。
module rgb2yuv(
input pclk,
input rst_n,
input [23:0] rgb_data,
input rgb_data_vaild,
input rgb_vsync,
input rgb_clk_en,
output wire [23:0] gray_data,
output wire gray_data_vaild,
output wire gray_vsync,
output wire gray_clk_en
);
wire [7:0] rgb_r;
wire [7:0] rgb_g;
wire [7:0] rgb_b;
wire [7:0] yuv_y;
wire [7:0] yuv_u;
wire [7:0] yuv_v;
reg [15:0] rgb_r0;
reg [15:0] rgb_g0;
reg [15:0] rgb_b0;
reg [15:0] rgb_r1;
reg [15:0] rgb_g1;
reg [15:0] rgb_b1;
reg [15:0] rgb_r2;
reg [15:0] rgb_g2;
reg [15:0] rgb_b2;
reg [15:0] rgb_r3;
reg [15:0] rgb_g3;
reg [15:0] rgb_b3;
reg [15:0] yuv_y0;
reg [15:0] yuv_u0;
reg [15:0] yuv_v0;
reg [7:0] yuv_y1;
reg [7:0] yuv_u1;
reg [7:0] yuv_v1;
reg [2:0] gray_data_vaild_dy;
reg [2:0] gray_vsync_dy;
reg [2:0] gray_clk_en_dy;
assign rgb_r=rgb_data[23:16];
assign rgb_g=rgb_data[15:8];
assign rgb_b=rgb_data[7:0];
assign gray_data_vaild=gray_data_vaild_dy[2];
assign gray_vsync=gray_vsync_dy[2];
assign gray_clk_en=gray_clk_en_dy[2];
assign yuv_y=gray_data_vaild? yuv_y1:8'd0;
assign yuv_u=gray_data_vaild? yuv_u1:8'd0;
assign yuv_v=gray_data_vaild? yuv_v1:8'd0;
assign gray_data={yuv_y,yuv_y,yuv_y};
// rgb to yuv calu
// Y = ( R*77 + G*150 + B*29) >>8
// U = (-R*44 - G*84 + B*128 + 32768) >>8
// V = ( R*128 -G*108 - B*20 + 32768) >>8
//step1:multiply data
always @(posedge pclk or negedge rst_n) begin
if(~rst_n)begin
rgb_r0<=16'd0;
rgb_g0<=16'd0;
rgb_b0<=16'd0;
rgb_r1<=16'd0;
rgb_g1<=16'd0;
rgb_b1<=16'd0;
rgb_r2<=16'd0;
rgb_g2<=16'd0;
rgb_b2<=16'd0;
end
else begin
rgb_r0<=8'd77*rgb_r;
rgb_g0<=8'd150*rgb_g;
rgb_b0<=8'd29*rgb_b;
rgb_r1<=8'd44*rgb_r;
rgb_g1<=8'd84*rgb_g;
rgb_b1<=8'd128*rgb_b;
rgb_r2<=8'd128*rgb_r;
rgb_g2<=8'd108*rgb_g;
rgb_b2<=8'd20*rgb_b;
end
end
//step2:add data
always @(posedge pclk or negedge rst_n) begin
if(~rst_n)begin
yuv_y0<=16'd0;
yuv_u0<=16'd0;
yuv_v0<=16'd0;
end
else begin
yuv_y0<=rgb_r0+rgb_g0+rgb_b0;
yuv_u0<=16'd32768-rgb_r1-rgb_g1+rgb_b1;
yuv_v0<=16'd32768+rgb_r2-rgb_g2-rgb_b2;
end
end
//step3:shift data
always @(posedge pclk or negedge rst_n) begin
if(~rst_n)begin
yuv_y1<=8'd0;
yuv_u1<=8'd0;
yuv_v1<=8'd0;
end
else begin
yuv_y1<=yuv_y0[15:8];
yuv_u1<=yuv_u0[15:8];
yuv_v1<=yuv_v0[15:8];
end
end
//dleay 3 tclk
always @(posedge pclk or negedge rst_n) begin
if(~rst_n)begin
gray_data_vaild_dy<=3'd0;
gray_vsync_dy<=3'd0;
gray_clk_en_dy<=3'd0;
end
else begin
gray_data_vaild_dy<={gray_data_vaild_dy[1:0],rgb_data_vaild};
gray_vsync_dy<={gray_vsync_dy[1:0],rgb_vsync};
gray_clk_en_dy<={gray_clk_en_dy[1:0],rgb_clk_en};
end
end
endmodule
我们将编写好的verilog代码添加到block design中并且连接各个信号线,如下图所示。检查无误后,生成bit流导出到sdk,sdk代码和摄像头显示代码完全相同。
4、灰度图显示效果
HDMI屏上正确显示了经过灰度处理的狗子!
5、灰度显示模块打包
为了通用性,我们将灰度模块打包成一个IP核,打包完的IP核如下图所示原图(左),打包图(右),我们在bd图中将原有模块用打包后的IP核进行替换,也可以得到正确的结果图。