一、肤色检测的原理
利用颜色信息分割的理论依据:肤色因为人种的不同有不同的差异,呈现出不同的颜色,但是排除亮度和视觉环境对肤色的影响之后,皮肤的色调基本一致。
肤色识别中常用的颜色空间为YCbCr颜色空间,其中Y代表亮度,Cb代表蓝色分量,Cr代表红色分量,Cb和Cr两者的结合称之为色彩分量。YCbCr颜色空间具有将色度和亮度分离的特点。YCbCr颜色空间中,肤色的聚类性比较好,而且是两维独立分布,能够很好的限制肤色的分布区域。RGB颜色空间和YCbCr颜色空间相对比,当光强发生改变时,RGB颜色空间中(R,G,B)会同时发生改变,而YCbCr颜色空间受光强相对独立,彩色分量受光强影响不大,YCbCr颜色空间更为适合用于肤色识别。RGB转在YCbCr的公式:
Y = 0.257*R+0.564*G+0.098*B+16
Cb= -0.148*R-0.291*G+0.439*B+128
Cr = 0.439*R-0.368*G-0.071*B+128
对肤色进行判断的条件使用如下条件:
Cb > 77 && Cb < 127
Cr > 133 && Cr < 173
二、代码实现
新建工程,在source下添加下面代码文件:
文件1:top.cpp
#include "top.h"
#include <string.h>
void hls::hls_skin_dection(RGB_IMAGE& src,RGB_IMAGE& dst,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper)
{
LOOp_ROWS:for(int row=0;row<rows;row++)
{
LOOp_COLS:for(int col = 0; col < cols; col++)
{
//变量定义
RGB_PIXEL src_data;
RGB_PIXEL pix;
RGB_PIXEL dst_data;
bool skin_region;
if(row<rows&&col<cols){
src>>src_data;
}
//获取rgb通道数据
uchar B=src_data.val[0];
uchar G=src_data.val[1];
uchar R=src_data.val[2];
//RGB-->YCbCr颜色空间转换
uchar y=(76*R+150*G+29*B)>>8;
uchar cb=((128*B-43*R-85*G)>>8)+128;
uchar cr=((128*R-107*G-21*B)>>8)+128;
//肤色区域判断
if(y>y_lower && y<y_upper && cb>cb_lower && cb<cb_upper && cr>cr_lower && cr<cr_upper)
skin_region=1;
else
skin_region=0;
uchar temp0=(skin_region==1)?(uchar)255:B;
uchar temp1=(skin_region==1)?(uchar)255:R;
uchar temp2=(skin_region==1)?(uchar)255:R;
dst_data.val[0]=temp0;
dst_data.val[1]=temp1;
dst_data.val[2]=temp2;
dst<<dst_data;
}
}
}
void ImgeProcess_Top(AXI_STREAM& input,AXI_STREAM& output,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper)
{
RGB_IMAGE img_0(rows,cols);
RGB_IMAGE img_1(rows,cols);
#pragma HLS DATAflow
hls::AXIvideo2Mat(input,img_0);
hls::hls_skin_dection(img_0, img_1, rows, cols, y_lower, y_upper, cb_lower, cb_upper, cr_lower, cr_upper);
hls::Mat2AXIvideo(img_1, output);
}
文件2:top.h
#ifndef _TOP_H_
#define _TOP_H_
#include "hls_video.h"
#define MAX_WIDTH 1920
#define MAX_HEIGHT 1082
typedef unsigned char uchar;
// i/o image setings
#define INPUT_IMAGE "test_1080p.bmp"
#define OUTPUT_IMAGE "result_1080p.bmp"
#define OUTPUT_IMAGE_GOLDEN "result_1080p_golden.bmp"
//typedef video library core structures
typedef hls::stream<ap_axiu<24,1,1,1> > AXI_STREAM;
typedef hls::Mat<MAX_HEIGHT,MAX_WIDTH,HLS_8UC3> RGB_IMAGE;
typedef hls::Scalar<3,unsigned char> RGB_PIXEL;
//hls命名空间。函数声明,用到了命名空间,这就是一个虚拟文件夹的意思
namespace hls
{
void hls_skin_dection(RGB_IMAGE& src,RGB_IMAGE& dst,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper);
}
void ImgeProcess_Top(AXI_STREAM& input,AXI_STREAM& output,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper);
#endif
有以上两个文件已经可以实现代码综合了。综合报告:
接下来是仿真,需要编写仿真文件在Test Bench中添加测试文件,tb.cpp:
#include "top.h"
#include "hls_opencv.h"
#include "iostream"
#include <time.h>
using namespace std;
using namespace cv;
int main(int argc,char** argv)
{
//IplImage* src =cvLoadImage(INPUT_IMAGE);
IplImage* src = cvLoadImage("test_img1.jpg");
IplImage* dst=cvCreateImage(cvGetSize(src),src->depth,src->nChannels);
AXI_STREAM src_axi,dst_axi;
IplImage2AXIvideo(src,src_axi);
ImgeProcess_Top(src_axi,dst_axi,src->height,src->width,0,255,75,125,131,185);
AXIvideo2IplImage(dst_axi,dst);
cvShowImage("src",src);
cvShowImage("dst_hls",dst);
waitKey(0);//参数<=0时等待按键事件发生,按下键的话返回按键的值, 否则返回-1;
return 0;
}
另外,在Test Bench中添加test_img1.jpg测试图片,点击仿真按钮就可以得到结果:
左边是原图,右边是结果。
三、代码优化
官网手册为:how_to_accelerate_opencv_applicationns_using_vivado_hls.pdf
这里面详细描述了hLS代码优化。
视频接口的约束如下:将src和dst指定为“INPUT_STREAM"命名的AXI4 Stream,将控制口分配到AXI4接口,指定”rows"可通过AXI4-Lite接口进行访问并且声明在函数执行过程中“rows”不会改变,其实这几优化语句中最关键的一条指令为启用数据流优化:#pragma HLS dataflow,它使得任务之间以流水线的方式执行。即
hls:AXIvideo2Mat(src,img_0);
hls::skin_detect(img_0,img_1,rows,cols,cb_lower,cb_upper,cr_lower,cr_upper);
hls::Mat2AXIvideo(img_1,dst);
这三个函数之间为流线方式。
将top.cpp文件添加约束语句:
#include "top.h"
#include <string.h>
void hls::hls_skin_dection(RGB_IMAGE& src,RGB_IMAGE& dst,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper)
{
LOOp_ROWS:for(int row=0;row<rows;row++)
{
LOOp_COLS:for(int col = 0; col < cols; col++)
{
#pragma HLS PIPELINE II=1 off
//变量定义
RGB_PIXEL src_data;
RGB_PIXEL pix;
RGB_PIXEL dst_data;
bool skin_region;
if(row<rows&&col<cols){
src>>src_data;
}
//获取rgb通道数据
uchar B=src_data.val[0];
uchar G=src_data.val[1];
uchar R=src_data.val[2];
//RGB-->YCbCr颜色空间转换
uchar y=(76*R+150*G+29*B)>>8;
uchar cb=((128*B-43*R-85*G)>>8)+128;
uchar cr=((128*R-107*G-21*B)>>8)+128;
//肤色区域判断
if(y>y_lower && y<y_upper && cb>cb_lower && cb<cb_upper && cr>cr_lower && cr<cr_upper)
skin_region=1;
else
skin_region=0;
uchar temp0=(skin_region==1)?(uchar)255:B;
uchar temp1=(skin_region==1)?(uchar)255:R;
uchar temp2=(skin_region==1)?(uchar)255:R;
dst_data.val[0]=temp0;
dst_data.val[1]=temp1;
dst_data.val[2]=temp2;
//复制处理完成后输出图像
dst<<dst_data;
}
}
}
void ImgeProcess_Top(AXI_STREAM& input,AXI_STREAM& output,int rows,int cols,
int y_lower,int y_upper,int cb_lower,int cb_upper,int cr_lower,int cr_upper)
{
#pragma HLS RESOURCE variable=input core=AXIS metadata="-bus_bundle INPUT_STREAM"
#pragma HLS RESOURCE variable=output core=AXIS metadata="-bus_bundle OUTPUT_STREAM"
#pragma HLS RESOURCE variable=rows core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE variable=cols core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE variable=y_lower core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE variable=y_upper core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE variable=cb_lower core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE variable=cb_upper core=AXI_SLAVE metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE core=AXI_SLAVE variable=cr_lower metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE core=AXI_SLAVE variable=cr_upper metadata="-bus_bundle CONTROL_BUS"
#pragma HLS RESOURCE core=AXI_SLAVE variable=return metadata="-bus_bundle CONTROL_BUS"
#pragma HLS INTERFACE ap_stable port=rows
#pragma HLS INTERFACE ap_stable port=cols
#pragma HLS INTERFACE ap_stable port=y_lower
#pragma HLS INTERFACE ap_stable port=y_upper
#pragma HLS INTERFACE ap_stable port=cb_lower
#pragma HLS INTERFACE ap_stable port=cb_upper
#pragma HLS INTERFACE ap_stable port=cr_lower
#pragma HLS INTERFACE ap_stable port=cr_upper
RGB_IMAGE img_0(rows,cols);
RGB_IMAGE img_1(rows,cols);
#pragma HLS DATAflow
hls::AXIvideo2Mat(input,img_0);
hls::hls_skin_dection(img_0, img_1, rows, cols, y_lower, y_upper, cb_lower, cb_upper, cr_lower, cr_upper);
hls::Mat2AXIvideo(img_1, output);
}
优化之后的综合报告:
对比两次的综合报告发现优化后的代码使用的dsp还是6个,但是触发器由原来的987变为518,查找表由原来的1454变为1193。
此时需要消耗六个周期(标号0到5)才处理完毕。
优化后得到的结果:
查看代码我们可以发现我们使用了 for 循环(LOOP),下面对循环 LOOP 控制的优化指令进行说明:
Unroll:展开循环,用于创建多个独立的操作,而不是对单个操作进行整合。
loop_Merge:合并连续的循环,降低总延迟,提高共享和优化。
loop_Flatten:允许将带有改善延迟和逻辑优化的嵌套循环,整理成一个单个的循环。
Dataflow:允许顺序循环,并发的操作。
Pipeline:通过执行并发的操作,提高吞吐量。
--晓凡 2022年9月4日于南宁书