【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Prompt Encoder

news2024/11/19 6:14:16

  论文:Segment Anything
  代码:https://github.com/facebookresearch/segment-anything

  上一篇:【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Image Encoder

  本篇示例依然采用上一篇的狗狗图像运行代码,预测部分代码如下:

input_point = np.array([[1300, 800]])   # 输入point的坐标
input_label = np.array([1])   # label=1表示前景, label=0表示背景
# 输入box的坐标,(700,400)为左上角坐标, (1900,1100)为右下角坐标
input_box = np.array([[700, 400, 1900, 1100]])   
# 调用预测函数
masks, scores, logits = predictor.predict(
    point_coords=input_point,
    point_labels=input_label,
    box=input_box,
    multimask_output=True,
)

1. Mask预测过程

(1)predict函数

位置:【segment_anything/predictor.py --> SamPredictor类 -->predict函数】
作用: 使用给定的prompt,调用predict_torch,预测mask与iou

def predict(
    self,
    point_coords: Optional[np.ndarray] = None,
    point_labels: Optional[np.ndarray] = None,
    box: Optional[np.ndarray] = None,
    mask_input: Optional[np.ndarray] = None,
    multimask_output: bool = True,
    return_logits: bool = False,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    
    if not self.is_image_set:
        raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")

    # Transform input prompts
    coords_torch, labels_torch, box_torch, mask_input_torch = None, None, None, None
    
    # 若prompt为point
    if point_coords is not None:
        assert (
            point_labels is not None
        ), "point_labels must be supplied if point_coords is supplied."
        # 原始point_coords:[x,y]给定的坐标点=(1300,800)
        # self.original_size原始图像大小=(1365,2048)
        # 由于图像缩放为1024, 给定坐标应随之变换, 变换后point_coords:[X,Y]=(650, 400.29)
        point_coords = self.transform.apply_coords(point_coords, self.original_size)  
        # 将变换后的坐标[650, 400.29]以及前景与背景的标签转化为tensor
        coords_torch = torch.as_tensor(point_coords, dtype=torch.float, device=self.device)
        labels_torch = torch.as_tensor(point_labels, dtype=torch.int, device=self.device)
        # 加一个维度使得coords_torch.size():[1,1,2], labels_torch.size():[1,1]
        coords_torch, labels_torch = coords_torch[None, :, :], labels_torch[None, :]
        
    # 若prompt为box
    if box is not None:
    	# 同样对box坐标进行变换, (700, 400, 1900, 1100)->(350, 200.1465, 950, 500.4029)
        box = self.transform.apply_boxes(box, self.original_size) 
        # 转换为tensor, box_torch.size():[1,4]
        box_torch = torch.as_tensor(box, dtype=torch.float, device=self.device)  
        box_torch = box_torch[None, :]  # 加一个维度使得box_torch.size():[1,1,4]
    
    # 若prompt为mask
    if mask_input is not None:
        mask_input_torch = torch.as_tensor(mask_input, dtype=torch.float, device=self.device)
        mask_input_torch = mask_input_torch[None, :, :, :]
	
	# masks.size():[1,3,1365,2048], iou_predictions.size():[1,3], low_res_masks.size():[1,3,256,256]
    masks, iou_predictions, low_res_masks = self.predict_torch(
        coords_torch,
        labels_torch,
        box_torch,
        mask_input_torch,
        multimask_output,
        return_logits=return_logits,
    )

    masks_np = masks[0].detach().cpu().numpy()
    iou_predictions_np = iou_predictions[0].detach().cpu().numpy()
    low_res_masks_np = low_res_masks[0].detach().cpu().numpy()
    return masks_np, iou_predictions_np, low_res_masks_np

   apply_coords函数: 对输入point进行坐标变换,将图像 [ H , W ] {[H, W]} [H,W]给定坐标位置 [ x , y ] {[x, y]} [x,y],映射到变换图像 [ H ∗ 1024 / W , 1024 ] {[H*1024/W, 1024]} [H1024/W,1024]上的位置 [ X , Y ] {[X, Y]} [X,Y]

  def apply_coords(self, coords: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray:
        old_h, old_w = original_size   # [H, W]
        new_h, new_w = self.get_preprocess_shape(
            original_size[0], original_size[1], self.target_length
        )   # [H*1024/W, 1024]
        coords = deepcopy(coords).astype(float)   # 输入坐标[x, y]
        # 将给定坐标位置[x, y]映射到变换图像[H*1024/W, 1024]上的位置[X, Y]
        coords[..., 0] = coords[..., 0] * (new_w / old_w)
        coords[..., 1] = coords[..., 1] * (new_h / old_h)
        return coords

   apply_boxes函数: 调用 apply_coords函数进行box的坐标变换

def apply_boxes(self, boxes: np.ndarray, original_size: Tuple[int, ...]) -> np.ndarray:
    boxes = self.apply_coords(boxes.reshape(-1, 2, 2), original_size)
    return boxes.reshape(-1, 4)

(2)predict_torch函数

位置:【segment_anything/predictor.py --> SamPredictor类 -->predict_torch函数】
作用: 调用prompt_encoder实现prompt嵌入编码,调用mask_decoder实现mask预测

def predict_torch(
    self,
    point_coords: Optional[torch.Tensor],
    point_labels: Optional[torch.Tensor],
    boxes: Optional[torch.Tensor] = None,
    mask_input: Optional[torch.Tensor] = None,
    multimask_output: bool = True,
    return_logits: bool = False,
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:

    if not self.is_image_set:
        raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")

    if point_coords is not None:
        points = (point_coords, point_labels)
    else:
        points = None

    # Embed prompts
    sparse_embeddings, dense_embeddings = self.model.prompt_encoder(
        points=points,
        boxes=boxes,
        masks=mask_input,
    )  # sparse_embeddings.size():[1,2,256], dense_embeddings.size():[1,256,64,64]

    # Predict masks
    low_res_masks, iou_predictions = self.model.mask_decoder(
        image_embeddings=self.features,
        image_pe=self.model.prompt_encoder.get_dense_pe(),
        sparse_prompt_embeddings=sparse_embeddings,
        dense_prompt_embeddings=dense_embeddings,
        multimask_output=multimask_output,
    )

    # Upscale the masks to the original image resolution
    masks = self.model.postprocess_masks(low_res_masks, self.input_size, self.original_size)

    if not return_logits:
        masks = masks > self.model.mask_threshold

    return masks, iou_predictions, low_res_masks

2. Prompt Encoder代码解析

(1)PromptEncoder类

位置:【segment_anything/modeling/prompt_encoder.py -->PromptEncoder类】
作用: 实现prompt输入嵌入编码

  先看PromptEncoder的 _ _ i n i t _ _ {\_\_init\_\_} __init__ 初始化函数和 f o r w a r d {forward} forward 函数:

class PromptEncoder(nn.Module):
    def __init__(
        self,
        embed_dim: int,
        image_embedding_size: Tuple[int, int],
        input_image_size: Tuple[int, int],
        mask_in_chans: int,
        activation: Type[nn.Module] = nn.GELU,
    ) -> None:
        
        super().__init__()
        self.embed_dim = embed_dim  # 嵌入维度256
        self.input_image_size = input_image_size  # 输入图像大小[1024, 1024]
        
        # 图像嵌入大小[64, 64] image_encoder编码器输出为[1,256,64,64]
        self.image_embedding_size = image_embedding_size  
        self.pe_layer = PositionEmbeddingRandom(embed_dim // 2)  # embed_dim // 2 = 128

        self.num_point_embeddings: int = 4  # pos/neg point + 2 box corners 有4个点
        # 4个点的嵌入向量 point_embeddings为4个Embedding(1, 256)
        point_embeddings = [nn.Embedding(1, embed_dim) for i in range(self.num_point_embeddings)]
        self.point_embeddings = nn.ModuleList(point_embeddings)  # 4个点的嵌入向量添加到网络
        self.not_a_point_embed = nn.Embedding(1, embed_dim)  # 不是点的嵌入向量

        self.mask_input_size = (4 * image_embedding_size[0], 4 * image_embedding_size[1])  # mask输入尺寸(256, 256)
        self.mask_downscaling = nn.Sequential(
            nn.Conv2d(1, mask_in_chans // 4, kernel_size=2, stride=2),  # 四倍下采样
            LayerNorm2d(mask_in_chans // 4),
            activation(),
            nn.Conv2d(mask_in_chans // 4, mask_in_chans, kernel_size=2, stride=2),
            LayerNorm2d(mask_in_chans),
            activation(),
            nn.Conv2d(mask_in_chans, embed_dim, kernel_size=1),  # 最后通道也是256
        )
        self.no_mask_embed = nn.Embedding(1, embed_dim)  # 没有mask时的嵌入向量
        
    def forward(
        self,
        points: Optional[Tuple[torch.Tensor, torch.Tensor]],
        boxes: Optional[torch.Tensor],
        masks: Optional[torch.Tensor],
    ) -> Tuple[torch.Tensor, torch.Tensor]:
        
        bs = self._get_batch_size(points, boxes, masks)  # batch size = 1
        sparse_embeddings = torch.empty((bs, 0, self.embed_dim), device=self._get_device())  # 空tensor
        
        # ------------sparse_embeddings-----------
        if points is not None:
            coords, labels = points  # coords=(650, 400.29), labels=1表示前景
            # 坐标点[X, Y]嵌入, point_embeddings.size():[1, 2, 256]
            point_embeddings = self._embed_points(coords, labels, pad=(boxes is None))  # 没有输入框的时候pad=True
            # sparse_embeddings.size():[1, 2, 256]
            sparse_embeddings = torch.cat([sparse_embeddings, point_embeddings], dim=1)
        if boxes is not None:
            box_embeddings = self._embed_boxes(boxes)
            sparse_embeddings = torch.cat([sparse_embeddings, box_embeddings], dim=1)
        # ------------sparse_embeddings-----------

        # ------------dense_embeddings------------
        if masks is not None:
            dense_embeddings = self._embed_masks(masks)  # 有mask采用mask嵌入向量
        else:
        	# 没有mask输入时采用 nn.Embedding 预定义嵌入向量
            # [1,256]->[1,256,1,1]->[1, 256, 64, 64]
            dense_embeddings = self.no_mask_embed.weight.reshape(1, -1, 1, 1).expand(
                bs, -1, self.image_embedding_size[0], self.image_embedding_size[1]
            )  # dense_embeddings.size():[1, 256, 64, 64]
        # ------------dense_embeddings------------

        return sparse_embeddings, dense_embeddings

  传送门:torch.nn.Embedding函数用法图解

   f o r w a r d {forward} forward 的过程中主要完成了sparse_embeddings(由point和box嵌入向量组成)和dense_embeddings(由mask嵌入向量组成)两种向量嵌入。

  ① _embed_points函数:输入的坐标点 [ x , y ] {[x, y]} [x,y]= ( 1300 , 800 ) {(1300, 800)} (1300,800) 经过映射变换后为 [ X , Y ] {[X, Y]} [X,Y]= ( 650 , 400.29 ) {(650, 400.29)} (650,400.29) ( 650 , 400.29 ) {(650, 400.29)} (650,400.29) s e l f . _ e m b e d _ p o i n t s {self.\_embed\_points} self._embed_points 函数完成嵌入:

def _embed_points(
    self,
    points: torch.Tensor,  # [[[650, 400.29]]]
    labels: torch.Tensor,  # [[1]]
    pad: bool,  # false
) -> torch.Tensor:
    
    points = points + 0.5  # Shift to center of pixel 移到像素中心=(650.5, 400.79)
    
    # 当没有box输入时, pad=ture
    if pad:
        padding_point = torch.zeros((points.shape[0], 1, 2), device=points.device)  # size():[1,1,2]
        padding_label = -torch.ones((labels.shape[0], 1), device=labels.device)  # 是负数,size():[1,1]
        points = torch.cat([points, padding_point], dim=1)  # [1, 2, 2]
        labels = torch.cat([labels, padding_label], dim=1)  # [1, 2]
	
	# self.pe_layer = PositionEmbeddingRandom(embed_dim // 2) = PositionEmbeddingRandom(128)
    point_embedding = self.pe_layer.forward_with_coords(points, self.input_image_size)  # 点嵌入[1,2,256]
    # -------------------------------------------------------------------------------------
    # self.point_embeddings中预设四个点的可学习嵌入向量,分别为前景点,背景点,box的左上角和右下角坐标点
    # -------------------------------------------------------------------------------------
    # 当labels=-1, 输入点是非标记点, 设为非标记点, 加上非标记点权重
    point_embedding[labels == -1] = 0.0
    point_embedding[labels == -1] += self.not_a_point_embed.weight
    # 当labels=0, 输入点是背景点, 加上背景点权重
    point_embedding[labels == 0] += self.point_embeddings[0].weight
    # 当labels=1, 输入点是目标点, 加上目标点权重
    point_embedding[labels == 1] += self.point_embeddings[1].weight
    return point_embedding

  ② _embed_boxes函数:box的左上角与右下角点 ( 700 , 400 , 1900 , 1100 ) {(700, 400, 1900, 1100)} (700,400,1900,1100)经过映射变换后为 ( 350 , 200.1465 , 950 , 500.4029 ) {(350, 200.1465, 950, 500.4029)} (350,200.1465,950,500.4029),由 s e l f . _ e m b e d _ b o x e s {self.\_embed\_boxes} self._embed_boxes 函数完成嵌入:

def _embed_boxes(self, boxes: torch.Tensor) -> torch.Tensor:
    
    # (350, 200.1465, 950, 500.4029)->(350.5000, 200.6465, 950.5000, 550.9030)
    boxes = boxes + 0.5  # Shift to center of pixel  size()=[1,1,4]
    coords = boxes.reshape(-1, 2, 2)  # [1,1,4]->[1,2,2]
    corner_embedding = self.pe_layer.forward_with_coords(coords, self.input_image_size)  # [1,2,256]
    # 目标框起始点的和末位点分别加上权重
    corner_embedding[:, 0, :] += self.point_embeddings[2].weight  # 左上角点
    corner_embedding[:, 1, :] += self.point_embeddings[3].weight  # 右下角点
    return corner_embedding

  ③_embed_masks函数:若有mask输入,由 s e l f . _ e m b e d _ m a s k s {self.\_embed\_masks} self._embed_masks 函数完成嵌入:

def _embed_masks(self, masks: torch.Tensor) -> torch.Tensor:
   
    mask_embedding = self.mask_downscaling(masks)
    return mask_embedding

  self.mask_downscaling结构:

(mask_downscaling): Sequential(
    (0): Conv2d(1, 4, kernel_size=(2, 2), stride=(2, 2))
    (1): LayerNorm2d()
    (2): GELU(approximate='none')
    (3): Conv2d(4, 16, kernel_size=(2, 2), stride=(2, 2))
    (4): LayerNorm2d()
    (5): GELU(approximate='none')
    (6): Conv2d(16, 256, kernel_size=(1, 1), stride=(1, 1))
  )

  结束了么,家人们!是不是在疑惑,还有最后一步了(ง •_•)ง,在 _embed_points函数_embed_boxes函数 中均调用了随机位置嵌入PositionEmbeddingRandom类,以进行point的位置编码。可以理解为,每一个point的向量嵌入都由point的位置编码和可学习nn.Embedding预设权重相加组成。

(2)PositionEmbeddingRandom类

位置:【segment_anything/modeling/prompt_encoder.py -->PositionEmbeddingRandom类】
作用: 调用forward_with_coords将point归一化到[0,1],调用_pe_encoding完成位置编码

class PositionEmbeddingRandom(nn.Module):
    
    def __init__(self, num_pos_feats: int = 64, scale: Optional[float] = None) -> None:
        super().__init__()
        if scale is None or scale <= 0.0:
            scale = 1.0
        self.register_buffer(
            "positional_encoding_gaussian_matrix",
            scale * torch.randn((2, num_pos_feats)),  # 生成随机数, 满足标准正态分布
        )

    def _pe_encoding(self, coords: torch.Tensor) -> torch.Tensor:
        """Positionally encode points that are normalized to [0,1]."""
        # assuming coords are in [0, 1]^2 square and have d_1 x ... x d_n x 2 shape
        # coords: [X/1024, Y/1024]=(0.6353, 0.3914)
        # 映射至[-1,1],适应三角函数. coords=(0.2705, -0.2172) size():[1,1,2]
        coords = 2 * coords - 1   
        # self.positional_encoding_gaussian_matrix是随机生成的: [2, 128]
        coords = coords @ self.positional_encoding_gaussian_matrix  # 矩阵乘法[1, 1, 128] / [64, 64, 128]
        coords = 2 * np.pi * coords  # 2*Π*R [1, 1, 128]
        # outputs d_1 x ... x d_n x C shape
        return torch.cat([torch.sin(coords), torch.cos(coords)], dim=-1)  # [1, 1, 256] / [64, 64, 256]

    def forward(self, size: Tuple[int, int]) -> torch.Tensor:
        """Generate positional encoding for a grid of the specified size."""
        h, w = size  # 64, 64
        device: Any = self.positional_encoding_gaussian_matrix.device
        grid = torch.ones((h, w), device=device, dtype=torch.float32)  # [64, 64]的全1矩阵
        y_embed = grid.cumsum(dim=0) - 0.5  # [64, 64] 列逐累加
        x_embed = grid.cumsum(dim=1) - 0.5  # [64, 64] 行逐累加
        y_embed = y_embed / h
        x_embed = x_embed / w
        # torch.stack([x_embed, y_embed], dim=-1)->size(): [64, 64, 2]
        pe = self._pe_encoding(torch.stack([x_embed, y_embed], dim=-1))  # [64, 64, 256]
        return pe.permute(2, 0, 1)  # C x H x W [256, 64, 64]

    def forward_with_coords(
        self, coords_input: torch.Tensor, image_size: Tuple[int, int]
    ) -> torch.Tensor:
        """Positionally encode points that are not normalized to [0,1]."""
        coords = coords_input.clone()  # [X+0.5, Y+0.5]=(650.5, 400.79)
        coords[:, :, 0] = coords[:, :, 0] / image_size[1]
        coords[:, :, 1] = coords[:, :, 1] / image_size[0]
        # 除以1024,归一化到[0,1]->[X/1024, Y/1024]=(0.6353, 0.3914)
        return self._pe_encoding(coords.to(torch.float))  # B x N x C

  奇怪的是,PositionEmbeddingRandom类自身的forward似乎并没有用上,也不知道干啥滴哩~

3. Prompt Encoder结构绘制

(1)结构打印

PromptEncoder(
  (pe_layer): PositionEmbeddingRandom()
  (point_embeddings): ModuleList(
    (0-3): 4 x Embedding(1, 256)
  )
  (not_a_point_embed): Embedding(1, 256)
  (mask_downscaling): Sequential(
    (0): Conv2d(1, 4, kernel_size=(2, 2), stride=(2, 2))
    (1): LayerNorm2d()
    (2): GELU(approximate='none')
    (3): Conv2d(4, 16, kernel_size=(2, 2), stride=(2, 2))
    (4): LayerNorm2d()
    (5): GELU(approximate='none')
    (6): Conv2d(16, 256, kernel_size=(1, 1), stride=(1, 1))
  )
  (no_mask_embed): Embedding(1, 256)
)

(2)结构绘制

在这里插入图片描述

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

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

相关文章

深度学习笔记之优化算法(八)Adam算法的简单认识

深度学习笔记之优化算法——Adam算法的简单认识 引言回顾&#xff1a;基于Nesterov动量的RMSProp算法Adam算法的简单认识一阶矩、二阶矩修正偏差的功能Adam的算法过程描述Adam示例代码 引言 上一节介绍了基于 Nesterov \text{Nesterov} Nesterov动量与 RMSProp \text{RMSProp}…

文字与视频结合效果

效果展示 CSS 知识点 mix-blend-mode 属性的运用 实现整体页面布局 <section class"sec"><video autoplay muted loop><source src"./video.mp4" type"video/mp4" /></video><h2>Run</h2><!-- 用于切…

【Java 进阶篇】JavaScript 与 HTML 的结合方式

JavaScript是一种广泛应用于Web开发中的脚本语言&#xff0c;它与HTML&#xff08;Hypertext Markup Language&#xff09;结合使用&#xff0c;使开发人员能够创建交互式和动态的网页。在这篇博客中&#xff0c;我们将深入探讨JavaScript与HTML的结合方式&#xff0c;包括如何…

Web知识:markupsafe.escape() 函数的作用

1、markupsafe.escape() 函数是 MarkupSafe 库中的一个函数&#xff0c;它的作用是对字符串进行 HTML 转义&#xff0c;以防止在 HTML 文档中引起意外的解析结果或安全漏洞。 2、在 Web 开发中&#xff0c;如果用户提供的数据直接插入到 HTML 页面中&#xff0c;而没有经过转义…

SpringBoot调用存储过程(入参,返参)(亲测有效!!!)

存储过程和函数是有区别的&#xff01;&#xff01;&#xff01; 创建函数&#xff0c;只是演示&#xff0c;以下函数不完整&#xff01;&#xff01;&#xff01;(只是看P_xxx参数) CREATE OR REPLACE PROCEDURE SP_TICKET_CHECKE_ONLINE_TEST (p_transcode IN OUT VA…

Java反射获取抽象类方法属性问题讲解

Java反射获取抽象类方法属性问题讲解 结论一、案例准备二、测试方法&#xff1a;使用反射获取抽象类私有方法和私有属性具体操作&#xff08;获取私有方法&#xff09;具体操作&#xff08;获取私有属性&#xff09; 结论 Java 通过反射可以获得抽象类的任何修饰符&#xff08…

Vue2 Watch的语法

Watch语法 一、监听普通数据类型&#xff08;1&#xff09;把要监听的msg值看作方法名&#xff0c;来进行监听。&#xff08;2&#xff09;把要监听的msg值看作对象&#xff0c;利用hanler方法来进行监听 二、监听对象&#xff1a;&#xff08;1&#xff09;监听对象需要用到深…

LeetCode【300】最长递增子序列

题目&#xff1a; 思路&#xff1a; 通常来说&#xff0c;子序列不要求连续&#xff0c;而子数组或子字符串必须连续&#xff1b;对于子序列问题&#xff0c;第一种动态规划方法是&#xff0c;定义 dp 数组&#xff0c;其中 dp[i] 表示以 i 结尾的子序列的性质。在处理好每个…

1808_ChibiOS基本的架构介绍

全部学习汇总&#xff1a; GreyZhang/g_ChibiOS: I found a new RTOS called ChibiOS and it seems interesting! (github.com) 简单看了一下ChibiOS的架构介绍&#xff0c;感觉这种OS以及组件非常适合快速构建一个应用。这里做一个简单的资料整理。。 1. 不同于其他的OS&#…

TCP/IP(九)TCP的连接管理(六)TIME_WAIT状态探究

一 TIME_WAIT探究 要明确TIME_WAIT状态在tcp四次挥手的阶段 ① 为什么 TIME_WAIT 等待的时间是 2MSL? 背景&#xff1a; 客户端在收到服务端第三次FIN挥手后,就会进入TIME_WAIT 状态,开启时长为2MSL的定时器1、MSL 是 Maximum Segment Lifetime 报文最大生存时间2、2MSL…

4.(vue3.x+vite)style动态绑定的方式

前端技术社区总目录(订阅之前请先查看该博客) 效果浏览 代码如下: <template><div><div :style="{

改造Vue-admin-template登录

这是是将Vue-admin-template改为登录自己的&#xff0c;拿自己的数据&#xff0c;原作者是gitee花裤衩或者github devServer: {proxy: {/dev-api: {target: http://localhost:8035,changeOrigin: true,pathRewrite: {^/dev-api: }}} }, main.js如下 import Vue from vueimpor…

VMware虚拟机安装Linux教程(图文超详细)

1.安装VMware 官方正版VMware下载地址 https://www.vmware.com/ 双击安装 以上就是VMware在安装时的每一步操作&#xff0c;基本上就是点击 "下一步" 一直进行安装。 2.安装Linux VMware虚拟机安装完毕之后&#xff0c;我们就可以打开VMware&#xff0c;并在上面来…

validator库的使用详解

TOC 基本使用 前言 在做API开发时&#xff0c;需要对请求参数的校验&#xff0c;防止用户的恶意请求。例如日期格式&#xff0c;用户年龄&#xff0c;性别等必须是正常的值&#xff0c;不能随意设置。以前会使用大量的if判断参数的值是否符合规范&#xff0c;现在可以使用val…

电脑如何查看是否支持虚拟化及如何开启虚拟化

什么是虚拟化? Intel Virtualization Technology就是以前众所周知的“Vanderpool”技术&#xff08;简称VT&#xff0c;中文译为虚拟化技术&#xff09;&#xff0c;这种技术可以让一个CPU工作起来就像多个CPU并行运行&#xff0c;从而使得在一部电脑内同时运行多个操作系统成…

rabbitmq-----黑马资料

rabbit的三种发送订阅模式 消息从发送&#xff0c;到消费者接收&#xff0c;会经理多个过程&#xff1a; 其中的每一步都可能导致消息丢失&#xff0c;常见的丢失原因包括&#xff1a; 发送时丢失&#xff1a;生产者发送的消息未送达exchange消息到达exchange后未到达queueMQ…

使用Python进行食品配送时间预测

一般的食品配送服务需要显示交付订单所需的准确时间&#xff0c;以保持与客户的透明度。这些公司使用机器学习算法来预测食品配送时间&#xff0c;基于配送合作伙伴过去在相同距离上花费的时间。 食品配送时间预测 为了实时预测食物的交付时间&#xff0c;我们需要计算食物准…

[安洵杯 2019]easy_web - RCE(关键字绕过)+md5强碰撞+逆向思维

[安洵杯 2019]easy_web 1 解题流程1.1 阶段一1.2 阶段二2 思考总结1 解题流程 1.1 阶段一 1、F12发现提示md5 is funny ~;还有img标签中,有伪协议和base64编码 2、url地址是index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=   这就有意思了,这里的img明显是编码后的…

如何给苹果ipa和安卓apk应用APP包体修改手机屏幕上logo图标iocn?

虽然修改应用文件图标是一个简单的事情&#xff0c;但是还是有很多小可爱是不明白的&#xff0c;你要是想要明白的话&#xff0c;那我就让你今天明白明白&#xff0c;我们今天采用的非常规打包方式&#xff0c;常规打包方式科技一下教程铺天盖地&#xff0c;既然小弟我出马&…

阿里云华中1(武汉)本地地域公网带宽价格表

阿里云华中1&#xff08;武汉&#xff09;地域上线&#xff0c;本地地域只有一个可用区A&#xff0c;高可用需要多可用区部署的应用&#xff0c;不建议选择本地地域&#xff0c;可以选择上海或杭州地域&#xff0c;阿里云服务器华中1&#xff08;武汉&#xff09;地域公网带宽价…