\u200E
市集系列 | PaddleSeg搭配FastDeploy,打造「AI美甲试色」预览神器
发布日期:2022-11-18T07:20:43.000+0000 浏览量:1882次
开发者市集是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 [ 43]]
         self.color = QColor()
         self.color.setHsv( 30085255)
         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,  0061)

        ColorText = QLabel( '颜色预览'self)
        grid.addWidget(ColorText,  6011)

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

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

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

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

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

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

        Exit_Exe = QPushButton( '退出'self)
        grid.addWidget(Exit_Exe,  19011)
        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,  012020)

         # 设置屏幕位置和大小
         # 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 >  00] = (hsv_img[mask >  00] - np.median(hsv_img[mask >  0, 0]) +  self.choosed_pic[mask >  00])
            hsv_img[mask >  01] = (hsv_img[mask >  01] - np.median(hsv_img[mask >  0, 1]) +  self.choosed_pic[mask >  01])
         else:
            hsv_img[mask >  00] = hsv_img[mask >  00] +  self.color.getHsv()[ 0]/ 2 - np.median(hsv_img[mask >  0, 0])
            hsv_img[mask >  01] = hsv_img[mask >  01] +  self.color.getHsv()[ 1] - np.median(hsv_img[mask >  0, 1])

        hsv_img[hsv_img[ :, :, 0] >  1790] =  179
        hsv_img[hsv_img[ :, :, 0] <  10] =  1
        hsv_img[hsv_img[ ::1] >  2550] =  255
        hsv_img[hsv_img[ ::1] <  10] =  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( 4030255)
        elif  self.sender().text() ==  '浅绿'# 浅绿 还不错
             self.color.setHsv( 10085255)
        elif  self.sender().text() ==  '青绿'# 青绿 还不错
             self.color.setHsv( 15085255)
        elif  self.sender().text() ==  '玫红'# 肉红色 感觉没啥问题就是不太好看
             self.color.setHsv( 350160255)
         else:
             self.color.setHsv( 30085255# 粉色 标准色贼拉好

         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 >  00] =  self.color.getHsv()[ 0]/ 2 *  1.1
        hsv_img[mask >  01] =  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】公众号
获取更多技术内容~