锚框 anchor box

博客地址:https://www.cnblogs.com/zylyehuo/

代码总览















代码解释

从头到尾用"快递站管理系统"的比喻方式,完整解释每一行代码的功能和意义。

1. 导入库 - 准备工具包

python 复制代码
import torch  # 📦 主工具箱:搬运工(张量操作)
from d21 import torch as d21  # 📦 专用工具包:快递站定制工具(边界框转换等)
python 复制代码
torch.set_printoptions(2)  # 📏 设置测量精度:小数点后2位

2. 锚框生成 - 布置仓库货架

python 复制代码
def multisourceplot(data, sizes, ratios):
    # 📏 测量仓库尺寸
    in_height, in_width = data.shape[-2:]  # 获取仓库的长宽
    
    # 🧰 准备工具
    device = data.device  # 确定使用哪种搬运车(CPU/GPU)
    num_sizes, num_ratios = len(sizes), len(ratios)  # 清点盒子类型
    
    # 📐 计算每个位置放几个盒子
    boxes_per_pixel = (num_sizes + num_ratios - 1)  # 每位置放4个盒子
    
    # 📦 准备盒子模板
    size_tensor = torch.tensor(sizes, device=device)  # 小/中/大三种盒子
    ratio_tensor = torch.tensor(ratios, device=device)  # 方形/长方形模板
    
    # 📍 确定每个格子的中心点
    offset_h, offset_w = 0.5, 0.5  # 从格子角落往中心走0.5步
    steps_h = 1.0 / in_height  # 纵向每步距离
    steps_w = 1.0 / in_width   # 横向每步距离
    
    # 🗺️ 创建坐标网格
    center_h = (torch.arange(in_height, device=device) + offset_h) * steps_h
    center_w = (torch.arange(in_width, device=device) + offset_w) * steps_w
    shift_y, shift_x = torch.meshgrid(center_h, center_w, indexing='ij')
    shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)  # 压平网格
    
    # 📏 计算每种盒子的实际尺寸
    # 第一类:固定形状,不同大小
    w1 = size_tensor * torch.sqrt(ratio_tensor[0])
    h1 = size_tensor / torch.sqrt(ratio_tensor[0])
    
    # 第二类:固定大小,不同形状
    w2 = sizes[0] * torch.sqrt(ratio_tensor[1:])
    h2 = sizes[0] / torch.sqrt(ratio_tensor[1:])
    
    # 📦 合并所有盒子尺寸
    w = torch.cat((w1, w2))
    h = torch.cat((h1, h2))
    
    # 🧩 组装盒子位置偏移量
    anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(
        in_height * in_width, 1) / 2  # 计算每个盒子四角偏移
    
    # 🗺️ 为每个位置分配中心点
    out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y], 
                          dim=1).repeat_interleave(boxes_per_pixel, dim=0)
    
    # 📌 最终确定每个盒子位置
    output = out_grid + anchor_manipulations  # 中心点 + 偏移量
    return output.unsqueeze(0)  # 📄 返回货架布置图(添加批次维度)

3. IoU计算 - 测量格子重叠率

python 复制代码
def box_iou(boxes1, boxes2):
    # 📏 定义量具:计算格子面积
    box_area = lambda boxes: ((boxes[:, 2] - boxes[:, 0]) * 
                             (boxes[:, 3] - boxes[:, 1]))
    
    # 📐 测量两组格子各自面积
    areas1 = box_area(boxes1)  # 第一组格子面积
    areas2 = box_area(boxes2)  # 第二组格子面积
    
    # 🔍 计算重叠区域
    # 左上角取较大值(更靠右下的左上角)
    inter_upperlefts = torch.max(boxes1[:, None, :2], boxes2[:, :2])
    # 右下角取较小值(更靠左上的右下角)
    inter_lowerrights = torch.min(boxes1[:, None, 2:], boxes2[:, 2:])
    # 计算重叠区域尺寸(负数取0)
    inters = (inter_lowerrights - inter_upperlefts).clamp(min=0)
    
    # 📏 计算重叠面积
    inter_areas = inters[:, :, 0] * inters[:, :, 1]
    # 📐 计算总面积
    union_areas = areas1[:, None] + areas2 - inter_areas
    
    # ➗ 返回重叠比例
    return inter_areas / union_areas

4. 锚框分配 - 给包裹分配存储格

python 复制代码
def assign_anchor_to_bbox(ground_truth, anchors, device, iou_threshold=0.5):
    # 📦 清点资源
    num_anchors = anchors.shape[0]  # 空盒子数量
    num_gt = ground_truth.shape[0]   # 包裹数量
    
    # 📊 测量每个空盒与包裹的匹配度
    jaccard = box_iou(anchors, ground_truth)  # 计算IoU矩阵
    
    # 🏷️ 初始化分配表(-1表示未分配)
    anchor_bbox_map = torch.full((num_anchors,), -1, dtype=torch.long, device=device)
    
    # ✅ 第一轮分配:高匹配度直接分配
    max_iou, indices = torch.max(jaccard, dim=1)  # 每个盒子找最佳匹配
    anc_i = torch.nonzero(max_iou >= iou_threshold).reshape(-1)  # 找出匹配度高的盒子
    box_j = indices[anc_i]  # 对应的包裹编号
    anchor_bbox_map[anc_i] = box_j  # 登记分配关系
    
    # 🔄 第二轮分配:确保每个包裹都有盒子
    col_discard = torch.full((num_anchors,), -1)  # 列作废标记
    row_discard = torch.full((num_gt,), -1)       # 行作废标记
    
    for _ in range(num_gt):  # 遍历每个包裹
        max_idx = torch.argmax(jaccard)  # 找全局最佳匹配
        box_idx = (max_idx % num_gt).long()  # 包裹编号
        anc_idx = (max_idx // num_gt).long()  # 盒子编号
        
        anchor_bbox_map[anc_idx] = box_idx  # 分配盒子
        
        # 🚫 标记已分配的包裹和盒子
        jaccard[:, box_idx] = col_discard  # 该包裹列作废
        jaccard[anc_idx, :] = row_discard  # 该盒子行作废
        
    return anchor_bbox_map  # 📄 返回分配表

5. 偏移计算 - 记录格子调整量

python 复制代码
def offset_boxes(anchors, assigned_bb, eps=1e-6):
    # 🔄 转换坐标格式:从四角→中心+尺寸
    c_anc = d21.box_corner_to_center(anchors)  # 格子当前尺寸
    c_assigned = d21.box_corner_to_center(assigned_bb)  # 包裹实际尺寸
    
    # 📏 计算中心点偏移(带缩放因子)
    offset_xy = 10 * (c_assigned[:, :2] - c_anc[:, :2]) / c_anc[:, 2:]
    
    # 📏 计算尺寸缩放量(对数形式更稳定)
    offset_wh = 5 * torch.log(eps + c_assigned[:, 2:] / c_anc[:, 2:])
    
    return torch.cat([offset_xy, offset_wh], axis=1)  # 📄 返回调整量表

6. 训练标签生成 - 创建员工培训手册

python 复制代码
def multibox_target(anchors, labels):
    # 📦 初始化培训资料
    batch_size = labels.shape[0]  # 培训包裹批次大小
    anchors = anchors.squeeze(0)  # 移除批次维度
    batch_offset, batch_mask, batch_class_labels = [], [], []  # 三个培训模块
    
    # 🔄 处理每个培训包裹
    for i in range(batch_size):
        label = labels[i, :, :]  # 当前包裹信息
        
        # 🏷️ 分配格子
        anchor_bbox_map = assign_anchor_to_bbox(label[:, 1:], anchors, device)
        
        # 🎭 创建格子使用标记
        bbox_mask = ((anchor_bbox_map >= 0).float().unsqueeze(-1)).repeat(1, 4)
        
        # 🏷️ 初始化标签
        class_labels = torch.zeros(anchors.shape[0], dtype=torch.long, device=device)
        assigned_bb = torch.zeros((anchors.shape[0], 4), dtype=torch.float32, device=device)
        
        # ✅ 标记有包裹的格子
        indices_true = torch.nonzero(anchor_bbox_map >= 0).flatten()
        bb_idx = anchor_bbox_map[indices_true]
        class_labels[indices_true] = label[bb_idx, 0].long() + 1  # 类别+1(0留给空)
        assigned_bb[indices_true] = label[bb_idx, 1:]  # 记录包裹位置
        
        # 📏 计算格子调整量
        offset = offset_boxes(anchors, assigned_bb) * bbox_mask
        
        # 📚 收集培训资料
        batch_offset.append(offset.reshape(-1))
        batch_mask.append(bbox_mask.reshape(-1))
        batch_class_labels.append(class_labels)
    
    # 📦 打包培训手册
    return (torch.stack(batch_offset), 
            torch.stack(batch_mask), 
            torch.stack(batch_class_labels))

7. 预测解码 - 应用调整建议

python 复制代码
def offset_inverse(anchors, offset_preds):
    # 🔄 转换坐标格式
    c_anc = d21.box_corner_to_center(anchors)
    
    # 🔧 应用中心点调整
    pred_bbox_xy = (offset_preds[:, :2] * c_anc[:, 2:] / 10 + c_anc[:, :2]
    
    # 🔧 应用尺寸调整
    pred_bbox_wh = torch.exp(offset_preds[:, 2:] / 5) * c_anc[:, 2:]
    
    # 📦 重组预测框
    pred_bbox = torch.cat([pred_bbox_xy, pred_bbox_wh], axis=1)
    
    # 🔄 转换回四角坐标
    return d21.box_center_to_corner(pred_bbox)

8. NMS - 去重检查

python 复制代码
def nms(boxes, scores, iou_threshold):
    # 📊 按可信度排序
    B = torch.argsort(scores, dim=-1, descending=True)
    keep = []  # 保留列表
    
    # 🔍 遍历所有盒子
    while B.numel() > 0:
        i = B[0]  # 当前最可信的盒子
        keep.append(i)  # 加入保留列表
        
        if B.numel() == 1:  # 只剩一个盒子时退出
            break
        
        # 📏 计算与其他盒子的重叠率
        iou = box_iou(boxes[i, :].reshape(-1, 4), 
                     boxes[B[1:], :].reshape(-1, 4)).reshape(-1)
        
        # 🚫 保留重叠率低的盒子
        inds = torch.nonzero(iou <= iou_threshold).reshape(-1)
        B = B[inds + 1]  # 更新待处理列表
    
    return torch.tensor(keep, device=boxes.device)  # 📄 返回保留盒子的索引

9. 检测后处理 - 生成最终报告

python 复制代码
def multibox_detection(cls_probs, offset_preds, anchors, nms_threshold=0.5, pos_threshold=0.01):
    # 📦 准备报告模板
    device = cls_probs.device
    batch_size = cls_probs.shape[0]
    anchors = anchors.squeeze(0)
    out = []  # 最终报告列表
    
    # 🔄 处理每个仓库区域
    for i in range(batch_size):
        # 📊 获取员工扫描结果
        cls_prob = cls_probs[i]  # 每个格子的包裹概率
        offset_pred = offset_preds[i].reshape(-1, 4)  # 尺寸调整建议
        
        # 🎯 找出可能含包裹的格子
        conf, class_id = torch.max(cls_prob[1:], 0)  # 跳过背景类别
        
        # 🔧 应用尺寸调整
        predicted_bb = offset_inverse(anchors, offset_pred)
        
        # 🚫 去重处理
        keep = nms(predicted_bb, conf, nms_threshold)
        
        # 📋 重组所有格子信息
        all_idx = torch.arange(anchors.shape[0], dtype=torch.long, device=device)
        combined = torch.cat([keep, all_idx])
        uniques, counts = combined.unique(return_counts=True)
        non_keep = uniques[counts == 1]  # 找出NMS未保留的格子
        
        # 🏷️ 标记空格子
        class_id[non_keep] = -1  # -1表示空格子
        
        # 📊 排序格子信息
        all_id_sorted = torch.cat([keep, non_keep])
        conf = conf[all_id_sorted]
        predicted_bb = predicted_bb[all_id_sorted]
        
        # 🚫 过滤低可信度格子
        below_min_idx = (conf < pos_threshold)
        class_id[below_min_idx] = -1  # 标记为空
        conf[below_min_idx] = 1 - conf[below_min_idx]  # 计算背景概率
        
        # 📝 生成区域报告
        pred_info = torch.cat((class_id.unsqueeze(1),
                              conf.unsqueeze(1),
                              predicted_bb), dim=1)
        out.append(pred_info)
    
    return torch.stack(out)  # 📑 返回最终报告

全流程总结

相关推荐
找方案16 小时前
AI视频生成对决:Sora vs 可灵 vs Veo,谁能定义未来
人工智能·机器学习·音视频
面朝大海,春不暖,花不开1 天前
Buat New Trenches, Kalian Bisa Baca Panduan Meta Ini
人工智能·机器学习
VL——MOESR1 天前
【具身智能】Cosmos Policy论文阅读随笔
论文阅读·人工智能·机器学习·具身智能·cosmospolicy
haon11221 天前
树模型在信贷风控怎么用——从决策树到 XGBoost
大数据·人工智能·算法·决策树·机器学习·数据挖掘
阳明山水1 天前
从相关到因果:预测科学的因果转向与可识别性挑战
人工智能·深度学习·算法·机器学习·架构
宣宣猪的小花园.1 天前
【嵌入式】故障与降级:看门狗、保护机制和工程可靠性的底线
开发语言·人工智能·嵌入式硬件·机器学习
pjj198541 天前
深度学习-训练,评估与数据增强
人工智能·深度学习·机器学习
满怀冰雪1 天前
25-迁移学习入门:加载预训练模型并微调
人工智能·python·机器学习·迁移学习·paddle
派大_星1 天前
PyTorch CNN图片分类与数据增强实践
人工智能·深度学习·机器学习
2601_962297481 天前
基于LangChain+LLM大模型+机器学习的恶意域名(流量)智能检测系统
机器学习·langchain·llm·恶意域名·流量检测