HLS第三课(xfopencv axis axim )

UG1233是使用xfopencv的依据。

vivado HLS中只有testBeach中可以使用opencv函数,而在source中不可以,因为source中的所有是要被综合的,只可以使用 xfOpenCV+hls video function

在C++中,template是在H文件中定义的。

+++++++++++++++++++++++++++++++++++++++++++++++
先来看看axim_read.h文件的编写。
首先是包含各种需要的头文件,如前所述,
1)C系统头文件
2)HLS相关头文件
3)XFOPENCV相关头文件

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

#include "ap_int.h"
#include "hls_stream.h"

#include "xf_config_params.h"

#include "common/xf_common.h"
#include "common/xf_utility.h"
#include "common/xf_infra.h"
#include "imgproc/xf_crop.hpp"
#include "imgproc/xf_resize.hpp"

然后是定义有用的宏。

#define _DATA_WIDTH_(_T, _N) (XF_PIXELWIDTH(_T, _N) * XF_NPIXPERCYCLE(_N))
#define _BYTE_ALIGN_(_N) ((((_N) + 7) / 8) * 8)

#define DATA_WIDTH(T, N)	((T) * (N))
#define T_UINT(T, N) 		ap_uint<DATA_WIDTH(T, N)>
#define T_AXIU(T, N)		ap_axiu<DATA_WIDTH(T, N), 1, 1, 1>

然后是定义模板函数。
1)mm2s_read_line
读一行像素数据,存入行缓存。

template<int BPP, int TYPE, int ROWS, int COLS, int NPPC>
void mm2s_read_line(T_UINT(BPP, NPPC)* src, T_UINT(BPP, NPPC) line_buffer[COLS/NPPC], int cols)
{
	for (int j = 0; j < cols; j++)
	{
#pragma HLS inline off		
#pragma HLS pipeline II = 1	
		line_buffer[j] = src[j];
	}	 
}

对于VideoIP而言,模板参数通常是BPP,TYPE,ROWS,COLS,NPPC。

这个函数的参数,
首先是有一个源变量,这个模块是并行输入多个像素处理的,所以总位宽是BPP*NPPC,这里用这两个参数来定义具体类型ap_uint,源被指定为一个指针。

在HLS中,指针和数组,都是可以接受的操作数。将被实现为AXIMM,例如DDR或者BRAM。
在HLS中,基本类型的变量,被理解为配置参数。将被实现为control bus reg。

同样的,目的变量line_buffer参数,被指定为一个数组名,而我们知道,在C语言中,数组名即指针。只不过,用数组名作为指针使用时,会检查限界,所以,更准确的说法是,
数组名即限界指针。
由于一次处理NPPC个像素,所以line_buffer在位宽扩展的时候,元素个数相应减少,即COLS/NPPC。

最后,由基本类型的参数cols,作为配置参数使用。这里,是以packet为单位,而不是以pixel为单位。

在for循环体中,实现逐个像素处理。

对于for循环体,我们使用了pragma。
首先关掉了inline。
然后,指定pipeline,进行循环展平,并指定了II为强约束的1。

2)mm2s_write_line
从行缓存中读一行像素数据,写入Mat。

template<int BPP, int TYPE, int ROWS, int COLS, int NPPC>
void mm2s_write_line(T_UINT(BPP, NPPC) line_buffer[COLS/NPPC], xf::Mat<TYPE, ROWS, COLS, NPPC>& dst, int cols, int m)
{
	for (int j = 0; j < cols; j++)
	{
#pragma HLS inline off		
#pragma HLS pipeline II = 1		
		T_UINT(BPP, NPPC) srcpixel = line_buffer[j];
		dst.write(m + j, srcpixel);
	}
}

首先定义一个参数,源变量,这里是line_buffer,
然后定义一个参数,目的变量,这里是dst,注意,HLS中使用对象时,要进行传名调用。
然后定义一个配置参数,cols,以packet为单位,而不是以pixel为单位。
然后定义一个配置参数,m,这是Mat中的base地址。

在for循环体中,实现逐个像素处理。
通过调用Mat类的成员函数write,实现写入。
注意,在写入时,对二维矩阵是进行降维处理的,要转换成线性化地址来操作。

对于for循环体,我们使用了pragma。

3)axim_mm2s
将AXIMM转换成AXIS。

template<int BPP, int TYPE, int ROWS, int COLS, int NPPC>
void axim_mm2s(T_UINT(BPP, NPPC)* src, xf::Mat<TYPE, ROWS, COLS, NPPC>& dst, int crop_x, int crop_y)
{
	int rows = dst.rows;
	int cols = dst.cols / NPPC; 
	int offset = crop_y * COLS;

	for (int i = 0; i < rows; i++)
	{
#pragma HLS dataflow
		T_UINT(BPP, NPPC) line_buffer[COLS/NPPC];
		int m = i * cols;
		int n = (offset + i * COLS + crop_x) / NPPC;
		T_UINT(BPP, NPPC)* rptr = (T_UINT(BPP, NPPC)*)&src[n];
		mm2s_read_line<BPP, TYPE, ROWS, COLS, NPPC>(rptr, line_buffer, cols);
		mm2s_write_line<BPP, TYPE, ROWS, COLS, NPPC>(line_buffer, dst, cols, m); 
	}	 
}

首先定义了一个参数,源变量,这里用指针,表示一个AXIMM。
然后定义了一个参数,目的变量,这里用Mat对象,表示一个AXIS。
然后定义了配置参数,x,y。

在函数内,首先提取当前需要的参数。
从Mat对象中提取总行数,然后提取总列数,由于进行了NPPC并行化处理,所以要除以NPPC,得到以packet为单位的cols。
然后根据配置的裁剪参数,设置offset。

在for循环体中,实现逐行处理。在调用的行处理函数中,会进一步实现逐像素处理。

这里面,使用了一个line_buffer进行缓存,可以提高并行度,增加流水线效率。
这个line_buffer被定义为局部变量,所以在模块内实现时,是一个模块内使用的DPRAM。
这里面,使用了两个局部变量m,n,用来进行线性化转换。
m被转换为row base,n被转换为packet base,
n作为index,作为src的索引位置,获取src[n]的指针,另存为rptr,
然后,调用mm2s_read_line的具象函数,从AXIMM中的对应的像素点开始,读取一整行数据,放到line_buffer中。
然后,调用mm2_write_line的具象函数,从line_buffer中读取一整行数据,写入Mat对象中,这里需要制定base。

在for循环体中,使用了了pragma。
dataflow这个pragma,使得内部以数据流的形式进行优化。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
再来看看CPP文件。
这里定义了一个简单的函数axi_read。

void axim_read(T_UINT(BAYER_BPP, BAYER_NPPC)* src, hls::stream<T_AXIU(BAYER_BPP, BAYER_NPPC)>& dst, int crop_x, int crop_y, int crop_width, int crop_height)
{
#pragma HLS INTERFACE m_axi offset=direct port=src depth=1940800
#pragma HLS INTERFACE axis register both port=dst
	
	xf::Mat<BAYER_TYPE, BAYER_HEIGHT, BAYER_WIDTH, BAYER_NPPC> image0(crop_height, crop_width);
#pragma HLS stream variable=image0.data dim=1 depth=1024
	
#pragma HLS dataflow	
	axim_mm2s<>(src, image0, crop_x, crop_y);
	 xf::xfMat2AXIvideo(image0, dst);
}

这个函数,定义了一个源变量,src,是一个指针,
而又定义了一个hls::stream类的对象,按照HLS的要求,这里使用传名调用。
定义了一系列配置参数,x,y,width,height,

在函数级别,使用了pragma。用来制定interface的实现方式。
将src参数,约束为m_axi接口,所以这个指针对应的变量,将被实现为AXIMM。并指定了depth。在程序中,使用"访数操作"时,(*src),将被HLS理解为通过AXI总线发起read或者write。
将dst参数,约束为axis接口,所以这个对象对应的变量,将被实现为AXIS。在程序中,调用这个对象时,只能使用一个数据方向,要么使用read,要么使用write。

在函数内,定义了一个局部变量,是一个xf::Mat的具象类型的对象,即image0。
随后,为该变量使用了pragma。
在函数级别,用stream这个约束,指定了对象中的data buffer,被实现为stream。在HLS中,stream被理解为FIFO。并为FIFO指定了dim和depth。

在函数级别,使用了pragma。
用dataflow这个约束,指定后续的调用,进行数据流方式的优化。


注意,这里调用axim_mm2s时,使用了隐式具象化,
由于没有给出模板函数的具体值,C++会根据传入的参数的类型进行自动推演,从而编译出合适的具象函数。

由两个功能级函数来完成业务级函数的任务。
首先是调用axim_mm2s函数,从src中读取图像存入image0这个Mat对象。
然后是调用xf::xfMat2AXIvideo函数,将Mat对象中的数据,转换成video格式的stream。

在函数中,我们对外部的dst对象,进行了单向的写入操作。所以在HLS的理解中,这是合法的,HLS理解为,外部有一个内存,且这个内存块具有一个s_axis接口,所以,本模块将会生成一个m_axis接口。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
补充:
可综合的hls stream。
为了将输入输出转换成stream,我们需要联合使用Mat和stream。
例如;

void bayer_video_axis(hls::stream<T_AXIU(BAYER_BPP, BAYER_NPPC)>& src, hls::stream<T_AXIU(BAYER_BPP, BAYER_NPPC)>& dst, int width, int height)
{
#pragma HLS INTERFACE axis register both port=src
#pragma HLS INTERFACE axis register both port=dst

#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL_BUS
#pragma HLS INTERFACE s_axilite port=width bundle=CONTROL_BUS
#pragma HLS INTERFACE s_axilite port=height bundle=CONTROL_BUS
	
	xf::Mat<BAYER_TYPE, BAYER_HEIGHT, BAYER_WIDTH, BAYER_NPPC> image0(height, width);
#pragma HLS stream variable=image0.data dim=1 depth=1024

#pragma HLS dataflow
    xf::AXIvideo2xfMat(src, image0);
    xf::xfMat2AXIvideo(image0, dst);
}

在函数中,定义stream对象的参数,
在函数中,定义一个mat对象,作为局部变量,
通过mat对象来进行stream转换。

在函数级别,使用了pragma。
用axis约束,来指定src和dst,
用s_axilite约束,来指定其他配置参数,以及return。
用stream约束,来指定Mat对象的databuffer的实现方式。

该函数中,用两个功能级函数来实现业务级函数的任务。
调用xf::AXIvideo2xfMat函数,将stream输入到mat,
调用 xf::xfMat2AXIvideo函数,将mat输出到stream。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

再来看看tb的CPP文件
首先把需要的头文件包含进来。

#include "axim_read.h"
#include "axim_read_tb.h"

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

//#include "common/xf_sw_utils.h"
#include "common/xf_axi.h"

using namespace cv;

然后定义main

int main(int argc, char *argv[])
{	
	if (argc != 2)
	{
		printf("usage: %s bayer.png\n", argv[0]);
		return -1;
	}	

	cv::Mat in_img;
	in_img = cv::imread(argv[1], 0);
	if (in_img.data == NULL)
	{
		fprintf(stderr,"Cannot open image at %s\n", argv[1]);
		return 0;
	}

	int width = in_img.size().width;
	int height = in_img.size().height;
	unsigned char bayer_mode = 0;
	int crop_width = 2048;
	int crop_height = 1200;
    int crop_x = width - crop_width;
	int crop_y = height - crop_height;
	
	if ((width != BAYER_WIDTH) || (height != BAYER_HEIGHT))
	{
		printf("image size is wrong, real:%dx%d, needed:%dx%d \n", width, height, BAYER_WIDTH, BAYER_HEIGHT);
		return 0;
	}
	/*
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width / BAYER_NPPC; j++)
        {
            for (int k = 0; k < BAYER_NPPC; k++)
            {
                in_img.at<uchar>(i, j * BAYER_NPPC + k) = j;
            }
        }
    }*/

	cv::Mat out_img;
	out_img.create(VIDEO_HEIGHT, VIDEO_WIDTH, CV_8U);
	
	 
	hls::stream<T_AXIU(BAYER_BPP, BAYER_NPPC)> src;
	hls::stream<T_AXIU(VIDEO_BPP, VIDEO_NPPC)> dst;

	cvMat2AXIvideoxf<BAYER_NPPC>(in_img, src);
    bayer_video_axis(src, dst, crop_width, crop_height); 
    AXIvideo2cvMatxf<BAYER_NPPC>(dst, out_img);
    cv::imwrite("hls.bmp", out_img);

	cv::imwrite("image.bmp", in_img);
	
	printf("test ok!\n");
	return 0;
}

只有在TB中,才可以使用cv域中的类和函数的。

首先是定义一个局部变量,in_img,并用cv::imread函数,从file path中读取图片,并存入cv::mat对象。

然后,从对象中,获取图像相关的配置信息。
调用成员函数size函数,返回一个Size类的对象,然后从Size对象中,获取width和height。

然后,调用成员函数at函数,返回一个pixel的指针,并向这个pixel中写入数据。这里使用了三层for循环,实现逐点操作。

然后,定义一个局部变量,out_image,并用成员函数create,分配一个data buffer。

然后,定义两个stream局部变量,src和dst。

然后,调用CV域中的具象函数cvMat2AXIvideoxf<BAYER_NPPC>,将mat中的数据输入到stream中。

然后,调用DUT,即bayer_video_axis函数,

然后,调用CV域中的具象函数AXIvideo2cvMatxf<BAYER_NPPC>,将stream中的数据,输入到mat中。

然后,调用CV域中的cv::imwrite函数,将cv::mat对象中的图像。输出到file path中,存为图片。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值