基于Pytorch的从零开始的目标检测

引言

目标检测是计算机视觉中一个非常流行的任务,在这个任务中,给定一个图像,你预测图像中物体的包围盒(通常是矩形的) ,并且识别物体的类型。在这个图像中可能有多个对象,而且现在有各种先进的技术和框架来解决这个问题,例如 Faster-RCNN 和 YOLOv3。

本文讨论将讨论图像中只有一个感兴趣的对象的情况。这里的重点更多是关于如何读取图像及其边界框、调整大小和正确执行增强,而不是模型本身。目标是很好地掌握对象检测背后的基本思想,你可以对其进行扩展以更好地理解更复杂的技术。

本文中的所有代码都在下面的链接中:https://jovian.ai/aakanksha-ns/road-signs-bounding-box-prediction

问题陈述

给定一个由路标组成的图像,预测路标周围的包围盒,并识别路标的类型。这些路标包括以下四种:

· 红绿灯

· 停止

· 车速限制

· 人行横道

这就是所谓的多任务学习问题,因为它涉及执行两个任务: 1)回归找到包围盒坐标,2)分类识别道路标志的类型

图片

数据集

我使用了来自 Kaggle 的道路标志检测数据集,链接如下:https://www.kaggle.com/andrewmvd/road-sign-detection

它由877张图像组成。这是一个相当不平衡的数据集,大多数图像属于限速类,但由于我们更关注边界框预测,因此可以忽略不平衡。

加载数据

每个图像的注释都存储在单独的 XML 文件中。我按照以下步骤创建了训练数据集:

· 遍历训练目录以获得所有.xml 文件的列表。

· 使用xml.etree.ElementTree解析.xml文件。

· 创建一个由文件路径、宽度、高度、边界框坐标( xmin 、 xmax 、 ymin 、        ymax )和每个图像的类组成的字典,并将字典附加到列表中。

· 使用图像统计数据字典列表创建一个 Pandas 数据库。

def filelist(root, file_type):
    """Returns a fully-qualified list of filenames under root directory"""
    return [os.path.join(directory_path, f) for directory_path, directory_name, 
            files in os.walk(root) for f in files if f.endswith(file_type)]

def generate_train_df (anno_path):
    annotations = filelist(anno_path, '.xml')
    anno_list = []
    for anno_path in annotations:
        root = ET.parse(anno_path).getroot()
        anno = {}
        anno['filename'] = Path(str(images_path) + '/'+ root.find("./filename").text)
        anno['width'] = root.find("./size/width").text
        anno['height'] = root.find("./size/height").text
        anno['class'] = root.find("./object/name").text
        anno['xmin'] = int(root.find("./object/bndbox/xmin").text)
        anno['ymin'] = int(root.find("./object/bndbox/ymin").text)
        anno['xmax'] = int(root.find("./object/bndbox/xmax").text)
        anno['ymax'] = int(root.find("./object/bndbox/ymax").text)
        anno_list.append(anno)
    return pd.DataFrame(anno_list)

· 标签编码类列

#label encode target
class_dict = {'speedlimit': 0, 'stop': 1, 'crosswalk': 2, 'trafficlight': 3}
df_train['class'] = df_train['class'].apply(lambda x:  class_dict[x])

调整图像和边界框的大小

由于训练一个计算机视觉模型需要的图像是相同的大小,我们需要调整我们的图像和他们相应的包围盒。调整图像的大小很简单,但是调整包围盒的大小有点棘手,因为每个包围盒都与图像及其尺寸相关。

下面是调整包围盒大小的工作原理:

· 将边界框转换为与其对应的图像大小相同的图像(称为掩码)。这个掩码只有        0 表示背景,1 表示边界框覆盖的区域。

图片

图片

· 将掩码调整到所需的尺寸。

· 从调整完大小的掩码中提取边界框坐标。

def create_mask(bb, x):
    """Creates a mask for the bounding box of same shape as image"""
    rows,cols,*_ = x.shape
    Y = np.zeros((rows, cols))
    bb = bb.astype(np.int)
    Y[bb[0]:bb[2], bb[1]:bb[3]] = 1.
    return Y

def mask_to_bb(Y):
    """Convert mask Y to a bounding box, assumes 0 as background nonzero object"""
    cols, rows = np.nonzero(Y)
    if len(cols)==0: 
        return np.zeros(4, dtype=np.float32)
    top_row = np.min(rows)
    left_col = np.min(cols)
    bottom_row = np.max(rows)
    right_col = np.max(cols)
    return np.array([left_col, top_row, right_col, bottom_row], dtype=np.float32)

def create_bb_array(x):
    """Generates bounding box array from a train_df row"""
    return np.array([x[5],x[4],x[7],x[6]])

def resize_image_bb(read_path,write_path,bb,sz):
    """Resize an image and its bounding box and write image to new path"""
    im = read_image(read_path)
    im_resized = cv2.resize(im, (int(1.49*sz), sz))
    Y_resized = cv2.resize(create_mask(bb, im), (int(1.49*sz), sz))
    new_path = str(write_path/read_path.parts[-1])
    cv2.imwrite(new_path, cv2.cvtColor(im_resized, cv2.COLOR_RGB2BGR))
    return new_path, mask_to_bb(Y_resized)


#Populating Training DF with new paths and bounding boxes
new_paths = []
new_bbs = []
train_path_resized = Path('./road_signs/images_resized')
for index, row in df_train.iterrows():
    new_path,new_bb = resize_image_bb(row['filename'], train_path_resized, create_bb_array(row.values),300)
    new_paths.append(new_path)
    new_bbs.append(new_bb)
df_train['new_path'] = new_paths
df_train['new_bb'] = new_bbs

数据增强

数据增强是一种通过使用现有图像的不同变体创建新的训练图像来更好地概括我们的模型的技术。我们当前的训练集中只有 800 张图像,因此数据增强对于确保我们的模型不会过拟合非常重要。

对于这个问题,我使用了翻转、旋转、中心裁剪和随机裁剪。

这里唯一需要记住的是确保包围盒也以与图像相同的方式进行转换。

# modified from fast.ai
def crop(im, r, c, target_r, target_c): 
    return im[r:r+target_r, c:c+target_c]

# random crop to the original size
def random_crop(x, r_pix=8):
    """ Returns a random crop"""
    r, c,*_ = x.shape
    c_pix = round(r_pix*c/r)
    rand_r = random.uniform(0, 1)
    rand_c = random.uniform(0, 1)
    start_r = np.floor(2*rand_r*r_pix).astype(int)
    start_c = np.floor(2*rand_c*c_pix).astype(int)
    return crop(x, start_r, start_c, r-2*r_pix, c-2*c_pix)

def center_crop(x, r_pix=8):
    r, c,*_ = x.shape
    c_pix = round(r_pix*c/r)
    return crop(x, r_pix, c_pix, r-2*r_pix, c-2*c_pix)


def rotate_cv(im, deg, y=False, mode=cv2.BORDER_REFLECT, interpolation=cv2.INTER_AREA):
    """ Rotates an image by deg degrees"""
    r,c,*_ = im.shape
    M = cv2.getRotationMatrix2D((c/2,r/2),deg,1)
    if y:
        return cv2.warpAffine(im, M,(c,r), borderMode=cv2.BORDER_CONSTANT)
    return cv2.warpAffine(im,M,(c,r), borderMode=mode, flags=cv2.WARP_FILL_OUTLIERS+interpolation)

def random_cropXY(x, Y, r_pix=8):
    """ Returns a random crop"""
    r, c,*_ = x.shape
    c_pix = round(r_pix*c/r)
    rand_r = random.uniform(0, 1)
    rand_c = random.uniform(0, 1)
    start_r = np.floor(2*rand_r*r_pix).astype(int)
    start_c = np.floor(2*rand_c*c_pix).astype(int)
    xx = crop(x, start_r, start_c, r-2*r_pix, c-2*c_pix)
    YY = crop(Y, start_r, start_c, r-2*r_pix, c-2*c_pix)
    return xx, YY

def transformsXY(path, bb, transforms):
    x = cv2.imread(str(path)).astype(np.float32)
    x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB)/255
    Y = create_mask(bb, x)
    if transforms:
        rdeg = (np.random.random()-.50)*20
        x = rotate_cv(x, rdeg)
        Y = rotate_cv(Y, rdeg, y=True)
        if np.random.random() > 0.5: 
            x = np.fliplr(x).copy()
            Y = np.fliplr(Y).copy()
        x, Y = random_cropXY(x, Y)
    else:
        x, Y = center_crop(x), center_crop(Y)
    return x, mask_to_bb(Y)


def create_corner_rect(bb, color='red'):
    bb = np.array(bb, dtype=np.float32)
    return plt.Rectangle((bb[1], bb[0]), bb[3]-bb[1], bb[2]-bb[0], color=color,
                         fill=False, lw=3)

def show_corner_bb(im, bb):
    plt.imshow(im)
    plt.gca().add_patch(create_corner_rect(bb))

图片

PyTorch 数据集

现在我们已经有了数据增强,我们可以进行训练验证拆分并创建我们的 PyTorch 数据集。我们使用 ImageNet 统计数据对图像进行标准化,因为我们使用的是预训练的 ResNet 模型并在训练时在我们的数据集中应用数据增强。

X_train, X_val, y_train, y_val = train_test_split(X, Y, test_size=0.2, random_state=42)

def normalize(im):
    """Normalizes images with Imagenet stats."""
    imagenet_stats = np.array([[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]])
    return (im - imagenet_stats[0])/imagenet_stats[1]
class RoadDataset(Dataset):
    def __init__(self, paths, bb, y, transforms=False):
        self.transforms = transforms
        self.paths = paths.values
        self.bb = bb.values
        self.y = y.values
    def __len__(self):
        return len(self.paths)
    
    def __getitem__(self, idx):
        path = self.paths[idx]
        y_class = self.y[idx]
        x, y_bb = transformsXY(path, self.bb[idx], self.transforms)
        x = normalize(x)
        x = np.rollaxis(x, 2)
        return x, y_class, y_bb
train_ds = RoadDataset(X_train['new_path'],X_train['new_bb'] ,y_train, transforms=True)
valid_ds = RoadDataset(X_val['new_path'],X_val['new_bb'],y_val)
batch_size = 64
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)
valid_dl = DataLoader(valid_ds, batch_size=batch_size)

PyTorch 模型

对于这个模型,我使用了一个非常简单的预先训练的 resNet-34模型。由于我们有两个任务要完成,这里有两个最后的层: 包围盒回归器和图像分类器。

class BB_model(nn.Module):
    def __init__(self):
        super(BB_model, self).__init__()
        resnet = models.resnet34(pretrained=True)
        layers = list(resnet.children())[:8]
        self.features1 = nn.Sequential(*layers[:6])
        self.features2 = nn.Sequential(*layers[6:])
        self.classifier = nn.Sequential(nn.BatchNorm1d(512), nn.Linear(512, 4))
        self.bb = nn.Sequential(nn.BatchNorm1d(512), nn.Linear(512, 4))
        
    def forward(self, x):
        x = self.features1(x)
        x = self.features2(x)
        x = F.relu(x)
        x = nn.AdaptiveAvgPool2d((1,1))(x)
        x = x.view(x.shape[0], -1)
        return self.classifier(x), self.bb(x)

训练

对于损失,我们需要同时考虑分类损失和边界框回归损失,因此我们使用交叉熵和 L1 损失(真实值和预测坐标之间的所有绝对差之和)的组合。我已经将 L1 损失缩放了 1000 倍,因为分类和回归损失都在相似的范围内。除此之外,它是一个标准的 PyTorch 训练循环(使用 GPU):

def update_optimizer(optimizer, lr):
    for i, param_group in enumerate(optimizer.param_groups):
        param_group["lr"] = lr


def train_epocs(model, optimizer, train_dl, val_dl, epochs=10,C=1000):
    idx = 0
    for i in range(epochs):
        model.train()
        total = 0
        sum_loss = 0
        for x, y_class, y_bb in train_dl:
            batch = y_class.shape[0]
            x = x.cuda().float()
            y_class = y_class.cuda()
            y_bb = y_bb.cuda().float()
            out_class, out_bb = model(x)
            loss_class = F.cross_entropy(out_class, y_class, reduction="sum")
            loss_bb = F.l1_loss(out_bb, y_bb, reduction="none").sum(1)
            loss_bb = loss_bb.sum()
            loss = loss_class + loss_bb/C
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            idx += 1
            total += batch
            sum_loss += loss.item()
        train_loss = sum_loss/total
        val_loss, val_acc = val_metrics(model, valid_dl, C)
        print("train_loss %.3f val_loss %.3f val_acc %.3f" % (train_loss, val_loss, val_acc))
    return sum_loss/total


def val_metrics(model, valid_dl, C=1000):
    model.eval()
    total = 0
    sum_loss = 0
    correct = 0 
    for x, y_class, y_bb in valid_dl:
        batch = y_class.shape[0]
        x = x.cuda().float()
        y_class = y_class.cuda()
        y_bb = y_bb.cuda().float()
        out_class, out_bb = model(x)
        loss_class = F.cross_entropy(out_class, y_class, reduction="sum")
        loss_bb = F.l1_loss(out_bb, y_bb, reduction="none").sum(1)
        loss_bb = loss_bb.sum()
        loss = loss_class + loss_bb/C
        _, pred = torch.max(out_class, 1)
        correct += pred.eq(y_class).sum().item()
        sum_loss += loss.item()
        total += batch
    return sum_loss/total, correct/total

model = BB_model().cuda()
parameters = filter(lambda p: p.requires_grad, model.parameters())
optimizer = torch.optim.Adam(parameters, lr=0.006)


train_epocs(model, optimizer, train_dl, valid_dl, epochs=15)

测试

现在我们已经完成了训练,我们可以选择一个随机图像并在上面测试我们的模型。尽管我们只有相当少量的训练图像,但是我们最终在测试图像上得到了一个相当不错的预测。

使用手机拍摄真实照片并测试模型将是一项有趣的练习。另一个有趣的实验是不执行任何数据增强并训练模型并比较两个模型。


# resizing test image
im = read_image('./road_signs/images_resized/road789.png')
im = cv2.resize(im, (int(1.49*300), 300))
cv2.imwrite('./road_signs/road_signs_test/road789.jpg', cv2.cvtColor(im, cv2.COLOR_RGB2BGR))


# test Dataset
test_ds = RoadDataset(pd.DataFrame([{'path':'./road_signs/road_signs_test/road789.jpg'}])['path'],pd.DataFrame([{'bb':np.array([0,0,0,0])}])['bb'],pd.DataFrame([{'y':[0]}])['y'])
x, y_class, y_bb = test_ds[0]

xx = torch.FloatTensor(x[None,])
xx.shape


# prediction
out_class, out_bb = model(xx.cuda())
out_class, out_bb

图片

总结

现在我们已经介绍了目标检测的基本原理,并从头开始实现它,您可以将这些想法扩展到多对象情况,并尝试更复杂的模型,如 RCNN 和 YOLO!

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

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

相关文章

硬件开发笔记(十二):RK3568底板电路电源模块和RTC模块原理图分析

若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134429973 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

【机器学习11】强化学习

1 基本概念 一个机器人在环境中会做各种动作, 环境会接收动作, 并引起自身状态的变迁, 同时给机器人以奖励。 机器人的目标就是使用一些策略, 做合适的动作, 最大化自身的收益。 整个场景一般可以描述为一个马尔可夫…

解决更换NodeJs版本后npm -v返回空白

一、问题描述 win11电脑上输入cmd进入控制台,输入 node --version 有正常返回安装的nodejs的版本号 再输入 npm -v 返回空白。正常情况应该是要返回版本号。 二、问题背景 最近准备学习vue,在不久前已经安装了NodeJs和python。运行了好几个开源项…

vue-pdf在vue框架中的使用

在components目录下新建PdfViewer/index.vue vue-pdf版本为4.3.0 <template><div :id"containerId" v-if"hasProps" class"container"><div class"right-btn"><div class"pageNum"><input v-m…

Pytorch plt.scatter()函数用法

一.scatter&#xff08;&#xff09;函数的定义 matplotlib.pyplot.scatter(x, y, sNone, cNone, markerNone, cmapNone, normNone, vminNone, vmaxNone, alphaNone, linewidthsNone, vertsNone, edgecolorsNone, *, dataNone, **kwargs) 特征值作用x&#xff0c;y绘制散点图…

【代数学习题3】从零理解数域扩张与嵌入 —— 同构、商环、分裂域与同态映射

数域的结构——数域的扩张、嵌入 写在最前面从零开始的概念合集从零理解数域的扩张和同构概念基本概念同构的概念商环的概念 2 3 \sqrt[3]{2} 32 ​ 有三个 Q \mathbb{Q} Q-嵌入&#xff08;同态映射&#xff09; Q ( 2 3 ) \mathbb{Q}(\sqrt[3]{2}) Q(32 ​) 和 Q [ x ] / (…

通过 Canal 将 MySQL 数据实时同步到 Easysearch

Canal 是阿里巴巴集团提供的一个开源产品&#xff0c;能够通过解析数据库的增量日志&#xff0c;提供增量数据的订阅和消费功能。使用 Canal 模拟成 MySQL 的 Slave&#xff0c;实时接收 MySQL 的增量数据 binlog&#xff0c;然后通过 RESTful API 将数据写入到 Easysearch 中。…

2023年AI生成音频研究报告

第一章 行业概况 1.1 定义 AI音频生成行业&#xff0c;作为人工智能生成内容&#xff08;AIGC&#xff09;技术渗透的关键领域&#xff0c;正迅速成为技术革新的前沿阵地。这一领域专注于运用先进的人工智能技术和复杂算法来创造音频内容&#xff0c;覆盖了语音合成、音乐制作…

(论文阅读40-45)图像描述1

40.文献阅读笔记&#xff08;m-RNN&#xff09; 简介 题目 Explain Images with Multimodal Recurrent Neural Networks 作者 Junhua Mao, Wei Xu, Yi Yang, Jiang Wang, Alan L. Yuille, arXiv:1410.1090 原文链接 http://arxiv.org/pdf/1410.1090.pdf 关键词 m-RNN、…

Odoo:行业领先的免费开源财务管理解决方案

面向现代企业的财务和会计软件 可靠关账&#xff0c;更快速、更准确地报告财务数据 Odoo ERP财务和会计软件可帮助财务主管设计、革新和理顺财务流程和运营。Odoo ERP无缝整合各种核心财务和会计功能&#xff0c;提供强大的合规管理特性&#xff0c;有助于企业改善业务绩效、提…

react 手机端 rc-table列隐藏(根据相关条件是否隐藏)、实现图片上传操作

最近公司某一项目的手机端&#xff0c;新增需求&#xff1a;table中的附件要可以编辑&#xff0c;并且是在特定条件下可编辑&#xff0c;其他仅做展示效果。 查阅官方文档&#xff0c;没有发现是否隐藏这一属性&#xff0c;通过css控制样式感觉也比较麻烦&#xff0c;后面发现可…

预告|万博智云CTO孙琦将出席2023 OpenInfra Days China,并发表最新容灾方案,欢迎报名参会!

2023年12月1日&#xff0c;北京海航万豪酒店将迎来一场令人瞩目的盛会——OpenInfra Days China 2023。 大会主题 本次活动是时隔两年来的首次线下盛会&#xff0c;聚集了全球社区的技术专家、行业领袖以及国内开源基础设施技术企业的代表&#xff0c;旨在交流研究OpenInfra基…

小程序授权获取头像

wxml <view class"header"><text>头像</text><button class"butt" plain"true" open-type"chooseAvatar" bind:chooseavatar"chooseAvatar"><image src"{{HeadUrl}}" mode"&quo…

Vue3-watchEffect函数

Vue3-watchEffect函数 功能&#xff1a;watchEffect 函数在一开始时就会执行一次&#xff0c;而当中的回调函数的属性发生变化&#xff0c;那么watchEffect 就会再执行一次&#xff0c;主要作用还是在于监视回调函数每次的变化。 // App.vue <template><h2>计数…

JavaScript 浮点数运算的精度问题及解决

JavaScript 浮点数运算的精度问题及解决 在 JavaScript 中整数和浮点数都属于 Number 数据类型&#xff0c;当浮点数做数学运算的时候&#xff0c;你经常会发现一些问题&#xff0c;举几个例子&#xff1a; 0.1 0.2 0.30000000000000004 console.log(0.1 0.2) 0.3000000…

“可信区块链运行监测服务平台TBM发展研讨会”将于11月23日在北京召开

为推动区块链治理与创新&#xff0c;积极推进信任科技生态体系建设&#xff0c;中国信息通信研究院、中国移动设计院联合区块链服务网络&#xff08;BSN&#xff09;发展联盟共同发起建立了可信区块链运行监测服务平台&#xff08;TBM&#xff09;。 TBM平台通过对区块链系统的…

requests爬虫IP连接初始化问题及解决方案

问题背景 在使用HTTPS爬虫IP连接时&#xff0c;如果第一次请求是chunked方式&#xff0c;那么HTTPS爬虫IP连接将不会被初始化。这个问题可能会导致403错误&#xff0c;或者在使用HTTPS爬虫IP时出现SSL错误。 解决方案 为了解决这个问题&#xff0c;我们可以在requests库的ada…

vue项目本地开发完成后部署到服务器后报404

vue项目本地开发完成后部署到服务器后报404是什么原因呢&#xff1f; 一、如何部署 前后端分离开发模式下&#xff0c;前后端是独立布署的&#xff0c;前端只需要将最后的构建物上传至目标服务器的web容器指定的静态目录下即可 我们知道vue项目在构建后&#xff0c;是生成一系…

仿真2.0 - 引入生成式AI

仿真模型是物理对象、系统或过程的虚拟表示&#xff0c;可预测其在不同场景中的行为和性能。 如今&#xff0c;仿真模型广泛应用于各行各业&#xff0c;以优化流程、为决策提供信息并创建数字孪生。 几十年来&#xff0c;仿真模型一直被用来对复杂的系统和过程进行建模。 这些…

interview review

M: gamma correction 人眼和相机对强度的变化敏感程序不一样, 人对暗部更敏感. 上面一条人眼觉得是均匀, 下面一条是相机真实的均匀. 人眼觉得的中间值 在相机中是21.8%, 为了让灰度的分布更符合人眼, 我们需要对图片进行gamma校正, 使得各用128个数字来表示相机真实世界中…
最新文章