图像万物分割——Segment Anything算法解析与模型推理

一、概述

在视觉任务中,图像分割任务是一个很广泛的领域,应用于交互式分割,边缘检测,超像素化,感兴趣目标生成,前景分割,语义分割,实例分割,泛视分割等。
交互式分割,这种分割任务,它允许用户手动细化掩码来分割任意类型的对象。然而,这种方法需要用户的不断参与和指导,类似于ps里面的抠图快速选择工具。
在这里插入图片描述

实例分割任务是它能够自动分割特定类别的对象,例如行人,狗,电视或椅子,但需要大量的手动标注数据,标注样本要以上万个样本,然后要经过大量的计算资源和代码算法知识来训练模型。这种方式应用最广泛应该是人像自动抠图:
在这里插入图片描述
为了 解决这些分割任务的局限性,Meta 推出了「分割一切」AI 算法Segment Anything,为分割任务提供一种通用的、全自动的分割解决方案。

二、Segment Anything 万物分割

1.算法摘要

作者介绍了Segment Anything (SA) 项目,这是一个旨在进行图像分割的新任务,同时提供了相应的模型和数据集。在该项目中,作者采用了一种高效的模型来进行数据收集,以构建迄今为止最大的分割数据集。他们在超过1,100万张公开的图像上进行了标注,生成了超过10亿个掩码。
这个模型(SAM)训练完成之后,以使其具备"promptable"(可提示)的性质,因此意味着它可以零样本(zero-shot)地适应新的数据集和任务,而无需先对数据进行标注和训练。作者对该模型进行了广泛的评估,发现它在许多任务上的零样本表现通常与完全监督的性能相媲美,甚至更好。作者公开了他们的模型(SAM),还发布了相应的图像数据集(SA-1B)。
在这里插入图片描述

2. 算法介绍

LLM的出现,让研究人员感受到,使用互联网规模的数据集上预训练的大型语言模型已经改变了自然语言处理(NLP)领域,因为它们表现出强大的零样本和少样本泛化能力,可以应对未在训练中出现的任务和数据分布。这种泛化通常通过提示工程(prompt engineering)来实现,其中手工制作的文本提示可以引导语言模型生成有效的文本响应。这些基础模型在使用丰富的互联网文本语料库进行预训练时,表现出令人惊讶的零样本和少样本性能,有时甚至可以与经过精细调整(fine-tune)的模型相媲美。研究经验表明,这种零样本和少样本性能会随着模型规模、数据集大小和总训练计算量的增加而改善。

在计算机视觉领域也在探索基础模型的应用,例如,CLIP和ALIGN使用对比学习来训练文本和图像编码器,经过训练后,这些编码器可以用于零样本泛化到新的视觉概念和数据分布。这些编码器还可以有效地与其他模块结合,用于解决下游任务,比如图像生成。然而,计算机视觉领域涉及的问题远不止这些,而且许多问题缺乏丰富的训练数据。

在这项研究工作中,SAM作者的目标是建立一个图像分割的基础模型,也就是一个可提示的模型,它可以在广泛的数据集上进行预训练以实现强大的泛化能力。一旦有了这个模型,作者进一步探索如何通过快速流程来解决各种新的数据分布上的下游分割问题。

这个计划的成功取决于三个关键要素:任务、模型和数据。作者需要解决以下关于图像分割的问题:

  1. 什么样的视觉分割任务可以实现零样本泛化?

  2. 为了实现这一个分割任务,对应的模型架构应该是什么样的?

  3. 哪些数据可以支持这个任务和模型的预训练?

作者首先定义了一个可提示的分割任务,这个任务足够通用,可以作为强大的预训练目标,同时也可以支持广泛的下游应用。这个任务要求一个支持多种提示的模型,并能够实时生成分割掩码,以支持交互式使用。然而,互联网上目前尚没有足够大规模的分割数据集来满足这个任务的需求。作者提出了“数据引擎”来应对这个问题,即通过模型辅助数据收集和不断迭代来改进数据,以填补数据的不足。这个方法可以在模型训练和数据收集之间进行交互,以实现更好的性能。

  • 分割任务
    在自然语言处理和计算机视觉领域,基础模型具有很大的前景,因为它们可以用于执行零样本学习和少样本学习,通过利用提示来适应新的数据集和任务。受到这种思路的启发,本文提出了一个称为"可提示分割任务"的新领域,其主要目标是在给定分割提示的情况下生成有效的分割掩码(如图1a所示)。

这些分割提示可以简单地指定图像中要分割的对象,例如,提示可以包括对象的位置信息或文本描述。
这里的"有效输出掩码"意味着,即使提示信息模糊不清,可能指向多个不同对象(例如,在图像上一个点可能表示衬衫或穿衬衫的人),生成的分割掩码也应该合理,至少应该包括这些对象中的一个。

在这项研究中,作者将可提示分割任务作为预训练目标,然后使用提示工程方法来解决各种不同的下游分割任务。这种方法有望为计算机视觉领域带来一种强大的学习范式,可以在面对新任务时从有限的提示信息中进行学习,而不需要大量的标记数据。这对于处理多样化和复杂的视觉任务可能具有很大的潜力。

  • 模型选择
    可提示分割任务对模型的架构提出了一些严格的要求,这包括对提示的支持灵活性、实时计算的需求,以便允许交互使用,以及能够处理歧义。作者提出了一个简单的模型设计,可以满足这些要求,被称为"Segment Anything"模型,简称SAM(见图1b)。SAM的架构包括以下组成部分:
  • 图像编码器:这是一个强大的模型,负责将输入图像转化为图像嵌入(image embedding),以捕捉图像的特征信息。
  • 提示编码器:这是一个用于嵌入提示信息的模型,它将提示信息转化为提示嵌入,以使模型能够理解提示中的内容。
  • 控码解码器:这是一个轻量级的模型,负责将图像嵌入和提示嵌入结合,然后预测分割掩码。这一部分的设计使得SAM可以实现对相同图像嵌入的不同提示信息的分配,从而使模型能够处理多样性的提示。

SAM的设计还允许它在不超过50毫秒的时间内从提示符中预测掩码,实现了实时性能,这对于实际应用和交互式任务非常重要。

作者的主要关注点包括边界框、关键点和分割掩码提示。为了解决歧义问题,SAM被设计成能够预测多个掩码,即使给定相同的提示。这使得SAM可以自然地处理提示中的歧义,比如前文提到的衬衫和穿衬衫的人之间的歧义示例。这个能力对于处理复杂的图像场景和多义性提示非常有帮助。

  • 数据引擎
    为了使SAM能够在新的数据分布上实现强大的泛化能力,需要在一个大型数据集上进行训练,该数据集应该覆盖各种不同的分割任务和场景。然而,典型的训练方法通常依赖于在线获取数据,而掩码标注信息通常相对稀缺,因此需要采用替代策略。作者提出的解决方案是构建一个称为"数据引擎"的系统,这个引擎包括三个主要阶段:辅助手动、半自动和全自动。
  1. 辅助手动阶段:在这个阶段,SAM与人工注释人员协作,类似于传统的交互式分割设置。人工注释人员手动为图像中的对象生成掩码,同时SAM提供辅助信息,例如提示信息,以帮助人工注释人员完成掩码的生成。这一阶段有助于收集一些基本的分割标注。

  2. 半自动阶段:在这个阶段,SAM能够自动为图像中的对象的某些子区域生成掩码。它会根据已有的掩码和提示信息,自动预测可能的对象位置,并生成相应的掩码。这减轻了人工注释人员的工作负担,因为他们可以专注于注释剩余的对象,从而提高了标注的多样性。

  3. 全自动阶段:在最后一个阶段,作者采用一种规则网格提示SAM,用于生成大量高质量掩码。这个提示方法能够为每张图像平均产生约100个掩码,以增加数据的多样性和覆盖不同的情况。

通过这种数据引擎的阶段性设计,作者能够有效地利用协作注释和自动化方法,以构建一个大规模的数据集,为SAM的训练提供了足够丰富和多样的标注数据,从而使其在新的数据分布上实现强大的泛化能力。这种方法有助于克服标注数据稀缺性的问题,尤其是对于复杂的分割任务。

3.数据集

作者最终的数据集SA-1B,包括来自1100万张经许可和隐私保护图像的超过10亿个掩码(见图2)。SA-1B使用作者的数据引擎的最后阶段完全自动收集,比现有的最大分割数据集拥有400多倍的掩码,并且作者广泛验证,掩码具有高质量和多样性。作者希望SA-1B能够成为一种有价值的资源,用于建立新的基础模型。

4.实验

作者广泛地评估SAM。首先,在23个分割数据集上的测试,作者发现SAM从单个前景点生成了高质量的掩码,通常仅略低于手动注释的真实值。其次,作者在使用提示工程的零样本传输协议(zero-shot transfer protocol)下的各种下游任务上发现了持续强大的定量和定性结果,包括边缘检测、感兴趣目标生成、实例分割和文本到掩码预测。这些结果表明,SAM可以在即时工程中开箱即用,解决涉及SAM训练数据之外的图像分布的各种任务。

三、模型C++推理

1.实现代码

#include "include/segment_anything.h"
namespace sam{
SegmentAnything::~SegmentAnything()
{
    image_encoder_net_.clear();
    mask_decoder_net_.clear();
}

static inline float intersection_area(const sam_result_t& a, const sam_result_t& b)
{
    cv::Rect_<float> inter = a.box & b.box;
    return inter.area();
}

static void qsort_descent_inplace(std::vector<sam_result_t>& faceobjects, int left, int right)
{
    int i = left;
    int j = right;
    float p = faceobjects[(left + right) / 2].iou_pred;

    while (i <= j)
    {
        while (faceobjects[i].iou_pred > p)
            i++;

        while (faceobjects[j].iou_pred < p)
            j--;

        if (i <= j)
        {
            // swap
            std::swap(faceobjects[i], faceobjects[j]);

            i++;
            j--;
        }
    }

    #pragma omp parallel sections
    {
        #pragma omp section
        {
            if (left < j) qsort_descent_inplace(faceobjects, left, j);
        }
        #pragma omp section
        {
            if (i < right) qsort_descent_inplace(faceobjects, i, right);
        }
    }
}

static void qsort_descent_inplace(std::vector<sam_result_t>& faceobjects)
{
    if (faceobjects.empty())
        return;

    qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
}

static void nms_sorted_bboxes(const cv::Mat& bgr,const std::vector<sam_result_t>& faceobjects, std::vector<int>& picked, float nms_threshold)
{
    picked.clear();

    const int n = faceobjects.size();

    std::vector<float> areas(n);
    for (int i = 0; i < n; i++)
    {
        areas[i] = faceobjects[i].box.area();
    }
    cv::Mat img = bgr.clone();
    for (int i = 0; i < n; i++)
    {
        const sam_result_t& a = faceobjects[i];

        int keep = 1;
        for (int j = 0; j < (int)picked.size(); j++)
        {
            const sam_result_t& b = faceobjects[picked[j]];

            // intersection over union
            float inter_area = intersection_area(a, b);
            float union_area = areas[i] + areas[picked[j]] - inter_area;
            // float IoU = inter_area / union_area
            if (inter_area / union_area > nms_threshold){
                keep = 0;
            }
                
        }

        if (keep)
            picked.push_back(i);
    }
}
int SegmentAnything::NMS(const cv::Mat& bgr, std::vector<sam_result_t>& proposals, std::vector<int>& picked, float nms_threshold)
{
    qsort_descent_inplace(proposals);
    nms_sorted_bboxes(bgr, proposals, picked, nms_threshold);
    
    return 0;
}

int SegmentAnything::Load(const std::string& image_encoder_param, const std::string& image_encoder_bin, const std::string& mask_decoder_param, const std::string& mask_decoder_bin)
{
    int ret = 0;
    ret = image_encoder_net_.load_param(image_encoder_param.c_str());
    if (ret < 0)
        return -1;
    ret = image_encoder_net_.load_model(image_encoder_bin.c_str());
    if (ret < 0)
        return -1;
    ret = mask_decoder_net_.load_param(mask_decoder_param.c_str());
    if (ret < 0)
        return -1;
    ret = mask_decoder_net_.load_model(mask_decoder_bin.c_str());
    if (ret < 0)
        return -1;

    return 0;
}
int SegmentAnything::ImageEncoder(const cv::Mat& bgr, ncnn::Mat& image_embeddings, image_info_t& image_info)
{
    const int target_size = 1024;
    int img_w = bgr.cols;
    int img_h = bgr.rows;

    int w = img_w;
    int h = img_h;
    float scale = 1.f;
    if (w > h)
    {
        scale = (float)target_size / w;
        w = target_size;
        h = h * scale;
    }
    else
    {
        scale = (float)target_size / h;
        h = target_size;
        w = w * scale;
    }

    ncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_BGR2RGB, img_w, img_h, w, h);

    int wpad = target_size - w;
    int hpad = target_size - h;
    ncnn::Mat in_pad;
    ncnn::copy_make_border(in, in_pad, 0, hpad, 0, wpad, ncnn::BORDER_CONSTANT, 0.f);

    in_pad.substract_mean_normalize(means_, norms_);

    ncnn::Extractor image_encoder_ex = image_encoder_net_.create_extractor();

    image_encoder_ex.input("image", in_pad);
    image_encoder_ex.extract("image_embeddings", image_embeddings);

    image_info.img_h = img_h;
    image_info.img_w = img_w;
    image_info.pad_h = h;
    image_info.pad_w = w;
    image_info.scale = scale;

    return 0;
}

int SegmentAnything::embed_masks(const prompt_info_t& prompt_info, ncnn::Mat& mask_input, ncnn::Mat& has_mask)
{
    mask_input = ncnn::Mat(256, 256, 1);
    mask_input.fill(0.f);
    has_mask = ncnn::Mat(1);
    has_mask.fill(0.f);

    return 0;
}
int SegmentAnything::transform_coords(const image_info_t& image_info, ncnn::Mat& point_coords)
{
    for(int h = 0; h < point_coords.h; ++h){
        float* ptr = point_coords.row(h);
        ptr[0] *= image_info.scale;
        ptr[1] *= image_info.scale;
    }

    return 0;
}
int SegmentAnything::embed_points(const prompt_info_t& prompt_info, std::vector<ncnn::Mat>& point_labels, ncnn::Mat& point_coords)
{
    int num_points = prompt_info.points.size() / 2;
    point_coords = ncnn::Mat(num_points * 2, (void*)prompt_info.points.data()).reshape(2, num_points).clone();

    ncnn::Mat point_labels1 = ncnn::Mat(256, num_points);
    ncnn::Mat point_labels2 = ncnn::Mat(256, num_points);
    ncnn::Mat point_labels3 = ncnn::Mat(256, num_points);
    ncnn::Mat point_labels4 = ncnn::Mat(256, num_points);
    ncnn::Mat point_labels5 = ncnn::Mat(256, num_points);
    ncnn::Mat point_labels6 = ncnn::Mat(256, num_points);

    point_labels1.row_range(0, num_points - 1).fill(1.f);
    point_labels1.row_range(num_points - 1, 1).fill(0.f);

    for (int i = 0; i < num_points - 1; ++i) {
        if (prompt_info.labels[i] == -1)
            point_labels2.row_range(i, 1).fill(1.f);
        else
            point_labels2.row_range(i, 1).fill(0.f);
    }
    point_labels2.row_range(num_points - 1, 1).fill(1.f);

    for (int i = 0; i < num_points - 1; ++i) {
        if (prompt_info.labels[i] == 0)
            point_labels3.row_range(i, 1).fill(1.f);
        else
            point_labels3.row_range(i, 1).fill(0.f);
    }
    point_labels3.row_range(num_points - 1, 1).fill(0.f);

    for (int i = 0; i < num_points - 1; ++i) {
        if (prompt_info.labels[i] == 1)
            point_labels4.row_range(i, 1).fill(1.f);
        else
            point_labels4.row_range(i, 1).fill(0.f);
    }
    point_labels4.row_range(num_points - 1, 1).fill(0.f);

    for (int i = 0; i < num_points - 1; ++i) {
        if (prompt_info.labels[i] == 2)
            point_labels5.row_range(i, 1).fill(1.f);
        else
            point_labels5.row_range(i, 1).fill(0.f);
    }
    point_labels5.row_range(num_points - 1, 1).fill(0.f);

    for (int i = 0; i < num_points - 1; ++i) {
        if (prompt_info.labels[i] == 3)
            point_labels6.row_range(i, 1).fill(1.f);
        else
            point_labels6.row_range(i, 1).fill(0.f);
    }
    point_labels6.row_range(num_points - 1, 1).fill(0.f);

    point_labels.push_back(point_labels1);
    point_labels.push_back(point_labels2);
    point_labels.push_back(point_labels3);
    point_labels.push_back(point_labels4);
    point_labels.push_back(point_labels5);
    point_labels.push_back(point_labels6);

    return 0;
}
int SegmentAnything::MaskDecoder(const ncnn::Mat& image_embeddings, image_info_t& image_info, 
    const prompt_info_t& prompt_info, std::vector<sam_result_t>& sam_results, float pred_iou_thresh, float stability_score_thresh)
{
    std::vector<ncnn::Mat> point_labels;
    ncnn::Mat point_coords;
    embed_points(prompt_info, point_labels, point_coords);

    transform_coords(image_info, point_coords);

    ncnn::Mat mask_input, has_mask;
    embed_masks(prompt_info, mask_input, has_mask);

    ncnn::Extractor mask_decoder_ex = mask_decoder_net_.create_extractor();
    mask_decoder_ex.input("mask_input", mask_input);
    mask_decoder_ex.input("point_coords", point_coords);
    mask_decoder_ex.input("point_labels1", point_labels[0]);
    mask_decoder_ex.input("point_labels2", point_labels[1]);
    mask_decoder_ex.input("point_labels3", point_labels[2]);
    mask_decoder_ex.input("point_labels4", point_labels[3]);
    mask_decoder_ex.input("point_labels5", point_labels[4]);
    mask_decoder_ex.input("point_labels6", point_labels[5]);
    mask_decoder_ex.input("image_embeddings", image_embeddings);
    mask_decoder_ex.input("has_mask_input", has_mask);

    ncnn::Mat scores;
    mask_decoder_ex.extract("scores", scores);

    ncnn::Mat masks;
    mask_decoder_ex.extract("masks", masks);

    //postprocess
    std::vector<std::pair<float, int>> scores_vec;
    for (int i = 1; i < scores.w; ++i) {
        scores_vec.push_back(std::pair<float, int>(scores[i], i));
    }

    std::sort(scores_vec.begin(), scores_vec.end(), std::greater<std::pair<float, int>>());

    if (scores_vec[0].first > pred_iou_thresh) {
        sam_result_t sam_result;
        ncnn::Mat mask = masks.channel(scores_vec[0].second);
        cv::Mat cv_mask_32f = cv::Mat::zeros(cv::Size(mask.w, mask.h), CV_32F);
        std::copy((float*)mask.data, (float*)mask.data + mask.w * mask.h, (float*)cv_mask_32f.data);
        
        cv::Mat single_mask_32f;
        cv::resize(cv_mask_32f(cv::Rect(0, 0, image_info.pad_w, image_info.pad_h)), single_mask_32f, cv::Size(image_info.img_w,image_info.img_h), 0, 0, 1);

        float stable_score = calculate_stability_score(single_mask_32f);
        if (stable_score < stability_score_thresh)
            return -1;

        single_mask_32f = single_mask_32f > 0;
        single_mask_32f.convertTo(sam_result.mask, CV_8UC1, 1, 0);
        
        if (postprocess_mask(sam_result.mask, sam_result.box) < 0)
            return -1;

        sam_results.push_back(sam_result);
    }
    else {
        return -1;
    }

    return 0;
}
int SegmentAnything::postprocess_mask(cv::Mat& mask, cv::Rect& box)
{
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(mask.clone(), contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    if(contours.size() == 0)
        return -1;

    if (contours.size() > 1) {
        float max_area = 0;
        int max_idx = 0;
        std::vector<std::pair<float,int>> areas;
        for (size_t i = 0; i < contours.size(); ++i) {
            float area = cv::contourArea(contours[i]);
            if (area > max_area) {
                max_idx = i;
                max_area = area;
            }
            areas.push_back(std::pair<float,int>(area,i));
        }
        
        for (size_t i = 0; i < areas.size(); ++i) {
            //if (i == max_idx)
            //    continue;
            //else {
            //    cv::drawContours(mask, contours, i, cv::Scalar(0), -1);
            //}
            if(areas[i].first < max_area * 0.3){
                cv::drawContours(mask, contours, i, cv::Scalar(0), -1);
            }
            else{
                box = box | cv::boundingRect(contours[i]);
            }
        }
    }
    else {
        box = cv::boundingRect(contours[0]);
    }
    return 0;
}
float SegmentAnything::calculate_stability_score(cv::Mat& mask, float mask_threshold, float stable_score_offset)
{
    float intersections = (float)cv::countNonZero(mask > (mask_threshold + stable_score_offset));
    float unions = (float)cv::countNonZero(mask > (mask_threshold - stable_score_offset));
    
    return intersections / unions;
}
}

2. 交互方法

分割交互方式中有好四种,开放式点,可以多个点组合, 矩形框, 分割一切,还有文字提示这几种方式。但文字提示效果不太稳定,C++代码没有实现这一部分。

开放式点
点击要分割目标的中间,分割包含该点的物体,会按最小分割的结果展示出来,如果想分割的物体大于展示的结果,可以在物体的其他部分也点击下:
在这里插入图片描述
选择矩形框
使用鼠标拖动在目标选择,分割目标:
在这里插入图片描述

分割一切
将图片中所有物体的分割都展示出来:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(c语言进阶)offsetof函数——计算结构体元素的偏移量

一.基本概念&#xff1a; 头文件&#xff1a;<stddef.h> offsetof(结构体名,结构体元素名) 返回值为size_t&#xff08;unsigned int&#xff09; 二.应用 例题&#xff1a; #include<stdio.h> #include<stddef.h> typedef struct S1 {char a;int b;c…

Clean 架构下的现代 Android 架构指南

Clean 架构下的现代 Android 架构指南 Clean 架构是 Uncle Bob 提出的一种软件架构&#xff0c;Bob 大叔同时也是 SOLID 原则的命名者。 Clean 架构图如下&#xff1a; 这张图描述的是整个软件系统的架构&#xff0c;而不是单体软件&#xff0c;其中至少包括服务端以及客户端…

JVM类加载全过程

Java虚拟机类加载的全过程&#xff0c;即加载&#xff0c;验证&#xff0c;准备&#xff0c;解析&#xff0c;初始化 一、加载 加载 是 类加载过程中的一个阶段&#xff0c; 有以下三部分组成 1&#xff09;通过一个类的全限定名来获取定义此类的二进制流 2&#xff09;将这…

【Java 基础】19 多线程基础

文章目录 进程和线程进程&#xff08;Process&#xff09;线程&#xff08;Thread&#xff09; 线程的创建1&#xff09;继承 Thread 类2&#xff09;实现 Runnable 接口3&#xff09;使用 Lambda 表达式4&#xff09;总结 线程的状态状态的分类状态间转换 多线程是一种 同时执…

Github无法打开

文章目录 一、问题二、解决2.1、科学上网&#xff08;使用中&#xff09;2.2、使用代理&#xff08;不稳定&#xff09;2.3、修改hosts&#xff08;得更新&#xff09;2.3.1、找到hosts文件2.3.2、复制hosts文件2.3.3、添加记录2.3.4、替换原来的hosts文件2.3.5、成功访问Githu…

CefSharp 获取POST(AJAX)、GET消息返回值(request)

CefSharp作为专门为爬虫工具开发的库比Selenium这种开发目的是页面测试工具然后用来做爬虫的工具要贴心得多。我们操作网页的时候发送或者做了某个动作提交表单之后需要知道我们的动作或者提交是否成功&#xff0c;因为有的页面会因为网络延迟问题提交失败&#xff0c;需要准确…

[Azure]azure磁盘加密(Windows/Linux) ADE(Azure Disk Encryption)

Azure 磁盘加密用于保护数据&#xff0c;对于Windows使用BitLocker对磁盘进行加密&#xff0c;同时与Key Vault集成&#xff0c;控制和管理Key和Secret。 本文利用Potal对磁盘进行加密 注&#xff1a;Azure DIsk Encryption 可能会导致VM重启&#xff0c;对VM造成影响&#xff…

哈希与哈希表

哈希表的概念 哈希表又名散列表&#xff0c;官话一点讲就是&#xff1a; 散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据关键码值(Key value)而直接进行访问的数据结构。也就是说&#xff0c;它通过把关键码值映射到表中一个位置来访问记…

基于SSM的老年公寓信息管理的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

nodejs微信小程序+python+PHP天天网站书城管理系统的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

QT 中 QMessageBox 的简单用法

效果 思路 // 创建一个question弹出对话框&#xff0c;添加两个按钮&#xff1a;Yes和NoQMessageBox *box new QMessageBox(QMessageBox::Question, "提示", "确认删除的信息吗&#xff1f;", QMessageBox::Yes | QMessageBox::No, this);box->button(…

成人学生钢笔练字快速入门,硬笔书法行书楷书教程合集

一、教程描述 虽然现在都是电脑打字&#xff0c;需要手写的场合越来越少&#xff0c;但是可以写一手人见人爱&#xff0c;花见花开的好字&#xff0c;仍然是很拉风很惊艳的&#xff0c;可以给人留下深刻印象。本套硬笔书法教程&#xff0c;大小40.90G&#xff0c;共有591个文件…

Python 网络爬虫(二):HTTP 基础知识

《Python入门核心技术》专栏总目录・点这里 文章目录 1. HTTP 协议简述2. HTTP 请求过程3. HTTP 的结构3.1 请求行3.2 请求头3.3 请求体3.4 状态行3.5 响应头3.6 响应体 4. Cookie 状态管理5. HTTP 请求示例6. 总结 大家好&#xff0c;我是水滴~~ 在准备学习网络爬虫之前&…

公有云迁移研究——AWS Route53

大纲 1 什么是Route 532 Route 53能做些什么# 3 通过DNS托管来实现分流3.1 创建DNS托管3.2 对托管创建记录对流量进行分配 4 通过流量策略来对流量进行分流4.1 创建流量策略 5 对比两者的区别6 推荐 在给客户从本地机房往AWS迁移的过程中&#xff0c;我们接到如下需求&#xff…

vue打印功能

安装 vue3-print-nb yarn add vue3-print-nb //或 npm install vue3-print-nb main.js中引入 vue3-print-nb import { createApp } from vue; import App from ./App.vue; const app createApp(App); // 打印插件 import print from vue3-print-nb app.use(print) // 页面…

公有云迁移研究——AWS Translate

大纲 1 什么是Translate2 Aws Translate是怎么运作的3 Aws Translate和Google Translate的区别4 迁移任务4.1 迁移原因 5 Aws Translate的Go demo6 迁移中遇到的问题6.1 账号和权限问题&#xff1a;6.2 小语种 1 什么是Translate Translate是一种文本翻译服务&#xff0c;它使…

制作一个RISC-V的操作系统三-编译与链接

文章目录 GCCGCC简介GCC的命令格式gcc -Egcc -cgcc -Sgcc -ggcc -vGCC的主要执行步骤GCC涉及的文件类型针对多个源文件的处理 ELFELF介绍ELF文件格式ELF文件处理相关工具&#xff1a;Binutils&#xff08;binary utility&#xff09;readlelf -hreadelf -S或readelf -SW&#x…

思维模型 路径依赖定律

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。难以摆脱的惯性。 1 路径依赖定律的应用 1.1 打破路径依赖定律的苹果 在 20 世纪 80 年代&#xff0c;苹果公司推出了 Macintosh 电脑&#xff0c;这是一款具有图形用户界面和鼠标的创新产…

MacOS M芯片 安装MySQL5.7教程

目录 1. 安装Homebrew1.1 快速安装1.2 检查是否安装成功 2. 通过Homebrew安装MySQL2.1 搜索 MySQL 版本2.2 安装MySQL 5.72.3 位置说明2.4 启动MySQL服务2.5 检查服务状态2.6 设置环境变量2.7 重置密码 3. 测试安装 1. 安装Homebrew 1.1 快速安装 /bin/bash -c "$(curl …

SQL Server 数据库,多表查询

4.2使用T-SQL实现多表查询 前面讲述过的所有查询都是基于单个数据库表的查询&#xff0c;如果一个查询需要对多个表进行操作&#xff0c; 就称为联接查询&#xff0c;联接查询的结果集或结果称为表之间的联接。 联接查询实际上是通过各个表之间共同列的关联性来查询数据的&…