在日常生活中我们最常用的是RGB色彩空间,此外还有一些色彩空间(也称彩色模型),它们在某些应用中可能比RGB更方便和更恰当,这些模型是RGB模型的变换,包括NTSC、YCbCr、HSV、CMY和HSI等模型,它们之间存在在相互转换的计算公式,下面就常用的YCbCr和
HSV模型展开介绍。
YCbCr模型介绍:YCbCr模型广泛用于数字视频中,在这种格式下,亮度信息用单个分量Y表示,彩色信息存储为两个色差分量Cb和Cr,分量Cb是蓝色分量和参考值的差,分量Cr是红色分量和参考值的差,其转换公式为:
{ Y = 0.299 R + 0.587 G + 0.114 B C b = − 0.172 R − 0.339 G + 0.511 B + 128 C r = 0.511 R − 0.428 G − 0.083 B + 128 \left\{ \begin{array}{c} Y=0.299R + 0.587G + 0.114B \\ Cb=-0.172R - 0.339G + 0.511B +128 \\ Cr= 0.511R - 0.428G -0.083B + 128 \\ \end{array} \right. ⎩ ⎨ ⎧Y=0.299R+0.587G+0.114BCb=−0.172R−0.339G+0.511B+128Cr=0.511R−0.428G−0.083B+128
注意:由于Verilog HDL无法进行浮点运算,因此使用扩大256倍,再向右移8Bit的方式,来转换公式,则变换之后的公式为:
{ Y = ( ( 77 R + 150 G + 29 B ) > > 8 ) C b = ( ( − 43 R − 85 G + 128 B + 32768 ) > > 8 ) C r = ( ( 128 R − 107 G − 21 B + 32768 ) > > 8 ) \left\{ \begin{array}{c} Y=((77R + 150G + 29B)>>8) \\ Cb=((-43R - 85G + 128B + 32768)>>8) \\ Cr= ((128R - 107G -21B + 32768)>>8) \\ \end{array} \right. ⎩ ⎨ ⎧Y=((77R+150G+29B)>>8)Cb=((−43R−85G+128B+32768)>>8)Cr=((128R−107G−21B+32768)>>8)
由公式可以看出,转换过程中,并不会出现结果为负数的情形,在算法移植中记得对运算过程进行拆分,其代码编写如下,可以看到,输出相较于输出分量延时3个CLK:
`timescale 1ns / 1ps
module VIP_RGB888_YCbCr(
input clk,
input rst_n,
//Image data prepred to be processd
input per_frame_vsync, //Prepared Image data vsync valid signal
input per_frame_href, //Prepared Image data href vaild signal
input per_frame_clken, //Prepared Image data output/capture enable clock
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 processd
output post_frame_vsync, //Processed Image data vsync valid signal
output post_frame_href, //Processed Image data href vaild signal
output post_frame_clken, //Processed Image data output/capture enable clock
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 计算出Y\Cb\Cr中每一个乘法的乘积
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 or negedge rst_n) begin
if(!rst_n) begin
img_red_r0 <= 0;
img_red_r1 <= 0;
img_red_r2 <= 0;
img_green_r0 <= 0;
img_green_r1 <= 0;
img_green_r2 <= 0;
img_blue_r0 <= 0;
img_blue_r1 <= 0;
img_blue_r2 <= 0;
end
else begin
img_red_r0 <= per_img_red * 8'd77;
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'd85;
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'd21;
end
end
//Step 2 计算出Y、Cb、Cr括号内的值
reg [15:0] img_Y_r0;
reg [15:0] img_Cb_r0;
reg [15:0] img_Cr_r0;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_Y_r0 <= 0;
img_Cb_r0 <= 0;
img_Cr_r0 <= 0;
end
else 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
end
//Step 3 右移8bit,将数值缩小256倍
reg [7:0] img_Y_r1;
reg [7:0] img_Cb_r1;
reg [7:0] img_Cr_r1;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
img_Y_r1 <= 0;
img_Cb_r1 <= 0;
img_Cr_r1 <= 0;
end
else 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
end
//lag 3 clocks signal sync
reg [2:0] per_frame_vsync_r;
reg [2:0] per_frame_href_r;
reg [2:0] per_frame_clken_r;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_frame_vsync_r <= 0;
per_frame_href_r <= 0;
per_frame_clken_r <= 0;
end
else begin
per_frame_vsync_r <= {per_frame_vsync_r[1:0], per_frame_vsync};
per_frame_href_r <= {per_frame_href_r[1:0], per_frame_href};
per_frame_clken_r <= {per_frame_clken_r[1:0], per_frame_clken};
end
end
assign post_frame_vsync = per_frame_vsync_r[2];
assign post_frame_href = per_frame_href_r[2];
assign post_frame_clken = per_frame_clken_r[2];
assign post_img_Y = post_frame_href ? img_Y_r1 : 8'd0;
assign post_img_Cb = post_frame_href ? img_Cb_r1: 8'd0;
assign post_img_Cr = post_frame_href ? img_Cr_r1: 8'd0;
endmodule
HSV模型展开介绍:
h = { 0 ° , max = min 60 ° ∗ g − b m a x − m i n + 0 ° , max = r,g>=b 60 ° ∗ g − b m a x − m i n + 360 ° , max = r,g=b} \\[2ex] 60°*\frac{g-b}{max-min}+360°, & \text{max = r,gh=⎩ ⎨ ⎧0°,60°∗max−ming−b+0°,60°∗max−ming−b+360°,60°∗max−minb−r+120°,60°∗max−minr−g+240°,max = minmax = r,g>=bmax = r,gmax =gmax =b
s = { 0 , max = 0 m a x − m i n m a x , otherwise s = \begin{cases} 0, & \text{max = 0} \\[2ex] \frac{max-min}{max}, & \text{otherwise} \\[2ex] \end{cases} s=⎩ ⎨ ⎧0,maxmax−min,max = 0otherwise
v = m a x v=max v=max
类比RGB转YCbCr代码,同样需要对RGB转HSV公式进行拆分,延迟于输出分量R、G、B分量4个CLK,即可得到H(色调)、S(饱和度)、V(亮度)分量,但需要注意的是H和S分量位宽为9bits,而V分量位宽为8bits。
`timescale 1ns / 1ps
module VIP_RGB888_HSV(
input clk,
input rst_n,
//Image data prepred to be processd
input per_frame_vsync, //Prepared Image data vsync valid signal
input per_frame_href, //Prepared Image data href vaild signal
input per_frame_clken, //Prepared Image data output/capture enable clock
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 processd
output post_frame_vsync, //Processed Image data vsync valid signal
output post_frame_href, //Processed Image data href vaild signal
output post_frame_clken, //Processed Image data output/capture enable clock
output [8:0] post_img_H, //Processed Image brightness output
output [8:0] post_img_S, //Processed Image blue shading output
output [7:0] post_img_V //Processed Image red shading output
);
//step1 将RGB分量统一扩大60倍,与输入相比,延时1个CLK
reg [13:0] per_img_red_r ;
reg [13:0] per_img_green_r ;
reg [13:0] per_img_blue_r ;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_img_red_r <= 14'd0;
per_img_green_r <= 14'd0;
per_img_blue_r <= 14'd0;
end
else begin
per_img_red_r <= per_img_red *60;
per_img_green_r <= per_img_green *60;
per_img_blue_r <= per_img_blue *60;
end
end
//step2 计算最大值和最小值,与输入相比,延时1个CLK
reg [7:0] max ; //用来存放R\G\B三分量最大值
reg [7:0] min ; //用来存放R\G\B三分量最小值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
max <= 8'd0;
else if((per_img_red >= per_img_blue)&&(per_img_red >= per_img_green))
max <= per_img_red ;
else if((per_img_blue >= per_img_red)&&(per_img_blue >= per_img_green))
max <= per_img_blue ;
else if((per_img_green >= per_img_blue)&&(per_img_green >= per_img_red))
max <= per_img_green ;
end
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
min <= 8'd0;
else if((per_img_red <= per_img_blue)&&(per_img_red <= per_img_green))
min <= per_img_red;
else if((per_img_blue <= per_img_red)&&(per_img_blue <= per_img_green))
min <= per_img_blue;
else if((per_img_green <= per_img_blue)&&(per_img_green <= per_img_red))
min <= per_img_green;
end
//step3 为了与最大值进行比较,与输入相比,延时1个CLK
reg [7:0] per_img_red_dly;
reg [7:0] per_img_blue_dly;
reg [7:0] per_img_green_dly;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_img_red_dly <= 8'd0;
per_img_blue_dly <= 8'd0;
per_img_green_dly <= 8'd0;
end
else begin
per_img_red_dly <= per_img_red;
per_img_blue_dly <= per_img_blue;
per_img_green_dly <= per_img_green;
end
end
//step4 存放除数,与输入相比,延时2个CLK
reg [7:0] max_min;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
max_min <= 8'd0;
else
max_min <= max - min;
end
//step5 存放被除数,与输入相比,延时2个CLK
reg [13:0] g_b;
reg [13:0] b_r;
reg [13:0] r_g;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
g_b <= 14'd0;
b_r <= 14'd0;
r_g <= 14'd0;
end
else if(per_img_green_r >= per_img_blue_r)
g_b <= per_img_green_r - per_img_blue_r;
else if(per_img_green_r < per_img_blue_r)
g_b <= per_img_blue_r - per_img_green_r;
else if(per_img_blue_r >= per_img_red_r)
b_r <= per_img_blue_r - per_img_red_r;
else if(per_img_blue_r < per_img_red_r)
b_r <= per_img_red_r - per_img_blue_r;
else if(per_img_red_r >= per_img_green_r)
r_g <= per_img_red_r - per_img_green_r;
else if(per_img_red_r < per_img_green_r)
r_g <= per_img_green_r - per_img_red_r;
end
//step 6 将max min r g b 再次延时,与输入相比,延时2个CLK
reg [7:0] per_img_red_r2 ;
reg [7:0] per_img_blue_r2 ;
reg [7:0] per_img_green_r2 ;
reg [7:0] max_r2 ;
reg [7:0] min_r2 ;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_img_red_r2 <= 8'd0;
per_img_blue_r2 <= 8'd0;
per_img_green_r2 <= 8'd0;
max_r2 <= 8'd0;
min_r2 <= 8'd0;
end
else begin
per_img_red_r2 <= per_img_red_dly;
per_img_blue_r2 <= per_img_blue_dly;
per_img_green_r2 <= per_img_green_dly;
max_r2 <= max;
min_r2 <= min;
end
end
//step7 存放除法结果,与输入相比,延时3个CLK
reg [13:0] temp;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
temp <= 14'd0;
else if(max_r2 == min_r2)
temp <= 14'd0;
else if(max_r2 == per_img_red_r2)
temp <= g_b/{6'd0,max_min};
else if(max_r2 == per_img_green_r2)
temp <= b_r/{6'd0,max_min};
else if(max_r2 == per_img_blue_r2)
temp <= r_g/{6'd0,max_min};
end
//step 6 将max min r g b 再次延时,与输入相比,延时3个CLK
reg [7:0] per_img_red_r3 ;
reg [7:0] per_img_blue_r3 ;
reg [7:0] per_img_green_r3 ;
reg [7:0] max_r3 ;
reg [7:0] min_r3 ;
reg [7:0] max_min_r3;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
per_img_red_r3 <= 8'd0;
per_img_blue_r3 <= 8'd0;
per_img_green_r3 <= 8'd0;
max_r3 <= 8'd0;
min_r3 <= 8'd0;
max_min_r3 <= 8'd0;
end
else begin
per_img_red_r3 <= per_img_red_r2;
per_img_blue_r3 <= per_img_blue_r2;
per_img_green_r3 <= per_img_green_r2;
max_r3 <= max_r2;
min_r3 <= min_r2;
max_min_r3 <= max_min;
end
end
//step8 计算H分量,与输入相比,延时4个CLK
reg [8:0] post_img_H_r;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
post_img_H_r <= 9'd0;
else if(max_r3 == min_r3)
post_img_H_r <= 9'd0;
else if(max_r3 == per_img_red_r3)
post_img_H_r <= (per_img_green_r3 >= per_img_blue_r3)?temp:(14'd360 - temp);
else if(max_r3 == per_img_green_r3)
post_img_H_r <= (per_img_blue_r3 >= per_img_red_r3)?(temp + 14'd120):(14'd120 - temp);
else if(max_r3 == per_img_blue_r3)
post_img_H_r <= (per_img_red_r3 >= per_img_green_r3)?(temp + 14'd240):(14'd240 - temp);
end
//step9 计算S分量,与输入相比,延时4个CLK
reg [15:0] post_img_S_r;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
post_img_S_r <= 16'd0;
else if(max_r3 == 8'd0)
post_img_S_r <= 16'd0;
else
post_img_S_r <= {max_min_r3,8'b0}/{8'b0,max_r3};
end
//step10 计算V分量,与输入相比,延时4个CLK
reg [7:0] post_img_V_r;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
post_img_V_r <= 8'd0;
else
post_img_V_r <= max_r3;
end
assign post_img_H = post_img_H_r[8:0];
assign post_img_S = post_img_S_r[8:0];
assign post_img_V = post_img_V_r ;
//为了保持同步,控制信号,相比于输入信号,延时4个CLK
reg [3:0] post_frame_vsync_delay;
reg [3:0] post_frame_href_delay ;
reg [3:0] post_frame_clken_delay;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
post_frame_vsync_delay <=4'd0;
post_frame_href_delay <=4'd0;
post_frame_clken_delay <=4'd0;
end
else begin
post_frame_vsync_delay <={post_frame_vsync_delay[2:0],per_frame_vsync};
post_frame_href_delay <={post_frame_href_delay [2:0],per_frame_href};
post_frame_clken_delay <={post_frame_clken_delay[2:0],per_frame_clken};
end
end
assign post_frame_vsync = post_frame_vsync_delay[3];
assign post_frame_href = post_frame_href_delay[3];
assign post_frame_clken = post_frame_clken_delay[3];
endmodule
参考文献:阮秋琦 《数字图像处理》;
CrazyBingo 《基于VIP_Board Big的FPGA入门进阶及图像处理算法开发教程》;