在上一篇文章中https://www.jianshu.com/p/3c103fe3243a,已经阐述了如何修改demo.py文件将模型能够跑起来并解决了中文无法显示的问题,本篇文章将通过源码来分析RPnet网络的架构,如何进行前向传播,以及损失函数的设置。
网络模型
![](https://img.haomeiwen.com/i18577060/6f4830071bbe03c5.png)
论文中提供的网络模型图片并不清晰,但可以看出输入图片的大小为480x480的彩色图片。模型分为两个部分,分别为检测模型和识别模型。
前向传播
检测模型
检测模型为源码中的class wR2,一共定义了10个隐藏层和1个分类层,每个隐藏层中包含卷积层和最大池化层,而分类层中有三个全连接网络。
以一张480x480彩色图片为例,wR2前向传播时的维度变化:
def forward(self, x):
x0 = self.wR2.module.features[0](x) # [1,3,480,480]--->[1,48,121,121]
_x1 = self.wR2.module.features[1](x0) # [1,48,121,121]--->[1,64,122,122]
x2 = self.wR2.module.features[2](_x1) # [1,64,122,122]--->[1,128,62,62]
_x3 = self.wR2.module.features[3](x2) # [1,128,62,62]--->[1,160,63,63]
x4 = self.wR2.module.features[4](_x3) # [1,160,63,63]--->[1,192,32,32]
_x5 = self.wR2.module.features[5](x4) # [1,192,32,32]--->[1,192,33,33]
x6 = self.wR2.module.features[6](_x5) # [1,192,33,33]--->[1,192,17,17]
x7 = self.wR2.module.features[7](x6) # [1,192,17,17]--->[1,192,18,18]
x8 = self.wR2.module.features[8](x7) # [1,192,18,18]--->[1,192,10,10]
x9 = self.wR2.module.features[9](x8) # [1,192,10,10]--->[1,192,11,11]
x9 = x9.view(x9.size(0), -1) # [1,192,11,11]--->[1,23232]
boxLoc = self.wR2.module.classifier(x9) # [1,23232]--->[1,4] #预测车牌位置
最后输出的是预测车牌的位置[px,py,ph,pw]
识别模型
识别模型为源码中的class fh02。
第一步:在前向传播时,先将预测位置的[px,py,ph,pw]转化为左上角和右下角坐标[x1,y1,x2,y2],对应源码:
postfix = torch.FloatTensor([[1, 0, 1, 0], [0, 1, 0, 1], [-0.5, 0, 0.5, 0], [0, -0.5, 0, 0.5]]).cuda()
boxNew = boxLoc.mm(postfix).clamp(min=0, max=1)
例如:原boxLoc [0.5,0.5,0.3,0.2]经过该方法将得到boxNew [0.35,0.4,0.65,0.6]这种左上角,右下角坐标表示形式。
第二步:获取wR2网络前向传播中隐藏层第一层,第三层,第五层的feature map。并在feature map上得到boxNew对应的区域。
以第一层feature map为例,经过boxNew [0.35,0.4,0.65,0.6]对该区域的提取,得到相应的白色区域:
![](https://img.haomeiwen.com/i18577060/b34fa403e90f8e55.png)
对白色区域进行roi_pooling函数,然后得到自适应最大池化的结果。源码中设置自适应最大池化大小为(16,8),所以得到[1,64,16,8]的结果,1为batch_size值,64为通道数,后面两个值则为最大池化的size。
同理,第三层得到的结果维度为[1,160,16,8],第五层的结果维度为[1,192,16,8],按照第二维即通道维将3个结果进行拼接,最终得到维数为[1,416,16,8]的结果(64+160+192=416),该过程也是论文中Multi-layer feature maps for recognition(多尺度卷积)所描述的内容。对应上图网络模型红色框的过程。
源码为:
h1, w1 = _x1.data.size()[2], _x1.data.size()[3]
p1 = torch.FloatTensor([[w1, 0, 0, 0], [0, h1, 0, 0], [0, 0, w1, 0], [0, 0, 0, h1]]).cuda()
h2, w2 = _x3.data.size()[2], _x3.data.size()[3]
p2 = torch.FloatTensor([[w2, 0, 0, 0], [0, h2, 0, 0], [0, 0, w2, 0], [0, 0, 0, h2]]).cuda()
h3, w3 = _x5.data.size()[2], _x5.data.size()[3]
p3 = torch.FloatTensor([[w3, 0, 0, 0], [0, h3, 0, 0], [0, 0, w3, 0], [0, 0, 0, h3]]).cuda()
# cen_x, cen_y, w, h --> x1, y1, x2, y2
assert boxLoc.data.size()[1] == 4
postfix = torch.FloatTensor([[1, 0, 1, 0], [0, 1, 0, 1], [-0.5, 0, 0.5, 0], [0, -0.5, 0, 0.5]]).cuda()
boxNew = boxLoc.mm(postfix).clamp(min=0, max=1)
roi1 = roi_pooling_ims(_x1, boxNew.mm(p1), size=(16, 8)) # [1,64,16,8]
roi2 = roi_pooling_ims(_x3, boxNew.mm(p2), size=(16, 8)) # [1,160,16,8]
roi3 = roi_pooling_ims(_x5, boxNew.mm(p3), size=(16, 8)) # [1,192,16,8]
rois = torch.cat((roi1, roi2, roi3), 1) # [1,416,16,8]
_rois = rois.view(rois.size(0), -1) # [1,53248]
第三步:对车牌号码进行预测,由于我国的车牌有7个号码,所以在fh02网络中一共定义了7个classifier,每个classifier由两个全连接层构成。因为7个号码每个号码的类别不同,所以输出结点个数也不尽相同。例如,车牌第一位表示省份,有38种取值,所以输出结点为38个。定义classifier源码:
provNum, alphaNum, adNum = 38, 25, 35
self.classifier1 = nn.Sequential(
nn.Linear(53248, 128),
# nn.ReLU(inplace=True),
nn.Linear(128, provNum),
)
前向传播进行预测的源码:
y0 = self.classifier1(_rois) # [1,38] 预测车牌的第一个字符:省份
y1 = self.classifier2(_rois) # [1,25] 预测车牌的第二个字符:市
y2 = self.classifier3(_rois) # [1,35] 预测车牌的第三个字符
y3 = self.classifier4(_rois) # [1,35] 预测车牌的第四个字符
y4 = self.classifier5(_rois) # [1,35] 预测车牌的第五个字符
y5 = self.classifier6(_rois) # [1,35] 预测车牌的第六个字符
y6 = self.classifier7(_rois) # [1,35] 预测车牌的第七个字符
前向传播结束,返回boxLoc,和7个号码的预测值。注意是boxLoc,而不是boxNew。
损失函数
损失函数由两部分组成,分别为定位损失和分类损失。
定位损失
定位损失又分为中心点cen_x,cen_y的损失,以及宽高w,h的损失,所占权重分别为0.8和0.2,这部分在论文中,我并没有看到。计算损失的函数为L1损失函数。
loss += 0.8 * nn.L1Loss().cuda()(fps_pred[:][:2], y[:][:2]) # 定位cen_x和cen_y损失
loss += 0.2 * nn.L1Loss().cuda()(fps_pred[:][2:], y[:][2:]) # 定位w和h损失
![](https://img.haomeiwen.com/i18577060/db354238e14f156b.png)
分类损失
分类损失具体指7个号码每个号码的交叉熵损失。
for j in range(7): #每个号码牌的交叉熵损失
l = torch.LongTensor([el[j] for el in YI]).cuda(0)
loss += criterion(y_pred[j], l) # 分类损失
![](https://img.haomeiwen.com/i18577060/4577273c0201049c.png)
结语
至此,将RPnet的模型结构和损失函数都以全部描述完,其实该模型并不复杂,我在上篇文章中也说过该模型的精度并不如意,各位小伙伴如果对该模型有什么提高的方法,也可在评论区讲下。
网友评论