vitis HLS中实现canny算法的IP核

news2024/11/6 7:20:54

一、前言

        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中综合编译即可。

               

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1332901.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GoogLeNet(V1)

目录 一、GooLeNet介绍 1、模型设计的motivation 2、Inception块 3、GoogLeNet架构 4、Inception后续变种 5、总结 二、代码实现 1、Inception块 2、GoogLeNet模型 3、训练模型 4、总结 一、GooLeNet介绍 GoogLeNet是由Google团队于2014年提出的深度卷积神经网络架构…

c++缺省参数与函数重载(超详细)

文章目录 前言一、缺省参数1.缺省参数的概念与使用2.缺省参数的分类3.缺省参数注意事项 二、函数重载1.什莫事函数重载2.函数重载的几种形式3.函数重载与缺省值的结合4.为什么c支持函数重载&#xff1f;&#xff1f; 总结 前言 在本文章中&#xff0c;我们将要详细介绍一下Cc缺…

【MySQL】数据库之索引的增删改查

目录 一、索引是什么 二、索引的作用 三、工作方式 四、创建索引的依据&#xff1a; 五、索引的分类 六、索引的增删改查&#xff08;索引是一种对象&#xff0c;与字段类似是命令&#xff09; 索引的添加 ​编辑第一种&#xff1a;普通索引的创建 第二种&#xff1a;唯…

C++ 比 C语言增加的新特性 2

1.C新增了带默认值参数的函数 1.1 格式 格式&#xff1a;返回值 函数名&#xff08;参数1初始值1&#xff0c;..........&#xff09;{} 例如&#xff1a;void function&#xff08;int a10&#xff09;{} 调用&#xff1a;不需要更改参数的值&#xff1a;function&#x…

001 图书增删改查 SSM MySQL

技术框架&#xff1a;Spring SpringMVC Mybatis JSP MySQL 001 图书增删改查 SSM MySQL package com.demo.controller;import com.demo.pojo.Book; import com.demo.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.spri…

leetcode 面试题 17.19. 消失的两个数字 (hard)(优质解法)

链接&#xff1a;面试题 17.19. 消失的两个数字 代码&#xff1a; class Solution {public int[] missingTwo(int[] nums) {int lengthnums.length;int tmp0;//将完整数据以及 nums 中的数据都进行异或&#xff0c;得到的就是缺失的两个数字 a^b 的结果for(int i1;i<length…

【飞翔的鸟】飞行游戏-uniapp项目开发流程详解

小时候玩过的飞行游戏&#xff0c;叫什么名字来着&#xff0c;通过点击操作控制煽动翅膀来持续飞行&#xff0c;躲避障碍物&#xff0c;有多远就飞多远吧&#xff0c;现在想起来&#xff0c;其中的实现原理非常简单&#xff0c;感兴趣的话来一起看看&#xff0c;这里给大家讲一…

RIS 系列 Mask Grounding for Referring Image Segmentation 论文阅读笔记

RIS 系列 Mask Grounding for Referring Image Segmentation 论文阅读笔记 一、Abstract二、引言三、相关工作Architecture Design for RISLoss Design for RISMasked Language Modeling 四、方法4.1 结构4.2 Mask Grounding讨论 4.3 跨模态对齐模块4.4 跨模态对齐损失4.5 损失…

顺序表基本操作实现

#include <stdio.h>#define MAX_SIZE 100// 定义顺序表的元素类型 typedef int ElementType;// 定义顺序表结构体 typedef struct {ElementType data[MAX_SIZE];int length; } SeqList;// 初始化顺序表 void InitList(SeqList *L) {L->length 0; }// 插入操作 int Li…

BP网络识别26个英文字母matlab

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;字母识别 获取完整源码源工程文件 一、 设计思想 字符识别在现代日常生活的应用越来越广泛&#xff0c;比如车辆牌照自动识别系统&#xff0c;手写识别系统&#xff0c;办公自动化等等。本文采用BP网络对26个英文字母进行…

优化小地图(非RawImage方法,节省性能)

优化小地图&#xff08;非RawImage方法&#xff0c;节省性能&#xff09; 一、小地图设计二、功能实现1.截取俯视图2.创建Cube包裹住场地&#xff0c;并且创建一个子物体坐标为&#xff08;0,0,0&#xff09;**3.创建UI显示小地图坐标转换代码如下&#xff1a; 一、小地图设计 …

我是如何转行 AI 并且实现薪资翻倍的

大家好啊&#xff0c;我是董董灿。 熟悉我的小伙伴都知道&#xff0c;我之前在北京某211大学&#xff0c;本硕读了7年的机械专业&#xff0c;后来硕士毕业后&#xff0c;果断转行去做了嵌入式开发&#xff0c;随后瞅准了 AI 爆发的时机果断转行去做了AI。 这段经历已经过去了…

【python与机器学习3】,感知机和与非门

1 电子和程序里的与门&#xff0c;非门&#xff0c;或门&#xff0c;与非门 &#xff0c;或非门&#xff0c;异或门 1.1 基础电路 与门&#xff08;AND gate&#xff09;、或门&#xff08;OR gate&#xff09;和非门&#xff08;NOT gate&#xff09;是数字逻辑电路中的三种基…

本地搭建【文档助手】大模型版(LangChain+llama+Streamlit)

概述 本文的文档助手就是&#xff1a;我们上传一个文档&#xff0c;然后在对话框中输入问题&#xff0c;大模型会把问题的答案返回。 安装步骤 先下载代码到本地 LangChain调用llama模型的示例代码&#xff1a;https://github.com/afaqueumer/DocQA&#xff08;代码不是本人…

自动驾驶规划算法

本文将讲解BFS&#xff0c;Dijstra&#xff0c;A*&#xff0c;动态规划的算法原理&#xff0c;不正之处望读者指正&#xff0c;希望有兴趣的读者能在评论区提出一些这些算法的面试考点&#xff0c;共同学习&#xff0c;一起进步 0 图论基础 图有三种&#xff1a;无向图、有向…

SRE 与 DevOps:你知道它们之间区别吗?

公众号「架构成长指南」&#xff0c;专注于生产实践、云原生、分布式系统、大数据技术分享 DevOps专注于消除阻碍开发和运维之间协作的隔阂&#xff0c;而SRE致力于设计和实施可扩展、可靠的系统&#xff0c;确保最大可靠性。 这篇文章将探讨DevOps和SRE之间的差异&#xff0c…

Podman配置mongodb

文章目录 查询镜像拉取镜像查看镜像运行容器创建root用户 查询镜像 podman search mongo拉取镜像 podman pull docker.io/library/mongo查看镜像 podman images运行容器 podman run -d -p 27017:27017 --namemongodb-test docker.io/library/mongo创建root用户 podman exe…

SSH秘钥登录服务器

一、查看本机 ssh 公钥&#xff0c;生成公钥 1.通过命令窗口 a. 打开你的 git bash 窗口 b. 进入 .ssh 目录&#xff1a;cd ~/.ssh c. 找到 id_rsa.pub 文件&#xff1a;ls d. 查看公钥&#xff1a;cat id_rsa.pub 或者 vim id_rsa.pub git–查看本机 ssh 公钥&#xff0c…

小白--将笔记本上的代码或者项目上传到github上去教程(使用git命令)

文章目录 一、操作教程二、常见问题1. 问题12. 问题2 三、注意 一、操作教程 https://blog.csdn.net/Elon15/article/details/125705706?ops_request_misc%257B%2522request%255Fid%2522%253A%2522170340591716800215092652%2522%252C%2522scm%2522%253A%252220140713.130102…

第六部分 集合论

目录 主要内容 集合的基本概念 集合的基本运算 集合恒等式 初级运算 文氏图 集合的广义并与广义交 广义运算的性质 例1 A{{a},{a,b}} 集合算律 例2 判断下列命题是否为真 例3 设 例4 判断以下命题的真假&#xff0c;并说明理由. 解题思路 主要内容 集合的基本概念 属于、包含…