一、开源C++跨平台人脸检测

仅用CPU就能跑到1000FPS

项目地址:https://github.com/ShiqiYu/libfacedetection

人脸检测可广泛应用于人机交互、安防监控、社交娱乐等领域,具有很强的实用价值,因此受到广泛关注与研究。在众多人脸检测方法中,使用卷积神经网络进行检测是目前较为流行的方法之一。然而在我们使用别人开源的项目时经常需要安装各种各样的依赖环境,不同的依赖环境在不同硬件平台或操作系统中支持程度不一样,增加了项目跨平台迁移的难度。

本文介绍的是一个使用卷积神经网络进行人脸检测的开源项目,它最大的亮点是能够在所有支持 C/C++的平台上编译运行。作者将预训练的 CNN 模型转换为静态变量后储存到了 C 文件里,使得该项目不需要任何其他依赖项(当然 OpenCV 还是需要的),仅仅只需要一个 C++编译器,就能在任何一个平台甚至嵌入式系统上编译并运行该项目。

51c视觉~CV~合集8~※~人脸检测_视觉

尤其吸引人的是该项目使用 C++编写且支持 AVX2,在 i7 的 CPU 上就能跑出丧心病狂的 1000FPS!下图为项目作者给出的检测效果示例。


51c视觉~CV~合集8~※~人脸检测_人脸检测_02

可以看到该项目不仅检测速度非常快,检测精度也很不错。于是,机器之心也上手测试了一番。

项目实测

我们在 Ubuntu 18.04 下测试这个人脸检测项目的效果。首先先使用一张相对简单的合照进行测试,其总共有 15 个人,分辨率为 970x546,检测结果如下图所示:

51c视觉~CV~合集8~※~人脸检测_OpenCV_03

可以看到它准确的识别出了图片中所有的人像,在 CPU 上仅耗时 133ms 且置信度都为 99%。下图为加上 bounding box 后的检测效果图:


51c视觉~CV~合集8~※~人脸检测_人脸检测_04

下面我们提高一下难度,使用一张开挂民族的图片测试一下效果。它也几乎把所有火车头上正面的面孔都识别出来了,火车车身上的人像没有识别出来,可能是因为那些人像实在是太小太密集的缘故。


51c视觉~CV~合集8~※~人脸检测_人脸检测_05

编译过程

该项目编译需要 OpenCV,如果缺少 OpenCV 在使用 cmake 生成 makefile 时会报如下错误:


51c视觉~CV~合集8~※~人脸检测_人脸检测_06

我们首先使用 wget https://github.com/opencv/opencv/archive/3.4.0.tar.gz 下载 OpenCV 3.4.0 的源码,紧接着安装编译 OpenCV 需要的相关依赖项:

apt-get install build-essential
apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-devadd-apt-repository "deb http://security.ubuntu.com/ubuntu xenial-security main"
apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev


安装完依赖项之后我们将 OpenCV 的压缩包解压到当前目录下:

tar xvzf 3.4.0.tar.gz

为了避免在编译时将源码文件弄乱,我们新建一个名为 linuxidcbuild 的文件夹,在其下进行 OpenCV 的编译与安装:

mkdir linuxidcbuild
cd linuxidcbuild/
cmake ../opencv-3.4.0 -DWITH_GTK_2_X=ON -DCMAKE_INSTALL_PREFIX=/usr/local


在生成 makefile 的过程中可能会出现卡在下图的情况,


51c视觉~CV~合集8~※~人脸检测_视觉_07

这是由于 ippicv 下载不成功导致的,在以下链接手动下载系统对应的 ippicv 版本:https://github.com/opencv/opencv_3rdparty/tree/ippicv/master_20170822/ippicv

之后使用 vim opencv-3.4.0/3rdparty/ippicv/ippicv.cmake 修改 OpenCV 的编译配置文件,将如下内容

"https://raw.githubusercontent.com/opencv/opencv_3rdparty/${IPPICV_COMMIT}/ippicv/ "

修改为我们刚存放 ippicv 的目录。再重新执行一次 cmake ../opencv-3.4.0 -DWITH_GTK_2_X=ON -DCMAKE_INSTALL_PREFIX=/usr/local,当出现下图的内容时说明成功生成了 makefile。


51c视觉~CV~合集8~※~人脸检测_人脸检测_08

接下来我们执行 make 进行对 OpenCV 的编译:


51c视觉~CV~合集8~※~人脸检测_OpenCV_09

这里编译的过程相对比较漫长,大约需要 20 分钟左右,消耗时间根据不同电脑配置会有所区别。当出现下图所示内容时说明编译完成。


51c视觉~CV~合集8~※~人脸检测_视觉_10

之后使用 make install 进行安装,


51c视觉~CV~合集8~※~人脸检测_视觉_11

安装成功后会出现如图所示界面:


51c视觉~CV~合集8~※~人脸检测_视觉_12

以上即完成对 OpenCV 的安装。安装完成后使用 vim /etc/ld.so.conf.d/opencv.conf 对其进行配置,在文件中加入/usr/local/lib 后保存退出。

使用 vim /etc/bash.bashrc 添加环境变量,在文末加入 export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig,使用 source /etc/bash.bashrc 让新的环境变量生效。这样就配置好了 OpenCV,之后使用如下命令编译该项目:

git clone https://github.com/ShiqiYu/libfacedetectioncd libfacedetectionmkdir build; cd build; cmake ..; make

项目编译完成后会在 build 目录下生成对应可执行文件,可使用如下命令运行:

./detect-image-demo ../images/test.png

虽然看似操作非常复杂,但实际上主要就是编译 OpenCV,其它模块都可以直接在项目中完成编译。最后,就可以愉快地使用这个极速人脸检测模型了。







二、人脸识别及检测x1

人脸识别作为一种生物特征识别技术,具有非侵扰性、非接触性、友好性和便捷性等优点。人脸识别通用的流程主要包括人脸检测、人脸裁剪、人脸校正、特征提取和人脸识别。人脸检测是从获取的图像中去除干扰,提取人脸信息,获取人脸图像位置,检测的成功率主要受图像质量,光线强弱和遮挡等因素影响。下图是整个人脸检测过程。

51c视觉~CV~合集8~※~人脸检测_人脸检测_13

二、识别检测方法

传统识别方法
(1)基于点云数据的人脸识别
(2)基于面部特征的3D人脸识别

深度学习识别方法
(1)基于深度图的人脸识别
(2)基于RGB-3DMM的人脸识别
(3)基于RGB-D的人脸识别

方法

关键点定位概述
一般人脸中有5个关键点,其中包括眼睛两个,鼻子一个,嘴角两个。还可以细致的分为68个关键点,这样的话会概括的比较全面,我们本次研究就是68个关键点定位。

51c视觉~CV~合集8~※~人脸检测_视觉_14

 上图就是我们定位人脸的68个关键点,其中他的顺序是要严格的进行排序的。从1到68点的顺序不能错误。

51c视觉~CV~合集8~※~人脸检测_视觉_15

解析

使用机器学习框架dlib做本次的项目。首先我们要指定参数时,要把dlib中的68关键点人脸定位找到。设置出来的68关键点人脸定位找到。并且设置出来。

from collections import OrderedDict
import numpy as np
import argparse
import dlib
import cv2

首先我们导入工具包。其中dlib库是通过这个网址http://dlib.net/files/进行下载的。然后我们导入参数。

ap = argparse.ArgumentParser()
ap.add_argument("-p", "--shape-predictor", required=True, help="path to facial landmark predictor")
ap.add_argument("-i", "--image", required=True, help="path to input image")
args = vars(ap.parse_args())

这里我们要设置参数,

--shape-predictor shape_predictor_68_face_landmarks.dat --image images/lanqiudui.jpg。如果一张图像里面有多个人脸,那么我们分不同部分进行检测,裁剪出来所对应的ROI区域。我们的整体思路就是先检测人脸所在的一个区域位置,然后检测鼻子相对于人脸框所在的一个位置,比如说人的左眼睛在0.2w,0.2h的人脸框处。

FACIAL_LANDMARKS_68_IDXS = OrderedDict([
("mouth", (48, 68)),
("right_eyebrow", (17, 22)),
("left_eyebrow", (22, 27)),
("right_eye", (36, 42)),
("left_eye", (42, 48)),
("nose", (27, 36)),
("jaw", (0, 17))
])

这个是68个关键点定位的各个部位相对于人脸框的所在位置。分别对应着嘴,左眼、右眼、左眼眉、右眼眉、鼻子、下巴。

FACIAL_LANDMARKS_5_IDXS = OrderedDict([
("right_eye", (2, 3)),
("left_eye", (0, 1)),
("nose", (4))
])

如果是5点定位,那么就需要定位左眼、右眼、鼻子。0、1、2、3、4分别表示对应的5个点。

detector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor(args["shape_predictor"])

加载人脸检测与关键点定位。加载出来。其中detector默认的人脸检测器。然后通过传入参数返回人脸检测矩形框4点坐标。其中predictor以图像的某块区域为输入,输出一系列的点(point location)以表示此图像region里object的姿势pose。返回训练好的人脸68特征点检测器。

image = cv2.imread(args["image"])
(h, w) = image.shape[:2]
width=500
r = width / float(w)
dim = (width, int(h * r))
image = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

这里我们把数据读了进来,然后进行需处理,提取h和w,其中我们自己设定图像的w为500,然后按照比例同比例设置h。然后进行了resize操作,最后转化为灰度图。

rects = detector(gray, 1)

这里调用了detector的人脸框检测器,要使用灰度图进行检测,这个1是重采样个数。这里面返回的是人脸检测矩形框4点坐标。然后对检测框进行遍历

for (i, rect) in enumerate(rects):
# 对人脸框进行关键点定位
# 转换成ndarray
shape = predictor(gray, rect)
shape = shape_to_np(shape)

这里面返回68个关键点定位。shape_to_np这个函数如下。

def shape_to_np(shape, dtype="int"):
# 创建68*2
coords = np.zeros((shape.num_parts, 2), dtype=dtype)
# 遍历每一个关键点
# 得到坐标
for i in range(0, shape.num_parts):
coords[i] = (shape.part(i).x, shape.part(i).y)
return coords

这里shape_tonp函数的作用就是得到关键点定位的坐标。

for (name, (i, j)) in FACIAL_LANDMARKS_68_IDXS.items():
clone = image.copy()
cv2.putText(clone, name, (10, 30), 
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) # 根据位置画点 
for (x, y) in shape[i:j]:
cv2.circle(clone, (x, y), 3, (0, 0, 255), -1)# 提取ROI区域 
(x, y, w, h) = cv2.boundingRect(np.array([shape[i:j]]))
roi = image[y:y + h, x:x + w]
(h, w) = roi.shape[:2] width=250
r = width / float(w) dim = (width, int(h * r))
roi = cv2.resize(roi, dim, interpolation=cv2.INTER_AREA)
# 显示每一部分 cv2.imshow("ROI", roi)
cv2.imshow("Image", clone)
cv2.waitKey(0)

这里字典FACIAL_LANDMARKS_68_IDXS.items()是同时提取字典中的key和value数值。然后遍历出来这几个区域,并且进行显示具体是那个区域,并且将这个区域画圆。随后提取roi区域并且进行显示。后面部分就是同比例显示w和h。然后展示出来。

output = visualize_facial_landmarks(image, shape)
cv2.imshow("Image", output)
cv2.waitKey(0)

最后展示所有区域。
其中visualize_facial_landmarks函数就是:

def visualize_facial_landmarks(image, shape, colors=None, alpha=0.75):
# 创建两个copy
# overlay and one for the final output image
overlay = image.copy()
output = image.copy()
# 设置一些颜色区域
if colors is None:
colors = [(19, 199, 109), (79, 76, 240), (230, 159, 23), (168, 100, 168), (158, 163, 32), (163, 38, 32), (180, 42, 220)]
# 遍历每一个区域
for (i, name) in enumerate(FACIAL_LANDMARKS_68_IDXS.keys()):
# 得到每一个点的坐标
(j, k) = FACIAL_LANDMARKS_68_IDXS[name]
pts = shape[j:k]
# 检查位置
if name == "jaw":
# 用线条连起来
for l in range(1, len(pts)):
ptA = tuple(pts[l - 1])
ptB = tuple(pts[l])
cv2.line(overlay, ptA, ptB, colors[i], 2)
# 计算凸包
else:
hull = cv2.convexHull(pts)
cv2.drawContours(overlay, [hull], -1, colors[i], -1)
# 叠加在原图上,可以指定比例
cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0, output)
return output

这个函数是计算cv2.convexHull凸包的,也就是下图这个意思。

51c视觉~CV~合集8~※~人脸检测_人脸检测_16

这个函数cv2.addWeighted是做图像叠加的。

src1, src2:需要融合叠加的两副图像,要求大小和通道数相等

alpha:src1 的权重

beta:src2 的权重

gamma:gamma 修正系数,不需要修正设置为 0

dst:可选参数,输出结果保存的变量,默认值为 None

dtype:可选参数,输出图像数组的深度,即图像单个像素值的位数(如 RGB 用三个字节表示,则为 24 位),选默认值 None 表示与源图像保持一致。

dst = src1 × alpha + src2 × beta + gamma;上面的式子理解为,结果图像 = 图像 1× 系数 1+图像 2× 系数 2+亮度调节量。

三、完整代码及效果展示

from collections import OrderedDict
import numpy as np
import argparse
import dlib
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--shape-predictor", required=True, help="path to facial landmark predictor")
ap.add_argument("-i", "--image", required=True, help="path to input image")
args = vars(ap.parse_args())
FACIAL_LANDMARKS_68_IDXS = OrderedDict([ ("mouth", (48, 68)), ("right_eyebrow", (17, 22)), ("left_eyebrow", (22, 27)), ("right_eye", (36, 42)), ("left_eye", (42, 48)), ("nose", (27, 36)), ("jaw", (0, 17))])
FACIAL_LANDMARKS_5_IDXS = OrderedDict([ ("right_eye", (2, 3)), ("left_eye", (0, 1)), ("nose", (4))])def shape_to_np(shape, dtype="int"):
# 创建68*2
coords = np.zeros((shape.num_parts, 2), dtype=dtype)
# 遍历每一个关键点
# 得到坐标 for i in range(0, shape.num_parts):
coords[i] = (shape.part(i).x, shape.part(i).y)
return coordsdef
visualize_facial_landmarks(image, shape, colors=None, alpha=0.75):
# 创建两个copy
# overlay and one for the final output image
overlay = image.copy()
output = image.copy()
# 设置一些颜色区域
if colors is None: colors = [(19, 199, 109), (79, 76, 240), (230, 159, 23), (168, 100, 168), (158, 163, 32), (163, 38, 32), (180, 42, 220)]
# 遍历每一个区域
for (i, name) in enumerate(FACIAL_LANDMARKS_68_IDXS.keys()):
# 得到每一个点的坐标
(j, k) = FACIAL_LANDMARKS_68_IDXS[name]
pts = shape[j:k]
# 检查位置
if name == "jaw":
# 用线条连起来
for l in range(1, len(pts)):
ptA = tuple(pts[l - 1])
ptB = tuple(pts[l])
cv2.line(overlay, ptA, ptB, colors[i], 2)
# 计算凸包
else:
hull = cv2.convexHull(pts)
cv2.drawContours(overlay, [hull], -1, colors[i], -1)
# 叠加在原图上,可以指定比例
cv2.addWeighted(overlay, alpha, output, 1 - alpha, 0, output)
return output
# 加载人脸检测与关键点定位
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor(args["shape_predictor"])
# 读取输入数据,预处理
image = cv2.imread(args["image"])
(h, w) = image.shape[:2]
width=500
r = width / float(w)
dim = (width, int(h * r))
image = cv2.resize(image, dim, interpolation=cv2.INTER_AREA)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 人脸检测rects = detector(gray, 1)
# 遍历检测到的框for (i, rect) in enumerate(rects):
# 对人脸框进行关键点定位
# 转换成
ndarray shape = predictor(gray, rect)
shape = shape_to_np(shape)
# 遍历每一个部分
for (name, (i, j)) in FACIAL_LANDMARKS_68_IDXS.items():
clone = image.copy()
cv2.putText(clone, name, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 根据位置画点
for (x, y) in shape[i:j]:
cv2.circle(clone, (x, y), 3, (0, 0, 255), -1)
# 提取ROI区域
(x, y, w, h) = cv2.boundingRect(np.array([shape[i:j]]))
roi = image[y:y + h, x:x + w]
(h, w) = roi.shape[:2]
width=250
r = width / float(w)
dim = (width, int(h * r))
roi = cv2.resize(roi, dim, interpolation=cv2.INTER_AREA)
# 显示每一部分
cv2.imshow("ROI", roi)
cv2.imshow("Image", clone)
cv2.waitKey(0)
# 展示所有区域 output = visualize_facial_landmarks(image, shape)
cv2.imshow("Image", output)  
cv2.waitKey(0)

51c视觉~CV~合集8~※~人脸检测_视觉_17

 最终将7个人的人脸都依次的检测到了。并且根据关键点定位到了






三、特征点检测~人脸融合

所谓人脸融合:给定输入人脸A、B,输出的人脸C具有A和B共同的特征,是一张全新的人脸,也可以说是一张假脸。

人脸融合的过程主要有三步:人脸特征点定位,人脸融合,人脸交换。

  • 第一步,通过深度学习训练的模型对两张待融合的图像进行关键点定位;
  • 第二步,根据定位结果对人脸进行融合;
  • 第三步,将融合得到的人脸交换到待交换的人脸上,合成最终图像。

实际上做到第二步已经达到了人脸融合的基本要求,对于人脸交换,大部分用于假脸交换起到一定的隐私保护作用,用在人脸融合这里也算刚刚好,如果随意用于交换真脸,那就是信息灾难了,例如各种换脸的视频电影层出不穷。

1 人脸特征点定位

1.1 训练模型

数据集越大,训练的模型越精确。人脸特征点检测有一个非常著名的数据集300-W(300 Faces In-The-Wild Challenge)。300-W是一项专注于人脸特征点的检测的竞赛,在该竞赛中,参赛队伍需要从600张图片中检测出人脸,并且将面部的68个特征点全部标记出来。300W数据的压缩包有2G多。包含各种各样已经标记好的人脸信息。因为在如此大的数据集上训练需要大量的资源和时间。所以,在本次的设计中,我们使用少量的数据集来训练。

训练特征点检测模型,这里我们用到了imglab工具。Imglab用于标记可用于训练dlib或其他对象检测器的对象的图像。通过cmd:

imglab -c training_with_face_landmarks.xml images

来创建我们记录标签的xml文件,接下来通过cmd:

imglab training_with_face_landmarks.xml

打开imglab工具对图像打标签。这里我们按照dlib官方给出的68个特征点对每张图片进行标注,shift+鼠标左键拖动截取人脸,双击选择的矩形,shift+左键进行特征点标注,如下图所示。

接下来就可以开始训练模型,这一步就看电脑性能了。首先定义参数设置函数:

options = dlib.simple_object_detector_training_options()

大部分参数,我们使用默认值,针对我们的训练集,主要设定如下几个参数。

  • Oversampling_amount: 通过对训练样本进行随机变形扩大样本数目。比如N张训练图片,通过设置该参数,训练样本数将变成N * oversampling_amount张。所以一般而言,值越大越好,只是训练耗时也会越久。因为本例中训练集数据较少,所以我们将值设得较高(300)。
  • Nu: 正则项。nu越大,表示对训练样本fit越好。
  • Tree depth: 树深。本例中通过增加正则化(将nu调小)和使用更小深度的树来降低模型的容量。
  • Be_verbose,是否输出训练的过程中的相关训练信息,设置为真。

1.2 测试模型

人脸特征点检测模型训练完成后,需要测试该模型的准确率。测试模型的准确率包括两部分,训练集测试和测试集测试。训练集就是训练该模型的数据集合。输出在训练集中的准确率的核心代码为:

print("Training accuracy{0}".format(dlib.test_shape_predictor(training_xml_path,"predictor.dat")))

测试集即非训练集数据的集合。输出在测试集中的准确率的核心代码为:

print("Testing accuracy:{0}".format(dlib.test_shape_predictor(testing_xml_path, "predictor.dat")))

1.3 特征点定位

用训练好的模型进行人脸特征点检测。具体步骤如下,导入上述训练好的模型:

predictor = dlib.shape_predictor("predictor.dat");

检测人脸:

dets = detector(img, 1)

返回人脸所在矩形,可通过len(dets)获得人脸个数;获取检测到的人脸特征点:

lanmarks = [[p.x, p.y] for p in predicator(img1, d).parts()];

在图片中循环打印出68个特征点,如下图所示。

51c视觉~CV~合集8~※~人脸检测_OpenCV_18

2 人脸融合

这一步首先介绍两个算法,Delaunay三角剖分,仿射变换。

2.1 Delaunay三角剖分

设点集V,x,y属于点集V,多边形S中两个端点为x,y的一条边e,若过x,y两点存在一个圆,且点集V中任何其他的点都不在该圆内,圆上包括x,y两点最多三点,那么称e为Delaunay边。那么如果点集V的一个三角剖分T只包含Delaunay边,那么该三角剖分称为Delaunay三角剖分。

Delaunay三角剖分有两个重要特性,一个是空接圆特性:Delaunay三角剖分是唯一的,任意四点不能共圆,在Delaunay三角剖分中任一三角形的外接圆范围内不会有其它点存在。另外一个是最大化最小角特性:在散点集形成的所有三角剖分中,Delaunay三角剖分所形成的三角形的最小角最大。

该算法其实来源于美术馆问题,美术馆是一个复杂的多边形,我们需要在美术馆当中安排警卫,在使得他们能观察到美术馆的所有角落前提下,安排的警卫最少。多边形中的视野问题不好处理,所以将多边形剖分成多个三角形从而使问题得到简化。警卫站在三角形的任意一个位置都能观察到这个三角形中的每一个点。数学家Steve Fisk对这个问题给出了非常精彩的解答:剖分成三角形后,每个三角形的三个顶点共用三种不同的颜色染色,染色结束后在最少的颜色上设置警卫即可,如下图所示。

51c视觉~CV~合集8~※~人脸检测_人脸检测_19

所以问题简化成将该多边形剖分成一个个三角形。另外三角剖分也有优劣之分,一般来说,剖分出来的三角形越匀称越好,匀称的三角形在图形图像处理方面效果比陡峭的三角形更好,如下图所示,都是对同一个六边形进行三角剖分,但是右边的效果更好,这就是Delaunay三角剖分。而在人脸融合技术中,Delaunay三角剖分也扮演了不可或缺的角色。

51c视觉~CV~合集8~※~人脸检测_人脸检测_20

我们根据得到的特征点对人脸进行Delaunay三角剖分,将人脸剖分成一个个三角形,然后将一个个三角形进行仿射变换,再调整透图片透明度,便可得到融合后的人脸,训练的特征点越多,融合的效果便越好。OpenCV提供了Subdiv2D类实现了Delaunay三角剖分算法,subdiv = cv2.Subdiv2D(rect),将68个特征点的位置插入subdiv,subdiv.insert§ ,获取Delaunay三角列表:

trangleList = subdiv.getTriangleList()

最后调用cv2.line()将剖分后的结果画出,如下图所示。

2.2 仿射变换

仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。仿射变换能够保持图像的“平直性”,包括旋转,缩放,平移,错切操作。一般而言,仿射变换矩阵为2*3的矩阵,第三列的元素起着平移的作用:

51c视觉~CV~合集8~※~人脸检测_视觉_21

前面两列的数字对角线上是缩放,其余为旋转或者错切的作用。仿射变换是一种二维坐标(x, y)到二维坐标(xt, yt)的线性变换。数学表达式如下图所示:

51c视觉~CV~合集8~※~人脸检测_OpenCV_22

直观的图像表示就是这样:

51c视觉~CV~合集8~※~人脸检测_OpenCV_23

首先对之前得到的特征点进行预处理。人脸特征点检测算法得出的人脸68个特征点保存在本地的txt文件中。同时,Delaunay三角剖分算法得到的三角形的索引结点也保存在本地的txt文件中。读取人脸A的特征点points1,人脸B的特征点points2。因为要求融合的人脸不一定是同等大小,所以我们取一个平均值保存在points中。Points即为我们融合后的人脸特征点所在位置。经过三角剖分后,人脸被剖分成一个个三角形。我们从保存的三角形索引结点中循环读取每一个三角形的索引点,通过索引结点找到人脸A和人脸B的该三角形所对应的三个特征点位置。取得的三角形以下称三角形A,三角形B。取平均值的三角形称为C。因为做仿射变换只需要变换一个我们剖分形成的三角形,不需要变换整张图片,所以我们可以求得该三角形的最小外接矩形,以提升变换效率,并减少计算量。

r = cv2.boundingRect(np.float32([t]))

返回的r为一个四元组,前两个值为矩形左上角的坐标,后两个值为矩形的长和宽。因为要在上面获得的外接矩形中进行仿射变换,所以原点从图片的左上角点变成了外接矩形的左上角点,从而要修改三角形的三点坐标。

tRect.append(((t[i][0] - r[0]), (t[i][1] - r[1])))

t为预处理中获得的三角形三个顶点坐标。在上一步中,我们获得了输出矩形图像。但是,我们对矩形区域内的三角形感兴趣。因此,我们使用fillConvexPoly创建一个掩模mask,用于遮蔽三角形外的所有像素。这个新的裁剪图像最终可以使用输出边界矩形的左上角坐标点置于输出图像中的正确位置。接下来就是通过仿射变换将三角形A,B变换到C的位置上。

warpImage1 = applyAffineTransform(img1Rect, t1Rect, tRect, size)
warpImage2 = applyAffineTransform(img2Rect, t2Rect, tRect, size)

ApplyAffineTransform是手写的函数,包括cv2.getAffineTransform,用来找到两个三角形之间的仿射变换矩阵,cv2.warpAffine将上个函数获得的矩阵应用于图像。

# Apply affine tranform calculated using srcTri and sdtTri to src and output an image of size
def applyAffineTransform(src, srcTri, dstTri, size):
    # Given a pair of triangles,find the affine transform.
    warpMat = cv2.getAffineTransform(np.float32(srcTri), np.float32(dstTri))
    # Apply the Affine Transform just foundto the src image
    dst = cv2.warpAffine(src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR,
                         borderMode=cv2.BORDER_REFLECT_101)
    return dst

2.3 图像融合

将人脸剖分成一个个小三角形并仿射变换到既定的位置后,进行图片融合。简单来说,通过把图像设置为不同的透明度,把两张图像融合为一张图像(一般要求图像需要是等尺寸大小的),公式如下图所示:

51c视觉~CV~合集8~※~人脸检测_人脸检测_24

仿射变换时,我们将人脸A的三角形A,人脸B的三角形B仿射到三角形C上。如果不处理,三角形A和三角形B仿射后的图像会叠加在C上。这里我们设置一个透明度参数alpha为0.5,两张人脸各占一半。

imgRect = (1.0 - alpha) * warpImage1 + alpha * warpImage2

warpImage1,warpImage2为经过仿射变换的图像。最后一步,最后将用于遮蔽三角形外的所有像素mask与上个步骤输出的图片结果相乘就可以得到对应区域的值了。融合结果如下图所示:

得到融合后的人脸后,最后一步,就是将融合后的人脸交换到待交换的人脸上。

3 人脸交换

人脸交换分为五个步骤:特征点检测,查找凸包,基于凸包三角剖分,仿射变换,无缝克隆。特征点检测,三角剖分和仿射变换之前的部分提及到,所以详细介绍一下查找凸包和无缝克隆。

3.1 凸包算法

如下图所示,所给平面上有点集V,包含p0到p12一共13个点,过p0,p1,p3,p10,p13点作一个多边形,点集V中所有点都包含在形成的这个多边形上。如果当这个多边形是凸多边形的时候,我们就叫它“凸包”。简单来说,点集的凸包即一个最小的凸多边形使得点集中所有的点在该凸边形内或该凸边形上。

51c视觉~CV~合集8~※~人脸检测_视觉_25

凸包算法为什么会用在人脸交换中呢?人脸交换同样需要特征点检测,同样需要三角剖分,与人脸融合不同的是人脸交换是直接通过仿射变换将人脸A剖分后的三角形仿射到人脸B上。人脸融合剖分的三角形越多,融合效果越完美。

人脸交换不同于融合,第一步只需要将人脸A贴到人脸B上,所以只需要求出68个特征点的凸包多边形,对这个多边形剖分即可。OpenCV便提供了求得这个凸包的函数cv2.convexHull()。人脸交换不同于融合,第一步只需要将人脸A贴到人脸B上,所以只需要求出68个特征点的凸包多边形,对这个多边形剖分即可,如下图所示。

仿射换脸核心代码如下:

# Warps and alpha blends triangular regions from img1 and img2 to img
def warpTriangle(img1, img2, t1, t2):
    # Find bounding rectangle for each triangle
    r1 = cv2.boundingRect(np.float32([t1]))
    r2 = cv2.boundingRect(np.float32([t2]))

    # Offset points by left top corner of the respective rectangles
    t1Rect = []
    t2Rect = []
    t2RectInt = []

    for i in range(0, 3):
        t1Rect.append(((t1[i][0] - r1[0]), (t1[i][1] - r1[1])))
        t2Rect.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))
        t2RectInt.append(((t2[i][0] - r2[0]), (t2[i][1] - r2[1])))

    # Get mask by filling triangle
    mask = np.zeros((r2[3], r2[2], 3), dtype=np.float32)
    cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0);

    # Apply warpImage to small rectangular patches
    img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]
    # img2Rect = np.zeros((r2[3], r2[2]), dtype = img1Rect.dtype)

    size = (r2[2], r2[3])

    img2Rect = applyAffineTransform(img1Rect, t1Rect, t2Rect, size)

    img2Rect = img2Rect * mask

    # Copy triangular region of the rectangular patch to the output image
    img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] * (
            (1.0, 1.0, 1.0) - mask)

    img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] = img2[r2[1]:r2[1] + r2[3], r2[0]:r2[0] + r2[2]] + img2Rect

得到的结果如下图,可以明显看出肤色的差异:

51c视觉~CV~合集8~※~人脸检测_人脸检测_26

3.2 无缝融合

优秀的技术就像魔法。优秀的魔术师将物理学、心理学和古老的魔术相结合实现了图像的无缝融合。图像的编辑包括全局变化和局部变化,无缝融合是将我们所选中的局部区域无缝且无影响地融合到目标图像中。传统上的工具来完成局部的剪切与融合,是通过克隆工具直接覆盖那部分融合区域的内容,因此如果选择的区域色彩和明暗有明显的差异,会导致明显的边缝,显得突兀。无缝融合由此诞生。

无缝融合的基本原理基于人的生物学特性,我们人眼天生就对“突变”更为敏感。简单来说,比如突然从密闭的黑暗房间出来,我们人眼会受到光线的刺激。但是如果我们渐变的适应一下,就不会感受到光线的刺激。就像白纸上的黑色图形,我们会感觉很“突兀”。同理如果连续平滑的将这个图像由白入灰再变黑,那么我们就感受不到这种突兀。无缝融合就是基于此生物学特性的魔法,虽然是将一张图像融合到另一张图像上,但是我们丝毫感觉不出“突兀”。

这种无缝编辑和克隆方法的核心是数学工具―泊松方程,前提是需满足在所选区域未知函数的拉普拉斯条件和它的Dirichlct边界条件:未知函数的边界值与目标图像中所选区域的边界值相同。因为在这两个条件下方程的解是唯一的。

首先,心理学家Land和Mccan在1971年提出通过拉普拉斯算子的限制可以减缓渐变的梯度,当把一幅图像混淆到另一幅图像上几乎注意不到有什么影响。并且,泊松方程可以完成无缝地填满目标图像中的选中区域。在数学上说到底就是构建方程组:Ax=b。然后通过求解这个方程组以得到每个像素点的值。矩阵A是一个系数矩阵,矩阵的每一行有五个非零元素,对应于拉普拉斯算法的卷积核,而算法的关键在于怎么构建方程组的b值 。首先,计算待融合图像A和背景图像B的梯度场;然后计算融合图像的梯度场,计算完后将A的梯度场覆盖到B的梯度场上;最后对梯度求偏导求得融合图像的散度b,解系数方程Ax=b。虽然看起来涉及到许多数学知识,实际上实现起来只要一个函数cv2.seamlessClone,这就是OpenCV(Computer Vision)。实现的无缝融合效果如下图:

51c视觉~CV~合集8~※~人脸检测_视觉_27

当然了,如果交换的人脸周围是黑的,就比如下图我们融合得到的中间的人脸,人脸交换时人脸轮廓难免会包括周围的黑色背景。所以,为了使无缝融合取得更好的效果,最好在图片周围加八个点,将环境也粗略融合一下,这样就可以避免人脸周围黑色造成换脸后的“伤疤”。

新加的这8个点,可以通过:

sp = img1.shape  # [高|宽|像素值由三种原色构成]  #高宽色素

获取图片的高和宽,例如高的一半可以sp[0]/2表示,宽的一半sp[1]/2。三角剖分结果如下:

得到融合的图像如下:

将这张人脸进行人脸交换,可以有效防止周围黑色环境无缝融合产生的“伤疤”,最后融合人脸得到的图片如下:

4 实验分析总结

从第一张图来看,我们对两张性格相同的人脸进行融合,实验结果显示本文提出的人脸融合技术可以实现人脸融合,且最后的融合结果具有高度的真实感,我们基本分辨不出这是融合的效果,初步达到了我们对人脸融合实现的要求。

从第二张、第三张图来看,我们对两张性别不同、肤色不同的人脸进行融合,并分别交换到这两张性别不同的人脸上,从视觉效果上看,我们的算法依然取得了较好的效果,融合的人脸不论是交换在男性身上还是交换在女性身上都显得非常自然,并且肤色也非常自然,性别,肤色并未对算法结果造成影响。

从第四张图来看,我们选择了两张人脸大小不一的图像来进行融合,我们发现融合仍然具有高度的真实感。但是发现一点瑕疵,左边的图像由于灯光的照射使得右侧脸颊有阴影,而我们的算法并不能判断出这是由于灯光产生的阴影还是肤色,导致融合后阴影仍然存在,致使结果有些不自然,但并不影响整个融合的实现,这个问题也是在未来需要解决的。

总的来说,实验结果证明本文提出的人脸融合实现的融合结果具有高度的真实感,基本实现了最初构想,在效果上也达到了预期要求。但仍然还有需要改进的方向:

(1)消除灯光照射产生阴影对人脸融合的影响。上述第四张图片由于灯光的照射使得人脸右侧脸颊产生阴影,而我们提到的无缝融合只能将其也归为肤色,导致融合后即使没用光照也有阴影,导致实验效果不自然。方向有两个,一个是对图像进行预处理,消除光照产生的影响;另一个就是在融合过程中对阴影部分进行处理。从可行上来看,对图像进行预处理显得更加便于操作。

(2)消除头发,眼镜等遮挡物对融合的影响。我们的算法是在人脸完全展示的状态下进行剖分并进行融合。如果出现遮挡物,仍然会把遮挡物当成人脸的一部分参与仿射变换并进行融合,这同样会影响实验结果。而随着社会的发展,发型遮挡人脸或者饰品遮挡人脸的情况也比较常见。





四、人脸识别算法及系统

人脸识别的目标

总结两点,第一,认出同一个人,不管你的状态怎么变,都能知道你就是你。第二、区分不同的人,可能这两个人长得很像,或者两个人都化妆了,但不管状态怎么变化,人脸识别都能知道这是两个不同的人。

人脸识别本身是作为生物识别技术的一种,主要是提供身份认证的手段,从精度上来讲,人脸识别并不是最高的。人脸识别受到很多其他条件的影响,比如说光照。人脸识别的好处在于一般不需要用户做太多的配合,现在各个地方的监控摄像头,包括电脑的摄像头、手机各种视频输入设备,照相设备已经非常非常的普及,用这种可见光的设备就可以做人脸识别。所以在引入人脸识别的时候可能新增加的投资是非常少的,这是它的优势。

人脸识别的流程

人脸识别的核心流程,所谓核心流程就是不管在什么样的人脸识别系统里面基本上都有这个流程。首先人脸检测,第二步做人脸对齐,第三步做特征提取,这是对每一张照片都要做的这三步,当要去做比对的时候就把提取的特征做比对,然后确定这两个脸是不是属于同一个人。

51c视觉~CV~合集8~※~人脸检测_人脸检测_28

人脸检测

人脸检测即判断一个大的场景中是否有人脸,并且要找到这个人脸的位置把它切出来。它是属于物体检测技术的一种,是整个人脸感知任务的基础。人脸检测的基本方法就是在图象金字塔上滑动窗口,用分类器选择候选窗口,用回归模型进行位置修正。

51c视觉~CV~合集8~※~人脸检测_人脸检测_29

上面画的三个窗口,一个是0.3倍、0.6倍、1.0倍,当人脸位置不定,大小无法识别时可采用此技术,让这个图本身变得大小不同,而滑动窗口的大小相同。深度网络一般输入的图象大小是固定的,那么前面的滑动窗口基本上也是固定的。为了让固定的滑动窗口能够覆盖不同的范围的话,就对整个图的大小进行缩放,取不同的比例。这边画0.3、0.6、1.0只是举例,实际用的时候还可以有很多其他不同的倍数。

分类器是指看滑动窗口每一个滑到的位置去判断是否是人脸,因为滑动窗口滑动到的位置有可能不包含整个人脸,或者说它比整个人脸大一点。为了找到的人脸能够更加精确,把滑动窗口放到回归模型里,即可帮助修正人脸检测的精确度。

输入的是滑动窗口,输出时如果里面有人脸,应该向哪边修正,以及它需要修正多少,所以Δx, Δy, Δw, Δh,就是它的坐标以及它的宽和高大概修正多少。有了修正的量和用分类器确定它是人脸的窗口以后,把这两个结合在一起,就能得到一个比较精确的人脸位置。

以上是人脸检测的流程,同时也可适用于其他的物体检测

人脸检测的评价指标

不管什么样的模型都是分速度和精度两个方面

速度

(1)速度是指定分辨率下的检测速度

之所以指定分辨率,是因为滑动窗口每滑到一个位置都要做一次分类和回归的判断,所以当图越大,需要做检测判断的窗口数可能就越多,整个人脸检测花的时间就越长。

因此评价一个算法或者模型的好坏,就得在固定的分辨率下面去看它的检测速度到底是多少。一般来说这个检测速度会是什么样的值,可能就是做一张图的人脸检测所花费的时间,比如说100毫秒、200毫秒还是50毫秒、30毫秒之类的。

另外一种表示速度的方法就是多少fps,现在一般的网络摄像头往往是25fps或者30fps,意思是每秒钟能处理多少张图,用fps的好处可以判断人脸检测是否可以做到实时检测,只要人脸检测的fps数大于摄像头的fps数就能够做到实时,否则就做不到。

(2)速度是否受统一个画面中的人脸个数影响

从我们实际操作来说,大部分来说是不受影响的,因为主要是受滑动窗口的次数影响,命中的次数倒不是特别重,但是稍微有那么一点影响。

精度

精度,基本上用召回率、误检率、ROC曲线这些来判定。召回率即指这张照片是人脸,真正的模型判断出来是人脸这个比例,误检率、负样本错误率即指这张照片不是人脸,但是误判断成人脸的比例。

ACC精度

ACC计算方法是正确的样本数除以总的样本数,比如说拿一万张照片去做人脸检测,这一万张照片里面有的是有人脸的,有的是没有人脸的。然后判断对的比例是多少。

但是这个精度存在一个问题,如果用它去判断,它对于正负样本的比例是完全无关的,即他不关心在正样本里面正确率是多少,在负样本里面正确率是多少,只关心总的。当此模型精确度是90%时,别人不知道在正负样本上面区别是多少。包括分类,包括回归,一般来说分类模型,会先用一个回归得到一个所谓置信度,置信度大于多少数值时认为他是,然后置信度小于同一个数值时认为他不是。

ACC统计模型是可调节的,即调整置信度,精度就会变化。

所以ACC值本身受样本的比例影响很大,所以用它来表征一个模型的好坏的话有点问题,当测试指标说达到了百分之九十九点几,单看这个值,是比较容易受骗或者说这个统计是有偏的。

为了解决这个问题,一般现在会用一个叫做ROC的曲线来表征这个模型的精度

ROC受试者工作特征曲线

51c视觉~CV~合集8~※~人脸检测_人脸检测_30

横坐标:FPR(False Positive Rate)即负样本错误率 纵坐标:TPR(True Positive Rate)即正样本正确率 可以区分算法在正样本和负样本上的性能,并且曲线形状与正负样本比例无关。

ROC(Receiver Operating Characteristic)曲线就是把横坐标、纵坐标用这个负样本错误率和正样本正确率两个标出来,这样的话同一个模型在这个图上面看到的不是一个点,或者说不是一个单一的数据,而是一条线。这条线即置信度的阈值,你调的越高就越严格,越低就越不严格。在这个上面的话就能反应出这个置信度的阈值的变化对它的影响。
以后大家最好不要直接问说你的精度是多少,而是看ROC曲线,这样更容易判断模型的能力到底怎么样。

人脸对齐

人脸对齐的目的使人脸纹理尽可能调整到标准位置,降低人脸识别器的难度。

51c视觉~CV~合集8~※~人脸检测_人脸检测_31

为了用人为的方式降低它的难度,就可以先把它做对齐,就是让检测到这个人的眼睛、鼻子、嘴巴全部归到同一个位置去,这样的话模型在比对的时候,就只要找同样位置附近,互相是不是相同还是相近,还是有很大不同。所以就是会做对齐的这么一步,这一步的话,我们现在常用的做法就是二维的做法,就是到这个图里面去找到关键特征点,一般现在就是五点的、十九点的,六十几个点,八十几个点的各种都有。但人脸识别的话五个基本上就够了。

这五个点之外的其他点的图象,可以认为它是做一个类似于插值的运算,然后把它贴到那个位置去,做完了以后,就可以送到后面的人脸识别器里面去做识别了。这个是一般的做法,还有更前沿的做法,有的研究机构在使用所谓的3D人脸对齐,就是我告诉你说一张正脸是什么样子的,比如旋转45度的时候长什么样子,那么用这种图给他训练过了以后,他就知道我看到一张向左右旋转了45度这张图,大概转正了以后有很大可能性是什么样子的,这个模型能去猜。

人脸特征提取算法

以前的传统方法是所谓的局部纹理模型,全局纹理模型,形状回归模型之类的这些都有。现在比较流行的就是用深度的卷积神经网络或者循环神经网络,或者3DMM参数的卷积神经网络。所谓3DMM参数的话就是有三维的信息在里面,然后有级联的深度神经网络。

51c视觉~CV~合集8~※~人脸检测_人脸检测_32

级联的深度神经网络,即拿到人脸,首先要推测五个点的位置,如果用一个单一的模型一次就要做到这一点的话,这个模型会需要很复杂。

但是怎么能够降低这个模型的复杂程度呢?

即做多次输入,第一次输入这个网络以后先做一次推测,这个推测是一个可接受的不那么精确的推测,大概知道人脸的五个点在哪。然后把这五个点和那张原图放到第二个网络里面去,得到大概的修正量,有了一个基础五个点以后再求修正量的话会比直接从原图上面找精确的五个点要稍微容易一些。所以用这种逐步求精的方式用多个网络级联在一起就能够做到一个速度跟精度的比较好的平衡,实际现在做的时候基本上用两层就大概差不多了。

人脸特征点提取的评价指标精度

为了能够让不同大小的人脸也能够放在一起互相比较,采用统计学上叫做归一化均方根误差。

51c视觉~CV~合集8~※~人脸检测_视觉_33

NRMSE(Normalized Root Mean Square Error)即归一化均方根误差用于衡量各个特征点坐标与标注坐标的差距。

举个栗子:我们在纸上画五个点,然后让机器来说这五个点的相互距离,所给出的数值越接近真实距离,则说明预测越精准。一般来说预测的数值一定会有些偏差,那么怎么表述这个精度值呢?我们通常就用距离的平均值或者均方根值来表达。然而问题来了,相同的机器预测不同大小的图像,精度值会显得不同,因为越大的图误差的绝对值会越高。换到不同大小的人脸道理也是一样。因此,我们的解决办法是把人脸本来的尺寸数值考虑进去,一般分母是人的双眼距离或者人脸的对角线距离,再用距离差值除以双眼之间的距离,或者是除以人脸的对角线,这样的话就可以得到一个基本上不随着人脸大小而变化的一个值,用它来评测。

人脸比对目的

即判断已经对齐好的两张人脸,是否属于同一个人

难点

同一张人脸在不同状况下会呈现不同的状态,比如说特别受光照、烟雾、化妆等等的影响。第二个就是映射到二维照片上的不同参数造成的,所谓映射到二维参数上就是说本来人脸长这样,拍摄设备去拍的时候,跟他呈现的角度,离他的距离,对焦是否准确,拍摄视角等等光线积淀这个都有影响,会让同一个人脸产生出不同的状态。第三就是年龄和整容的影响。

人脸比对的方法传统方法

由人工去抽取一些特征HOG、SIFT、小波变换等,抽取的这种特征一般来说可能是要固定参数,即不需要训练,不需要学习,使用一套固定的算法,再对这个特征进行比较。

深度方法

主流的方法就是深度的方法,即深度卷积神经网络,这个网络一般来说是用DCNN去代替前面的那些特征抽取方法,即把一张图上面,一个人脸上面的一些各个不同的特征弄出来,DCNN里面有很多参数,这个参数是学出来的,不是人告诉他的,学出来的话相当于能比人总结出来的这些会更好。

然后得到的一组特征一般现在的维度可能是128维、256维或者512维、1024维,然后做对比. 判断特征向量之间的距离,一般使用欧氏距离或余弦相似度。

人脸比对的评价指标同样分为速度与精度,速度包括单张人脸特征向量计算时间和比对速度。精度包括ACC和ROC。由于前面已介绍过,这里着重介绍一下比对速度。

普通比对是一个简单的运算,是做两个点的距离,可能只需要去做一次内积,就是两个向量的内积,但当人脸识别遇到1:N对比时,当那个N库很大的时候,拿到一张照片要去N库里面做搜索的时候,搜索的次数会非常多,比如N库一百万,可能要搜索一百万次,一百万次的话就相当于要做一百万次的比对,这个时候的话对于总时间还是有要求的,所以就会有各种各样的技术对这种比对进行加速。

51c视觉~CV~合集8~※~人脸检测_OpenCV_34

人脸识别相关的其他的算法

主要有人脸追踪、质量评估、活体识别。

人脸追踪

在监控等视频人脸识别的场景下,如果对走过的同一个人的每一帧都执行整套人脸识别流程,不仅浪费计算资源,而且有可能因为一些低质量的帧造成误识别,所以有必要判断哪些人脸是属于同一个人的。并挑选出合适的照片做识别,大大提升模型的整体性能。

现在不止人脸追踪,还有各种各样不同的物体追踪或者是车辆追踪等等,都会使用到追踪算法,此类算法不依赖于或不会一直依赖于检测。例如在最开始检测到一个物体后,接下来就完全不检测了,只通过追踪算法去做。同时为了做到非常高精度,为了做到不丢失,每一次的追踪花的时间是比较多的。

为防止追踪到的人脸和人脸识别器范围不吻合,一般来说还是会拿人脸检测器做一次检测,这种检测方法是依赖于人脸检测做的比较轻量化的追踪,在某些场景下,可以做到一个速度和质量上面的一个平衡。

51c视觉~CV~合集8~※~人脸检测_视觉_35

这种检测方法叫做Tracking by Detection,即每一帧仍然去做人脸检测,检测到人脸以后,根据每一个人脸的四个值,即他的坐标位置,他的宽、高,去比较前后两帧的人脸的位置和大小,大概可以推测出这两个人脸到底是不是属于同一个移动物体。

可选的间隔全画面检测

是指当去做Tracking by Detection的时候,前后两帧,一种做法是都做全画面的检测,所谓全画面检测即把全画面全部扫一遍,但是此方法耗时严重,因此有时会采用另一种方法,间隔几帧做一次全画面,一般预测一个下一帧,位置变化不会太多,只要把前一帧的位置上下左右稍微扩大一点,再去检测一次,往往大概率机会是可以检测到,大多数的帧可以跳。

为什么一定要间隔几帧再做一次全画面检测呢?

是为防止有新的物体进来,如果只根据前一个物体的位置来找的话,可能有新的物体进来的时候就没检测到,防止这种情况的话可以隔个五帧、十帧再去做一次全画面检测。

人脸质量评估

由于人脸识别器训练数据等的限制,不可能对所有状态下的人脸性能都很好,质量评估会判断检测出来的人脸与识别器特性的吻合程度,只选取吻合程度高的人脸送去做识别,提高系统的整体性能。

人脸质量评估包含以下4要素:

  • 人脸的大小,选用太小的脸做识别效果会大打折扣。
  • 人脸姿态,就是指三个轴方向的旋转角度,一般来说跟识别器训练用的数据有关。如果训练的时候大部分用的是姿态不太大的人脸的话,在真正做识别的时候也最好不要挑那种偏转很大的,否则会不适用。
  • 模糊程度,此要素很重要,如果照片已经丢失掉信息了,再做识别会存在问题。
  • 遮挡,如果眼睛、鼻子等被盖住了,这块的特征就无法拿到,或者说拿到的是错的,是一个遮挡物的特征,对后面的识别有影响。如果能判断出来是被遮挡的,然后弃用,或者做一些特殊处理,例如不把它放到识别模型里面去。

活体识别

这是所有的人脸识别系统都会遇到的问题,只识别人脸的话,照片也可以蒙混过关。为了让系统不被攻击就会去做一些判断,这个到底是一个真脸还是假脸。

基本上目前的方法大概有三种:

  • 传统的动态识别,很多银行的取款机会有这种要求用户做一些配合,例如让用户眨眼、转头,以此来判断用户是不是根据眨眼、转头做了同样的配合。因此动态识别存在一个问题,即需要用户的配合较多,这样用户使用体验会有点不好。
  • 静态识别,即不根据动作来判断,只是根据这个照片本身来判断是否是真实人脸,还是一个假脸。它的根据是一般现在用的攻击的手段,是比较方便的,例如拿手机,或者是拿一个显示屏,就用屏幕做攻击。这种屏幕的发光能力跟实际的光照条件下面人脸的发光能力是不一样的,例如显示器有1600万发色数,是无法做到可见光的发光能力,即全部是连续的所有的波段都能发出来。因此当再对这种屏幕做拍摄时,和真正自然环境下面的一次成像对比,人眼也能看出来会有一些变化,会有一些不自然。通过这种不自然放到一个模型里面训练过以后,还是可以根据这种细微的差别判断出来到底是不是真脸。
  • 立体识别,如果用两个摄像机或者一个带深度信息的摄像机,就能知道拍到的各个点离摄像机的距离是多少,相当于对人物做3D成像,这样用一个屏幕去拍摄,屏幕肯定是个平面,意识到是一个平面的,平面的肯定不是一个真的人。这个就是用立体的识别方式去排除平面人脸。

51c视觉~CV~合集8~※~人脸检测_OpenCV_36

人脸识别的系统构成

首先做一个分类,从对比形式来看,有1:1的识别系统,1:N的识别系统;从对比的对象来看,有照片的比对系统以及视频的比对系统;按部署形式的话,有私有部署,有云部署或者移动设备部署。

照片1:1的识别系统

51c视觉~CV~合集8~※~人脸检测_OpenCV_37

1:1的识别系统最简单,拿两张照片,每一张照片去生成一个特征向量,然后去比一下这两个特征向量是不是同一个人,就可以识别了。延庆川北小区45孙老师 收卖废品破烂垃圾炒股 废品孙 再回收

照片1:N的识别系统

51c视觉~CV~合集8~※~人脸检测_视觉_38

1:N的识别系统,即判断照片素材在不在一个样本库里。此样本库是预先准备好的,可能会有白名单或者黑名单,里面有每个人的一张照片,拿这张照片生成了一系列的特征向量。这个作为一个样本库,用上传的照片跟样本库里面的所有的特征去比,看跟哪个最像,就认为他是这个人,这个是1:N的识别系统。

视频1:1的识别系统

51c视觉~CV~合集8~※~人脸检测_OpenCV_39

视频1:1识别系统,和照片的1:1系统类似,但是比对的对象不是照片,而是视频流。拿到视频流以后会做检测,做追踪,做质量评估,等拿到合适的照片以后才去做比对。

视频1:N的识别系统

51c视觉~CV~合集8~※~人脸检测_视觉_40

视频1:N适配系统和1:N照片的系统类似,只不过拿来做识别的是视频流,同样也是要做检测、追踪、质量评估。

51c视觉~CV~合集8~※~人脸检测_视觉_41

一般所谓系统构成不一定是人脸识别系统,各种AI系统大概都会如此。首先是计算资源层,在CPU或GPU上运行,在GPU上运行可能还有CUDA,CUDN等的支持。

其次是运算工具层,包括深度学习前向网络运算库、矩阵运算库和图象处理工具库。由于不可能每一个做算法的人都自己去写数据运算,因此都会用一些已有的数据运算库,例如TensorFlow或者MXNET或者Caffe等都会提供,或者自己写一套也是可以。

最后是应用算法层,包括人脸检测、特征点定位、质量评估等等算法实现,以上是大概的系统构成。






五、人脸技术の隐私保护~活体检测


这是几份近期的人脸技术的工作,人脸图像处理识别技术作为CV领域的一大分支,仍然有很多内容值得探索。

Diff-Privacy: Diffusion-based Face Privacy Protection

论文作者:Xiao He,Mingrui Zhu,Dongxin Chen,Nannan Wang,Xinbo Gao

作者单位:Xidian University; Chongqing University of Posts and Telecommunications

论文链接:http://arxiv.org/abs/2309.05330v1

内容简介:

1)方向:人脸隐私保护

2)应用:网络图片、手机隐私保护

3)背景:随着人工智能技术的普及,个人数据的广泛收集和滥用,隐私保护已成为当务之急。匿名化和视觉身份信息隐藏是两个重要的面部隐私保护任务,它们的目标是在人类感知水平上从面部图像中移除识别特征。

4)方法:本文提出了一种基于扩散模型的面部隐私保护方法,将匿名化和视觉身份信息隐藏统一为一个任务。通过训练多尺度图像反演模块(MSI)获取原始图像的一组SDM格式条件嵌入。基于这些条件嵌入,设计相应的嵌入调度策略,并在去噪过程中构建不同的能量函数,实现匿名化和视觉身份信息隐藏。

5)结果:大量实验证明了所提框架在保护面部隐私方面的有效性。

51c视觉~CV~合集8~※~人脸检测_OpenCV_42

51c视觉~CV~合集8~※~人脸检测_视觉_43

51c视觉~CV~合集8~※~人脸检测_人脸检测_44

51c视觉~CV~合集8~※~人脸检测_OpenCV_45

51c视觉~CV~合集8~※~人脸检测_人脸检测_46

51c视觉~CV~合集8~※~人脸检测_OpenCV_47

Semantic Latent Decomposition with Normalizing Flows for Face Editing 

论文作者:Binglei Li,Zhizhong Huang,Hongming Shan,Junping Zhang

作者单位:Fudan University

论文链接:http://arxiv.org/abs/2309.05314v1

项目链接:https://github.com/phil329/SDFlow

内容简介:

1)方向:人脸编辑技术

2)应用:人脸编辑

3)背景:在StyleGAN的隐空间中导航已经显示出对于人脸编辑的有效性。然而,由于隐空间中不同属性之间的纠缠,导致现有方法在复杂导航中遇到挑战。

4)方法:本文提出了一种新的框架SDFlow,通过使用连续条件归一化流在原始隐空间中进行语义分解。具体而言,SDFlow通过联合优化两个组件来将原始隐代码分解为不同的无关变量:(i)一个语义编码器,用于从输入人脸估计语义变量;(ii)一个基于流的转换模块,将隐代码映射到高斯分布中的语义无关变量,条件是学习到的语义变量。为了消除变量之间的纠缠,采用了一个在互信息框架下的解缠学习策略,从而提供精确的操作控制。

5)结果:实验结果表明,SDFlow在定性和定量上都优于现有的最先进的人脸编辑方法。源代码可在https://github.com/phil329/SDFlow上获得。

51c视觉~CV~合集8~※~人脸检测_人脸检测_48

MaskRenderer: 3D-Infused Multi-Mask Realistic Face Reenactment

论文作者:Tina Behrouzi,Atefeh Shahroudnejad,Payam Mousavi

作者单位:University of Toronto; Amii (Alberta Machine Intelligence Institute)

论文链接:http://arxiv.org/abs/2309.05095v1

内容简介:

1)方向:人脸再现技术

2)应用:生成逼真的面部图像

3)背景:尽管最近的人脸再现研究取得了一些有希望的结果,但仍然存在一些挑战,如身份泄露和模仿口部动作,特别是对于大幅度的姿势变化和被遮挡的面部。

4)方法:MaskRenderer通过以下方法解决了这些问题:(i) 使用3DMM对3D面部结构建模,相对于2D表示更好地处理了姿态变化、遮挡和口部运动;(ii) 在训练过程中使用三元损失函数进行交叉再现以更好地保留身份信息;(iii) 多尺度遮挡,改善修复和恢复缺失区域。

5)结果:在VoxCeleb1测试集上进行的综合定量和定性实验证明,与最先进的模型相比,MaskRenderer在未见过的面孔上表现更好,特别是在源身份和驱动身份非常不同的情况下。

51c视觉~CV~合集8~※~人脸检测_OpenCV_49

51c视觉~CV~合集8~※~人脸检测_人脸检测_50

 

51c视觉~CV~合集8~※~人脸检测_人脸检测_51

Semi-Supervised learning for Face Anti-Spoofing using Apex frame 

论文作者:Usman Muhammad,Mourad Oussalah,Jorma Laaksonen

作者单位:University of Oulu;Aalto University

论文链接:http://arxiv.org/abs/2309.04958v1

内容简介:

1)方向:人脸活体检测

2)应用:金融、授权活体验证

3)背景:传统的人脸活体检测领域特征提取技术要么分析整个视频序列,要么专注于特定片段以提升模型性能。然而,确定哪些帧为人脸活体检测提供了最有价值的输入仍然是一个具有挑战性的任务。

4)方法:本文通过采用高斯加权方法来创建视频的顶点帧来解决这个挑战。具体来说,通过计算视频的帧的加权和来得出一个顶点帧,其中权重是使用以视频中心帧为中心的高斯分布确定的。此外,通过探索各种时间长度,使用高斯函数产生多个未标记的顶点帧,无需进行卷积。通过这样做,作者充分利用了半监督学习的好处,它考虑了标记和未标记的顶点帧,从而有效地区分了真实和伪造的类别。

5)结果:实验证明,使用四个人脸活体检测数据库:CASIA、REPLAY-ATTACK、OULU-NPU 和 MSU-MFSD,顶点帧在推动人脸活体检测技术方面具有显著的有效性。

51c视觉~CV~合集8~※~人脸检测_OpenCV_52

51c视觉~CV~合集8~※~人脸检测_视觉_53

 

51c视觉~CV~合集8~※~人脸检测_OpenCV_54