一、实验目的
BMCV提供了一套基于Sophon AI芯片优化的机器视觉库,通过利用芯片的TPU 和VPP模块,可以完成色彩空间转换、尺度变换、仿射变换、透射变换、线性变换、画框、JPEG编解码、BASE64 编解码、NMS、排序、特征匹配等操作。
本实验的目的是掌握算能的BMCV接口使用方法,掌握bmcv_sobel,bmcv_canny边缘检测函数的使用方法。
二、实验内容
- 编写代码,通过OpenCV读取图片文件,并调用BMCV的bmcv_sobel、bmcv_canny函数来实现对图片的边缘检测,最后输出检测结果。
- 直接利用OpenCV的边缘检测接口,实现边缘检测功能;
- 对比OpenCV与BMCV边缘检测所需要的时间;
三、开发环境
开发主机:Ubuntu 22.04 LTS
硬件:算能SE5
本地如果有SE5硬件,则可以PC机作为客户端,SE5作为服务器端。本地如果没有SE5硬件,只有云空间,则可以直接将客户端和服务器端都通过云空间实现,机在云空间的SE5模拟环境中实现。
四、实验器材
开发主机 + 云平台
五、实验过程与结论
关键函数解析
请参考算能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:
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 方向上的差分阶数
具体函数接口说明如下:
bmcv_image_canny:
/**
* @brief 对输入图像进行Canny边缘检测。
*
* @param handle BM库句柄,用于管理BM库资源和状态。
* @param input 输入图像,要求为bm_image类型。
* @param output 输出图像,存储Canny边缘检测结果,也为bm_image类型。
* @param threshold1 Canny边缘检测的低阈值。
* @param threshold2 Canny边缘检测的高阈值。
* @param aperture_size Sobel算子的孔径大小,默认为3。
* @param l2gradient 是否使用L2范数作为梯度大小的测量,默认为false,即使用L1范数。
*
* @return bm_status_t 返回BM库的状态码,用于指示函数执行结果。
*/
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);
具体函数接口说明如下:
本实验及实验5,实验6,实验7中使用BMCV相关函数的基本处理流程如下图所示,仅需调整红框模块中所调用的API即可实现不同实验功能:
图4-1 实验流程框图
首先,本实例为了利用BMCV接口,需要引用相关的BMCV相关头文件:
#include "bmcv_api.h"
创建Mat类对象并读取图片数据:
# 创建OpenCV类对象
cv::Mat Input,Out;
# 读取第二个命令行参数存入mat对象中(读取数据)
Input = cv::imread(argv[1], 0);
注意,这里OpenCV类读取到的图片文件输出的格式是MAT格式,而BMCV处理的图片是bm_image格式,即BMCV对象。因此,我们需要先创建BMCV对象,然后将OpenCV类读取到的图片通过toBMI接口转换为BMCV对象。
# 创建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对象操作要求,在对象创建后,需要为该对象申请内部管理内存。如下函数所示:
bm_image_alloc_contiguous_mem(1, &input);
bm_image_alloc_contiguous_mem(1, &output);
也可以通过bm_image_alloc_dev_mem(input)函数申请内存:
bm_image_alloc_dev_mem(input)
bm_image_alloc_dev_mem(output);
然后通过toBMI函数将OpenCV读取的图片mat类数据转化为BMCV类数据,再调用bmcv_image_sobel函数进行处理:
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数据格式保存
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);
综上,我们可以得到利用BMCV sobel函数进行图像边缘检测的完整的关键代码如下:
#include <iostream>
#include <vector>
#include "bmcv_api.h"
#include "common.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <memory>
#include <opencv2/opencv.hpp>
// 避免在全局命名空间中使用using namespace
// 通过使用cv::来限定OpenCV相关标识符
// 避免潜在的命名冲突
using namespace cv;
using namespace std;
int main(int argc, char *argv[]) {
// 检查命令行参数
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <image_file_path>" << std::endl;
return EXIT_FAILURE;
}
// 获取BM句柄
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 EXIT_SUCCESS;
}
如果采用bmcv_image_canny函数进行边缘检测,只需要将上述代码中的bmcv_image_sobel函数改为bmcv_image_canny函数即可:
// 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文件(文件名为Makefile):
DEBUG ?= 0
PRODUCTFORM ?= soc
BM_MEDIA_ION ?= 0
INSTALL_DIR ?= release
# 注意:这个地方一定要根据自己的目录路径进行设置
# 设置 top_dir 为 Sophon SDK 的根目录
top_dir := /home/embedded-systems/sophonsdk_v3.0.0
ifeq ($(PRODUCTFORM),x86) # pcie 模式
CROSS_CC_PREFIX = x86_64-linux-
else # pcie_arm64 和 soc 模式
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) $(BMCV_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)
$(CXX) -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执行结果
直接输入make命令,即可编译文件产生bmcv_sobel程序(没有后缀)。
向云平台或SE5上传待检测的图片,并执行如下代码:
./bmcv_sobel greycat.jpeg bmcv
运行程序后,对同一张图片进行处理所得出的sobel和canny边缘检测的两个结果:
Sobel:
Canny:
如上图所示,两种边缘检测都能大概检测出图像边缘,但精细程度不同。在实际应用时可选择自己所适合的方式选择合适的边缘检测方式。
5.3 OpenCV关键函数解析
OpenCV也提供了Sobel和Canny边缘检测算子,具体函数原型如下:
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接口进行处理。如下图所示:
//头文件
#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函数时所需的时间。经硬件加速后,程序所需的运行时间明显减少。