HLS #3 Sobel算例实例应用

对了,我最近开通了微信公众号,计划是两边会同步更新,并逐步的会将博客上的文章同步至公众号中。
感兴趣的朋友可以扫描下方的二维码或者搜索“里先森sements”来关注,欢迎来玩~!


本篇文章介绍了如何使用HLS制作一个接受24bit RGB图像数据的AXIStream输入流,进行RGB转Gray后,进行Sobel运算,并还原为24bitRGBAXIStream数据流进行输出。为了节约占用资源,这里后端使用了一个VideoScaler进行图像放大,以便HDMI进行输出。

目录

0 - 准备工作

1 - HLS工程

1.1 - 设计思路

1.2 - 函数添加

2 - VIVADO工程

3 - SDK程序

4 - 测试


0 - 准备工作

1 - HLS工程

1.1 - 设计思路

我们期望让ZYNQ从SD卡读取一个BMP图片,将其转换为RGB数组存储到DDR上,通过VDMA传输到我们利用HLS生成的LeeVideoOperation IP核中进行处理,最后输出到HDMI进行显示。

这里有几个问题,第一,读取的BMP图片为RGB格式,传输占用24bit通道,而受限于内部资源以及HLS编译堆栈限制,我们这里在HLS里面能够处理的为8bit 640*480的灰度图像。同时,为了送到HDMI进行显示,我们至少需要720P即1280*720像素的24bit图像。因此这里我们既需要进行RGB转gray完成24bit到8bit供sobel计算,又需要8bit灰度转24bit 假RGB图像给HDMI进行显示。

为此,我们的IP内部流程图应当如下所示:

1.2 - 函数添加

这里不介绍如何新建一个HLS工程,参考前文即可。

注意:在使用xfopencv时,避免包含 hls_video 头文件,否则会提示ap_axiu类型重复定义

top函数文件

#include "accelmain.h"
//#include "hls_video.h"			//包含此头文件会导致提示 ap_axiu 重复定义

/*
 * 	读入BGR格式的 ap_axiu<24,1,1,1> axi数据流,
 * 	取平均值方式输出灰度ap_axiu<8,1,1,1>数据流
 */
void CvtStreamBGR2Gray(hls::stream< ap_axiu<24,1,1,1>>& srcStream,hls::stream< ap_axiu<8,1,1,1>>& dstStream,int inputWidth,int inputHeight)
{
	ap_axiu<24,1,1,1> _axiIn;
	ap_axiu<8,1,1,1> _axiOut;

	loop_trans:
	for(int i = 0 ; i < inputWidth * inputHeight ; i++)
	{
		_axiIn = srcStream.read();

		//Read the pix data
		int _pixData , _pixB , _pixG , _pixR;
		_pixData = _axiIn.data.to_uint();
		_pixB = (unsigned char)(_pixData & 0xFF)
		,_pixG = (unsigned char)((_pixData >> 8) & 0xFF)
		,_pixR = (unsigned char)((_pixData >> 16) & 0xFF);

		//gray = (R+G+B)/3
		ap_uint<8> _pixGray;
		_pixGray = (ap_uint<8>)((_pixB + _pixG + _pixR)/3);

		//(ap_uint<8>)(_axiIn.data.to_uint() & 0x0000FF);	//trans to int , mask the G and R color , then send to axiOut
		_axiOut.data = _pixGray;
		_axiOut.keep = _axiIn.keep;
		_axiOut.strb = _axiIn.strb;
		_axiOut.user = _axiIn.user;
		_axiOut.last = _axiIn.last;
		_axiOut.id = _axiIn.id;
		_axiOut.dest = _axiIn.dest;

		dstStream.write(_axiOut);
	}
}

/*
 * 	读入灰度格式的 ap_axiu<8,1,1,1> axi数据流,
 * 	填充方式输出BGRap_axiu<24,1,1,1>数据流
 */
void CvtStreamGray2BGR(hls::stream< ap_axiu<8,1,1,1>>& srcStream,hls::stream< ap_axiu<24,1,1,1>>& dstStream,int inputWidth,int inputHeight)
{
	ap_axiu<24,1,1,1> _axiOut;
	ap_axiu<8,1,1,1> _axiIn;

	loop_trans:
	for(int i = 0 ; i < inputWidth * inputHeight ; i++)
	{
		_axiIn = srcStream.read();

		unsigned char _pixData ;
		_pixData = _axiIn.data.to_char();

		ap_uint<24> _pixBGR;
		_pixBGR = (ap_uint<24>)((_pixData << 16) | (_pixData << 8) |(_pixData));

		_axiOut.data = _pixBGR;
		_axiOut.keep = _axiIn.keep;
		_axiOut.strb = _axiIn.strb;
		_axiOut.user = _axiIn.user;
		_axiOut.last = _axiIn.last;
		_axiOut.id = _axiIn.id;
		_axiOut.dest = _axiIn.dest;

		dstStream.write(_axiOut);
	}
}

void sobel_accel(xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> &_src,xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> &_dstgx,xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> &_dstgy)
{
	xf::Sobel<XF_BORDER_CONSTANT,3,XF_8UC1,XF_8UC1,MAX_HEIGHT, MAX_WIDTH,XF_NPPC1>(_src, _dstgx,_dstgy);
}

void IpSobelAccelTop(hls::stream< ap_axiu<24,1,1,1>>& srcStream,hls::stream< ap_axiu<24,1,1,1>>& dstStream,int inputWidth,int inputHeight,bool xory)
{
	hls::stream< ap_axiu<8,1,1,1>> grayStream("grayStream");
	hls::stream< ap_axiu<8,1,1,1>> sobelStream("sobelStream");
	xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> _matSrc(inputHeight,inputWidth);
	xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> _matSobelx(inputHeight,inputWidth);
	xf::Mat<XF_8UC1, MAX_HEIGHT, MAX_WIDTH, XF_NPPC1> _matSobely(inputHeight,inputWidth);

	CvtStreamBGR2Gray(srcStream,grayStream,inputWidth,inputHeight);
	xf::AXIvideo2xfMat(grayStream,_matSrc);

	sobel_accel(_matSrc, _matSobelx, _matSobely);

	xf::xfMat2AXIvideo(_matSobelx,sobelStream);
	CvtStreamGray2BGR(sobelStream,dstStream,inputWidth,inputHeight);

}

top函数头文件

#include "hls_stream.h"
#include "ap_int.h"
#include "common/xf_common.h"
#include "common/xf_utility.h"
#include "common/xf_infra.h"
#include "imgproc/xf_canny.hpp"
#include "imgproc/xf_sobel.hpp"
#include "imgproc/xf_edge_tracing.hpp"

#define MAX_WIDTH	640
#define MAX_HEIGHT	480

void IpSobelAccelTop(hls::stream< ap_axiu<24,1,1,1>>& srcStream,hls::stream< ap_axiu<24,1,1,1>>& dstStream,int inputWidth,int inputHeight,bool xory);

testbench文件

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "testbench.h"
#include "accelmain.h"

int main(int argc,char** argv)
{
	if(argc != 2)
	{
		fprintf(stderr,"Invalid Number of Arguments!\nUsage:\n");
		fprintf(stderr,"<Executable Name> <input image path> \n");
		return -1;
	}

	/*	read in the color image	*/
	cv::Mat cvmRawImg;
	cvmRawImg = cv::imread(argv[1]);
	cv::imwrite("cvmRawImg.jpg",cvmRawImg);

	/*	Design Under Test	*/
	hls::stream< ap_axiu<24,1,1,1>> srcStream;
	hls::stream< ap_axiu<24,1,1,1>> dstStream;
	cv::Mat cvmIpImg;
	cvmIpImg.create(cvmRawImg.rows,cvmRawImg.cols,CV_8UC3);

	//Get the output of accel function
	cvMat2AXIvideoxf<XF_NPPC1>(cvmRawImg,srcStream);
	IpSobelAccelTop(srcStream,dstStream,cvmRawImg.cols,cvmRawImg.rows,true);
	AXIvideo2cvMatxf<XF_NPPC1>(dstStream,cvmIpImg);

	//write out for check
	cv::imwrite("cvmIpImg.jpg",cvmIpImg);

	return 0;
}

testbench头文件

#ifndef TESTBENCH_H_
#define TESTBENCH_H_

#include "ap_int.h"
#include "hls_stream.h"
#include "opencv/cv.h"
#include "opencv/highgui.h"
#include "opencv2/imgproc/imgproc.hpp"

#include "common/xf_axi.h"
#include "common/xf_sw_utils.h"
#include "common/xf_common.h"
#include "common/xf_utility.h"
#include "common/xf_infra.h"
#include "imgproc/xf_sobel.hpp"

#endif

CFLAG。在使用xfopencv 2019版本时,simulation与synthesis使用统一的CFLAGS,即

-I你代码下xfopencv的Include文件夹路径 -D__SDSVHLS__ --std=c++0x 

里先森按:我这里使用的是VIVADO 2019,故部分参数设置页面可能与之前的版本不太一样,但是不影响参数设置。例如下图中多了一个 CSIMFLAGS,但是中本例中不需要设置

在添加Directive优化指令时,记得将IpSobelAccelTop内部的两个grayStream和sobelStream添加HLS STREAM定义,并FIFO栏填写,不使用默认值,否则会提示

ERROR: [XFORM 203-733] An internal stream xxxx.xxxx.V.user.V' with default size is used in a non-dataflow region, which may result in deadlock. Please consider to resize the stream using the directive 'set_directive_stream' or the 'HLS stream' pragma.

仿真结果

综合结果

2 - VIVADO工程

VTC配置

Clocking Wizard配置

VDMA配置

这里使用了米联客的样例“SD卡读取图片显示”,在其上做改动。VDMA通路上串入了两个IP,先是我们的 LeeVideoOperation ,然后是 VideoScaler 缩放ip,其可以将指定分辨率的输入缩放到指定分辨率。

这里,VDMA从PS的DDR读取的是一幅640*480的图片,24bit 传入 LeeVideoOperation处理,输出24bit AXIS数据流到 VideoScaler,VideoScaler将640*480分辨率缩放到1280*720分辨率,再到AXIS4-Stream to Video Out,再接HDMi输出。

Clocking Wizard 与 VideoTimingControler(VTC)都是按照720P分辨率来进行配置的,VTC里有默认的几种常用分辨率选项可以直接选择,选择完后可以看到实际需要的横向与纵向像素个数,据此再计算Clocking Wizard所需要产生的时钟评频率。这里我们在720P的分辨率下,实际有1650*750个像素,按照60FPS的帧率来计算,就需要1650*750*60 = 74.25MHZ的时钟频率,clk2的时钟频率是clk1的时钟频率x5。

3 - SDK程序

SDK程序比较简单,由两个代码文件组成,但是构建SDK工程时注意勾选添加 Fat 文件系统支持库。

BMP.c文件完成BMP图片的读取并加载为像素写入到指定的内存数组。

#include "bmp.h"
#include "ff.h"


/****************************************************************************
* Function Name  : BMP_ReadHeader
* Description    : 将读取到的数组函数转换位BPM文件信息结构体类型。由于在内存
*                * 上面数组的存储方式与结构体不同,所以要转换,而且SD读取到的
*                * 文件信息是小端模式。高位是低字节,低位是高字节,跟我们常用
*                * 的正好相反所以将数据转换过来。
* Input          : header:要转换的数组
*                * bmp:转换成的结构体
* Output         : None
* Return         : None
****************************************************************************/

void BMP_ReadHeader(uint8_t *header, BMP_HeaderTypeDef *bmp)
{

	bmp->fileHeader.bfType = ((*header) << 8) | (*(header + 1));
	header += 2;
	
	bmp->fileHeader.bfSize = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                         ((*(header + 1)) << 8) | (*header);
	header += 8;

	bmp->fileHeader.bfOffBits = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                            ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.bitSize = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                          ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.biWidth = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                          ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.biHeight = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                           ((*(header + 1)) << 8) | (*header);
	header += 6;

	bmp->infoHeader.biBitCount = ((*(header + 1)) << 8) | (*header);
	                         
	header += 2;

	bmp->infoHeader.biCompression = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                                ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.biSizeImage = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                              ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.biXPelsPerMeter = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                                  ((*(header + 1)) << 8) | (*header);
	header += 4;

	bmp->infoHeader.biYPelsPerMeter = ((*(header + 3)) << 24) | ((*(header + 2)) << 16) |
	                                  ((*(header + 1)) << 8) | (*header);
}


/****************************************************************************
* Function Name  : BMP_Picture
* Description    : 显示BMP格式的图片
* Input          : dir:要显示的图片路径和名字
* Output         : None
* Return         : None
****************************************************************************/


void BMP_Picture(uint8_t *dir , uint8_t  * buf ,uint32_t len)
{
		FRESULT res;
		FIL fsrc;
		UINT  br;
		UINT  a;

		uint8_t buffer[1024];

		BMP_HeaderTypeDef bmpHeader;
		
		/* 打开要读取的文件 */
		res = f_open(&fsrc, (const TCHAR*)dir, FA_READ);

		if(res == FR_OK)   //打开成功
	    {
			/* 读取BMP文件的文件信息 */
	        res = f_read(&fsrc, buffer, sizeof(buffer), &br);

			/* 将数组里面的数据放入到结构数组中,并排序好 */
			BMP_ReadHeader(buffer, &bmpHeader);

			a = bmpHeader.fileHeader.bfOffBits;    //去掉文件信息才开始是像素数据

			res=f_lseek(&fsrc, a);
			if(res)
			{
				return 0;
			}
			res = f_read(&fsrc, buf, len, &br);
	    }
    f_close(&fsrc);  //不论是打开,还是新建文件,一定记得关闭
}



bmp.h文件

#ifndef _bmp_H
#define _bmp_H
#include <stdio.h>

typedef struct 
{
	uint16_t bfType;        //文件类型,BMP格式为字符串BM
	uint32_t bfSize;		//图片大小,单位为KB
	uint16_t bfReserved1;	//保留位
	uint16_t bfReserved2;	//保留位
	uint32_t bfOffBits;  	//从文件头到实际图像数据之间的字节偏移量
} BMP_FileHeaderTypeDef;

typedef struct 
{
	uint32_t bitSize;		 //BMP_InfoHeaderTypeDef结构体所需要的字节数
	uint32_t biWidth;		 //图片宽度,像素位单位
	int32_t  biHeight;		 //图片高度,像素为单位。正为倒立,负为正向。
	uint16_t biPlanes;		 //颜色平面数,总为1
	uint16_t biBitCount;	 //比特数/像素。其值为:1、4、8、16、24或32
	uint32_t biCompression;  //数据压缩类型
	uint32_t biSizeImage;	 //图像大小
	uint32_t biXPelsPerMeter;//水平分辨率
	uint32_t biYPelsPerMeter;//垂直分辨率
	uint32_t biClrUsed;		 //颜色索引数
	uint32_t biClrImportant; //重要颜色索引数
		
}BMP_InfoHeaderTypeDef;

typedef struct
{
	BMP_FileHeaderTypeDef fileHeader;
	BMP_InfoHeaderTypeDef infoHeader;
		
}BMP_HeaderTypeDef;


void BMP_ReadHeader(uint8_t *header, BMP_HeaderTypeDef *bmp);
void BMP_Picture(uint8_t *dir , uint8_t * buf ,uint32_t len);



#endif

main.c文件完成了SD卡初始化、我们HLS生成的IP初始化、VideoScalerIP核初始化 以及 VDMA 初始化。并通过BMP.c文件中的函数将SD卡上FAT文件系统中的图片读入到内部数组,调用VDMA传输显示。这里循环调用两张图片,间隔几秒就进行切换。

/*
 *
Company: cz123 Electronic Technology Co., Ltd.
         Liyang Milian Electronic Technology Co., Ltd.
Brand: MSXBO
BBS: www.osrc.cn
WEB: www.msxbo.com
Author: tjy
Copyright:  by msxbo 2019-2029
 *
 *
 * */
#include <stdio.h>
#include "xparameters.h"
#include "xsdps.h"
#include "xil_printf.h"
#include "ff.h"
#include "xvideo_scaler.h"
#include "xipsobelacceltop.h"

#define H_STRIDE            640
#define H_ACTIVE            640
#define V_ACTIVE            480
#define VIDEO_LENGTH  (H_STRIDE*V_ACTIVE)


#define VDMA_BASEADDR   XPAR_AXI_VDMA_0_BASEADDR
#define DDR_BASEADDR    0x00000000
#define VIDEO_BASEADDR0 0x01000000
#define BUF_SIZE 640*480*3

static FATFS SD_Dev; // File System instance
char *SD_Path = "0:/";  //  string pointer to the logical drive number
XVideo_scaler gVideoScaler;
XIpsobelacceltop gIpSobelAccelTop;

u8 RD_Buf1[BUF_SIZE] __attribute__ ((aligned(32)));
u8 RD_Buf2[BUF_SIZE] __attribute__ ((aligned(32)));
u8 RD_Buf3[BUF_SIZE] __attribute__ ((aligned(32)));
u8 RD_Buf4[BUF_SIZE] __attribute__ ((aligned(32)));


void Xil_DCacheFlush(void);

void show_img( const unsigned char * addr, u32 size_x, u32 size_y)
{
	u32 x=0;
	u32 y=0;
	u32 r,g,b;
	for(y=size_y;y>0;y--)
	{
		for(x=0;x<size_x;x++)
		{
			b = *(addr++);
			g = *(addr++);
			r = *(addr++);
			Xil_Out32((VIDEO_BASEADDR0+(((y-1)*size_x)+x)*4),((r<<16)|(g<<8)|(b<<0)));
		}
	}

	Xil_DCacheFlush();
}


void VDMA_init()
{
	int i;
	for(i=0;i<VIDEO_LENGTH;i++)
	{
		Xil_Out32(VIDEO_BASEADDR0+i*4,0);
	}
	Xil_DCacheFlush();
	Xil_Out32((VDMA_BASEADDR + 0x000), 0x3);
	Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR0);
	Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*4));
	Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*4));
	Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE);

}

int SD_init()
{
	FRESULT result;
	//-----------------------mount dev-----------------------------------------------
	result = f_mount(&SD_Dev,SD_Path, 0);
	if (result != 0) {
		return XST_FAILURE;
	}
	return XST_SUCCESS;
}


int VideoScalerInit()
{
	int _status;

	_status = XVideo_scaler_Initialize(&gVideoScaler,XPAR_VIDEO_SCALER_0_DEVICE_ID);
	if(_status != XST_SUCCESS)
	{
		xil_printf("\nInit VideoScaler Error\n");
		return -1;
	}
	xil_printf("\nInit VideoScaler Success\n");

	//config video scaler
	XVideo_scaler_Set_in_height(&gVideoScaler,V_ACTIVE);
	XVideo_scaler_Set_in_width(&gVideoScaler,H_ACTIVE);
	XVideo_scaler_Set_out_height(&gVideoScaler,720);
	XVideo_scaler_Set_out_width(&gVideoScaler,1280);

	//start video scaler
	XVideo_scaler_EnableAutoRestart(&gVideoScaler);
	XVideo_scaler_Start(&gVideoScaler);

	return 0;
}

int IpSobelAccelTopInit()
{
	int _status;

	_status = XIpsobelacceltop_Initialize(&gIpSobelAccelTop,XPAR_IPSOBELACCELTOP_0_DEVICE_ID);
	if(_status != XST_SUCCESS)
	{
		xil_printf("\nInit IpSobelAccelTop Error\n");
		return -1;
	}
	xil_printf("\nInit IpSobelAccelTop Success\n");

	//config
	XIpsobelacceltop_Set_inputHeight(&gIpSobelAccelTop,V_ACTIVE);
	XIpsobelacceltop_Set_inputWidth(&gIpSobelAccelTop,H_ACTIVE);
	XIpsobelacceltop_Set_xory(&gIpSobelAccelTop,0);

	//start
	XIpsobelacceltop_EnableAutoRestart(&gIpSobelAccelTop);
	XIpsobelacceltop_Start(&gIpSobelAccelTop);
}

int main()
{
	IpSobelAccelTopInit();
	VideoScalerInit();
	VDMA_init();
	SD_init();

	//xil_printf("\nStart Read BMP\n");
	BMP_Picture((u8 *)"1.bmp" , RD_Buf1 ,BUF_SIZE);
	BMP_Picture((u8 *)"2.bmp" , RD_Buf2 ,BUF_SIZE);

	//xil_printf("\nStart HDMI Show\n");

	while(1)
	{
		show_img(RD_Buf1,640,480);
		xil_printf("idle:%d,ready:%d,done:%d",XVideo_scaler_IsIdle(&gVideoScaler),XVideo_scaler_IsReady(&gVideoScaler),XVideo_scaler_IsDone(&gVideoScaler));
		sleep(2);
		show_img(RD_Buf2,640,480);
		sleep(2);
		//show_img(RD_Buf3,1280,720);
		//sleep(5);
		//show_img(RD_Buf4,1280,720);
		//sleep(5);
	}

    return 0;
}

4 - 测试

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值