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

一、实验目的

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

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

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相关头文件:

#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 Guide110页中的示例代码所采用的方法。

将处理结果转化为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>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图像处理:caif (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函数即可:

// 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文件:

DEBUG        ?= 0
PRODUCTFORM  ?= soc
BM_MEDIA_ION ?= 0INSTALL_DIR    ?= release//注意:这个地方一定要根据自己的目录路径进行设置
top_dir :=../../..ifeq ($(PRODUCTFORM),x86) # pcie modeCROSS_CC_PREFIX = x86_64-linux-
else # pcie_arm64 and soc modeCROSS_CC_PREFIX = aarch64-linux-gnu-
endifCC  = $(CROSS_CC_PREFIX)gcc
CXX = $(CROSS_CC_PREFIX)g++CPPFLAGS := -std=gnu++11 -fPIC -Wall -Wl,--fatal-warning
ifeq ($(DEBUG), 0)CPPFLAGS += -O2
elseCPPFLAGS += -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 -lbmlibCPPFLAGS += $(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 -fSOURCES := bmcv_sobel.cppOBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES)).phony: all cleanall: $(TARGET)$(TARGET): $(OBJECTPATHS)$(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)install: $(TARGET)install -d $(INSTALL_DIR)/bininstall $(TARGET) $(INSTALL_DIR)/binuninstall:$(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 -fSOURCES := bmcv_sobel.cppOBJECTPATHS:=$(patsubst %.cpp,%.o,$(SOURCES)).phony: all cleanall: $(TARGET)$(TARGET): $(OBJECTPATHS)$(CC) -o $@ $(OBJECTPATHS) $(LDFLAGS) $(LDLIBS)install: $(TARGET)install -d $(INSTALL_DIR)/bininstall $(TARGET) $(INSTALL_DIR)/binuninstall:$(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上传待检测的图片,并执行如下代码:

./bmcv_sobel greycat.jpeg bmcv

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

Sobel:

Canny:

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

5.3 OpenCV关键函数解析

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

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 硬件加速性能对比

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

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

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

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

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

相关文章

C++中的内存管理

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2023年11月21日 &#x1f4e8; 博主码云地址&#xff1a;博主码云地址 &#x1f4d5;参考书籍&…

基于C#实现KMP算法

一、BF 算法 如果让你写字符串的模式匹配&#xff0c;你可能会很快的写出朴素的 bf 算法&#xff0c;至少问题是解决了&#xff0c;我想大家很清楚的知道它的时间复杂度为 O&#xff08;MN&#xff09;&#xff0c;原因很简单&#xff0c;主串和模式串失配的时候&#xff0c;我…

电脑便签工具推荐哪个?电脑上好用的便签软件使用哪一款

对于职场办公人士来讲&#xff0c;一款好用的电脑便签工具可以给日常工作带来极大的便利&#xff0c;如果您的日常工作离不开电脑工具&#xff0c;您就会知晓电脑便签工具在日常工作中的重要&#xff0c;电脑便签通常以一个小的窗口呈现在电脑桌面上&#xff0c;记录一些工作中…

KVM网络环境下vlan和trunk的理解

vmware exsi 平台&#xff0c;虚拟交换机管理界面的上行链路是什么意思 VMware ESXi中的虚拟交换机管理界面中的“上行链路”&#xff08;uplinks&#xff09;是指虚拟交换机连接到物理网络的物理网络适配器。在ESXi中&#xff0c;虚拟交换机&#xff08;vSwitch&#xff09;用…

JAXB:根据Java文件生成XML schema文件

说明 JAXB有个schemagen脚本&#xff0c;可以根据Java文件生成XML schema。这个工具在JAXB独立发布包中有&#xff0c;可以从官网下载JAXB的独立发布包&#xff1a; https://eclipse-ee4j.github.io/jaxb-ri/ 示例 使用schemagen -d <path> <java files>格式 …

安装rabbitMQ

安装rabbitMQ linux安装rabbitMQ centos7下 需要先安装JDK 安装Erlang 需对应版本 安装对应版本的rabbitMQ 参考&#xff1a;CentOS7安装RabbitMQ简单实用教程_普通网友的博客-CSDN博客 启动 systemctl start rabbitmq-server出现的问题 启动失败 Job for rabbitmq-ser…

顺序表和链表

1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线…

最新最全系列之Selenium:传入webdriver驱动的新方法 Service()函数;以前的executable_path报警告,即将弃用

传入webdriver驱动的新方法 Service()函数&#xff1b;以前的executable_path报警告&#xff0c;即将弃用 以前的方法 举例&#xff1a;webdriver.Chrome(executable_pathdriver_path)&#xff1b;看提示警告&#xff0c;提示该方法即将被弃用&#xff1b;如下图&#xff1a; …

mysql底层是如何存放数据的

总览 首先总的来说&#xff0c;分为四个层级&#xff0c;行页区段。行就是数据库里的一行数据。 但一次从磁盘读进内存的数据量是一页&#xff08;页是读写的单位&#xff0c;默认16KB一页&#xff09;&#xff0c;页分很多种类&#xff0c;例如数据页、溢出页、undo日志页。 …

工业领域的设备“监测”和“检测”有何区别?

在工业领域中&#xff0c;设备的监测和检测是关键的运维活动&#xff0c;它们在保障设备可靠性和生产效率方面发挥着重要作用。尽管这两个术语经常被人们混为一谈&#xff0c;但它们在含义和应用上存在一些关键区别。 "监测"与"检测"的概念 1. 监测&#…

使用wxPython和PyMuPDF合并PDF文档并自动复制到剪贴板

导语&#xff1a;处理大量的PDF文档可能会变得复杂和耗时。但是&#xff0c;使用Python编程和一些强大的库&#xff0c;如wxPython和PyMuPDF&#xff0c;可以使这个任务变得简单而高效。本文将详细解释一个示例代码&#xff0c;展示如何使用这些库来创建一个可以选择文件夹中的…

C#学习相关系列之Linq常用方法---排序(一)

一、构建数据 public class Student_1{public int ID { get; set; }public string Name { get; set; }public int Chinese { get; set; }public int Math { get; set; }public int English { get; set; }public override string ToString(){return string.Format("ID:{0},…

从零开始 通义千问大模型本地化到阿里云通义千问API调用

从零开始 通义千问大模型本地化到阿里云通义千问API调用 一、通义千问大模型介绍 何为“通义千问”&#xff1f; “通义千问大模型”是阿里云推出的一个超大规模的语言模型&#xff0c;具有强大的归纳和理解能力&#xff0c;可以处理各种自然语言处理任务&#xff0c;包括但…

springboot多环境配置

前言 在实际项目研发中&#xff0c;需要针对不同的运行环境&#xff0c;如开发环境、测试环境、生产环境等&#xff0c;每个运行环境的数据库…等配置都不相同&#xff0c;每次发布测试、更新生产都需要手动修改相关系统配置。这种方式特别麻烦&#xff0c;费时费力&#xff0…

ProtoBuf的使用

目录 1.创建.proto文件 1.1文件规范 1.2添加注释 1.3指定proto3语法 1.4package声明符 1.5定义消息(message) 1.6定义消息字段 2.编译contacts.proto文件 3.序列化与反序列化的使用 1.创建.proto文件 1.1文件规范 • 创建.proto文件时&#xff0c;⽂件命名应该使用全…

利用互斥锁解决缓存击穿问题

核心思路&#xff1a;相较于原来从缓存中查询不到数据后直接查询数据库而言&#xff0c;现在的方案是 进行查询之后&#xff0c;如果从缓存没有查询到数据&#xff0c;则进行互斥锁的获取&#xff0c;获取互斥锁后&#xff0c;判断是否获得到了锁&#xff0c;如果没有获得到&am…

DNS如何在Windows NIC配置多个DNS服务器时完成DNS解析查询

使用多个 DNS 服务器 IP 配置 DNS 客户端会增加 DNS 基础结构的容错。所以建议给Windows客户端使用多个 DNS 服务器。 文章目录 什么是DNS&#xff1f;在 NIC 上配置单个 DNS 服务器时&#xff0c;DNS 客户端的解析过程在 NIC 上配置两个 DNS 服务器时&#xff0c;DNS 客户端的…

【Docker】从零开始:3.Docker运行原理

【Docker】从零开始&#xff1a;3.Docker运行原理 Docker 工作原理Docker与系统的关系Docker平台架构图解 Docker 工作原理 Docker与系统的关系 Docker 是一个 Client-Server 结构的系统&#xff0c;Docker 守尹进程运行在王机上&#xff0c; 然后通过 Socket 连接从各尸端坊…

Vue3-shallowRef 和 shallowReactive函数(浅层次的响应式)

Vue3-shallowRef 和 shallowReactive函数&#xff08;浅层次的响应式&#xff09; shallowRef函数 功能&#xff1a;只给基本数据类型添加响应式。如果是对象&#xff0c;则不会支持响应式&#xff0c;层成也不会创建Proxy对象。ref和shallowRef在基本数据类型上是没有区别的…

第1关:图的邻接表存储及求邻接点操作

任务要求参考答案评论2 任务描述相关知识编程要求测试说明 任务描述 本关任务&#xff1a;要求从文件输入顶点和边数据&#xff0c;包括顶点信息、边、权值等&#xff0c;编写程序实现以下功能。 1&#xff09;构造图G的邻接表和顶点集&#xff0c;即图的存储结构为邻接表。 …