LFFD: A Light and Fast Face Detector for Edge Devices
摘要
從微信推文中得知此人臉識別算法可以在跑2K圖片90fps,仔細一看是在RTX2070下使用tensorrt下才能達到。最近剛好有個目標檢測的任務,檢測的目的其實差不多,我們籃球組比賽中需要檢測籃球和排球,傳統方法的魯棒性不好,因此擬打算用自己寫個神經網絡結構在nuc x86 cpu下能夠取得不錯的推理速度和准確率。所以准備參考這篇論文復現並修改實現籃球、排球的實時目標檢測任務。
本文提出了輕量級的人臉檢測算法,可以達到2K圖片90FPS,本文的大體框架是借鑒SSD目標檢測框架的,但是卻是一個anchor free的人臉檢測框架,因為作者認為感受野本身就是天然的anchor,所以作者通過精細設計感受野完成了本篇的人臉檢測任務。認為淺層layer的有效感受野應該與小目標size比值較大,這樣可以充分利用周圍特征對人臉檢測的貢獻;深層layer由於其感受野比較大,檢測大目標,大目標人臉本身有足夠的鼻子、眼鏡等信息可以幫助判別人臉,所以不需要太大的有效感受野與人臉比例。根據這些想法,作者設計了本文的人臉檢測網絡結構。
感受野和有效感受野
感受野通俗講就是feature map上一個點對應原圖的一片pixel區域,計算方法也比較簡單,我自己簡單寫了一下。
總結起來公式就是
其實對於感受野而言,並不是感受野內的所有點都對后面結果起到決定性作用,而是以感受野中心呈高斯分布的區域內的點對后面結果起到關鍵作用,這區域稱為有效感受野ERF。
據此作者得出下面三個對人臉識別很有幫助的結論:
- 對於人臉小目標來說,ERF最好能盡可能覆蓋context information
- 對於中等人臉,ERF只需要覆蓋一部分context information
- 對於大人臉目標,ERF甚至不需要覆蓋其他額外的context information
感受野的可視化
為了更進一步了解感受野,我們嘗試將感受野可視化出來。
下面的代碼是證明了作者論文中感受野的參數和有效感受野參數。這里只是以第一個part為例分析。
代碼:
import cv2 as cv
import torch
import torch.nn as nn
import torchvision
from torchvision.models import ResNet
import numpy as np
class ResBlock(nn.Module):
def __init__(self,channels):
super(ResBlock, self).__init__()
self.conv2dRelu = nn.Sequential(
nn.Conv2d(channels,channels,kernel_size=3,stride=1,padding=1),
nn.ReLU(channels),
nn.Conv2d(channels,channels,kernel_size=3,stride=1,padding=1),
nn.ReLU(channels)
)
self.relu = nn.ReLU(channels)
def forward(self,x):
return self.relu(x + self.conv2dRelu(x))
class TinyNet(nn.Module):
def __init__(self):
super(TinyNet,self).__init__()
self.c1 = nn.Sequential(
nn.Conv2d(3,64,kernel_size=3,stride=2,padding=0),
nn.ReLU(64)
)
self.c2 = nn.Sequential(
nn.Conv2d(64,64,kernel_size=3,stride=2,padding=0),
nn.ReLU(64)
)
self.tinypart1 = nn.Sequential(
ResBlock(64),
ResBlock(64),
ResBlock(64)
)
def forward(self,x):
c1 = self.c1(x)
c2 = self.c2(c1)
c8 = self.tinypart1(c2)
return c8
if __name__ == "__main__":
model = TinyNet()
for module in model.modules():
try:
nn.init.constant_(module.weight, 0.05)
nn.init.zeros_(module.bias)
nn.init.zeros_(module.running_mean)
nn.init.ones_(module.running_var)
except Exception as e:
pass
if type(module) is nn.BatchNorm2d:
module.eval()
x = torch.ones(1,3,640,640,requires_grad= True)
pred = model(x)
grad = torch.zeros_like(pred, requires_grad= True)
grad[0, 0, 64, 64] = 1
pred.backward(gradient = grad)
grad_input = x.grad[0,0,...].data.numpy()
grad_input = grad_input / np.max(grad_input)
# 有效感受野 0.75 - 0.85
#grad_input = np.where(grad_input>0.85,1,0)
#grad_input = np.where(grad_input>0.75,1,0)
# 注釋掉即為感受野
grad_input = (grad_input * 255).astype(np.uint8)
kernel = np.ones((5, 5), np.uint8)
grad_input = cv.dilate(grad_input, kernel=kernel)
contours, _ = cv.findContours(grad_input, mode=cv.RETR_EXTERNAL, method=cv.CHAIN_APPROX_SIMPLE)
rect = cv.boundingRect(contours[0])
print(rect[-2:])
cv.imshow( "a",grad_input)
cv.waitKey(0)
感受野:
對應輸出(感受野大小):
(55,55)
有效感受野(閾值0.75):
對應輸出(有效感受野大小):
(15,15)
有效感受野(閾值0.85):
對應輸出(有效感受野大小):
(11,11)
網絡結構
作者認為RF就是天然的anchor,由於人臉目標一般是方的,所以不需要考慮各種比例的box。在box匹配的時候,作者認為rf中心落在gt內的box為正樣本,同時落在多個gt中的box忽略掉、其他沒有落在任何gt中的box為負樣本。此外,作者定義的gray scale,后面再提。
對於這樣的網絡設計,作者是這么想的:
100 pixels的RF的有效感受野為20-40pixels,所以作者就分了四個part,tiny part的c8 RF SIZE為55,去檢測10-15pixels的人臉,c10檢測15-20,以此類推。其中rf與avg face scale的比值隨着層數加深而減少,高層大感受野預測大目標就不需要太多的context information,前面講了。
網絡實現中全用的3*3和1*1的卷積核,實現起來很簡單。data augmentation用了 color distort、Random sampling for each scale和Randomly horizontal flip。
loss是regression loss和classification loss加權和。
regression loss直接簡單粗暴L2loss,預測相對值:
classification loss是crossentropy loss。
box匹配的時候定義了gray scale,認為處於gray scale的box所在的branch是不反傳這些對應的loss的。
對c13出的box而言,其檢測的人臉像素為20-40pixels,認為[18,20]以及[40,44]像素的人臉不被c13預測,這是因為這些人臉屬於hard目標,網絡往往只能看到局部特征,很難判別,所以c13這個brach不預測他,讓別的branch預測,對訓練有好處。
此外還用了負樣本挖掘(sort負樣本的loss,選擇loss比較大的一部分訓練,其他不參與梯度反傳,加速收斂)。
訓練參數:
xavier初始化、圖像標准化x-127.5/127.5、sgd 0.9 momentum,0weight decay,init lr0.1.
1080ti訓練了5天。
后續
復現並修改后的BasketNet:https://github.com/aoru45/BasketNet