温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

AI教程|用滴滴云Notebook笔记本服务实现Yolo3口罩检测

发布时间:2020-08-10 21:53:08 来源:ITPUB博客 阅读:186 作者:java06051515 栏目:云计算

在本教程中,您将学习如何使用经典的one-stage目标检测网络Yolo v3来实现口罩检测,关于Yolo v3的资料可以阅读paper。 本教程所有工程文件可在滴滴云S3存储服务下载。
滴滴云Notebook笔记本服务集成了CUDA、CuDNN、Python、TensorFlow、Pytorch、MxNet、Keras等深度学习框架,无需用户自己安装。

一、购买Notebook服务

注册滴滴云并实名认证后可购买Notebook服务。

  • 进入控制台Notebook页面,单击 创建Notebook实例按钮。

  • 选择基础配置:

    • 选择 付费方式:当前仅支持按时长。
    • 选择 可用区:选择靠近您客户的地域,包括广州1、2区。
    • 选择 配置规格:根据需要的CPU、GPU、显卡和内存,选择相关配置。
    • 选择 镜像:提供了Jupyter Notebook镜像和Jupyter Lab镜像,这里选择>jupyter-lab-v1。
    • 设置 系统盘:根据需求选择系统盘的大小,设置范围为80GB - 500GB。
AI教程|用滴滴云Notebook笔记本服务实现Yolo3口罩检测
image.png
  • 设置名称和标签
    • 输入 Notebook名称。
    • 输入 标签键以及键值,单击 添加按钮,可添加多个标签。
      AI教程|用滴滴云Notebook笔记本服务实现Yolo3口罩检测
      image.png

访问Notebook

  1. 进入我的Notebook页面,在 操作列单击 打开Notebook

  2. 进入Notebook详情页面,单击 打开Notebook

AI教程|用滴滴云Notebook笔记本服务实现Yolo3口罩检测
notebook-open.png

二、调试代码

import cv2import mathimport matplotlib.pyplot as pltimport numpy as npimport osimport randomimport timeimport torchimport torchvisionimport torch.nn as nnimport torch.nn.init as initimport torch.optim as optimimport xml.etree.ElementTree as ET 
from torch.utils.data import Dataset, DataLoader

下载口罩检测数据集并上传到Notebook服务器,这里我们以AIZOO开源数据集为例,下载地址: https://pan.baidu.com/s/1nsQf_Py5YyKm87-8HiyJeQ ,提取码:eyfz,大文件上传可能需要用到滴滴云S3存储服务。为了便于用户下载,我们提前上传到公共数据集对象存储S3,执行以下shell命令即可,如自行上传可以跳过

!wget https://dataset-public.s3.didiyunapi.com/detection/人脸口罩检测/part1.tgz
!wget https://dataset-public.s3.didiyunapi.com/detection/人脸口罩检测/part2.tgz
!wget https://dataset-public.s3.didiyunapi.com/detection/人脸口罩检测/val.tgz
!tar -zxf part1.tgz
!tar -zxf part2.tgz
!tar -zxf val.tgz
 
--2020-03-05 14:59:25--  https://dataset-public.s3.didiyunapi.com/detection/%E4%BA%BA%E8%84%B8%E5%8F%A3%E7%BD%A9%E6%A3%80%E6%B5%8B/part1.tgz
Resolving dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)... 125.94.54.9
Connecting to dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)|125.94.54.9|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 270682151 (258M) [application/gzip]
Saving to: ‘part1.tgz’
100%[======================================>] 270,682,151 6.15MB/s   in 42s    
2020-03-05 15:00:06 (6.18 MB/s) - ‘part1.tgz’ saved [270682151/270682151]
--2020-03-05 15:00:07--  https://dataset-public.s3.didiyunapi.com/detection/%E4%BA%BA%E8%84%B8%E5%8F%A3%E7%BD%A9%E6%A3%80%E6%B5%8B/part2.tgz
Resolving dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)... 125.94.54.9
Connecting to dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)|125.94.54.9|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 337432016 (322M) [application/gzip]
Saving to: ‘part2.tgz’
100%[======================================>] 337,432,016 6.41MB/s   in 53s    
2020-03-05 15:00:59 (6.10 MB/s) - ‘part2.tgz’ saved [337432016/337432016]
--2020-03-05 15:01:00--  https://dataset-public.s3.didiyunapi.com/detection/%E4%BA%BA%E8%84%B8%E5%8F%A3%E7%BD%A9%E6%A3%80%E6%B5%8B/val.tgz
Resolving dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)... 125.94.54.9
Connecting to dataset-public.s3.didiyunapi.com (dataset-public.s3.didiyunapi.com)|125.94.54.9|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 184383116 (176M) [application/gzip]
Saving to: ‘val.tgz’
100%[======================================>] 184,383,116 6.25MB/s   in 29s    
2020-03-05 15:01:28 (6.12 MB/s) - ‘val.tgz’ saved [184383116/184383116]

加载数据,这里我们需要编写自定义数据集的Dataset类型

VOC_CLASSES = ('face', 'face_mask')class AnnotationTransform(object):
    def __init__(self, class_to_ind=None, keep_difficult=True):
        self.class_to_ind = class_to_ind or dict(
            zip(VOC_CLASSES, range(len(VOC_CLASSES))))
        self.keep_difficult = keep_difficult    def __call__(self, target):
        res = np.empty((0,5)) 
        for obj in target.iter('object'):
            difficult = int(obj.find('difficult').text) == 1
            if not self.keep_difficult and difficult:
                continue
            name = obj.find('name').text.lower().strip()
            bbox = obj.find('bndbox')
            pts = ['xmin', 'ymin', 'xmax', 'ymax']
            bndbox = []
            for i, pt in enumerate(pts):
                cur_pt = int(bbox.find(pt).text) - 1
                bndbox.append(cur_pt)
            label_idx = self.class_to_ind[name]
            bndbox.append(label_idx)
            res = np.vstack((res, bndbox))  # [xmin, ymin, xmax, ymax, label_ind]
        return res  # [[xmin, ymin, xmax, ymax, label_ind], ... ]def preproc_for_test(image, input_size, mean, std):
    interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4]
    interp_method = interp_methods[random.randrange(5)]
    image = cv2.resize(image, input_size, interpolation=interp_method)
    image = image.astype(np.float32)
    image = image[:, :, ::-1]
    image /= 255.
    if mean is not None:
        image -= mean    if std is not None:
        image /= std    return image.transpose(2, 0, 1)class TrainTransform(object):
    def __init__(self, rgb_means=None, std=None, max_labels=50):
        self.means = rgb_means
        self.std = std
        self.max_labels = max_labels    def __call__(self, image, targets, img_size):
        boxes = targets[:, :4].copy()  # Nx4
        labels = targets[:, 4].copy()
        if len(boxes) == 0:
            targets = np.zeros((self.max_labels, 5), dtype=np.float32)
            image = preproc_for_test(image, img_size, self.means, self.std)
            image = np.ascontiguousarray(image, dtype=np.float32)
            return torch.from_numpy(image), torch.from_numpy(targets)
        height, width, _ = image.shape
        boxes_o = targets[:, :4]
        labels = targets[:, 4]
        b_x_o = (boxes_o[:, 2] + boxes_o[:, 0]) * .5
        b_y_o = (boxes_o[:, 3] + boxes_o[:, 1]) * .5
        b_w_o = (boxes_o[:, 2] - boxes_o[:, 0]) * 1.
        b_h_o = (boxes_o[:, 3] - boxes_o[:, 1]) * 1.
        boxes_o[:, 0] = b_x_o
        boxes_o[:, 1] = b_y_o
        boxes_o[:, 2] = b_w_o
        boxes_o[:, 3] = b_h_o        # resize
        interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4]
        interp_method = interp_methods[random.randrange(5)]
        image_t = cv2.resize(image, img_size, interpolation=interp_method)
        boxes = boxes_o
        boxes[:, 0::2] /= width
        boxes[:, 1::2] /= height
        boxes[:, 0::2] *= img_size[0]
        boxes[:, 1::2] *= img_size[1]
        image_t = preproc_for_test(image_t, img_size, self.means, self.std)
         
        labels = np.expand_dims(labels, 1)
        targets_t = np.hstack((labels, boxes))
        padded_labels = np.zeros((self.max_labels, 5))
        padded_labels[range(len(targets_t))[:self.max_labels]] = targets_t[:self.max_labels]
        padded_labels = np.ascontiguousarray(padded_labels, dtype=np.float32)
        image_t = np.ascontiguousarray(image_t, dtype=np.float32)
        return torch.from_numpy(image_t), torch.from_numpy(padded_labels)
     # 数据集类型定义class VOCDetection(Dataset):
    def __init__(self, root, preproc=None, target_transform=AnnotationTransform(), img_size=(416, 416), split='train'):
        super().__init__()
        self.root = root
        self.preproc = preproc
        self.target_transform = target_transform
        self.img_size = img_size
        self._annopath = os.path.join('%s', 'Annotations', '%s.xml')
        self._imgpath = os.path.join('%s', 'JPEGImages', '%s.jpg')
        self._classes = VOC_CLASSES
        self._year = '2012'  # options: '2007', which is related to eval protocol
        self.item_container = set()
        if split == 'train':
            for folder in ['part1', 'part2']:
                for item in os.listdir(os.path.join(self.root, folder)):
                    self.item_container.add(os.path.join(self.root, folder, item[:-4]))
        else:
            for folder in ['val']:
                for item in os.listdir(os.path.join(self.root, folder)):
                    self.item_container.add(os.path.join(self.root, folder, item[:-4]))
        self.item_container = list(self.item_container)
    def __getitem__(self, index):
        item = self.item_container[index]
        target = ET.parse(item+'.xml').getroot()
        img = cv2.imread(item+'.jpg')
        # img = Image.open(self._imgpath % img_id).convert('RGB')
        height, width, _ = img.shape        if self.target_transform is not None:
            target = self.target_transform(target)
        if self.preproc is not None:
            img, target = self.preproc(img, target, self.img_size)
        img_info = (width, height)
        return img, target, img_info, item    def __len__(self):
        return len(self.item_container)
     dataset = VOCDetection(root='./', preproc=TrainTransform(),split='train')

定义模型,对模型参数进行初始化操作,这里需要导入自定义库yolo,可在滴滴云S3存储服务下载。

from yolo import YOLOv3
model = YOLOv3(num_classes = len(VOC_CLASSES))def init_yolo(M):
    for m in M.modules():
        if isinstance(m, nn.Conv2d):
            init.kaiming_normal_(m.weight, a=0.1, mode='fan_in')
            if m.bias is not None:
                init.zeros_(m.bias)
        elif isinstance(m, nn.BatchNorm2d):
            init.ones_(m.weight)
            init.zeros_(m.bias)
        elif isinstance(m, nn.Linear):
            init.normal_(m.weight, 0, 0.01)
            init.zeros_(m.bias)
            m.state_dict()[key][...] = 0model.apply(init_yolo)model.train()torch.backends.cudnn.benchmark = Truedevice = torch.device("cuda")model = model.to(device)

现在我们使用Adam算法来训练模型

# 在训练之前我们先定义一些超参数batch_size = 8  # 每一批次训练大小,不宜太小,大小受GPU显存限制base_lr = 0.0001  # 基准学习率warmup_epochs = 10  # 学习率逐渐增加到base_lr的epochepochs = 70  # 总共训练epoch数save_interval = 10  # 保存模型的epoch间隔steps = [50, 60]  # 学习率减少的epochdataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)optimizer = optim.Adam(model.parameters(), lr=base_lr, weight_decay=0.0005)epoch_size = len(dataset) // (batch_size*1)epoch = 1def set_lr(tmp_lr):
    for param_group in optimizer.param_groups:
        param_group['lr'] = tmp_lrwhile epoch < epochs+1:
    print('\n[Epoch {} started]'.format(epoch))
    for iter_i,  (imgs, targets, _, _) in enumerate(dataloader):
        start = time.time()
        if epoch % save_interval == 0:
            torch.save(model.state_dict(), 'yolov3_mask_detection_{}.pth'.format(epoch))
        # 更新学习率
        if epoch < warmup_epochs:
            tmp_lr = base_lr * pow((iter_i+epoch*epoch_size)*1. / (warmup_epochs*epoch_size), 1)
            set_lr(tmp_lr)
        elif epoch == warmup_epochs:
            tmp_lr = base_lr
            set_lr(tmp_lr)
        elif epoch in steps and iter_i == 0:
            tmp_lr = tmp_lr * 0.1
            set_lr(tmp_lr)
        optimizer.zero_grad()
        imgs = imgs.to(device).to(torch.float32)
        targets = targets.to(device).to(torch.float32)
        loss_dict = model(imgs, targets, epoch)
        loss = sum(loss for loss in loss_dict['losses'])
        loss.backward()
        optimizer.step()
        end = time.time()
        if iter_i % 1 == 0:
        # 打印训练过程信息
            print('\r[Epoch %d/%d][Iter %d/%d][LR %.6f]'
                '[Loss: l1 %.2f, conf %.6f, cls %.6f][Time: %.2f s]......'
            % (epoch, epochs, iter_i+1, epoch_size, tmp_lr,
             sum(l1_loss for l1_loss in loss_dict['l1_losses']).item(),
             sum(conf_loss for conf_loss in loss_dict['conf_losses']).item(),
             sum(cls_loss for cls_loss in loss_dict['cls_losses']).item(), end-start),
            end='')
    epoch += 1torch.save(model.state_dict(), 'yolov3_mask_detection_final.pth'.format(epoch))
[Epoch 1 started]
[Epoch 1/70][Iter 765/765][LR 0.000020][Loss: l1 4.36, conf 1747.822632, cls 1.573278][Time: 0.61 s]........
[Epoch 2 started]
[Epoch 2/70][Iter 765/765][LR 0.000030][Loss: l1 5.01, conf 781.659180, cls 1.723707][Time: 0.63 s].........
[Epoch 3 started]
[Epoch 3/70][Iter 765/765][LR 0.000040][Loss: l1 18.94, conf 331.378754, cls 5.523039][Time: 0.69 s].......
[Epoch 4 started]
[Epoch 4/70][Iter 78/765][LR 0.000041][Loss: l1 6.72, conf 293.010193, cls 1.820950][Time: 0.66 s].......
KeyboardInterrupt:

得到训练的模型之后可以开始测试

class ValTransform(object):
    def __init__(self, rgb_means=None, std=None, swap=(2, 0, 1)):
        self.means = rgb_means
        self.swap = swap
        self.std = std    def __call__(self, img, res, input_size):
        interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4]
        interp_method = interp_methods[0]
        img = cv2.resize(np.array(img), input_size, interpolation=interp_method).astype(np.float32)
        img = img[:, :, ::-1]
        img /= 255.
        if self.means is not None:
            img -= self.means        if self.std is not None:
            img /= self.std
        img = img.transpose(self.swap)
        img = np.ascontiguousarray(img, dtype=np.float32)
        return torch.from_numpy(img), torch.zeros(1, 5)
     transform = ValTransform()im = cv2.imread("val/test_00000760.jpg")  # 输入的图片ori_im = im.copy()height, width, _ = im.shape
test_size = (416, 416)im_input, _ = transform(im, None, test_size)im_input = im_input.to(device).type(torch.float32).unsqueeze(0)model.load_state_dict(torch.load('yolov3_mask_detection_final.pth'))  # 加载训练权重device = torch.device("cuda")model = model.to(device)model.eval()outputs = model(im_input)

对模型输出做后处理,除去置信度较低的bbox,并利用非极大抑制(NMS)去除同类型IoU较大的bbox

def postprocess(prediction, num_classes=2, conf_thre=0.3, nms_thre=0.45):
    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]
    output = [None for _ in range(len(prediction))]
    for i, image_pred in enumerate(prediction):
        # If none are remaining => process next image
        if not image_pred.size(0):
            continue
        # Get score and class with highest confidence
        class_conf, class_pred = torch.max(
            image_pred[:, 5:5 + num_classes], 1,  keepdim=True)
        conf_mask = (image_pred[:, 4] * class_conf.squeeze() >= conf_thre).squeeze()
        # Detections ordered as (x1, y1, x2, y2, obj_conf, class_conf, class_pred)
        detections = torch.cat(
            (image_pred[:, :5], class_conf, class_pred.float()), 1)
        detections = detections[conf_mask]
        if not detections.size(0):
            continue
        # Iterate through all predicted classes
        unique_labels = detections[:, -1].unique()
        for c in unique_labels:
            # Get the detections with the particular class
            detections_class = detections[detections[:, -1] == c]
            nms_out_index = torchvision.ops.nms(
                detections_class[:, :4], detections_class[:, 4]*detections_class[:, 5], nms_thre)
            detections_class = detections_class[nms_out_index]
            if output[i] is None:
                output[i] = detections_class            else:
                output[i] = torch.cat((output[i], detections_class))
    return output
outputs = postprocess(outputs, 2, 0.01, 0.35)outputs = outputs[0].cpu().data
bboxes = outputs[:, 0:4]bboxes[:, 0::2] *= width / test_size[0]bboxes[:, 1::2] *= height / test_size[1]bboxes[:, 2] = bboxes[:, 2] - bboxes[:, 0]bboxes[:, 3] = bboxes[:, 3] - bboxes[:, 1]cls = outputs[:, 6]scores = outputs[:, 4] * outputs[:, 5]

最后我们将处理得到的结果可视化出来

def vis(img, boxes, scores, cls_ids, conf=0.5, class_names=None, color=None):
    colors = torch.FloatTensor([[1,0,1],[0,0,1],[0,1,1],[0,1,0],[1,1,0],[1,0,0]]);
    def get_color(c, x, max_val):
        ratio = float(x)/max_val * 5
        i = int(math.floor(ratio))
        j = int(math.ceil(ratio))
        ratio = ratio - i
        r = (1-ratio) * colors[i][c] + ratio*colors[j][c]
        return int(r*255)
    width = img.shape[1]
    height = img.shape[0]
    for i in range(len(boxes)):
        box = boxes[i]
        cls_conf = scores[i]
        if cls_conf < conf:
            continue
        x1 = int(box[0])
        y1 = int(box[1])
        x2 = int(box[0]+box[2])
        y2 = int(box[1]+box[3])
        if color:
            rgb = color        else:
            rgb = (255, 0, 0)
        if class_names is not None:
            cls_conf = scores[i]
            cls_id = int(cls_ids[i])
            class_name = class_names[cls_id]
            classes = len(class_names)
            offset = cls_id * 123456 % classes
            red   = get_color(2, offset, classes)
            green = get_color(1, offset, classes)
            blue  = get_color(0, offset, classes)
            if color is None:
                rgb = (red, green, blue)
            img = cv2.putText(img, '%s: %.2f'%(class_name,cls_conf), (x1,y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, rgb, 2)
        img = cv2.rectangle(img, (x1,y1), (x2,y2), rgb, 1)
    return img
pred_im = vis(ori_im, bboxes.numpy(), scores.numpy(), cls.numpy(), conf=0.3, class_names=VOC_CLASSES)plt.rcParams['figure.figsize'] = (20, 12)plt.imshow(pred_im[:,:,::-1])
<matplotlib.image.AxesImage at 0x7f0c4cfc7950>
0015e6a27a5a1671b03db56e2718f19
AI教程|用滴滴云Notebook笔记本服务实现Yolo3口罩检测
image.png

需要说明的是本教程只是用于入门学习,输入图像尺寸选择416x416,没有加入复杂的数据增广算法、网络训练优化tricks,这些因素都导致模型存在人群密集情况漏检以及小人头漏检问题。如果希望获得更好的效果,可以使用人脸检测模型,混入这个口罩数据集,这样能够利用更多的人脸数据。最后对检测出的人脸图像做2分类即可。

作者:朱中涛【滴滴出行产品专家】

为研发提效,全是技术干货的滴滴云技术沙龙报名中!

马上关注滴滴云公众号:

回复「上课」获取免费报名资格

回复「服务器」免费获得云服务器入门1个月体验

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI