一、说明
实现平台:Quartus17.1、MATLAB2022b和Modelsim SE-64 10.4
二、内容
本文内容主要实现图片进行RGB模式转化为YCbCr模式的算法实现,首先用MATLAB进行算法的验证,最后再通过FPGA实现具体算法。
三、原理
YCbCr是一种数字信号,其在数字视频领域应用广泛,是计算机中应用最多的格式,JPEG,MPEG等都采用YCbCr格式。
YCbCr格式具体可以分为两种格式:tv range格式和full range模式,主要区别如下:
1) tv range格式
Y∈[16,235],Cb∈[16,240],Cr∈[16,240],主要是广播电视采用的数字标准。
2)full range格式
Y,Cb,Cr∈[0,255],主要是PC端采用的标准。
图像传感器可以配置输出RGB/YCbCr格式图像,根据对应手册的转换公式即可。本文根据OV7725图像传感器手册的对应关系:

使用硬件实现算法,首先要考虑去掉浮点,再不考虑精度的前提下,将数值扩大256倍,为防止溢出,舍去小数点而不进行四舍五入,公式如下:
由于数值扩大了256倍,即2的8次方,因此对上述结果再右移8bit,得到结果如下:
同上,Cb/Cr的计算公式如下:
四、MATLAB实现
1)RGB转YCbCr
clear all;close all;clc;
%read pc image to matlab
IMG1 = imread('...\素材\0_images\Scart.jpg');%读取jpg图像
h = size(IMG1,1); %读取图像高度
w = size(IMG1,2); %读取图像宽度
subplot(221);
imshow(IMG1);
title('RGB Image');
% RGB转YCbCr
%Y = (R*76 + G*150 + B*29) >>8
%Cb = (-R*43 - G*84 + B*128 + 32768) >>8
%Cr = (R*128 - G*107 - B*20 + 32768) >>8
IMG1 = double(IMG1);
IMG_YCbCr = zeros(h,w,3);
for i = 1:h
for j = 1:w
IMG_YCbCr(i,j,1) = bitshift((IMG1(i,j,1)*76 + IMG1(i,j,2)*150 + IMG1(i,j,3)*29),-8);
IMG_YCbCr(i,j,2) = bitshift((-IMG1(i,j,1)*43 - IMG1(i,j,2)*84 + IMG1(i,j,3)*128 + 32768),-8);
IMG_YCbCr(i,j,3) = bitshift((IMG1(i,j,1)*128 - IMG1(i,j,2)*107 - IMG1(i,j,3)*20 + 32768),-8);
end
end
%display Y Cb Cr channel
IMG_YCbCr = uint8(IMG_YCbCr);
subplot(222);imshow(IMG_YCbCr(:,:,1));title('Y Channel');
subplot(223);imshow(IMG_YCbCr(:,:,2));title('Cb Channel');
subplot(224);imshow(IMG_YCbCr(:,:,3));title('Cr Channel');
MATLAB执行显示图结果(RGB图,Y通道图,Cb通道图和Cr通道图)

2)仿真数据准备
为了后续FPGA设计中,配合ModelSim进行RTL仿真,实现仿真模型与Verilog代码的对比分析,需要提前生成Y、Cb、Cr通道的数据。
首先生成测试图像的数据,用MATLAB转化为img_RGB.dat,保存于当前目录下。将MATLAB转换好的Y、Cb、Cr通道的数据写入到img_YCbCr.dat文件中,用于后续与RTL仿真生成的结果进行对比,具体实现代码封装成函数,如下:
function RGB2YCbCr_Data_Gen(img_RGB, img_YCbCr);
% 打印函数1:RGB输入,YCbCr输出
% RGB2YCbCr_Data_Gen(uinit8 img_RGB, uint8 img_YCbCr)
% img_RGB:输入待处理的RGB图像
% img_YCbCr:输入处理后的YCbCr图像
% img_RGB.dat:输出 待处理的RGB图像hex数据(比对源数据)
% img_YCbCr.dat:输出处理完的YCbCr图像hex数据(比对结果)
h1 = size(img_RGB,1); % 读取图像高度
w1 = size(img_RGB,2); % 读取图像宽度
h2 = size(img_YCbCr,1); % 读取图像高度
w2 = size(img_YCbCr,2); % 读取图像宽度
%Simulation source data generate
bar = waitbar(0,'Speed of source data generating ...'); %creat process bar
fid = fopen('.\img_RGB.dat','wt');
for row = 1:h1
r = lower(dec2hex(img_RGB(row,:,1),2))';
g = lower(dec2hex(img_RGB(row,:,2),2))';
b = lower(dec2hex(img_RGB(row,:,3),2))';
str_data_tmp = [];
for col = 1:w1
str_data_tmp = [str_data_tmp,r(col*2-1:col*2),' ',g(col*2-1:col*2),' ',b(col*2-1:col*2),' '];
end
str_data_tmp = [str_data_tmp,10];
fprintf(fid,'%s',str_data_tmp);
waitbar(row/h1);
end
fclose(fid);
close(bar); %close waitbar
%Simulation target data generate
bar = waitbar(0,'Speed of source data generating ...'); %creat process bar
fid = fopen('.\img_YCbCr.dat','wt');
for row = 1:h2
Y = lower(dec2hex(img_YCbCr(row,:,1),2))';
Cb = lower(dec2hex(img_YCbCr(row,:,2),2))';
Cr = lower(dec2hex(img_YCbCr(row,:,3),2))';
str_data_tmp = [];
for col = 1:w2
str_data_tmp = [str_data_tmp,Y(col*2-1:col*2),' ',Cb(col*2-1:col*2),' ',Cr(col*2-1:col*2),' '];
end
str_data_tmp = [str_data_tmp,10];
fprintf(fid,'%s',str_data_tmp);
waitbar(row/h2);
end
fclose(fid);
close(bar); %close waitbar
执行该函数:
% -------------------------------------------------------------------------
% Generate image Source Data and Target Data
RGB2YCbCr_Data_Gen(IMG1, IMG_YCbCr);
生成文件过程:

最后分别产生了img_RGB.dat和img_YCbCr.dat文件:

文件具体内容如下:

五、FPGA实现
1)FPGA实现代码:
`timescale 1ns/1ns
module RGB888_YCbCr444
(
//global clock
input clk,
input rst_n,
//Image data prepared to be processed
input per_img_vsync, //prepared Image data vsync valid signal
input per_img_href, //prepared Image data href valid signal
input [7:0] per_img_red, //prepared Image red data to be processed
input [7:0] per_img_green, //prepared Image green data to be processed
input [7:0] per_img_blue, //prepared Image blue data to be processed
//processed image data
output post_img_vsync, //processed Image data vsync valid signal
output post_img_href, //processed Image data href valid signal
output [7:0] post_img_Y, //processed Image brightness output
output [7:0] post_img_Cb, //processed Image blue shading output
output [7:0] post_img_Cr //processed Image red shading output
);
//step 1 9 parallel multiplication using 1 clocks
reg [15:0] img_red_r0,img_red_r1,img_red_r2;
reg [15:0] img_green_r0,img_green_r1,img_green_r2;
reg [15:0] img_blue_r0,img_blue_r1,img_blue_r2;
always @(posedge clk)begin
img_red_r0 <= per_img_red * 8'd76;
img_red_r1 <= per_img_red * 8'd43;
img_red_r2 <= per_img_red * 8'd128;
img_green_r0 <= per_img_green * 8'd150;
img_green_r1 <= per_img_green * 8'd84;
img_green_r2 <= per_img_green * 8'd107;
img_blue_r0 <= per_img_blue * 8'd29;
img_blue_r1 <= per_img_blue * 8'd128;
img_blue_r2 <= per_img_blue * 8'd20;
end
//step2 Addition and subtraction operations using 1 clock
reg [15:0] img_Y_r0;
reg [15:0] img_Cb_r0;
reg [15:0] img_Cr_r0;
always @(posedge clk)begin
img_Y_r0 <= img_red_r0 + img_green_r0 + img_blue_r0;
img_Cb_r0 <= img_blue_r1 - img_red_r1 - img_green_r1 + 16'd32768;
img_Cr_r0 <= img_red_r2 - img_green_r2 - img_blue_r2 + 16'd32768;
end
//step3 Reduce the data to 1/256 and directly intercept the upper 8 bits using 1 clock
reg [7:0] img_Y_r1;
reg [7:0] img_Cb_r1;
reg [7:0] img_Cr_r1;
always @(posedge clk)begin
img_Y_r1 <= img_Y_r0[15:8];
img_Cb_r1 <= img_Cb_r0[15:8];
img_Cr_r1 <= img_Cr_r0[15:8];
end
//lag 3 clocks signal sync
reg [2:0] per_img_vsync_r;
reg [2:0] per_img_href_r;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
per_img_vsync_r <= 0;
per_img_href_r <= 0;
end
else begin
per_img_vsync_r <= {per_img_vsync_r[1:0],per_img_vsync};
per_img_href_r <= {per_img_href_r[1:0],per_img_href};
end
end
assign post_img_vsync = per_img_vsync_r[2];
assign post_img_href = per_img_href_r[2];
assign post_img_Y = post_img_href ? img_Y_r1 : 8'd0;
assign post_img_Cb = post_img_href ? img_Cb_r1 : 8'd0;
assign post_img_Cr = post_img_href ? img_Cr_r1 : 8'd0;
endmodule
2)testbench文件:
testbench中主要由两个任务,分别为image_input任务和image_result
_chech任务。其中,image_input任务从MATLAB仿真目录下的dat文件中读取分辨率为640*480的图像数据并按照一定的时序产生视频激励信号;image_result_check任务从MATLAB仿真目录下的dat文本中读取YCbCr图像数据,用于对ModelSim仿真结果进行对比校验,当发现MATLAB和ModelSim仿真结果不一致时,会将错误数据的图像行、列位置;ModelSim仿真结果和MATLAB仿真结果打印出来。
具体实现代码如下:
`timescale 1 ns/ 1 ps
module RGB888_YCbCr444_vlg_tst();
localparam image_width = 640;
localparam image_height = 480;
//----------------------------------------------------------------------
// clk & rst_n
reg clk;
reg rst_n;
initial
begin
clk = 1'b0;
forever #5 clk = ~clk;
end
initial
begin
rst_n = 1'b0;
repeat(50) @(posedge clk);
rst_n = 1'b1;
end
//----------------------------------------------------------------------
// Image data prepred to be processed
reg per_img_vsync;
reg per_img_href;
reg [7:0] per_img_red;
reg [7:0] per_img_green;
reg [7:0] per_img_blue;
// Image data has been processed
wire post_img_vsync;
wire post_img_href;
wire [7:0] post_img_Y;
wire [7:0] post_img_Cb;
wire [7:0] post_img_Cr;
//----------------------------------------------------------------------
// task and function
reg [31:0] row_cnt,row_cnt1;
reg [31:0] col_cnt,col_cnt1;
reg [7:0] mem [image_width*image_height*3-1:0];
reg [7:0] mem1 [image_width*image_height*3-1:0];
task image_input;begin
$readmemh(".../img_RGB.dat",mem);
for(row_cnt = 0;row_cnt < image_height;row_cnt = row_cnt + 1)
begin
repeat(5) @(posedge clk);
per_img_vsync = 1'b1;
repeat(5) @(posedge clk);
for(col_cnt = 0;col_cnt < image_width;col_cnt = col_cnt + 1)
begin
per_img_href = 1'b1;
per_img_red = mem[(row_cnt*image_width+col_cnt)*3+0];
per_img_green = mem[(row_cnt*image_width+col_cnt)*3+1];
per_img_blue = mem[(row_cnt*image_width+col_cnt)*3+2];
@(posedge clk);
end
per_img_href = 1'b0;
end
per_img_vsync = 1'b0;
@(posedge clk);
end
endtask
reg post_img_vsync_r;
always @(posedge clk)
begin
if(rst_n == 1'b0)
post_img_vsync_r <= 1'b0;
else
post_img_vsync_r <= post_img_vsync;
end
wire post_img_vsync_pos;
wire post_img_vsync_neg;
assign post_img_vsync_pos = ~post_img_vsync_r & post_img_vsync;
assign post_img_vsync_neg = post_img_vsync_r & ~post_img_vsync;
reg frame_flag;
task image_result_check;begin
frame_flag = 0;
$readmemh(".../img_YCbCr.dat",mem1);
while(1)
begin
@(posedge clk);
if(post_img_vsync_pos == 1'b1)
begin
frame_flag = 1;
row_cnt1 = 0;
col_cnt1 = 0;
$display("##############image result check begin##############");
end
if(frame_flag == 1'b1)
begin
if(post_img_href == 1'b1)
begin
if((post_img_Y != mem1[(row_cnt1*image_width+col_cnt1)*3+0])||(post_img_Cb != mem1[(row_cnt1*image_width+col_cnt1)*3+1])||(post_img_Cr != mem1[(row_cnt1*image_width+col_cnt1)*3+2]))
begin
$display("result error ---> row_num : %0d;col_num : %0d;pixel data(y cb cr) : (%h %h %h);reference data(y cb cr) : (%h %h %h)",row_cnt+1,col_cnt+1,post_img_Y,post_img_Cb,post_img_Cr,mem[(row_cnt*image_width+col_cnt)*3+0],mem[(row_cnt*image_width+col_cnt)*3+1],mem[(row_cnt*image_width+col_cnt)*3+2]);
end
col_cnt1 = col_cnt1 + 1;
end
if(col_cnt1 == image_width)
begin
col_cnt1 = 0;
row_cnt1 = row_cnt1 + 1;
end
end
if(post_img_vsync_neg == 1'b1)
begin
frame_flag = 0;
$display("##############image result check end##############");
end
end
end
endtask
//----------------------------------------------------------------------
RGB888_YCbCr444 u1
(
// global clock
.clk (clk ),
.rst_n (rst_n ),
// Image data prepred to be processed
.per_img_vsync (per_img_vsync ),
.per_img_href (per_img_href ),
.per_img_red (per_img_red ),
.per_img_green (per_img_green ),
.per_img_blue (per_img_blue ),
// Image data has been processed
.post_img_vsync (post_img_vsync ),
.post_img_href (post_img_href ),
.post_img_Y (post_img_Y ),
.post_img_Cb (post_img_Cb ),
.post_img_Cr (post_img_Cr )
);
initial
begin
per_img_vsync = 0;
per_img_href = 0;
per_img_red = 0;
per_img_green = 0;
per_img_blue = 0;
end
initial
begin
wait(rst_n);
fork
begin
repeat(5) @(posedge clk);
image_input;
end
image_result_check;
join
end
endmodule
六、仿真结果
仿真结果如下,没有发现错误,表明仿真结果成功。

根据红框位置可进行验算,输入的R,G,B数据分别为133,152,169,根据公式可以分别计算出Y,Cb,Cr的值分别为:
与仿真结果一致,结果正确。
若结果不正确的话会打印错误信息,如下图:


被折叠的 条评论
为什么被折叠?



