HLS工具
以个人的理解,xilinx将HLS(高层次综合)定位于更方便的将复杂算法转化为硬件语言,通过添加某些配置条件HLS工具可以把可并行化的C/C++的代码转化为vhdl或verilog,相比于纯人工使用vhdl实现图像算法,该工具综合出的代码的硬件资源占用可能较多,但并没有相差太大(见论文:基于HLS的 SURF特征提取硬件加速单元设计与实现),而纯人工用硬件描述语言实现一个复杂的图像处理算法要求十分深厚的FPGA功底,下面简单总结下好早之前做的一个在zybo开发板上的HLS图像处理通路。
主要从三个工程分析
1.sobel边缘检测
头文件部分涉及到‘hls_video.h’:主要就做这么几件事情,定义头文件名,导入需要的库文件,定义参数,重定义数据结构,声明函数
#ifndef _TOP_H_ #define _TOP_H_ #include"hls_video.h" //这里调用可以综合的视频库 // maximum image size #define MAX_WIDTH 1920 #define MAX_HEIGHT 1080 // I/O Image Settings #define INPUT_IMAGE "test_1080p.jpg" #define OUTPUT_IMAGE "result_1080p.jpg" #define OUTPUT_IMAGE_GOLDEN "result_1080p_golden.jpg" // typedef video library core structures typedef hls::stream24,1,1,1> > AXI_STREAM_IN; typedef hls::stream 24,1,1,1> > AXI_STREAM_OUT; typedef hls::Mat RGB_IMAGE; // top level function for HW synthesis void hls_sobel(AXI_STREAM_IN& src_axi, AXI_STREAM_OUT& dst_axi, int rows, int cols); #endif
主要函数的具体定义:
#include "top.h" void hls_sobel(AXI_STREAM_IN& input, AXI_STREAM_OUT& output, int rows, int cols) { #pragma HLS RESOURCE variable=input core=AXI4Stream metadata="-bus_bundle INPUT_STREAM" #pragma HLS RESOURCE variable=output core=AXI4Stream metadata="-bus_bundle OUTPUT_STREAM" #pragma HLS INTERFACE ap_none port=cols #pragma HLS INTERFACE ap_none port=rows //AP_CONTROL_BUS_AXI(CONTROL_BUS); //set_directive_interface -mode ap_ctrl_none hls_sobel #pragma HLS interface ap_ctrl_none port=return RGB_IMAGE img_0(rows, cols);//输入图像的存储空间 RGB_IMAGE img_1(rows, cols);//输出图像内存大小的指定 #pragma HLS DATAFLOW // must use data flow to stream the data hls::AXIvideo2Mat(input, img_0); //read video stream by frames hls::Sobel<1,0,3>(img_0, img_1);//use Hls Sobel //根据模板,<1,0,3>中前两个部分规定了x方向的检测还是y方向的检测,<1,0>表示的是y方向的检测,size=3代表sobel算子的维数规定。 //hls::Erode<>();//use Hls Sobel hls::Mat2AXIvideo(img_1, output); //write the frames to video stream }
除了优化部分以外,主要做的就是定义输入输出图像的内存空间,然后将输入的数据格式转换为mat格式,用于实现ip1image的处理,然后就是直接调用库里的sobel函数,最后图像转化为video-stream输出即可。
需要注意优化部分:视频输入输出接口优化为stream格式,将控制接口分配到AXI4 Lite接口,指定“rows”可通过AXI4-Lite接口进行访问并且声明在函数执行过程中 “rows”不会改变,其实这几句优化语句中最关键的一条指令为启用数据流优化:#pragma HLS dataflow,它使得任务之间以流水线的方式执行,还有就是#pragma HLS interface ap_ctrl_none port=return。
测试函数:
#include "top.h" #include "opencv/cv.h" #include "opencv/cxcore.h" #include "opencv/highgui.h" #include "hls_opencv.h" int main (int argc, char** argv) //argc 是 argument count的缩写,表示传入main函数的参数个数; //argv 是 argument vector的缩写,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所以确切的说需要我们输入的main函数的参数个数应该是argc-1个; { IplImage* src = cvLoadImage(INPUT_IMAGE);//为输入图像定义好存储空间 IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, src->nChannels);//为输出图像定义好存储空间 AXI_STREAM_IN src_axi;//定义输入输出AXI数据流 AXI_STREAM_OUT dst_axi;// IplImage2AXIvideo(src, src_axi); //将图像转为视频流结构 hls_sobel(src_axi, dst_axi, src->height, src->width); AXIvideo2IplImage(dst_axi, dst); cvSaveImage(OUTPUT_IMAGE, dst); cvReleaseImage(&src); cvReleaseImage(&dst); }//不加符号
注意头文件导入时路径的问题,思路是定义好图像空间后定义视频图像的变量,然后将输入的图像转换为视频流结构,用自己写的函数处理后,观察处理后的结果即可
最后生成IP核的摸样
2.skin-detect部分
头文件
#ifndef _TOP_H_ #define _TOP_H_ #include "hls_video.h" // maximum image size #define MAX_WIDTH 1920 #define MAX_HEIGHT 1080 typedef unsigned char uchar; // I/O Image Settings #define INPUT_IMAGE "test.jpg" #define OUTPUT_IMAGE "result_1080p.bmp" #define OUTPUT_IMAGE_GOLDEN "test_img1.jpg" // typedef video library core structures typedef hls::stream24,1,1,1> > AXI_STREAM; typedef hls::Mat RGB_IMAGE; typedef hls::Scalar<3, unsigned char> RGB_PIXEL; //函数声明,,,用到了命名空间,这就是一个虚拟文件夹的意思 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 ImgProcess_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
涉及到的mat以及scalar格式的数据结构可参考:
主要函数:
#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++) { //#pragma HLS loop_flatten off LOOp_COLS:for(int col = 0; col < cols; col++) { #pragma HLS pipeline II=1 off //--------------------------优化指令1 //变量定义 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; //将肤色区域的值大小置为255,即白色 uchar temp0= (skin_region == 1)? (uchar)120: B; uchar temp1= (skin_region == 1)? (uchar)120: G; uchar temp2= (skin_region == 1)? (uchar)120: R; dst_data.val[0] = temp0; dst_data.val[1] = temp1; dst_data.val[2] = temp2; //复制处理完成后输出图像 dst << dst_data; } } } //该函数的作用主要是把上面的skin-detect函数的输入输出端口做一个封装,以便处理视频输入信号 void ImgProcess_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) //定义该函数的功能用于将上面的肤色检测用AXI_STREAM结构封装端口 { ///* #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 core=AXI_SLAVE variable=rows metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=cols metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=y_lower metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=y_upper metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=cb_lower metadata="-bus_bundle CONTROL_BUS" #pragma HLS RESOURCE core=AXI_SLAVE variable=cb_upper 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);//将video图像数据结构转换为mat型(RGB图像)数据,以便于输入到下面的检测函数中。 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);//将mat型(RGB图像)数据转换为video图像数据结构输出 }
我们对视频接口的约束如下:将src和dst指定为为以 “INPUT_STREAM” 命名的AXI4 Stream,将控制接口分配到AXI4 Lite接口,指定“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),这三个函数之间为流水线方式。
测试文件:思路也是导入图像文件,然后创建好保存结果的空间,然后定义视频流变量,将图像输入转换为axi-stream格式,然后用自己编写的函数处理后,输出转换为IplImage格式后,输出图像观察即可。
#include "top.h" #include "hls_opencv.h" #include "iostream" #includeusing namespace std; using namespace cv; int main (int argc, char** argv) { //IplImage* src = cvLoadImage(INPUT_IMAGE); IplImage* src = cvLoadImage("test.jpg"); //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); ImgProcess_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; }
下一篇介绍:
3.FFT的HLS实现
3.HOG实现部分https://www.cnblogs.com/zhazhiqiang/p/3595266.html