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


    一、实验目的

    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_sobelbmcv_image_canny函数用于进行边缘检测。

    bmcv_image_sobel

    1. bm_status_t bmcv_image_sobel (
    2. bm_handle_t handle,       //BMCV句柄
    3. bm_image input,           //输入的BMI图片(待处理)
    4. bm_image output,          //输出的BMI图片(处理结果)
    5. int dx,                   //x 方向上的差分阶数
    6. 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

    1. bm_status_t bmcv_image_canny (
    2. bm_handle_t handle,
    3. bm_image input,
    4. bm_image output,
    5. float threshold1,
    6. float threshold2,
    7. int aperture_size = 3,
    8. 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相关头文件:

    #include "bmcv_api.h"

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

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

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

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

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

    1. bm_image_alloc_contiguous_mem(1, &input);
    2. bm_image_alloc_contiguous_mem(1, &output);

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

    1. bm_image_alloc_dev_mem(input)
    2. bm_image_alloc_dev_mem(output);

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

    1. cv::bmcv::toBMI(Input,&input);
    2. # Sobel边缘检测
    3. bmcv_image_sobel(handle, input, output, 0, 1)

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

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

    1. cv::bmcv::toMAT(&output, Out);
    2. cv::imwrite("out.jpg", Out);

    销毁内存

    1. bm_image_free_contiguous_mem(1, &input);
    2. bm_image_free_contiguous_mem(1, &output);
    3. bm_image_destroy(input);
    4. bm_image_destroy(output);
    5. bm_dev_free(handle);

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

    1. #include
    2. #include
    3. #include "bmcv_api.h"
    4. #include "common.h"
    5. #include "stdio.h"
    6. #include "stdlib.h"
    7. #include "string.h"
    8. #include
    9. using namespace cv;
    10. using namespace std;
    11. int main(int argc, char *argv[]) {
    12. bm_handle_t handle; //获取句柄
    13. bm_dev_request(&handle, 0);
    14. int width = 600; //定义图片数据
    15. int height = 600;
    16. cv::Mat Input,Out,Test;
    17. Input = cv::imread(argv[1], 0); //opencv读取图片,通过命令行参数传入
    18. // 智能指针获取分配内存数据
    19. std::unique_ptr<unsigned char[]> src_data(
    20. new unsigned char[width * height]);
    21. std::unique_ptr<unsigned char[]> res_data(
    22. new unsigned char[width * height]);
    23. // bmcv处理
    24. bm_image input, output;
    25. bm_image_create(handle,height,width,FORMAT_GRAY, DATA_TYPE_EXT_1N_BYTE,&input);
    26. bm_image_alloc_contiguous_mem(1, &input, 1); // 分配device memory
    27. unsigned char * input_img_data = src_data.get();
    28. bm_image_copy_host_to_device(input, (void **)&input_img_data);
    29. bm_image_create(handle,height,width,FORMAT_GRAY,DATA_TYPE_EXT_1N_BYTE,&output);
    30. bm_image_alloc_contiguous_mem(1, &output, 1);
    31. cv::bmcv::toBMI(Input,&input); //自动进行内存同步
    32. // bmcv图像处理:ca
    33. if (BM_SUCCESS != bmcv_image_sobel(handle, input, output, 0, 1)) {
    34. std::cout << "bmcv sobel error !!!" << std::endl;
    35. bm_image_destroy(input);
    36. bm_image_destroy(output);
    37. bm_dev_free(handle);
    38. return -1;
    39. }// 将输出结果转成mat数据并保存
    40. cv::bmcv::toMAT(&output, Out);
    41. cv::imwrite("out.jpg", Out);
    42. bm_image_free_contiguous_mem(1, &input);
    43. bm_image_free_contiguous_mem(1, &output);
    44. bm_image_destroy(input);
    45. bm_image_destroy(output);
    46. bm_dev_free(handle);
    47. return 0;
    48. }

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

    1. // bmcv图像处理:canny
    2. if (BM_SUCCESS != bmcv_image_canny(handle, input, output, 0, 200)) {
    3. td::cout << "bmcv canny error !!!" << std::endl;
    4. bm_image_destroy(input);
    5. bm_image_destroy(output);
    6. bm_dev_free(handle);
    7. exit(-1);
    8. }

    编写makfile文件:

    1. DEBUG ?= 0
    2. PRODUCTFORM ?= soc
    3. BM_MEDIA_ION ?= 0
    4. INSTALL_DIR ?= release
    5. //注意:这个地方一定要根据自己的目录路径进行设置
    6. top_dir :=../../..
    7. ifeq ($(PRODUCTFORM),x86) # pcie mode
    8. CROSS_CC_PREFIX = x86_64-linux-
    9. else # pcie_arm64 and soc mode
    10. CROSS_CC_PREFIX = aarch64-linux-gnu-
    11. endif
    12. CC = $(CROSS_CC_PREFIX)gcc
    13. CXX = $(CROSS_CC_PREFIX)g++
    14. CPPFLAGS := -std=gnu++11 -fPIC -Wall -Wl,--fatal-warning
    15. ifeq ($(DEBUG), 0)
    16. CPPFLAGS += -O2
    17. else
    18. CPPFLAGS += -g
    19. endif
    20. # NATIVE API SDK
    21. NATIVE_SDK_HEADERS:=-I$(top_dir)/include/decode
    22. NATIVE_SDK_LDFLAGS:=-L$(top_dir)/lib/decode/${PRODUCTFORM}
    23. NATIVE_SDK_LDLIBS :=-lbmion -lbmjpulite -lbmjpuapi -lbmvpulite -lbmvpuapi -lbmvideo -lbmvppapi -lyuv
    24. # FFMPEG SDK
    25. FF_SDK_HEADERS := -I$(top_dir)/include/ffmpeg
    26. FF_SDK_LDFLAGS := -L$(top_dir)/lib/ffmpeg/$(PRODUCTFORM)
    27. FF_SDK_LDLIBS := -lavcodec -lavformat -lavutil -lswresample -lswscale
    28. # OpenCV SDK
    29. OCV_SDK_HEADERS := -I$(top_dir)/include/opencv/opencv4
    30. OCV_SDK_LDFLAGS := -L$(top_dir)/lib/opencv/$(PRODUCTFORM)
    31. OCV_SDK_LDLIBS := -lopencv_core -lopencv_imgcodecs -lopencv_imgproc -lopencv_videoio
    32. # BMCV SDK
    33. BMCV_SDK_HEADERS := -I$(top_dir)/include/bmlib
    34. BMCV_SDK_LDFLAGS := -L$(top_dir)/lib/bmnn/$(PRODUCTFORM)
    35. ifeq (${PRODUCTFORM}, x86)
    36. BMCV_SDK_LDFLAGS := -L$(top_dir)/lib/bmnn/pcie
    37. endif
    38. BMCV_SDK_LDLIBS := -lbmcv -lbmlib
    39. CPPFLAGS += $(NATIVE_SDK_HEADERS) $(FF_SDK_HEADERS) $(OCV_SDK_HEADERS) $(BMCV_SDK_HEADERS)
    40. LDFLAGS := $(NATIVE_SDK_LDFLAGS) $(FF_SDK_LDFLAGS) $(OCV_SDK_LDFLAGS)
    41. LDLIBS := $(NATIVE_SDK_LDLIBS) $(FF_SDK_LDLIBS) $(OCV_SDK_LDLIBS) $(BMCV_SDK_LDLIBS) -lpthread -lstdc++
    42. TARGET=bmcv_sobel
    43. MAKEFILE=Makefile
    44. ALLOBJS=*.o
    45. ALLDEPS=*.dep
    46. RM=rm -rf
    47. CP=cp -f
    48. SOURCES := bmcv_sobel.cpp
    49. OBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES))
    50. .phony: all clean
    51. all: $(TARGET)
    52. $(TARGET): $(OBJECTPATHS)
    53. $(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)
    54. install: $(TARGET)
    55. install -d $(INSTALL_DIR)/bin
    56. install $(TARGET) $(INSTALL_DIR)/bin
    57. uninstall:
    58. $(RM) $(INSTALL_DIR)/bin/$(TARGET)
    59. clean:
    60. $(RM) $(TARGET)
    61. $(RM) $(ALLDEPS)
    62. $(RM) $(ALLOBJS)
    63. bmcv_sobel.o : bmcv_sobel.cpp $(MAKEFILE)
    64. $(CXX) $(CPPFLAGS) -c $< -o $@ -MD -MF $(@:.o=.dep)
    65. LDLIBS := $(NATIVE_SDK_LDLIBS) $(FF_SDK_LDLIBS) $(OCV_SDK_LDLIBS) $(BMCV_SDK_LDLIBS) -lpthread -lstdc++
    66. TARGET=bmcv_sobel
    67. MAKEFILE=Makefile
    68. ALLOBJS=*.o
    69. ALLDEPS=*.dep
    70. RM=rm -rf
    71. CP=cp -f
    72. SOURCES := bmcv_sobel.cpp
    73. OBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES))
    74. .phony: all clean
    75. all: $(TARGET)
    76. $(TARGET): $(OBJECTPATHS)
    77. $(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)
    78. install: $(TARGET)
    79. install -d $(INSTALL_DIR)/bin
    80. install $(TARGET) $(INSTALL_DIR)/bin
    81. uninstall:
    82. $(RM) $(INSTALL_DIR)/bin/$(TARGET)
    83. clean:
    84. $(RM) $(TARGET)
    85. $(RM) $(ALLDEPS)
    86. $(RM) $(ALLOBJS)
    87. bmcv_sobel.o : bmcv_sobel.cpp $(MAKEFILE)
    88. $(CXX) $(CPPFLAGS) -c $< -o $@ -MD -MF $(@:.o=.dep)
    5.2 BMCV执行结果

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

    ./bmcv_sobel greycat.jpeg bmcv

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

    Sobel:

    Canny:

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

    5.3 OpenCV关键函数解析

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

    1. void cv::Canny(InputArray image,
    2. OutputArray edges,
    3. double threshold1,
    4. double threshold2,
    5. int apertureSize = 3,
    6. bool L2gradient = false
    7. )
    8. void cv::Sobel(InputArray src,
    9. OutputArray dst, //输出图像,与输入图像src具有相同的尺寸和通道数,数据类型由第三个参数ddepth控制。
    10. int ddepth, // ddepth:输出图像的数据类型(深度), 为-1时,输出图像的数据类型自动选择。
    11. int dx,
    12. int dy,
    13. int ksize = 3,
    14. double scale = 1,
    15. double delta = 0,
    16. int borderType = BORDER_DEFAULT) //像素外推法选择标志,默认为//BORDER_DEFAULT,表示不包含边界值倒序填充。

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

    1. //头文件
    2. #include
    3. #include
    4. #include
    5. #include
    6. ....
    7. //关键代码
    8. cv::Mat srcImage = cv::imread(argv[1], 1);
    9. cv::Mat grayImage;
    10. cv::Mat srcImage1 = srcImage.clone();
    11. cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
    12. Mat dstImage, edge;
    13. dstImage.create(srcImage1.size(), srcImage1.type());
    14. dstImage = Scalar::all(0);
    15. srcImage1.copyTo(dstImage, edge);

    5.4 硬件加速性能对比

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

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

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

  • 相关阅读:
    什么是NLP-自然语言处理
    Python Selenium 执行 JavaScript
    PDM篇 | SOLIDWORKS 2024新功能
    football 篮球数据集-目标检测548张
    【前端】前端权限管理的实现方式:基于Vue项目的详细指南
    【开源】新生报到网站 JAVA+Vue.js+SpringBoot+MySQL
    分布式互斥
    echarts 容器自适应
    【Python】9*9乘法口诀表(while、for两种循环)
    TiDB 工具下载
  • 原文地址:https://blog.csdn.net/m0_52537869/article/details/134535641