肤色识别算法的Verilog代码基于FPGA 的彩色图片转灰度实现的。本文参考硅农图像处理教学文章。一个良心公众号,有很多开源技术含量高的东西,有需要的可以找他们获取。
将彩色图像转化为灰度的方法有两种,一个是令RGB三个分量的数值相等,输出后便可以得到灰度图像,另一种是转化为YCbCr格式,将Y分量提取出来,YCbCr格式中的Y分量表示的是图像的亮度和浓度所以只输出Y分量,得到的图像就是灰度图像了。我在这里选择第二种方法实现。YCBCr是通过有序的三元组来表示的,三元由Y( Luminance )、Cb(Chrominance-Blue)和Cr (Chrominance-Red)组成,其中Y表示颜色的明亮度和浓度,而Cb和Cr则分别表示颜色的蓝色浓度偏移量和红色浓度偏移量。人的肉眼对由 YCbCr色彩空间编码的视频中的Y分量更敏感,而Cb和Cr的微小变化不会引起视觉上的不同,根据该原理,通过对Cb和Cr进行子采样来减小图像的数据量,使得图像对存储需求和传输带宽的要求大大降低,从而达到在完成图像压缩的同时也保证了视觉上几乎没有损失的效果,进而使得图像的传输速度
更快,存储更加方便。我们要的到灰度图像,首先要将采集到的彩色图像转化为YCbCr。
我通过串口发送的彩色图片数据是 RGB332 8bit,根据官方给出的转化公式是RGB888->YCbCr,所以我首先要将8bit RGB332转化为24bit RGB888。转化如下,这里用到了循环补偿的概念。
循环补偿的概念
8bit RGB332 -> 24bit RGB888的转换
8bit RGB332{ R2 R1 RO} { G2 G1 G0} {B1 B0}
24bit RGB888{ R2 R1 R0 0000o} G2G1 G0 o00 o 0} {B1 B0 0oooo 0}
24bit RGB888 {R2R1RO R2 R1 R0 0 0} G2 G1 G0 G2 G1 G0 0 0 H B1B0 B1 B0 0 00 o}
24bit RGB888 { R2R1 R0 R2 R1RO R2R1}{G2 G1 G0 G2 G1 GO G2 G1]} { B1B0B1B0B1B0 0 0}
24bit RGB888{R2R1RO R2R1 RO R2R1 ][ G2 G1 G0 G2 G1 G0 G2 G1 ]}[B1BOB1B0 B1B0B1B0}
从如上转化可以看出,B分量进行了四轮补偿。进行这样的补偿,在做色彩格式转化的时候,能够明显的改善色彩效果,减少精度上的损失。代码实现部分如下。
assign cmos_R0={ cmos_R, cmos_R, cmos_R[2:1]};
assign cmos_G0={ cmos_G, cmos_G,cmos_G[2:1]};
assign cmos_B0={ cmos_B, cmos_B, cmos_B, cmos_B}
下面是官方给的RGB888 to YCbCr 的算法公式,我们可以直接把算法移植到FPGA 上,但是我们都知FPUA 元公L1就不了FPDGA擅长的乘法运算和加端先都扩大256倍,然后再右移8位,这样就得到了FPGA 擅长的乘法运算和加法运算了。
这个计算式子看起来是十分简单的,但是要是直接用Verilog直接写出来,那么只能说,这个人的代码写的一塌糊涂,所以这里就引出FPGA 中流水线的设计思想。
在这里我们选择加3级流水线,就第一个Y分量而言,先计算括号中得乘法运算,消耗一个时钟,然后将括号中的数据求和,消耗一个时钟,这里为了计算方便,将128也扩大256倍,放到括号中,最终结果除以256就行了也就是右移8位,在FPGA中我们只需要舍弃低8位取高8位就行。
将RGB565—>YCbCr成功后,提取出Y的值输出,就可以得到灰度色彩的图像了。
将采集到的RGB565的像素数据,输入到算法处理模块进行操作,由RGB565——>YCbCr——Gray官方给出的公式来算,先将RGB565拆分开RGB三个分量,使用如上公式计算的到Y Cb Cr是三个分量。
对于肤色检测其实也是基于这个基础上,首先利用如上图公式将RGB转化为YCbCr,然后通过对Cb和Cr分量设置阈值,我这边设置的是当Cb和Cr分量在这个阈值之间时,输出为全1,即白色,其他情况输出为全0,即为黑色,我使用前面的200x200的图片做实验,效果不怎么好﹐最后借用业界前辈CrazyBingo大神的摄像头驱动,试了一下这个肤色识别算法,最后得到的效果还是可以的。这个用YCbCr阈值法实现肤色识别的方法,是不很精确,后面我会尝试用另一种识别方法来试着实现。
肤色识别YCbCr阈值
77< Cb < 127
133 < Cr < 173
`timescale 1ns/1ps
module RGB565_YCbCr_gray(
input clk,//COMS pixel clk 24Mhz
input rst_n,
//others
input [4:0] cmos_R,
input [5:0] cmos_G,
input [4:0] cmos_B,
input per_frame_clken,
input per_frame_vsync,
input per_frame_href,
output [7:0] img_Y,
output [7:0] img_Cb,
output [7:0] img_Cr,
output post_frame_clken,
output post_frame_vsync,
output post_frame_href
);
//--------------------------------------------
//RGB565 to RGB 888 高位补低位
wire [7:0] cmos_R0;
wire [7:0] cmos_G0;
wire [7:0] cmos_B0;
assign cmos_R0 = {cmos_R, cmos_R[4:2]};
assign cmos_G0 = {cmos_G, cmos_G[5:4]};
assign cmos_B0 = {cmos_B, cmos_B[4:2]};
//--------------------------------------------
/*//Refer to page 5
Y = (77 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 85 *G + 128*B)>>8 + 128
Cr = (128*R - 107*G - 21 *B)>>8 + 128
--->
Y = (77 *R + 150*G + 29 *B)>>8
Cb = (-43*R - 85 *G + 128*B + 32768)>>8
Cr = (128*R - 107*G - 21 *B + 32768)>>8*/
//--------------------------------------------
//RGB888 to YCrCb
//step1 conmuse 1clk
reg [15:0] cmos_R1, cmos_R2, cmos_R3;
reg [15:0] cmos_G1, cmos_G2, cmos_G3;
reg [15:0] cmos_B1, cmos_B2, cmos_B3;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
cmos_R1 <= 16'd0;
cmos_G1 <= 16'd0;
cmos_B1 <= 16'd0;
cmos_R2 <= 16'd0;
cmos_G2 <= 16'd0;
cmos_B2 <= 16'd0;
cmos_R3 <= 16'd0;
cmos_G3 <= 16'd0;
cmos_B3 <= 16'd0;
end
else begin
cmos_R1 <= cmos_R0 * 8'd77;
cmos_G1 <= cmos_G0 * 8'd150;
cmos_B1 <= cmos_B0 * 8'd29;
cmos_R2 <= cmos_R0 * 8'd43;
cmos_G2 <= cmos_G0 * 8'd85;
cmos_B2 <= cmos_B0 * 8'd128;
cmos_R3 <= cmos_R0 * 8'd128;
cmos_G3 <= cmos_G0 * 8'd107;
cmos_B3 <= cmos_B0 * 8'd21;
end
end
//-----------------------------------------------
//step2 consume 1clk
reg [15:0] img_Y0;
reg [15:0] img_Cb0;
reg [15:0] img_Cr0;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
img_Y0 <= 16'd0;
img_Cb0 <= 16'd0;
img_Cr0 <= 16'd0;
end
else begin
img_Y0 <= cmos_R1 + cmos_G1 + cmos_B1;
img_Cb0 <= cmos_B2 - cmos_R2 - cmos_G2 + 16'd32768;
img_Cr0 <= cmos_R3 - cmos_G3 - cmos_B3 + 16'd32768;
end
end
//-------------------------------------------
//step3 conmuse 1clk
reg [7:0] img_Y1;
reg [7:0] img_Cb1;
reg [7:0] img_Cr1;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
img_Y1 <= 8'd0;
img_Cb1 <= 8'd0;
img_Cr1 <= 8'd0;
end
else begin
img_Y1 <= img_Y0 [15:8];
img_Cb1 <= img_Cb0 [15:8];
img_Cr1 <= img_Cr0 [15:8];
end
end
//------------------------------------------
//step4 consume 1clk
reg [7:0] gray_data_r;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
gray_data_r <= 'b0;
else if(img_Cb1 > 77 && img_Cb1 < 127 && img_Cr1 > 133 && img_Cr1 < 173)
gray_data_r <= 8'hff;
else
gray_data_r <= 'b0;
end
//---------------------------------------------
//pre_frame_clken, pre_frame_href, pre_frame_vsync,lag 3clk
reg [3:0] per_frame_clken_r;
reg [3:0] per_frame_href_r;
reg [3:0] per_frame_vsync_r;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)begin
per_frame_clken_r <= 4'b0;
per_frame_href_r <= 4'b0;
per_frame_vsync_r <= 4'b0;
end
else begin
per_frame_clken_r <= {per_frame_clken_r [2:0], per_frame_clken};
per_frame_href_r <= {per_frame_href_r [2:0],per_frame_href};
per_frame_vsync_r <= {per_frame_vsync_r [2:0],per_frame_vsync};
end
end
assign post_frame_clken = per_frame_clken_r [3];
assign post_frame_href = per_frame_href_r [3];
assign post_frame_vsync = per_frame_vsync_r [3];
assign img_Y = post_frame_href? gray_data_r: 1'b0;
assign img_Cb = post_frame_href? img_Cb1: 1'b0;
assign img_Cr = post_frame_href? img_Cr1: 1'b0;
endmodule