转自AI Studio,原文链接:基于PP-ShiTu的商品识别系统 - 飞桨AI Studio

基于PP-ShiTu图像识别系统的商品识别

背景介绍

1、项目效果

视频地址

2、发展概况

随着无人超市、新零售等商业模式的发展,越来越多的品牌商及商超零售企业开始引进人工智能技术,探索商品管理、成本控制、用户体验等多维度的数字化转型,转型中所涉及的货架陈列分析、智能结算、智能库存管理、智能货柜、电商平台等以图搜图的场景背后的核心技术都离不开商品识别算法。

3、痛点问题

  1. 结算效率要求极高:在零售场景中,若顾客购买的商品较多,采用传统的条形码结算,效率较低,顾客购物体验较差;
  2. 不同商品相似度极高:比如同一种饮料的不同口味,就很可能拥有非常类似的包装。而且即便对于同一件商品,在不同情况下所获得的商品图像都往往存在相当大的差异
  3. 品类更新极快:商超零售场景下,新品通常以小时级别速度更新迭代,每增加新产品时若仅靠单一模型均需重新训练模型,模型训练成本及时间成本极大。

4、解决方案

PaddleClas团队开源的图像识别PP-ShiTu技术方案,主要由主体检测、特征学习和向量检索三个模块组成,是一个实用的轻量级通用图像识别系统。基于此技术方案,商超零售企业可实现大量商品的一键式智能化识别,大大提高识别效率,节省人工及时间成本。

此外,当新品迭代更新时,PP-shitu无需重新训练模型,能够做到“即增即用”,完美解决上述痛点问题,大大提高了人工智能在商超零售行业的应用落地可能性。

PP-shitu技术方案可具体应用于例如:商品结算、库存管理等关于商品识别的商超细分场景。

一、环境准备

In [ ]

# 克隆 PaddleClas
# 此代码只需要执行一次,本项目已完成克隆
# # github仓库
# !git clone https://github.com/PaddlePaddle/PaddleClas.git -b release/2.3 
# gitee仓库(推荐)
# !git clone https://gitee.com/paddlepaddle/PaddleClas.git -b release/2.3

二、 安装依赖

In [ ]

# 如果需要进行持久化安装, 需要使用持久化路径, 如下方代码示例:
# If a persistence installation is required, 
# you need to use the persistence path as the following: 
!mkdir /home/aistudio/external-libraries
!pip install -r /home/aistudio/PaddleClas/requirements.txt

In [ ]

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: 
# Also add the following code, 
# so that every time the environment (kernel) starts, 
# just run the following code: 
import sys 
sys.path.append('/home/aistudio/external-libraries')

三、数据集准备及预处理

1、数据集介绍

目前开源的商品识别方向的数据集

  • Products-10K Large Scale Product Recognition Dataset :数据集中的所有图片均来自京东商城。数据集中共包含 1 万个经常购买的 SKU。所有 SKU 组织成一个层次结构。总共有近 19 万张图片。在实际应用场景中,图像量的分布是不均衡的。所有图像都由生产专家团队手工检查/标记。
  • RP2K: A Large-Scale Retail Product Dataset for Fine-Grained Image Classification :收集了超过 500,000 张货架上零售产品的图像,属于 2388 种不同的产品,其中,训练集344829张图片,测试集39457张图片。所有图片均在实体零售店人工拍摄,自然采光,符合实际应用场景。本项目使用的数据集为RP2K数据集。

2、RP2K数据集可视化

  • RP2K数据集中为每种商品提供了不同尺寸的图片:

  • RP2K数据集对商品进行不同维度的分类:

  • RP2K数据集对商品属性信息的详细标注:

In [ ]

# 解压数据集
!unzip -oq /home/aistudio/data/data130337/dataset.zip -d /home/aistudio/work/

In [ ]

# 查看文件夹架构,顺便统计文件总数量
# !tree /home/aistudio/work/dataset/all/train
!tree /home/aistudio/work/dataset/all/test
  • 本项目中有两个RP2K数据集,均已解压
  • 项目训练使用的是work/文件夹下的RP2K数据集,其中没有中文label,每个子文件夹是一个类,从1-2388,共2388类
  • 因此目前推理模型在图片上输出的结果中预测的仅是序号类别,需要将序号映射为中文label
  • 第二个数据集解压在了data/文件夹下,这个数据集有中文label,每个子文件夹的名字都是中文的商品名称
  • 可以利用这两个数据集生成序号label和中文商品名称之间的映射文件

In [ ]

# 解压标签数据集
# !unzip -oq /home/aistudio/data/data67153/RP2K_rp2k_dataset.zip -d /home/aistudio/data

In [ ]

# !tree /home/aistudio/data/all/test

In [ ]

# 数据预处理
import os
data_dir = 'work/dataset/all/train'
pathlist = os.listdir(data_dir)
ls = [int(k) for k in pathlist]
print(max(ls))
print(min(ls))
print(len(ls))
# 图像的可视化
import matplotlib.pyplot as plt 
%matplotlib inline
from PIL import Image
import numpy as np
img_dir = 'work/dataset/all/train/2383'
img_name = '327804.jpg'
img_path = os.path.join(img_dir, img_name)
img = Image.open(img_path)
img = np.array(img)
print(img.shape)
plt.figure(figsize=(8,8))
plt.imshow(img)
2388
1
2388
(716, 218, 3)
<matplotlib.image.AxesImage at 0x7f091f2f29d0>

<Figure size 576x576 with 1 Axes>

四、 模型选择

PP-ShiTu是一个实用的轻量级通用图像识别系统,主要由主体检测、特征学习和向量检索三个模块组成。该系统从骨干网络选择和调整、损失函数的选择、数据增强、学习率变换策略、正则化参数选择、预训练模型使用以及模型裁剪量化8个方面,采用多种策略,对各个模块的模型进行优化,最终得到在CPU上仅0.2s即可完成10w+库的图像识别的系统。

1、主体检测

主体检测技术是目前应用非常广泛的一种检测技术,它指的是检测出图片中一个或者多个主体的坐标位置,然后将图像中的对应区域裁剪下来,进行识别,从而完成整个识别过程。主体检测是识别任务的前序步骤,可以有效提升识别精度。
考虑到商品识别实际应用场景中,需要快速准确地获得识别结果,故本项目选取适用于 CPU 或者移动端场景的轻量级主体检测模型PicoDet作为本项目主体检测部分的模型。此模型融合了ATSS、Generalized Focal Loss、余弦学习率策略、Cycle-EMA、轻量级检测 head等一系列优化算法,基于COCO train2017数据集进行大规模预训练,最终inference模型大小(MB)仅30.1MB,mAP可达40.1%,在cpu下单张图片预测耗时仅29.8ms,完美符合本项目实际落地需求,故在本项目中不对主体检测部分做适应性训练。

2、特征提取

特征提取是图像识别中的关键一环,它的作用是将输入的图片转化为固定维度的特征向量,用于后续的向量检索。好的特征需要具备相似度保持性,即在特征空间中,相似度高的图片对其特征相似度要比较高(距离比较近),相似度低的图片对,其特征相似度要比较小(距离比较远)。Deep Metric Learning用以研究如何通过深度学习的方法获得具有强表征能力的特征。
考虑到本项目的真实落地的场景中,推理速度及预测准确率是考量模型好坏的重要指标,所以本项目采用 CPU 级轻量化骨干网络 PP_LCNet_x2_5 作为骨干网络, Neck 部分选用 Linear Layer, Head 部分选用 ArcMargin,Loss 部分选用 CELoss,并结合度量学习arcmargin算法,对高相似物体的区分效果远超单一模型。在 Intel 至强 6148 处理器,PP-LCNet 的单张图像 5.39ms 的预测速度下,在 ImageNet 上 Top1 识别准确率可以达到 80.82%,准确率超越大模型 ResNet50 的模型效果,而预测速度却可以达到后者的 3 倍!PP-ShiTu 充分挖掘该网络的潜力,学习一个具有超强泛化能力的特征提取模型,同一模型可在多个数据集上同时达到较高精度。

3、向量检索

PP-ShiTu 的第三个模块是向量检索。当获得了图像特征后,我们通过计算向量距离来获得两张图像的相似度,进一步通过向量检索获取最终识别结果。这种方式最大的优点是,当增加新的品类时,不需要重新训练提取特征模型,仅需要更新检索库即可识别新的目标。为了更好地兼容(Linux, Windows, MacOS)多平台,在图像识别系统中,本项目使用 Faiss 。在此过程中,本项目选取 HNSW32 为检索算法,使得检索精度、检索速度能够取得较好的平衡,更为贴切本项目实际应用场景的使用需求。

五、模型训练(识别模型backbone)

  • 需要修改配置文件:./ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml
  • 主要是将数据集的路径正确填写,其他配置按需修改
  • 修改内容较多,并且一般各种设置对于不同数据集都会有不同程度的影响,该配置文件中的各种设置仅供参考

In [ ]

# 检测模型训练
%cd /home/aistudio/PaddleClas
!python tools/train.py \
    -c ./ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \
    -o Arch.Backbone.pretrained=True \
    -o Arch.Head.class_num=2389 \
    -o Global.epochs=100 \
    -o Global.device=gpu \
    -o Global.checkpoints=./output_wide/RecModel/latest
  • 训练完成后输出日志文件等信息全部存放在output文件下的ResModel里面

六、特征提取模型评估

In [ ]

%cd /home/aistudio/PaddleClas
!python tools/eval.py \
    -c ./ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \
    -o Global.pretrained_model="/home/aistudio/PaddleClas/output/RecModel/best_model"
  • best model的统计是metric,会考虑到类内类间的差异,更好反映模型的健壮性(Best_Metric: 0.94251,观察到的最大值不太准)

  • 评估结果展示,总共跑了三种配置,每种配置训练100个Epoch,结果展示如下:

  • output: recall1: 92.392, recall5: 96.010

  • output_new: recall1: 94.074, recall5: 97.242, (recall1最高可以达到94.249)

  • output_wide: recall1: 94.101, recall5: 97.283,(best metric最优)

可见,最优的模型为output_wide模型,我们之后的推理模型都用wide模型

七. 模型推理

导出推理模型

PaddlePaddle框架保存的权重文件分为两种:支持前向推理和反向梯度的训练模型 和 只支持前向推理的推理模型。二者的区别是推理模型针对推理速度和显存做了优化,裁剪了一些只在训练过程中才需要的tensor,降低显存占用,并进行了一些类似层融合,kernel选择的速度优化。因此可执行如下命令导出推理模型。

In [ ]

# 导出推理模型
%cd /home/aistudio/PaddleClas
!python tools/export_model.py \
    -c ./ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \
    -o Global.pretrained_model="output_wide/RecModel/best_model"
  • 生成的推理模型位于 inference 目录,里面包含三个文件,分别为 inference.pdmodel、inference.pdiparams、inference.pdiparams.info。 其中: inference.pdmodel 用来存储推理模型的结构, inference.pdiparams 和 inference.pdiparams.info 用来存储推理模型相关的参数信息。

In [ ]

# 测试代码:获取待测试图片的特征向量
%cd /home/aistudio/PaddleClas/deploy
!python python/predict_rec.py \
    -c configs/inference_rec.yaml  \
    -o Global.rec_inference_model_dir="../inference" \
    -o Global.infer_imgs="/home/aistudio/work/dataset/test_image/wine.jpg"
  • 可以正常通过导出的推理模型完成图片特征向量的计算,说明推理模型导出成功

八、搭建商品识别系统

1、商品识别系统的识别原理如下:

  1. 输入图片经过保存的推理模型后,被编码为长度为128的向量。
  2. 在索引文件中使用向量检索算法查找与当前图片编码向量最接近的一个已有向量。
  3. 如果检索到的向量的分数低于阈值,那么认为此时匹配失败,索引库中没有对应的类别,输出为空。
  4. 如果检索到的向量的分数高于阈值,那么匹配成功,输出分数最高的向量对应的label作为识别结果。
  5. 利用预训练完成的主体检测模型Pico-Det对图片的商品主题进行检测,并将label和对应分数标注在预测锚框上方。
  • 建立索引库
    修改configs/build_general.yaml文件内容:
Global:
  rec_inference_model_dir: "/home/aistudio/PaddleClas/inference"
IndexProcess:
  index_method: "HNSW32" # supported: HNSW32, IVF, Flat
  image_root: "/home/aistudio/work/dataset/all/"
  index_dir: "/home/aistudio/work/dataset/all/index"
  data_file:  "/home/aistudio/work/dataset/all/gallery_label.txt"
  index_operation: "new" # suported: "append", "remove", "new"
  delimiter: "\t"
  dist_type: "IP"
  embedding_size: 4096

执行如下代码:

In [ ]

# 数据集没有提供gallery_label.txt
# 读取test_list.txt,然后每一类选择三个(不足则全部选择),加入gallery_label.txt
import os
contents = []
with open("/home/aistudio/work/dataset/all/test_list.txt", 'r') as fa:
    contents = fa.readlines()

def warp(s):
    ls = s.split()[0:2]
    new_s = "\t".join(ls)
    return new_s

contents = list(map(warp, contents))

import shutil
os.chdir('/home/aistudio/work/dataset/all/')
with open("/home/aistudio/work/dataset/all/gallery_label.txt", 'w') as fb:
    cnt = 0
    prev = ''
    for line in contents:
        if prev == '' or line.split('\t')[-1] != prev:
            cnt = 0
        if cnt < 3:
            path = line.split('\t')[0]
            name = path.split('/')[-1]
            shutil.copy(path, 'gallery/' + name)
            prev = line.split('\t')[-1]
            fb.write('gallery/' + name + '\t' + prev + '\n')
            cnt += 1

In [ ]

# 首先建立索引库
%cd /home/aistudio/PaddleClas/deploy
!python3 python/build_gallery.py \
    -c configs/build_general.yaml \
    -o IndexProcess.data_file="/home/aistudio/work/dataset/all/gallery_label.txt" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index"
/home/aistudio/PaddleClas/deploy
2022-05-17 11:33:33 INFO: 
===========================================================
==        PaddleClas is powered by PaddlePaddle !        ==
===========================================================
==                                                       ==
==   For more info please go to the following website.   ==
==                                                       ==
==       https://github.com/PaddlePaddle/PaddleClas      ==
===========================================================

2022-05-17 11:33:33 INFO: Global : 
2022-05-17 11:33:33 INFO:     batch_size : 32
2022-05-17 11:33:33 INFO:     cpu_num_threads : 10
2022-05-17 11:33:33 INFO:     enable_benchmark : True
2022-05-17 11:33:33 INFO:     enable_mkldnn : True
2022-05-17 11:33:33 INFO:     enable_profile : False
2022-05-17 11:33:33 INFO:     gpu_mem : 8000
2022-05-17 11:33:33 INFO:     ir_optim : True
2022-05-17 11:33:33 INFO:     rec_inference_model_dir : /home/aistudio/PaddleClas/inference
2022-05-17 11:33:33 INFO:     use_fp16 : False
2022-05-17 11:33:33 INFO:     use_gpu : True
2022-05-17 11:33:33 INFO:     use_tensorrt : False
2022-05-17 11:33:33 INFO: IndexProcess : 
2022-05-17 11:33:33 INFO:     data_file : /home/aistudio/work/dataset/all/gallery_label.txt
2022-05-17 11:33:33 INFO:     delimiter : 	
2022-05-17 11:33:33 INFO:     dist_type : IP
2022-05-17 11:33:33 INFO:     embedding_size : 4096
2022-05-17 11:33:33 INFO:     image_root : /home/aistudio/work/dataset/all/
2022-05-17 11:33:33 INFO:     index_dir : /home/aistudio/work/dataset/all/index
2022-05-17 11:33:33 INFO:     index_method : HNSW32
2022-05-17 11:33:33 INFO:     index_operation : new
2022-05-17 11:33:33 INFO: RecPostProcess : None
2022-05-17 11:33:33 INFO: RecPreProcess : 
2022-05-17 11:33:33 INFO:     transform_ops : 
2022-05-17 11:33:33 INFO:         ResizeImage : 
2022-05-17 11:33:33 INFO:             size : 224
2022-05-17 11:33:33 INFO:         NormalizeImage : 
2022-05-17 11:33:33 INFO:             mean : [0.485, 0.456, 0.406]
2022-05-17 11:33:33 INFO:             order : 
2022-05-17 11:33:33 INFO:             scale : 0.00392157
2022-05-17 11:33:33 INFO:             std : [0.229, 0.224, 0.225]
2022-05-17 11:33:33 INFO:         ToCHWImage : None
100%|██████████████████████████████████████| 5850/5850 [00:20<00:00, 281.66it/s]
2022-05-17 11:33:55 WARNING: The HNSW32 method dose not support 'remove' operation
  • 索引库建立完成后便可以使用主体检测模型进行检测,并使用推理模型进行识别。

运行如下命令,下载通用检测inference模型并解压:

下载完成后/home/aistudio/PaddleClas/deploy/文件夹下建立了一个models的子文件夹,里面是主体检测模型解压完成的文件夹,文件夹里包含四个文件,其中三个是推理模型的相关权重参数,另一个是推理用的配置文件。

In [ ]

%cd /home/aistudio/PaddleClas/deploy/
%mkdir models
%cd models
# 下载通用检测 inference 模型并解压
# 上面那里保存的是特征提取的backbone的推理模型,这里是主体检测的推理模型Picodet
!wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar && tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar

修改推理文件configs/inference_general.yaml内容:

Global:
  det_inference_model_dir: "./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer"
  rec_inference_model_dir: "/home/aistudio/PaddleClas/inference"
  rec_nms_thresold: 0.05
IndexProcess:
  index_method: "HNSW32" # supported: HNSW32, IVF, Flat
  image_root: "/home/aistudio/dataset/all/gallery/"
  index_dir: "/home/aistudio/dataset/all/index"
  data_file:  "/home/aistudio/work/dataset/all/gallery_label.txt"
  index_operation: "new" # suported: "append", "remove", "new"
  delimiter: "\t"
  dist_type: "IP"
  embedding_size: 4096
  batch_size: 32
  return_k: 5
  score_thres: 0.5

注意,配置文件中的infer_imgs字段不需要修改,我们在推理的时候使用命令行传入需要推理的图片路径即可

九、 系统测试:

1、第一种测试情况为待测试图片为索引库中已经存在的图片

In [ ]

# 基于索引库的图像识别
%cd /home/aistudio/PaddleClas/deploy
%pwd
!python python/predict_system.py \
    -c configs/inference_general.yaml \
    -o Global.infer_imgs="/home/aistudio/work/dataset/test_image/wine.jpg" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index"

其中 bbox 表示检测出的主体所在位置,rec_docs 表示索引库中与检测框最为相似的类别,rec_scores 表示对应的置信度。

检测的可视化结果也保存在 output 文件夹下,对于本张图像,识别结果可视化如下所示:

2. 若商品为原索引库里没有的商品:

对图像 /home/aistudio/dataset/test_image/recognition_2.jpg 进行识别, 待检索图像如下所示。

运行如下识别命令:

In [ ]

# 识别
%cd /home/aistudio/PaddleClas/deploy
!python python/predict_system.py \
    -c configs/inference_general.yaml \
    -o Global.infer_imgs="/home/aistudio/work/dataset/test_image/xiaodu.jpg" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index"
  • 由于默认的索引库中不包含对应的索引信息,所以这里的识别结果有误,此时我们可以通过构建新的索引库的方式,完成未知类别的图像识别。

  • 当索引库中的图像无法覆盖我们实际识别的场景时,即在预测未知类别的图像时,只需要将对应类别的相似图像添加到索引库中,从而完成对未知类别的图像识别,这一过程是不需要重新训练的。

  • 准备新的数据与标签

    首先需要将与待检索图像相似的图像列表拷贝到索引库原始图像的文件夹。这里将所有的底库图像数据都放在文件夹 /home/aistudio/dataset/all/gallery/ 中。

    然后需要编辑记录了图像路径和标签信息的文本文件,这里 PaddleClas 将更正后的标签信息文件放在了 /home/aistudio/dataset/all/gallery_update.txt 文件中。可以与原来的 /home/aistudio/dataset/all/gallery_label.txt 标签文件进行对比,添加了小度充电宝和韩国进口火山泥的索引图像。

    每一行的文本中,第一个字段表示图像的相对路径,第二个字段表示图像对应的标签信息,中间用 \t 键分隔开

In [ ]

# 使用下面的命令建立新的 index 索引,加速识别后的检索过程
%cd /home/aistudio/PaddleClas/deploy/
%pwd
!python python/build_gallery.py \
    -c configs/build_general.yaml \
    -o IndexProcess.data_file="/home/aistudio/work/dataset/all/gallery_update.txt" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index_update"
/home/aistudio/PaddleClas/deploy
2022-05-17 11:42:10 INFO: 
===========================================================
==        PaddleClas is powered by PaddlePaddle !        ==
===========================================================
==                                                       ==
==   For more info please go to the following website.   ==
==                                                       ==
==       https://github.com/PaddlePaddle/PaddleClas      ==
===========================================================

2022-05-17 11:42:10 INFO: Global : 
2022-05-17 11:42:10 INFO:     batch_size : 32
2022-05-17 11:42:10 INFO:     cpu_num_threads : 10
2022-05-17 11:42:10 INFO:     enable_benchmark : True
2022-05-17 11:42:10 INFO:     enable_mkldnn : True
2022-05-17 11:42:10 INFO:     enable_profile : False
2022-05-17 11:42:10 INFO:     gpu_mem : 8000
2022-05-17 11:42:10 INFO:     ir_optim : True
2022-05-17 11:42:10 INFO:     rec_inference_model_dir : /home/aistudio/PaddleClas/inference
2022-05-17 11:42:10 INFO:     use_fp16 : False
2022-05-17 11:42:10 INFO:     use_gpu : True
2022-05-17 11:42:10 INFO:     use_tensorrt : False
2022-05-17 11:42:10 INFO: IndexProcess : 
2022-05-17 11:42:10 INFO:     data_file : /home/aistudio/work/dataset/all/gallery_update.txt
2022-05-17 11:42:10 INFO:     delimiter : 	
2022-05-17 11:42:10 INFO:     dist_type : IP
2022-05-17 11:42:10 INFO:     embedding_size : 4096
2022-05-17 11:42:10 INFO:     image_root : /home/aistudio/work/dataset/all/
2022-05-17 11:42:10 INFO:     index_dir : /home/aistudio/work/dataset/all/index_update
2022-05-17 11:42:10 INFO:     index_method : HNSW32
2022-05-17 11:42:10 INFO:     index_operation : new
2022-05-17 11:42:10 INFO: RecPostProcess : None
2022-05-17 11:42:10 INFO: RecPreProcess : 
2022-05-17 11:42:10 INFO:     transform_ops : 
2022-05-17 11:42:10 INFO:         ResizeImage : 
2022-05-17 11:42:10 INFO:             size : 224
2022-05-17 11:42:10 INFO:         NormalizeImage : 
2022-05-17 11:42:10 INFO:             mean : [0.485, 0.456, 0.406]
2022-05-17 11:42:10 INFO:             order : 
2022-05-17 11:42:10 INFO:             scale : 0.00392157
2022-05-17 11:42:10 INFO:             std : [0.229, 0.224, 0.225]
2022-05-17 11:42:10 INFO:         ToCHWImage : None
100%|██████████████████████████████████████| 5852/5852 [00:21<00:00, 271.76it/s]
2022-05-17 11:42:33 WARNING: The HNSW32 method dose not support 'remove' operation
  • 基于新的索引库的图像识别

最终新的索引信息保存在文件夹 /home/aistudio/dataset/all/index_update 中。

使用新的索引库,对上述图像进行识别,运行命令如下:

In [ ]

%cd /home/aistudio/PaddleClas/deploy/
!python python/predict_system.py \
    -c configs/inference_general.yaml \
    -o Global.infer_imgs="/home/aistudio/work/dataset/test_image/xiaodu.jpg" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index_update"
/home/aistudio/PaddleClas/deploy
2022-05-17 11:43:18 INFO: 
===========================================================
==        PaddleClas is powered by PaddlePaddle !        ==
===========================================================
==                                                       ==
==   For more info please go to the following website.   ==
==                                                       ==
==       https://github.com/PaddlePaddle/PaddleClas      ==
===========================================================

2022-05-17 11:43:18 INFO: DetPostProcess : 
2022-05-17 11:43:18 INFO: DetPreProcess : 
2022-05-17 11:43:18 INFO:     transform_ops : 
2022-05-17 11:43:18 INFO:         DetResize : 
2022-05-17 11:43:18 INFO:             interp : 2
2022-05-17 11:43:18 INFO:             keep_ratio : False
2022-05-17 11:43:18 INFO:             target_size : [640, 640]
2022-05-17 11:43:18 INFO:         DetNormalizeImage : 
2022-05-17 11:43:18 INFO:             is_scale : True
2022-05-17 11:43:18 INFO:             mean : [0.485, 0.456, 0.406]
2022-05-17 11:43:18 INFO:             std : [0.229, 0.224, 0.225]
2022-05-17 11:43:18 INFO:         DetPermute : 
2022-05-17 11:43:18 INFO: Global : 
2022-05-17 11:43:18 INFO:     batch_size : 1
2022-05-17 11:43:18 INFO:     cpu_num_threads : 10
2022-05-17 11:43:18 INFO:     det_inference_model_dir : ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer
2022-05-17 11:43:18 INFO:     enable_benchmark : True
2022-05-17 11:43:18 INFO:     enable_mkldnn : True
2022-05-17 11:43:18 INFO:     enable_profile : False
2022-05-17 11:43:18 INFO:     gpu_mem : 8000
2022-05-17 11:43:18 INFO:     image_shape : [3, 640, 640]
2022-05-17 11:43:18 INFO:     infer_imgs : /home/aistudio/work/dataset/test_image/xiaodu.jpg
2022-05-17 11:43:18 INFO:     ir_optim : True
2022-05-17 11:43:18 INFO:     labe_list : ['foreground']
2022-05-17 11:43:18 INFO:     max_det_results : 5
2022-05-17 11:43:18 INFO:     rec_inference_model_dir : /home/aistudio/PaddleClas/inference
2022-05-17 11:43:18 INFO:     rec_nms_thresold : 0.05
2022-05-17 11:43:18 INFO:     threshold : 0.2
2022-05-17 11:43:18 INFO:     use_fp16 : False
2022-05-17 11:43:18 INFO:     use_gpu : True
2022-05-17 11:43:18 INFO:     use_tensorrt : False
2022-05-17 11:43:18 INFO: IndexProcess : 
2022-05-17 11:43:18 INFO:     batch_size : 32
2022-05-17 11:43:18 INFO:     data_file : /home/aistudio/work/dataset/all/gallery_label.txt
2022-05-17 11:43:18 INFO:     delimiter : 	
2022-05-17 11:43:18 INFO:     dist_type : IP
2022-05-17 11:43:18 INFO:     embedding_size : 4096
2022-05-17 11:43:18 INFO:     image_root : /home/aistudio/dataset/all/gallery/
2022-05-17 11:43:18 INFO:     index_dir : /home/aistudio/work/dataset/all/index_update
2022-05-17 11:43:18 INFO:     index_method : HNSW32
2022-05-17 11:43:18 INFO:     index_operation : new
2022-05-17 11:43:18 INFO:     return_k : 5
2022-05-17 11:43:18 INFO:     score_thres : 0.5
2022-05-17 11:43:18 INFO: RecPostProcess : None
2022-05-17 11:43:18 INFO: RecPreProcess : 
2022-05-17 11:43:18 INFO:     transform_ops : 
2022-05-17 11:43:18 INFO:         ResizeImage : 
2022-05-17 11:43:18 INFO:             size : 224
2022-05-17 11:43:18 INFO:         NormalizeImage : 
2022-05-17 11:43:18 INFO:             mean : [0.485, 0.456, 0.406]
2022-05-17 11:43:18 INFO:             order : 
2022-05-17 11:43:18 INFO:             scale : 0.00392157
2022-05-17 11:43:18 INFO:             std : [0.229, 0.224, 0.225]
2022-05-17 11:43:18 INFO:         ToCHWImage : None
Inference: 27.196168899536133 ms per batch image
[{'bbox': [522, 43, 708, 413], 'rec_docs': '韩国进口火山泥', 'rec_scores': 0.67161095}, {'bbox': [140, 86, 302, 400], 'rec_docs': '小度充电宝', 'rec_scores': 0.5900027}]

由测试效果图可知,模型对于未参与训练的商品及多个商品均有较好的识别效果,并且上图是鑫哥的PP-ShiTu图像分类项目中的识别结果,下图是本项目模型的识别结果,精度有所提高,说明上大数据集RP2K进行训练还是更有效的。

十、 模型优化思路

1、检测模型调优

PP-ShiTu中检测模型采用的 PicoDet算法,在使用官方模型后,如果不满足精度需求,则可以参考此部分文档,进行模型调优

2、识别模型调优

因为要对模型进行训练,所以参照数据准备部分描述收集自己的数据集。值得注意的是,此部分需要准备大量的数据,以保证识别模型效果。

  • 数据增强:根据实际情况选择不同数据增强方法。如:实际应用中数据遮挡比较严重,建议添加RandomErasing增强方法。
  • 换不同的backbone,一般来说,越大的模型,特征提取能力更强。
  • 增加模型的宽度,本项目中将分类头的embedding_size扩大为4096,是默认配置的8倍,一般来说,模型宽度越大,学习能力越强。
  • 选择不同的Metric Learning方法。不同的Metric Learning方法,对不同的数据集效果可能不太一样,建议尝试其他Loss
  • 采用蒸馏方法,对小模型进行模型能力提升,但是进行知识蒸馏可能比较困难。
  • 增补数据集。针对错误样本,添加badcase数据。

模型训练完成后,参照系统测试进行检索库更新。同时,对整个pipeline进行测试,如果精度不达预期,则重复此步骤。

  • 下一步是部署,但是限于时间原因,目前该项目的前端尚未搭建完成,最近杂事比较多,等有时间了会将其进行部署(Web或者安卓都可以考虑),因为目前看来,推理速度是15FPS左右,可以在移动端尝试。

  • 因此,下一步将模型部署换成了视频流的实时监测,思路也比较简单,感兴趣的可以详细查看下方的代码。

十一、接下来进行视频流实时监测

  • 使用VideoStream流实现视频帧的检测

In [ ]

# 导入依赖库
import paddle
import cv2
import os
import numpy as np

1、视频流检测的主要步骤:

  1. 通过opencv将视频转为图像。
  2. 将图像放进商品检测的模型中,返回商品检测框的数据集合。
  3. 设置某个阈值,计算某个商品检测框与下一帧的商品检测框的数据集合的IOU,当IOU大于该阈值时则判断该商品检测框与下一帧的某个商品检测框是同一商品。
  4. 设置商品检测框数据的缓冲区,当缓冲区中的商品检测框数据大于(或小于)某个数值时(如right>1600),则判断商品数量加1,并将该商品检测框数据移除缓冲区。
  5. 在图像上用红色框表示缓冲区的数据,绿色框表示非缓冲区的数据,并写入实时人流数。
  6. 通过opencv将图像转为视频。

2、不足之处:

  1. 在商品较多并且含有小体积商品时,会出现部分商品挡住另一部分的商品,导致一些图片小商品未被检测到。

3、改进方案:

  1. 在商品检测的基础上再增加特征提取网络进行商品识别,则视频中出现的商品种类则可以记录下来。
  2. 采用多角度拍摄,可以避免拍摄的死角。

In [ ]

import cv2
import numpy as np
from PIL import Image, ImageDraw
import os
def CutVideo2Image(video_path, img_path):
    #将视频输出为图像
    #video_path为输入视频文件路径
    #img_path为输出图像文件夹路径
    cap = cv2.VideoCapture(video_path)
    index = 0
    while(True):
        ret,frame = cap.read() 
        if ret:
            cv2.imwrite(img_path+'/%d.jpg'%index, frame)
            index += 1
        else:
            break
    cap.release()

def CombVideo(in_path, out_path, size):
    #将图片合成视频
    #in_path为输入图像文件夹路径
    #out_path为输出视频文件路径
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out = cv2.VideoWriter(out_path,fourcc, fps=15.0, frameSize=size)
    files = os.listdir(in_path)
    for path in files:
        if path[-3:] != 'jpg':
            files.remove(path)
    for i in range(len(files)):
        img = cv2.imread(in_path + '/%d.jpg' % i)
        img = cv2.resize(img, size)
        out.write(img)
    out.release()

def Iou(bbox1,bbox2):
    #计算Iou
    #bbox1,bbox为xyxy数据
    area1 = (bbox1[2]-bbox1[0])*(bbox1[3]-bbox1[1])
    area2 = (bbox2[2]-bbox2[0])*(bbox2[3]-bbox2[1])
    w = min(bbox1[3],bbox2[3])-max(bbox1[1],bbox2[1])
    h = min(bbox1[2],bbox2[2])-max(bbox1[0],bbox2[0])
    if w<=0 or h<=0:
        return 0
    area_mid = w*h
    return area_mid/(area1+area2-area_mid)

In [ ]

# 加载推理模型
from paddle.inference import Config
from paddle.inference import create_predictor
def init_predictor():
    model_file = 'PaddleClas/deploy/models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer/inference.pdmodel'
    params_file = 'PaddleClas/deploy/models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer/inference.pdiparams'
    config = Config(model_file, params_file)

    config.enable_memory_optim()
    config.enable_use_gpu(1000, 0)

    predictor = create_predictor(config)
    return predictor


def run(predictor, img):
    input_names = predictor.get_input_names()
    for i, name in enumerate(input_names):
        input_tensor = predictor.get_input_handle(name)
        input_tensor.reshape(img[i].shape)
        input_tensor.copy_from_cpu(img[i].copy())

    predictor.run()

    results = []
    output_names = predictor.get_output_names()
    for i, name in enumerate(output_names):
        output_tensor = predictor.get_output_handle(name)
        output_data = output_tensor.copy_to_cpu()
        results.append(output_data)
    return results

In [ ]

def resize(img, target_size):
    """resize to target size"""
    if not isinstance(img, np.ndarray):
        raise TypeError('image type is not numpy.')
    im_shape = img.shape
    im_size_min = np.min(im_shape[0:2])
    im_size_max = np.max(im_shape[0:2])
    im_scale_x = float(target_size) / float(im_shape[1])
    im_scale_y = float(target_size) / float(im_shape[0])
    img = cv2.resize(img, None, None, fx=im_scale_x, fy=im_scale_y)
    return img


def normalize(img, mean, std):
    img = img / 255.0
    mean = np.array(mean)[np.newaxis, np.newaxis, :]
    std = np.array(std)[np.newaxis, np.newaxis, :]
    img -= mean
    img /= std
    return img


def preprocess(img, img_size):
    mean = [0.485, 0.456, 0.406]
    std = [0.229, 0.224, 0.225]
    img = resize(img, img_size)
    img = img[:, :, ::-1].astype('float32')  # bgr -> rgb
    img = normalize(img, mean, std)
    img = img.transpose((2, 0, 1))  # hwc -> chw
    return img[np.newaxis, :]


def draw_bbox(img, result, threshold=0.5, save_name='res.jpg'):
    """draw bbox"""
    draw = ImageDraw.Draw(img)
    for res in result:
        cat_id, score, bbox = res[0], res[1], res[2:]
        if score < threshold:
            continue
        xmin, ymin, xmax, ymax = bbox
        draw.line([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin),
                   (xmin, ymin)],
                  width=2,
                  fill=(255, 0, 0))
        print('category id is {}, bbox is {}'.format(cat_id, bbox))
    img.save(save_name, quality=95)

In [ ]

def Solve(img_name, save_img_name):
    # img_name = 'kite.jpg'
    # save_img_name = 'res.jpg'
    im_size = 608
    pred = init_predictor()
    img = cv2.imread(img_name)
    data = preprocess(img, im_size)
    scale_factor = np.array([im_size * 1. / img.shape[0], im_size * 1. / img.shape[1]]).reshape((1, 2)).astype(np.float32)
    im_shape = np.array([im_size, im_size]).reshape((1, 2)).astype(np.float32)
    result = run(pred, [im_shape, data, scale_factor])
    img = Image.open(img_name).convert('RGB')
    draw_bbox(img, result[0], save_name=save_img_name)

def GetItem(in_path, out_path):
    # 商品检测
    # in_path为输入图像文件夹的路径
    # out_path为输出图像文件夹的路径,并为商品标注检测框和进行计数
    files = os.listdir(in_path)
    for i in range(len(files)-1):
        #文件中的每张图片
        Solve(in_path +'/%d.jpg' % i, out_path +'/%d.jpg' % i)

    # CombVideo(out_path, out_path+'/video.mp4', (608, 608))

以下是测试代码

In [ ]

# 视频转图片
CutVideo2Image('work/dataset/det.mp4', 'work/dataset/video_to_imgs')
# 图片处理
# GetItem('work/dataset/video_to_imgs', 'work/dataset/imgs_to_video')

In [15]

# 基于索引库的图像识别
%cd /home/aistudio/PaddleClas/deploy
%pwd
!python python/predict_system.py \
    -c configs/inference_general.yaml \
    -o Global.infer_imgs="/home/aistudio/work/dataset/video_to_imgs" \
    -o IndexProcess.index_dir="/home/aistudio/work/dataset/all/index_update"

In [21]

# 图片转视频
CombVideo('./output', './video.avi', (1514, 848))
# video_capture = cv2.VideoCapture('work/dataset'+'/video.avi')
#video_capture = cv2.VideoCapture(1)
#video_capture = cv2.VideoCapture(0)
# 其中0表示打开内置摄像头,1表示打开外接摄像头
# if video_capture.isOpened():  # VideoCaputre对象是否成功打开
#     print('打开摄像头或者视频成功')
#     readVideo_flag = True
# else:
#     print('打开摄像头或者视频失败')
#     readVideo_flag = False
# while readVideo_flag:
#     ret, frame = video_capture.read() 
#     if ret != True:
#         break
#     print("下面在notebook环境下无法展示")
#     # cv2.imshow('frame',frame)
# video_capture.release()

十二、总结

  • 本次项目我是第一次接触PP-ShiTu图像识别系统,个人感觉非常好用,各种模型的配置文件都已经写好了,比自己从头搭建甚至说去找其他的开源代码要方便的多。

  • 在模型调优上也比较方便,根据自己的数据集选择合适的模型即可,可以对其宽度进行适当加宽,但是学习率策略、数据增强、EMA和weight decay等都保持默认是可以达到最好的效果的,说明PaddleCls提供了高质量的配置文件,大大减轻了我们的调优负担。

  • 感谢导师郑博培大佬的指导,受益匪浅!

  • 团队介绍:

深藏Blue队

导师:郑博培 北京联合大学 机器人学院 自动化专业 2018级 本科生 百度飞桨开发者技术专家 PPDE 中国科学院自动化研究所复杂系统管理与控制国家重点实验室实习生 阿里云人工智能、DevOps助理工程师

队长:赵祎安 大连理工大学百度飞桨领航团团长 计算机科学与技术专业 2019级 本科生

队员:杜恒辉、高兴、申晨 大连理工大学 计算机科学与技术专业 2019级 本科生

请点击此处查看本环境基本用法.
Please click here for more detailed instructions.

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐