牵绳遛狗你我他文明家园每一天,助力共建文明社区,基于DETR(DEtection TRansformer)开发构建公共场景下未牵绳遛狗检测识别系统

遛狗是每天要打卡的事情,狗狗生性活泼爱动,一天不遛就浑身难受,遛狗最重要的就是要拴绳了,牵紧文明绳是养犬人的必修课。外出遛狗时,主人手上的牵引绳更多是狗狗生命健康的一道重要屏障。每天的社区生活中,相信大家都会或多或少的在路上遇上一些遛狗的人不讲文明不讲武德,出门就是习惯性的不牵绳子遛狗,对于自己不熟悉的狗狗来说我们自然是害怕的,频频报道的狗咬人的事件也是层出不穷,,“狗狗性格温顺不会咬人的”这一类所谓的说辞不是放纵不牵绳子的理由。

对于此类的现象是否能够从技术的角度来进行思考甚至是干预呢?我想理论上来说也是可行的,本文的主要目的就是站在不牵绳遛狗这个大背景下探索基于技术手段来分析对此类行为干预的可行性,这里主要是基于DETR开发构建对应的目标检测模型,我们的设计初衷就是考虑未来这样的技术手段能够结合路边、河道、社区、门口等等的可用的视频摄像头,对于画面中出现的遛狗目标对象进行实时的智能计算分析,如果发现问题就可以通过语音播报提醒,如果还是不加改正就可以将当前的时段视频发送到相关的部门来跟进处理,当然了,这些比较偏向业务应用层面不是我们开发者所能决定的,这里主要是结合我们的所见所想来开发构建实践性质的项目。

首先看下实例效果:

这里没有有效可用的数据集,我们选择的是从网络爬取对应的场景下的数据集然后人工进行数据的标注处理,这里我们讨论商定主要选取的标注对象有两个:狗和狗绳,这里也是借鉴参考了江边禁止垂钓场景下数据的标注策略。

接下来看下数据集:

DETR (DEtection TRansformer) 是一种基于Transformer架构的端到端目标检测模型。与传统的基于区域提议的目标检测方法(如Faster R-CNN)不同,DETR采用了全新的思路,将目标检测问题转化为一个序列到序列的问题,通过Transformer模型实现目标检测和目标分类的联合训练。

DETR的工作流程如下:

输入图像通过卷积神经网络(CNN)提取特征图。
特征图作为编码器输入,经过一系列的编码器层得到图像特征的表示。
目标检测问题被建模为一个序列到序列的转换任务,其中编码器的输出作为解码器的输入。
解码器使用自注意力机制(self-attention)对编码器的输出进行处理,以获取目标的位置和类别信息。
最终,DETR通过一个线性层和softmax函数对解码器的输出进行分类,并通过一个线性层预测目标框的坐标。
DETR的优点包括:

端到端训练:DETR模型能够直接从原始图像到目标检测结果进行端到端训练,避免了传统目标检测方法中复杂的区域提议生成和特征对齐的过程,简化了模型的设计和训练流程。
不受固定数量的目标限制:DETR可以处理变长的输入序列,因此不受固定数量目标的限制。这使得DETR能够同时检测图像中的多个目标,并且不需要设置预先确定的目标数量。
全局上下文信息:DETR通过Transformer的自注意力机制,能够捕捉到图像中不同位置的目标之间的关系,提供了更大范围的上下文信息。这有助于提高目标检测的准确性和鲁棒性。
然而,DETR也存在一些缺点:

计算复杂度高:由于DETR采用了Transformer模型,它在处理大尺寸图像时需要大量的计算资源,导致其训练和推理速度相对较慢。
对小目标的检测性能较差:DETR模型在处理小目标时容易出现性能下降的情况。这是因为Transformer模型在处理小尺寸目标时可能会丢失细节信息,导致难以准确地定位和分类小目标。

如果对如何使用DETR模型来开发构建自己的个性化目标检测模型有疑问的话,可以参考我的超详细教程文章,如下:

《DETR (DEtection TRansformer)基于自建数据集开发构建目标检测模型超详细教程》

官方项目地址在这里,如下所示:

可以看到目前已经收获了超过1.23w的star量,还是很不错的了。

DETR整体数据流程示意图如下所示:

官方也提供了对应的预训练模型,可以自行使用:

namebackbonescheduleinf_timebox APurlsize
0DETRR505000.03642.0model | logs159Mb
1DETR-DC5R505000.08343.3model | logs159Mb
2DETRR1015000.05043.5model | logs232Mb
3DETR-DC5R1015000.09744.9model | logs232Mb

COCO panoptic val5k models:

namebackbonebox APsegm APPQurlsize
0DETRR5038.831.143.4download165Mb
1DETR-DC5R5040.231.944.6download165Mb
2DETRR10140.13345.1download237Mb

本文选择的预训练官方权重是detr-r50-e632da11.pth,首先需要基于官方的预训练权重开发能够用于自己的 个性化数据集的权重,如下所示:

pretrained_weights = torch.load("./weights/detr-r50-e632da11.pth")
num_class = 2 + 1
pretrained_weights["model"]["class_embed.weight"].resize_(num_class+1,256)
pretrained_weights["model"]["class_embed.bias"].resize_(num_class+1)
torch.save(pretrained_weights,'./weights/detr_r50_%d.pth'%num_class)

因为这里我的类别数量为2,所以num_class修改为:2+1,根据自己的实际情况修改即可。生成后如下所示:

终端执行:

python main.py --dataset_file "coco" --coco_path "/0000" --epoch 100 --lr=1e-4 --batch_size=32 --num_workers=0 --output_dir="outputs" --resume="weights/detr_r50_3.pth"

即可启动训练。训练启动如下:

DETR模型的训练依旧是很耗费算力资源。

训练完成输出如下:

DONE (t=0.15s).
IoU metric: bbox
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.466
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 0.855
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 0.477
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.321
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.496
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.494
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.605
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.647
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.494
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.677

等待漫长的训练过程完成后,我们来对结果进行可视化,核心代码如下:
【日志可视化】

def plot_logs(logs, fields=('class_error', 'loss_bbox_unscaled', 'mAP'), ewm_col=0, log_name='log.txt'):
    '''
    Function to plot specific fields from training log(s). Plots both training and test results.
    :: Inputs - logs = list containing Path objects, each pointing to individual dir with a log file
              - fields = which results to plot from each log file - plots both training and test for each field.
              - ewm_col = optional, which column to use as the exponential weighted smoothing of the plots
              - log_name = optional, name of log file if different than default 'log.txt'.
    :: Outputs - matplotlib plots of results in fields, color coded for each log file.
               - solid lines are training results, dashed lines are test results.
    '''
    func_name = "plot_utils.py::plot_logs"
 
    # verify logs is a list of Paths (list[Paths]) or single Pathlib object Path,
    # convert single Path to list to avoid 'not iterable' error
 
    if not isinstance(logs, list):
        if isinstance(logs, PurePath):
            logs = [logs]
            print(f"{func_name} info: logs param expects a list argument, converted to list[Path].")
        else:
            raise ValueError(f"{func_name} - invalid argument for logs parameter.\n \
            Expect list[Path] or single Path obj, received {type(logs)}")
 
    # Quality checks - verify valid dir(s), that every item in list is Path object, and that log_name exists in each dir
    for i, dir in enumerate(logs):
        if not isinstance(dir, PurePath):
            raise ValueError(f"{func_name} - non-Path object in logs argument of {type(dir)}: \n{dir}")
        if not dir.exists():
            raise ValueError(f"{func_name} - invalid directory in logs argument:\n{dir}")
        # verify log_name exists
        fn = Path(dir / log_name)
        if not fn.exists():
            print(f"-> missing {log_name}.  Have you gotten to Epoch 1 in training?")
            print(f"--> full path of missing log file: {fn}")
            return
 
    # load log file(s) and plot
    dfs = [pd.read_json(Path(p) / log_name, lines=True) for p in logs]
 
    fig, axs = plt.subplots(ncols=len(fields), figsize=(16, 5))
 
    for df, color in zip(dfs, sns.color_palette(n_colors=len(logs))):
        for j, field in enumerate(fields):
            if field == 'mAP':
                coco_eval = pd.DataFrame(
                    np.stack(df.test_coco_eval_bbox.dropna().values)[:, 1]
                ).ewm(com=ewm_col).mean()
                axs[j].plot(coco_eval, c=color)
            else:
                df.interpolate().ewm(com=ewm_col).mean().plot(
                    y=[f'train_{field}', f'test_{field}'],
                    ax=axs[j],
                    color=[color] * 2,
                    style=['-', '--']
                )
    for ax, field in zip(axs, fields):
        ax.legend([Path(p).name for p in logs])
        ax.set_title(field)

结果如下所示:

【precision recall可视化】

def plot_precision_recall(files, naming_scheme='iter'):
    if naming_scheme == 'exp_id':
        # name becomes exp_id
        names = [f.parts[-3] for f in files]
    elif naming_scheme == 'iter':
        names = [f.stem for f in files]
    else:
        raise ValueError(f'not supported {naming_scheme}')
    fig, axs = plt.subplots(ncols=2, figsize=(16, 5))
    for f, color, name in zip(files, sns.color_palette("Blues", n_colors=len(files)), names):
        data = torch.load(f)
        # precision is n_iou, n_points, n_cat, n_area, max_det
        precision = data['precision']
        recall = data['params'].recThrs
        scores = data['scores']
        # take precision for all classes, all areas and 100 detections
        precision = precision[0, :, :, 0, -1].mean(1)
        scores = scores[0, :, :, 0, -1].mean(1)
        prec = precision.mean()
        rec = data['recall'][0, :, 0, -1].mean()
        print(f'{naming_scheme} {name}: mAP@50={prec * 100: 05.1f}, ' +
              f'score={scores.mean():0.3f}, ' +
              f'f1={2 * prec * rec / (prec + rec + 1e-8):0.3f}'
              )
        axs[0].plot(recall, precision, c=color)
        axs[1].plot(recall, scores, c=color)
 
    axs[0].set_title('Precision / Recall')
    axs[0].legend(names)
    axs[1].set_title('Scores / Recall')
    axs[1].legend(names)
    return fig, axs

结果如下所示:

这里我们对其计算了F1值,代码如下:

def F1(P,R):
    """
    F1值
    """
    return 2*P*R/(P+R)

结果如下:

评估结果详情如下:

iter 000: mAP@50= 57.0, score=0.590, f1=0.701
iter 050: mAP@50= 83.8, score=0.830, f1=0.900
iter latest: mAP@50= 85.5, score=0.865, f1=0.902
iter 000: mAP@50= 57.0, score=0.590, f1=0.701
iter 050: mAP@50= 83.8, score=0.830, f1=0.900
iter latest: mAP@50= 85.5, score=0.865, f1=0.902

当然了,这里我们所做的实验性质的工作还是很初级的,不够充分的,我们这里的想法前面也有提到过,主要是参考了江边禁止垂钓的数据标注和建模的思路,但是只是检测到狗或者说是狗绳并不能严格用来判定是否确实是未牵绳遛狗,一方面现实世界干扰很多,狗绳可能很细小导致画面中看不到所以检测不到,另一方面,画面中同时检测到了狗绳和狗就是牵绳遛狗了?这个肯定也是未必的,可能有多只狗的出现,也可能有非狗绳对象被误识别的可能,总之想要完全落地不能单独依靠单一的模型去完成,我们这里仅仅是抛砖引玉,探索AI和生活场景相结合的可能性,我个人觉得这样的工作比较有趣,当真的有一天AI能真真切切造福生活,提高我们每个人的生活质量的时候那大概是技术最有价值的时刻了。

  • 29
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Together_CZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值