开发者市集是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
https://github.com/PaddlePaddle/FastDeploy
训练分割模型
https://aistudio.baidu.com/aistudio/datasetdetail/68764/0
图片地址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张图片对新采集到的图片进行标注,从而通过增加数据集数量的方式提高模型的检测质量。主要的数据集扩充流程如下。
美甲渲染
当我们完成了模型训练后,只能将手指图片中的指甲部分分割出来,想要真正“做美甲”,还需要设计指甲渲染策略。下面,简单介绍如何对指甲进行渲染。
在不考虑指甲前缘和指甲弧的情况下,不妨假设指甲部分的颜色是统一且均匀的。则最后呈现在我们眼前的指甲色彩主要由几部分决定:指甲基色和光线、景深以及各种因素。
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
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()
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
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')
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信息进行融合,欢迎大家对模型进行改进~
本文分享自 PaddlePaddle 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!