基于OpenVINO实现无监督异常检测

news2024/11/27 4:31:12

    异常检测(AD) 在欺诈检测、网络安全和医疗诊断等关键任务应用中至关重要。由于数据的高维性和底层模式的复杂性,图像、视频和卫星图像等视觉数据中的异常检测尤其具有挑战性。然而,视觉异常检测对于检测制造中的缺陷、识别监控录像中的可疑活动以及检测医学图像中的异常至关重要。

    在本文中,您将学习如何使用OpenVINO 工具包中的FiftyOne和Anomalib对视觉数据执行异常检测。为了演示,我们将使用MVTec AD 数据集,其中包含具有划痕、凹痕和孔洞等异常的各种物体的图像。

    它涵盖以下内容:

    • 在 FiftyOne 中加载 MVTec AD 数据集

    • 使用 Anomalib 训练异常检测模型

    • 评估 FiftyOne 中的异常检测模型

安装依赖项

    确保你在虚拟环境中运行它python=3.10。Anomalib 需要 Python 3.10,因此请确保你安装了正确的版本。

conda create -n anomalib_env python=3.10conda activate anomalib_env

    此后,按照Anomalib README中的说明从源代码安装 Anomalib及其依赖项。这些在 Google Colab 上可能需要一些时间,但对于本地安装应该很快:

pip install -U torchvision einops FrEIA timm open_clip_torch imgaug lightning kornia openvino git+https://github.com/openvinotoolkit/anomalib.git

    我们准备安装更多软件包。现在您可以明白为什么我们建议为该项目使用虚拟环境!

    • huggingface_hub用于加载 MVTec AD 数据集

    • clip用于计算图像嵌入

    • umap-learn用于降维

pip install -U huggingface_hub umap-learn git+https://github.com/openai/CLIP.git

加载和可视化 MVTec AD 数据集

    现在,让我们从FiftyOne导入我们需要的所有相关模块:​​​​​​​

import fiftyone as fo # 基础库和应用程序import fiftyone.brain as fob # ML 方法import fiftyone.zoo as foz # zoo 数据集和模型from fiftyone import ViewField as F # 定义视图的助手import fiftyone.utils.huggingface as fouh # Hugging Face 集成

    并从 Hugging Face Hub 加载 MVTec AD 数据集:

dataset = fouh.load_from_hub("Voxel51/mvtec-ad", persistent=True, overwrite=True)

    在继续之前,让我们看一下FiftyOne 应用程序中的数据集:

session = fo.launch_app(dataset)

图片

    该数据集包含 12 个对象类别的 5354 张图像。每个类别都有“良好”和“异常”图像,这些图像存在划痕、凹痕和孔洞等缺陷。每个异常样本还带有一个掩模,用于定位图像中的缺陷区域。

    缺陷标签因类别而异,这在现实世界的异常检测场景中很常见。在这些场景中,您会为每个类别训练不同的模型。在这里,我们将介绍一个类别的流程,您可以将相同的步骤应用于其他类别。

    还有一点需要注意的是,数据集被分为训练集和测试集。训练集只包含“良好”图像,而测试集则包含“良好”和“异常”图像。

    在训练模型之前,让我们深入研究数据集。通过计算图像嵌入并在低维空间中可视化它们,我们可以了解数据中隐藏的结构和模式。首先,我们将使用 CLIP 模型计算数据集中所有图像的嵌入:​​​​​​​

model = foz.load_zoo_model( "clip-vit-base32-torch" )   # 从 zoo 加载 CLIP 模型
# 计算数据集的嵌入dataset.compute_embeddings(     model=model, embeddings_field= "clip_embeddings" , batch_size= 64 ) 
# 使用 UMAP 对嵌入进行降维fob.compute_visualization(     dataset, embeddings= "clip_embeddings" , method= "umap" , brain_key= "clip_vis" )

    刷新 FiftyOne 应用程序,单击“+”选项卡,然后选择“Embeddings”。从下拉菜单中选择“all_clip_vis”。您将看到 2D 空间中图像嵌入的散点图,其中每个点对应于数据集中的一个样本。

图片

    使用颜色下拉菜单,注意嵌入如何根据对象类别进行聚类。这是因为 CLIP 对图像的语义信息进行编码。此外,CLIP 嵌入不会根据缺陷类型在类别内进行聚类。

训练异常检测模型

    现在我们对数据集有了了解,我们准备使用 Anomalib 训练异常检测模型。

    任务:Anomalib 支持图像的分类、检测和分割任务。我们将重点关注分割,其中模型预测图像中的每个像素是否异常,并创建一个定位缺陷的掩码。

    模型:Anomalib 支持多种异常检测算法。在本演练中,我们将使用两种算法:

    • PaDiM:用于异常检测和定位的补丁分布建模框架

    • PatchCore:迈向工业异常检测的全面召回

    预处理:在训练模型之前,我们将在本演练中将图像大小调整为 256x256 像素。通过 Torchvision 的 Resize 类将其添加为转换,我们可以在训练和推理过程中动态调整图像大小。

    从Anomalib 和辅助模块导入处理图像和路径所需的模块:

​​​​​​​

import numpy as npimport osfrom pathlib import Pathfrom PIL import Imagefrom torchvision.transforms.v2 import Resize
from anomalib import TaskTypefrom anomalib.data.image.folder import Folderfrom anomalib.deploy import ExportType, OpenVINOInferencerfrom anomalib.engine import Enginefrom anomalib.models import Padim, Patchcore

    现在,定义一些在整个笔记本中使用的常量。

    • OBJECT:我们将重点关注的对象类别。在本演练中,我们将使用“瓶子”。如果您想要循环遍历类别,可以使用 dataset.distinct("category.label") 从数据集中获取类别列表。

    • ROOT_DIR:Anomalib 将在其中查找图像和掩码的根目录。我们的数据已存储在磁盘上,因此我们只需将文件符号链接到 Anomalib 所需的目录即可。

    • TASK:我们正在执行的任务。我们将在本演练中使用“分段”。

    • IMAGE_SIZE:在训练模型之前调整图像的大小。我们将使用 256x 256 像素。

OBJECT = "bottle"  ## 要训练的对象ROOT_DIR = Path( "/tmp/mvtec_ad" ) ## 用于存储 anomalib 数据的根目录TASK = TaskType.SEGMENTATION ## 模型的任务类型IMAGE_SIZE = ( 256 , 256 ) ## 预处理图像大小以保证均匀性

    对于给定的对象类型(类别),create_datamodule()下面的函数会创建一个 AnomalibDataModule对象。这将被传递到我们引擎的fit()方法来训练模型,并用于实例化数据加载器以进行训练和验证。

    代码可能看起来很复杂,所以让我们分解一下发生了什么:

    • 我们创建的数据子集仅包含“良好”的训练图像和“异常”图像以供验证。

    • 我们将图像和掩码符号链接到 Anomalib 期望的目录。

    • 我们从 Anomalib 实例化并设置一个数据模块Folder,它是自定义数据集的通用类。

    💡 也可以DataLoader从头开始创建一个 torch 并将其传递给引擎的fit()方法。这可以让你更好地控制数据加载过程。这留给读者练习 😉。

def create_datamodule(object_type, transform=None):    ## Build transform    if transform is None:        transform = Resize(IMAGE_SIZE, antialias=True)
    normal_data = dataset.match(F("category.label") == object_type).match(        F("split") == "train"    )    abnormal_data = (        dataset.match(F("category.label") == object_type)        .match(F("split") == "test")        .match(F("defect.label") != "good")    )
    normal_dir = Path(ROOT_DIR) / object_type / "normal"    abnormal_dir = ROOT_DIR / object_type / "abnormal"    mask_dir = ROOT_DIR / object_type / "mask"
    # create directories if they do not exist    os.makedirs(normal_dir, exist_ok=True)    os.makedirs(abnormal_dir, exist_ok=True)    os.makedirs(mask_dir, exist_ok=True)
    if not os.path.exists(str(normal_dir)):        normal_data.export(            export_dir=str(normal_dir),            dataset_type=fo.types.ImageDirectory,            export_media="symlink",        )
    for sample in abnormal_data.iter_samples():        base_filename = sample.filename        dir_name = os.path.dirname(sample.filepath).split("/")[-1]        new_filename = f"{dir_name}_{base_filename}"        if not os.path.exists(str(abnormal_dir / new_filename)):            ## symlink anomalous image into Anomalib abnormal dir            os.symlink(sample.filepath, str(abnormal_dir / new_filename))

        if not os.path.exists(str(mask_dir / new_filename)):            ## symlink mask into Anomalib mask dir            os.symlink(sample.defect_mask.mask_path, str(mask_dir / new_filename))

    ## Create a DataModule in Anomalib    datamodule = Folder(        name=object_type,        root=ROOT_DIR,        normal_dir=normal_dir,        abnormal_dir=abnormal_dir,        mask_dir=mask_dir,        task=TASK,        transform=transform    )    datamodule.setup()    return datamodule

    现在,我们可以将所有内容整合在一起。train_and_export_model()下面的函数使用 Anomalib 的类训练异常检测模型Engine,将模型导出到 OpenVINO,并返回模型“推理器”对象。推理器对象用于对新图像进行预测。

def  train_and_export_model ( object_type, model, transform= None ):     ## 在我们的数据上训练模型    datamodule = create_datamodule(object_type, transform=transform)     engine = Engine(task=TASK)     engine.fit(model=model, datamodule=datamodule) 
    ## 将模型导出为 OpenVINO 格式以进行快速推理    engine.export(         model=model,         export_type=ExportType.OPENVINO,     )     output_path = Path(engine.trainer.default_root_dir) 
    openvino_model_path = output_path / "weights" / "openvino" / "model.bin"     metadata = output_path / "weights" / "openvino" / "metadata.json" 

    ## 从导出加载推理对象 inferencer     = OpenVINOInferencer(         path=openvino_model_path,         metadata=metadata,         device= "CPU" ,     )     return inferencer

    我们先尝试PaDiM一下。训练过程应该不到一分钟:

model = Padim()
inferencer = train_and_export_model(OBJECT, model)

    就这样,我们就有了一个针对“瓶子”类别进行训练的异常检测模型。让我们在单个图像上运行推理器并检查结果:

## get the test split of the datasettest_split = dataset.match(F("category.label") == OBJECT).match(F("split") == "test")
## get the first sample from the test splittest_image = Image.open(test_split.first().filepath)
output = inferencer.predict(image=test_image)print(output)
ImageResult(image=[[[255 255 255]  [255 255 255]  [255 255 255]  ...  [255 255 255]  [255 255 255]  [255 255 255]]
  ...  [255 255 255]  [255 255 255]  [255 255 255]]], pred_score=0.7751642969087686, pred_label=1, anomaly_map=[[0.32784402 0.32784402 0.32784414 ... 0.3314721  0.33147204 0.33147204] [0.32784402 0.32784402 0.32784414 ... 0.3314721  0.33147204 0.33147204] [0.32784408 0.32784408 0.3278442  ... 0.33147222 0.33147216 0.33147216] ... [0.32959    0.32959    0.32959005 ... 0.3336093  0.3336093  0.3336093 ] [0.3295899  0.3295899  0.32958996 ... 0.33360928 0.33360928 0.33360928] [0.3295899  0.3295899  0.32958996 ... 0.33360928 0.33360928 0.33360928]], gt_mask=None, gt_boxes=None, pred_boxes=None, box_labels=None, pred_mask=[[0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] ... [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0] [0 0 0 ... 0 0 0]], heat_map=[[[153 235 255]  [153 235 255]  [153 235 255]  ...  [153 236 255]  [153 236 255]  [153 236 255]]  ...  [153 238 255]  [153 238 255]  [153 238 255]]], segmentations=[[[255 255 255]  [255 255 255]  [255 255 255]  ...  [255 255 255]  [255 255 255]  [255 255 255]]  ...  [255 255 255]  [255 255 255]  [255 255 255]]])

    输出包含一个标量异常分数pred_score、一个pred_mask表示预测异常区域的和一个显示每个像素异常分数的热图 anomaly_map。这些都是理解模型预测的宝贵信息。

    下面的函数run_inference()将以 FiftyOne 样本集合(例如我们的测试集)作为输入,以及推理器对象和用于将结果存储在样本中的键。它将对集合中的每个样本运行模型并存储结果。阈值参数充当异常分数的截止值。如果分数高于阈值,则样本被视为异常。在此示例中,我们将使用 0.5 的阈值,但您可以尝试使用不同的值。

def run_inference(sample_collection, inferencer, key, threshold=0.5):    for sample in sample_collection.iter_samples(autosave=True, progress=True):        output = inferencer.predict(image=Image.open(sample.filepath))
        conf = output.pred_score        anomaly = "normal" if conf < threshold else "anomaly"
        sample[f"pred_anomaly_score_{key}"] = conf        sample[f"pred_anomaly_{key}"] = fo.Classification(label=anomaly)        sample[f"pred_anomaly_map_{key}"] = fo.Heatmap(map=output.anomaly_map)        sample[f"pred_defect_mask_{key}"] = fo.Segmentation(mask=output.pred_mask)

    让我们对测试分割进行推理,并在 FiftyOne 应用程序中可视化结果:

run_inference(test_split, inferencer, "padim")session = fo.launch_app(view=test_split)

图片

评估异常检测模型

    我们有一个异常检测模型,但我们如何知道它是否好用?首先,我们可以使用精度、召回率和 F1 分数指标来评估模型。FiftyOne 的评估 API使这变得简单。我们将评估模型的全图像分类性能以及分割性能。

    我们需要准备评估数据。首先,我们需要为“正常”图像添加空掩码,以确保评估公平:

for sample in test_split.iter_samples(autosave=True, progress=True):    if sample["defect"].label == "good":        sample["defect_mask"] = fo.Segmentation(            mask=np.zeros_like(sample["pred_defect_mask_padim"].mask)        )

    我们还需要确保真实值和预测值之间的命名/标签的一致性。我们将所有“良好”图像重命名为“正常”,并将每种类型的异常重命名为“异常”:

old_labels = test_split.distinct("defect.label")label_map = {label:"anomaly" for label in old_labels if label != "good"}label_map["good"] = "normal"mapped_view = test_split.map_labels("defect", label_map)session.view = mapped_view.view()

图片

    对于分类,我们将使用二元评估,其中“正常”为负类,“异常”为正类:

eval_classif_padim = mapped_view.evaluate_classifications(    "pred_anomaly_padim",    gt_field="defect",    eval_key="eval_classif_padim",    method="binary",    classes=["normal", "anomaly"],)eval_classif_padim.print_report()
               precision    recall  f1-score   support
      normal       0.95      0.90      0.92        20     anomaly       0.97      0.98      0.98        63
    accuracy                           0.96        83   macro avg       0.96      0.94      0.95        83weighted avg       0.96      0.96      0.96        83

比较异常检测模型

    虽然异常检测是无监督的,但这并不意味着我们不能比较模型并选择最适合我们用例的模型。我们可以在同一数据上训练多个模型,并使用 F1 分数、准确率和召回率等指标比较它们的性能。我们还可以通过检查它们生成的掩码和热图来直观地比较模型。

    我们重复一下PatchCore模型的训练过程,并比较一下这两个模型:

## 训练 Patchcore 模型并运行推理
model = Patchcore() 
## 这将需要更长的时间来训练,但仍应少于 5 分钟inferencer = train_and_export_model(OBJECT, model) 
run_inference(mapped_view, inferencer, "patchcore" ) 
## 在分类任务上评估 Patchcore 模型eval_classif_patchcore = tagged_view.evaluate_classifications(     "pred_anomaly_patchcore" ,     gt_field= "defect" ,     eval_key= "eval_classif_patchcore" ,     method= "binary" ,     classes=[ "normal" , "anomaly" ], ) 
eval_classif_patchcore.print_report()
              precision    recall  f1-score   support
      normal       0.95      1.00      0.98        20     anomaly       1.00      0.98      0.99        63
    accuracy                           0.99        83   macro avg       0.98      0.99      0.98        83weighted avg       0.99      0.99      0.99        83
eval_seg_patchcore = mapped_view.match(F("defect.label") == "anomaly").evaluate_segmentations(    "pred_defect_mask_patchcore",    gt_field="defect_mask",    eval_key="eval_seg_patchcore",)eval_seg_patchcore.print_report(classes=[0, 255])session.view = mapped_view.shuffle().view()
      precision    recall  f1-score   support
           0       0.99      0.95      0.97 47143269.0         255       0.60      0.85      0.70 3886731.0
   micro avg       0.95      0.95      0.95 51030000.0   macro avg       0.80      0.90      0.84 51030000.0weighted avg       0.96      0.95      0.95 51030000.0

    这些指标支持了我们在应用程序中看到的结果:PatchCore 对“异常”类别的召回率更高,但准确率较低。这意味着它更有可能发现异常,但也更有可能做出误报预测。毕竟,PatchCore 是为工业异常检测中的“全面召回”而设计的。

图片

    通过查看热图,我们还可以看到每个模型更擅长检测哪些类型的异常。两个模型的集合可能对不同类型的异常更具鲁棒性。

下一步是什么

    在本演练中,我们学习了如何使用 FiftyOne 和 Anomalib 对视觉数据执行异常检测。虽然我们训练了两个模型,但我们只触及了视觉异常检测的皮毛。

    如果你想提高性能,还有许多其他方法可以改变:

    • 算法:我们仅使用了 PaDiM 和 PatchCore。Anomalib 目前支持 13 种算法!

    • Backbone:用于特征提取的模型架构

    • 超参数:异常检测算法特有的参数。对于 PatchCore,这包括coreset_sampling_ratio和num_neighbors。

    • 数据增强:人为增加训练集的大小并提高模型泛化的技术。

    • 定制解决方案:引入新算法/技术永远不会太晚!

—THE END—

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

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

相关文章

突发!凌晨4点某制造业大厂国产数据库集群故障...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

02Linux文件,目录,过滤,管道常用命令

Linux基础概述 Linux基础目录 Linux没有盘符这个概念, 只有一个顶级根目录 /, 所有文件都在它下面 在Windows系统中路径之间的层级关系使用/来表示在Linux系统中路径之间的层级关系使用/来表示,出现在开头的/表示根目录, /home/a.txt表示根目录下的home文件夹内有a.txt文件 …

Android Studio 中文汉化教程

1. 中文语言包 一般jetbrains系列软件都可以使用“中文语言包”进行汉化&#xff0c;语言包如下图所示&#xff1a; 然而&#xff0c;Android Studio的Marketplace并没有类似的中文语言包&#xff08;如下图&#xff09;&#xff0c;经过查阅相关资料发现需要去jetbrains的插件…

像素着色技术在AI绘画中的革新作用

摘要&#xff1a;随着人工智能技术的不断进步&#xff0c;AI绘画已成为艺术和技术领域中的一个热门话题。本文将探讨像素着色技术在AI绘画中的应用及其对创作过程的影响&#xff0c;揭示这一技术如何推动艺术创作的革新。 引言&#xff1a; 传统的绘画方法要求艺术家具备高超的…

代码随想录算法训练营第36期DAY50

DAY50 如果写累了就去写套磁信吧。 198打家劫舍 class Solution {public: int rob(vector<int>& nums) { vector<int> dp(nums.size()); dp[0]nums[0]; if(nums.size()1) return nums[0]; dp[1]max(nums[0],nums[1]); …

嵌入式Linux中使用PWM应用编程详解

大家好,今天给大家分享一下,如何使用linux系统中PWM实现应用端的控制,以及PWM控制详解。 第一:PWM基本原理 PWM,英文名Pulse width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字…

【Python机器学习】PCA——特征提取(1)

PCA的一个重要应用是特征提取。特征提取背后的思想是&#xff0c;可以找到一种数据表示&#xff0c;比给定的原始表示更适合于分析。特征提取很有用&#xff0c;它的一个很好的应用实例就是图像。图像由像素组成&#xff0c;通常存储于红绿蓝强度。图像中的对象通常由上千个像素…

Python可视化 | 使用matplotlib绘制面积图示例

面积图是数据可视化中的一个有效工具&#xff0c;用于说明时间上的关系和趋势。它们提供了一种全面的、视觉上迷人的方法&#xff0c;通过熟练地将折线图的可读性与填充区域的吸引力相结合来呈现数值数据。 在本文中&#xff0c;我们将学习更多关于在Python中创建面积折线图的…

Polar Web【简单】PHP反序列化初试

Polar Web【简单】PHP反序列化初试 Contents Polar Web【简单】PHP反序列化初试思路EXP手动脚本PythonGo 运行&总结 思路 启动环境&#xff0c;显示下图中的PHP代码&#xff0c;于是展开分析&#xff1a; 首先发现Easy类中有魔术函数 __wakeup() &#xff0c;实现的是对成员…

时光正好保剑锋的抱治百病与成年人的世界

《时光正好》&#xff1a;保剑锋的“抱治百病”与成年人的世界在繁忙的都市里&#xff0c;每个角落上演着各自的人生戏码。而在这些戏码中&#xff0c;由保剑锋主演的《时光正好》无疑成为了近期引人注目的焦点。这部电视剧以其真实而深刻的剧情&#xff0c;让我们看到了成年人…

【教学嘞-64-01】20240607颜色找茬-0-255随机底色

背景需求 AI对话大师到期了&#xff0c;后续无法使用了&#xff0c;这是我用AI对话大师写的最后一个代码 代码展示 颜色找茬&#xff1a;找出不同颜色的色块 AI对话大师&#xff0c;阿夏 2025年6月7日from PIL import Image, ImageDraw import randompath rC:\Users\jg2yXRZ\…

vite常识性报错解决方案

1.导入路径不能以“.ts”扩展名结束。考虑改为导入“xxx.js” 原因&#xff1a;当你尝试从一个以 .ts 结尾的路径导入文件时&#xff0c;ESLint 可能会报告这个错误&#xff0c;因为它期望导入的是 JavaScript 文件&#xff08;.js 或 .jsx&#xff09;而不是 TypeScript 文件&…

毫米波雷达深度学习技术-1.6目标识别2

1.6.4 自动编码器和变体自动编码器 自编码器包括一个编码器神经网络&#xff0c;随后是一个解码器神经网络&#xff0c;其目的是在输出处重建输入数据。自动编码器的设计在网络中施加了一个瓶颈&#xff0c;它鼓励原始输入的压缩表示。通常&#xff0c;自编码器旨在利用数据中的…

cleanmymacx最新破解版百度云免费下载 cleanmymac激活码分享

CleanMyMac X4.15.3 中文破解版是一款 mac 系统清理软件&#xff0c;是一款专业的 Mac 清理、优化和维护工具&#xff0c;可以帮助用户清理系统垃圾、卸载不需要的应用程序、优化系统性能、保护隐私和安全等&#xff0c;让用户的 Mac 始终保持高效、安全和稳定。 CleanMyMac X4…

[经验] 涠洲岛在广西吗 #职场发展#知识分享#媒体

涠洲岛在广西吗 广西涠洲岛&#xff0c;是中国南海上的一颗闪亮明珠&#xff0c;位于广西北部湾沿海&#xff0c;东经108.71度&#xff0c;北纬21.54度&#xff0c;距离北海市区30公里&#xff0c;是中国最大的海岛之一&#xff0c;风景秀丽&#xff0c;气候温和。岛上山青水秀…

【进阶篇-Day2:JAVA包、抽象类、接口、多态】

目录 1、包2、抽象类2.1 抽象类的定义2.2 抽象类的注意事项2.3 abstract关键字的冲突 3、接口3.1 接口的介绍3.2 接口中的成员特点3.2.1 接口中的成员特点3.2.2 类和接口之间的各种关系 3.3 抽象类和接口的对比 4、多态4.1 多态的介绍4.2 多态的前提4.3 多态的成员访问特点4.4 …

Java线程本地变量ThreadLocal

ThreadLocal ThreadLocal有什么用 通常情况下&#xff0c;我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢&#xff1f; JDK中的ThreadLocal类正是为了解决这样的问题&#xff0c;ThreadLocal类主要解决的就是让每…

SpringBoot+Vue网上购物商城系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 系统角色对应功能 用户商家管理员 系统功能截图

测试基础11:测试用例设计方法-等价类划分

课程大纲 1、概述 1.1测试用例设计方法意义 穷举测试&#xff1a;每种输入都测一次。最完备&#xff0c;但不现实。 使用设计方法&#xff0c;用最少的数据&#xff08;成本&#xff09;&#xff0c;实现最大的测试覆盖。 1.2常用设计方法 ①等价类划分 ②边界值分析 ③错误推…

vue3自定义组件并全局导入

自定义组件&#xff1a;幕布 涉及的效果&#xff1a;点击出现幕布并且内容可变动 使用的技术&#xff1a;vue3的slot componentcomponents curtain&#xff08;组件页面&#xff09;&#xff1a; <template><div class"curtain"><slot></s…