超详细的人脸检测:MTCNN代码分析,手把手带你从零开始码代码

2023-11-09

前言

之前我发过MTCNN的原理分析,不了解的朋友可以看看深刻理解MTCNN原理, 超级详细,从零开始做人脸检测。Tensorflow2实现
授人以鱼,不如授人以渔,本文与其他代码分析的文章不一样,本文不会涉及原理过多的东西,原理参照我的上一片文章,主要以代码段的思路讲解,从最开始得到预训练权重后,怎么解析参数,怎么一步一步构建代码框架。可以执行获得最后结果,优化部分留给读者自行钻研!本文尽量不出现大段代码。**基于Tensorflow2.0/keras。讲的主要是思路,别的框架读者一样可以写出来
我的
参考代码
https://github.com/Luo-DH/MTCNN_test欢迎给我个star,如果你喜欢的话,欢迎给我点个赞,加个关注!!!


准备

  • 我们需要pnet.h5, rnet.h5, onet.h5,我的github里面可以拿到这三个网络的预训练权重
  • 还需要一个能打代码的东西,可以是记事本,可以是vim,可以是pycharm,推荐使用jupyter notebook,方便看到每一步的结果。
  • 热情,毅力

开始

思路
  1. 环境搭建
  2. 整体流程

环境搭建

推荐大家使用虚拟环境,如果你是小白不了解虚拟环境,可以安装anaconda我以前用的anaconda,后来用上virtualenvwrapper就再也不想用anaconda了。害!!!

  1. 搭建环境LINUX
# 新建一个文件夹
mkdir mtcnn_test
# 进入文件夹目录
cd mtcnn_test
# 查看文件夹的内容
ll # 是个空文件夹
# 创建虚拟环境
mkvirtualenv ENV_TF2
# 安装相关的包和模块
pip install tensorflow
pip install opencv-python
pip install numpy
pip install notebook

使用windows或者pycharm类似,只需要将相关的包和模块安装上就行.

  1. 整体流程
  • 获取图片
  • 传入pnet网络
  • 将pnet网络获得的输出传入rnet网络
  • 将rnet网络获得的输出传入onet网络
  • 得到最后输出的矩形框

获取图片
import cv2
image = cv2.imread("./face.jpg")# 读取的图片最好是正方形,
                                # 虽然mtcnn支持不同尺寸的图片输入,我们开始
                                # 使用正方形比较直观

推荐使用opencv读取图片。主要是我个人用的习惯而已啦!当然可以使用其他方式读取图片。需要注意的是opencv读取的图片颜色通道是BGR,而我们需要获得RGB顺序通道的图片.

# 如果是用opencv读取图片,一定要走这一步
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
图像金字塔

论文中提到将图像进行不同比例的缩放,以便于获得不同大小的人脸,具体原因可以看我之前的博客,有详细说明。

factor = 0.709 比例因子,一般用这个比例进行缩放

  • 获得缩放比例
factor = 0.709
image_width = image.shape[0]
scales = [(factor**i) for i in range(0, 10) if (factor**i)*image_width > 12]
  • 将图片进行缩放
#创建一个列表,用于存放pnet输入所需要的图片
imgs = []
for scale in scales:
    new_width = int(image.shape[0] * scale) # 必须是整数才能作为新图像的边长
    new_height = int(image.shape[1] * scale)
    # 进行缩放
    img_ =  cv2.resize(image.copy(), (new_width, new_height))
	
    imgs.append(img_)
	
图片预处理
pnet_need_imgs = []
for img in imgs:
    img = (img - 127.5)/127.5 # 归一化
	# 归一化的具体原因此处不展开
    # 修改图片的维数为四维(网络的输入必须是思维)
    img = img.reshape(1, *img.shape)
    # shape=(1, x, x, 3)
    pnet_need_imgs.append(img)# 获得pnet的输入

目前为止我们已经得到了pnet的输入,另外一种获得方法可以参考我github上的代码,效率会更高。之后我们来构建pnet网络的结构和读取预训练权重

pnet网络初始化

这一部分不需要理解,就只是按照论文的网络架构读取,读者可以直接复制

import tensorflow as tf
print("Tensorflow Version: {}".format(tf.__version__)) # 2.0以上

    def create_model():
        """定义PNet网络的架构"""
        input = tf.keras.Input(shape=[None, None, 3])
        x = tf.keras.layers.Conv2D(10, (3, 3), strides=1,padding='valid', name='conv1')(input)
        x = tf.keras.layers.PReLU(shared_axes=[1, 2],name='PReLU1')(x)
        x = tf.keras.layers.MaxPooling2D()(x)
        x = tf.keras.layers.Conv2D(16, (3, 3), strides=1,padding='valid',name='conv2')(x)
        x = tf.keras.layers.PReLU(shared_axes=[1, 2],name='PReLU2')(x)
        x = tf.keras.layers.Conv2D(32, (3, 3),strides=1, padding='valid', name='conv3')(x)
        x = tf.keras.layers.PReLU(shared_axes=[1, 2],name='PReLU3')(x)

        classifier = tf.keras.layers.Conv2D(2, (1, 1),activation='softmax',name='conv4-1')(x)
        bbox_regress = tf.keras.layers.Conv2D(4, (1, 1),name='conv4-2')(x)

        model = tf.keras.models.Model([input], [classifier, bbox_regress])

        return model
    
    # 创建模型
    model = create_model()
    #model.summary() 可以看到网络的架构
    model.load_weights("./pnet.h5", by_names=True)# 读取权重,输入pnet.h5的地址

模型已经处理完毕,只需要把图片一张一张输进去就可以了

输入网络预测
outs = [] # 用来存放输出的结果
for img in pnet_need_imgs:
    # 一张一张图片输入
    out = model.predict(img)
    
    outs.append(out)

此时已经获得了pnet网络的输出结果,结果的具体含义参考我之前的博客,此处依旧不展开。下一步是解析这些输出结果

结果解析

out的内容包括了人脸的置信度还有对应的偏移量的值,我们只需要拿到置信度大于阈值的那个out,然后拿到这个out的偏移量,将偏移量加上坐标的值,就可以获得预测的人脸框

我用一个out举例子,上面得到的outs,只需要做个循环就可以得到结果

先获得人脸置信度
cls_prob = out[0][:, :, 1]
(x, y) = np.where(cls_prob > 0.5) # 假设阈值是0.5
scores= np.array(out[0][i, x, y, 1][np.newaxis, :].T) # 把阈值拿出来,后面需要用到
# 此时我们已经获得了大于阈值的位置
# 我们需要把对应位置的偏移量拿出来
offset = out[1][i, x, y]*12*(1/scale) # scale是这张图片对应的缩放比例,我要要把图像
																	  # 还原成原来的比例,所以需要乘以(1/scale)
获得对应的矩形框的坐标
# 获得矩形框的坐标
bbx = np.array((y, x)).T
# 左上角的坐标
left_top = np.fix(((bbx * 2) + 0) * (1/scale)) 
# 右下角的坐标
right_down = np.fix(((bbx * 2) + 11) * (1/scale))
# 获得矩形框的坐标 [x1, y1, x2, y2]
boundingbox = np.concatenate((left_top, right_down), axis=1)
将矩形框的坐标与偏移量的坐标相加
#将矩形框和偏移量相加
boundingbox = boundingbox + offset

此时我们得到的矩形框坐标[x1,y1,x2,y2]就是解析过后的值,可是这个值仍然可能出现问题,比如出现负数或者x2>x1的情况,所以我们还需要简单处理一下

其他处理
# 把矩形框和得分放在一起,方便后面作非极大值抑制
boundingbox = np.concatenate((boundingbox, scores), axis=1)

处理就是将值简单的微调,都可以处理成函数,具体的函数可以参考我的代码,主要在src/Net.py中,主要是_nms(), _rect2square(), _trimming_frame()这三那个函数,就不放上来了,可以直接调用

rects = _rect2square(boundingbox)
rects = _trimming_frame(rects)
rects = _nms(rects, 0.7)
pnet总结

此时我们已经完全获取了pnet处理后得到的矩形框,读者可以用以下函数来查看所获得的矩形框

for rect in rects:
    img_ = cv2.rectangle(image.copy(), (int(rect[0]), int(rect[1])), (int(rect[0]), int(rect[1])), (0, 255, 0), 4)
    
    cv2.imshow("img_", img_)
    cv2.waitKey(0)
    cv2.destroyAllWIndows()
rnet部分
处理获得rnet输入所需要的图片

把pnet得到的矩形框在原图中截取出来

rnet_need_imgs = []
for rect in rects:

    tmp_roi = image.copy()[int(rect[1]): int(rect[3]), \
                           int(rect[0]): int(rect[2])]

    # resize成24x24大小
    tmp_roi = cv2.resize(tmp_roi, (24, 24))

    rnet_need_imgs.append(tmp_roi)

处理rnet网络结构

以下代码一样读者可以直接复制,基本没有可以修改的地方

def create_model(cls):
    """定义RNet网络的架构"""
    input = tf.keras.Input(shape=[24, 24, 3])
    x = tf.keras.layers.Conv2D(28, (3, 3), strides=1, padding='valid', name='conv1')(input)
    x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='prelu1')(x)
    x = tf.keras.layers.MaxPooling2D(pool_size=3, strides=2, padding='same')(x)

    x = tf.keras.layers.Conv2D(48, (3, 3), strides=1, padding='valid', name='conv2')(x)
    x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='prelu2')(x)
    x = tf.keras.layers.MaxPooling2D(pool_size=3, strides=2)(x)

    x = tf.keras.layers.Conv2D(64, (2, 2), strides=1, padding='valid', name='conv3')(x)
    x = tf.keras.layers.PReLU(shared_axes=[1, 2], name='prelu3')(x)

    x = tf.keras.layers.Permute((3, 2, 1))(x)
    x = tf.keras.layers.Flatten()(x)

    x = tf.keras.layers.Dense(128, name='conv4')(x)
    x = tf.keras.layers.PReLU(name='prelu4')(x)

    classifier = tf.keras.layers.Dense(2, activation='softmax', name='conv5-1')(x)
    bbox_regress = tf.keras.layers.Dense(4, name='conv5-2')(x)

    model = tf.keras.models.Model([input], [classifier, bbox_regress])

    return model

model = create_model()

model.load_weights("./rnet.h5", by_names=True)
输入网络进行预测
out = model.predict(rnet_need_imgs)

rnet不需要循环放入网络,图片被一次传入了网络,得到了所有的结果

处理结果
classifier = out[0]
x = np.where(classifier[:, 1] > 0.6) # 阈值设定为0.6
获取boundingbox的值有点复杂
# 获得相应位置的offset值
offset = out[1]
offset = offset[x, None]

dx1 = np.array(offset[0])[:, :, 0]
dy1 = np.array(offset[0])[:, :, 1]
dx2 = np.array(offset[0])[:, :, 2]
dy2 = np.array(offset[0])[:, :, 3]

# 我们需要用到pnet获得的矩形框的值
pnet_got_rects = np.array(rects)

通过pnet的rects,我们可以拿到对应的宽和高,用于还原图大小

x1 = np.array(pnet_got_rects[x][:, 0])[np.newaxis, :].T
y1 = np.array(pnet_got_rects[x][:, 1])[np.newaxis, :].T
x2 = np.array(pnet_got_rects[x][:, 2])[np.newaxis, :].T
y2 = np.array(pnet_got_rects[x][:, 3])[np.newaxis, :].T

w = x2 - x1
h = y2 - y1

new_x1 = np.fix(x1 + dx1*w)
new_x2 = np.fix(x2 + dx2*w)
new_y1 = np.fix(y1 + dy1*h)
new_y2 = np.fix(y2 + dy2*h)
score = np.array(classifier[x, 1]).T

boundingbox = np.concatenate((new_x1, 
                              new_y1, 
                              new_x2, 
                              new_y2, 
                              score), axis=1)

此时,我们已经得到了rnet的输出结果,我们需和pnet相同的处理

rects = _rect2square(boundingbox)
rects = _trimming_frame(rects)
rects = _nms(rects, 0.7)
rnet总结

我们已经获得了rnet的处理结果,和上面方法一样,可以画出图片查看以下

onet部分

留给读者思考,处理方式和rnet差不多,有疑问可以和我联系!

结语

代码可能有很多不清晰的地方,建议读者多看几次,特别是关于mtcnn原理的解读,更加方便看懂此篇文章。如果有疑问欢迎与我联系,如果对你有帮助,可以给我点个赞,加个关注,谢谢!


参考代码https://github.com/Luo-DH/MTCNN_test

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

超详细的人脸检测:MTCNN代码分析,手把手带你从零开始码代码 的相关文章

随机推荐

  • 关于2020HUEL_ACM暑期集训八月集训计划变更

    时间飞逝若白驹过隙 转眼2020疫情期间这个特殊的暑假已经过去一半 因为一些原因我们第二个月不与hky一起训练了 正常周赛 咱们的集训计划也会发生相应的改变 具体如下 时间安排 八月共四个周 第一周 继续学习最小生成树 2天 和最短路 4天
  • 屏幕适配方案

    屏幕dp px换算公式 其中width height为我们的手机屏幕宽高 即1920 1080 inch为对角尺寸 常见5 5寸 5 0寸等 public void getAndroiodScreenProperty WindowManag
  • 机器学习集成模型学习——投票集成Voting(二)

    Voting集成 投票机制就是多个模型分别预测 然后投票 票数最高的就是整个模型最后的效果 案例代码 from sklearn linear model import LogisticRegression from sklearn naiv
  • github访问很慢的问题

    github访问很慢的问题 公司一直用着svn 之前也的确用过github的版本管理 但是一直都是可视化的操作 这几天面试了几名前端 问了一下发现他们在之前的公司里都是用git的 于是今天好好温故了一下怎么用命令行进行一下git操作 首先要
  • R语言合并数据框的行与列

    合并数据框的行与列 大家好 这里是想做生信大恐龙 的生信小白 先赞后看养成习惯 还没关注的小伙伴点点关注不迷路 今天熟悉数据操作中的数据合并 数据合并 合并数据框的行与列 一 rbind 和cbind 函数 二 merge 函数 总结 一
  • 【日常实用】git rebase合并多笔为一笔提交

    前言 在日常开发中 经常会遇到master分支和自己的分支已经渐行渐远 分叉久已 此时并不能用get merge 的方式合并代码了 这个时候想要将多笔提交合并成一笔并解决好冲突提交话 get rebase 就派上了用场了 举例说明 一 新建
  • kibana使用

    数据导出 查询完数据后 数据量很大 我们要对数据进行分析 可以导出来用excel分析 导出很简单 就是先保存后分享就可以了 使用kibana界面导出数据 我们要进行重复行统计的时候 可以使用excel的数据透视表 很强大 excel如何快速
  • EM算法

    尽管最大似然估计 MLE 和EM都可以找到 最佳拟合 参数 但是它们查找模型的方式却大不相同 MLE首先累积所有数据 然后使用该数据构建最可能的模型 EM首先对参数进行猜测 考虑丢失的数据 然后对模型进行调整以适合猜测和观察到的数据 该算法
  • 【IDEA】IDEA git log 点击 没有代码变更

    文章目录 1 概述 1 概述 Git log 下查看分支提交的信息 结果只有提交信息 没有代码变更信息 如下图 这里有坑 不是消失了 是下拉框拉倒最上面了 这里有条线 往下拉就能看到了
  • 使用kubeadm工具升级kubernetes

    一 背景 kubeadm部署的kubernetes集群进行升级 通常先升级控制节点 控制节点升级完成后再升级工作节点 本博文只升级了控制节点 工作节点按照相同的流程进行升级即可 环境说明 主机名 节点 11 0 1 200 k8s客户端操控
  • show_space存储过程脚本

    CREATE OR REPLACE PROCEDURE show space p segname 1 IN VARCHAR2 p type 1 IN VARCHAR2 DEFAULT TABLE p space IN VARCHAR2 DE
  • 程序下载微云

    https share weiyun com 5OxmUiI
  • python/sympy计算施密特正交化向量

    sympy的符号计算功能很强大 学习矩阵分析 重温了线性代数中施密特正交化的方法 正好可以用sympy解决一些计算问题 施密特正交化 也称 Gram Schmidt 正交化过程 Gram Schmidt Orthogonalization
  • 【linux】01 dnf 命令安装(新的代替yum 的安装方式)

    一 dnf 介绍 参考文章 DNF 是新一代的 rpm 软件包管理器 他首先出现在 Fedora 18 这个发行版中 而最近 它取代了 yum 正式成为 Fedora 22 的包管理器 DNF 包管理器克服了 YUM 包管理器的一些瓶颈 提
  • jdk8特性Collections.sort()的使用

    Java中Collections sort 的使用学习记录 在日常开发中 很多时候都需要对一些数据进行排序的操作 然而那些数据一般都是放在一个集合中如 Map Set List 等集合中 他们都提共了一个排序方法 sort 要对数据排序直接
  • k8s服务(service)详解

    目录 1 Service介绍 1 1 kube proxy支持的工作模式 userspace 模式 iptables 模式 ipvs 模式 2 Service类型 3 Service使用 3 1 实验环境准备 3 2 ClusterIP类型
  • vue3中使用jsx报错React is not defined和h is not defined

    js项目中jsx的使用 js项目引入Vue 3 Babel JSX 插件 修改vite config js import defineConfig from vite import vue from vitejs plugin vue im
  • python定时器

    一 定时器 在实际应用中 我们需要使用定时器去触发一些事件 Python中的定时器是timer 首先看个例子 import datetime 导入datetime模块 import threading 导入threading模块 def r
  • Hbase Java API 介绍

    Hbase Java API 介绍 几个主要 Hbase API 类和数据模型之间的对应关系 1 HBaseAdmin 关系 org apache hadoop hbase client HBaseAdmin 作用 提供了一个接口来管理 H
  • 超详细的人脸检测:MTCNN代码分析,手把手带你从零开始码代码

    前言 之前我发过MTCNN的原理分析 不了解的朋友可以看看深刻理解MTCNN原理 超级详细 从零开始做人脸检测 Tensorflow2实现 授人以鱼 不如授人以渔 本文与其他代码分析的文章不一样 本文不会涉及原理过多的东西 原理参照我的上一