【23】多尺度检测及检测数据集

文章目录

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torchvision
import torch
import os

1. 多尺度边界框检测

# 测试图像
# imagepath = 'E:\学习\机器学习\数据集\VOC2012\VOCdevkit\VOC2012\JPEGImages\\2007_001423.jpg'
imagepath = 'E:\学习\机器学习\数据集\VOC2012\VOCdevkit\VOC2012\JPEGImages\\2007_001526.jpg'

# 读取并显示图像
image = plt.imread(imagepath)
h, w = image.shape[:2]

定义一些列的显示函数

# 功能: 指定输入图像、尺度列表和宽高比列表,然后此函数将生成以每个像素为中心具有不同形状的锚框,返回所有的锚框
def multibox_prior(data, sizes, ratios):

    device = data.device
    size_tensor = torch.tensor(sizes, device=device)
    ratio_tensor = torch.tensor(ratios, device=device)

#     print(data.shape)
    # 获取图像宽高
    img_height, img_width = data.shape[-2:]

    # 避免anchor太密集,只挑选特定的boxes
    boxes_per_pixel = len(sizes) + len(ratios) - 1

    # 获取每个像素的中心点
    steps_h = 1.0 / img_height  # 高度步长
    steps_w = 1.0 / img_width   # 宽度步长

    # 根据图像像素点位置 * 步长 来实现归一化处理,使得图像尺寸计算为1
    # 0.5 指的是像素点中心位置的偏移量
    center_h = (torch.arange(img_height, device=device) + 0.5) * steps_h
    center_w = (torch.arange(img_width, device=device) + 0.5) * steps_w
    # print(center_h.shape, center_w.shape)   # torch.Size([333]) torch.Size([500])

    # 根据步长位置构建每个像素点的坐标信息
    shift_y, shift_x = torch.meshgrid(center_h, center_w)
    # print(shift_y.shape, shift_x.shape)     # torch.Size([333, 500]) torch.Size([333, 500])

    # 分别转换成列表,方便拼接,其中(shift_x, shift_y)就代表了图像中全部像素点的中心坐标
    shift_y, shift_x = shift_y.reshape(-1), shift_x.reshape(-1)

    # 现在对(shift_x, shift_y)进行拼接,方便一会转换成左上角与右下角的坐标格式,所以需要设置两组坐标
    # 其中参数dim=1表示的是对列进行拼接
    center_point = torch.stack([shift_x, shift_y, shift_x, shift_y], dim=1)
    # 由于每个像素点会生成(n+m−1)个anchor,所以需要对坐标列表重复5次
    # repeat_interleave函数是对每一行分别进行先复制; repeat函数是对每一块分别进行复制
    center_point = center_point.repeat_interleave(boxes_per_pixel, dim=0)
    # print(center_point)

    # 现在构造出了中心点坐标,接着需要构造偏移信息列表,使中心坐标+偏移量就转换成转换成左上角与右下角的坐标格式
    # 其中: anchor_w = s * sqrt(w * h * r)   anchor_h = s * sqrt(w * h / r)
    # 这样使得 anchor_w / anchor_h = r   anchor_w * anchor_h = (ws)*(hs)
    # anchor_w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]), size_tensor[0] * torch.sqrt(ratio_tensor[1:]))) \
    #            * math.sqrt(img_width * img_height)
    # anchor_h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]), size_tensor[0] / torch.sqrt(ratio_tensor[1:]))) \
    #            * math.sqrt(img_width * img_height)
    # anchor_w, anchor_h:
    # tensor([306.0331, 204.0221, 102.0110, 432.7961, 216.3981])
    # tensor([306.0331, 204.0221, 102.0110, 216.3981, 432.7962])
    # 现在得到的5个anchor是在图像上的像素大小,需要同样对其进行归一化操作

    # 而另一种方法是:
    # 其中size值的是相比原图的大小, ratio值的宽高比
    anchor_w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]), size_tensor[0] * torch.sqrt(ratio_tensor[1:])))   \
                * img_height / img_width  # 由于图像一般是矩形的,为了显示出是正方形,这里需要对宽度做一个缩放因子
    anchor_h = torch.cat((size_tensor / torch.sqrt(ratio_tensor[0]), size_tensor[0] / torch.sqrt(ratio_tensor[1:])))
    # anchor_w, anchor_h:
    # tensor([0.4995, 0.3330, 0.1665, 0.7064, 0.3532])
    # tensor([0.7500, 0.5000, 0.2500, 0.5303, 1.0607])
#     print(anchor_w)
#     print(anchor_h)

    # 获得偏移量
    anchor_offset = torch.stack((-anchor_w, -anchor_h, anchor_w, anchor_h))
    anchor_offset = anchor_offset.T.repeat(img_height * img_width, 1) / 2    # 先转置再按偏移块来重复

    # 更加中心点坐标与偏移量,获取anchor
    anchors = center_point + anchor_offset

    return anchors.unsqueeze(0)


# 将边界框 (左上x, 左上y, 右下x, 右下y) 格式转换成 matplotlib 格式:
# ((左上x, 左上y), 宽, 高)
def bbox_to_rect(bbox, color, linewidth=2):
    
    # 注意,这里输入的是单个边界框
    xy = (bbox[0], bbox[1])   # 左上角坐标
    width = bbox[2]-bbox[0]   # 右下角的x坐标 - 左上角的x坐标
    height = bbox[3]-bbox[1]  # 右下角的y坐标 - 左上角的y坐标
    
    # 返回matplotlib 的边界框格式
    # fill=False: 取消填充功能,否则不是边界框而是一个色块
    # edgecolor: 边界框颜色
    # linewidth:  边界框的宽度
    return plt.Rectangle(xy, width, height, fill=False, edgecolor=color, linewidth=linewidth)


# 功能: 显示一个像素点上的所有边界框(这里设置了一个像素点上会有5个anchor)
def show_bboxes(axes, bboxes, labels=None, colors=None):

    # 如果没有传入颜色设置,这里会进行颜色一个初始化设置
    if colors is None:
        colors = ['blue', 'red', 'green', 'gray', 'pink']

    # 如果没有传入标签设置,这里会进行标签一个初始化设置
    if labels is None:
        labels = [i for i in range(len(bboxes))]
    
    
    # print(labels)
    # 以增加补丁的方式在原图上绘制矩形框
    for i, bbox in enumerate(bboxes):
        color = colors[i % len(colors)]
        rect = bbox_to_rect(bbox, color)  # 循环采用列表中的5种颜色
        # 增加矩形框补丁
        axes.add_patch(rect)
        # 增加文本补丁
        axes.text(rect.xy[0], rect.xy[1], labels[i], fontsize=8, color='white',
                  va='center', ha='center', bbox=dict(facecolor=color, edgecolor="black"))


def display_anchors(fmap_w, fmap_h, s):
#     d2l.set_figsize()
    # 前两个维度上的值不影响输出
    fmap = torch.zeros((1, 10, fmap_h, fmap_w))
    anchors = multibox_prior(fmap, sizes=s, ratios=[1, 2, 0.5])
    bbox_scale = torch.tensor((w, h, w, h))
    show_bboxes(plt.imshow(image).axes, anchors[0] * bbox_scale)

使用不同的尺度构建anchor

display_anchors(fmap_w=4, fmap_h=4, s=[0.15])
torch.Size([1, 10, 4, 4])
tensor([0.1500, 0.2121, 0.1061])
tensor([0.1500, 0.1061, 0.2121])

【23】多尺度检测及检测数据集

display_anchors(fmap_w=2, fmap_h=2, s=[0.4])
torch.Size([1, 10, 2, 2])
tensor([0.4000, 0.5657, 0.2828])
tensor([0.4000, 0.2828, 0.5657])

【23】多尺度检测及检测数据集

display_anchors(fmap_w=1, fmap_h=1, s=[0.8])
torch.Size([1, 10, 1, 1])
tensor([0.8000, 1.1314, 0.5657])
tensor([0.8000, 0.5657, 1.1314])

【23】多尺度检测及检测数据集

假设此处的 c 张特征图是 CNN 基于输入图像的正向传播算法获得的中间输出。 既然每张特征图上都有 hw 个不同的空间位置,那么相同空间位置可以看作含有 c 个单元。感受野的定义,特征图在相同空间位置的 c 个单元在输入图像上的感受野相同: 它们表征了同一感受野内的输入图像信息。 因此,我们可以将特征图在同一空间位置的 c 个单元变换为使用此空间位置生成的 a 个锚框类别和偏移量。 本质上,我们用输入图像在某个感受野区域内的信息,来预测输入图像上与该区域位置相近的锚框类别和偏移量

当不同层的特征图在输入图像上分别拥有不同大小的感受野时,它们可以用于检测不同大小的目标。 例如,我们可以设计一个神经网络,其中靠近输出层的特征图单元具有更宽的感受野,这样它们就可以从输入图像中检测到较大的目标。

2. 目标检测数据集

这里代码提供的下载数据集方式我好像无法成功,只能手动下载,同时自己实现一下加载此数据集

from torch.utils.data import DataLoader, Dataset
# 一个用于加载香蕉检测数据集的自定义数据集
class BananasDataset(Dataset):
   
    def __init__(self, is_train):
        
        # 加载图像与标签信息
        self.images, self.labels = self.read_data_bananas(is_train)

        # 根据训练集或测试集打印出数量
        print('read ' + str(len(self.images)) + (' training examples' if is_train else ' validation examples'))

    def __getitem__(self, idx):
        
        # 对每一张图像的处理, 这里转变为了浮点数
        return (self.images[idx].float(), self.labels[idx])

    def __len__(self):
        
        # 返回图像数量
        return len(self.images)
    
    def read_data_bananas(self, is_train=True):
        
        # 保存测试集与训练集的图像是一致的
        datapath = 'E:\学习\机器学习\数据集\\banana-detection'
        csv_file = os.path.join(datapath, 'bananas_train' if is_train else 'bananas_val', 'label.csv')
        csv_data = pd.read_csv(csv_file)
        csv_data = csv_data.set_index('img_name')
        
        images, targets = [], []
        # 迭代每一行,添加信息
        for img_name, target in csv_data.iterrows():
            
            # 每张图像的路径
            imagepath = os.path.join(datapath, 'bananas_train' if is_train else 'bananas_val', 'images', img_name)
            # 读取图片数据, 类似于Image.open(x).convert('RGB')
            images.append(torchvision.io.read_image(imagepath))
            # target中包含了类别与边界框信息(左上角与右下角组成),eg:[0, 104, 20, 143, 58]组成的一个列表
            targets.append(list(target))
        
        # 对于边界框与类别信息在第2个维度前做了增维处理, 并且进行归一化处理, 图像的尺寸是256
        # labels.shape: torch.Size([1000, 1, 5])
        return images, torch.tensor(targets).unsqueeze(1) / 256

batch_size = 16

# 对于测试集,无须按随机顺序读取
train_iter = DataLoader(BananasDataset(is_train=True), batch_size, shuffle=True)
val_iter = DataLoader(BananasDataset(is_train=False), batch_size)
read 1000 training examples
read 100 validation examples

展示 10 幅带有真实边界框的图像

def bbox_to_rect(bbox, color, linewidth=2):

    # 注意,这里输入的是单个边界框
    xy = (bbox[0], bbox[1])  # 左上角坐标
    width = bbox[2] - bbox[0]  # 右下角的x坐标 - 左上角的x坐标
    height = bbox[3] - bbox[1]  # 右下角的y坐标 - 左上角的y坐标

    # 返回matplotlib 的边界框格式
    # fill=False: 取消填充功能,否则不是边界框而是一个色块
    # edgecolor: 边界框颜色
    # linewidth:  边界框的宽度
    return plt.Rectangle(xy, width, height, fill=False, edgecolor=color, linewidth=linewidth)

需要注意,这里对图像数值进行了归一化处理; 而且label的信息也是经过相对处理的,所以一会如果想要显示label需要对其乘上原图像大小

images, labels = next(iter(train_iter))
images = images.permute(0, 2, 3, 1) / 255
images.shape
torch.Size([16, 256, 256, 3])

根据图像与标签显示数据

index = 1
plt.figure(figsize=(9, 8), dpi=100)

for image, label in zip(images, labels):
    
    plt.subplot(4, 4, index)
    plt.axis('off')
    # 显示图像
    fig = plt.imshow(image)
    
    # 注意这里的label是归一化处理后的,需要乘上原数值
    bbox = label[0,1:] * 255
    
    # 添加矩形框
    rect = bbox_to_rect(bbox, 'w')
#     rect = plt.Rectangle((bbox[0], bbox[1]), bbox[2] - bbox[0], bbox[3] - bbox[1], 
#                          fill=False, edgecolor='red', linewidth=2)
    fig.axes.add_patch(rect)
    
    index += 1
    

【23】多尺度检测及检测数据集
这是一个比较小的数据集,方便调试测试使用,利于学习。

上一篇:linux 挂载磁盘


下一篇:one-stage-anchor-free算法fcosnet:FCOS: Fully Convolutional One-Stage Object Detection