Python深度学习基于Tensorflow(10)目标检测

目标检测的主要任务有两步:1. 确定目标位置;2. 对目标位置的目标进行分类;前者属于定位问题,后者属于分类问题;

分类问题在前面的学习有了合理的解决办法,目前主要是定位问题怎么处理,这也是目标检测的重点;

边界框的表示

首先,目标定位一般使用矩形来作为边界框,边界框的表示方式有两种:一种是使用图像中左上角和右下角两点确定边界框,一种是根据矩形特定位置点以及宽和高确定边界框;plt.Rectangle 采取的方式是左上角以及宽和高;

# 两点式转化为中心式
def box_2p_to_center(box_2p):
    left, up, right, dowm = box_2p[:, 0], box_2p[:, 1], box_2p[:, 2], box_2p[:, 3]
    x = (left + right) / 2
    y = (up + dowm) / 2 
    width = right - left
    height = dowm - up
    return np.c_[x, y, width , height]

# 中心式转化为两点式
def box_center_to_2p(box_center):
    x, y, width, height = box_center[:,0], box_center[:,1], box_center[:,2], box_center[:,3]
    left = x - width/2
    right = x + width/2
    up = y - height/2
    dowm = y + height/2
    return np.c_[left, up, right, dowm]

这里以这张猫狗图片为例子,可以直接保存测试;

![[下载.jpg]]

使用 plt.Rectangle 在图片上标记

import numpy as np
import matplotlib.pyplot as plt


def bbox_to_rect(bbox, color):
    return plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)

img = plt.imread('./cat-dog.jpg')

dog_bbox, cat_bbox,bak_bbox = [20.0, 5.0, 440.0, 480.0], [445.0, 86.0, 730.0, 490.0],[390.0, 17.0, 600.0, 71.0]

fig = plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'yellow'))
fig.axes.text(dog_bbox[0]+5,dog_bbox[1]+20,'dog',style='italic',bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
fig.axes.add_patch(bbox_to_rect(bak_bbox, 'red'))
fig.axes.text(cat_bbox[0]+5,cat_bbox[1]+20,'cat',style='italic',bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'blue'))
fig.axes.text(bak_bbox[0]+5,bak_bbox[1]+20,'background',style='italic',bbox={'facecolor': 'white', 'alpha': 0.5, 'pad': 0})
plt.axis('off')
plt.savefig('cat-dog_.jpg')

得到如下结果:

![[Pasted image 20240514021205.png]]

在实际操作中,我们可以使用 LabelImg 快速标记出目标位置并给出目标分类制作数据;LabelImg 的链接如下: labelImg

使用 选择性搜索 框定目标

选择性搜索(Selective Search, SS)方法是通过图像中的纹理,边缘,颜色等信息对图像进行自底向上的分割,然后对分割区域进行不同尺度的合并,在合并过程中,每生成一个新的区域就产生一个候选框,区域肯定是不规则,我们通过选取区域的最大外接矩阵作为候选框区域,这种方法速度较慢;

在这篇博客中尝试使用了颜色分割合并:[Initial Image Segmentation Generator]论文实现:Efficient Graph-Based Image Segmentation;该方法的一个重要特点是,它能够在低变异性图像区域保留细节,而忽略了高变异性图像区域的细节。也就是说在变化不大的区域可以对细节进行保留,在变化很大的区域对细节进行剔除,这个效果根据后面合并小区域得到的。

在这里我们可以利用 OpenCVcreateSelectiveSearchSegmentation() 函数来获取候选框区域;该函数除了安装基本的 OpenCV 外,还需要安装拓展,代码: pip install opencv-contrib-python --user

import cv2 as cv

def get_ss_rects(img):
    cv.setUseOptimized(True)
    ss = cv.ximgproc.segmentation.createSelectiveSearchSegmentation()
    ss.setBaseImage(img)
    ss.switchToSelectiveSearchFast()
    rects = ss.process()
    return rects

测试一下SS方法:

import tensorflow as tf
import cv2 as cv
import matplotlib.pyplot as plt

def get_ss_rects(img):
    cv.setUseOptimized(True)
    ss = cv.ximgproc.segmentation.createSelectiveSearchSegmentation()
    ss.setBaseImage(img)
    ss.switchToSelectiveSearchFast()
    rects = ss.process()
    return rects

im = cv.imread('./cat-dog.jpg')
rects = get_ss_rects(im)
imOut = im.copy()

for i, rect in enumerate(rects):
    x, y, w, h = rect
    cv.rectangle(imOut, (x,y), (x+w,y+h), (0,255,0), 1, cv.LINE_AA)
plt.imshow(imOut[:,:,::-1])
plt.axis('off')
plt.show()

得到结果如图所示:

![[Pasted image 20240514043723.png]]

得到处理时间为:CPU times: total: 2.08 s Wall time: 2.36 s

使用RPN框定目标

锚框和锚点

首先要介绍的是锚框和锚点,其定义是根据原图和特征图来说明的,首先原图(origin map)采取池化的方式缩小尺寸变成特征图(feature map);这样导致特征图某一点与原图某一块相对应,特征图的那一点被称为锚点;其对应的那一块被称为锚框,锚框的中心点也可以看做是锚点;

![[Pasted image 20240514133659.png]]

将锚框按照比例(如 1:2, 1:1, 2:1)进行变换,然后再依次进行宽和高的缩放和放大(如8, 16, 32)就可以得到9个框;

其中池化缩小的倍数被称为 base_size,变换比例被称为 ratios,对宽和高放大的倍数被称为 scales

使用代码获取特征图上第一个点的锚框实现如下:

def generate_anchors(base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)):
    """
    Generate anchor (reference) windows by enumerating aspect ratios X
    scales wrt a reference (0, 0, 15, 15) window.
    """

    base_anchor = np.array([1, 1, base_size, base_size]) - 1
    ratio_anchors = _ratio_enum(base_anchor, ratios)
    anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                         for i in range(ratio_anchors.shape[0])])
    return anchors

def _whctrs(anchor):
    """
    Return width, height, x center, and y center for an anchor (window).
    """

    w = anchor[2] - anchor[0] + 1
    h = anchor[3] - anchor[1] + 1
    x_ctr = anchor[0] + 0.5 * (w - 1)
    y_ctr = anchor[1] + 0.5 * (h - 1)
    return w, h, x_ctr, y_ctr

def _mkanchors(ws, hs, x_ctr, y_ctr):
    """
    Given a vector of widths (ws) and heights (hs) around a center
    (x_ctr, y_ctr), output a set of anchors (windows).
    """

    ws = ws[:, np.newaxis]
    hs = hs[:, np.newaxis]
    anchors = np.hstack((x_ctr - 0.5 * (ws - 1),
                         y_ctr - 0.5 * (hs - 1),
                         x_ctr + 0.5 * (ws - 1),
                         y_ctr + 0.5 * (hs - 1)))
    return anchors

def _ratio_enum(anchor, ratios):
    """
    Enumerate a set of anchors for each aspect ratio wrt an anchor.
    """

    w, h, x_ctr, y_ctr = _whctrs(anchor)
    size = w * h
    size_ratios = size / ratios
    ws = np.round(np.sqrt(size_ratios))
    hs = np.round(ws * ratios)
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
    return anchors

def _scale_enum(anchor, scales):
    """
    Enumerate a set of anchors for each scale wrt an anchor.
    """
    w, h, x_ctr, y_ctr = _whctrs(anchor)
    ws = w * scales
    hs = h * scales
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
    return anchors

获取特征图上所有点的锚框如下:

def get_anchors(height, width, base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)):
    anchor_rect = []
    base_anchors = generate_anchors(base_size, ratios, scales)
    for i in range(width):
        for j in range(height):
            for item in base_anchors:
                x1, y1, x2, y2 = item
                x1, x2 = x1 + base_size*i, x2 + base_size*i
                y1, y2 = y1 + base_size*j, y2 + base_size*j
                item = [x1, y1, x2, y2]
                anchor_rect.append(item)
                
    anchor_rect = np.array(anchor_rect)
    return anchor_rect


# 画出锚框
def bbox_to_rect(bbox, color):
    return plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=0.5)

# 一步到位
def plot_anchors(anchors):
    fig = plt.figure(figsize=(10, 10))
    # 获取范围,方便限制坐标轴
    a, b = np.min(anchors, axis=0), np.max(anchors, axis=0)
    plt.scatter([a[0],  b[2]], [a[1], b[3]], c='white')
    ax = plt.gca()
    for anchor in anchors:
        ax.add_patch(bbox_to_rect(anchor, 'red'))
    plt.axis('off')
    plt.show()

# 获取 40,40 特征图上的锚框
anchors = get_anchors(40,40)
plot_anchors(anchors)

得到结果:

![[Pasted image 20240514134855.png]]

子任务1:对锚框进行二分类

二分类任务主要是判断锚框是背景还是前景,现有数据只有大量的锚框以及目标(Ground Truth, GT)框,我们需要根据目标框对锚框进行分类,这里我们可以采用 IOU 计算方式: I O U = A ∩ B A ∪ B IOU=\frac{A \cap B}{ A \cup B} IOU=ABAB
多对一的代码实现如下:

# numpy 实现
def compute_iou(boxes, box):
    # 计算交集
    xy_max = np.minimum(boxes[:, 2:], box[2:])
    xy_min = np.maximum(boxes[:, :2], box[:2])
    inter = np.clip(xy_max-xy_min, a_min=0, a_max=np.inf)
    inter = inter[:, 0]*inter[:, 1]

    # 计算面积
    area_boxes = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
    area_box = (box[2]-box[0])*(box[3]-box[1])

    return inter/(area_box+area_boxes-inter)

# tensorflow 实现
def compute_iou(boxes, box):
    # 计算交集
    boxes, box = tf.cast(boxes, dtype=tf.float32), tf.cast(box, dtype=tf.float32)
    xy_max = tf.minimum(boxes[:, 2:], box[2:])
    xy_min = tf.maximum(boxes[:, :2], box[:2])
    inter = tf.clip_by_value(xy_max - xy_min, clip_value_min=0., clip_value_max=tf.int32.max)
    inter = inter[:, 0]*inter[:, 1]

    # 计算面积
    area_boxes = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
    area_box = (box[2]-box[0])*(box[3]-box[1])

    return inter/(area_box+area_boxes-inter)

定义损失,这里随机抽取了128个正例,128个反例,因此每一个 batch_size 中有 batch_size * 256 个例子;

其中正例我采用的方法是从 IOU 得分最高的64个中加权有放回抽取得到;反例则是在 IOU 小于 0.3 的集合中随机抽取得到;

def compute_cls(y_true, y_pred):
	"""y_pred 是一个 [batch_size, 50, 38, 9] 的四维空间"""
	y_pred = tf.reshape(y_pred, [tf.shape(y_pred)[0], -1])
    anchors = get_anchors(50,38)
    indexs_list = []
    labels_list = []
    all_indices = tf.TensorArray(tf.int32, size=0, dynamic_size=True)
    all_labels = tf.TensorArray(tf.int32, size=0, dynamic_size=True)

    for ix in tf.range(tf.shape(y_true)[0]):
        iou = compute_iou(anchors, y_true[ix])

        # Sorting and selecting indices for sampling. Note: tf.argsort and tf.gather are used instead of np.argsort and np.random.choice
        sorted_indices = tf.argsort(iou, direction='DESCENDING')
        top_k_indices = sorted_indices[:64]
        positive_sampled_indices = tf.gather(top_k_indices, tf.random.categorical(tf.math.log(tf.expand_dims(weights, axis=0)), 128, dtype=tf.int32))[0]
        
        # Negative sampling. tf.where and tf.random.shuffle are used here.
        neg_mask = tf.cast(iou < 0.3, tf.int32)
        neg_indices = tf.where(neg_mask)
        neg_sampled_indices = tf.cast(tf.random.shuffle(neg_indices)[:128], dtype=tf.int32)
        

        # Combining indices and labels
        positive_indexs = tf.concat([tf.fill([128, 1], ix), tf.expand_dims(positive_sampled_indices, axis=1)], axis=1)
        negative_indexs = tf.concat([tf.fill([128, 1], ix), tf.expand_dims(neg_sampled_indices[:, 0], axis=1)], axis=1)

        # Creating labels tensor
        labels = tf.concat([tf.ones_like(positive_sampled_indices), tf.zeros_like(neg_sampled_indices[:, 0])], axis=0)

        # Gathering indices and concatenating lists
        all_indices = all_indices.write(all_indices.size(), tf.concat([positive_indexs, negative_indexs], axis=0))
        all_labels = all_labels.write(all_labels.size(), labels)
    final_indexs = tf.reshape(all_indices.stack(), [-1, 2])
    final_labels = tf.reshape(all_labels.stack(), [-1])
    
    return tf.keras.losses.binary_crossentropy(final_labels, tf.gather_nd(y_pred, final_indexs))

子任务2:对锚框进行边框回归

边框回归(Bounding Box Regression,BBR),由于锚框的尺寸和尺度都是固定的,现实情况中基本不可能出现目标尺寸和尺度与锚框相同,因此我们需要调整一下锚框;因此:Bounding-box regression 是用来对算法提取的预测框Region Proposal进行微调,使其更加接近于物体的真实标注框Ground Truth。

![[Pasted image 20240514223242.png]]

从红色变成绿色,比较简单是思路就是 先平移后放缩 的方式,实现步骤如下:

P a n : G x ′ = A w ⋅ d x ( A ) + A x G y ′ = A h ⋅ d y ( A ) + A y Z o o m : G w ′ = A w ⋅ e x p ( d w ( A ) ) G h ′ = A h ⋅ e x p ( d h ( A ) ) \begin{align} Pan: \quad G_x' &= A_w \cdot d_x(A) + A_x \\ G_y' &= A_h \cdot d_y(A) + A_y \\ Zoom: \quad G_w' &= A_w \cdot exp(d_w(A)) \\ G_h' &= A_h \cdot exp(d_h(A)) \end{align} Pan:GxGyZoom:GwGh=Awdx(A)+Ax=Ahdy(A)+Ay=Awexp(dw(A))=Ahexp(dh(A))

其中 A x , A y , A w , A h A_x, A_y, A_w, A_h Ax,Ay,Aw,Ah 表示红色框的中心点,宽和高; G x , G y , G w , G h G_x, G_y, G_w, G_h Gx,Gy,Gw,Gh 表示绿色框的中心点,宽和高; G x ′ , G y ′ , G w ′ , G h ′ G_x', G_y', G_w', G_h' Gx,Gy,Gw,Gh表示预测的中心点,宽和高; d x ( A ) , d y ( A ) , d w ( A ) , d h ( A ) d_x(A),d_y(A),d_w(A),d_h(A) dx(A),dy(A),dw(A),dh(A)表示学习的平移和放缩参数;

其损失可以利用 d x ( A ) , d y ( A ) , d w ( A ) , d h ( A ) d_x(A),d_y(A),d_w(A),d_h(A) dx(A),dy(A),dw(A),dh(A) 以及 中间变量 t x , t y , t w , t h t_x,t_y,t_w,t_h tx,ty,tw,th 来表示:
Z o o m : t w = log ⁡ G w A w t h = log ⁡ G h A h P a n : t x = G x − A x A w t y = G y − A y A h \begin{align} Zoom: \quad t_w &= \log \frac{G_w}{A_w} \\ t_h &= \log \frac{G_h}{A_h} \\ Pan: \quad t_x &= \frac{G_x - A_x}{A_w} \\ t_y &= \frac{G_y - A_y}{A_h} \\ \end{align} Zoom:twthPan:txty=logAwGw=logAhGh=AwGxAx=AhGyAy

d x ( A ) , d y ( A ) , d w ( A ) , d h ( A ) d_x(A),d_y(A),d_w(A),d_h(A) dx(A),dy(A),dw(A),dh(A)是由锚点所在的channel向量 ϕ ( A ) \phi(A) ϕ(A)经过线性变换得到,但由于使用了线性变换,锚点向量的表达能力不强,最主要的就是指数对数这种非线性变换难以学习和拟合,而指数和对数缩放大小是正数,不能删除;以 t w t_w tw 为例子证明如下:

t w = log ⁡ ( G w A w ) = log ⁡ ( A w + G w − A w A w ) = log ⁡ ( 1 + G w − A w A w ) = G w − A w A w t_w = \log(\frac{G_w}{A_w})=\log(\frac{A_w+G_w-A_w}{A_w})=\log(1+\frac{G_w-A_w}{A_w})=\frac{G_w-A_w}{A_w} tw=log(AwGw)=log(AwAw+GwAw)=log(1+AwGwAw)=AwGwAw

只有在 G w − A w G_w-A_w GwAw 接近于 0 的情况下,可以消去对数,因此可以使用非线性变换的方式优化该算法,这也是 YOLOv2 做的优化;为了使 G w − A w G_w-A_w GwAw接近于0,这里要求 IOU 必须要大于0.6

损失可以得到: L = ∑ ∣ t − W ϕ ( A ) ∣ + λ ∣ ∣ W ∣ ∣ 1 \mathcal{L} = \sum|t-W\phi(A)| + \lambda||W||_1 L=tWϕ(A)+λ∣∣W1
实现损失代码如下:

def compute_reg(y_true, y_pred):
    """y_pred 是一个 [batch_size, 50, 38, 9, 4] 的五维空间"""
    y_pred = tf.reshape(y_pred, [tf.shape(y_pred)[0], -1, 4])  # Reshape to handle flattened predictions
    anchors = get_anchors(50, 38)  # Ensure this function exists and returns expected anchors
    
    all_da = tf.TensorArray(tf.float32, size=0, dynamic_size=True)
    all_t = tf.TensorArray(tf.float32, size=0, dynamic_size=True)
    
    for ix in tf.range(tf.shape(y_pred)[0]):
        iou = compute_iou(anchors, y_true[ix])
        indexs = tf.reshape(tf.where(iou > 0.6), [-1])
        da = tf.gather(y_pred[ix], tf.cast(indexs, tf.int32))  # Gather based on indices
        g = tf.gather(y_true, [ix])  # Gather ground truths
        a = tf.gather(anchors, tf.cast(indexs, tf.int32))  # Gather anchors
        
        g = tf.cast(g, tf.float32)
        a = tf.cast(a, tf.float32)
        
        # Calculate t_x, t_y, t_w, t_h (assuming g and a are in the correct format)
        t_w = tf.math.log((g[:, 2] - g[:, 0]) / (a[:, 2] - a[:, 0]))
        t_h = tf.math.log((g[:, 3] - g[:, 1]) / (a[:, 3] - a[:, 1]))
        t_x = ((g[:, 0] + g[:, 2]) / 2 - (a[:, 0] + a[:, 2]) / 2) / (a[:, 2] - a[:, 0])
        t_y = ((g[:, 1] + g[:, 3]) / 2 - (a[:, 1] + a[:, 3]) / 2) / (a[:, 3] - a[:, 1])
        t = tf.stack([t_x, t_y, t_w, t_h], axis=1)
        
        all_da = all_da.write(ix, da)
        all_t = all_t.write(ix, t)
    
    # Stack and reshape for loss computation
    da = tf.reshape(all_da.stack(), [-1, 4])
    t = tf.reshape(all_t.stack(), [-1, 4])
    
    return tf.reduce_mean(tf.abs(da - t))

使用 tf.while_loop 计算如下:

def compute_reg(y_true, y_pred):
    """y_pred 是一个 [batch_size, 50, 38, 9, 4] 的五维空间"""
    y_pred = tf.reshape(y_pred, [tf.shape(y_pred)[0], -1, 4])  # Reshape to handle flattened predictions
    anchors = get_anchors(50, 38)  # Ensure this function exists and returns expected anchors
    
    ix = tf.constant(0, dtype=tf.int32)
    n = tf.cast(tf.shape(y_true)[0], dtype=tf.int32)
    total_loss = tf.constant(0.0, dtype=tf.float32)

    def cond(ix, total_loss):
        return ix < n

    def body(ix, total_loss):
        iou = compute_iou(anchors, y_true[ix])
        indexs = tf.reshape(tf.where(iou > 0.6), [-1])
        da = tf.gather(y_pred[ix], tf.cast(indexs, tf.int32))  # Gather based on indices
        g = tf.gather(y_true, [ix])  # Gather ground truths
        a = tf.gather(anchors, tf.cast(indexs, tf.int32))  # Gather anchors
        
        g = tf.cast(g, tf.float32)
        a = tf.cast(a, tf.float32)
        
        # Calculate t_x, t_y, t_w, t_h (assuming g and a are in the correct format)
        t_w = tf.math.log((g[:, 2] - g[:, 0]) / (a[:, 2] - a[:, 0]))
        t_h = tf.math.log((g[:, 3] - g[:, 1]) / (a[:, 3] - a[:, 1]))
        t_x = ((g[:, 0] + g[:, 2]) / 2 - (a[:, 0] + a[:, 2]) / 2) / (a[:, 2] - a[:, 0])
        t_y = ((g[:, 1] + g[:, 3]) / 2 - (a[:, 1] + a[:, 3]) / 2) / (a[:, 3] - a[:, 1])
        t = tf.stack([t_x, t_y, t_w, t_h], axis=1)
        
        # Compute loss for the current sample and accumulate it
        sample_loss = tf.reduce_mean(tf.abs(da - t))
        total_loss += sample_loss
        return tf.add(ix, 1), total_loss  # Increment index and return updated loss accumulation
    
    _, final_loss = tf.while_loop(cond, body, [ix, total_loss])
    return final_loss
使用非极大值抑制过滤沉余框

使用SS或者RPN生成推荐区域后,会出现许多的框实际上是指向同一目标,因此会存在大量的沉余框,需要剔除;如图所示:

![[Pasted image 20240515133613.png]]

SS由于信息量过少,可能只能使用 IOU 进行解决,而RPN由于在 softmax 阶段产生了概率,所以RPN可以使用非极大值抑制(Non-Maximum Suppression,NMS),其思想是搜索局部极大值,抑制非极大值元素,其流程如下:

假设有六个候选框,根据分类器类别分类概率做排序,从小到大排列为:ABCDEF
1. 从最大概率矩形框F开始,分别判断A~E与F的重叠度IOU是否大于某个设定的阈值;
2. 假设B、D与F的重叠度超过阈值,那么就扔掉B、D;并标记第一个矩形框F,是我们保留下来第一个框;
3. 从剩下的矩形框A、C、E中,选择概率最大的E,然后判断E与A、C的重叠度,重叠度大于一定 的阈值,那么就扔掉;并标记E是我们保留下来的第二个矩形框;
4. 一直重复这个过程,找到所有曾经被保留下来的矩形框;

这只是一个循环过程,很好实现:

def nms(boxes, scores, iou_threshold):
    """boxes 是一个 [-1, 4], scores 是一个 [-1] """
    boxes, scores = tf.cast(boxes, tf.float32), tf.cast(scores, tf.float32)
    nms_indices = tf.TensorArray(tf.int32, size=0, dynamic_size=True)
    
    def cond(boxes, scores, nms_indices):
        return tf.reduce_any(tf.not_equal(scores, 0))

    def body(boxes, scores, nms_indices):
        
        idx = tf.argsort(scores, direction='DESCENDING')
        scores = tf.gather(scores, idx)
        boxes = tf.gather(boxes, idx)
        current_box = tf.gather(boxes, idx[0])
        nms_indices = nms_indices.write(nms_indices.size(), idx[0])

        ious = compute_iou(boxes, current_box)
        mask = tf.math.less(ious, iou_threshold)
        
        scores = tf.cast(mask, tf.float32) * scores

        return boxes, scores, nms_indices

    _, _, nms_indices = tf.while_loop(cond, body, [boxes, scores, nms_indices])
    
    final_indices = nms_indices.stack()
    final_boxes = tf.gather(boxes, final_indices)
    return final_boxes

可以得到如下结果:

![[Pasted image 20240515155202.png]]

框定的图像输出为固定大小

由于神经网络操作需要输入的尺寸一样才能进行一些常规的算法操作,而框定的图像由于边框回归,锚框比例和大小的作用下,出现图像大小不一致的情况,这里我们需要把图像大小固定为某一尺寸;

这里有两种办法:

  1. 直接对候选区域进行拉伸或者缩放,这种方法的缺点是:容易导致图形变形,从而会影响识别效果;
  2. SPP-Net,使用池化的方法构建空间金字塔,即对每个候选框使用不同大小的金字塔映射;

金字塔映射和普通池化固定大小不同,金字塔映射固定的是处理后的尺寸,操作是动态的;普通池化固定的是操作,处理后的尺寸是动态的;SPP-Net 对候选框采取了多个尺寸(5x5,3x3,1x1)的金字塔映射,然后将金字塔展平进行全连接分类;

![[Pasted image 20240515160951.png]]

一般来说,在固定图像大小之前还可以进行一次边框回归增加准确性;

简单的分类

这部分过于简单,主要原则在最后一层的激活层:单目标使用sigmoid,多目标使用softmax;不要忘了 无目标

参考

Faster RCNN原理篇(一)——Bounding-Box Regression边界框回归的学习和理解_边界框为什么要归一化-CSDN博客
Faster RCNN原理篇(二)——RoIPooling和RoIAlign的学习和理解_roipooling 归一化-CSDN博客
Faster RCNN原理篇(三)——区域候选网络RPN(Region Proposal Network)的学习、理解-CSDN博客
一文读懂Faster RCNN - 知乎 (zhihu.com)

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值