自动驾驶多任务框架Hybridnets——同时处理车辆检测、可驾驶区域分割、车道线分割模型部署(C++/Python)

一、多感知任务

在移动机器人的感知系统,包括自动驾驶汽车和无人机,会使用多种传感器来获取关键信息,从而实现对环境的感知和物体检测。这些传感器包括相机、激光雷达、雷达、惯性测量单元(IMU)、全球导航卫星系统(GNSS)等。在自动驾驶和移动机器人的感知系统里面,相机的功能主要是周围的物体,这些感知任务包括任务包括物体检测和分割。
物体检测:物体检测是指通过处理传感器数据来检测环境中的物体,如汽车、行人、骑自行车的人、红绿灯等。YOLO系列网络是常用于实时物体检测的解决方案。这些网络可以实时识别多个物体,并提供它们的位置和边界框。
在这里插入图片描述

实例分割:实例分割是一项更复杂的任务,它不仅可以检测物体,还可以将它们分割成属于不同实例的部分。这对于跟踪和识别不同的车辆或行人非常有用。通常,Mask R-CNN等网络用于实例分割。
在这里插入图片描述

语义分割:语义分割是将图像中的每个像素分配到特定的语义类别的任务,例如将道路、建筑物、车辆等分割成不同的区域。U-Net和全卷积网络(FCN)等CNN架构通常用于语义分割。
在处理这些任务时,有两种主要方法可以考虑:
单一模型:开发一个单一的神经网络模型,可以同时处理多个感知任务,如检测、分割。这种方法可以提高计算效率,但需要大量的训练数据和复杂的网络结构。
多模型集成:使用多个独立的模型来处理不同的感知任务,每个模型专注于一个任务。这种方法可以更容易地处理不同任务之间的数据不平衡,并且可以根据任务的重要性对不同模型进行资源分配。

二、Hybridnets多任务框架

Hybridnets一种多任务处理的端到端多感知网络,它提出了几种关键的优化方法以提高精度。

  1. 引入了基于加权双向特征网络的高效分割头和边界框/类别预测网络。
  2. 提出了加权双向特征网络中各层的自动定制锚定方法。
  3. 提出了一种有效的训练损失函数和训练策略,以平衡和优化网络性能。

基于这些优化,Hybridnets开发了一种端到端感知网络,用于执行多任务处理,包括交通目标检测、可驾驶区域分割和车道线检测,将其称为混合网。混合网在伯克利DeepDrive数据集上表现出色,平均精度达到了77.3%,平均交并比为31.6%,并且参数数量仅为1283万,浮点操作数为156亿。此外,它能够实时执行视觉感知任务,因此是一个实用而准确的多任务处理解决方案。
在这里插入图片描述
算法源码:https://github.com/datvuthanh/HybridNets.git

三、使用C++进行模型部署

把训练好的模型转成onnx格式的模型,然后使用OpenCV 4.6 和contrib dnn模块进行推理,使用的IDE是Vs 2019。
首先定义一人模型推理的类,这里使用为了方便演示,只用CPU进行推理。

struct Net_config
{float confThreshold; float nmsThreshold; std::string modelpath;std::string anchorpath;
};class HybridNets
{
public:HybridNets(Net_config config);cv::Mat detect(cv::Mat frame); ~HybridNets(); 
private:int inpWidth;int inpHeight;std::vector<std::string> det_class_names = { "car" };std::vector<std::string> seg_class_names = { "Background", "Lane", "Line" };int det_num_class;int seg_numclass;float confThreshold;float nmsThreshold;cv::dnn::Net net;float* anchors = nullptr;const float mean_[3] = { 0.485, 0.456, 0.406 };const float std_[3] = { 0.229, 0.224, 0.225 };const bool keep_ratio = true;cv::Mat resize_image(cv::Mat srcimg, int* newh, int* neww, int* padh, int* padw);cv::Mat normalize_(cv::Mat img);std::vector<cv::Vec3b> class_colors = { cv::Vec3b(0,0,0), cv::Vec3b(0, 255, 0), cv::Vec3b(255, 0, 0) };
};HybridNets::HybridNets(Net_config config)
{this->confThreshold = config.confThreshold;this->nmsThreshold = config.nmsThreshold;this->net = cv::dnn::readNet(config.modelpath); this->det_num_class = det_class_names.size();this->seg_numclass = seg_class_names.size();size_t pos = config.modelpath.rfind("_");size_t pos_ = config.modelpath.rfind(".");int len = pos_ - pos - 1;std::string hxw = config.modelpath.substr(pos + 1, len);pos = hxw.rfind("x");std::string h = hxw.substr(0, pos);len = hxw.length() - pos;std::string w = hxw.substr(pos + 1, len);this->inpHeight = stoi(h);this->inpWidth = stoi(w);pos = config.anchorpath.rfind("_");pos_ = config.anchorpath.rfind(".");len = pos_ - pos - 1;std::string len_ = config.anchorpath.substr(pos + 1, len);len = stoi(len_);this->anchors = new float[len];FILE* fp = fopen(config.anchorpath.c_str(), "rb");fread(this->anchors, sizeof(float), len, fp);fclose(fp);
}HybridNets::~HybridNets()
{delete[] anchors;anchors = nullptr;
}cv::Mat HybridNets::resize_image(cv::Mat srcimg, int* newh, int* neww, int* padh, int* padw)
{int srch = srcimg.rows, srcw = srcimg.cols;*newh = this->inpHeight;*neww = this->inpWidth;cv::Mat dstimg;if (this->keep_ratio && srch != srcw) {float hw_scale = (float)srch / srcw;if (hw_scale > 1) {*newh = this->inpHeight;*neww = int(this->inpWidth / hw_scale);resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);*padw = int((this->inpWidth - *neww) * 0.5);copyMakeBorder(dstimg, dstimg, 0, 0, *padw, this->inpWidth - *neww - *padw, cv::BORDER_CONSTANT, 114);}else {*newh = (int)this->inpHeight * hw_scale;*neww = this->inpWidth;resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);*padh = (int)(this->inpHeight - *newh) * 0.5;copyMakeBorder(dstimg, dstimg, *padh, this->inpHeight - *newh - *padh, 0, 0, cv::BORDER_CONSTANT, 114);}}else {resize(srcimg, dstimg, cv::Size(*neww, *newh), cv::INTER_LINEAR);}return dstimg;
}cv::Mat HybridNets::normalize_(cv::Mat img)
{std::vector<cv::Mat> bgrChannels(3);split(img, bgrChannels);for (int c = 0; c < 3; c++){bgrChannels[c].convertTo(bgrChannels[c], CV_32FC1, 1.0 / (255.0 * std_[c]), (0.0 - mean_[c]) / std_[c]);}cv::Mat m_normalized_mat;merge(bgrChannels, m_normalized_mat);return m_normalized_mat;
}cv::Mat HybridNets::detect(cv::Mat srcimg)
{int newh = 0, neww = 0, padh = 0, padw = 0;cv::Mat rgbimg;cvtColor(srcimg, rgbimg, cv::COLOR_BGR2RGB);cv::Mat dstimg = this->resize_image(rgbimg, &newh, &neww, &padh, &padw);cv::Mat normalized_mat = this->normalize_(dstimg);cv::Mat blob = cv::dnn::blobFromImage(normalized_mat);this->net.setInput(blob);std::vector<cv::Mat> outs;this->net.forward(outs, this->net.getUnconnectedOutLayersNames());float* classification = (float*)outs[0].data;float* box_regression = (float*)outs[1].data;float* seg = (float*)outs[2].data;std::vector<cv::Rect> boxes;std::vector<float> confidences;std::vector<int> classIds;float ratioh = (float)srcimg.rows / newh, ratiow = (float)srcimg.cols / neww;const int num_proposal = outs[1].size[1];  for (int n = 0; n < num_proposal; n++){float conf = classification[n];if (conf > this->confThreshold){const int row_ind = n * 4;float x_centers = box_regression[row_ind + 1] * this->anchors[row_ind + 2] + this->anchors[row_ind];float y_centers = box_regression[row_ind] * this->anchors[row_ind + 3] + this->anchors[row_ind + 1];float w = exp(box_regression[row_ind + 3]) * this->anchors[row_ind + 2];float h = exp(box_regression[row_ind + 2]) * this->anchors[row_ind + 3];float xmin = (x_centers - w * 0.5 - padw) * ratiow;float ymin = (y_centers - h * 0.5 - padh) * ratioh;w *= ratiow;h *= ratioh;cv::Rect box = cv::Rect(int(xmin), int(ymin), int(w), int(h));boxes.push_back(box);confidences.push_back(conf);classIds.push_back(0);}}std::vector<int> indices;cv::dnn::NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices);cv::Mat outimg = srcimg.clone();for (size_t i = 0; i < indices.size(); ++i){int idx = indices[i];cv::Rect box = boxes[idx];rectangle(outimg, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), cv::Scalar(0, 0, 255), 2);std::string label = cv::format("%.2f", confidences[idx]);label = this->det_class_names[classIds[idx]] + ":" + label;putText(outimg, label, cv::Point(box.x, box.y - 5), cv::FONT_HERSHEY_SIMPLEX, 0.75, cv::Scalar(0, 0, 255), 1);}const int area = this->inpHeight * this->inpWidth;int i = 0, j = 0, c = 0;for (i = 0; i < outimg.rows; i++){for (j = 0; j < outimg.cols; j++){const int x = int(j / ratiow) + padw;  const int y = int(i / ratioh) + padh;int max_id = -1;float max_conf = -10000;for (c = 0; c < seg_numclass; c++){float seg_conf = seg[c * area + y * this->inpWidth + x];if (seg_conf > max_conf){max_id = c;max_conf = seg_conf;}}if (max_id > 0){outimg.at<cv::Vec3b>(i, j)[0] = this->class_colors[max_id][0];outimg.at<cv::Vec3b>(i, j)[1] = this->class_colors[max_id][1];outimg.at<cv::Vec3b>(i, j)[2] = this->class_colors[max_id][2];}}}return outimg;
}

读取视频,然后使用模型进行推理

int main()
{Net_config cfg = { 0.3, 0.5, "models/hybridnets_768x1280.onnx", "models/anchors_736560.bin" };HybridNets net(cfg);//cv::VideoWriter outputVideo;cv::VideoCapture cap("test2.mp4");//cv::Size S = cv::Size((int)cap.get(cv::CAP_PROP_FRAME_WIDTH),//(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));//std::string out_path = "dst.ma4";if (cap.isOpened()){//outputVideo.open(out_path, -1, 30.0, S, true);cv::Mat cv_fram;while (1){cap.read(cv_fram);if (!cv_fram.empty()){cv::Mat outimg = net.detect(cv_fram);//outputVideo << outimg;cv::imshow("视频", outimg);}if (cv::waitKey(100) == 27)break;}}cap.release();return 0;
}

推理效果如下:
在这里插入图片描述

四、使用Python进行模型部署

import cv2
import argparse
import numpy as np
import osprint(cv2.__version__)class HybridNets():def __init__(self, modelpath, anchorpath, confThreshold=0.5, nmsThreshold=0.5):self.det_classes = ["car"]self.seg_classes = ["Background", "Lane", "Line"]self.net = cv2.dnn.readNet(modelpath)self.confThreshold = confThresholdself.nmsThreshold = nmsThresholdh, w = os.path.basename(modelpath).split('_')[-1].replace('.onnx', '').split('x')self.inpHeight, self.inpWidth = int(h), int(w)self.mean_ = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape((1, 1, 3))self.std_ = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape((1, 1, 3))self.anchors = np.load(anchorpath)  ### cx_cy_w_hdef resize_image(self, srcimg, keep_ratio=True):padh, padw, newh, neww = 0, 0, self.inpWidth, self.inpHeightif keep_ratio and srcimg.shape[0] != srcimg.shape[1]:hw_scale = srcimg.shape[0] / srcimg.shape[1]if hw_scale > 1:newh, neww = self.inpHeight, int(self.inpWidth / hw_scale)img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_LINEAR)padw = int((self.inpWidth - neww) * 0.5)img = cv2.copyMakeBorder(img, 0, 0, padw, self.inpWidth - neww - padw, cv2.BORDER_CONSTANT,value=(114, 114, 114))  # add borderelse:newh, neww = int(self.inpHeight * hw_scale), self.inpWidthimg = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_LINEAR)padh = int((self.inpHeight - newh) * 0.5)img = cv2.copyMakeBorder(img, padh, self.inpHeight - newh - padh, 0, 0, cv2.BORDER_CONSTANT,value=(114, 114, 114))else:img = cv2.resize(srcimg, (self.inpWidth, self.inpHeight), interpolation=cv2.INTER_LINEAR)return img, newh, neww, padh, padwdef detect(self, srcimg):img, newh, neww, padh, padw = self.resize_image(cv2.cvtColor(srcimg, cv2.COLOR_BGR2RGB))scale_h, scale_w = srcimg.shape[0] / newh, srcimg.shape[1] / newwimg = (img.astype(np.float32) / 255.0 - self.mean_) / self.std_# Sets the input to the networkblob = cv2.dnn.blobFromImage(img)self.net.setInput(blob)classification, box_regression, seg = self.net.forward(self.net.getUnconnectedOutLayersNames())x_centers = box_regression[..., 1] * self.anchors[..., 2] + self.anchors[..., 0]y_centers = box_regression[..., 0] * self.anchors[..., 3] + self.anchors[..., 1]w = np.exp(box_regression[..., 3]) * self.anchors[..., 2]h = np.exp(box_regression[..., 2]) * self.anchors[..., 3]xmin = x_centers - w * 0.5ymin = y_centers - h * 0.5bboxes_wh = np.stack([xmin, ymin, w, h], axis=2).squeeze(axis=0)confidences = np.max(classification.squeeze(axis=0), axis=1)  ####max_class_confidenceclassIds = np.argmax(classification.squeeze(axis=0), axis=1)mask = confidences > self.confThresholdbboxes_wh = bboxes_wh[mask]confidences = confidences[mask]classIds = classIds[mask]bboxes_wh -= np.array([[padw, padh, 0, 0]])  bboxes_wh *= np.array([[scale_w, scale_h, scale_w, scale_h]])indices = cv2.dnn.NMSBoxes(bboxes_wh.tolist(), confidences.tolist(), self.confThreshold,self.nmsThreshold).flatten().tolist()drive_area_mask = np.squeeze(seg, axis=0)[:, padh:(self.inpHeight - padh), padw:(self.inpWidth - padw)]seg_id = np.argmax(drive_area_mask, axis=0).astype(np.uint8)seg_id = cv2.resize(seg_id, (srcimg.shape[1], srcimg.shape[0]), interpolation=cv2.INTER_NEAREST)outimg = srcimg.copy()for ind in indices:x, y, w, h = bboxes_wh[ind,:].astype(int)cv2.rectangle(outimg, (x, y), (x + w, y + h), (0, 0, 255), thickness=2, lineType=cv2.LINE_AA)cv2.putText(outimg, self.det_classes[classIds[ind]]+ ":" + str(round(confidences[ind], 2)), (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255),thickness=1, lineType=cv2.LINE_AA)outimg[seg_id == 1] = [0, 255, 0]outimg[seg_id == 2] = [255, 0, 0]return outimgif __name__ == "__main__":parser = argparse.ArgumentParser()parser.add_argument('--imgpath', type=str, default='images/test.jpg', help="image path")parser.add_argument('--modelpath', type=str, default='models/hybridnets_768x1280.onnx')parser.add_argument('--anchorpath', type=str, default='models/anchors_768x1280.npy')parser.add_argument('--confThreshold', default=0.3, type=float, help='class confidence')parser.add_argument('--nmsThreshold', default=0.5, type=float, help='nms iou thresh')args = parser.parse_args()yolonet = HybridNets(args.modelpath, args.anchorpath, confThreshold=args.confThreshold,nmsThreshold=args.nmsThreshold)srcimg = cv2.imread(args.imgpath)srcimg = yolonet.detect(srcimg)cv2.namedWindow('dst', 0)cv2.imshow('dst', srcimg)cv2.waitKey(0)cv2.destroyAllWindows()

在这里插入图片描述

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

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

相关文章

SpringCloud(二)

1.Nacos配置管理 Nacos除了可以做注册中心&#xff0c;同样可以做配置管理来使用。 1.1.统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#…

NIFI实现数据库数据增量同步

说明 nifi版本&#xff1a;1.23.2&#xff08;docker镜像&#xff09; 需求背景 将数据库中的数据同步到另一个数据库中&#xff0c;要求对于新增的数据和历史有修改的数据进行增量同步 模拟数据 建表语句 源数据库和目标数据库结构要保持一致&#xff0c;这样可以避免后…

固定资产管理数据怎么算?

在企业的运营中&#xff0c;固定资产的管理是一个至关重要的环节。然而&#xff0c;对于许多企业来说&#xff0c;理解和管理这些资产的数据却常常是一团迷雾。那么&#xff0c;固定资产管理数据究竟应该如何计算呢&#xff1f;这是一个需要我们深入探讨的问题。  我们需要明…

MySQL——命令行客户端的字符集问题

原因&#xff1a;服务器端认为你的客户端的字符集是utf-8&#xff0c;而实际上你的客户端的字符集是GBK。 查看所有字符集&#xff1a;SHOW VARIABLES LIKE character_set_%; 解决方案&#xff0c;设置当前连接的客户端字符集 “SET NAMES GBK;”

Android12之/proc/pid/status参数含义(一百六十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

php://filter协议在任意文件读取漏洞(附例题)

php://filter php://fiter 中文叫 元器封装&#xff0c;咱也不知道为什么这么翻译&#xff0c;目前我的理解是可以通过这个玩意对上面提到的php IO流进行处理&#xff0c;及现在可以对php的 IO流进行一定操作。 过滤器&#xff1a;及通过php://filter 对php 的IO流进行的具体…

微服务之流控、容错组件sentinel

背景 2012年阿里巴巴研发的流量治理组件&#xff0c;核心功能流控、容错 有什么功能 流量控制 流量控制 网关控制 黑白名单 熔断降级 熔断 保护分布式系统防止因为调用下有服务时产生故障或者请求超时等异常影响上游服务&#xff0c;使用熔断方案&#xff0c;类似断路器…

T2I-Adapter:增强文本到图像生成的控制能力

链接&#xff1a;GitHub - TencentARC/T2I-Adapter: T2I-Adapter 文本到图像生成 (T2I) 是人工智能领域的一个重要研究方向。近年来&#xff0c;随着深度学习技术的发展&#xff0c;T2I 技术取得了显著进展&#xff0c;生成的图像在视觉效果上已经与真实图像难以区分。 然而&…

ILS解析漏洞复现

搭建好ILS后&#xff0c;访问127.0.0.1:8000 写一个phpinfo的脚本 可以看到。现在是不能访问的 赋予 IIS 解析 phpinfo 能力 打开服务器管理器&#xff0c;打开 IIS 管理器 点击处理程序映射 再次访问&#xff0c;发现程序可以访问 将index.php改为index.png 此时php脚本自然是…

【pdf密码】如何限制他人对PDF文件编辑?

制作好的PDF文件&#xff0c;先要设置一个密码防止他人对文件进行编辑&#xff0c;那么我们可以对PDF文件设置限制编辑&#xff0c;设置方法很简单&#xff0c;我们在PDF编辑器中点击文件 – 属性 – 安全&#xff0c;在权限下拉框中选中【密码保护】 然后在密码保护界面中&…

查看创建好的数据库

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法格式: show create database 数据库名称; 案列:查看testing数据库信息 mysql> show create database testing; ------------------------…

SpringMVC相关知识点

1.Spring MVC的理解&#xff1f; 首先&#xff0c;MVC模型是模型&#xff0c;视图&#xff0c;控制器的简写&#xff0c;其思想核心是通过将请求处理控制&#xff0c;业务逻辑&#xff0c;数据封装&#xff0c;数据显示等流程节点分离的思想来组织代码。 所以&#xff0c;MVC是…

华为星闪联盟:引领无线通信技术创新的先锋

星闪&#xff08;NearLink&#xff09;&#xff0c;是由华为倡导并发起的新一代无线短距通信技术&#xff0c;它从零到一全新设计&#xff0c;是为了满足万物互联时代个性化、多样化的极致、创新体验需求而诞生的。这项技术汇聚了中国300多家头部企业和机构的集体智慧&#xff…

【STM32】FSMC—扩展外部 SRAM 初步使用 1

基于野火指南者《零死角玩转 STM32F103—指南者》的学习 STM32F103系列 FSMC Flexible Static Memory Controller简介 1.详细功能参看《STM32F10x参考手册》&#xff0c;这边是概述 是一个外设&#xff0c;挂载在AHB总线下。 可以用于驱动包括 SRAM、NOR FLASH 以及 NAND FL…

C#自定义控件组件实现Chart图表(多Y轴,选择图例加粗,选择放大,缩放,点击查看信息等功能)

先看看ECharts的效果 C# 工具箱里的Chart控件就不演示了,很多效果没办法做出来,做出来效果也很不理想。所以,需要自己去手动实现工具箱里的Chart没办法实现的效果; 先看看实现后的效果 绑定数据 点击图表 点击右侧图例加粗 选择放大 右键 点击缩小,恢复

RJ45水晶头网线顺序出错排查

线序 网线水晶头RJ45常用的线序标准ANSI / TIA-568定义了T568A与T568B两种线序&#xff0c;一般使用T568B&#xff0c;水晶头8个孔对应的8条线颜色如下图&#xff1a; 那1至8的编号&#xff0c;是从水晶头哪一面为参考呢&#xff0c;如下图&#xff0c;是水晶头金手指一面&am…

华为云云耀云服务器L实例评测 | 由于自己原因导致MySQL数据库被攻击 【更新中。。。】

目录 引出起因&#xff08;si因&#xff09;解决报错诶嘿&#xff0c;连上了 不出意外&#xff0c;就出意外了打开数据库what&#xff1f;&#xff1f;&#xff1f; 找华为云求助教训&#xff1a;备份教训&#xff1a;密码 解决1.改密码2.新建一个MySQL&#xff0c;密码设置复杂…

node.js下载安装环境配置以及快速使用

目录 一、下载 二、安装 三、测试安装是否成功 四、配置环境 五、测试配置环境是否成功 六、安装淘宝镜像 七、快速上手 1、建立一个自己的工作目录 2、下载工作代码 八、各种配置文件匹配问题入坑 九、总结 一、下载 Node.js 中文网 想选择其他版本或者其他系统使用…

Linux服务使用宝塔面板搭建网站,并发布公网访问 - 内网穿透

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 前言 宝塔面板作为简单好用的服务器运维管理面板&#xff0c;它支持Linux/Windows系统&#xff0c;我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等&…

地理地形sdk:Tatuk GIS Developer Kernel for .NET Crack

Tatuk GIS Developer Kernel for .NET 是一个变体&#xff0c;它是受控代码和 .NET GIS SDK&#xff0c;用于为用户 Windows 操作系统创建专业 GIS 软件的过程。它被认为是一个完全针对Win Forms 的.NET CIL&#xff0c;WPF 框架是针对C# 以及VB.NET、VC、Oxy 以及最终与.NET 的…