图像万物分割——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++代码没有实现这一部分。

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

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

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

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

相关文章

WPF halcon 机器视觉

1 鼹鼠的故事第14集 鼹鼠与智能房 鼹鼠无意中坐进了一辆小汽车&#xff0c;小汽车开进了一所智能住宅。鼹鼠看到房主在智能房里&#xff0c;享受着现代化的服务。趁着主人看电视的时候&#xff0c;鼹鼠也享用了一顿丰盛的智能晚餐。 小编大胆的畅想&#xff0c;这些食物 前一秒…

【安卓】安卓xTS之Media模块 学习笔记(1) xTS介绍

0. 声明 因为安卓的迭代比较快&#xff0c;大框架更新也是常有的事&#xff0c; 在此单独声明&#xff0c;本系列内容适用于 安卓13/安卓14&#xff08;2023.12 左右&#xff09;。 更早的版本和往后的版本不做保证。 1.背景 Media的安卓xTS相关测试和功能修复已经进行了一段…

C语言小游戏:三子棋

目录 &#x1f30d;前言 &#x1f685;目录设计 &#x1f48e;游戏逻辑设置 ⚔三子棋棋盘设计 ⚔三子棋运行逻辑 &#x1f440;怎么设置人下棋 &#x1f440;怎么设置电脑下棋 ✈如何判断输赢 ✍结语 &#x1f30d;前言 Hello,csdn的各位小伙伴你们好啊!这次小赵给大…

问题:HTTP method names must be tokens

问题&#xff1a;HTTP method names must be tokens 2023-12-04T09:43:16.65408:00 INFO 7808 --- [nio-6600-exec-1] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request headerNote: further occurrences of HTTP request parsing errors will be logg…

freeRTOS创建任务

一.动态创建任务 1.函数xTaskCreate() BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数const char * const pcName, // 任务的名字const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节void * const pvParameters, // …

rust入门(rust教程、rust安装方法)

文章目录 Rust开发入门Rust的特性Rust的应用场景Rust安装——环境配置1. 安装rustup具体执行步骤 2. 验证安装 Rust的卸载基本语法变量与数据类型控制流函数 Rust的所有权系统错误处理实战&#xff1a;构建一个小项目创建新项目编写代码运行项目安装相关链接器运行 删除项目 Ru…

【云原生 | Docker】Docker核心概念 应用上手最佳流程

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

Unity版本使用情况统计(更新至2023年10月)

本期UWA发布的内容是第十三期Unity版本使用统计&#xff0c;统计周期为2023年5月至2023年10月&#xff0c;数据来源于UWA网站&#xff08;www.uwa4d.com&#xff09;性能诊断提测的项目。希望给Unity开发者提供相关的行业趋势&#xff0c;了解近半年来哪些Unity版本的使用概率更…

ESP32-Web-Server编程-通过 Web 下载文本

ESP32-Web-Server编程-通过 Web 下载文本 概述 当你希望通过网页导出设备的数据时&#xff0c;可以在 ESP32 上部署一个简单的文件 Web 服务器。 需求及功能解析 本节演示如何在 ESP32 上部署一个最简单的 Web 服务器&#xff0c;来接收浏览器或者 wget 指令请求文件数据。…

基于APM(PIX)飞控和mission planner制作遥控无人车-从零搭建自主pix无人车普通舵机转向无人车-1(以乐迪crossflight飞控为例)

1.前期准备 准备通过舵机转向的无人车地盘、遥控器、地面站电脑、飞控等。安装驱动程序、端口程序、netframwork等&#xff0c;不再赘述。 2.安装固件 安装ardurover固件&#xff0c;如果在线失败&#xff0c;选择官方最新的固件下载到本地&#xff0c;选择本地安装。 3.调试…

qt使用wimlib-imagex,做windows系统备份还原

wimlib-imagex是个第三方工具&#xff0c;可对系统映像进行操作&#xff0c;下载地址&#xff1a; https://wimlib.net/downloads/index.html 程序主要用到以下这两个文件&#xff1a;libwim-15.dll和wimlib-imagex.exe wimlib-imagex.exe的调用命令参数&#xff0c;可以通过…

python爬虫非对称加密RSA案例:某观鸟网站

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、找出需要加密的参数 js运行 atob(‘aHR0cDovL2JpcmRyZXBvcnQuY24vaG9tZS9hY3Rpdml0eS9wYWdlLmh0bWw’) 拿到网址&#xf…

【离散数学】——期末刷题题库(集合)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

如何使用Node.js快速创建本地HTTP服务器并实现异地远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

php之zip文件中压缩、解压、增加文件、删除

官方文档 PHP: Zip - Manual 在PHP中&#xff0c;zip扩展提供了处理ZIP文件的功能。它允许你创建、读取、更新和提取ZIP文件。 首先&#xff0c;确保你的PHP环境已经安装了zip扩展。你可以在php.ini文件中找到以下行来检查&#xff1a; extensionzip 如果没有注释掉&#xf…

Linux系统下Nginx的安装步骤

目录 Nginx简介Nginx的作用Nginx的安装方法方法一方法二方法三 本文主要介绍在Linux系统下&#xff0c;三种常见Nginx安装方法。 Nginx简介 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;也可以作为邮件代理服务器和通用的TCP/UDP代理服务器。它最初由Igor Sysoev创建…

10、SQL注入——数据库基础

文章目录 一、数据库概述二、数据库分类Mysql数据库连接方法 三、识别数据库四、SQL语法4.1 SQL基本语法4.2 高级操作 一、数据库概述 数据库&#xff08;database&#xff09;&#xff1a;存储在磁盘、磁带、光盘或其他外存介质上、按一定结构组织在一起的相关数据的集合。数…

JS浮点数精度问题及解决方案

前端面试大全JS浮点数精度问题及解决方案 &#x1f31f;经典真题 &#x1f31f;浮点数精度常见问题 &#x1f31f;为什么会有这样的问题 &#x1f31f;真题解答 &#x1f31f;总结 &#x1f31f;经典真题 为什么 console.log(0.20.10.3) 得到的值为 false &#x1f31f;…

kk(kubeadmin)在麒麟与centos安装k8s集群踩坑记录

4台机器的集群两台centos&#xff0c;两台麒麟v10 问题1.执行到集群和节点加入集群时候报错 ERRO[18:48:06 CST] Failed to add master to cluster: Failed to exec command: sudo env PATH$PATH:/sbin:/usr/sbin /bin/sh -c “/usr/local/bin/kubeadm join --config/etc/kube…

css实现正六边形嵌套圆心

要实现一个正六边形嵌套圆心&#xff0c;可以使用CSS的::before和::after伪元素以及border-radius属性。以下是具体的解析和代码&#xff1a; 使用::before和::after伪元素创建正六边形。设置正六边形的背景色。使用border-radius属性使正六边形的内角为60度。在正六边形内部创…
最新文章