市集系列 | PaddleSeg搭配FastDeploy,打造「AI美甲试色」预览神器

Posted 百度大脑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了市集系列 | PaddleSeg搭配FastDeploy,打造「AI美甲试色」预览神器相关的知识,希望对你有一定的参考价值。

开发者市集是WAVE SUMMIT+峰会上由开发者们基于飞桨打造酷炫项目的展示和交流平台。开发者们脑洞大开的Al创意,每年都会吸引不少人驻足观看。

今年11月的WAVE SUMMIT+2022峰会也将展示30余个开源展示项目,覆盖智慧城市、体育、趣味互动等产业应用。通过市集系列文章,我们先一探究竟~

今天将由飞桨开发者技术专家张一乔介绍“美甲预览机”项目。

项目简介

很多人觉得时尚感重点都在衣服的选款,其实很多小细节也影响着造型给人的感觉,美甲就是这样的时髦细节之一。随着美甲的流行,做美甲已经成为了很多人的日常休闲方式之一。但是面对琳琅满目的美甲照片“卖家秀”,很多人会存有疑虑,这些美甲的颜色呈现在自己的手上会有什么效果?美甲预览机就为大家解决这个问题。

美甲预览机可以被看作在指甲上的AI试衣间(下文简称美甲机)。它的主要功能是实时识别摄像头拍摄的画面,并将画面中的指甲部分渲染成涂过美甲的样子,无需用户真正做一次美甲,即可便捷地观察到自己做过美甲的样子,可以用来给不确定美甲方案、单纯对美甲感兴趣的人进行预览。

项目效果

项目流程图

本项目详细介绍了如何通过深度学习方法,轻松构造美甲机。项目内容包含训练指甲识别模型、Jetson Nano配置(Jetson Nano可用笔记本代替)、美甲机组装、效果优化等。项目主要流程如下:

技术细节

开发环境准备

AI Studio在线运行环境(若算力允许,可以自行替换其他训练设备)、个人主机、摄像头。(可选)Jetson Nano/Aibox、补光灯、双面胶、纸箱、线。

注:所有边缘设备,包括PC都可用于部署,本文以AiBox为例,使用FastDeploy进行部署。

关于FastDeploy,部署环境中可以直接使用CPU进行模型推理,安装命令如下:

pip install fastdeploy-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html
  • 更多支持模型和安装(如GPU版本)请参考

https://github.com/PaddlePaddle/FastDeploy

训练分割模型

  • 首先,训练一个指甲分割模型,基础数据集的下载链接为

https://aistudio.baidu.com/aistudio/datasetdetail/68764/0

  • 获得数据后,按照下述格式写入txt文档

图片地址1<空格>标签地址1 

图片地址2<空格>标签地址2 

图片地址3<空格>标签地址3 

图片地址4<空格>标签地址4 

图片地址5<空格>标签地址5

我们选择飞桨图像分割套件PaddleSeg中的PP-LiteSeg模型进行训练,下载并安装PaddleSeg,链接如下。

https://github.com/PaddlePaddle/PaddleSeg

  • 训练需要准备的核心配置文件,如下

batch_size: 8 # 配置批大小和迭代次数
iters: 5000

train_dataset: # 设置训练集路径,图像增强方法仅包含随机裁剪/缩放/调整明暗度和归一
  type: Dataset
  dataset_root: /home/aistudio
  train_path: /home/aistudio/train.txt
  num_classes: 2
  mode: train
  transforms:
    - type: RandomPaddingCrop
      crop_size: [480, 360]
    - type: Resize
      target_size: [480, 360]
    - type: RandomHorizontalFlip
    - type: RandomDistort
      brightness_range: 0.5
      contrast_range: 0.5
      saturation_range: 0.5
    - type: Normalize

val_dataset: # 设置验证集
  type: Dataset
  dataset_root: /home/aistudio
  val_path: /home/aistudio/val.txt
  num_classes: 2
  mode: val
  transforms:
    - type: Normalize

optimizer: # 设置优化器
  type: sgd
  momentum: 0.9
  weight_decay: 4.0e-5

lr_scheduler: # 设置学习率
  type: PolynomialDecay
  learning_rate: 0.01
  end_lr: 0
  power: 0.9

loss: # 使用交叉熵损失
  types:
    - type: CrossEntropyLoss
  coef: [1, 1, 1]


model: # 选择模型为PPLiteSeg
  type: PPLiteSeg
  backbone:
    type: STDC2
    pretrained: https://bj.bcebos.com/paddleseg/dygraph/PP_STDCNet2.tar.gz

将上述内容写入yaml文件,并且命名为myconfig.yml放置于AI Studio根目录/home/aistudio下,就可以通过下述代码一键训练了。

%cd ~/PaddleSeg
! export CUDA_VISIBLE_DEVICES=0 
! python train.py \\
       --config ~/myconfig.yml \\
       --save_interval 2500 \\
       --do_eval \\
       --use_vdl \\
       --save_dir output

训练好的模型保存在~/PaddleSeg/output/best_model/中。最后,将训练后的模型导出成预测模型,用于后续部署。

! python export.py \\
       --config ~/myconfig.yml \\
       --model_path output/best_model/model.pdparams \\
       --save_dir ~/infer_model

数据扩充

在实际使用中,有时候会发现模型的分割效果并不好,解决这个问题的直接方法就是数据扩充。由于本项目的基础训练集一共只有52张图片,本项目直接基于52张图片对新采集到的图片进行标注,从而通过增加数据集数量的方式提高模型的检测质量。主要的数据集扩充流程如下。

  • 训练OCRNet模型

  • 采集包含指甲的图片

  • 使用OCRNet对图片进行预标注

  • 对预测后的图片进行人工微调

  • 修图完成后,你就拥有了一份更大的训练数据,就可以训练一个更好的分割网络啦

美甲渲染

当我们完成了模型训练后,只能将手指图片中的指甲部分分割出来,想要真正“做美甲”,还需要设计指甲渲染策略。下面,简单介绍如何对指甲进行渲染。

在不考虑指甲前缘和指甲弧的情况下,不妨假设指甲部分的颜色是统一且均匀的。则最后呈现在我们眼前的指甲色彩主要由几部分决定:指甲基色和光线、景深以及各种因素。

  • 对于某个特定的像素点

Color=Base_Color+Influence

我们做美甲,相当于对指甲基色,也就是Base_ColorBase进行修改。因此,只需要将抖动项,也就是Influence求出即可。

对于一张给定的指甲图片,其指甲盖部分的基色可以通过求均值、求中位数获得。而Influence可以通过Color−Base_Color获得。从而做了美甲的图片中各个像素点的值为:

Target_Color=Target_Base_Color+Influence

对于HSV模型,可以认为亮度(V)是不需要调整的。因此仅对色调(H)和饱和度(S)应用上述模型即可。

PyQt5封装

现在已经有了分割模型和渲染策略,稍作结合即可。使用PyQt5时,只需要读取模型,并在展示图片之前自行实现图片处理逻辑即可。封装函数如下:

  • 首先,导入需要使用的包

import sys
import cv2
import numpy as np
import math
import time
import collections
import os
import sys
from PyQt5.QtWidgets import QWidget, QDesktopWidget, QApplication, QPushButton, QFileDialog, QLabel, QTextEdit, \\
    QGridLayout, QFrame, QColorDialog
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QColor, QImage, QPixmap
import fastdeploy
  • 之后继承QWidget模块,创建一个class类,比如起名为Example

class Example(QWidget):
  • 首先进行初始化,包括界面内容和模型内容等

def __init__(self):
        super().__init__()
        w, h = [QApplication.desktop().screenGeometry().width() - 800,
                QApplication.desktop().screenGeometry().height() - 800]
        # w = min(w, int(h/3*4))
        self.img_width, self.img_height = [w, int(w/4*3)]
        self.infer_img_width, self.infer_img_height = [120 * x for x in [4, 3]]
        self.color = QColor()
        self.color.setHsv(300, 85, 255)
        self.ifpic = 0

        self.initCamera()
        self.initUI()
        self.initModel()

    # 初始化Camera相关信息
    def initCamera(self):
        # 开启视频通道
        self.camera_id = 0 # 为0时表示视频流来自摄像头
        self.camera = cv2.VideoCapture()  # 视频流
        self.camera.open(self.camera_id)

        # 通过定时器读取数据
        self.flush_clock = QTimer()  # 定义定时器,用于控制显示视频的帧率
        self.flush_clock.start(60)   # 定时器开始计时60ms,结果是每过60ms从摄像头中取一帧显示
        self.flush_clock.timeout.connect(self.show_frame)  # 若定时器结束,show_frame()

    def initUI(self):

        grid = QGridLayout()
        self.setLayout(grid)

        self.Color_Frame = QFrame(self)
        self.Color_Frame.setStyleSheet("QWidget  background-color: %s " % self.color.name())
        grid.addWidget(self.Color_Frame, 0, 0, 6, 1)

        ColorText = QLabel('颜色预览', self)
        grid.addWidget(ColorText, 6, 0, 1, 1)

        Choose_Color = QPushButton('粉色', self)
        grid.addWidget(Choose_Color, 7, 0, 1, 1)
        Choose_Color.clicked.connect(self.choose_color)

        Choose_Color = QPushButton('米黄', self)
        grid.addWidget(Choose_Color, 8, 0, 1, 1)
        Choose_Color.clicked.connect(self.choose_color)

        Choose_Color = QPushButton('浅绿', self)
        grid.addWidget(Choose_Color, 9, 0, 1, 1)
        Choose_Color.clicked.connect(self.choose_color)

        Choose_Color = QPushButton('青绿', self)
        grid.addWidget(Choose_Color, 10, 0, 1, 1)
        Choose_Color.clicked.connect(self.choose_color)

        Choose_Color = QPushButton('玫红', self)
        grid.addWidget(Choose_Color, 11, 0, 1, 1)
        Choose_Color.clicked.connect(self.choose_color)

        Choose_Pic = QPushButton('自选图片', self)
        grid.addWidget(Choose_Pic, 13, 0, 1, 1)
        Choose_Pic.clicked.connect(self.showDialog)

        Exit_Exe = QPushButton('退出', self)
        grid.addWidget(Exit_Exe, 19, 0, 1, 1)
        Exit_Exe.clicked.connect(self.close)

        self.Pred_Box = QLabel()  # 定义显示视频的Label
        self.Pred_Box.setFixedSize(self.img_width, self.img_height)
        grid.addWidget(self.Pred_Box, 0, 1, 20, 20)

        # 设置屏幕位置和大小
        # self.setGeometry(300, 300, 600, 600)
        # 设置屏幕大小
        # self.resize(600, 600)

        self.setWindowTitle('test')
        self.show()
        # set center

        # self.center()
        # self.setWindowTitle('Center')
        # self.show()
  • 通过initModel创建用于推理的模型

def initModel(self):
        # 读取模型
        model_dir = "../infer_model_specify_size/"
        self.model = fastdeploy.vision.segmentation.PaddleSegModel(model_dir+'model.pdmodel',
                                                                   model_dir+'model.pdiparams',
                                                                   model_dir+'deploy.yaml')

在show_frame中读取摄像头并展示图片。
    def show_frame(self):
        _, img = self.camera.read()  # 从视频流中读取

        img = cv2.resize(img, (self.img_width, self.img_height))  # 把读到的帧的大小重新设置为 640x480
        mask, pred = self.Infer(img)

        # 往显示视频的Label里 显示QImage
        showImage = QImage(pred, pred.shape[1], pred.shape[0], QImage.Format_RGB888)
        self.Pred_Box.setPixmap(QPixmap.fromImage(showImage))

通过infer函数进行图片的预处理、推理以及渲染。
    def Infer(self, ori_img):
        h, w, c = ori_img.shape

        img = cv2.resize(ori_img, (self.infer_img_width, self.infer_img_height))
        pre = self.model.predict(img)

        mask = np.reshape(pre.label_map, pre.shape) * 255
        mask = np.array(mask).astype('float32')
        mask = paddle.vision.resize(mask, [h,w])
        # cv2.imwrite('mask.jpg', mask)

        hsv_img = cv2.cvtColor(ori_img, cv2.COLOR_BGR2HSV).astype('float32')

        if self.ifpic == 1:
            hsv_img[mask > 0, 0] = (hsv_img[mask > 0, 0] - np.median(hsv_img[mask > 0,0]) + self.choosed_pic[mask > 0, 0])
            hsv_img[mask > 0, 1] = (hsv_img[mask > 0, 1] - np.median(hsv_img[mask > 0,1]) + self.choosed_pic[mask > 0, 1])
        else:
            hsv_img[mask > 0, 0] = hsv_img[mask > 0, 0] + self.color.getHsv()[0]/2 - np.median(hsv_img[mask > 0,0])
            hsv_img[mask > 0, 1] = hsv_img[mask > 0, 1] + self.color.getHsv()[1] - np.median(hsv_img[mask > 0,1])

        hsv_img[hsv_img[:,:,0] > 179, 0] = 179
        hsv_img[hsv_img[:,:,0] < 1, 0] = 1
        hsv_img[hsv_img[:, :, 1] > 255, 0] = 255
        hsv_img[hsv_img[:, :, 1] < 1, 0] = 1

        bgr_img = cv2.cvtColor(hsv_img.astype('uint8'), cv2.COLOR_HSV2RGB)
        # bgr_img = paddle.vision.resize(bgr_img, [h,w])
        # cv2.imwrite('result.jpg', bgr_img)

        return mask, bgr_img

通过choose_color提供更多的切换颜色的接口。
    def choose_color(self):
        self.ifpic = 0 # 取消自选图片的状态
        if self.sender().text() == '米黄': # 米黄 有点偏绿 过曝了 # (50, 85, 255)
            self.color.setHsv(40, 30, 255)
        elif self.sender().text() == '浅绿': # 浅绿 还不错
            self.color.setHsv(100, 85, 255)
        elif self.sender().text() == '青绿': # 青绿 还不错
            self.color.setHsv(150, 85, 255)
        elif self.sender().text() == '玫红': # 肉红色 感觉没啥问题就是不太好看
            self.color.setHsv(350, 160, 255)
        else:
            self.color.setHsv(300, 85, 255) # 粉色 标准色贼拉好

        self.Color_Frame.setStyleSheet("QWidget  background-color: %s " % self.color.name())

        # print(self.color.name)

    def showDialog(self):
        fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
        try:
            self.ifpic = 1
            print(fname)
            self.choosed_pic = cv2.imread(fname[0])
            self.choosed_pic = cv2.resize(self.choosed_pic, (self.img_width, self.img_height))
            self.choosed_pic = cv2.cvtColor(self.choosed_pic, cv2.COLOR_BGR2HSV).astype('float32')
        except:
            self.ifpic = 0

        print(self.ifpic)
  • 最后,在主函数中调用声明好的类即可

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

FastDeploy部署优化

FastDeploy是一款全场景、易用灵活、极致高效的AI推理部署套件。提供开箱即用的云边端部署体验, 支持超过150+模型,并实现端到端的推理性能优化,覆盖多个任务场景,满足开发者多场景、多硬件、多平台的产业部署需求。

本案例使用FastDeploy进行美甲机部署,只需要在PyQt5的部署代码中替换如下内容:

  • 引入包

import fastdeploy
  • 将美甲机代码中initModel函数替换为如下内容

def initModel(self):
        # 读取模型
        model_dir = "../infer_model/"
        self.model = fastdeploy.vision.segmentation.PaddleSegModel(model_dir+'model.pdmodel',model_dir+'model.pdiparams',model_dir+'deploy.yaml')
  • 将美甲机代码中Infer函数替换为如下内容

def Infer(self, ori_img):
        h, w, c = ori_img.shape

        img = paddle.vision.resize(ori_img,[self.infer_img_height, self.infer_img_width])

        pre = self.model.predict(img)

        mask = np.reshape(pre.label_map, pre.shape) * 255
        mask = np.array(mask, np.uint8)
        mask = paddle.vision.resize(mask, [h,w])

        hsv_img = cv2.cvtColor(ori_img, cv2.COLOR_BGR2HSV)

        hsv_img[mask > 0, 0] = self.color.getHsv()[0]/2 * 1.1
        hsv_img[mask > 0, 1] = self.color.getHsv()[1]

        bgr_img = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2RGB)

        return mask, bgr_img

相比传统推理引擎的Infer函数,FastDeploy推理套件的Infer函数不需要写入一大堆数据预处理的内容,直接调用FastDeploy封装好的predict即可预测,极大加速AI模型部署落地速度。

总结

整个美甲机部署和优化的全过程就结束了,美甲机的应用效果可以通过不断填充数据集逐渐完善。这个项目中使用了PaddleSeg套件和FastDeploy,前者可以便于我们快速应用很多鲁棒的分割模型,后者则方便我们快速地在多种边缘设备上优化部署性能,免除了根据不同设备配置不同加速框架的烦恼,真正的一套代码完成所有。

如果对上述两个工具感兴趣,可以前往PaddleSeg和FastDeploy的Github仓库了解~最后的最后,美甲机除了标准深度学习支持,还需要一些数据渲染上的优化策略,本项目使用了基于HSV的渲染策略,还可以考虑对RGB信息进行融合,欢迎大家对模型进行改进~

WAVE SUMMIT+2022

WAVE SUMMIT+2022将于11月30日在深圳举办,欢迎大家扫码报名!关注飞桨公众号,后台回复关键词「WAVE」进入官网社群了解更多峰会详情!

WAVE SUMMIT+2022报名入口

关注【飞桨PaddlePaddle】公众号

获取更多技术内容~

以上是关于市集系列 | PaddleSeg搭配FastDeploy,打造「AI美甲试色」预览神器的主要内容,如果未能解决你的问题,请参考以下文章

如何使用PaddleSeg 2.0快速测量心胸比?

paddleseg学习记录——模型预测

52训练paddleSeg模型,部署自己的模型到OAK相机上

深度学习|飞桨图像分割利器PaddleSeg技术分享

基于PaddleSeg实现眼底血管分割——助力医疗人员更高效检测视网膜疾病

PaddleSeg图像分割库再添新武器,新增压缩部署方案FLOPs降低51%