睿智的目標檢測44——Keras 搭建自己的Centernet目標檢測平臺
- 學習前言
- 什么是Centernet目標檢測演算法
- 原始碼下載
- Centernet實作思路
- 一、預測部分
- 1、主干網路介紹
- 2、利用初步特征獲得高解析度特征圖
- 3、Center Head從特征獲取預測結果
- 4、預測結果的解碼
- 5、在原圖上進行繪制
- 二、訓練部分
- 1、真實框的處理
- 2、利用處理完的真實框與對應圖片的預測結果計算loss
- 訓練自己的Centernet模型
學習前言
Centernet是一個比較優秀的無先驗框演算法,我們一起學習一哈,

什么是Centernet目標檢測演算法

如今常見的目標檢測演算法通常使用先驗框的設定,即先在圖片上設定大量的先驗框,網路的預測結果會對先驗框進行調整獲得預測框,先驗框很大程度上提高了網路的檢測能力,但是也會收到物體尺寸的限制,
Centernet采用不同的方法,構建模型時將目標作為一個點——即目標BBox的中心點,
Centernet的檢測器采用關鍵點估計來找到中心點,并回歸到其他目標屬性,

論文中提到:模型是端到端可微的,更簡單,更快,更精確,Centernet的模型實作了速度和精確的很好權衡,
原始碼下載
https://github.com/bubbliiiing/centernet-keras
喜歡的可以點個star噢,
Centernet實作思路
一、預測部分
1、主干網路介紹
Centernet用到的主干特征網路有多種,一般是以Hourglass Network、DLANet或者Resnet為主干特征提取網路,由于centernet所用到的Hourglass Network引數量太大,有19000W引數,DLANet并沒有keras資源,本文以Resnet為例子進行決議,
ResNet50有兩個基本的塊,分別名為Conv Block和Identity Block,其中Conv Block輸入和輸出的維度是不一樣的,所以不能連續串聯,它的作用是改變網路的維度;Identity Block輸入維度和輸出維度相同,可以串聯,用于加深網路的,
Conv Block的結構如下:

Identity Block的結構如下:

這兩個都是殘差網路結構,
當我們輸入的圖片是512x512x3的時候,整體的特征層shape變化為:

我們取出最終一個block的輸出進行下一步的處理,也就是圖上的C5,它的shape為16x16x2048,利用主干特征提取網路,我們獲取到了一個初步的特征層,其shape為16x16x2048,
#-------------------------------------------------------------#
# ResNet50的網路部分
#-------------------------------------------------------------#
from __future__ import print_function
import numpy as np
import keras.backend as K
from keras import layers
from keras.layers import Input
from keras.layers import Activation,BatchNormalization,Flatten
from keras.layers import Dense,Conv2D,MaxPooling2D,ZeroPadding2D,AveragePooling2D,Dropout,Conv2DTranspose
from keras.models import Model
from keras.preprocessing import image
from keras.regularizers import l2
from keras.utils.data_utils import get_file
from keras.applications.imagenet_utils import decode_predictions
from keras.applications.imagenet_utils import preprocess_input
def identity_block(input_tensor, kernel_size, filters, stage, block):
filters1, filters2, filters3 = filters
conv_name_base = 'res' + str(stage) + block + '_branch'
bn_name_base = 'bn' + str(stage) + block + '_branch'
x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a', use_bias=False)(input_tensor)
x = BatchNormalization(name=bn_name_base + '2a')(x)
x = Activation('relu')(x)
x = Conv2D(filters2, kernel_size,padding='same', name=conv_name_base + '2b', use_bias=False)(x)
x = BatchNormalization(name=bn_name_base + '2b')(x)
x = Activation('relu')(x)
x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
x = BatchNormalization(name=bn_name_base + '2c')(x)
x = layers.add([x, input_tensor])
x = Activation('relu')(x)
return x
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
filters1, filters2, filters3 = filters
conv_name_base = 'res' + str(stage) + block + '_branch'
bn_name_base = 'bn' + str(stage) + block + '_branch'
x = Conv2D(filters1, (1, 1), strides=strides,
name=conv_name_base + '2a', use_bias=False)(input_tensor)
x = BatchNormalization(name=bn_name_base + '2a')(x)
x = Activation('relu')(x)
x = Conv2D(filters2, kernel_size, padding='same',
name=conv_name_base + '2b', use_bias=False)(x)
x = BatchNormalization(name=bn_name_base + '2b')(x)
x = Activation('relu')(x)
x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x)
x = BatchNormalization(name=bn_name_base + '2c')(x)
shortcut = Conv2D(filters3, (1, 1), strides=strides,
name=conv_name_base + '1', use_bias=False)(input_tensor)
shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut)
x = layers.add([x, shortcut])
x = Activation('relu')(x)
return x
def ResNet50(inputs):
x = ZeroPadding2D((3, 3))(inputs)
# 256,256,64
x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=False)(x)
x = BatchNormalization(name='bn_conv1')(x)
x = Activation('relu')(x)
# 128,128,64
x = MaxPooling2D((3, 3), strides=(2, 2), padding="same")(x)
# 128,128,256
x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
# 64,64,512
x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
x = identity_block(x, 3, [128, 128, 512], stage=3, block='b')
x = identity_block(x, 3, [128, 128, 512], stage=3, block='c')
x = identity_block(x, 3, [128, 128, 512], stage=3, block='d')
# 32,32,1024
x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b')
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c')
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d')
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e')
x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f')
# 16,16,2048
x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
return x
2、利用初步特征獲得高解析度特征圖

利用上一步獲得到的resnet50的最后一個特征層的shape為(16,16,2048),
對于該特征層,centernet利用三次反卷積進行上采樣,從而更高的解析度輸出,為了節省計算量,這3個反卷積的輸出通道數分別為256,128,64,
每一次反卷積,特征層的高和寬會變為原來的兩倍,因此,在進行三次反卷積上采樣后,我們獲得的特征層的高和寬變為原來的8倍,此時特征層的高和寬為128x128,通道數為64,
此時我們獲得了一個128x128x64的有效特征層(高解析度特征圖),我們會利用該有效特征層獲得最終的預測結果,

實作代碼如下:
num_filters = 256
# 16, 16, 2048 -> 32, 32, 256 -> 64, 64, 128 -> 128, 128,64
for i in range(3):
# 進行上采樣
x = Conv2DTranspose(num_filters // pow(2, i), (4, 4), strides=2, use_bias=False, padding='same',
kernel_initializer='he_normal',
kernel_regularizer=l2(5e-4))(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
3、Center Head從特征獲取預測結果

通過上一步我們可以獲得一個128x128x64的高解析度特征圖,
這個特征層相當于將整個圖片劃分成128x128個區域,每個區域存在一個特征點,如果某個物體的中心落在這個區域,那么就由這個特征點來確定,
(某個物體的中心落在這個區域,則由這個區域左上角的特征點來約定)
我們可以利用這個特征層進行三個卷積,分別是:
1、熱力圖預測,此時卷積的通道數為num_classes,最終結果為(128,128,num_classes),代表每一個熱力點是否有物體存在,以及物體的種類;
2、中心點預測,此時卷積的通道數為2,最終結果為(128,128,2),代表每一個物體中心距離熱力點偏移的情況;
3、寬高預測,此時卷積的通道數為2,最終結果為(128,128,2),代表每一個物體寬高的預測情況;
實作代碼為:
def centernet_head(x,num_classes):
x = Dropout(rate=0.5)(x)
#-------------------------------#
# 解碼器
#-------------------------------#
num_filters = 256
# 16, 16, 2048 -> 32, 32, 256 -> 64, 64, 128 -> 128, 128,64
for i in range(3):
# 進行上采樣
x = Conv2DTranspose(num_filters // pow(2, i), (4, 4), strides=2, use_bias=False, padding='same',
kernel_initializer='he_normal',
kernel_regularizer=l2(5e-4))(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
# 最侄訓得128,128,64的特征層
# hm header
y1 = Conv2D(64, 3, padding='same', use_bias=False, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4))(x)
y1 = BatchNormalization()(y1)
y1 = Activation('relu')(y1)
y1 = Conv2D(num_classes, 1, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4), activation='sigmoid')(y1)
# wh header
y2 = Conv2D(64, 3, padding='same', use_bias=False, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4))(x)
y2 = BatchNormalization()(y2)
y2 = Activation('relu')(y2)
y2 = Conv2D(2, 1, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4))(y2)
# reg header
y3 = Conv2D(64, 3, padding='same', use_bias=False, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4))(x)
y3 = BatchNormalization()(y3)
y3 = Activation('relu')(y3)
y3 = Conv2D(2, 1, kernel_initializer='he_normal', kernel_regularizer=l2(5e-4))(y3)
return y1, y2, y3
4、預測結果的解碼
在對預測結果進行解碼之前,我們再來看看預測結果代表了什么,預測結果可以分為3個部分:
1、heatmap熱力圖預測,此時卷積的通道數為num_classes,最終結果為(128,128,num_classes),代表每一個熱力點是否有物體存在,以及物體的種類,最后一維度num_classes中的預測值代表屬于每一個類的概率;
2、reg中心點預測,此時卷積的通道數為2,最終結果為(128,128,2),代表每一個物體中心距離熱力點偏移的情況,最后一維度2中的預測值代表當前這個特征點向右下角偏移的情況;
3、wh寬高預測,此時卷積的通道數為2,最終結果為(128,128,2),代表每一個物體寬高的預測情況,最后一維度2中的預測值代表當前這個特征點對應的預測框的寬高;
特征層相當于將影像劃分成128x128個特征點每個特征點負責預測中心落在其右下角一片區域的物體,
如圖所示,藍色的點為128x128的特征點,此時我們對左圖紅色的三個點進行解碼操作演示:
1、進行中心點偏移,利用reg中心點預測對特征點坐標進行偏移,左圖紅色的三個特征點偏移后是右圖橙色的三個點;
2、利用中心點加上和減去 wh寬高預測結果除以2,獲得預測框的左上角和右下角,
3、此時獲得的預測框就可以繪制在圖片上了,

除去這樣的解碼操作,還有非極大抑制的操作需要進行,防止同一種類的框的堆積,
在論文中所說,centernet不像其它目標檢測演算法,在解碼之后需要進行非極大抑制,centernet的非極大抑制在解碼之前進行,采用的方法是最大池化,利用3x3的池化核在熱力圖上搜索,然后只保留一定區域內得分最大的框,
在實際使用時發現,當目標為小目標時,確實可以不在解碼之后進行非極大抑制的后處理,如果目標為大目標,網路無法正確判斷目標的中心時,還是需要進行非擊大抑制的后處理的,
def nms(heat, kernel=3):
hmax = MaxPooling2D((kernel, kernel), strides=1, padding='SAME')(heat)
heat = tf.where(tf.equal(hmax, heat), heat, tf.zeros_like(heat))
return heat
def topk(hm, max_objects=100):
# hm -> Hot map熱力圖
# 進行熱力圖的非極大抑制,利用3x3的卷積對熱力圖進行Max篩選,找出值最大的
hm = nms(hm)
# (b, h * w * c)
b, h, w, c = tf.shape(hm)[0], tf.shape(hm)[1], tf.shape(hm)[2], tf.shape(hm)[3]
# 將所有結果平鋪,獲得(b, h * w * c)
hm = tf.reshape(hm, (b, -1))
# (b, k), (b, k)
scores, indices = tf.math.top_k(hm, k=max_objects, sorted=True)
# 計算求出網格點,類別
class_ids = indices % c
xs = indices // c % w
ys = indices // c // w
indices = ys * w + xs
return scores, indices, class_ids, xs, ys
def decode(hm, wh, reg, max_objects=100,num_classes=20):
scores, indices, class_ids, xs, ys = topk(hm, max_objects=max_objects)
# 獲得batch_size
b = tf.shape(hm)[0]
# (b, h * w, 2)
reg = tf.reshape(reg, [b, -1, 2])
# (b, h * w, 2)
wh = tf.reshape(wh, [b, -1, 2])
length = tf.shape(wh)[1]
# 找到其在1維上的索引
batch_idx = tf.expand_dims(tf.range(0, b), 1)
batch_idx = tf.tile(batch_idx, (1, max_objects))
full_indices = tf.reshape(batch_idx, [-1]) * tf.to_int32(length) + tf.reshape(indices, [-1])
# 取出top_k個框對應的引數
topk_reg = tf.gather(tf.reshape(reg, [-1,2]), full_indices)
topk_reg = tf.reshape(topk_reg, [b, -1, 2])
topk_wh = tf.gather(tf.reshape(wh, [-1,2]), full_indices)
topk_wh = tf.reshape(topk_wh, [b, -1, 2])
# 計算調整后的中心
topk_cx = tf.cast(tf.expand_dims(xs, axis=-1), tf.float32) + topk_reg[..., 0:1]
topk_cy = tf.cast(tf.expand_dims(ys, axis=-1), tf.float32) + topk_reg[..., 1:2]
# (b,k,1) (b,k,1)
topk_x1, topk_y1 = topk_cx - topk_wh[..., 0:1] / 2, topk_cy - topk_wh[..., 1:2] / 2
# (b,k,1) (b,k,1)
topk_x2, topk_y2 = topk_cx + topk_wh[..., 0:1] / 2, topk_cy + topk_wh[..., 1:2] / 2
# (b,k,1)
scores = tf.expand_dims(scores, axis=-1)
# (b,k,1)
class_ids = tf.cast(tf.expand_dims(class_ids, axis=-1), tf.float32)
# (b,k,6)
detections = tf.concat([topk_x1, topk_y1, topk_x2, topk_y2, scores, class_ids], axis=-1)
return detections
5、在原圖上進行繪制
通過第三步,我們可以獲得預測框在原圖上的位置,而且這些預測框都是經過篩選的,這些篩選后的框可以直接繪制在圖片上,就可以獲得結果了,
二、訓練部分
1、真實框的處理
既然在centernet中,物體的中心落在哪個特征點的右下角就由哪個特征點來負責預測,那么在訓練的時候我們就需要找到真實框和特征點之間的關系,
真實框和特征點之間的關系,對應方式如下:
1、找到真實框的中心,通過真實框的中心找到其對應的特征點,
2、根據該真實框的種類,對網路應該有的熱力圖進行設定,即heatmap熱力圖,其實就是將對應的特征點里面的對應的種類,它的中心值設定為1,然后這個特征點附近的其它特征點中該種類對應的值按照高斯分布不斷下降,

3、除去heatmap熱力圖外,還需要設定特征點對應的reg中心點和wh寬高,在找到真實框對應的特征點后,還需要設定該特征點對應的reg中心和wh寬高,這里的reg中心和wh寬高都是對于128x128的特征層的,
4、在獲得網路應該有的預測結果后,就可以將預測結果和應該有的預測結果進行對比,對網路進行反向梯度調整了,
實作代碼如下:
class Generator(object):
def __init__(self,batch_size,train_lines,val_lines,
input_size,num_classes,max_objects=100):
self.batch_size = batch_size
self.train_lines = train_lines
self.val_lines = val_lines
self.input_size = input_size
self.output_size = (int(input_size[0]/4) , int(input_size[1]/4))
self.num_classes = num_classes
self.max_objects = max_objects
def get_random_data(self, annotation_line, input_shape, random=True, jitter=.3, hue=.1, sat=1.5, val=1.5, proc_img=True):
'''r實時資料增強的隨機預處理'''
line = annotation_line.split()
image = Image.open(line[0])
iw, ih = image.size
h, w = input_shape
box = np.array([np.array(list(map(int,box.split(',')))) for box in line[1:]])
# resize image
new_ar = w/h * rand(1-jitter,1+jitter)/rand(1-jitter,1+jitter)
scale = rand(0.25, 2)
if new_ar < 1:
nh = int(scale*h)
nw = int(nh*new_ar)
else:
nw = int(scale*w)
nh = int(nw/new_ar)
image = image.resize((nw,nh), Image.BICUBIC)
# place image
dx = int(rand(0, w-nw))
dy = int(rand(0, h-nh))
new_image = Image.new('RGB', (w,h), (128,128,128))
new_image.paste(image, (dx, dy))
image = new_image
# flip image or not
flip = rand()<.5
if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)
# distort image
hue = rand(-hue, hue)
sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat)
val = rand(1, val) if rand()<.5 else 1/rand(1, val)
x = cv2.cvtColor(np.array(image,np.float32)/255, cv2.COLOR_RGB2HSV)
x[..., 0] += hue*360
x[..., 0][x[..., 0]>1] -= 1
x[..., 0][x[..., 0]<0] += 1
x[..., 1] *= sat
x[..., 2] *= val
x[x[:,:, 0]>360, 0] = 360
x[:, :, 1:][x[:, :, 1:]>1] = 1
x[x<0] = 0
image_data = cv2.cvtColor(x, cv2.COLOR_HSV2RGB)*255
# correct boxes
box_data = np.zeros((len(box),5))
if len(box)>0:
np.random.shuffle(box)
box[:, [0,2]] = box[:, [0,2]]*nw/iw + dx
box[:, [1,3]] = box[:, [1,3]]*nh/ih + dy
if flip: box[:, [0,2]] = w - box[:, [2,0]]
box[:, 0:2][box[:, 0:2]<0] = 0
box[:, 2][box[:, 2]>w] = w
box[:, 3][box[:, 3]>h] = h
box_w = box[:, 2] - box[:, 0]
box_h = box[:, 3] - box[:, 1]
box = box[np.logical_and(box_w>1, box_h>1)] # discard invalid box
box_data = np.zeros((len(box),5))
box_data[:len(box)] = box
if len(box) == 0:
return image_data, []
if (box_data[:,:4]>0).any():
return image_data, box_data
else:
return image_data, []
def generate(self, train=True):
while True:
if train:
# 打亂
shuffle(self.train_lines)
lines = self.train_lines
else:
shuffle(self.val_lines)
lines = self.val_lines
batch_images = np.zeros((self.batch_size, self.input_size[0], self.input_size[1], self.input_size[2]), dtype=np.float32)
batch_hms = np.zeros((self.batch_size, self.output_size[0], self.output_size[1], self.num_classes), dtype=np.float32)
batch_whs = np.zeros((self.batch_size, self.max_objects, 2), dtype=np.float32)
batch_regs = np.zeros((self.batch_size, self.max_objects, 2), dtype=np.float32)
batch_reg_masks = np.zeros((self.batch_size, self.max_objects), dtype=np.float32)
batch_indices = np.zeros((self.batch_size, self.max_objects), dtype=np.float32)
b = 0
for annotation_line in lines:
img,y=self.get_random_data(annotation_line,self.input_size[0:2])
if len(y)!=0:
boxes = np.array(y[:,:4],dtype=np.float32)
boxes[:,0] = boxes[:,0]/self.input_size[1]*self.output_size[1]
boxes[:,1] = boxes[:,1]/self.input_size[0]*self.output_size[0]
boxes[:,2] = boxes[:,2]/self.input_size[1]*self.output_size[1]
boxes[:,3] = boxes[:,3]/self.input_size[0]*self.output_size[0]
for i in range(len(y)):
bbox = boxes[i].copy()
bbox = np.array(bbox)
bbox[[0, 2]] = np.clip(bbox[[0, 2]], 0, self.output_size[1] - 1)
bbox[[1, 3]] = np.clip(bbox[[1, 3]], 0, self.output_size[0] - 1)
cls_id = int(y[i,-1])
h, w = bbox[3] - bbox[1], bbox[2] - bbox[0]
if h > 0 and w > 0:
ct = np.array([(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2], dtype=np.float32)
ct_int = ct.astype(np.int32)
# 獲得熱力圖
radius = gaussian_radius((math.ceil(h), math.ceil(w)))
radius = max(0, int(radius))
batch_hms[b, :, :, cls_id] = draw_gaussian(batch_hms[b, :, :, cls_id], ct_int, radius)
batch_whs[b, i] = 1. * w, 1. * h
# 計算中心偏移量
batch_regs[b, i] = ct - ct_int
# 將對應的mask設定為1,用于排除多余的0
batch_reg_masks[b, i] = 1
# 表示第ct_int[1]行的第ct_int[0]個,
batch_indices[b, i] = ct_int[1] * self.output_size[0] + ct_int[0]
batch_images[b] = preprocess_image(img)
b = b + 1
if b == self.batch_size:
b = 0
yield [batch_images, batch_hms, batch_whs, batch_regs, batch_reg_masks, batch_indices], np.zeros((self.batch_size,))
batch_images = np.zeros((self.batch_size, self.input_size[0], self.input_size[1], 3), dtype=np.float32)
batch_hms = np.zeros((self.batch_size, self.output_size[0], self.output_size[1], self.num_classes),
dtype=np.float32)
batch_whs = np.zeros((self.batch_size, self.max_objects, 2), dtype=np.float32)
batch_regs = np.zeros((self.batch_size, self.max_objects, 2), dtype=np.float32)
batch_reg_masks = np.zeros((self.batch_size, self.max_objects), dtype=np.float32)
batch_indices = np.zeros((self.batch_size, self.max_objects), dtype=np.float32)
2、利用處理完的真實框與對應圖片的預測結果計算loss
loss計算分為三個部分,分別是:
1、熱力圖的loss
2、reg中心點的loss
3、wh寬高的loss
具體情況如下:
1、熱力圖的loss采用focal loss的思想進行運算,其中 α 和 β 是Focal Loss的超引數,而其中的N是正樣本的數量,用于進行標準化, α 和 β在這篇論文中和分別是2和4,
整體思想和Focal Loss類似,對于容易分類的樣本,適當減少其訓練比重也就是loss值,
在公式中,帶帽子的Y是預測值,不戴帽子的Y是真實值,

2、reg中心點的loss和wh寬高的loss使用的是普通L1損失函式

reg中心點預測和wh寬高預測都直接采用了特征層坐標的尺寸,也就是在0到128之內,
由于wh寬高預測的loss會比較大,其loss乘上了一個系數,論文是0.1,
reg中心點預測的系數則為1,
總的loss就變成了:

實作代碼如下:
def focal_loss(hm_pred, hm_true):
# 找到正樣本和負樣本
pos_mask = tf.cast(tf.equal(hm_true, 1), tf.float32)
# 小于1的都是負樣本
neg_mask = tf.cast(tf.less(hm_true, 1), tf.float32)
neg_weights = tf.pow(1 - hm_true, 4)
pos_loss = -tf.log(tf.clip_by_value(hm_pred, 1e-7, 1.)) * tf.pow(1 - hm_pred, 2) * pos_mask
neg_loss = -tf.log(tf.clip_by_value(1 - hm_pred, 1e-7, 1.)) * tf.pow(hm_pred, 2) * neg_weights * neg_mask
num_pos = tf.reduce_sum(pos_mask)
pos_loss = tf.reduce_sum(pos_loss)
neg_loss = tf.reduce_sum(neg_loss)
cls_loss = tf.cond(tf.greater(num_pos, 0), lambda: (pos_loss + neg_loss) / num_pos, lambda: neg_loss)
return cls_loss
def reg_l1_loss(y_pred, y_true, indices, mask):
b, c = tf.shape(y_pred)[0], tf.shape(y_pred)[-1]
k = tf.shape(indices)[1]
y_pred = tf.reshape(y_pred, (b, -1, c))
length = tf.shape(y_pred)[1]
indices = tf.cast(indices, tf.int32)
# 找到其在1維上的索引
batch_idx = tf.expand_dims(tf.range(0, b), 1)
batch_idx = tf.tile(batch_idx, (1, k))
full_indices = (tf.reshape(batch_idx, [-1]) * tf.to_int32(length) +
tf.reshape(indices, [-1]))
# 取出對應的預測值
y_pred = tf.gather(tf.reshape(y_pred, [-1,c]),full_indices)
y_pred = tf.reshape(y_pred, [b, -1, c])
mask = tf.tile(tf.expand_dims(mask, axis=-1), (1, 1, 2))
# 求取l1損失值
total_loss = tf.reduce_sum(tf.abs(y_true * mask - y_pred * mask))
reg_loss = total_loss / (tf.reduce_sum(mask) + 1e-4)
return reg_loss
def loss(args):
#-----------------------------------------------------------------------------------------------------------------#
# hm_pred:熱力圖的預測值 (self.batch_size, self.output_size[0], self.output_size[1], self.num_classes)
# wh_pred:寬高的預測值 (self.batch_size, self.output_size[0], self.output_size[1], 2)
# reg_pred:中心坐標偏移預測值 (self.batch_size, self.output_size[0], self.output_size[1], 2)
# hm_true:熱力圖的真實值 (self.batch_size, self.output_size[0], self.output_size[1], self.num_classes)
# wh_true:寬高的真實值 (self.batch_size, self.max_objects, 2)
# reg_true:中心坐標偏移真實值 (self.batch_size, self.max_objects, 2)
# reg_mask:真實值的mask (self.batch_size, self.max_objects)
# indices:真實值對應的坐標 (self.batch_size, self.max_objects)
#-----------------------------------------------------------------------------------------------------------------#
hm_pred, wh_pred, reg_pred, hm_true, wh_true, reg_true, reg_mask, indices = args
hm_loss = focal_loss(hm_pred, hm_true)
wh_loss = 0.1 * reg_l1_loss(wh_pred, wh_true, indices, reg_mask)
reg_loss = reg_l1_loss(reg_pred, reg_true, indices, reg_mask)
total_loss = hm_loss + wh_loss + reg_loss
# total_loss = tf.Print(total_loss,[hm_loss,wh_loss,reg_loss])
return total_loss
訓練自己的Centernet模型
Centernet整體的檔案夾構架如下:

本文使用VOC格式進行訓練,
訓練前將標簽檔案放在VOCdevkit檔案夾下的VOC2007檔案夾下的Annotation中,

訓練前將圖片檔案放在VOCdevkit檔案夾下的VOC2007檔案夾下的JPEGImages中,

在訓練前利用voc2centernet.py檔案生成對應的txt,

再運行根目錄下的voc_annotation.py,運行前需要將classes改成你自己的classes,
classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]

就會生成對應的2007_train.txt,每一行對應其圖片位置及其真實框的位置,

在訓練前需要修改model_data里面的voc_classes.txt檔案,需要將classes改成你自己的classes,

然后運行train.py就可以開始訓練啦,

運行train.py即可開始訓練,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/224738.html
標籤:AI
上一篇:2021年的秋招,你還會推薦應屆生進入互聯網行業嗎?這個行業的前景怎么樣呢?
下一篇:Day 28 JWT認證相關
