明月深度学习实践011:使用FastAPI部署人脸识别引擎

2021-10-28 14:40:03 浏览数 (1)

在使用过程中,发现还是应该写一个demo,这样才更好入门,今天要做的就是这个demo:使用FastAPI来部署一个人脸识别引擎。

01 关于人脸识别引擎


人脸识别大体上分成三个步骤:

  1. 人脸检测(GPU算法)
  2. 人脸对齐(CPU算法)
  3. 人脸识别(GPU算法)

刚开始的时候,引擎只提供一个接口,同时包含上面三个功能,但是这其实是不合理的,因为在我们的场景中人脸检测的使用比人脸识别的概率多很多,因为摄像头中很多帧图像可能都是没有人脸的。所以,在做架构的时候,我们拆成了两个接口:

  1. 人脸检测与人脸对齐
  2. 人脸识别

整体架构:

人脸检测和人脸识别都会被部署成多个服务,然后使用Nginx来做负载均衡。

02 FastAPI接口层的主要文件介绍


接口层相关的文件其实主要是两个:

  • main.py: FastAPI的主入口文件,检测和识别接口就定义在这个文件里;
  • main_settings.py: 接口参数配置文件,如输入输出参数等。

当然比较相关的文件还有一个项目的配置文件settings.py。

03 主入口:main.py


这是FastAPI的主入口:

代码语言:javascript复制
# 详见:https://github.com/IBBD/docs/blob/main/demo/FastAPI/main.py
DESCRIPTION = """
主要实现功能:
- 人脸检测
- 人脸识别
### 接口列表:
"""
DET_URL = '/images/detect'
REG_URL = '/images/recognize'
if det_args['open']:
    DESCRIPTION  = '- 人脸检测 %sn' % DET_URL
if reg_args['open']:
    DESCRIPTION  = '- 人脸识别 %sn' % REG_URL
DESCRIPTION  = """n
### 使用说明
1. 将图像转为base64格式
可以看这里:https://github.com/ibbd-dev/python-image-utils/blob/master/image_utils/convert.py ,方法`pil_base64`或者`cv2_base64`
2. 测试代码: 看[这里](https://github.com/IBBD/docs/blob/main/tests/人脸识别引擎测试.ipynb)。
"""

# 版本号
VERSION = '0.8'
app = FastAPI(
    title="人脸检测与识别引擎",
    description=DESCRIPTION,
    version=VERSION,
)

if det_args['open']:
    @app.post(DET_URL, response_model=List[DetItem],
              summary='人脸检测')
    async def api_detect(params: DetParams):
        """输入多个图像,返回人脸box及其关键点
        说明:一个图像可能会识别到多个人脸
        """
        if len(params.images) == 0:
            return []
        params.images = conc_map(base64_cv2, params.images,
                                 max_workers=cpu_workers)
        res = images_detect(**dict(params))
        for img_data in res:     # 格式化返回值
            img_data['faces'] = conc_map(cv2_base64, img_data['faces'],
                                         max_workers=cpu_workers)
            img_data['scores'] = list(img_data['scores'])
            img_data['boxes'] = [list(b) for b in img_data['boxes']]
            img_data['landmarks'] = [[list(l) for l in lmk] 
                                      for lmk in img_data['landmarks']]

        return res

if reg_args['open']:
    @app.post(REG_URL, response_model=List[RegItem],
              summary='人脸识别')
    async def api_recognize(params: RegParams):
        """人脸识别,返回人脸特征向量,每个人脸图像会对应一个特征向量
        每个特征向量是一个多维的列表
        """
        if len(params.faces) == 0:
            return []
        params.faces = conc_map(base64_cv2, params.faces,
                                max_workers=cpu_workers)
        res = images_recognize(**dict(params))
        for data in res:     # 格式化返回值
            data['embedding'] = data['embedding'].tolist()

        return res

我们部署的时候,部署成多个容器服务,但是我们代码实现还是在同一个项目里面的,所以我们允许在配置文件里配置该启动什么接口。检测与识别主要功能在函数images_detect和images_recognize中实现,而在接口层中,主要实现的是对输入参数的格式化,和对输出数据的规范化。

如果实现的接口比较多,则可以将接口分拆到不同的文件里。展示的效果大概如下:

在接口文档的这部分,应该将接口的基本情况说清楚。

04 接口参数配置


我们的接口参数都是分拆出来,实现在一个独立的文件里:

代码语言:javascript复制
# 详见:https://github.com/IBBD/docs/blob/main/demo/FastAPI/main_settings.py
# 人脸关键点的样例
landmarks_example = [
    [30.2946, 51.6963],
    [65.5318, 51.5014],
    [48.0252, 71.7366],
    [33.5493, 92.3655],
    [62.7299, 92.2041]
]

class DetParams(BaseModel):
    """检测参数"""
    images: List[str] = Field(..., title='待检测的图像列表',
                              description='base64格式。每个图像可能会识别到多个人脸')
    threshold: float = Field(det_args['threshold'], ge=0.,
                             title='人脸检测的得分阈值',
                             description='可以使用该值过滤掉一些质量比较差的人脸')
    align: bool = Field(det_args['align'], title='人脸是否进行对齐',
                        description='若为true,则需要对返回的人脸进行对齐。')

class DetItem(BaseModel):
    """单个图像的检测结果"""
    faces: List[str] = Field(..., title='识别到的人脸图像列表',
                             description='base64格式')
    boxes: List[List[float]] = Field(..., example=[[10, 11, 15, 16]],
                                     title='人脸图像在原图像中的坐标',
                                     description='注意这里的坐标是相对于输入图像的。坐标格式:[x1, y1, x2, y2]')
    scores: List[float] = Field(..., example=[0.995],
                                title='人脸的得分值',
                                description='该值越大,人脸质量通常越好。前端可以根据该值做进一步的过滤')
    landmarks: List[List[List[float]]] = Field(..., example=[landmarks_example],
                                               title='人脸图像的关键点坐标',
                                               description='每个人脸有关键点共5个,注意这里的坐标是相对于输入图像的。坐标格式:[[x1, y1], [x2, y2], [x3, y3], [x4, y4], [x5, y5]]')

class RegParams(BaseModel):
    """识别参数"""
    faces: List[str] = Field(..., title='待识别的人脸图像列表',
                             description='base64格式')

class RegItem(BaseModel):
    """单个人脸图像的识别结果"""
    embedding: List[float] = Field(..., title='人脸特征向量',
                                   description='每个人脸对应一个特征向量')

前面的文件也介绍过,每一个参数都应该有一些必要的属性,如:

  • 默认值:如果在请求的时候,不传该值,则会使用默认值。如果是三个点,则表示会该参数是必须的。
  • 样例值example:这个值只是展示在界面上,注意和默认值区分。
  • 标题title
  • 描述description
  • 还有一些参数校验的,如限制取值范围的:ge, le, gt, lt等

我们看一下人脸检测接口输入参数的文档效果:

其他的部分就不展示了。

05 小结


FastAPI最大的好处是能保持代码与文档的一致性,根据历史经验,以前文档和代码分离的时候,经常出现文档和代码不一致,文档明明这样说,但是请求接口时却掉进了坑里。

所以,我们在使用FastAPI来部署接口的时候,应该要养成习惯,在代码里写好文档。

0 人点赞