福州大学《嵌入式系统综合设计》实验四:边缘检测

一、实验目的

BMCV 提供了一套基于 Sophon AI 芯片优化的机器视觉库,通过利用芯片的 TPU 和 VPP模块,可以完成色彩空间转换、尺度变换、仿射变换、透射变换、线性变换、画框、JPEG 编解码、BASE64 编解码、NMS、排序、特征匹配等操作。

本实验的目的是掌握算能的BMCV接口使用方法,掌握bmcv_sobel,bmcv_canny边缘检测函数的使用方法。

二、实验内容

基于套接字、多线程、同步锁机制实现多媒体文件的收发;

发送端Ubuntu的PC机读取文件,每1024个字节组成一个包通过TCP报文发送到接收端;接收端SE5上启动2个线程,线程1接收报文并将报文存入缓存;线程2通过缓存读取报文存入文件中;要求线程1和线程2之间通过同步锁进行线程同步。

  1. 编写代码,通过OpenCV读取图片文件,并调用BMCV的bmcv_sobel、bmcv_canny函数来实现对图片的边缘检测,最后输出检测结果。
  2. 直接利用OpenCV的边缘检测接口,实现边缘检测功能;
  3. 对比OpenCV与BMCV边缘检测所需要的时间;

三、开发环境

开发主机:Ubuntu 22.04 LTS

硬件:算能SE5

本地如果有SE5硬件,则可以PC机作为客户端,SE5作为服务器端。本地如果没有SE5硬件,只有云空间,则可以直接将客户端和服务器端都通过云空间实现,机在云空间的SE5模拟环境中实现。

四、实验器材

开发主机 + 云平台

**五、**实验过程与结论

5.1 BMCV****关键函数解析

请参考算能BMCV开发资料:《BMCV User Guide》,也可以通过以下网址下载:

https://doc.sophgo.com/docs/2.7.0/docs_latest_release/bmcv/BMCV_User_Guide_zh.pdf

OpenCV的开发资料可参考《OpenCV官方文档》

算能BMCV提供了bmcv_image_sobel和bmcv_image_canny函数用于进行边缘检测。

bmcv_image_sobel:

cpp 复制代码
bm_status_t bmcv_image_sobel (
bm_handle_t handle,       //BMCV句柄
bm_image input,           //输入的BMI图片(待处理)
bm_image output,          //输出的BMI图片(处理结果)
int dx,                   //x 方向上的差分阶数
int dy)                   //y 方向上的差分阶数

具体函数接口说明如下:

(1)第二个参数和第三个参数图像的格式为bm_image,bm_image 需要外部调用 bmcv_image_creat创建。image 内存可以使用 bm_image_alloc_dev_mem 或者 bm_image_copy_host_to_device来开辟新的内存,或者使用 bmcv_image_attach 来 attach 已有的内存。

(2)dx, dy取值皆为1或0。 其中,dx=1,dy=0,表示计算X方向的导数,检测出的是垂直方向上的边缘;dx=0,dy=1,表示计算Y方向的导数,检测出的是垂直方向上的边缘。

(3) Sobel 核的大小,必须是-1,1,3,5 或7。其中特殊地,如果是-1 则使用3×3 Scharr 滤波器,如果是1 则使用3×1 或者1×3 的核。默认值为3。scale 为对求出的差分结果乘以的系数,默认值为1。Delta为在输出最终结果之前加上该偏移量,默认值为0。通常不需要对scale和Delta进行设置。

bmcv_image_canny:

cpp 复制代码
bm_status_t bmcv_image_canny (
bm_handle_t handle,
bm_image input,
bm_image output,
float threshold1,
float threshold2,
int aperture_size = 3,
bool l2gradient = false);

具体函数接口说明如下:

(1)第二个参数和第三个参数图像的格式为bm_image,bm_image 需要外部调用 bmcv_image_create 创建。image 内存可以使用 bm_image_alloc_dev_mem 或者 bm_image_copy_host_to_device来开辟新的内存,或者使用 bmcv_image_attach 来 attach 已有的内存。

(2)threshold1 和threshold2 为双阈值法的第一、第二个阈值。aperture_size 为 其中Sobel 核的大小,目前仅支持3。l2gradient 表示是否使用L2 范数来求图像梯度, 默认值为false,默认为由L1范数来求解图像梯度。

注意,BMCV的函数接口都是基于BMI格式进行图像处理。如上面的函数说明,其中第二个参数和第三个参数都是基于bm_image格式的。因此,需要首先通过OpenCV读取图片,并将图片格式转换为BMI格式后,才可以调用bmcv_image_sobel和bmcv_image_canny函数进行边缘检测。

本实验及实验5,实验6,实验7中使用BMCV相关函数的基本处理流程如下图所示,仅需调整红框模块中所调用的API即可实现不同实验功能:

图4-1 实验流程框图

首先,本实例为了利用BMCV接口,需要引用相关的BMCV相关头文件:

cpp 复制代码
#include "bmcv_api.h"

创建Mat类对象并读取图片数据:

cpp 复制代码
# 创建OpenCV类对象
cv::Mat Input,Out;
# 读取第二个命令行参数存入mat对象中(读取数据)
Input = cv::imread(argv[1], 0);

注意,这里OpenCV类读取到的图片文件输出的格式是MAT格式,而BMCV处理的图片是bm_image格式,即BMCV对象。因此,我们需要先创建BMCV对象,然后将OpenCV类读取到的图片通过toBMI接口转换为BMCV对象。

cpp 复制代码
# 创建BMCV对象
bm_image input, output;
bm_image_create(handle,height,width,FORMAT_GRAY,DATA_TYPE_EXT_1N_BYTE,&input);
# 以下是c++智能指针:划分一块内存区域并获取其信息
std::unique_ptr<unsigned char[]> src_data(new unsigned char[width * height]);
std::unique_ptr<unsigned char[]> res_data(new unsigned char[width * height]);

BMCV对象操作要求,在对象创建后,需要为该对象申请内部管理内存。如下函数所示:

cpp 复制代码
bm_image_alloc_contiguous_mem(1, &input);
bm_image_alloc_contiguous_mem(1, &output);

也可以通过bm_image_alloc_dev_mem(input)函数申请内存:

cpp 复制代码
bm_image_alloc_dev_mem(input)
bm_image_alloc_dev_mem(output);

然后通过toBMI函数将OpenCV读取的图片mat类数据转化为BMCV类数据,再调用bmcv_image_sobel函数进行处理:

cpp 复制代码
cv::bmcv::toBMI(Input,&input);
# Sobel边缘检测
bmcv_image_sobel(handle, input, output, 0, 1)

需要注意的是这里用了toBMI函数实际内部做了一个内存同步的操作。也就是OpenCV读取的mat格式图片实际处于系统内存中,通过toBMI转换后同步到设备内存中。这里也可以通过bm_image_copy_host_to_device函数完成内存的同步。具体见上述的《BMCV User Guide》110页中的示例代码所采用的方法。

将处理结果转化为mat数据格式保存

cpp 复制代码
cv::bmcv::toMAT(&output, Out);
cv::imwrite("out.jpg", Out);

销毁内存

cpp 复制代码
bm_image_free_contiguous_mem(1, &input);
bm_image_free_contiguous_mem(1, &output);
bm_image_destroy(input);
bm_image_destroy(output);
bm_dev_free(handle);

综上,我们可以得到利用BMCV sobel函数进行图像边缘检测的关键代码如下:

cpp 复制代码
#include <iostream>
#include <vector>
#include "bmcv_api.h"
#include "common.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <memory>

using namespace cv;
using namespace std;

int main(int argc, char *argv[]) {	
bm_handle_t handle;					  //获取句柄 
	bm_dev_request(&handle, 0);
	int width =  600;					   	  //定义图片数据 
	int height = 600;
	cv::Mat Input,Out,Test; 				   
	Input = cv::imread(argv[1], 0);	      //opencv读取图片,通过命令行参数传入
		
	// 智能指针获取分配内存数据 
	std::unique_ptr<unsigned char[]> src_data(
		  new unsigned char[width * height]);
	std::unique_ptr<unsigned char[]> res_data(
		  new unsigned char[width * height]);

	// bmcv处理 
	bm_image input, output;
	bm_image_create(handle,height,width,FORMAT_GRAY, DATA_TYPE_EXT_1N_BYTE,&input);
	bm_image_alloc_contiguous_mem(1, &input, 1); 	// 分配device memory 
	unsigned char * input_img_data = src_data.get();
	bm_image_copy_host_to_device(input, (void **)&input_img_data);
    bm_image_create(handle,height,width,FORMAT_GRAY,DATA_TYPE_EXT_1N_BYTE,&output);
	bm_image_alloc_contiguous_mem(1, &output, 1);	
		
	cv::bmcv::toBMI(Input,&input);                  //自动进行内存同步
    // bmcv图像处理:ca
	if (BM_SUCCESS != bmcv_image_sobel(handle, input, output, 0, 1)) {
		std::cout << "bmcv sobel error !!!" << std::endl;
		bm_image_destroy(input);
		bm_image_destroy(output);
		bm_dev_free(handle);
		return -1;
	}// 将输出结果转成mat数据并保存 
	cv::bmcv::toMAT(&output, Out);
	cv::imwrite("out.jpg", Out);
		
	bm_image_free_contiguous_mem(1, &input);
	bm_image_free_contiguous_mem(1, &output);
	bm_image_destroy(input);
	bm_image_destroy(output);
	bm_dev_free(handle);
    return 0; 
}

如果采用bmcv_image_canny函数进行边缘检测,只需要将上述代码中的bmcv_image_sobel函数改为bmcv_image_canny函数即可:

cpp 复制代码
// bmcv图像处理:canny 
if (BM_SUCCESS != bmcv_image_canny(handle, input, output, 0, 200)) {
	    td::cout << "bmcv canny error !!!" << std::endl;
		bm_image_destroy(input);
		bm_image_destroy(output);
		bm_dev_free(handle);
		exit(-1);
}

编写makfile文件:

bash 复制代码
DEBUG        ?= 0
PRODUCTFORM  ?= soc
BM_MEDIA_ION ?= 0

INSTALL_DIR    ?= release

//注意:这个地方一定要根据自己的目录路径进行设置
top_dir :=../../..

ifeq ($(PRODUCTFORM),x86) # pcie mode
    CROSS_CC_PREFIX = x86_64-linux-
else # pcie_arm64 and soc mode
    CROSS_CC_PREFIX = aarch64-linux-gnu-
endif

CC  = $(CROSS_CC_PREFIX)gcc
CXX = $(CROSS_CC_PREFIX)g++

CPPFLAGS := -std=gnu++11 -fPIC -Wall -Wl,--fatal-warning
ifeq ($(DEBUG), 0)
    CPPFLAGS += -O2
else
    CPPFLAGS += -g
endif

# NATIVE API SDK
NATIVE_SDK_HEADERS:=-I$(top_dir)/include/decode
NATIVE_SDK_LDFLAGS:=-L$(top_dir)/lib/decode/${PRODUCTFORM}
NATIVE_SDK_LDLIBS :=-lbmion -lbmjpulite -lbmjpuapi -lbmvpulite -lbmvpuapi -lbmvideo -lbmvppapi -lyuv

# FFMPEG SDK
FF_SDK_HEADERS := -I$(top_dir)/include/ffmpeg
FF_SDK_LDFLAGS := -L$(top_dir)/lib/ffmpeg/$(PRODUCTFORM)
FF_SDK_LDLIBS  := -lavcodec -lavformat -lavutil -lswresample -lswscale

# OpenCV SDK
OCV_SDK_HEADERS := -I$(top_dir)/include/opencv/opencv4
OCV_SDK_LDFLAGS := -L$(top_dir)/lib/opencv/$(PRODUCTFORM)
OCV_SDK_LDLIBS  := -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_videoio

# BMCV SDK
BMCV_SDK_HEADERS := -I$(top_dir)/include/bmlib
BMCV_SDK_LDFLAGS := -L$(top_dir)/lib/bmnn/$(PRODUCTFORM)
ifeq (${PRODUCTFORM}, x86)
BMCV_SDK_LDFLAGS :=  -L$(top_dir)/lib/bmnn/pcie
endif
BMCV_SDK_LDLIBS  := -lbmcv -lbmlib

CPPFLAGS += $(NATIVE_SDK_HEADERS) $(FF_SDK_HEADERS) $(OCV_SDK_HEADERS) $(BMCV_SDK_HEADERS)
LDFLAGS  := $(NATIVE_SDK_LDFLAGS) $(FF_SDK_LDFLAGS) $(OCV_SDK_LDFLAGS)

LDLIBS   := $(NATIVE_SDK_LDLIBS) $(FF_SDK_LDLIBS) $(OCV_SDK_LDLIBS) $(BMCV_SDK_LDLIBS) -lpthread -lstdc++

TARGET=bmcv_sobel
MAKEFILE=Makefile
ALLOBJS=*.o
ALLDEPS=*.dep
RM=rm -rf
CP=cp -f

SOURCES := bmcv_sobel.cpp

OBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES))

.phony: all clean

all: $(TARGET)

$(TARGET): $(OBJECTPATHS)
	$(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)

install: $(TARGET)
	install -d $(INSTALL_DIR)/bin
	install $(TARGET) $(INSTALL_DIR)/bin

uninstall:
	$(RM) $(INSTALL_DIR)/bin/$(TARGET) 

clean:
	$(RM) $(TARGET)
	$(RM) $(ALLDEPS)
	$(RM) $(ALLOBJS)

bmcv_sobel.o : bmcv_sobel.cpp $(MAKEFILE)
	$(CXX) $(CPPFLAGS) -c $< -o $@ -MD -MF $(@:.o=.dep)
LDLIBS   := $(NATIVE_SDK_LDLIBS) $(FF_SDK_LDLIBS) $(OCV_SDK_LDLIBS) $(BMCV_SDK_LDLIBS) -lpthread -lstdc++

TARGET=bmcv_sobel
MAKEFILE=Makefile
ALLOBJS=*.o
ALLDEPS=*.dep
RM=rm -rf
CP=cp -f

SOURCES := bmcv_sobel.cpp

OBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES))

.phony: all clean

all: $(TARGET)

$(TARGET): $(OBJECTPATHS)
	$(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)

install: $(TARGET)
	install -d $(INSTALL_DIR)/bin
	install $(TARGET) $(INSTALL_DIR)/bin

uninstall:
	$(RM) $(INSTALL_DIR)/bin/$(TARGET) 

clean:
	$(RM) $(TARGET)
	$(RM) $(ALLDEPS)
	$(RM) $(ALLOBJS)

bmcv_sobel.o : bmcv_sobel.cpp $(MAKEFILE)
	$(CXX) $(CPPFLAGS) -c $< -o $@ -MD -MF $(@:.o=.dep)
5.2 BMCV 执行结果

向云平台或SE5上传待检测的图片,并执行如下代码:

cpp 复制代码
./bmcv_sobel greycat.jpeg bmcv

运行程序后,对同一张图片进行处理所得出的sobel和canny边缘检测的两个结果:

Sobel:

Canny:

如上图所示,两种边缘检测都能大概检测出图像边缘,但精细程度不同。在实际应用时可选择自己所适合的方式选择合适的边缘检测方式。

5.3 OpenCV关键函数解析

OpenCV也提供了Sobel和Canny边缘检测算子,具体函数原型如下:

cpp 复制代码
void cv::Canny(InputArray image,
    OutputArray  edges,
    double  threshold1,
    double 	threshold2,
    int 	apertureSize = 3,
    bool    L2gradient = false 
)

	
void cv::Sobel(InputArray src,
    OutputArray dst, //输出图像,与输入图像src具有相同的尺寸和通道数,数据类型由第三个参数ddepth控制。
    int ddepth,    // ddepth:输出图像的数据类型(深度), 为-1时,输出图像的数据类型自动选择。
    int dx,
    int dy,
    int ksize = 3,
    double scale = 1,
    double delta = 0,
    int borderType = BORDER_DEFAULT) //像素外推法选择标志,默认为//BORDER_DEFAULT,表示不包含边界值倒序填充。

同名参数的含义与BMCV中参数含义相同。OpenCV下,不需要进行BMI转换,直接可以将读取到的MAT格式的图片通过sobel 和Canny接口进行处理。如下图所示:

cpp 复制代码
//头文件
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
....
//关键代码
cv::Mat srcImage = cv::imread(argv[1], 1);
cv::Mat grayImage; 
cv::Mat srcImage1 = srcImage.clone();
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
Mat dstImage, edge;
dstImage.create(srcImage1.size(), srcImage1.type());
dstImage = Scalar::all(0);
srcImage1.copyTo(dstImage, edge);

5.4 硬件加速性能对比

此外,在算能云平台上,基于BMCV的sobel函数,因为使用了硬件加速,所以可以提升速率。

为验证执行程序所需的时间,须在运行时通过time命令来实现,如下图所示:

第一张图为用OpenCV的sobel函数所需时间,第二张图为用bmcv的sobel函数时所需的时间。经硬件加速后,程序所需的运行时间明显减少。

相关推荐
yaoxin5211236 分钟前
第三十二章 UDP 客户端 服务器通信
服务器·网络协议·udp
筱小虾米2 小时前
解决SSL VPN客户端一直提示无法连接服务器的问题
运维·服务器
honey ball3 小时前
LLC与反激电路设计【学习笔记】
单片机·嵌入式硬件·学习
wanhengwangluo7 小时前
裸金属服务器能够帮助企业解决哪些问题?
运维·服务器
titxixYY7 小时前
SElinux
linux·运维·服务器
聚名网8 小时前
手机无法连接服务器1302什么意思?
运维·服务器·智能手机
代码欢乐豆9 小时前
软件工程第13章小测
服务器·前端·数据库·软件工程
Graceful_scenery9 小时前
STM32F103外部中断配置
stm32·单片机·嵌入式硬件
望获linux10 小时前
在 ARM 平台上如何实现Linux系统的1秒启动
linux·服务器·开发语言·数据库·操作系统·嵌入式操作系统·arm平台
先天打工圣体的男人10 小时前
Linux中安装InfluxDB
linux·运维·服务器