浅易理解:非极大抑制NMS

什么是非极大抑制NMS

非极大值抑制(Non-Maximum Suppression,简称NMS)是一种在计算机视觉和图像处理领域中广泛使用的后处理技术,特别是在目标检测任务中。它的主要目的是解决目标检测过程中出现的重复检测问题,即对于同一个物体,算法可能会预测出多个重叠或相似的边界框(bounding boxes)。

在目标检测算法得出一系列候选边界框及其对应的类别得分(confidence score)之后,NMS过程如下:

  1. 排序:首先根据每个边界框的得分进行降序排序,选取得分最高的边界框作为保留的对象。

  2. 抑制:对于排序后的边界框列表,对每一个框i,检查其与得分低于它的所有其他框j之间的重叠程度。通常使用交并比(Intersection over Union,IoU)来量化两个框的重叠面积占它们并集面积的比例。

  3. 剔除:如果框i与某个框j的IoU超过预设的阈值(比如0.5),则认为框j是冗余的,将其剔除(抑制)。

  4. 迭代:重复上述步骤,直到处理完所有候选边界框,最终剩下的边界框集合即是经过非极大值抑制后的结果,这些框代表了各自区域内最有可能对应真实物体的检测结果。

总之,非极大值抑制确保了对同一物体只有一个最精确的边界框被保留下来,从而减少误报和重复检测,提高了目标检测的精度。

在目标检测中,NMS的目的就是要去除冗余的检测框,保留最好的一个

非极大抑制的概念只需要看这两幅图就知道了:

下图是经过非极大抑制的。

下图是未经过非极大抑制的。

NMS的原理是对于预测框的集合S及其对应的置信度score(这里的置信度就是softmax得出的概率值,它的含义是多大的把握预测正确,也就是有多大的把握确定检测框中存在真正的目标),选择具有最大score的检测框,记为M,将其从集合S中移除并加入到最终的检测结果集合中.并且将集合S中剩余检测框中与检测框M的IoU大于阈值的框从集合S中移除.重复这个过程,直到集合S为空。

使用流程如下图所示:

首先是检测出一系列的检测框

将检测框按照类别进行分类

对同一类别的检测框应用NMS获取最终的检测结果

代码:

NMS 算法一般是为了去掉模型预测后的多余框,其一般设有一个nms_threshold=0.5,具体的实现思路如下:

  • 选取这类box中scores最大的哪一个,它的index记为 i ,并保留它;
  • 计算 boxes[i] 与其余的 boxes 的 IOU 值;
  • 如果其 IOU>0.5 了,那么就舍弃这个box(由于可能这两个box表示同一目标,所以保留分数高的哪一个);
  • 从最后剩余的boxes中,再找出最大scores的哪一个,如此循环往复。
def nms(boxes, scores, threshold=0.5, top_k=200):'''Args:boxes: 预测出的box, shape[M,4]scores: 预测出的置信度,shape[M]threshold: 阈值top_k: 要考虑的box的最大个数Return:keep: nms筛选后的box的新的index数组count: 保留下来box的个数'''keep = scores.new(scores.size(0)).zero_().long()x1 = boxes[:, 0]y1 = boxes[:, 1]x2 = boxes[:, 2]y2 = boxes[:, 3]area = (x2-x1)*(y2-y1)  # 面积,shape[M]_, idx = scores.sort(0, descending=True) # 降序排列scores的值大小# 取前top_k个进行nmsidx = idx[:top_k]count = 0while idx.numel():# 记录最大score值的indexi = idx[0]# 保存到keep中keep[count] = i# keep 的序号count += 1if idx.size(0) == 1: # 保留框只剩一个breakidx = idx[1:] # 移除已经保存的index# 计算boxes[i]和其他boxes之间的iouxx1 = x1[idx].clamp(min=x1[i])yy1 = y1[idx].clamp(min=y1[i])xx2 = x2[idx].clamp(max=x2[i])yy2 = y2[idx].clamp(max=y2[i])w = (xx2 - xx1).clamp(min=0)h = (yy2 - yy1).clamp(min=0)# 交集的面积inter = w * h  # shape[M-1]iou = inter / (area[i] + area[idx] - inter)# iou满足条件的idxidx = idx[iou.le(threshold)] # Shape[M-1]return keep, count

其中:

  • torch.numel(): 表示一个张量总元素的个数
  • torch.clamp(min, max): 设置上下限
  • tensor.le(x): 返回tensor<=x的判断 

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

通过一个例子看些NMS的使用方法,假设定位车辆,算法就找出了一系列的矩形框,我们需要判别哪些矩形框是没用的,需要使用NMS的方法来实现。

假设集合S中有A、B、C、D、E 5个候选框,每个框旁边的数字是它的置信度,我们设定NMS的iou阈值是0.5,接下来进行迭代计算:

第一轮:因为B是得分最高的(即B的置信度最高),在集合S的其余候选框中,如果与B的IoU>0.5会被删除。A,C,D,E中现在分别与B计算IoU,DE结果>0.5,剔除DE(说明BDE检测的是同一个目标,保留置信度最大的候选框;而AC可能检测的是另一个目标),B作为一个预测结果,从集合S中移除,并放入最终的检测结果集合中。此时新的集合S中只剩下候选框A,C

第二轮:在新的集合S中,A的置信度得分最高,将集合S中剩下的候选框分别与A计算IoU,因为A与C的iou>0.5,所以剔除C,A作为另外一个预测结果从集合S中移除,并放入最终的检测结果集合中,此时集合S为空,所以循环结束。

最终结果为在这个5个中检测出了两个目标为A和B。

单类别的NMS的实现方法如下所示:

import numpy as np
def nms(bboxes, confidence_score, threshold):"""非极大抑制过程:param bboxes: 同类别候选框坐标:param confidence: 同类别候选框分数(即置信度):param threshold: iou阈值:return:"""# 1、没有传入候选框则返回空列表if len(bboxes) == 0:return [], []#强制转换为numpy类型的数组,这样才可以进行切片等numpy所支持的操作bboxes = np.array(bboxes)score = np.array(confidence_score)# 取出所有候选框的左上角坐标和右下角坐标x1 = bboxes[:, 0]y1 = bboxes[:, 1]x2 = bboxes[:, 2]y2 = bboxes[:, 3]# 2、对候选框进行NMS筛选# 返回的框坐标和分数picked_boxes = []picked_score = []# 对置信度进行排序, 获取排序后的下标序号, argsort默认从小到大排序order = np.argsort(score)#计算所有候选框的面积 areas = (x2 - x1) * (y2 - y1)while order.size > 0:# 将当前置信度最大的候选框的索引,加入返回值列表中,因为是从小到大排序,所有最后一个值最大,即 order[-1]表示最后一个元素index = order[-1]#将置信度最大的候选框及其置信度值加入返回值列表中picked_boxes.append(bboxes[index])picked_score.append(score[index])# 获取当前置信度最大的候选框与其他任意候选框的相交面积,这里的order[:-1]表示除了最后一个元素之外的所有元素,np.max和np.maximum的实现功能是不同的#np.maximum的用法:np.maximum([2,4,7],[3,1,5])输出的结果是array([3, 4, 7]);np.maximum([2],[3,1,5])的输出结果是array([3, 2, 5])#np.max的用法:np.max([2,4,7])输出结果是7x11 = np.maximum(x1[index], x1[order[:-1]])y11 = np.maximum(y1[index], y1[order[:-1]])x22 = np.minimum(x2[index], x2[order[:-1]])y22 = np.minimum(y2[index], y2[order[:-1]])# 计算相交的面积,不重叠时面积设为0w = np.maximum(0.0, x22 - x11)h = np.maximum(0.0, y22 - y11)inter_area = w * h# 计算交并比iou = inter_area / (areas[index] + areas[order[:-1]] - inter_area)# 获取IoU小于阈值的候选框的索引keep_boxes = np.where(iou < threshold)#更新order,以便保留IoU小于阈值的框,order = order[keep_boxes]# 返回NMS后的框及分类结果   return picked_boxes, picked_score

假设有检测结果如下:当阈值threshold设置的越大,则保留越多的候选框

  • 当threshold取0.3时:

bounding = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.65, 0.8]
threshold = 0.3
picked_boxes, picked_score = nms(bounding, confidence_score, threshold)
print('阈值threshold为:', threshold)
print('NMS后得到的bbox是:', picked_boxes)
print('NMS后得到的bbox的confidences是:', picked_score)

返回结果:

阈值threshold为: 0.3
NMS后得到的bbox是: [array([187,  82, 337, 317])]
NMS后得到的bbox的confidences是: [0.9]

当threshold取0.5时:

bounding = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.65, 0.8]
threshold = 0.5
picked_boxes, picked_score = nms(bounding, confidence_score, threshold)
print('阈值threshold为:', threshold)
print('NMS后得到的bbox是:', picked_boxes)
print('NMS后得到的bbox的confidences是:', picked_score)

返回结果:

阈值threshold为: 0.5
NMS后得到的bbox是: [array([187,  82, 337, 317]), array([246, 121, 368, 304])]
NMS后得到的bbox的confidences是: [0.9, 0.8]

上述所讲的NMS方法都是先将检测框按照类别进行分类,然后对对同一类别的检测框应用NMS。但是在实际的任务中,如果所预测的类别很多时,那么这种效率非常低。所以有些时候我们会使用新的方法进行NMS:它的大致思想是先将不同类别的预测框在坐标位置上尽可能的区分开,然后就可以一次性对所有预测框进行NMS(此时不用先进行分类,然后分别对每一个类别依次做NMS)

,比如下图所示,蓝色方框的类别索引是1,黄色方框的类别索引是2,这些不同类别的预测框在位置上靠的很近,此时如果直接对所有类别同时做NMS,效果就很差。所以我们会设法将蓝色方框和黄色方框分离开,本例的方法是首先找到所有方框中坐标值最大的数值max_value,比如这里是81,

然后使用类别索引 indxs与val_value相乘,得到不同类别框的偏移量offsets,它的公式是:offsets=indxs*max_value

比如对于类别索引为1的方框,它的偏移量是offsets=indxs*max_value=1*81=81,对于类别索引为1的方框,它的偏移量是offsets=indxs*max_value=2*81=162

计算完每个类别的偏移量后,我们就得到新的预测框的坐标以及其对于的新位置,如下所示。然后就可以一次性对所有预测框进行NMS(此时不用先进行分类,然后分别对每一个类别依次做NMS)

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

概括非极大抑制的功能就是:

筛选出一定区域内属于同一种类得分最大的框。

1、非极大抑制NMS的实现过程
本博文实现的是多分类的非极大抑制:
输入shape为[ batch_size, all_anchors, 5+num_classes ]

第一个维度是图片的数量。
第二个维度是所有的预测框。
第三个维度是所有的预测框的预测结果。

非极大抑制的执行过程如下所示:
1、对所有图片进行循环。
2、找出该图片中得分大于门限函数的框。在进行重合框筛选前就进行得分的筛选可以大幅度减少框的数量。
3、判断第2步中获得的框的种类与得分。取出预测结果中框的位置与之进行堆叠。此时最后一维度里面的内容由5+num_classes变成了4+1+2,四个参数代表框的位置,一个参数代表预测框是否包含物体,两个参数分别代表种类的置信度与种类。
4、对种类进行循环,非极大抑制的作用是筛选出一定区域内属于同一种类得分最大的框,对种类进行循环可以帮助我们对每一个类分别进行非极大抑制。
5、根据得分对该种类进行从大到小排序。
6、每次取出得分最大的框,计算其与其它所有预测框的重合程度,重合程度过大的则剔除。

视频中实现的代码是numpy形式,而且库比较久远。这里改成pytorch的形式,且适应当前的库。

实现代码如下:

def bbox_iou(self, box1, box2, x1y1x2y2=True):"""计算IOU"""if not x1y1x2y2:b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2else:b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]inter_rect_x1 = torch.max(b1_x1, b2_x1)inter_rect_y1 = torch.max(b1_y1, b2_y1)inter_rect_x2 = torch.min(b1_x2, b2_x2)inter_rect_y2 = torch.min(b1_y2, b2_y2)inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1, min=0) * \torch.clamp(inter_rect_y2 - inter_rect_y1, min=0)b1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)b2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)iou = inter_area / torch.clamp(b1_area + b2_area - inter_area, min = 1e-6)return ioudef non_max_suppression(self, prediction, num_classes, input_shape, image_shape, letterbox_image, conf_thres=0.5, nms_thres=0.4):#----------------------------------------------------------##   将预测结果的格式转换成左上角右下角的格式。#   prediction  [batch_size, num_anchors, 85]#----------------------------------------------------------#box_corner          = prediction.new(prediction.shape)box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2prediction[:, :, :4] = box_corner[:, :, :4]output = [None for _ in range(len(prediction))]for i, image_pred in enumerate(prediction):#----------------------------------------------------------##   对种类预测部分取max。#   class_conf  [num_anchors, 1]    种类置信度#   class_pred  [num_anchors, 1]    种类#----------------------------------------------------------#class_conf, class_pred = torch.max(image_pred[:, 5:5 + num_classes], 1, keepdim=True)#----------------------------------------------------------##   利用置信度进行第一轮筛选#----------------------------------------------------------#conf_mask = (image_pred[:, 4] * class_conf[:, 0] >= conf_thres).squeeze()#----------------------------------------------------------##   根据置信度进行预测结果的筛选#----------------------------------------------------------#image_pred = image_pred[conf_mask]class_conf = class_conf[conf_mask]class_pred = class_pred[conf_mask]if not image_pred.size(0):continue#-------------------------------------------------------------------------##   detections  [num_anchors, 7]#   7的内容为:x1, y1, x2, y2, obj_conf, class_conf, class_pred#-------------------------------------------------------------------------#detections = torch.cat((image_pred[:, :5], class_conf.float(), class_pred.float()), 1)#------------------------------------------##   获得预测结果中包含的所有种类#------------------------------------------#unique_labels = detections[:, -1].cpu().unique()if prediction.is_cuda:unique_labels = unique_labels.cuda()detections = detections.cuda()for c in unique_labels:#------------------------------------------##   获得某一类得分筛选后全部的预测结果#------------------------------------------#detections_class = detections[detections[:, -1] == c]# #------------------------------------------## #   使用官方自带的非极大抑制会速度更快一些!# #------------------------------------------## keep = nms(#     detections_class[:, :4],#     detections_class[:, 4] * detections_class[:, 5],#     nms_thres# )# max_detections = detections_class[keep]# 按照存在物体的置信度排序_, conf_sort_index = torch.sort(detections_class[:, 4]*detections_class[:, 5], descending=True)detections_class = detections_class[conf_sort_index]# 进行非极大抑制max_detections = []while detections_class.size(0):# 取出这一类置信度最高的,一步一步往下判断,判断重合程度是否大于nms_thres,如果是则去除掉max_detections.append(detections_class[0].unsqueeze(0))if len(detections_class) == 1:breakious = self.bbox_iou(max_detections[-1], detections_class[1:])detections_class = detections_class[1:][ious < nms_thres]# 堆叠max_detections = torch.cat(max_detections).data# Add max detections to outputsoutput[i] = max_detections if output[i] is None else torch.cat((output[i], max_detections))if output[i] is not None:output[i]           = output[i].cpu().numpy()box_xy, box_wh      = (output[i][:, 0:2] + output[i][:, 2:4])/2, output[i][:, 2:4] - output[i][:, 0:2]output[i][:, :4]    = self.yolo_correct_boxes(box_xy, box_wh, input_shape, image_shape, letterbox_image)return output

 参考文章:【SSD算法】史上最全代码解析-核心篇 - 知乎

参考文章:睿智的目标检测31——非极大抑制NMS与Soft-NMS-CSDN博客

参考文章:NMS(非极大值抑制)_nms非极大值抑制-CSDN博客

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

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

相关文章

《JAVA与模式》之简单工厂模式

系列文章目录 文章目录 系列文章目录前言一、简单工厂模式二、简单工厂模式的优缺点前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 在阎宏博士的《JAVA与模式》一…

【Hadoop】Hadoop概述与核心组件

目录 Hadoop概述Hadoop 发展历史Hadoop 三大发行版本1.Apache Hadoop&#xff08;常用&#xff09;2.Cloudera Hadoop3.Hortonworks Hadoop优势优势总结——4高&#xff08;高可靠、高扩展、高效、高容错&#xff09; Hadoop组成1.HDFS管理者&#xff1a;NameNode&#xff08;n…

NCP1271D65R2G中文资料规格书PDF数据手册引脚图参数图片价格功能特性描述

产品描述&#xff1a; NCP1271 是成功的 7 引脚电流模式 NCP12XX 系列的新一代引脚-引脚兼容新产品。该控制器通过使用可调节 Soft Skip 模式和集成的高电压启动 FET&#xff0c;实现了卓越的待机功耗。此专属 Soft Skip 还大大降低了噪音的风险。 因此可以在箝位网络中使用不…

SQLiteC/C++接口详细介绍之sqlite3类(四)

快速跳转文章列表&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;三&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;五&#xff09; 编写不易&#xff0c;有用的朋友点个赞或加粉一下万分感…

java Day7 正则表达式|异常

文章目录 1、正则表达式1.1 常用1.2 字符串匹配&#xff0c;提取&#xff0c;分割 2、异常2.1 运行时异常2.2 编译时异常2.3 自定义异常2.3.1 自定义编译时异常2.3.2 自定义运行时异常 1、正则表达式 就是由一些特定的字符组成&#xff0c;完成一个特定的规则 可以用来校验数据…

Qt/C++音视频开发69-保存监控pcm音频数据到mp4文件/监控录像/录像存储和回放/264/265/aac/pcm等

一、前言 用ffmpeg做音视频保存到mp4文件&#xff0c;都会遇到一个问题&#xff0c;尤其是在视频监控行业&#xff0c;就是监控摄像头设置的音频是PCM/G711A/G711U&#xff0c;解码后对应的格式是pcm_s16be/pcm_alaw/pcm_mulaw&#xff0c;将这个原始的音频流保存到mp4文件是会…

使用OpenCV实现两张图像融合在一起

简单介绍 图像融合技术是一种结合多个不同来源或不同传感器捕获的同一场景的图像数据&#xff0c;以生成一幅更全面、更高质量的单一图像的过程。这种技术广泛应用于遥感、医学影像分析、计算机视觉等多个领域。常见的图像融合技术包括基于像素级、特征级和决策级的融合方法&a…

家具工厂5G智能制造数字孪生可视化平台,推进家具行业数字化转型

家具制造5G智能制造工厂数字孪生可视化平台&#xff0c;推进家具行业数字化转型。随着科技的飞速发展&#xff0c;家具制造业正迎来一场前所未有的数字化转型。在这场家具制造业转型中&#xff0c;5G智能制造工厂数字孪生可视化平台发挥着至关重要的作用。 5G智能制造工厂数字孪…

Adobe PDF背景设置护眼模式,缓解眼部疲劳

一、背景 在用Adobe PDF看论文时&#xff0c;默认的白色背景看久了&#xff0c;眼睛会特别疲劳&#xff0c;下面介绍如何设置背景为护眼模式。 二、设置PDF为护眼模式 使用Adobe Acrobat Pro DC打开任意PDF文件&#xff0c;在上方工具栏选择“编辑”&#xff0c;在下拉菜单栏…

如何配置Apache的反向代理

目录 前言 一、反向代理的工作原理 二、Apache反向代理的配置 1. 安装Apache和相关模块 2. 配置反向代理规则 3. 重启Apache服务器 三、常见的使用案例 1. 负载均衡 2. 缓存 3. SSL加密 总结 前言 随着Web应用程序的不断发展和扩展&#xff0c;需要处理大量的请求和…

Vue.js+SpringBoot开发天沐瑜伽馆管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 瑜伽课程模块2.3 课程预约模块2.4 系统公告模块2.5 课程评价模块2.6 瑜伽器械模块 三、系统设计3.1 实体类设计3.1.1 瑜伽课程3.1.2 瑜伽课程预约3.1.3 系统公告3.1.4 瑜伽课程评价 3.2 数据库设计3.2.…

html和winform webBrowser控件交互并播放视频(包含转码)

1、 为了使网页能够与winform交互 将com的可访问性设置为真 [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name "FullTrust")][System.Runtime.InteropServices.ComVisibleAttribute(true)] 2、在webBrow…

【算法与数据结构】深入解析二叉树(二)之堆结构实现

文章目录 &#x1f4dd;二叉树的顺序结构及实现&#x1f320; 二叉树的顺序结构&#x1f320; 堆的实现&#x1f320; 堆的实现&#x1f309;堆向下调整算法&#x1f309;堆的创建&#x1f309;建堆时间复杂度&#x1f309;堆的插入&#x1f309;堆的删除 &#x1f320;堆向上调…

网络解析知识

网络解析 1.TCP三次握手和四次挥手 1.TCP的传输过程&#xff1a; Seq 序列号 保障传输过程可靠。 ACK &#xff08;确认消息&#xff09; SYN &#xff08;在建立TCP连接的时候使用&#xff09; FIN &#xff08;在关闭TCP连接的时候使用&#xff09;3.TCP建立连接…

Caffeine缓存

本地缓存基于本地环境的内存&#xff0c;访问速度非常快&#xff0c;对于一些变更频率低、实时性要求低的数据&#xff0c;可以放在本地缓存中&#xff0c;提升访问速度 使用本地缓存能够减少和Redis类的远程缓存间的数据交互&#xff0c;减少网络 I/O 开销&#xff0c;降低这…

【AIGC调研系列】通义灵码与copilot的对比

通义灵码与GitHub Copilot的对比主要集中在几个方面&#xff1a;代码编写能力、免费性、操作界面和适配性。 首先&#xff0c;在代码编写能力上&#xff0c;虽然GitHub Copilot在整体上要强于通义灵码&#xff0c;但通义灵码的能力也不算弱&#xff0c;并且在某些特定的小类任…

双线性插值缩放算法原理以及matlab与verilog的实现(二)

系列文章目录 双线性插值缩放算法原理以及matlab与verilog的实现&#xff08;一&#xff09; 文章目录 系列文章目录前言一、前提回顾二、FPGA实现步骤2.1 找到源图像四个像素点求目标像素点2.2 FPGA实现步骤2.3 总体框架2.4 ROM缓存模块2.5 VGA模块2.6 双线性算法模块 三、下…

PlantUML + VS Code

PlantUML 使用实例 文章目录 PlantUML 使用实例1. PlantUML简介1.1 什么是PlantUML1.2 PlantUML优势在哪 2. 怎么用2.1 环境依赖2.2 VS Code组件安装 3. 常用语法3.1 标记开始结束3.2 声明参与者3.3 声明关系3.4 对消息序列编号3.5 组合消息 4. 实例 1. PlantUML简介 1.1 什么…

deepin23beta中SQLite3数据库安装与使用

SQLite 是一个嵌入式 SQL 数据库引擎&#xff0c;它实现了一个自包含、无服务器、零配置、事务性 SQL 数据库引擎。 SQLite 的代码属于公共领域&#xff0c;因此可以免费用于任何商业或私人目的。 SQLite 是世界上部署最广泛的数据库&#xff0c;其应用程序数量之多&#xff0c…

SQLiteC/C++接口详细介绍之sqlite3类(三)

快速跳转文章列表&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;二&#xff09; 下一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;四&#xff09; 6.sqlite3_create_module与sqlite3_create_module_v2函数…