江大白的《30天入门深度学习》系列课程:试听部分
本节课的内容是:
(1)对标注软件Labelimg进行学习;
(2)先从图片方面,对人脸墨镜特效进行处理,并从代码层面一步步讲解;
(3)再从视频方面,对于人脸进行人脸墨镜操作;
(4)针对本次课程的内容,会布置一个作业,即对于兔耳朵帽子特效进行学习,并将学习的感受写出来;

1、Labelimg标注软件的学习(略)

软件下载地址: https://github.com/tzutalin/labelImg
使用视频展示:https://v.qq.com/x/page/n3303uma5eu.html
个人推荐微软开发的、标注界面更好看且使用更人性化的一个标注软件:vott。

  • 标注对象:图片和视频
  • 平台支持: Windows, Linux and OSX
  • 安装环境:NodeJS (>= 10.x, Dubnium) and NPM。也有可以直接使用的安装包
  • 输出数据格式:
    Azure Custom Vision Service
    Microsoft Cognitive Toolkit (CNTK)
    TensorFlow (Pascal VOC and TFRecords)
    VoTT (generic JSON schema)
    Comma Separated Values (CSV)

软件下载地址:https://github.com/microsoft/VoTT
软件使用界面:待补充

2、对图像进行人脸墨镜特效代码实现

代码实践讲解视频,原理见 上一节课程)

2.1 人脸&眼镜准备

这里主要包含:人脸检测+关键点定位+眼镜角度大小调整三个部分
前面两步应该没有问题,那么如何调整眼镜角度和大小呢?

2.1.1 整眼镜角度和大小

① 对于人脸关键点进行定位,找到人眼区域。这里主要使用到的是两边眼角0和2号点的位置。
② 计算人眼角度(tanh)和宽度(两个坐标在x轴上的差),调整墨镜大小

  1. # 通过对边比邻边的方式计算旋转角度(计算公式见图)
  2. angle = math.atan((landmarks.part(2).y - landmarks.part(0).y) / (landmarks.part(0).x - landmarks.part(2).x)) * 180 / 3.14
  3. # 通过眼角的两个点,计算两个点之间x坐标的长度
  4. width_2_points = int((landmarks.part(0).x - landmarks.part(2).x))
  5. # 调整眼镜的宽度和高度,更加适配于人脸的大小
  6. # 这里的逻辑是三庭五眼吗?
  7. # 宽度是的,3个眼睛宽度*1.8=5.4个眼睛宽度(符合眼镜略大于人脸宽度)
  8. # 我看了一下,这里用到的两个眼镜的原始比例分别是2.5:1和3:1。这里应该可以解释1.8和0.6的关系
  9. w_eyegalss, h_eyeglass = int(width_2_points * 1.8), int(width_2_points * 0.6)

image.png正切函数图:(180为一个周期,即45=45+180)
③ 对眼镜进行归一化(这里的归一化,其实应该是上面计算得到的预期尺寸),并进行高斯滤波

  1. # 将眼镜大小resize至预期尺寸
  2. img_eyeglass_resize = cv2.resize(img_eyeglass, (w_eyegalss, h_eyeglass), interpolation=cv2.INTER_CUBIC)
  3. # 对resize后的眼镜图片进行高斯模糊滤波(高斯模糊是用来做什么的呢?减少图像噪声以及降低细节层次)
  4. img_eyeglass_gaussian = cv2.GaussianBlur(img_eyeglass_resize, (5, 5), 0)

高斯模糊百科:

  • 特点一:对一幅图像进行多次连续高斯模糊的效果与一次更大的高斯模糊可以产生同样的效果
  • 特点二:每个像素的值都是周围相邻像素值的加权平均。原始像素的值有最大的高斯分布值,所以有最大的权重,相邻像素随着距离原始像素越来越远,其权重也越来越小
  • 特点三:理论上来讲,图像中每点的分布都不为零,这也就是说每个像素的计算都需要包含整幅图像。但实际只考虑3σ以内的像素点。
  • 特点四:高斯平滑也用于计算机视觉算法中的预先处理阶段,以增强图像在不同比例大小下的图像效果(参见尺度空间表示以及尺度空间实现)。这就是本节课中,为什么要在resize之后再做高斯模糊的原因。

④ 通过眼部区域的角度,对眼镜进行旋转校正

  1. # 计算图像绕着某一点的旋转矩阵,参数是①旋转中心、②旋转角度、③旋转后图像的缩放比例
  2. rotate = cv2.getRotationMatrix2D((w_eyegalss // 2, h_eyeglass // 2), angle, 1)
  3. # 通过放射变换,将resize后的眼镜图像进行旋转,(w,h)为旋转后的图像的尺寸
  4. img_eyeglass_rotate = cv2.warpAffine(img_eyeglass_gaussian, rotate, (w_eyegalss, h_eyeglass))

note1:在计算旋转矩阵的时候,并没给出图像本身大小。因此这个应该也可以适用于其他图像的旋转(但是旋转角度、图像变换大小固定)。
note2:由于img_eyeglass_gaussian是经resize后的图像处理得到,因此仿射变换前后的图像大小其实一样。

2.2 人脸&贴合

这里主要包括:墨镜抠取+人脸墨镜区域+人脸墨镜贴合这三个部分

2.2.1 抠取墨镜图像

① 彩色图像灰度化
为了便于后面墨镜图像的二值化操作,先使用cv2.cvtColor函数将旋转后的眼镜图像转换成灰度图像。
② 灰度图像二值化
这里目的是通过眼镜的图像,得到人脸上可以佩戴眼镜的区域。利用cv2.threshold函数可以得到眼镜区域。
③ 彩色图像按位与操作
利用cv2.bitwse_and按位与操作,将彩色图像中的眼镜区域抠取出来。

2.2.2 人眼&眼镜贴合

① 黑白眼镜图像取反
为了找出人脸上可以佩戴眼镜的区域,所以对于二值化的眼镜图像,利用cv2.bitwise_not函数进行取反操作。
② 截取人眼区域
利用numpy数组切片的方式,截取一个和眼镜图像大小相当的眼眼睛局部区域
③ 人脸二值化取反
这一步主要是利用cv2.bitwise_and取反操作,将图中的眼镜部分掏空。
④ 人脸眼镜贴合
采用cv2.add按位加操作,将两张图片融合到一起了。

2.2.3 人脸原图调整

将上述处理好的图像再放回原图中。

3、 对视频进行人脸墨镜特效

就是在针对图像处理的基础上加一个视频流的读取操作。视频课讲解

4、对图像进行兔耳特效代码实现

由于兔耳是戴在头上的,所以可以不需要进行 人脸特征点检测。
代码主要流程:加载人脸检测器——加载人脸与兔耳图像——进行人脸检测——将兔耳缩放至合适大小——灰度化、二值化、并进行抠图——在人脸图像中抠出预防止帽子的矩形区域(并进行抠图)——将抠图好的帽子放在抠图好的矩形区域中——最后放回原图中
一些细节说明:

4.1 获取人脸的位置(通过左上角+宽高的方式表示)

  1. x0, y0, width_face, height_face = box_info.left(), box_info.top(), box_info.right() - box_info.left(), box_info.bottom() - box_info.top()

4.2 调整帽子尺寸

主要是高和宽,这里将帽子的高度调为能在图像中看到全貌的尺寸;宽度与人脸相同。

  • imgComposeSizeH这个变量的计算分为两步,①按人脸宽度对帽子进行等比例缩放,由于帽子原图是1:1,所以这一步是将帽子放缩为width_face*width_face大小;②当把帽子放在(人脸最上方-20个像素)的位置时,判断帽子高度是否超过人脸图像。如果是那么将帽子高度调整为刚好从(人脸最上方-20个像素)到人脸图像上边缘。
  • 再按计算好的宽高对帽子图像进行缩放

    1. mgComposeSizeH = int(height_hat / width_hat * width_face)
    2. # 因为帽子在脸部上方,当脸部靠近视频顶部,为了保证帽子能显示出来,帽子的高度默认为脸部到视频上方的距离
    3. if imgComposeSizeH > (y0 - 20):
    4. imgComposeSizeH = (y0 - 20)
    5. # 将帽子的图片归一化到适合的宽和高大小,宽度为人脸的宽度,高度为上面调整后的帽子高度
    6. imgComposeSize = cv2.resize(img_hat, (width_face, imgComposeSizeH), interpolation=cv2.INTER_NEAREST)

    4.3 判断图像是否越界

    缩放之后,大白老师对缩放后的帽子图像进行了是否越界的判断

  • 其实我觉得可能没什么必要,因为条件imgComposeSizeH <= (y0 - 20),可以推出 0 <= (y0 - 20 - imgComposeSizeH) 即 0 <= top。所以top不可能小于0。

    1. top = (y0 - 20 - imgComposeSizeH)
    2. # 当人脸太靠近图片上方,通过前一行的计算,有可能为负数,所以当出现这样的情况时,top设置为0
    3. if top <= 0:
    4. top = 0

    4.4 抠图&替换等常规操作

    上面这个top在哪里用到了呢?

  • ①对人脸图像中的帽子区域做裁剪的时候!

  • 再就是常规的②灰度化、
  • ③二值化 、
  • ④⑤⑥抠图(在裁剪的人脸图像上抠图需要做:取反、再求and;在帽子上直接抠图:直接求and)、
  • ⑦将之前准备好的帽子和扣完图剪裁区域做按位相加、
  • ⑧再把这个处理好的图像返回人脸原图中。
    1. small_img_hat = img[top:top + height_hat_new, x0:x0 + width_hat_new]
    2. small_img_hat_gray = cv2.cvtColor(imgComposeSize, cv2.COLOR_RGB2GRAY)
    3. ret, mask_hat = cv2.threshold(small_img_hat_gray, 10, 255, cv2.THRESH_BINARY)
    4. mask_hat_inv = cv2.bitwise_not(mask_hat)
    5. img1_bg = cv2.bitwise_and(small_img_hat, small_img_hat, mask=mask_hat_inv)
    6. img2_fg = cv2.bitwise_and(imgComposeSize, imgComposeSize, mask=mask_hat)
    7. dst = cv2.add(img1_bg, img2_fg)
    8. img[top:top + height_hat_new, x0:x0 + width_hat_new] = dst
    课程结束 !