构建高效、精准的人脸识别系统——RetinaFace、FaceNet和MySQL基础上的实践与总结

写在前面:本博客仅作记录学习之用,部分图片来自网络,如需使用请注明出处,同时如有侵犯您的权益,请联系删除!

前言

本文讨论的人脸识别方法主要分为三个部分,其中第一个部分是检测网络RetinaFace,用于检测人脸;第二个部分是Facenet,用于生成身份识别所需的特征,第三部分是基于Mysql的人脸特征存储与识别。该方法具有精度较高、鲁棒性强、实时性好、数据库管理方便的特点。

本文将从基础准备、人脸检测、人脸处理、身份识别、以及数据库等方面进行说明,内容有点长,请耐心看完。

1. 基础准备

基础内容则是数据库Mysql和训练网络需要是数据集。

1. 数据库的下载

  • 点击此处下载链接即可前往官网,如下界面:

在这里插入图片描述

  • 选择合适的版本下载即可,当前是8.x版本,5.x系选择archives
    在这里插入图片描述

  • 不用注册登录,点击最下方just start download
    在这里插入图片描述

  • 查看以前的版本,多是5.x版本。在这里插入图片描述

  • 下载的结果是.msi文件。下载的结果


2. 数据库的安装

  • 双击.msi文件运行。
    在这里插入图片描述
  • 出现下面这个警告,没有影响,继续即可。

在这里插入图片描述


3. 人脸数据集

这里主要是提供了常见的几个人脸数据集,以及部分下载的链接,可根据所需要的图像大小进行裁剪到合适的尺寸。

FFHQ数据集:全称Flickr-Faces-Hight-Quality,FFHQ是一个高质量的人脸数据集,包含1024x1024分辨率的70000张PNG格式高清人脸图像,标签具有脸部关键点的信息。

FFHQ百度网盘: 仅包含图片,约90G,并非2TB的整个数据集。

LFW数据集:Labelled Faces in the Wild,是一个包含人脸图像的公共数据库,它由美国马里兰大学的Gary B. Huang、Manu Ramesh和Tamara Berg等人创建。该数据集收集了数千张摄于日常生活和互联网上的800多个人物的照片,这些人来自世界各地,并涵盖多个年龄段和种族。LFW数据集被广泛用于测试人脸识别算法的性能,在该任务中取得了相当高的准确率。

G-Lab人脸生成:某一研究生所做的网站,多是网盘的连接,部分截图如下。
在这里插入图片描述


2. 人脸检测

人脸检测无非就是通用检测方法和专职检测方法两种。

  • 通用检测方法:通用检测方法适用于多类目标检测,在人脸检测领域主要有基于二阶段的RCNN系列和基于一阶段的YOLO系列。二阶段方法如Faster R-CNN、Mask R-CNN等先通过Region Proposal Network(RPN)在图片中生成一系列候选框(region proposals),再对这些 proposals 进行分类,从而检测出人脸和其他物体。一阶段方法如YOLO系列相对较简单而快速,它将检测过程分为分类和位置回归两个步骤,从而实现了 end-to-end 的检测。

  • 专职检测方法:另一方面,面向特定问题进行优化的人脸检测方案则更加精细和全面。例如专注于人脸检测的RetinaFace网络结合了多尺度金字塔图片特征以及级联多层级的人脸检测策略模块,加上精心设计的思想能够通过有效减少重复特征条件下的FP(false positive)误报率,显著提升人脸检测的准确性和速度。适用于特定场景或具有特殊需求的专职人脸检测方法对于某些项目任务十分必要,例如安防领域、广告推荐领域等

如果单纯只做人脸检测,后者往往会更合适一点,为什么?这还得从他们的应用场景和方法谈起。

  • 应用场景通用检测方法适用于需要同时检测多种物体的场景,例如目标检测领域,需要适应不同种类物体并对它们进行分类和定位。专职人脸检测方法更加注重解决针对人脸的特殊性问题,例如人脸尺度变化、姿态变化、光线变化等。对于具有明确场景需求或需高精度人脸检测的项目任务,如人脸识别、安防视频监控等。通用检测方法在对于人脸检测,特别是那些人脸相对其他物体突出、占据较大部分图片区域的情况下,通用检测方法可能会面临一些挑战。专职人脸检测方法拓展到通用检测领域效果t也稍逊一筹。

  • 网络结构:两阶段的通用检测方法,一方面使用ROI(Region of Interest) 策略引入了较多的计算复杂度,实时性不高。单阶段的通用检测方法也容易出现漏检、误检等问题。对于人脸检测系统来说,实时性要求较高,准确率也很重要。通用的网络直接为了应对多类别的检测,结构相对复杂,实时性会差一些,即便是One-stage的方法。

  • 计算资源:都已经有专职人脸检测方法了,为什么还要用通用的检测方法来重新训练呢,即便是有预训练的主干网络,也还是的消耗资源。直接用专职人脸检测方法不香吗?现在不就流行白嫖吗,嘿嘿!

总之,相比之下,专职人脸检测方法更加注重解决针对人脸的特殊性问题,专职人脸检测方法包括Retinaface 可以提供更好的表现。此外,Retinaface 还优化了模型结构以及采用 Focal Loss 等策略,大幅降低了非人脸对象误报率,提供了兼顾准确与速度的人脸检测性能,下文简单的介绍了人脸检测的两种方法。


1.2 专职人脸检测方法

RetinaFace

RetinaFace:《RetinaFace: Single-stage Dense Face Localisation in the Wild》(PDF)(Code
网络结构

  • Retinaface是一个基于SSD网络结构扩展得到的检测器,其主要由两个部分组成:特征提取网络和回归网络。其中特征提取网络接受输入图像,然后生成特征图,在特征图上执行分类和回归操作以预测具有不同长宽高比的候选框的位置和置信度得分。

  • 特别地,Retinaface为解决人脸检测过程中问题,采用了一个精心设计的多层次检测框处理机制,使得检测到的框可以针对性地进行调整,更好地适应各种尺寸比例的人脸。采用了金字塔式的特征背景筛选算法来降低背景误检率,提高合格检测率。

  • RetinaFace:在精度和速度上都取得了非常好的表现,能够快速、准确地检测出图像中的人脸,并且可以识别侧面脸和遮挡脸等复杂情况,大大提高了人脸识别的鲁棒性。

  • Retinaface在通过主干特征多个不同深度的特征提取网络获得特征,以特征金字塔进行特征的融合。一般使用mobilenet0.25和Resnet50作为主干,前者具有深度可分离卷积故具备更少的推理时间,精度略差,后者精度较高但是无法在cpu上做到实时。

    https://github.com/deepinsight/insightface/tree/master/RetinaFace.


1.2 通用人脸检测方法

yolox

YOLOX:《YOLOX: Exceeding YOLO Series in 2021》PDF)(Code)
在这里插入图片描述
图片参考自:睿智的目标检测53——Pytorch搭建YoloX目标检测平台

  • yolox采用了一种类似于金字塔式的网络设计——SPP,其网络结构主要由特征提取网络、特征融合网络、卷积头和后处理组成。其中特征提取网络使用CSPNet进行通道空间分离的特征提取,有效地减少了计算量,降低了内存消耗。同时,特征融合网络采用了跨级别路径连接的方法,在多尺度上提升了检测精度。

  • 此外,yolox检测头不同于yolo3-5的结构,将分类和回归的任务分开为两个支路,减少两种之间的耦合,详细信息可参考该论文的开源代码。
    在这里插入图片描述


1.3 检测性能

由于RetinaFace的训练集需要五点标注,如使用自己的训练集需要大量的人工标注,并且能切只能检测人脸,缺乏扩展的能力。因此这里使用了yolox-tiny作为检测网络,在自制数据集上测试的结果如下,使用inter® i5-6500 CPU@3.20GHz。
在这里插入图片描述


3.人脸处理

检测网络是获取人脸的工具,获取的结果难以保证,因此人脸预处理是必需的,因为在实际的场景,由于摄像头聚焦,抖动,光线等因素是的获得的人脸图片具有不同的干扰。此外还由于人脸存在不同的姿势,为提高识别率,故需要此步骤。

3.1 关键点坐标获取

人脸对齐即获取脸部关键点进行人脸的旋转,使得识别时脸部始终正脸,对于侧脸可使用GAN生成正脸的图像。最直接的对齐方法就是获得脸部组件的坐标,计算其中的几何角度,并旋转对应角度实现对齐或者直接使用仿射变换将图片旋转到模板样式。

3.1.1 RetinaFace

在这里插入图片描述

  • 由于RetinaFace的检测结果是具有五官的坐标,这得益于五点标注的的数据集。因此只需要将检测的结果返回,计算两眼、嘴角与水平的夹角进行旋转,同时对其进行平移,保证鼻尖位于中间。
  • 为保持较好的实时性,主干网络选择了mibilenet,如右图所示,五官的位置并没那么准确。同时在较大的人脸上,眼球的定位很难定位到眼睛的中央位置,因此使用网络检测五官坐标是不准确的。实际对齐的效果很一般,原因在于五个点很难代表面部组件的位置,并且也快速著称的RetinaFace,在精度上差一些,尤其是使用mobilenet0.25,对齐效果相对更差一点。下面是RetinaFace返回的五官坐标。

def dete_face(image):# 检测人脸,返回每张脸的关键点坐标
    pic = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    _, locations, lmk_points = retinaface.detect_image(pic)
    return image, locations, lmk_points
    
def get_key_points(pic_path, savepath): # 检测模板人脸,如ffhq数据集,统计关键点坐标,并写入.npy文件中
    '''
    @param pic_path: FFHQ IAMGES
    @param savepath: KEY POINTS.NPY SAVE ROOT
    @return: None
    '''
    pics = os.listdir(pic_path)
    key_points = []
    (x1, y1) = (0, 0)
    (x2, y2) = (0, 0)
    (x3, y3) = (0, 0)
    (x4, y4) = (0, 0)
    (x5, y5) = (0, 0)

    for index, pic in enumerate(tqdm(pics)):
        image = cv2.imread(os.path.join(pic_path, pic))
        _, _, lmk_points = dete_face(image)
        assert len(lmk_points) > 0, 'No faces'
        (x1, y1) = (lmk_points[0][0][0] + x1, lmk_points[0][0][1] + y1)
        (x2, y2) = (lmk_points[0][1][0] + x2, lmk_points[0][1][1] + y2)
        (x3, y3) = (lmk_points[0][2][0] + x3, lmk_points[0][2][1] + y3)
        (x4, y4) = (lmk_points[0][3][0] + x4, lmk_points[0][3][1] + y4)
        (x5, y5) = (lmk_points[0][4][0] + x5, lmk_points[0][4][1] + y5)
    
    if y1==y2:
        (x1, y1) = (x1 / len(pics), y1 / len(pics))
        (x2, y2) = (x2 / len(pics), y2 / len(pics))
        (x3, y3) = (x3 / len(pics), y3 / len(pics))
        (x4, y4) = (x4 / len(pics), y4 / len(pics))
        (x5, y5) = (x5 / len(pics), y5 / len(pics))
    else:
        y1 = (y1+y2)//2
        y2 = y1
        (x1, y1) = (x1 / len(pics), y1 / len(pics))
        (x2, y2) = (x2 / len(pics), y2 / len(pics))
        (x3, y3) = (x3 / len(pics), y3 / len(pics))
        (x4, y4) = (x4 / len(pics), y4 / len(pics))
        (x5, y5) = (x5 / len(pics), y5 / len(pics))
    key_points.append((x1, y1))
    key_points.append((x2, y2))
    key_points.append((x3, y3))
    key_points.append((x4, y4))
    key_points.append((x5, y5))

    np.save(f"{savepath}", key_points)

3.1.2 dlib关键点标定

上述的方法基本可以实现五官的定位,但一个点定位一个区域的位置是不准确的,对齐很难统一到相同的格式,不利于人脸识别,基于此,把目光转向了更多关键点的dlib上,一般68个关键点或者5点,如下图所示。采取模板的对齐样式,具体实现为:使用统一格式数据集,可可采取FFHQ数据集格式作为目标格式,使用关键点检测统计所需要的关键点的位置,并保存为期望的模板。对检测到的图像获取其需要的关键点,通过仿射变换,将检测的图像旋转对齐到模板的位置。

                                    68点                                          5点

在此基础上,一个组件(区域)拥有多个关键点进行标定,对齐的关键点选择更加多样,即使存在个别关键点不正确,也对总体的结果影响不大。下面是关于获得五点坐标和保存为模板的代码。

def get_template(src_path, save_name=None):
    arr = np.zeros((5, 2))
    detector = dlib.get_frontal_face_detector()
    dsp = dlib.shape_predictor('shape_predictor_5_face_landmarks.dat')
    count = 0

    for name in tqdm(os.listdir(src_path)):
        img_path = os.path.join(src_path, name)
        img = cv2.imread(img_path)
        # 图片读取失败,如图片破损
        if img is None:
            print(f"{name} read failed.")
            continue
        else:
            del img
            dlib_img = dlib.load_rgb_image(img_path)
            height, width, channels = dlib_img.shape  # (h, w, c)
            if max(height, width) < 20: continue

            elif max(height, width) > 1000:
                scale = 800 / max(width, height)
                new_width, new_height = int(width * scale), int(height * scale)
                dlib_img = cv2.resize(dlib_img, (new_width, new_height))
                points = get_points(dlib_img, detector, dsp)
            else:
                points = get_points(dlib_img, detector, dsp)
            if points is not None and len(points) == 1:
                count += 1
                arr += points[0]
            else:
                continue
    arr = arr // count
    np.save('example.npy', arr)

def get_points(img, detector, shape_predictor, size_threshold=999):
    dets = detector(img, 1)
    if len(dets) == 0:
        return None

    all_points = []
    for det in dets:
        rec = det
        if rec.width() > size_threshold or rec.height() > size_threshold:
            break
        shape = shape_predictor(img, rec)
        single_points = []
        for i in range(5):
            single_points.append([shape.part(i).x, shape.part(i).y])
        all_points.append(np.array(single_points))
    if len(all_points) <= 0:
        return None
    else:
        return all_points

3.2 仿射变换对齐

仿射变换是将二维平面中的点集映射到另一个二维平面上,保持了原始图形中的直线和平行线在变换后仍为直线和平行线的特性。利用仿射变换进行对齐的基本原理是:首先在两幅图像中选择一些对应的匹配点,然后根据这些匹配点计算出仿射变换矩阵,最后利用该矩阵对两幅图像进行变换,使它们达到对齐的效果。常常采用以下步骤来实现对齐:

  • 获得待对齐图像和模板特征点坐标。
  • 根据特征点坐标,运用数学公式计算仿射变换矩阵,该矩阵包含旋转、缩放、平移等变换信息。
  • 利用计算得到的仿射变换矩阵对图像进行变换,实现对齐,即将两张图像中相应的特征点重合在一起,达到精准对齐目的。
    下面是两种方法实现对齐的代码,方法如上述描述一致。

3.2.1 RetinaFace对齐

def detect_and_align_faces_retina(img, face_detector, template_path, template_scale=1, size_threshold=999): # 检测任意图像,将检测到的人脸以上述npy文件方式对齐
    align_out_size = (512, 512)
    ref_points = np.load(template_path) / template_scale
    img, locations, lmk_points = face_detector(img)
    if len(locations) < 0:
        print('No faces detected')
        return [None, None]
    else:
        aligned_faces = []
        tform_params = []
        for i, location in enumerate(locations):
            if abs(location[0][0]-location[1][0]) > size_threshold or abs(location[0][1]-location[1][1]) > size_threshold:
                raise ValueError('Face is too large')
            single_points = []
            for j in range(5):
                single_points.append([lmk_points[i][j][0], lmk_points[i][j][1]])
            single_points = np.array(single_points)
            tform = trans.SimilarityTransform()  # 相似变换
            tform.estimate(single_points, ref_points)
            tmp_face = trans.warp(img, tform.inverse, output_shape=align_out_size, order=3)
            aligned_faces.append(tmp_face*255)
            tform_params.append(tform)
        return [aligned_faces, tform_params]


def save_imgs(img_list, save_dir): # 保存列表里面的图像
    for idx, img in enumerate(img_list):
        save_path = os.path.join(save_dir, '{:03d}.jpg'.format(idx))
        io.imsave(save_path, img.astype(np.uint8))
if __name__ == "__main__":# 主函数,检测任意图像,将检测到的人脸以上述npy文件方式对齐后保存
    save_lq_dir = 'img/face'
    os.makedirs(save_lq_dir, exist_ok=True)
    img = 'gg.jpg'
    aligned_faces, tform_params = detect_and_align_faces_retina(img, dete_face,  template_scale=1, size_threshold=999)
    save_imgs(aligned_faces, save_lq_dir)

3.2.2 Dlib对齐

detector = dlib.get_frontal_face_detector()
sp = dlib.shape_predictor('shape_predictor_5_face_landmarks.dat')
def align(pic_list):
    align_faces = []
    out_size = (112, 112)
    template_scale = 1
    template_path = 'example.npy'

    reference = np.load(template_path) / template_scale
    for index, pic in enumerate(pic_list):
        points = get_points(pic, detector, sp)
        if points is not None:
            for idx, spoint in enumerate(points):
                tform = trans.SimilarityTransform()
                tform.estimate(spoint, reference)
                M = tform.params[0:2, :]
                crop_img = cv2.warpAffine(pic, M, out_size)
                # crop_img = cv2.cvtColor(crop_img, cv2.COLOR_BGR2RGB)
                crop_img = Image.fromarray(np.uint8(crop_img))
                align_faces.append(crop_img)
        else:
            pass

    return align_faces

3.3 人脸增强

由于直接检测到的人脸,可能受到遮挡、光线以及比较模糊等干扰,使得检测和识别出错。因此在识别前可进一步进行去退化操作,此处称为增强,比如去口罩,去模糊、减少光影影响。训练一个GAN网络应该就能解决,留给未来的自己。下面简单展示了光影对人脸识别的影响,检测框和识别结果都在变化,人的姿态基本没变化。

光影对人脸检测和识别的影响


4.身份识别

4.1 Facenet和Dlib对比

FaceNetdlib 都是常用的人脸识别工具之一,而且在学术界和工业界都取得了不俗的成绩。然而,从算法精度、处理速度和计算资源等方面综合考虑,目前 FaceNet 的人脸识别效果相对较高。

  • FaceNet : 是由 Google 提出的一种基于深度卷积神经网络的人脸识别模型,通过生成多通道的人脸特征向量进行人脸识别,并采用三元组损失函数优化模型训练过程。
    • 优点:该模型能够有效地解决同一人脸不同状态、光照、角度和表情等因素的干扰,并且在一些公开数据集上实现了与或超越人类水平的识别准确度,具备很好的泛化能力,能够对新样本进行良好的识别。可以通过训练增强算法效果。
    • 弊端:对计算资源和时间消耗较大,尤其是在训练较大网络时,需要较长时间训练和大量的计算资源支持。不太适合小规模数据集,容易出现过拟合现象。对输入图像的尺寸和质量要求较高。

  • Dlib 人脸识别库基于载入训练好的支持向量机(SVM)模型进行分类并提取人脸特征点,其计算速度非常快,而且对小规模数据集也能够获得不错的效果,使用较为方便。
    • 优点:处理速度较快,尤其是在设计简单网络时性能表现比较好。对小规模数据集有较好的适应性,容易搭建低复杂度模型,训练效果不错。
    • 弊端:在处理复杂情况(如光照、姿态变化等)时,会出现较大误差。训练过程中需要手工设置参数,对操作者的算法素质要求较高。

在实际应用中,可以根据具体场景和需求合理选择 FaceNet 或 dlib 进行人脸识别任务。相对而言,FaceNet 更加适合大规模数据集下的复杂人脸识别场景,而 dlib 更加适合小规模数据集下简单人脸识别场景。


问:灵魂问题:想不想一劳永逸?
答:什么,你说不想,那就用dlib吧

  • 支持向量机(SVM)你懂吧,找个超平面把每个人都分开,换言之和分类模型没什么区别。因此导致了一旦需要新增一个人的身份,需要对 dlib 进行重新训练。即需要将新的人脸数据集交给 dlib 进行重新训练,以更新分类器的判断能力,从而实现新身份的识别需求,很繁琐。

  • FaceNet还不明白?基于深度学习的模型,就是提取特征的,利用多通道特征向量进行人脸识别。因此当有新的人脸加入时,不需要重新训练或者重新标记训练集就可以实现新增身份的识别。只需要使用已有的模型对这些人脸进行检测和提取特征向量,编码后与库中的其他人脸编码进行比对就可以完成身份的识别。但是,为了获取更好的识别精度,可以根据具体情况进行微调阈值。

总之,在面对新增或者删除人员身份的场景下,FaceNet 比 dlib 更加便利一些。此外数据集可选自己制作的数据,也可选取公开数据集,将身份和对应图像进行对应即可,对于训练LFW数据集即可。


4.2 识别网络

Facenet:《FaceNet: A Unified Embedding for Face Recognition and Clustering》(PDF)

                                                      正负样本说明
  • 采用深度卷积神经网络(CNN)学习将图像映射到欧式空间。空间距离直接和图片相似度相关:同一个人的不同图像在空间距离很小,不同人的图像在空间中有较大的距离,可以用于人脸验证、识别和聚类。在800万人,2亿多张样本集训练后,FaceNet在LFW数据集上测试的准确率达到了99.63%

                                                            损失介绍
														结构流程
  • 在 Inter® Core™ i5-6500 CPU@3.20GHz 进行实验,主干网络确定为 mobilenet,因为深度可分离卷积的计算量小于传统的卷积,有利于提高运算速度。在 LFW 数据集上试的准确率以及计算的最佳阈值,以及 ROC 曲线如下:
    在这里插入图片描述
    在这里插入图片描述

      														ROC曲线
    

4.3 识别性能

在这里插入图片描述

由表 3 可知,在固定的视频帧大小条件下,检测的帧率取决于两者的配合,从表 1、表 2 清晰的看出 facenet 的识别速度是远大于 Yolox-tiny 的检测速度的,在 Yolox-tiny 输入较小时,facenet 的输入大小对最后的实时性有着相对较大的影响,但是随着 Yolox-tiny 的输入的进一步加大,最后的帧率受到 facenet 的影响几乎不变,因此改善性能的方法可考虑使用更加轻量级的检测网络或者在此基础上对网络进行剪枝或者蒸馏以提高实时性。

5.数据库

数据库在人脸识别中具有非常重要的作用。数据库中存储了已知的人脸图像和与之对应的身份信息,这些信息是人脸识别系统进行比对和验证的基础。对于一个人脸识别系统而言,数据库的品质和规模会直接影响其性能和准确度。

在实现人脸识别系统时,需要在数据库中建立一个完整且精细的人脸特征库。特征库可根据专业算法、手工标注等多个方面进行构建,其中存储着不同姿态、表情、光照、环境下的人脸图像等丰富信息。

在系统的应用过程中,当新出现一个人脸图像时,系统将该图像与数据库中的人脸特征进行比对和匹配,最终确定其身份。因此,数据库的质量越高、人脸数量越大,人脸识别系统的判别准确率也就更高。同时,数据库还需要随着时间不断更新,以适应新的人脸识别需求。

5.1 数据库基础

5.1.1 数据类型

想要建立表,需要了解基本的数据类型,下面是mysql整型bigint、int、mediumint、smallint 和 tinyint的语法介绍

    • tinyint:1个字节,有符号的范围是 [-2^7, 2^7 - 1],无符号的范围是 [0, 2^8 - 1]整型数据。
    • smallint:2个字节,有符号的范围是[-2^15, 2^15 – 1]的整型数据,无符号的范围是 [0, 2^16 - 1]。
    • mediumint:3个字节,有符号的范围 [-2^23, 2^23 - 1] ,无符号的范围是 [0, 2^32 - 1]。
    • int:4个字节 ,有符号的范围[-2^31, 2^31 - 1] 的整型数据,无符号的范围[0, 2^32 - 1]
    • bigint: 有符号范围为 [-2^63, 2^63 - 1],无符号范围为 [0, 2^64 - 1]。
    • float:单精度浮点数,范围在 10^-38 至 10^38 之间。
    • double (或 real):双精度浮点数,范围在 [10^-308, 10^308 ]之间。

  • char 和 varchar 都是 MySQL 数据库中的字符类型,有以下不同之处:

    • 存储方式:char 类型的数据是固定长度的,无论实际存储的字符串长度是否达到了定义的长度,占用指定长度的存储空间;而 varchar 类型的数据则是可变长度的,只占用实际存储的字符串长度所需的存储空间。
    • 查询和比较效率:由于 char 类型在存储时占用固定的存储空间,相同长度的字符数据在查询和比较方面的效率通常高于 varchar。但如果存储的数据比较长且不均匀(即某些数据长度很短,某些数据长度很长),使用 varchar 可能会更加节省空间。
    • 存储空间:char 类型的存储空间是固定的,与定义的长度相关;而 varchar 类型的存储空间,则取决于存储的实际数据长度。

综上,需要存储长度固定、查询和比较频繁的字符串数据,使用 char 类型可能会更加合适;如果需要存储长度可变、节省空间的字符串数据,使用 varchar 类型则更为适合。


5.1.2 常见关键字

在 MySQL 数据库中,有一类属性关键字,可以用于指定表中某列的唯一性和不能为空。常见的包括以下几种:

  • UNIQUE:用于创建一个唯一约束,确保表中该列的所有值都是唯一的。

  • NOT NULL:用于指定该列不能为 NULL 值。

  • PRIMARY KEY:用于将该列指定为主键列,具有唯一性和非空性约束。

  • FOREIGN KEY:用于创建外键约束,使该列与另一个表中的某个列产生关联关系。

这些关键字可以单独或者组合使用,以实现更多复杂的约束要求。例如,在创建表时,可以将 UNIQUE 和 NOT NULL 结合使用,从而创建一个既具有唯一性,又具有非空性的列。在与其他表进行关联时,可以通过使用 FOREIGN KEY 技术来建立正确的业务逻辑关系。


5.1.2 基础指令

数据库表的基础指令:

  • CREATE TABLE:用于创建新的数据库表。

  • DROP TABLE:用于删除现有的数据库表。

  • ALTER TABLE:修改已存在的数据库表的结构,如添加、修改或删除列、添加约束、重命名表等。

  • SELECT:用于从表中查询数据。

  • INSERT INTO:将数据插入到表中。

  • UPDATE:更新表中的数据。

  • DELETE FROM:从表中删除数据。

  • TRUNCATE TABLE:快速删除表中的所有数据,常用于清空表数据。

  • GRANT:赋予用户或用户组对数据库表的某些权限。

  • REVOKE:撤消用户或用户组对数据库表的某些权限。

  • SHOW CREATE TABLE:查看指定表的创建语句。

  • DESCRIBE 或 DESC:查看表的结构信息。

  • USE:选择要操作的数据库。

  • alter table tmp1 auto_increment=1:设置主键自增起点


在 MySQL 数据库中,DELETE FROM 和 TRUNCATE TABLE 都是常见的数据表清除操作,二者的区别如下:

  • DELETE FROM 可以带上 WHERE 子句,可以根据指定条件删除部分记录;TRUNCATE TABLE 则无法使用 WHERE 子句,会将整张表所有的记录全部清除。

  • DELETE FROM 操作属于 DML 操作(Data Manipulation Language),每次删除一条或多条记录时都需要写入 redo log、binlog 等文件中,并且占用相应的系统资源。同时,由于删除操作只删除了表中的某些行,可能会导致表碎片问题,从而影响到系统性能。TRUNCATE TABLE 则属于 DDL 操作(Data Definition Language),它直接清空整个表,不需要写入日志文件,因此执行速度通常比 DELETE 快很多。

  • 在执行 DELETE FROM 后,表的自增长 ID 是不会重置的,因此我们仍然可以向表中插入新的数据,并且新纪录的ID 会依次递增;而当使用 TRUNCATE TABLE 清空表中的记录时,自增长 ID 会被重置为默认值。

综上所述, DELETE 操作更加灵活,支持条件删除和回滚操作,但是相对而言也会有更大的性能开销和表碎片问题;而 TRUNCATE 操作执行速度更快,但是无法满足特定的删除需求,并且清空表后 ID 会被重置。因此,在具体使用时,可以根据场景和业务需求选择合适的操作方式来达到最优化的效果。


5.2 简单操作示例

  • 登录、展示数据库以及田间和删除数据库。
    在这里插入图片描述

  • 建立表格,设置主键
    在这里插入图片描述
    在这里插入图片描述

  • 建立一个新人表格,id设置主键,以及性别、年龄、特征字符串。因为姓名可能重名,但是根据其他信息基本上就能唯一对应一个人的身份。
    在这里插入图片描述

  • 建立一个表格与真实人名的对应表,id设置主键,以及表名,人名。
    在这里插入图片描述

5.3 身份编码解码及存储

此处使用 base64 编码,将numpy数组转化为字符串以便于存入数据库中,下面是大致的流程:

_, locations, _ = retinaface.detect_image(image) #检测人脸

align_faces = align(locations) #对齐人脸
enc = facenet.encode_face(align_faces[0]) #得到人脸特征向量

# 特征向量编码,将128维特征向量编码为字符串,使用 base64 编码。base64 是一种将二进制数据转换成 ASCII 码字符的编码方式,适用于在需要传输或储存二进制数据时使用。
feature_bytes = enc.tobytes()
encoded_feature_str = base64.b64encode(feature_bytes).decode('ascii')

#二进制数据解码,通过 base64.b64decode() 方法,可以将 base64 字符串解码成 bytes 对象的 decoded_feature_bytes。接着再使用 NumPy 的 frombuffer() 方法,将 bytes 对象直接转换成 Numpy 数组 decoded_feature_array。
decoded_feature_bytes = base64.b64decode(encoded_feature_str)
decoded_feature_array = np.frombuffer(decoded_feature_bytes, dtype=np.float32)

# 存储
# 为一个身份添加新表
ret= Identity_cur.add_name_table(names[i])
# 向新表写入数据
Identity_cur.write_info_into_table(names[i], 'm', 30, encoded_feature_str)
# 向表名和实际人名的表写入新表的对应人名
Identity_name_cur.add_Identity_name_Data(f"p_{names[i]}", names[i])

5.4 优化查询

  • 原识别流程:将检测到的所有人脸,依次和数据库中的表的内容逐一对比,下面是大致的流程:
database = Identity_cur.get_all_table_data()#加载数据
img ='img_path'
image = cv2.imread(img)
if image is None:
    print('Open Error! Try again!')
    continue
else:
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    _, crop_faces, locations = retinaface.detect_image(image) #检测人脸
    align_faces = align(crop_faces) #对齐人脸
    for i, pic in enumerate(align_faces): # 每张脸逐一和database 数据进行匹配
        pro_feat = facenet.encode_face(align_faces[i]) # 编码人脸
        pre_name = None
        min_score = 10
        for _, one_table in enumerate(database):# 每张脸逐一和database中的表数据进行匹配
            name = one_table[0]
            tuple_feats = one_table[1]
            for j, column in enumerate(tuple_feats):# 每张脸逐一和database中的表数据中每一行进行匹配
                encoded_feature_str = column[-1] # 特征编码数据
                decoded_feature_bytes = base64.b64decode(encoded_feature_str) # 特征解码
                decoded_feature_array = np.frombuffer(decoded_feature_bytes, dtype=np.float32)
                l1 = np.linalg.norm(pro_feat - decoded_feature_array)# 计算距离
                if l1 < min_score and l1 < 1: # 获得分数最低的表名
                    min_score = l1
                    pre_name = name
        if pre_name != None: # 获得对应表名的人名
            p_name = Identity_name_cur.get_tname_panme(pre_name)
  • 优化识别流程:将检测到的所有人脸,建立线程池,多线程依次和数据库中的表的内容逐一对比,下面是大致的流程:
def decode_match(face, database, location):#编码和逐一和数据库中的表的内容对比
    min_score = 10
    pre_name = None
    p_name = None
    pro_feat = facenet.encode_face(face)

    for _, one_table in enumerate(database):
        name = one_table[0]
        tuple_feats = one_table[1]
        for j, column in enumerate(tuple_feats):
            encoded_feature_str = column[-1]
            decoded_feature_bytes = base64.b64decode(encoded_feature_str)
            decoded_feature_array = np.frombuffer(decoded_feature_bytes, dtype=np.float32)
            l1 = np.linalg.norm(pro_feat - decoded_feature_array)
            if l1 < min_score and l1 < 0.9:
                min_score = l1
                pre_name = name
    if pre_name != None:
        p_name = Identity_name_cur.get_tname_panme(pre_name)
    return p_name, location
database = Identity_cur.get_all_table_data()
img = 'img_path'
image = cv2.imread(img)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
_, crop_faces, locations = retinaface.detect_image(image)
align_faces = align(crop_faces) # 检测和对齐

if len(align_faces)>0: #检测到脸
    t1 = time.time()
    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:  # 创建一个最大容纳数量为8的线程池
        futures = []
        for id, face in enumerate(align_faces):  # 通过submit提交执行的函数到线程池中
            future = executor.submit(decode_match, face, database, locations[id])
            futures.append(future)

        results = []
        for future in concurrent.futures.as_completed(futures):  # 判断任务是否结束,结束后给主线程返回结果
            result = future.result()  # 用 result() 获取返回结果
            results.extend(result)  # 整合为列表

此处展示了一人识别、多人识别的情形,可以看到优化的结果在人数较少的情况下保持相当的效果,在人数较多时大概减少了约1/3的识别时间,补充以下数据库的内容,一共四个人本人、奥巴马、张学友,以及上述视频的mary,各自的内容数为1、2、3、1条,意味着每张人脸需要与其计算7次距离以确定身份。

人数优化前(秒)优化后(秒)
10.03590.0339
100.32510.2174
301.10890.7240

6. 识别效果

此处展示了上述表格中的识别结果,检测框在Retinaface结果上做了调整,打码为了不被和谐。
在这里插入图片描述


致谢

欲尽善本文,因所视短浅,怎奈所书皆是瞽言蒭议。行文至此,诚向予助与余者致以谢意。

参考文献

  1. 人脸检测之Retinaface算法:论文阅读及源码解析
  2. 睿智的目标检测53——Pytorch搭建YoloX目标检测平台
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值