本节主要介绍了FPGA图像处理中RGB转YCbCr算法,以及它的MATLAB与FPGA实现。
RGB与YCbCr色域介绍
RGB介绍:计算机处理的BMP图片为24Bit的位图,即每一通道的颜色可以细分为$2^8 = 256$级别(RGB888),每一通道的色彩分辨率能达到256级,总共能综合出的颜色种类为:
$$
R的种类\times G的种类\times B的种类 = 2^8 \times 2^8\times 2^8 = 16777216\approx1600万
$$YCbCr介绍:Y表示颜色的明亮度和浓度,Cb和Cr则分别表示颜色的蓝色浓度偏移量和红色浓度偏移量。YCbCr是一种数字信号,其色彩模型源于YUV颜色模型。在数字视频领域应用广泛是计算机中应用最多的格式,JPEG、MPEG、H.264/5、AVS等都采用YCbCr格式。YCbCr格式可以继续分为两种格式:tv range格式与full range格式
- tv range格式:$Y\in [16, 235], Cb\in[16,240], Cr\in[16, 240]$,主要是广播电视采用的数字标准。
- full range格式:$Y、Cb、Cr\in [0, 255]$,主要是PC端采用的标准,所以也称为pc range格式
对full range或者pc range的YCbCr格式,其RGB与YCbCr的相互转换公式为:
$$
\begin{bmatrix}
Y \\
Cb \\
Cr \\
\end{bmatrix} = \begin{bmatrix}
0 \\
128 \\
128 \\
\end{bmatrix}+\begin{bmatrix}
0.299 & 0.587 & 0.114 \\
-0.169 & -0.331 & 0.500 \\
0.500 & -0.419 & -0.081 \\
\end{bmatrix}\times \begin{bmatrix}
R \\
G \\
B \\
\end{bmatrix}, 其中\begin{cases}
\begin{aligned}
R/G/B \in [0,255]\
Y/Cb/Cr \in [0,255]
\end{aligned}
\end{cases}
$$
RGB转YCbCr硬件实现推导
流水线计算:
- Step1:多个乘法器并行计算多个像素点的RGB通道乘法
- Step2:将每个像素点乘完后的结果相加
- Step3:将累加后的结果取高8bit,得到最后的结果
定点量化推导:
把数值进行256倍扩大,舍去小数
那么以Y通道为例,量化后的Y1计算为:$Y1 = R\times76.544+G\times150.272+B\times 29.184\approx R\times 76 +G \times 150+B\times 29$
由于数值扩大了256倍,即2的8次方,因此对上述结果再右移8bit(实际可取高8bit,不用移位),得到的结果如下(其中76+150+29=255 <256)
$$
Y2 = (R\times 76 + G \times 150 +B\times 29) >> 8\
Cb = (-R\times 43 - G\times 84 + B\times 128+32768) >>8\
Cr = (R\times128 -G\times107 -B\times20+32768)>>8
$$
RGB转YCbCr的MATLAB实现
MATLAB代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30clear 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');
% -------------------------------------------------------------------------
% Relized by user logic
% 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');仿真结果:
RGB转YCbCr的FPGA实现
源代码VIP_RGB888_YCbCr444.v:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
module VIP_RGB888_YCbCr444
(
//global clock
input clk, //cmos video pixel clock
input rst_n, //global reset
//Image data prepred to be processed
input per_img_vsync, //Prepared Image data vsync valid signal
input per_img_href, //Prepared Image data href vaild 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
//Image data has been processed
output post_img_vsync, //Processed Image data vsync valid signal
output post_img_href, //Processed Image data href vaild 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
);
//--------------------------------------------
/*********************************************
//Refer to full/pc range YCbCr format
Y = R*0.299 + G*0.587 + B*0.114
Cb = -R*0.169 - G*0.331 + B*0.5 + 128
Cr = R*0.5 - G*0.419 - B*0.081 + 128
--->
Y = (76 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 84 *G + 128*B + 32768)>>8
Cr = (128*R - 107*G - 20 *B + 32768)>>8
**********************************************/
//Step 1
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
//--------------------------------------------------
//Step 2
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
//--------------------------------------------------
//Step 3
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;
endmoduletestbench.sv:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172`timescale 1ns/1ns
module testbench;
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
task image_input;
bit [31:0] row_cnt;
bit [31:0] col_cnt;
bit [7:0] mem [image_width*image_height*3-1:0];
$readmemh(".././source_datasets/img_RGB.dat",mem);
for(row_cnt = 0;row_cnt < image_height;row_cnt++)
begin
repeat(5) @(posedge clk);
per_img_vsync = 1'b1;
repeat(5) @(posedge clk);
for(col_cnt = 0;col_cnt < image_width;col_cnt++)
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);
endtask : image_input
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;
task image_result_check;
bit frame_flag;
bit [31:0] row_cnt;
bit [31:0] col_cnt;
bit [ 7:0] mem [image_width*image_height*3-1:0];
frame_flag = 0;
$readmemh(".././source_datasets/img_YCbCr.dat",mem);
while(1)
begin
@(posedge clk);
if(post_img_vsync_pos == 1'b1)
begin
frame_flag = 1;
row_cnt = 0;
col_cnt = 0;
$display("##############image result check begin##############");
end
if(frame_flag == 1'b1)
begin
if(post_img_href == 1'b1)
begin
if((post_img_Y != mem[(row_cnt*image_width+col_cnt)*3+0])||(post_img_Cb != mem[(row_cnt*image_width+col_cnt)*3+1])||(post_img_Cr != mem[(row_cnt*image_width+col_cnt)*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_cnt = col_cnt + 1;
end
if(col_cnt == image_width)
begin
col_cnt = 0;
row_cnt = row_cnt + 1;
end
end
if(post_img_vsync_neg == 1'b1)
begin
frame_flag = 0;
$display("##############image result check end##############");
end
end
endtask : image_result_check
//----------------------------------------------------------------------
VIP_RGB888_YCbCr444 u_VIP_RGB888_YCbCr444
(
// 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仿真结果:
仿真平台的架构和代码写法要重点学习:
在该设计文件中,是直接对数据进行流水计算的,没有在数据计算前先判断数据是否有效再计算(我直接一直都是这么写得),但该设计中是在数据输出的时候,增加了一个assign组合逻辑的选择判断输出的,感觉这个写法更好,简洁明了,在数据计算的时候不要总担心数据是否有效,最后算完再来把关就行。
1
2
3assign 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;
Reference
- 《基于MATLAB与FPGA的图像处理教程》图书及其配套资料