例说FPGA连载71:AV视频采集之YCbCr转RGB实现 特权同学,版权所有 配套例程和更多资料下载链接: http://pan.baidu.com/s/1c0nf6Qc 该模块的内部功能框图如图12.40所示。YCrCb输入视频流经过该模块内部缓存排序、乘累积运算(放大256倍)、加法运算以及溢出与缩小(256倍)处理后,输出RGB视频流。 图12.40 色彩格式转换模块功能框图 在视频图像显示、处理时,采用的颜色空间主要有RGB和YCrCb两种。RGB基于三基色原理,颜色实现简单,在计算机、电视机显示系统中应用广泛。YCrCb将颜色的亮度信号与色度信号分离,易于实现压缩,方便传输和处理。在视频压缩、传输等应用中经常需要实现RGB与YCbCr颜色空间的相互变换。 在RGB颜色空间中,自然界所有颜色都可以用红(R)、绿(G)、蓝(B)三种颜色的不同强度组合而重现。RGB的取值范围分别为0~255。 RGB生成颜色容易实现,被广泛应用在计算机、彩色电视机的显示系统中。但是RGB表示颜色的效率并不是很高,3个颜色分量同等重要,而且亮度信息存在于所有颜色分量中,当需要对像素点的亮度或者色度值进行修改时,必须同时改变RGB三者的值。 在YCRCb颜色空间中,Y表示亮度信号,取值范围为16~235;Cr,Cb表示色度信号,取值范围为16~240,亮度信号与色度信号相互独立。这种颜色表示方法可以利用人眼的特性降低数字彩色图像的存储空间。人眼视觉系统对亮度细节的敏感度高于颜色细节,适当减少色度分辨率不会明显影响图像的画质,易于实现数据压缩。 由于人眼对亮度Y的信息最敏感,因此ITU656送出的YCrCb数据格式每个像素都包含Y信息,而相邻两个像素公用一组CrCb信息,这样它相对RGB模式就能够减少传输数据量。ITU656的YCrCb数据对应所属的像素点如图12.41所示。 图12.41 ITU656格式的像素色彩对应关系 RGB与YCrCb的转换关系式如下。 由于FPGA逻辑运算中,我们无法直接运用浮点数进行运算,因此可以先将等式左右分别放大256倍进行运算,运算完成后再缩小256倍即可。等式左右放大后的结果如下所示。 我们接下来看看逻辑代码中如何实现这个矩阵运算的。我们把上面的等式分别展开为3个等式如下。 R*256 = 256*Y + 359*Cr – 45941 G*256 = 256*Y – 88*Cb – 183*Cr + 34678 B*256 = 256*Y + 454*Cb - 58065 我们可以进一步把前面的3个等式拆分为以下3步运算。 步骤1: R’ = 256*Y + 359*Cr G’ = 256*Y – 88*Cb – 183*Cr B’ = 256*Y + 454*Cb 步骤2: R’’ = R’ – 45941 G’’ = G’ + 34678 B’’ = B’ - 58065 步骤3: R = R’’>>8 G = G’’>>8 B = B’’>>8 对于步骤1的公式,Y、Cr、Cb本身是8bit的无符号数,而与他们相乘法的参数最大值为454,并且“有加有减”,因此我们可以取这些参数为10bit的有符号数,即最高位bit9为符号位,可表示的数值范围是-512到511。那么,步骤1运算等式可以表示如下: R’*256 = 18’d256*Y + 18’d359*Cr G’*256 = 18’d256*Y + 18'h3ffa8*Cb + 18'h3ff49*Cr B’*256 = 18’d256*Y + 18’d454*Cb 基于这3个公式,我们再去看3个乘累加器的输入,就可以很好的理解了,其代码如下。 //R muxadd2 muxadd_instr ( .aclr0 ( !rst_n ), .clock0 ( clk ), .dataa_0 ( {1'd0,dy} ), .dataa_1 ( {1'd0,dcr} ), .datab_0 ( 18'd256 ), .datab_1 ( 18'd359 ), .result ( dtr ) ); //G muxadd muxadd_instg ( .aclr0 ( !rst_n ), .clock0 ( clk ), .dataa_0 ( {1'd0,dy} ), .dataa_1 ( {1'd0,dcb} ), .dataa_2 ( {1'd0,dcr} ), .datab_0 ( 18'd256 ), .datab_1 ( 18'h3ffa8 ), .datab_2 ( 18'h3ff49 ), .result ( dtg ) ); //B muxadd2 muxadd_instb ( .aclr0 ( !rst_n ), .clock0 ( clk ), .dataa_0 ( {1'd0,dy} ), .dataa_1 ( {1'd0,dcb} ), .datab_0 ( 18'd256 ), .datab_1 ( 18'd454 ), .result ( dtb ) ); ● dataa_0、dataa_1、dataa_2都是9bit的有符号数,datab_0、datab_1、datab_2都是18bit的有符号数。 ● Dy、dcb、dcr对应公式中的Y、Cb、Cr,由于他们都是8bit的无符号数,因此给他们补1bit的1'b0。 ● dataa_0和datab_0是一组乘法输入;dataa_1和datab_1是一组乘法输入;dataa_2和datab_2是一组乘法输入。 对于步骤2的公式,由于步骤1得到的结果都是18bit的有符号数,并且步骤2中需要运算的加减参数也都是18bit有符号数计数范围之内,因此我们也去18bit有符号数。那么步骤2的公式可以表示为: R’’ = R’ + 18'h34c8b G’’ = G’ + 18’d34678 B’’ = B’ + 18'h31d2e 那么,我们第2步运算的逻辑代码如下。 //------------------------------------------------------ reg[17:0] dor,dog,dob; //第二步中间处理数据 always @(posedge clk or negedge rst_n) if(!rst_n) begin dor <= 18'd0; dog<= 18'd0; dob<= 18'd0; end else begin dor <= (dtr+18'h34c8b);//r dog <= (dtg+18'd34678);//g dob <= (dtb+18'h31d2e);//b end 对于第3步公式,虽然只是简单的移位,但是我们同时也需要对计算结果是否有溢出做判断并处理。产生溢出基本上是由于我们的运算精度或者说运算过程中的“四舍五入”导致。如何判断溢出?也很简单,我们可以预期的是R、G、B运算的结果一定都是8bit的无符号数,由于一开始运算我们就放大了256倍,即右移了8bit,因此R’’、G’’、B’’有效的数据应该是16bit。而我们用18bit去表示,最高位bit17为1的必然是负数,直接取小值“0”即可;接着,判断次高位bit16为1的必然是超出R、G、B的最大值255的数据,那么我们直接去“255”即可。根据以上的溢出判断和处理方法,我们的逻辑代码如下。 //------------------------------------------------------ reg[7:0] der,deg,deb; //第三步中间处理数据 always @(posedge clk or negedge rst_n) if(!rst_n) begin der <= 8'd0; deg <= 8'd0; deb <= 8'd0; end else begin /*r*/ if(dor[17]) der <= 8'd0; else if(dor[16]) der <=8'd255; else der <= dor[15:8]; /*g*/ if(dog[17]) deg <= 8'd0; else if(dog[16]) deg <=8'd255; else deg <= dog[15:8]; /*b*/ if(dob[17]) deb <= 8'd0; else if(dob[16]) deb <=8'd255; else deb <= dob[15:8]; end 最终我们得到了RGB888的数据(即R、G、B均为8bit数据表示),但是实际液晶屏接口是RGB565,所以我们只能忍痛割爱去除RGB的低位,如下代码取RGB565输出。 assign drgb = {der[7:3],deg[7:2],deb[7:3]}; //RGB色彩输出 我们再来看与输出数据同步的有效标志信号drgb_rdy是如何产生的。如图12.42所示,这是整个drgb_rdy以及内部控制部分时序的详解波形示意图。 图12.42 色彩转换处理后的数据输出波形图 结合代码,我们依次来看看这部分逻辑功能。我们用一个2bit计数器acnt,依次将存放在avdout总线上的YCrCb数据分别提取到3个独立的寄存器dy、dcb、dcr上。这样我们每4个时钟周期就有2个时钟周期,dy、dcb、dcr上的数据分别对应两个像素点的所有数据量了。 //------------------------------------------------------ //输入的AV采样YCbCr数据格式为: //Cb0--Y0--Cr0--Y1--Cb1--Y2--Cr1--Y3…… reg[7:0] dy,dcb,dcr; //待转换的YCbCr数据 reg[1:0] acnt; //译码计数器,0-3 always @(posedge clk or negedge rst_n) if(!rst_n) begin dy <= 8'd0; dcb <= 8'd0; dcr <= 8'd0; acnt <= 2'd0; end else if(avout_rdy) begin //输入给译码逻辑的YCbCr数据 acnt <= acnt+1'b1; case(acnt) 2'd0: dcb <= avdout; 2'd1: dy <= avdout; 2'd2: dcr <= avdout; 2'd3: dy <= avdout; default: ; endcase end else begin dy <= 8'd0; dcb <= 8'd0; dcr <= 8'd0; acnt <= 2'd0; end 所有的时钟周期,转换运算的3个步骤都是进行,只是每4个时钟周期只有2个时钟周期运算的结果是符合我们需要的。而这两个时钟周期对应着计数器rcnt的低两位rcnt[1:0]取值为2'b00和2'b11。此外,我们的数据从输入到输出经过了3个步骤,势必有延时。这个延时,总延时合计4个时钟周期,分别为第1步乘累加运算花费2个时钟周期、第2步加法运算花费1个时钟周期、第3步移位和溢出判断处理花费1个时钟周期。因此,结合前面给出的波形示意,我们增加了(rcnt > 12'd6)来作为drgb_rdy输出的一个条件。drgb_rdy产生的逻辑代码如下。 //------------------------------------------------------ reg drgb_rdyr; //转换后的RGB有效数据指示信号,高电平有效 reg[10:0] rcnt; //输出控制计数器 always @(posedge clk or negedge rst_n) if(!rst_n) rcnt <= 11'd0; else if(avout_rdy) rcnt <=rcnt+1'b1; else begin if((rcnt > 11'd1276)&& (rcnt < 11'd1286)) rcnt <= rcnt+1'b1; else rcnt <= 11'd0; end always @(posedge clk or negedge rst_n) if(!rst_n) drgb_rdyr <= 1'b0; else if(((rcnt[1:0] == 2'b00) ||(rcnt[1:0] == 2'b11)) && (rcnt > 12'd6)) drgb_rdyr <= 1'b1;//转换后的RGB有效数据指示信号 else drgb_rdyr <= 1'b0; assign drgb_rdy = drgb_rdyr;
|