python神經(jīng)網(wǎng)絡(luò)facenet人臉檢測(cè)及keras實(shí)現(xiàn)
什么是facenet
最近學(xué)了我最喜歡的mtcnn,可是光有人臉有啥用啊,咱得知道who啊,開(kāi)始facenet提取特征之旅。
谷歌人臉檢測(cè)算法,發(fā)表于 CVPR 2015,利用相同人臉在不同角度等姿態(tài)的照片下有高內(nèi)聚性,不同人臉有低耦合性,提出使用 cnn + triplet mining 方法,在 LFW 數(shù)據(jù)集上準(zhǔn)確度達(dá)到 99.63%。
通過(guò) CNN 將人臉映射到歐式空間的特征向量上,實(shí)質(zhì)上:不同圖片人臉特征的距離較大;通過(guò)相同個(gè)體的人臉的距離,總是小于不同個(gè)體的人臉這一先驗(yàn)知識(shí)訓(xùn)練網(wǎng)絡(luò)。
測(cè)試時(shí)只需要計(jì)算人臉特征EMBEDDING,然后計(jì)算距離使用閾值即可判定兩張人臉照片是否屬于相同的個(gè)體。

簡(jiǎn)單來(lái)講,在使用階段,facenet即是:
1、輸入一張人臉圖片
2、通過(guò)深度學(xué)習(xí)網(wǎng)絡(luò)提取特征
3、L2標(biāo)準(zhǔn)化
4、得到128維特征向量。
代碼下載鏈接:https://pan.baidu.com/s/1T2b5u2mZ9yMtKt3TvLxTaw
提取碼:xmg0
Inception-ResNetV1
Inception-ResNetV1是facenet使用的主干網(wǎng)絡(luò)。
它的結(jié)構(gòu)很有意思!
如圖所示為整個(gè)網(wǎng)絡(luò)的主干結(jié)構(gòu):

可以看到里面的結(jié)構(gòu)分為幾個(gè)重要的部分
1、stem
2、Inception-resnet-A
3、Inception-resnet-B
4、Inception-resnet-C
1、Stem的結(jié)構(gòu):

在facenet里,它的Input為160x160x3大小,輸入后進(jìn)行:
兩次卷積 -> 一次最大池化 -> 兩次卷積
python實(shí)現(xiàn)代碼如下:
inputs = Input(shape=input_shape) # 160,160,3 -> 77,77,64 x = conv2d_bn(inputs, 32, 3, strides=2, padding='valid', name='Conv2d_1a_3x3') x = conv2d_bn(x, 32, 3, padding='valid', name='Conv2d_2a_3x3') x = conv2d_bn(x, 64, 3, name='Conv2d_2b_3x3') # 77,77,64 -> 38,38,64 x = MaxPooling2D(3, strides=2, name='MaxPool_3a_3x3')(x) # 38,38,64 -> 17,17,256 x = conv2d_bn(x, 80, 1, padding='valid', name='Conv2d_3b_1x1') x = conv2d_bn(x, 192, 3, padding='valid', name='Conv2d_4a_3x3') x = conv2d_bn(x, 256, 3, strides=2, padding='valid', name='Conv2d_4b_3x3')
2、Inception-resnet-A的結(jié)構(gòu):

Inception-resnet-A的結(jié)構(gòu)分為四個(gè)分支
1、未經(jīng)處理直接輸出
2、經(jīng)過(guò)一次1x1的32通道的卷積處理
3、經(jīng)過(guò)一次1x1的32通道的卷積處理和一次3x3的32通道的卷積處理
4、經(jīng)過(guò)一次1x1的32通道的卷積處理和兩次3x3的32通道的卷積處理
234步的結(jié)果堆疊后j進(jìn)行一次卷積,并與第一步的結(jié)果相加,實(shí)質(zhì)上這是一個(gè)殘差網(wǎng)絡(luò)結(jié)構(gòu)。
實(shí)現(xiàn)代碼如下:
branch_0 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 32, 3, name=name_fmt('Conv2d_0b_3x3', 1))
branch_2 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_0a_1x1', 2))
branch_2 = conv2d_bn(branch_2, 32, 3, name=name_fmt('Conv2d_0b_3x3', 2))
branch_2 = conv2d_bn(branch_2, 32, 3, name=name_fmt('Conv2d_0c_3x3', 2))
branches = [branch_0, branch_1, branch_2]
mixed = Concatenate(axis=channel_axis, name=name_fmt('Concatenate'))(branches)
up = conv2d_bn(mixed,K.int_shape(x)[channel_axis],1,activation=None,use_bias=True,
name=name_fmt('Conv2d_1x1'))
up = Lambda(scaling,
output_shape=K.int_shape(up)[1:],
arguments={'scale': scale})(up)
x = add([x, up])
if activation is not None:
x = Activation(activation, name=name_fmt('Activation'))(x)
3、Inception-resnet-B的結(jié)構(gòu):

Inception-resnet-B的結(jié)構(gòu)分為四個(gè)分支
1、未經(jīng)處理直接輸出
2、經(jīng)過(guò)一次1x1的128通道的卷積處理
3、經(jīng)過(guò)一次1x1的128通道的卷積處理、一次1x7的128通道的卷積處理和一次7x1的128通道的卷積處理
23步的結(jié)果堆疊后j進(jìn)行一次卷積,并與第一步的結(jié)果相加,實(shí)質(zhì)上這是一個(gè)殘差網(wǎng)絡(luò)結(jié)構(gòu)。
實(shí)現(xiàn)代碼如下:
branch_0 = conv2d_bn(x, 128, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 128, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 128, [1, 7], name=name_fmt('Conv2d_0b_1x7', 1))
branch_1 = conv2d_bn(branch_1, 128, [7, 1], name=name_fmt('Conv2d_0c_7x1', 1))
branches = [branch_0, branch_1]
mixed = Concatenate(axis=channel_axis, name=name_fmt('Concatenate'))(branches)
up = conv2d_bn(mixed,K.int_shape(x)[channel_axis],1,activation=None,use_bias=True,
name=name_fmt('Conv2d_1x1'))
up = Lambda(scaling,
output_shape=K.int_shape(up)[1:],
arguments={'scale': scale})(up)
x = add([x, up])
if activation is not None:
x = Activation(activation, name=name_fmt('Activation'))(x)
4、Inception-resnet-C的結(jié)構(gòu):

Inception-resnet-B的結(jié)構(gòu)分為四個(gè)分支
1、未經(jīng)處理直接輸出
2、經(jīng)過(guò)一次1x1的128通道的卷積處理
3、經(jīng)過(guò)一次1x1的192通道的卷積處理、一次1x3的192通道的卷積處理和一次3x1的128通道的卷積處理
23步的結(jié)果堆疊后j進(jìn)行一次卷積,并與第一步的結(jié)果相加,實(shí)質(zhì)上這是一個(gè)殘差網(wǎng)絡(luò)結(jié)構(gòu)。
實(shí)現(xiàn)代碼如下:
branch_0 = conv2d_bn(x, 192, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 192, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 192, [1, 3], name=name_fmt('Conv2d_0b_1x3', 1))
branch_1 = conv2d_bn(branch_1, 192, [3, 1], name=name_fmt('Conv2d_0c_3x1', 1))
branches = [branch_0, branch_1]
mixed = Concatenate(axis=channel_axis, name=name_fmt('Concatenate'))(branches)
up = conv2d_bn(mixed,K.int_shape(x)[channel_axis],1,activation=None,use_bias=True,
name=name_fmt('Conv2d_1x1'))
up = Lambda(scaling,
output_shape=K.int_shape(up)[1:],
arguments={'scale': scale})(up)
x = add([x, up])
if activation is not None:
x = Activation(activation, name=name_fmt('Activation'))(x)
5、全部代碼
from functools import partial
from keras.models import Model
from keras.layers import Activation
from keras.layers import BatchNormalization
from keras.layers import Concatenate
from keras.layers import Conv2D
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import GlobalAveragePooling2D
from keras.layers import Input
from keras.layers import Lambda
from keras.layers import MaxPooling2D
from keras.layers import add
from keras import backend as K
def scaling(x, scale):
return x * scale
def _generate_layer_name(name, branch_idx=None, prefix=None):
if prefix is None:
return None
if branch_idx is None:
return '_'.join((prefix, name))
return '_'.join((prefix, 'Branch', str(branch_idx), name))
def conv2d_bn(x,filters,kernel_size,strides=1,padding='same',activation='relu',use_bias=False,name=None):
x = Conv2D(filters,
kernel_size,
strides=strides,
padding=padding,
use_bias=use_bias,
name=name)(x)
if not use_bias:
x = BatchNormalization(axis=3, momentum=0.995, epsilon=0.001,
scale=False, name=_generate_layer_name('BatchNorm', prefix=name))(x)
if activation is not None:
x = Activation(activation, name=_generate_layer_name('Activation', prefix=name))(x)
return x
def _inception_resnet_block(x, scale, block_type, block_idx, activation='relu'):
channel_axis = 3
if block_idx is None:
prefix = None
else:
prefix = '_'.join((block_type, str(block_idx)))
name_fmt = partial(_generate_layer_name, prefix=prefix)
if block_type == 'Block35':
branch_0 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 32, 3, name=name_fmt('Conv2d_0b_3x3', 1))
branch_2 = conv2d_bn(x, 32, 1, name=name_fmt('Conv2d_0a_1x1', 2))
branch_2 = conv2d_bn(branch_2, 32, 3, name=name_fmt('Conv2d_0b_3x3', 2))
branch_2 = conv2d_bn(branch_2, 32, 3, name=name_fmt('Conv2d_0c_3x3', 2))
branches = [branch_0, branch_1, branch_2]
elif block_type == 'Block17':
branch_0 = conv2d_bn(x, 128, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 128, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 128, [1, 7], name=name_fmt('Conv2d_0b_1x7', 1))
branch_1 = conv2d_bn(branch_1, 128, [7, 1], name=name_fmt('Conv2d_0c_7x1', 1))
branches = [branch_0, branch_1]
elif block_type == 'Block8':
branch_0 = conv2d_bn(x, 192, 1, name=name_fmt('Conv2d_1x1', 0))
branch_1 = conv2d_bn(x, 192, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 192, [1, 3], name=name_fmt('Conv2d_0b_1x3', 1))
branch_1 = conv2d_bn(branch_1, 192, [3, 1], name=name_fmt('Conv2d_0c_3x1', 1))
branches = [branch_0, branch_1]
mixed = Concatenate(axis=channel_axis, name=name_fmt('Concatenate'))(branches)
up = conv2d_bn(mixed,K.int_shape(x)[channel_axis],1,activation=None,use_bias=True,
name=name_fmt('Conv2d_1x1'))
up = Lambda(scaling,
output_shape=K.int_shape(up)[1:],
arguments={'scale': scale})(up)
x = add([x, up])
if activation is not None:
x = Activation(activation, name=name_fmt('Activation'))(x)
return x
def InceptionResNetV1(input_shape=(160, 160, 3),
classes=128,
dropout_keep_prob=0.8):
channel_axis = 3
inputs = Input(shape=input_shape)
# 160,160,3 -> 77,77,64
x = conv2d_bn(inputs, 32, 3, strides=2, padding='valid', name='Conv2d_1a_3x3')
x = conv2d_bn(x, 32, 3, padding='valid', name='Conv2d_2a_3x3')
x = conv2d_bn(x, 64, 3, name='Conv2d_2b_3x3')
# 77,77,64 -> 38,38,64
x = MaxPooling2D(3, strides=2, name='MaxPool_3a_3x3')(x)
# 38,38,64 -> 17,17,256
x = conv2d_bn(x, 80, 1, padding='valid', name='Conv2d_3b_1x1')
x = conv2d_bn(x, 192, 3, padding='valid', name='Conv2d_4a_3x3')
x = conv2d_bn(x, 256, 3, strides=2, padding='valid', name='Conv2d_4b_3x3')
# 5x Block35 (Inception-ResNet-A block):
for block_idx in range(1, 6):
x = _inception_resnet_block(x,scale=0.17,block_type='Block35',block_idx=block_idx)
# Reduction-A block:
# 17,17,256 -> 8,8,896
name_fmt = partial(_generate_layer_name, prefix='Mixed_6a')
branch_0 = conv2d_bn(x, 384, 3,strides=2,padding='valid',name=name_fmt('Conv2d_1a_3x3', 0))
branch_1 = conv2d_bn(x, 192, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1, 192, 3, name=name_fmt('Conv2d_0b_3x3', 1))
branch_1 = conv2d_bn(branch_1,256,3,strides=2,padding='valid',name=name_fmt('Conv2d_1a_3x3', 1))
branch_pool = MaxPooling2D(3,strides=2,padding='valid',name=name_fmt('MaxPool_1a_3x3', 2))(x)
branches = [branch_0, branch_1, branch_pool]
x = Concatenate(axis=channel_axis, name='Mixed_6a')(branches)
# 10x Block17 (Inception-ResNet-B block):
for block_idx in range(1, 11):
x = _inception_resnet_block(x,
scale=0.1,
block_type='Block17',
block_idx=block_idx)
# Reduction-B block
# 8,8,896 -> 3,3,1792
name_fmt = partial(_generate_layer_name, prefix='Mixed_7a')
branch_0 = conv2d_bn(x, 256, 1, name=name_fmt('Conv2d_0a_1x1', 0))
branch_0 = conv2d_bn(branch_0,384,3,strides=2,padding='valid',name=name_fmt('Conv2d_1a_3x3', 0))
branch_1 = conv2d_bn(x, 256, 1, name=name_fmt('Conv2d_0a_1x1', 1))
branch_1 = conv2d_bn(branch_1,256,3,strides=2,padding='valid',name=name_fmt('Conv2d_1a_3x3', 1))
branch_2 = conv2d_bn(x, 256, 1, name=name_fmt('Conv2d_0a_1x1', 2))
branch_2 = conv2d_bn(branch_2, 256, 3, name=name_fmt('Conv2d_0b_3x3', 2))
branch_2 = conv2d_bn(branch_2,256,3,strides=2,padding='valid',name=name_fmt('Conv2d_1a_3x3', 2))
branch_pool = MaxPooling2D(3,strides=2,padding='valid',name=name_fmt('MaxPool_1a_3x3', 3))(x)
branches = [branch_0, branch_1, branch_2, branch_pool]
x = Concatenate(axis=channel_axis, name='Mixed_7a')(branches)
# 5x Block8 (Inception-ResNet-C block):
for block_idx in range(1, 6):
x = _inception_resnet_block(x,
scale=0.2,
block_type='Block8',
block_idx=block_idx)
x = _inception_resnet_block(x,scale=1.,activation=None,block_type='Block8',block_idx=6)
# 平均池化
x = GlobalAveragePooling2D(name='AvgPool')(x)
x = Dropout(1.0 - dropout_keep_prob, name='Dropout')(x)
# 全連接層到128
x = Dense(classes, use_bias=False, name='Bottleneck')(x)
bn_name = _generate_layer_name('BatchNorm', prefix='Bottleneck')
x = BatchNormalization(momentum=0.995, epsilon=0.001, scale=False,
name=bn_name)(x)
# 創(chuàng)建模型
model = Model(inputs, x, name='inception_resnet_v1')
return model
檢測(cè)人臉并實(shí)現(xiàn)比較:
利用opencv自帶的cv2.CascadeClassifier檢測(cè)人臉并實(shí)現(xiàn)人臉的比較:根目錄擺放方式如下:

demo文件如下:
import numpy as np
import cv2
from net.inception import InceptionResNetV1
from keras.models import load_model
import face_recognition
#---------------------------------#
# 圖片預(yù)處理
# 高斯歸一化
#---------------------------------#
def pre_process(x):
if x.ndim == 4:
axis = (1, 2, 3)
size = x[0].size
elif x.ndim == 3:
axis = (0, 1, 2)
size = x.size
else:
raise ValueError('Dimension should be 3 or 4')
mean = np.mean(x, axis=axis, keepdims=True)
std = np.std(x, axis=axis, keepdims=True)
std_adj = np.maximum(std, 1.0/np.sqrt(size))
y = (x - mean) / std_adj
return y
#---------------------------------#
# l2標(biāo)準(zhǔn)化
#---------------------------------#
def l2_normalize(x, axis=-1, epsilon=1e-10):
output = x / np.sqrt(np.maximum(np.sum(np.square(x), axis=axis, keepdims=True), epsilon))
return output
#---------------------------------#
# 計(jì)算128特征值
#---------------------------------#
def calc_128_vec(model,img):
face_img = pre_process(img)
pre = model.predict(face_img)
pre = l2_normalize(np.concatenate(pre))
pre = np.reshape(pre,[1,128])
return pre
#---------------------------------#
# 獲取人臉框
#---------------------------------#
def get_face_img(cascade,filepaths,margin):
aligned_images = []
img = cv2.imread(filepaths)
img = cv2.cvtColor(img,cv2.COLOR_BGRA2RGB)
faces = cascade.detectMultiScale(img,
scaleFactor=1.1,
minNeighbors=3)
(x, y, w, h) = faces[0]
print(x, y, w, h)
cropped = img[y-margin//2:y+h+margin//2,
x-margin//2:x+w+margin//2, :]
aligned = cv2.resize(cropped, (160, 160))
aligned_images.append(aligned)
return np.array(aligned_images)
#---------------------------------#
# 計(jì)算人臉距離
#---------------------------------#
def face_distance(face_encodings, face_to_compare):
if len(face_encodings) == 0:
return np.empty((0))
return np.linalg.norm(face_encodings - face_to_compare, axis=1)
if __name__ == "__main__":
cascade_path = './model/haarcascade_frontalface_alt2.xml'
cascade = cv2.CascadeClassifier(cascade_path)
image_size = 160
model = InceptionResNetV1()
# model.summary()
model_path = './model/facenet_keras.h5'
model.load_weights(model_path)
img1 = get_face_img(cascade,r"img/Larry_Page_0000.jpg",10)
img2 = get_face_img(cascade,r"img/Larry_Page_0001.jpg",10)
img3 = get_face_img(cascade,r"img/Mark_Zuckerberg_0000.jpg",10)
print(face_distance(calc_128_vec(model,img1),calc_128_vec(model,img2)))
print(face_distance(calc_128_vec(model,img2),calc_128_vec(model,img3)))
實(shí)現(xiàn)效果為:
[0.6534328]
[1.3536944]
以上就是python神經(jīng)網(wǎng)絡(luò)facenet人臉檢測(cè)及keras實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于facenet人臉檢測(cè)keras實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python漢字轉(zhuǎn)拼音pypinyin庫(kù)、輸出excel的xlwt庫(kù)
本文介紹了如何使用Python的pypinyin庫(kù)和xlwt庫(kù),將漢字文本轉(zhuǎn)換為帶有拼音標(biāo)注的Excel文件,通過(guò)讀取文本、獲取拼音并寫(xiě)入Excel,實(shí)現(xiàn)了漢字和拼音的一一對(duì)應(yīng),同時(shí),文章也指出了潛在的問(wèn)題2025-04-04
Python3 虛擬開(kāi)發(fā)環(huán)境搭建過(guò)程(圖文詳解)
這篇文章主要介紹了Python3 虛擬開(kāi)發(fā)環(huán)境搭建過(guò)程,本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
Python運(yùn)行的17個(gè)時(shí)新手常見(jiàn)錯(cuò)誤小結(jié)
當(dāng)初學(xué) Python 時(shí),想要弄懂 Python 的錯(cuò)誤信息的含義可能有點(diǎn)復(fù)雜。這里列出了常見(jiàn)的的一些讓你程序 crash 的運(yùn)行時(shí)錯(cuò)誤2012-08-08
Python實(shí)現(xiàn)指定范圍內(nèi)篩選并剔除Excel表格中的數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Python如何實(shí)現(xiàn)在指定范圍內(nèi)篩選并剔除Excel表格中的數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-06-06
PHP基于phpqrcode類(lèi)庫(kù)生成二維碼過(guò)程解析
這篇文章主要介紹了PHP基于phpqrcode類(lèi)庫(kù)生成二維碼過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Python數(shù)據(jù)庫(kù)編程之pymysql詳解
本文主要介紹了Python數(shù)據(jù)庫(kù)編程中pymysql,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
Python實(shí)現(xiàn)簡(jiǎn)易的圖書(shū)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Python實(shí)現(xiàn)簡(jiǎn)易的圖書(shū)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
python numpy--數(shù)組的組合和分割實(shí)例
這篇文章主要介紹了python numpy--數(shù)組的組合和分割實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02
python錯(cuò)誤提示:Errno?2]?No?such?file?or?directory的解決方法
我相信很多人在學(xué)習(xí)Python的時(shí)候,特別是在open文件的時(shí)候總還碰到,還報(bào)錯(cuò)IOError:[Errno?2]沒(méi)有這樣的文件或目錄:'E://aaa.txt',這篇文章主要給大家介紹了關(guān)于python錯(cuò)誤提示:Errno?2]?No?such?file?or?directory的解決方法,需要的朋友可以參考下2022-02-02
python詞云庫(kù)wordcloud的使用方法與實(shí)例詳解
這篇文章主要介紹了python詞云庫(kù)wordcloud的使用方法與實(shí)例詳解,需要的朋友可以參考下2020-02-02

