保姆级 Keras 实现 Faster R-CNN 十四 (预测)

news2025/2/24 12:41:03

保姆级 Keras 实现 Faster R-CNN 十四

  • 一. 预测模型
  • 二. TargetLayer
  • 三. 预测
  • 四. 显示预测结果
  • 五. 加载训练好的参数
  • 六. 效果展示
  • 七. 代码下载

上一篇 文章中我们完成了 Faster R-CNN 训练的功能, 现在到了预测部分了

一. 预测模型

因为在预测的时候并不需标签, 所以 RoiLabelLayer 就不需要了, 也不需要将标签与 rcnn_cls, rcnn_reg 组合. 模型变得更简单了. 以下是用于预测的模型, 相对的是 上一篇 文章中的训练模型

# 创建预测模型
# iou_thres: 做 NMS 时 IoU 阈值
def create_predict_model(self, iou_thres = 0.6, summary = True):
    x_image = keras.layers.Input(shape = (None, None, 3), name = "input")
    features = self.base_net(x_image)
    rpn_cls, rpn_reg = self.rpn_net(features)
    
    proposals = ProposalLayer(self.base_anchors,
                              stride = self.feature_stride,
                              num_rois = self.train_num,
                              iou_thres = self.nms_thres,
                              name = "proposal")([x_image, rpn_cls, rpn_reg])
    
    pooled_rois = RoiPoolingLayer(name = "roi_pooling")([x_image, features, proposals])
    
    rcnn_cls, rcnn_reg = self.fast_rcnn(pooled_rois,
                                        cells = self.dense_cells,
                                        num_classes = self.NUM_CLS)
    
    targets = TargetLayer(iou_thres = iou_thres,
                          name = "targets")([x_image, proposals, rcnn_reg, rcnn_cls])
    
    self.model = keras.Model(inputs = x_image,
                             outputs = targets,
                             name = "faster_rcnn")
    
    if summary:
        self.model.summary()

二. TargetLayer

在上面的模型中, 我们在后面接了一个 TargetLayer, 有了它可以将模型输出变成我们想要的三个值, 分别是 预测框坐标, 类别与分数, 模型用起来更方便, TargetLayer 代码如下

# 定义 Target Layer
class TargetLayer(Layer):
    # iou_thres: 做 NMS 时 IoU 阈值
    def __init__(self, iou_thres = 0.6, **kwargs):
        self.iou_thres = iou_thres
        self.ANCHOR_DIMS = 4
        super(TargetLayer, self).__init__(**kwargs)

    def build(self, input_shape):
        self.targets = input_shape[1][1] # NMS 后剩下的目标数量, 最多为建议框的数量
        super(TargetLayer, self).build(input_shape)
        
    def call(self, inputs):
        # inputs 是一个列表, 可以拆分为下面的参数
        # image: 输入的原始图像
        # boxes: 建议框
        # deltas: 修正值
        # scores: 类别分数
        image, boxes, deltas, scores = inputs
        
        batch_size = tf.shape(image)[0]
        image_shape = tf.shape(image)[1: 3]
        
        # 类别序号
        class_id = tf.argmax(scores, axis = -1)
        # 最大类别分数
        scores = tf.reduce_max(scores, axis = -1)
        # 将序号为 0 对应的分数变成 0, 因为是背景, 判断为背景的分数自然就高, NMS 会有问题
        mask = tf.cast(class_id > 0, dtype = tf.float32)
        scores *= mask
        # 修正建议框
        boxes = self.apply_box_deltas(image_shape, boxes, deltas)
        
        # 拆分与组合操作
        selected_boxes, selected_ids, selected_scores = tf.map_fn(
            lambda i: self.batch_process(image_shape,
                                         tf.reshape(boxes, (batch_size, -1, self.ANCHOR_DIMS)),
                                         tf.reshape(scores, (batch_size, -1)),
                                         tf.reshape(class_id, (batch_size, -1)),
                                         i),
            tf.range(batch_size, dtype = tf.int32),
            dtype = (tf.float32, tf.int64, tf.float32),
            back_prop = False)
        
        boxes = tf.reshape(selected_boxes, (batch_size, -1, self.ANCHOR_DIMS))
        class_id = tf.reshape(selected_ids, (batch_size, -1, 1))
        scores = tf.reshape(selected_scores, (batch_size, -1, 1))  
        
        return [boxes, class_id, scores]
        
    def compute_output_shape(self, input_shape):
        return [(input_shape[1][0], self.targets, input_shape[1][2]),
                (input_shape[3][0], self.targets, 1),
                (input_shape[3][0], self.targets, 1)]
    
    # 修正建议框
    def apply_box_deltas(self, image_shape, boxes, deltas):
        # 宽度和高度
        w = boxes[..., 3] - boxes[..., 1]
        h = boxes[..., 2] - boxes[..., 0]
        # 中心坐标
        x = boxes[..., 1] + w * 0.5
        y = boxes[..., 0] + h * 0.5

        # 修正 anchor_box
        x += deltas[..., 0] * w
        y += deltas[..., 1] * h
        w *= tf.exp(deltas[..., 2])
        h *= tf.exp(deltas[..., 3])

        # 转换成 y1, x1, y2, x2 格式
        x1 = x - w * 0.5
        y1 = y - h * 0.5
        x2 = x + w * 0.5
        y2 = y + h * 0.5
        
        # 不管是训练还是预测, 超出范围的框分数也可能比较大, 所以都截断保留
        x1 = tf.maximum(x1, 0)
        y1 = tf.maximum(y1, 0)
        x2 = tf.minimum(x2, tf.cast(image_shape[1], dtype = tf.float32))
        y2 = tf.minimum(y2, tf.cast(image_shape[0], dtype = tf.float32))

        # 如果用 tf.image.non_max_suppression 的话, 要按 y1, x1, y2, x2 的格式
        boxes = tf.stack([y1, x1, y2, x2], axis = -1)

        return boxes
                              
    # 数据填充
    # pad_num: 填充数量
    def data_pad(self, boxes, class_ids, scores, pad_num):
        padd_boxes = tf.zeros((pad_num, 4), dtype = tf.float32)
        padd_ids = tf.zeros((pad_num, ), dtype = tf.int64)
        padd_scores = tf.zeros((pad_num, ), dtype = tf.float32)
        
        boxes = tf.concat((boxes, padd_boxes), axis = 0)
        class_ids = tf.concat((class_ids, padd_ids), axis = 0)
        scores = tf.concat((scores, padd_scores), axis = 0)
        
        return boxes, class_ids, scores
    
    # 处理 batch 内一个数据
    # boxes: 修正后的建议区域矩形
    # scores: 建议框矩形对应的分数
    # i: batch 内第几个数据
    def batch_process(self, image_shape, boxes, scores, class_ids, i):
        selected_indices = tf.image.non_max_suppression(boxes[i], scores[i],
                                                        self.targets, self.iou_thres)
        selected_boxes = tf.gather(boxes[i], selected_indices)
        selected_ids = tf.gather(class_ids[i], selected_indices)
        selected_scores = tf.gather(scores[i], selected_indices)
        
        num_selected_boxes = tf.shape(selected_boxes)[0]
        pad_num = self.targets - num_selected_boxes
        
        selected_boxes, selected_ids, selected_scores = tf.cond(
            num_selected_boxes < self.targets,
            lambda: self.data_pad(selected_boxes, selected_ids, selected_scores, pad_num),
            lambda: (selected_boxes, selected_ids, selected_scores))
        
        return selected_boxes, selected_ids, selected_scores

代码也不复杂, 和 ProposalLayer 有点像, 可以对比着看

三. 预测

有了完成的模型, 我们就可以预测了, 也定义一个函数方便调用

# 预测
# x: 生成器或图像路径
def predict(self, x):
    # 如果是图像路径, 那要将图像预处理成网络输入格式
    # 如果不是则是 input_reader 返回的图像, 已经满足输入格式
    if isinstance(x, str):
        img_src = cv.imread(x)
        img_new, scale = self.new_size_image(img_src)
        x = [img_new]
        x = np.array(x).astype(np.float32) / 255.0
    else:
        (x, _, __), y = next(x)
        
    return x, self.model.predict(x)

预测的时候, 参数可以是一个生成器或图像的路径, 用图像路径作为参数时, 一次只能预测一张图像
返回值是预测图像, 坐标值, 类别, 分数

四. 显示预测结果

有了预测结果, 我们需要将预测的结果标记到图像上

# 显示预测结果
# x: 生成器或图像路径
# show_proposals: 如果 show_proposals > 0, 只显示 show_proposals 个建议框, 否则显示预测结果
# color_list: 显示颜色表
# show_cols: 显示列数
def show_predict(self, x, show_proposals = 0, color_list = None, show_cols = 4):
    image, (boxes, class_ids, scores) = self.predict(x)

    print(image.shape, boxes.shape, class_ids.shape)

    batch_size = image.shape[0]
    image_shape = image.shape[1: 3]

    show_list = []

    if show_proposals > 0:
        proposal_model = keras.Model(inputs = self.model.input,
                                     outputs = self.model.get_layer("proposal").output)

        proposals = proposal_model.predict(image)
        print(proposals.shape)

        for i in range(batch_size):
            img_show = image[i].copy()
            for j, box in enumerate(proposals[i]):
                if j >= show_proposals: # 显示建议框的数量
                    break
                # 预测的 box 的坐标顺序是 (y1, x1, y2, x2), 显示的时候变成(x1, y1, x2, y2)
                cv.rectangle(img_show, (int(box[1]), int(box[0])), (int(box[3]), int(box[2])),
                             (random.random(), random.random(), random.random()), 2)

            show_list.append((img_show, show_proposals))
    else:
        # 显示颜色
        if None == color_list:
            color_list = []
            for i in range(self.NUM_CLS):
                color_list.append((random.random(), random.random(), random.random()))

        for i in range(batch_size):
            targets = 0
            img_show = image[i].copy()
            for j, box in enumerate(boxes[i]):
                idx = int(class_ids[i][j])
                score = scores[i][j]
                if idx > 0:
                    targets += 1
                    # 预测的 box 的坐标顺序是 (y1, x1, y2, x2), 显示的时候变成(x1, y1, x2, y2)
                    cv.rectangle(img_show, (int(box[1]), int(box[0])), (int(box[3]), int(box[2])),
                                 color_list[idx], 2)

                    text_x, text_y = int(box[1]), int(box[0])

                    if text_y <= 24:
                        text_x += 4
                        text_y += 20
                    else:
                        text_y -= 8

                    text = self.categories[idx] + " {0:.2f}".format(float(score))
                    font = cv.FONT_HERSHEY_COMPLEX_SMALL
                    (w, h), _ = cv.getTextSize(text, font, 1, 1)

                    if text_x + w > image_shape[1]:
                        text_x = image_shape[1] - w

                    text_background = np.ones((h + 8, w, 3), np.float32) * 0.5

                    img_show[text_y - h - 2: text_y + 6, text_x: text_x + w] = cv.addWeighted(
                        img_show[text_y - h - 2: text_y + 6, text_x: text_x + w], 0.4,
                        text_background, 0.6, 0)

                    cv.putText(img_show, text, (text_x, text_y),
                               font, 1, color_list[idx], 1, cv.LINE_AA)

            show_list.append((img_show, targets))

    figsize = (min(12, max(10, show_cols * 4)), max(6, batch_size // show_cols * 4))
    plt.figure("predict_images", figsize = figsize)

    show_rows = max(1, batch_size // show_cols + (1 if batch_size % show_cols else 0))

    for i, (img_show, t) in enumerate(show_list):
        if batch_size > 1:
            plt.subplot(show_rows, show_cols, i + 1)
        plt.title("targets: " + str(t), color = 'gray')
        plt.imshow(img_show[..., : : -1])

    plt.show()

show_proposals 参数需要提一下, 这个参数是用来控制显示建议框的, 当 show_proposals > 0 时, 只显示指定数量的建议框, 方便查看建议框的效果

五. 加载训练好的参数

预测模型需要加载训练好的参数, 代码如下

# 加载模型与参数
# file_name: 保存的文件名称
# load_model: 是否要加载模型
# load_weight: 是否加载存参数
def load(self, file_name, load_model = False, load_weight = True):
    if load_model or (True == load_weight and None == self.model):
        self.model = load_model(osp.join(self.log_path, file_name + "_model.h5"))
        
    if load_weight:
        self.model.load_weights(osp.join(self.log_path, file_name + "_weights.h5"), True)

六. 效果展示

需要预测的时候, 我们只需如下操作即可

# 如果要检测的目标数比较少, 预测时可以把 train_num 改小一点
# faster_rcnn.train_num = 64
faster_rcnn.create_predict_model(iou_thres = 0.6, summary = False)
faster_rcnn.load("faster_rcnn", False, True)

# 测试集
test_reader = faster_rcnn.input_reader(faster_rcnn.test_set, batch_size = 4, train_mode = False)
# 显示预测结果
# show_proposals > 0 时, 只显示建议框
faster_rcnn.show_predict(test_reader, show_proposals = 0, color_list = BGR_COLOR, show_cols = 2)

# test_reader 也可以直接给图像的路径, 比如
# faster_rcnn.show_predict(r"test_set\00001.jpg", show_proposals = 0, color_list = BGR_COLOR, show_cols = 2)

以下是 VOC2007 训练集的测试效果, 图像中有黑边是为了同一 batch 中的图有相同的尺寸而做了填充

voc_test_1

测试集的效果

voc_test_2
voc_test_3

以下是小浣熊的预测效果

训练集数据

raccoon

从网上下载的图像测试

raccoon2

在前面的文章中用的数据集是 VOC2007, 为了训练快一点, 我们做的数据增强也只有简单的翻转图像. 对于 VOC2007, 这样训练出来的模型在测试集上效果会差一点. 解决这个问题最简单粗暴的方式就是增加训练数据量. 最简单的增加数据量的方法就是做数据增强. 所以我们可以在 data_augment 函数中增加一些 旋转, 缩放, 裁切, 变形, 改变亮度, 改变色温 之类的增强. 这里就不演示了. 只是要注意的是标签要做相应的变化, train 函数的参数 augmented_num 也要修改成对应的值

小浣熊的预测貌似效果还可以, 是因为我挑了一些和训练集比较相似的图像, 如果预测其他背景或者毛色差异比较大的图像, 效果就差很多. 主要是训练的图像只有两百张. 多一点的话, 效果也会好一点

到这里, 《保姆级 Keras 实现 Faster R-CNN》系列文章就结束了

七. 代码下载

示例代码可下载 Jupyter Notebook 示例代码

上一篇: 保姆级 Keras 实现 Faster R-CNN 十三 (训练)

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

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

相关文章

[Linux打怪升级之路]-system V共享内存

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…

基于Ubuntu Server编译YTM32 SDK工程

基于Ubuntu Server编译YTM32 SDK工程 文章目录 基于Ubuntu Server编译YTM32 SDK工程需求下载软件包安装与配置配置虚拟机联网模式启用ssh连接启用ftp连接安装armgcc编译工具链确认make工具 验证 需求 在Linux系统环境下搭建SDK的编译环境&#xff1a; 方便加入到持续集成工具…

16.The Tensor Product:Vector/Covector combinations

本节将概括目前为止所学的张量积知识。并讨论一般张量&#xff0c;它可以由任意数量的向量和协向量的任意组合来生成。 同样&#xff0c;也是使用的非标准的符号。 (2&#xff0c;0)阶张量&#xff0c; 由两个向量生成的。 &#xff08;1&#xff0c;2&#xff09;阶张…

C++学习之多态详解

目录 多态的实现 例题 重载 重写 重定义的区别 抽象类 多态实现原理 多态的实现 C中的多态是指&#xff0c;当类之间存在层次结构&#xff0c;并且类之间是通过继承关联时&#xff0c;就会用到多态。多态意味着调用成员函数时&#xff0c;会根据调用函数的对象的类型来执…

Spring IOC之@ComponentScan

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

[SWPUCTF 2023 秋季新生赛] web题解

文章目录 colorful_snakeNSS_HTTP_CHEKER一键连接!ez_talkPingpingpingUnS3rialize查查needRCE-PLUSbackup colorful_snake 打开题目&#xff0c;查看js源码 直接搜flag 把那三行代码复制到控制器&#xff0c;得到flag NSS_HTTP_CHEKER 都是http请求基本知识 抓包按照要求来&…

企业知识库管理系统怎么做?

21世纪&#xff0c;一个全新的信息化时代&#xff0c;从最初的传统办公到现在的信息化办公&#xff0c;一个世纪的跨越造就了各种大数据的诞生。 知识库系统 在这个数据横行的时代&#xff0c;文档管理产品市场逐渐兴盛起来&#xff0c;企业知识库管理系统作为企业的智慧信息的…

计算机组成原理new15 磁盘

文章目录 磁盘的结构磁盘的性能指标磁盘阵列固态硬盘SSD关于机械硬盘和固态硬盘的地址 这里计算传输时间和数据传输率是难点 磁盘的结构 注&#xff1a;磁盘的基本读取单位是扇区&#xff0c;但是每次只能读取1bit&#xff0c;这里我们应该理解为磁盘每次的读写操作至少要持续…

初始 c++(1)

目录: 目录 1: 命名空间的详解 2:c的输入与输出关键字及理解 3:详细讲解第一个c程序(每段代码的意思) 4:缺省参数的理解 5:函数重载 引言&#xff1a;从今天开始我们就开始学习c了&#xff0c;让我们一起开始新的知识吧&#xff01; 1&#xff1a;命名空间 所谓的命名空间…

深度学习学习笔记-模型的修改和CRUD

目录 1.打印模型,理解模型结构2.模型保存与加载3.模型的模块CRUD和模块的层的CRUD 1.打印模型,理解模型结构 import torchclass MyModel(torch.nn.Module):def __init__(self):super().__init__()self.layer1 torch.nn.Sequential(torch.nn.Linear(3, 4),torch.nn.Linear(4, …

云计算——网络虚拟化简介

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 前期回顾 前言 一.网络虚拟化 二.网络虚拟化介绍 三.为什么要网络虚拟化 四,网络…

STM32+2.9inch微雪墨水屏(电子纸)实现显示

本篇文章从硬件原理以及嵌入式编程等角度完整的介绍了墨水屏驱动过程&#xff0c;本例涉及的墨水屏为2.9inch e-Paper V2,它采用的是“微胶囊电泳显示”技术进行图像显示&#xff0c;其基本原理是悬浮在液体中的带电纳米粒子受到电场作用而产生迁移&#xff0c;从而改变显示屏各…

【C++初阶】小白入门C++

目录 前言&#xff1a;1、C关键字2、命名空间2.1命名空间是什么2.2为什么要有命名空间2.3命名空间怎么使用2.3.1命名空间的写法2.3.2命名空间是可以嵌套的2.3.3使用命名空间的三种方式 3、C输入和输出3.1初识cout和cin3.2C的输入输出可以自动识别变量类型 4、缺省参数4.1缺省参…

Linux:用户和权限

Linux&#xff1a;用户和权限 1. 认知root用户1.1 root用户&#xff08;超级管理员&#xff09;1.2 su和exit命令1.3 sudo命令1.3.1 为普通用户配置sudo认证 2. 用户、用户组管理2.1 用户组管理2.2 用户管理2.3 getent命令 3. 查看权限控制3.1 认知权限信息3.1.1 案例 4. 修改权…

react 实战- 玩转 react 全家桶(进阶)学习

一个命令是怎么运行起来的? Shell运行一个命令,实际上是运行一个脚本 环境变量 装了node以后,node的路径,就被注册到了环境变量里. 一个js的东西,可以注册? bin Webpack配置 构建 import A from A , const Arequire(A) 为什么可以这么写?为哈都行?本质上,是构建工…

人人自媒体的时候,Ai绘画还值得踏入吗?

前言 先说结论&#xff0c;如果你不打算涉足自媒体&#xff0c;平时也从不上网发什么内容去展示自己的话&#xff0c;其实AI绘画对你来说意义不大。但如果你对自媒体感兴趣&#xff0c;会涉及发作品&#xff0c;发内容&#xff0c;甚至去设计图片&#xff0c;那么AI绘画值得你…

【【萌新的SOC学习之自定义IP核的学习与设计】】

萌新的SOC学习之自定义IP核的学习与设计 本章为了更加深入的了解FPGA的自定义IP和IP封装测试等问题 参考了正点原子 第六讲自定义IP核呼吸灯实验 和 第十九章 IP封装与接口定义实验 为了更好的理解自定义IP核 我们先介绍一个带AXI主从接口的IP核 我们可以展开AXI从接口 下…

Vue--》简易资金管理系统后台项目实战(后端)

今天开始使用 node vue3 ts搭建一个简易资金管理系统的前后端分离项目&#xff0c;因为前后端分离所以会分两个专栏分别讲解前端与后端的实现&#xff0c;后端项目文章讲解可参考&#xff1a;前端链接&#xff0c;我会在前后端的两类专栏的最后一篇文章中会将项目代码开源到我…

转行做程序员,多晚都不晚

大家好啊&#xff0c;我是董董灿。 最近有不少小伙伴加我微信咨询一些问题&#xff0c;有同学想了解AI行业的现状&#xff0c;想着转行的&#xff0c;也有在校生想了解毕业后工作方向的&#xff0c;当然也有想学习编程知识的。 诚惶诚恐&#xff0c;没想到之前写的文章&#…

【CHI】CHI协议,transaction事务汇总

前言 CHI协议最难的是什么&#xff0c;就是那一堆各种各样的事务&#xff0c;你不知道什么场景应该使用什么合适的事务&#xff0c;收到X事务又该回复什么事务。相当于CHI给你制定了很多种&#xff08;尽可能覆盖完全&#xff09;场景及事务&#xff0c;你需要去了解&#xff0…