基於MSCNN的人羣密度估計之MSCNN

MSCNN

基於MSCNN的人羣密度估計:

網絡結構

網上已經有很多介紹mscnn網絡結構的文章了,我就不再贅述。只說一下網絡中Multi-Scale Blob結構的實現,在keras中實現很簡單,看看源碼就很容易理解了。論文中分了兩種msb結構,一種是正常的,一個塊中包含4個卷積層,卷積核大小分別是(9,7,5,3);還有一個稱爲mini msb塊,包含3個卷積層,卷積核的大小爲(7,5,3)。MSCNN的網絡模型的代碼主要參考了https://www.jianshu.com/p/29a213f22b40,特此感謝!在輸出層千萬不能使用relu,否則會導致預測的輸出全部爲0,而且損失值還在一直下降!在最後的輸出層我試過relu(sigmoid(x))、relu(tanth(x))、sigmoid(x)、tanth(x),最終還是tanth(x)靠譜!

def MSB(filter_num):
    def f(x):
        params = {
            'strides': 1,
            'activation': 'relu',
            'padding': 'same',
            'kernel_regularizer': l2(5e-4)
        }
        x1 = Conv2D(filters=filter_num, kernel_size=(9, 9), **params)(x)
        x2 = Conv2D(filters=filter_num, kernel_size=(7, 7), **params)(x)
        x3 = Conv2D(filters=filter_num, kernel_size=(5, 5), **params)(x)
        x4 = Conv2D(filters=filter_num, kernel_size=(3, 3), **params)(x)
        x = concatenate([x1, x2, x3, x4])
        x = BatchNormalization()(x)
        return x
    return f
    
def MSB_mini(filter_num):
    def f(x):
        params = {
            'strides': 1,
            'activation': 'relu',
            'padding': 'same',
            'kernel_regularizer': l2(5e-4)
        }
        x2 = Conv2D(filters=filter_num, kernel_size=(7, 7), **params)(x)
        x3 = Conv2D(filters=filter_num, kernel_size=(5, 5), **params)(x)
        x4 = Conv2D(filters=filter_num, kernel_size=(3, 3), **params)(x)
        x = concatenate([x2, x3, x4])
        x = BatchNormalization()(x)
        x = Activation('relu')(x)
        return x
    return f

然後在主幹網將這兩種塊有機的結合起來

def MSCNN(input_shape):    
    input_tensor = Input(shape=input_shape)
    # block1
    x = Conv2D(filters=64, kernel_size=(9, 9), strides=1, padding='same', activation='relu')(input_tensor)
    # block2
    x = MSB(4*16)(x)
    x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(x)
    # block3
    x = MSB(4*32)(x)
    x = MSB(4*32)(x)
    x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(x)

    x = MSB_mini(3*64)(x)
    x = MSB_mini(3*64)(x)
    # x = MSB(4*64)(x)

    x = Conv2D(1000, (1, 1), activation='relu', kernel_regularizer=l2(5e-4))(x)

    x = Conv2D(1, (1, 1), activation='tanh')(x)

    model = Model(inputs=input_tensor, outputs=x)
    return model

關於損失函數

  • 論文中直接使用了mse作爲損失函數,我就不寫公式了,體現在代碼中就一行。keras支持多種損失函數,具體請參加官方文檔。這裏想多囉嗦一句,keras對用戶很友好,封裝了很多API,極大的減少了代碼量而且結構清晰,但是這樣對於初學者來說,在享受便捷的同時卻缺少了對細節的理解,不利於成長!如果想對深度學習有更深入的理解,而不是僅成爲一個名副其實的“調包俠”,還是乖乖的先使用tf的原生API來寫比較好,對於理解模型和公式都有好處!(在實際中的一點感悟,還請莫笑)
model.compile(optimizer=Adam(lr=3e-4), loss='mse')
  • 另一種損失函數
    在使用MSE作爲損失函數的時候,只考慮了像素誤差,而忽略了估計密度圖和真實密度圖之間的全局和局部的相關性。中科院提出的DS Net中使用了多尺度密度水平一致性損失。給出的公式如下:
    在這裏插入圖片描述
    其中 s 是用於一致性檢查的尺度級別數,P 是平均池化操作,kjk_{j}爲平均池化的指定輸出大小。在論文中作者採用三個尺度級別,每個輸出尺寸分別爲 1×1、2×2 和 4×4。輸出大小爲 1×1 的第一個尺度級別捕獲密度水平的全局特徵,而其他兩個尺度級別表示圖像塊的局部密度水平。MSE的損失函數如下:
    在這裏插入圖片描述
    則總的損失函數爲上式兩個損失的加權求和
    L=Le+λLcL = L_{e} + \lambda L_{c} 其中λ\lambda取100或1000
    這篇論文沒有給出源碼,我自己寫了實現,不過我的最終效果同直接使用mse區別不大,也可能是因爲我沒有使用論文中的DS Net的原因。
def get_avgpoolLoss(y_true, y_pred, k):
    loss = KTF.mean((abs(AveragePooling2D(pool_size=(k, k), strides=(1, 1))(y_true) -
                AveragePooling2D(pool_size=(k, k), strides=(1, 1))(y_pred)))) / k
    return loss


def denseloss(y_true, y_pred, e=1000):
    Le = KTF.mean(KTF.square(y_pred-y_true), axis=-1)
    Lc = get_avgpoolLoss(y_true, y_pred, 1)
    Lc += get_avgpoolLoss(y_true, y_pred, 2)
    Lc += get_avgpoolLoss(y_true, y_pred, 4)
    shp = KTF.get_variable_shape(y_pred)
    Lc = Lc / (shp[1] * shp[2])
    return Le + e * Lc

使用的時候,直接將損失函數的名稱改爲denseloss即可

model.compile(optimizer=Adam(lr=3e-4), loss=denseloss)

網絡訓練

將數據生成器寫好後,直接訓練即可。數據的生成請參看 數據集製作和數據生成器

def get_callbacks():    
    early_stopping = EarlyStopping(monitor='val_loss', patience=20)
    reduce_lr = ReduceLROnPlateau(monitor='loss', factor=0.1, patience=5, min_lr=1e-7, verbose=True)
    models_path = os.path.join(ROOT_DIR, 'models')
    if not os.path.exists(models_path):
        os.mkdir(models_path)
    model_checkpoint = ModelCheckpoint(os.path.join(models_path, 'mscnn_model_weights.h5'), monitor='val_loss',
                                       verbose=True, save_best_only=True, save_weights_only=True)
    callbacks = [early_stopping, reduce_lr, model_checkpoint, TensorBoard(log_dir='../tensorlog')]
    return callbacks
    
 model.fit_generator(CrowDataset().gen_train(batch_size, 224),
                        steps_per_epoch=CrowDataset().get_train_num() // batch_size,
                        validation_data=CrowDataset().gen_valid(batch_size, 224),
                        validation_steps=CrowDataset().get_valid_num() // batch_size,
                        epochs=int(args_['epochs']),
                        callbacks=callbacks)

預測與輸出

由於本項目將所有大於100人的圖片都輸出100人,所以在輸出的時候 第一步使用密度等級網絡將圖片分成3類,然後僅僅將輸出的標籤爲1的圖片送入mscnn再進行人數預測,最終給出所有圖片中包含的人數。在我1080ti的顯卡下,每秒輸出20到30張。

    # 密度等級分類模型
    dense_net = DenseLevelNet(VGG_Model, Dense_Model)
    dense_model = dense_net.model()
    dense_model.load_weights(Dense_Model, by_name=True)

    # 具體人數分類模型
    crow_model = MSCNN((224, 224, 3))
    crow_model.load_weights(Mscnn_Model)

    for img_name in tqdm(images):
        try:
            img = imopen(img_name)
            img = np.expand_dims(img, axis=0)
            dense_prob = dense_model.predict(img)
            dense_level = np.argmax(dense_prob, axis=1)
            dense_level = dense_level[0]
            if dense_level == 0:
                crow_count = 0
            elif dense_level == 2:
                crow_count = 100
            else:
                dmap = crow_model.predict(img)                
                dmap = np.squeeze(dmap, axis=-1)
                crow_count = int(np.sum(dmap))                
            res.append([os.path.split(img_name)[1], crow_count])
        except Exception as e:
            print(img_name)
            res.append([os.path.split(img_name)[1], -1])
發佈了63 篇原創文章 · 獲贊 55 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章