vitis HLS中实现canny算法的IP核

一、前言

        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.mfbz.cn/a/270227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

linux:下载、网络请求、端口

一&#xff1a;ping命令 可以通过ping命令,检查指定的网络服务器是否是可联通状态 语法: ping [-c num] ip或主机名 1、选项&#xff1a;-c,检查的次数&#xff0c;不使用-c选项&#xff0c;将无限次数持续检查 2、参数&#xff1a;ip或主机名&#xff0c;被检查的服务器的…

【知识点随笔分享 | 第九篇】常见的限流算法

目录 前言&#xff1a; 1.固定窗口限流&#xff1a; 缺点&#xff1a; 2.滑动窗口限流&#xff1a; 优点&#xff1a; 滴桶限流&#xff1a; 缺点&#xff1a; 令牌桶限流&#xff1a; 优点&#xff1a; 总结: 前言&#xff1a; 当今互联网时代&#xff0c;随着网络…

【Linux系统编程】【Google面试题改编】线程之间的同步与协调 Linux文件操作

编写程序&#xff0c;有四个线程1、2、3、4 线程1的功能就是输1,线程2的功能就是输出2,以此类推……现在有四个文件ABCD初始都为空 现要让四个文件呈如下格式&#xff1a; A: 1 22 333 4444 1 22 333 4444… B: 22 333 4444 1 22 333 4444 1… C: 333 4444 1 22 333 4444 1 2…

VMware安装linux系统一

1、创建虚拟机 1.1、创建新的虚拟机 1.2、进入安装向导 1.3、安装操作系统&#xff0c;选择稍后安装操作系统 1.4、选择Linux,版本选择CentOS64位 1.5、设置虚拟机名称和安装位置 1.6、设置磁盘大小 1.7、创建虚拟机 1.8、完成安装 2、配置虚拟机 2.1、选择编辑虚拟机 2.2、修…

【笔记】入门PCB设计(全30集带目录) 杜洋工作室 AD09 Altium Designer

入门PCB设计&#xff08;全30集带目录&#xff09; 杜洋工作室 AD09 p1 创建p2 原理图上增加元件1&#xff09;加元件2&#xff09;放导线3&#xff09;自定义元件1. 自定义排针2.有引脚的元件 p3 完整原理图 p1 创建 step1.创建&#xff08;PCB&#xff09;工程,后缀.PrjPCB。…

算法导论复习(三)

这一次我们主要复习的是递归式求解 递归式求解主要有的是三种方法&#xff1a; 代换法递归树法主方法 我们进行处理的时候要 代换法 方法讲解 主要就是猜测答案的形式 我们只在乎 n 在无穷大的时候成立就行 关于答案的形式&#xff0c;我发现最后能够是 n log n 的形式的…

计算机网络简述

前言 计算机网路是一个很庞大的话题。在此我仅对其基础概述以及简单应用进行陈述。后续或有补充以形成完善的计算机网络知识体系。 一.计算机网络的定义 根据百度词条的描述&#xff0c;计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过…

微信小程序开发系列-03全局配置中的“window”和“tabBar”

本文继续学习下全局配置中的“window”和“tabBar”。 window 用于设置小程序的导航栏、标题、窗口颜色等。&#xff08;吐槽一句&#xff0c;官网这里的属性描述真的让人看不懂&#xff0c;只有靠自己实际运行调试才能知道是什么意思。&#xff09; 导航栏 设置导航栏背景色…

TCP协议工作原理及实战(一)

实战项目目标&#xff1a; ui搭建&#xff1a;clientconnect 客户端连接 clientdisconnect 客户端断开 socketreaddate 使用套接字传输数据 newconnection新的连接 获取本机的IP地址&#xff1a; 获取本机的ip地址可以参考前面的QT网络编程协议 将得到的ip地址放入combox中…

Flutter详解及案例代码

概念 Flutter是由Google开发的开源UI框架&#xff0c;旨在快速构建高质量的移动应用程序。与传统的移动应用开发方式不同&#xff0c;Flutter使用单一代码库构建应用程序&#xff0c;可以同时在iOS和Android上运行。 Flutter的核心是使用Dart语言编写的&#xff0c;并且具有自…

在killercoda中的一次apiserver异常追查思路

笔者&#xff1a; 最近在准备cks考试&#xff0c; 然后又发现了killercoda这个能够提供模拟考试环境的平台。它提供了很棒的引导&#xff0c;教你一步步追查问题&#xff0c;形成一整套追查思路&#xff0c;我觉得很不错&#xff0c;特此分享。 准备工作 首先还是需要养成配置…

亲测解决,nacos下线失败!

场景重现 当多个开发者共同投入一个项目的时候&#xff0c;通常会出现一个项目同时启动&#xff0c;调用接口调试工具共同测试的接口开发情况的情形&#xff1b;为了保证测试环境的稳定性&#xff0c;我们一般不通过页面进行调试&#xff0c;这时我们会采用在nacos服务中&…

Java Web Day07-08_Layui

1. Layui概念介绍 layui&#xff08;谐音&#xff1a;类 UI) 是一套开源的 Web UI 解决方案&#xff0c;采用自身经典的模块化规范&#xff0c;并遵循原生 HTML/CSS/JS 的开发方式&#xff0c;极易上手&#xff0c;拿来即用。其风格简约轻盈&#xff0c;而组件优雅丰盈&#x…

《C++避坑神器·二十四》简单搞懂json文件的读写之根据键值对读写Json

c11 json解析库nlohmann/json.hpp文件整个代码由一个头文件组成 json.hpp&#xff0c;没有子项目&#xff0c;没有依赖关系&#xff0c;没有复杂的构建系统&#xff0c;使用起来非常方便。 json.hpp库在文章末尾下载 读写主要有两种方式&#xff0c;第一种根据键值对读写&…

Linux 与 Shell

Linux系统的四部分&#xff1a;Linux系统的核心是内核。内核主要负责四种功能&#xff1a; 系统内存管理 操作系统内核的主要功能之一&#xff1a;内存管理。&#xff08;物理内存 虚拟内存&#xff09;内核通过硬盘上称为交换空间&#xff08;swap space&#xff09;的存储区…

知识付费网站搭建不再神秘,小白也能轻松掌握

产品服务 线上线下课程传播 线上线下活动管理 项目撮合交易 找商机找合作 一对一线下交流 企业文化宣传 企业产品销售 更多服务 实时行业资讯 动态学习交流 分销代理推广 独立知识店铺 覆盖全行业 个人IP打造 独立小程序 私域运营解决方案 公域引流 营销转化…

mySQL事务与存储引擎

目录 mySQL事务 1.事务的概念 2.事务的ACID特点 3.多客户端同时访问一个表时&#xff0c;出现的一致性问题 4.事务的隔离级别 5.事务的隔离级别作用范围 查询全局事务隔离级别 设置全局事务隔离级别 ​编辑查询会话事务隔离级别 设置会话事务隔离级别 6.事务控制语句…

Python爬虫中文乱码处理实例代码解析

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python爬虫中文乱码处理实例代码解析。全文2800字&#xff0c;阅读大约8分钟 在进行网络数据抓取时&#xff0c;常常会遇到中文乱码的问题&#xff0c;这可能导致数据无法正…

ts相关笔记(extends、infer、Pick、Omit)

最近刷了本ts小册&#xff0c;对一些知识点做下笔记。 extends extends 是一个关键字&#xff0c;用于对类型参数做一些约束。 A extends B 意味着 A 是 B 的子类型&#xff0c;比如下面是成立的 ‘abc’ extends string599 extends number 看下面例子&#xff1a; type …

智能优化算法应用:基于卷积优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于卷积优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于卷积优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.卷积优化算法4.实验参数设定5.算法结果6.…
最新文章