• 边缘部署轻量化人脸检测模型


    Ultra-Light-Fast-Generic-Face-Detector-1MB-master

    模型是针对边缘计算设备设计的轻量人脸检测模型。

    • 在模型大小上,默认FP32精度下(.pth)文件大小为 1.04~1.1MB,推理框架int8量化后大小为 300KB 左右。
    • 在模型计算量上,320x240的输入分辨率下 90~109 MFlops左右。
    • 模型有两个版本,version-slim(主干精简速度略快),version-RFB(加入了修改后的RFB模块,精度更高)。
    • 提供320x240、640x480不同输入分辨率下使用widerface训练的预训练模型,更好的工作于不同的应用场景。

    2数据处理

    2.1 输入尺寸的选择

    由于涉及实际部署时的推理速度,因此模型输入尺寸的选择也是一个很重要的话题。

    在作者的原github中,也提到了一点,如果在实际部署的场景中大多数情况为中近距离、人脸大同时人脸的数量也比较少的时候,则可以采用

    320×240

    的输入尺寸;

    如果在实际部署的场景中大多数情况为中远距离、人脸小同时人脸的数量也比较多的时候,则可以采用

    640×480

    或者

    480×360

    的输入尺寸;

    这里由于使用的是EAIDK310进行部署测试,边缘性能不是很好,因此选择原作者推荐的最小尺寸

    320×240

    进行训练和部署测试。 注意:过小的输入分辨率虽然会明显加快推理速度,但是会大幅降低小人脸的召回率。

    2.2 数据筛选

    由于widerface官网数据集中有比较多的低于10像素的人脸照片,因此在这里选择剔除这些像素长宽低于10个pixel的照片;

    3SSD网络结构

    SSD是一个端到端的模型,所有的检测过程和识别过程都是在同一个网络中进行的;同时SSD借鉴了Faster R-CNN的Anchor机制的想法,这样就像相当于在基于回归的的检测过程中结合了区域的思想,可以使得检测效果较定制化边界框的YOLO v1有比较好的提升。

    SSD较传统的检测方法使用顶层特征图的方法选择了使用多尺度特征图,因为在比较浅的特征图中可以对于小目标有比较好的表达,随着特征图的深入,网络对于比较大特征也有了比较好表达能力,故SSD选择使用多尺度特征图可以很好的兼顾大目标和小目标。

    SSD模型结构如下:

    这里关于SSD不进行更多的阐述,想了解的小伙伴自行搜索查看

    整个项目模型搭建如下:

    1. # 网络的主题结构为SSD模型
    2. class SSD(nn.Module):
    3. def __init__(self, num_classes: int, base_net: nn.ModuleList, source_layer_indexes: List[int],
    4. extras: nn.ModuleList, classification_headers: nn.ModuleList,
    5. regression_headers: nn.ModuleList, is_test=False, config=None, device=None):
    6. """Compose a SSD model using the given components.
    7. """
    8. super(SSD, self).__init__()
    9. self.num_classes = num_classes
    10. self.base_net = base_net
    11. self.source_layer_indexes = source_layer_indexes
    12. self.extras = extras
    13. self.classification_headers = classification_headers
    14. self.regression_headers = regression_headers
    15. self.is_test = is_test
    16. self.config = config
    17. # register layers in source_layer_indexes by adding them to a module list
    18. self.source_layer_add_ons = nn.ModuleList([t[1] for t in source_layer_indexes
    19. if isinstance(t, tuple) and not isinstance(t, GraphPath)])
    20. if device:
    21. self.device = device
    22. else:
    23. self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    24. if is_test:
    25. self.config = config
    26. self.priors = config.priors.to(self.device)
    27. def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
    28. confidences = []
    29. locations = []
    30. start_layer_index = 0
    31. header_index = 0
    32. end_layer_index = 0
    33. for end_layer_index in self.source_layer_indexes:
    34. if isinstance(end_layer_index, GraphPath):
    35. path = end_layer_index
    36. end_layer_index = end_layer_index.s0
    37. added_layer = None
    38. elif isinstance(end_layer_index, tuple):
    39. added_layer = end_layer_index[1]
    40. end_layer_index = end_layer_index[0]
    41. path = None
    42. else:
    43. added_layer = None
    44. path = None
    45. for layer in self.base_net[start_layer_index: end_layer_index]:
    46. x = layer(x)
    47. if added_layer:
    48. y = added_layer(x)
    49. else:
    50. y = x
    51. if path:
    52. sub = getattr(self.base_net[end_layer_index], path.name)
    53. for layer in sub[:path.s1]:
    54. x = layer(x)
    55. y = x
    56. for layer in sub[path.s1:]:
    57. x = layer(x)
    58. end_layer_index += 1
    59. start_layer_index = end_layer_index
    60. confidence, location = self.compute_header(header_index, y)
    61. header_index += 1
    62. confidences.append(confidence)
    63. locations.append(location)
    64. for layer in self.base_net[end_layer_index:]:
    65. x = layer(x)
    66. for layer in self.extras:
    67. x = layer(x)
    68. confidence, location = self.compute_header(header_index, x)
    69. header_index += 1
    70. confidences.append(confidence)
    71. locations.append(location)
    72. confidences = torch.cat(confidences, 1)
    73. locations = torch.cat(locations, 1)
    74. if self.is_test:
    75. confidences = F.softmax(confidences, dim=2)
    76. boxes = box_utils.convert_locations_to_boxes(
    77. locations, self.priors, self.config.center_variance, self.config.size_variance
    78. )
    79. boxes = box_utils.center_form_to_corner_form(boxes)
    80. return confidences, boxes
    81. else:
    82. return confidences, locations
    83. def compute_header(self, i, x):
    84. confidence = self.classification_headers[i](x)
    85. confidence = confidence.permute(0, 2, 3, 1).contiguous()
    86. confidence = confidence.view(confidence.size(0), -1, self.num_classes)
    87. location = self.regression_headers[i](x)
    88. location = location.permute(0, 2, 3, 1).contiguous()
    89. location = location.view(location.size(0), -1, 4)
    90. return confidence, location
    91. def init_from_base_net(self, model):
    92. self.base_net.load_state_dict(torch.load(model, map_location=lambda storage, loc: storage), strict=True)
    93. self.source_layer_add_ons.apply(_xavier_init_)
    94. self.extras.apply(_xavier_init_)
    95. self.classification_headers.apply(_xavier_init_)
    96. self.regression_headers.apply(_xavier_init_)
    97. def init_from_pretrained_ssd(self, model):
    98. state_dict = torch.load(model, map_location=lambda storage, loc: storage)
    99. state_dict = {k: v for k, v in state_dict.items() if not (k.startswith("classification_headers") or k.startswith("regression_headers"))}
    100. model_dict = self.state_dict()
    101. model_dict.update(state_dict)
    102. self.load_state_dict(model_dict)
    103. self.classification_headers.apply(_xavier_init_)
    104. self.regression_headers.apply(_xavier_init_)
    105. def init(self):
    106. self.base_net.apply(_xavier_init_)
    107. self.source_layer_add_ons.apply(_xavier_init_)
    108. self.extras.apply(_xavier_init_)
    109. self.classification_headers.apply(_xavier_init_)
    110. self.regression_headers.apply(_xavier_init_)
    111. def load(self, model):
    112. self.load_state_dict(torch.load(model, map_location=lambda storage, loc: storage))
    113. def save(self, model_path):
    114. torch.save(self.state_dict(), model_path)

    损失函数

    损失函数作者选择使用的依旧是SSD的Smooth L1 Loss以及Cross Entropy Loss,其中Smooth L1 Loss用于边界框的回归,而Cross Entropy Loss则用于分类。

    具体pytorch实现如下:

    1. class MultiboxLoss(nn.Module):
    2. def __init__(self, priors, neg_pos_ratio,
    3. center_variance, size_variance, device):
    4. """Implement SSD Multibox Loss.
    5. Basically, Multibox loss combines classification loss
    6. and Smooth L1 regression loss.
    7. """
    8. super(MultiboxLoss, self).__init__()
    9. self.neg_pos_ratio = neg_pos_ratio
    10. self.center_variance = center_variance
    11. self.size_variance = size_variance
    12. self.priors = priors
    13. self.priors.to(device)
    14. def forward(self, confidence, predicted_locations, labels, gt_locations):
    15. """Compute classification loss and smooth l1 loss.
    16. Args:
    17. confidence (batch_size, num_priors, num_classes): class predictions.
    18. locations (batch_size, num_priors, 4): predicted locations.
    19. labels (batch_size, num_priors): real labels of all the priors.
    20. boxes (batch_size, num_priors, 4): real boxes corresponding all the priors.
    21. """
    22. num_classes = confidence.size(2)
    23. with torch.no_grad():
    24. # derived from cross_entropy=sum(log(p))
    25. loss = -F.log_softmax(confidence, dim=2)[:, :, 0]
    26. mask = box_utils.hard_negative_mining(loss, labels, self.neg_pos_ratio)
    27. confidence = confidence[mask, :]
    28. # 分类损失函数
    29. classification_loss = F.cross_entropy(confidence.reshape(-1, num_classes), labels[mask], reduction='sum')
    30. pos_mask = labels > 0
    31. predicted_locations = predicted_locations[pos_mask, :].reshape(-1, 4)
    32. gt_locations = gt_locations[pos_mask, :].reshape(-1, 4)
    33. # 边界框回归损失函数
    34. smooth_l1_loss = F.smooth_l1_loss(predicted_locations, gt_locations, reduction='sum') # smooth_l1_loss
    35. # smooth_l1_loss = F.mse_loss(predicted_locations, gt_locations, reduction='sum') #l2 loss
    36. num_pos = gt_locations.size(0)
    37. return smooth_l1_loss / num_pos, classification_loss / num_pos

    结果预测

    输入为:

    输出为:

    输入为

    输出为

    模型转换
    由于部署使用的是Tengine边缘推理框架,由于pytorch输出的模型无法直接转换到tmfile模型下,因此还是选择使用onnx中间件的形式进行过度,具体实现代码如下:

    1. model_path = "models/pretrained/version-RFB-320.pth"
    2. net = create_Mb_Tiny_RFB_fd(len(class_names), is_test=True)
    3. net.load(model_path)
    4. net.eval()
    5. net.to("cuda")
    6. model_name = model_path.split("/")[-1].split(".")[0]
    7. model_path = f"models/onnx/{model_name}.onnx"
    8. dummy_input = torch.randn(1, 3, 240, 320).to("cuda")
    9. # dummy_input = torch.randn(1, 3, 480, 640).to("cuda") #if input size is 640*480
    10. torch.onnx.export(net, dummy_input, model_path, verbose=False, input_names=['input'], output_names=['scores', 'boxes'])

    得到onnx模型后便可以进行Tengine模型的转换和部署。

    参考

    https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB

    关于cv2.rectangle()函数

    cv2.rectangle(img, pt1, pt2, color, thickness, lineType, shift )

    参数表示依次为: (图片,长方形框左上角坐标, 长方形框右下角坐标, 字体颜色,字体粗细)

    在图片img上画长方形,坐标原点是图片左上角,向右为x轴正方向,向下为y轴正方向。左上角(x,y),右下角(x,y) ,颜色(B,G,R), 线的粗细如:
    cv2.rectangle(frame, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), color, 2)

    关于裁剪下发给对比侧

    #裁剪
    x, y, w, h = (int(box[0]), int(box[1]))[0], (int(box[0]), int(box[1]))[1], (int(box[2]), int(box[3]))[0] - \
    (int(box[0]), int(box[1]))[0], \
    (int(box[2]), int(box[3]))[1] - \
    (int(box[0]), int(box[1]))[1]
    cropped_image = image[y:y + h, x:x + w]
    # 显示裁剪后的图片
    cv2.imshow('Cropped image', cropped_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # 如果需要保存裁剪后的图片

    cv2.imwrite('cropped_image.jpg', cropped_image)

  • 相关阅读:
    Java面向对象进阶1——static修饰符
    Elasticsearch8.13.4版本的Docker启动关闭HTTPS
    leetcode1302. 层数最深叶子节点的和
    杂想之一个C++内存泄露案例
    【科普向】5G核心网架构和关键技术
    Leetcode刷题Day5休息 & Day6----------哈希表
    搭建私有组件库
    医院陪诊系统:改善患者体验的技术创新
    JAVA后端开发面试基础知识(二)——JAVA
    Java设计模式-创建者模式-工厂模式
  • 原文地址:https://blog.csdn.net/baidu_37366055/article/details/138211546