1.课程学习
本节课主要对于大白AI课程:https://mp.weixin.qq.com/s/STbdSoI7xLeHrNyLlw9GOg
《Pytorch推理及范式》课程中的第五节课进行学习。
2.作业题目
必做题:
(1) 自己找 2 张其他图,用 Yolox_s 进行目标检测,并注明输入尺寸和两个阈值。
两张图片的输入尺寸都是640*640,两个阈值分别是: conf_threshold = 0.6 nms_threshold = 0.45
思考题:
(1) Yolox_s:用 time 模块和 for 循环,对”./images/1.jpg”连续推理 100 次,统计时
间开销。有 CUDA 的同学,改下代码:self.device=torch.device(‘cuda’),统计时间
开销。
CPU:
图片运行100次耗时: 84.00596022605896
推理一次平均耗时: 0.8400596022605896
GPU:
图片运行100次耗时: 48.27049493789673
推理一次平均耗时: 0.4827049493789673
(2) 有 CUDA 的同学,分别用 Yolox_tiny、Yolox_s、Yolox_m、Yolox_l、Yolox_x
对”./images/1.jpg”连续推理 100 次,统计时间开销。
Yolo_tiny:
Yolox_s:
图片运行100次耗时: 48.27049493789673
推理一次平均耗时: 0.4827049493789673
Yolox_m:
图片运行100次耗时: 126.11759567260742
推理一次平均耗时: 1.2611759567260743
Yolox_l:
Yolox_x:
最后两个模型用公司的笔记本跑不起来了。。。。
3.课程总结:
本次课程到这就结束了,下面谈谈我对此次课程的心得体会:
收获:
通过这次课程,我学会了一种通用的推理流程,通过这个流程理解了模型推理的核心内容,即数据预处理,数据进网络,数据后处理,这三步到底在干些什么。后面就是靠这个流程慢慢体会模型推理的本质和乐趣,以后c++推理也可以用这个流程。逐步地入门、熟悉模型推理。
建议:
对于课程的形式,我觉得非常合适,这课后作业有点太单调了,每次的作业基本上都一样,我觉得可以适当加点难度。
有的时候知道怎么推理了,却对算法原理不是很清楚,之前看过大白老师的yolo系列文章,让我对yolo算法有了进一步的理解,对于别的算法,很难找到写的这么透彻的文章,希望老师讲课的时候能推荐一下对算法讲的比较好的文章,或者讲一下如何学习一个新的算法,感觉这个也很重要。
阶段:
在职,入职4个月。
4.完整代码:
import torch
import cv2
import numpy as np
import torchvision.models as models
import time
from lesson5.models_yolox.visualize import vis
import torchvision.ops
from lesson5.models_yolox.yolox_s import YOLOX
class ModelPipline(object):
def __init__(self):
# 进去模型的图片大小:为数据预处理和后处理做准备
self.inputs_size = (640, 640)
# CPU or CUDA:为数据预处理和模型加载做准备
self.device = torch.device('cuda')
# 载入模型结构和模型权重
self.num_classes = 80
self.model = self.get_model()
# 后处理的阈值
self.conf_threshold = 0.6
self.nms_threshold = 0.45
# 标签载入
label_name = open(r'D:\Pycharm Projcets\Pytorch_test\torch_inference\lesson5\labels\coco_label.txt')
self.label_names = [line.strip('n') for line in label_name]
def predict(self, image):
# 数据预处理
inputs, r = self.preprocess(image)
# 数据进网络
outputs = self.model(inputs)
# 数据后处理
results = self.postprocess(outputs, r)
return results
def get_model(self):
model = YOLOX(num_classes=self.num_classes)
pretrained_state_dict = torch.load(r"D:\Pycharm Projcets\Pytorch_test\torch_inference\lesson5\weights\yolox_m.pth",
map_location=lambda storage, loc: storage)["model"]
model.load_state_dict(pretrained_state_dict)
model.to(self.device)
model.eval()
return model
def preprocess(self, image):
# 原图尺寸
h, w = image.shape[:2]
# 生成一张 w=h=640的mask,数值全是114
padded_img = np.ones((self.inputs_size[0], self.inputs_size[1], 3)) * 114.0
# 计算原图的长边缩放到640所需要的比例
r = min(self.inputs_size[0] / h, self.inputs_size[1] / w)
# 对原图做等比例缩放, 使得长边=640
resize_img = cv2.resize(image, (int(w * r), int(h * r)), interpolation=cv2.INTER_LINEAR).astype(np.float32)
# 将缩放后的原图填充到 640*640的mask的左上方
padded_img[: int(h * r), :int(w * r)] = resize_img
# BGR ————> RGB
padded_img = padded_img[:, :, ::-1]
# 归一化和标准化, 和训练时保持一致
inputs = padded_img / 255
inputs = (inputs - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225])
# # 以下是图像任务的通用处理
# (H, W, C) ——> (C, H, W)
inputs = inputs.transpose(2, 0, 1)
# (C, H, W) ——> (1, C, H, W)
inputs = inputs[np.newaxis, :, :, :]
# NumpyArray ——> Tensor
inputs = torch.from_numpy(inputs)
# dtype float32
inputs = inputs.type(torch.float32)
# 与self.model放在相同硬件上
inputs = inputs.to(self.device)
return inputs, r
def postprocess(self, prediction, r):
# prediction.shape = [1, 8400, 85],下面先将85中的前4列进行转换,从xc,yc,w,h变为x0,y0,x1,y1
# 将每个anchor的“中心点横坐标”、“中心点纵坐标”、“框的宽”、“框的高”转换为
# anchor的“左上角横坐标”、“左上角纵坐标”、“右下角横坐标”、“右下角纵坐标”。
box_corner = prediction.new(prediction.shape)
box_corner[:, :, 0] = prediction[:, :, 0] - prediction[:, :, 2] / 2
box_corner[:, :, 1] = prediction[:, :, 1] - prediction[:, :, 3] / 2
box_corner[:, :, 2] = prediction[:, :, 0] + prediction[:, :, 2] / 2
box_corner[:, :, 3] = prediction[:, :, 1] + prediction[:, :, 3] / 2
prediction[:, :, :4] = box_corner[:, :, :4]
# 只处理单张图片 由于只处理单张图片,所以减少一个维度,从(1, 8400, 85) 变为(8400, 85) 。
image_pred = prediction[0]
# class_conf.shape = [8400,1],求每个anchor在80个类别中的最高分。class_pred.shape=[8400, 1],每个anchor的label index。
# 求出每个 anchor 分数最高的类别,将该最高分和前景分相乘,作为该 anchor 最终的
# 置信度,和人为设置的阈值 self.conf_threshold 作比较,判断该 anchor 之后是否要保留
class_conf, class_pred = torch.max(image_pred[:, 5: 5 + self.num_classes], 1, keepdim=True)
conf_score = image_pred[:, 4].unsqueeze(dim=1) * class_conf # image_pred[:, :4]是框的坐标信息,image_pred[:, 4]是前景得分,image_pred[:, 5:]是类别得分
conf_mask = (conf_score >= self.conf_threshold).squeeze()
# detection.shape=[8400, 6],分别是x0, y0, x1, y1, obj_score*class_score, class_label
# 求出的 anchor 框的位置信息、置信度信息、类别信息整合到一起
detections = torch.cat((image_pred[:, :4], conf_score, class_pred.float()), 1)
# 将obj_score*class_score > conf_thre 筛选出来
# 剔除置信度小于 self.conf_threshold 的anchor框
detections = detections[conf_mask]
# 通过阈值筛选后, 如果没有剩余目标则结束
# 如把所有 anchor 都剔除了,即图中没有有效检测框,则返回 None。
if not detections.size(0):
return None
# NMS
# NMS(非极大抑制),如果两个相同类别的 anchor 框的 IOU>self.nms_threshold,
# 则会剔除置信度较低的 anchor。因此人为设置的阈值 self.nms_threshold 越大,图中保留的框越多
nms_out_index = torchvision.ops.batched_nms(detections[:, :4], detections[:, 4], detections[:, 5], self.nms_threshold)
detections = detections[nms_out_index]
# 把坐标映射回原图
detections = detections.data.cpu().numpy()
bboxes = (detections[:, :4] / r).astype(np.int64)
scores = detections[:, 4]
labels = detections[:, 5].astype(np.int64)
return bboxes, scores, labels
# 实例化
model_detect = ModelPipline()
# 获取标签名称
label_name = model_detect.label_names
# 读取图片
image = cv2.imread(r'D:\Pycharm Projcets\Pytorch_test\torch_inference\lesson5\images\1.jpg')
# 模型推理
result = model_detect.predict(image)
start = time.time()
for i in range(100):
result = model_detect.predict(image)
end = time.time()
print("图片运行100次耗时:", end - start)
print("推理一次平均耗时:", (end - start) / 100)
# 可视化结果
if result is not None:
bboxes, scores, labels = result
image = vis(image, bboxes, scores, labels, label_name)
cv2.imwrite('01.jpg', image)
cv2.imshow("result", image)
cv2.waitKey(0)