vitis HLS中实现canny算法的IP核

7 篇文章 15 订阅
6 篇文章 1 订阅

一、前言

        canny边缘检测主要用于提取图像的边缘,是最常用且有效的边缘检测算法。在AMD赛灵思提供的库函数中,使用xf::cv::Canny和xf::cv::EdgeTracing两个函数实现canny边缘提取。本文举例说明如何在vitis HLS 2023.1中实现canny算法。

二、xf::cv::Canny和xf::cv::EdgeTracing函数解析

        首先看一下这两个函数的调用接口:

1、xf::cv::Canny函数

template<int FILTER_TYPE, //sobel滤波宽度,仅支持3和5
         int NORM_TYPE,   //范数类型,支持L1范数和L2范数
         int SRC_T,       //输入图像类型,仅支持8UC1
         int DST_T,       //输出图像类型,仅支持2UC1
         int ROWS,       //最大图像行数
         int COLS,      //最大图像列数
         int NPC,      //输入图像的单个时钟处理像素数
         int NPC1,      //输出图像的单个时钟处理像素数
         bool USE_URAM=false, //是否使用URAM
         int XFCVDEPTH_IN_1 = _XFCVDEPTH_DEFAULT, //输入图像深度
         int XFCVDEPTH_OUT_1 = _XFCVDEPTH_DEFAULT> //输出图像深度
void Canny(xf::cv::Mat<SRC_T, ROWS, COLS, NPC, XFCVDEPTH_IN_1> &_src_mat, //输入图像矩阵
           xf::cv::Mat<DST_T, ROWS, COLS, NPC1, XFCVDEPTH_OUT_1> & _dst_mat, //输出图像矩阵
           unsigned char _lowthreshold,   //边缘提取低阈值
           unsigned char _highthreshold)  //边缘提取高阈值

        在xf::cv::Canny算法中,首先通过3*3的高斯噪声滤波器对图像进行滤波;此后使用Sobel梯度函数计算沿着x和y方向的梯度,用以计算像素的幅度和相位;然后使用最大值抑制算法,得到对应的边缘点进行输出,输出结果的单个像素的位宽为2bits,,然后经过打包输出。

        xf::cv::Canny函数输出的图像像素2bits,含义表示如下:

                00-表示背景

                01-表示弱边缘

                11-表示强边缘

2、xf::cv::EdgeTracing函数

template<int SRC_T,  //输入图像类型
         int DST_T,  //输出图像类型
         int ROWS,   //图像最大行数
         int COLS,   //图像最大列数
         int NPC_SRC,//输入图像的NPPC,每个时钟处理像素数
         int NPC_DST,//输出图像的NPPC,每个时钟处理像素数
         bool USE_URAM=false, //是否使用URAM
         int depthm = -1> //图像深度
void EdgeTracing(xf::cv::Mat<SRC_T, ROWS, COLS, NPC_SRC, depthm> & _src,//输入图像矩阵
                 xf::cv::Mat<DST_T, ROWS, COLS, NPC_DST, depthm> & _dst) //输出图像矩阵

        xf::cv::EdgeTracing函数主要用于处理canny算法,将离散的强边缘和弱边缘进行边缘跟踪,将离散的边缘点串联起来,最终将2UC1的图像输出为一个8UC1的图像。

        对于此函数需要特别注意,其无法实现数据的DATAFLOW,只能采取内存映射读取的方式进行读写访问。并且在综合的时候,需要在cflag总添加编译指令"-D__SDA_MEM_MAP__",否则综合时会报错。具体可以参考后面的示例。

        关于其余详细信息,可以参考:xilinx.github.io/Vitis_Libraries/vision/2022.1/api-reference.html#canny-edge-detection

三、vitis HLS canny算法中的具体代码实现

        这部分的代码实现不难,在赛灵思提供的示例程序中就有现成的参考示例,不过是在L2文件夹下,主要是vitis下的实现demo。不过稍微进行更改,就可以在vitisHLS中成功完成综合和联合仿真了。

        若想要查看赛灵思提供的参考示例,请访问Xilinx/Vitis_Libraries: Vitis Libraries (github.com)

        下面主要描述在vitisHLS中是如何完成vitisHLS代码的。

1、首先提供一下头文件define.h代码

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



#define FILTER_WIDTH 3

#define NORM_TYPE XF_L1NORM

#define XF_USE_URAM false

#define IMAGE_PTR_WIDTH 64

#define WIDTH 512
#define HEIGHT 512



#define THRES_LOW 120 //边缘提取低阈值
#define THRES_HIGH 180 边缘提取高阈值

void canny_accel(ap_uint<IMAGE_PTR_WIDTH>* img_inp,
                 ap_uint<IMAGE_PTR_WIDTH>* img_out,
                 int rows,
                 int cols,
                 int low_threshold,
                 int high_threshold) ;

void edgetracing_accel(ap_uint<IMAGE_PTR_WIDTH>* img_inp,
				ap_uint<IMAGE_PTR_WIDTH>* img_out,
				int rows,
				int cols);

2、xf::cv::Canny的代码示例



#include "define.h"

static constexpr int _XF_DEPTH_I = (HEIGHT * WIDTH * (XF_PIXELWIDTH(XF_8UC1, XF_NPPC8))) / (IMAGE_PTR_WIDTH);
static constexpr int _XF_DEPTH_O = (HEIGHT * WIDTH * (XF_PIXELWIDTH(XF_2UC1, XF_NPPC32))) / (IMAGE_PTR_WIDTH);

void canny_accel(ap_uint<IMAGE_PTR_WIDTH>* img_inp,
                 ap_uint<IMAGE_PTR_WIDTH>* img_out,
                 int rows,
                 int cols,
                 int low_threshold,
                 int high_threshold) {
// clang-format off
    #pragma HLS INTERFACE m_axi     port=img_inp  depth=_XF_DEPTH_I bundle=gmem1
    #pragma HLS INTERFACE m_axi     port=img_out  depth=_XF_DEPTH_O bundle=gmem2

// clang-format on

// clang-format off
    #pragma HLS INTERFACE s_axilite port=rows     
    #pragma HLS INTERFACE s_axilite port=cols     
    #pragma HLS INTERFACE s_axilite port=low_threshold     
    #pragma HLS INTERFACE s_axilite port=high_threshold     
    #pragma HLS INTERFACE s_axilite port=return
    // clang-format on

    int npcCols = cols;
    int divNum = (int)(cols / 32);
    int npcColsNxt = (divNum + 1) * 32;
    if (cols % 32 != 0) {
        npcCols = npcColsNxt;
    }
    //printf("actual number of cols is %d \n", npcCols);

    xf::cv::Mat<XF_8UC1, HEIGHT, WIDTH, XF_NPPC8> in_mat(rows, cols);
    xf::cv::Mat<XF_2UC1, HEIGHT, WIDTH, XF_NPPC32> dst_mat(rows, npcCols);

#pragma HLS DATAFLOW

    xf::cv::Array2xfMat<IMAGE_PTR_WIDTH, XF_8UC1, HEIGHT, WIDTH, XF_NPPC8>(img_inp, in_mat);
    xf::cv::Canny<FILTER_WIDTH, NORM_TYPE, XF_8UC1, XF_2UC1, HEIGHT, WIDTH, XF_NPPC8, XF_NPPC32, XF_USE_URAM>
                 (in_mat, dst_mat, low_threshold, high_threshold);
    xf::cv::xfMat2Array<IMAGE_PTR_WIDTH, XF_2UC1, HEIGHT, WIDTH, XF_NPPC32>(dst_mat, img_out);
}

        此部分代码注意几点如下:

        1、#pragma HLS DATAFLOW 实现数据的流水线处理。

        2、输出图像为XF_2UC1格式,无法直接在opencv中显示。

        3、图像列数需要为32的整数倍

        4、CFLAG正常设置即可,我这边设置的是 "-D__SDSVHLS__ -IE:/vitis_hls_image/vitis_hls_tutorial/include -std=c++0x -O3"

3、xf::cv::EdgeTracing的代码示例



#include "define.h"

static constexpr int _XF_DEPTH_I = (HEIGHT * WIDTH * (XF_PIXELWIDTH(XF_2UC1, XF_NPPC32))) / (IMAGE_PTR_WIDTH);
static constexpr int _XF_DEPTH_O = (HEIGHT * WIDTH * (XF_PIXELWIDTH(XF_8UC1, XF_NPPC8))) / (IMAGE_PTR_WIDTH);


void edgetracing_accel(ap_uint<IMAGE_PTR_WIDTH>* img_inp,
						ap_uint<IMAGE_PTR_WIDTH>* img_out,
						int rows,
						int cols) {
// clang-format off
    #pragma HLS INTERFACE m_axi     port=img_inp  depth=_XF_DEPTH_I bundle=gmem3
    #pragma HLS INTERFACE m_axi     port=img_out  depth=_XF_DEPTH_O bundle=gmem4
// clang-format on

// clang-format off
    #pragma HLS INTERFACE s_axilite port=rows     
    #pragma HLS INTERFACE s_axilite port=cols     
    #pragma HLS INTERFACE s_axilite port=return
    // clang-format on

    int npcCols = cols;
    int divNum = (int)(cols / 32);
    int npcColsNxt = (divNum + 1) * 32;
    if (cols % 32 != 0) {
        npcCols = npcColsNxt;
    }

    int npcCols_8 = cols;
    int divNum_8 = (int)(cols / 8);
    int npcColsNxt_8 = (divNum_8 + 1) * 8;
    if (cols % 8 != 0) {
        npcCols_8 = npcColsNxt_8;
    }
    // printf("actual number of cols is %d \n", npcCols);
    // printf("actual number of cols is multiple 8 :%d \n", npcCols_8);

    // printf("\nbefore allocate\n");
    xf::cv::Mat<XF_2UC1, HEIGHT, WIDTH, XF_NPPC32> _dst1(rows, npcCols, img_inp);
    xf::cv::Mat<XF_8UC1, HEIGHT, WIDTH, XF_NPPC8> _dst2(rows, npcCols_8, img_out);
    // printf("\nbefore kernel call\n");
    xf::cv::EdgeTracing<XF_2UC1, XF_8UC1, HEIGHT, WIDTH, XF_NPPC32, XF_NPPC8, XF_USE_URAM>(_dst1, _dst2);
    // printf("\nafter kernel call\n");
}

        此部分代码注意几点如下:

        1、不能添加指令:#pragma HLS DATAFLOW 。否则综合会报错

        2、图像列数需要为32的整数倍

        3、CFLAG设置需要额外注意添加__SDA_MEM_MAP指令,否则综合报错。我这边设置的是 "-D__SDSVHLS__ -D__SDA_MEM_MAP__ -IE:/vitis_hls_image/vitis_hls_tutorial/include -std=c++0x -O3"

4、综合注意事项

        我刚开始编译的时候,总以为可以将xf::cv::Canny和xf::cv::EdgeTracing两个函数综合到1个IP核里,但是最终我失败了。这两个函数,一个是DATAFLOW形式,一个是__SDA_MEM_MAP__的编译方式,是无法在一个IP核中编译成功的,需要将2个函数分别编译成一个IP,在vivado中按照下图的方式相连。

5、testbench的代码示例


#include <iostream>
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <opencv2/highgui.hpp>
#include <opencv/cxcore.h>
#include <opencv2/imgproc.hpp>

#include "define.h"
#include "common/xf_sw_utils.hpp"





int main(int argc, char* argv[])
{


//------------1、读取图像转化为灰度图像---------------------------
	printf("argc == %d \n",argc);
    if (argc != 2) {
        printf("input error: PLEASE INPUT IMAGE PATH 1\n");
        return 1;
    }

//opencv canny边缘处理
    cv::Mat img_in; //输入图像

    img_in = cv::imread(argv[1],cv::IMREAD_GRAYSCALE);//按照GRAY图读取图像

    cv::imwrite("opencv读取的图像.png",img_in);//显示

    //-----直接进行canny处理
	cv::Mat image_canny_only(img_in.rows, img_in.cols, img_in.type());

	cv::Canny(img_in,image_canny_only,THRES_LOW,THRES_HIGH);

	cv::imwrite("openCV处理后图像-无高斯滤波.png",image_canny_only);//

	//----预先进行高斯滤波处理
	cv::Mat image_gaus(img_in.rows, img_in.cols, img_in.type());
	cv::Mat image_canny(img_in.rows, img_in.cols, img_in.type());

	//实际上HLS处理中,首先进行了高斯滤波,因此在opencv中也加入高斯滤波
	cv::GaussianBlur( img_in, image_gaus, cv::Size(3,3),0, 0,cv::BORDER_CONSTANT);
	cv::Canny(image_gaus,image_canny,THRES_LOW,THRES_HIGH);

	cv::imwrite("openCV处理后图像-高斯滤波.png",image_canny);//

//2、HLS canny边缘处理---------------------------

    xf::cv::Mat<XF_8UC1,HEIGHT,WIDTH,XF_NPPC8> image_in; //输入图像

    image_in = xf::cv::imread<XF_8UC1,HEIGHT,WIDTH,XF_NPPC8>(argv[1],cv::IMREAD_GRAYSCALE);//按照GRAY图读取图像

    xf::cv::imwrite<XF_8UC1,HEIGHT,WIDTH,XF_NPPC8>("HLS读取的图像.png",image_in);//显示

    xf::cv::Mat<XF_2UC1,HEIGHT,WIDTH,XF_NPPC32>hls_image_canny;

    canny_accel(      (ap_uint<IMAGE_PTR_WIDTH>*) image_in.data,
					(ap_uint<IMAGE_PTR_WIDTH>*) hls_image_canny.data,
					512,
					512,
					THRES_LOW,
					THRES_HIGH
					);

	xf::cv::Mat<XF_8UC1,HEIGHT,WIDTH,XF_NPPC8> hls_image_edge;

	edgetracing_accel( (ap_uint<IMAGE_PTR_WIDTH>*) hls_image_canny.data,
			          (ap_uint<IMAGE_PTR_WIDTH>*) hls_image_edge.data,
					  HEIGHT,
					  WIDTH);


	xf::cv::imwrite<XF_8UC1,HEIGHT,WIDTH,XF_NPPC8>("HLS处理后图像.png",hls_image_edge);//



	cv::waitKey(0);/// 等待用户按任意按键退出程序
	return 0;
}

        对输入测试激励tina.png,原图、opencv处理结果(无高斯滤波),opencv处理(有高斯滤波),HLS处理结果分别如下:

原图 
opencv处理结果(无高斯滤波)

opencv处理(有高斯滤波)

 HLS处理

        分析上面的结果,可以看到HLS处理的canny图像边缘,由于包含了高斯滤波,所以与opencv处理(有高斯滤波)的处理结果最接近,且结果基本正确。

        实测,该函数综合、联合仿真结果均正确。

四、完整的vitisHLS示例工程

        有兴趣的可以参考完整的示例代码:vitis-HLScanny算法实现图像边缘检测资源-CSDN文库

        (我偷懒了点,将xf::cv::Canny和xf::cv::EdgeTracing放在一个工程的两个函数中。在实际使用时,需要分别将xf::cv::Canny和xf::cv::EdgeTracing对应的函数设置为顶层函数,分别进行综合、导出RTL文件,这样就可以得到2个IP了,把这两个IP都添加到vivado中综合编译即可。

               

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值