Windows 10+dlib19.17+face_recognition:使用人脸识别对家庭照片进行分类,2.43秒/张,速度提高40%以上(三)多进程+健壮性
在Windows 10环境中,我们使用dlib19.17+face_recognition对家庭照片进行分类,速度可以达到4-6秒/张,相关内容请参考:
Windows 10+dlib19.17+face_recognition:使用人脸识别对家庭照片进行分类,4-6秒/张(一)单线程
我们对这样的速度不太满意,打算提高它的运行速度(或者说:缩短整个任务的运行时间),在上一篇文章中我们发现Python的多线程不能提高并行效率,但可以用同时运行多个程序来缩短任务运行时间:
Windows 10+dlib19.17+face_recognition:使用人脸识别对家庭照片进行分类,4-6秒/张(二)并行加速
(1)多进程并行加速
在本文中,我们尝试用Python多进程来提高任务执行效率,代码如下:
# -*- coding: UTF-8 -*-
import dlib
import face_recognition
import numpy as np
from multiprocessing import Process
from multiprocessing import Pool
from datetime import datetime
import os
import shutil
#全局变量
number_process = 3
#人脸检测进程,将文件名转化为整数,按整除的余数分类待处理文件,若命中则复制文件到指定目录
def face_check_and_copy(num):
# 从图片中加载已知的人脸并获得编码
steve_image = face_recognition.load_image_file("f:/images/steve.jpg")
steve_face_encoding = face_recognition.face_encodings(steve_image)[0]
lucy_image = face_recognition.load_image_file("f:/images/lucy.jpg")
lucy_face_encoding = face_recognition.face_encodings(lucy_image)[0]
known_face_encodings = [
steve_face_encoding,
lucy_face_encoding,
]
known_face_names = [
"Steve",
"Lucy",
]
count_checked, count_copied, count_failed = 0, 0, 0
# 遍历目录下的所有.jpg文件
f = os.walk("f:\images")
for path, d, filelist in f:
for filename in filelist:
if filename.endswith('jpg'):
#将文件名的字符转化为ASCII码,累计得到一个整数
only_name = os.path.splitext(filename)[0]
int_filename = 0
for every_char in only_name:
int_filename += ord(every_char)
if int_filename % number_process == num:
image_path = os.path.join(path, filename)
# 加载图片
try:
unknown_image = face_recognition.load_image_file(image_path)
except:
print('%s.jpg load_image_file() failed.' %only_name)
count_failed += 1
continue
# 找到图中所有人脸的位置
try:
face_locations = face_recognition.face_locations(unknown_image)
except:
print('%s.jpg face_location() failed.' %only_name)
count_failed += 1
continue
# face_locations = face_recognition.face_locations(unknown_image, number_of_times_to_upsample=0, model="cnn")
# 根据位置加载人脸编码的列表
try:
face_encodings = face_recognition.face_encodings(unknown_image, face_locations)
count_checked += 1
except:
print('%s.jpg face_encodings() failed.' %only_name)
count_failed += 1
continue
# 遍历所有人脸编码,与已知人脸对比
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
# 获得对比结果,tolerance值越低比对越严格
try:
matches = face_recognition.compare_faces(known_face_encodings, face_encoding, tolerance=0.4)
except:
print('%s.jpg compare_faces() failed.' % only_name)
count_failed += 1
break
# 获得比对成功的姓名(未做进一步处理),并复制文件到指定目录
name = "Unknown"
if True in matches:
first_match_index = matches.index(True)
name = known_face_names[first_match_index]
# 若同一个图片中有多个已知人脸,重复复制文件会报错
try:
shutil.copy(image_path, "f:/images_family")
count_copied += 1
break
except shutil.Error:
break
print('No.%d process: %d pictures checked, %d pics copied, and %d pics failed.' %(num, count_checked, count_copied, count_failed))
#主进程开始
if __name__ == '__main__':
#显示有几个CPU(硬件CPU的线程数),Pool()最大进程数默认为CPU的个数
print('%d CPUs found.' %os.cpu_count())
# 测试起始时间
t1 = datetime.now()
#创建子进程池,子进程异步并发执行
poo = Pool()
for i in range(number_process):
poo.apply_async(face_check_and_copy, (i,))
# 关闭子进程池
poo.close()
# 等待所有子进程结束
poo.join()
# 测试结束时间
t2 = datetime.now()
# 显示总的时间开销
print('time spend: %d seconds, %d microseconds.' %((t2-t1).seconds, (t2-t1).microseconds))
测试结果表明,2个进程并发执行,相比单线程时的205秒,50张图片的人脸识别任务的总运行时间减少了40%,体现出并行计算加速的效果(因为把“从图片中加载已知的人脸并获得编码”的处理放到了每个子进程中,因此稍微增加了子进程的耗时),CPU的利用率大概在33%-74%之间,瞬间有摸高到100%;内存使用率在75%-85%之间,最高到94%:

3个进程并发执行,任务的总运行时间减少了50%,CPU的利用率在50%-60%左右,内存使用率在85%以上,效率提高一倍:

使用3个进程对更大规模的文件集进行处理,58个文件夹,1841个文件(包括:图片,非图片),共7.44GB,程序检查了1690张图片,发现其中135张包含已知人脸并复制到指定目录下,共运行4107秒(1小时8分27秒),处理速度平均2.43秒/张,与之前单线程(也是单进程)最快的处理速度4.1秒/张比较,处理速度提高也超过了40%,这样的速度就可以满足一般的实用要求了:

但当我们设定number_process = 4或者更大的数字时,由于电脑内存不足(笔记本电脑的内存为8GB),face_recognition函数报错,会出现加载图片数据(load_image_file)失败或者放弃对目标图片进行人脸识别(face_locations)的情况。
所以,在使用多进程并行加速的时候,一方面要尽量挖掘硬件资源的潜力,同时也要考虑软件库函数的健壮性及可扩展性问题。
(2)增强应用的健壮性
我们在不改变底层软件库的情况下,试图在应用层层面加强整个程序的健壮性,思路是:当face_recognition函数报错,所在的应用进程进入睡眠,过几秒后再次调用face_recognition函数,直至失败次数达到程序设定的上限为止。代码如下:
# -*- coding: UTF-8 -*-
import dlib
import face_recognition
import numpy as np
from multiprocessing import Process
from multiprocessing import Pool
from datetime import datetime
import time
import os
import shutil
#全局变量:进程数, 函数调用失败时的重试次数
number_process = 4
times_retry = 20
#人脸检测进程,将文件名转化为整数,按整除的余数分类待处理文件,若命中则复制文件到指定目录
def face_check_and_copy(num):
# 从图片中加载已知的人脸并获得编码
steve_image = face_recognition.load_image_file("f:/images/steve.jpg")
steve_face_encoding = face_recognition.face_encodings(steve_image)[0]
lucy_image = face_recognition.load_image_file("f:/images/lucy.jpg")
lucy_face_encoding = face_recognition.face_encodings(lucy_image)[0]
known_face_encodings = [
steve_face_encoding,
lucy_face_encoding,
]
known_face_names = [
"Steve",
"Lucy",
]
count_checked, count_copied, count_failed = 0, 0, 0
fail_flag = 0
# 遍历目录下的所有.jpg文件
f = os.walk("f:/images")
for path, d, filelist in f:
for filename in filelist:
if filename.endswith('jpg'):
#将文件名的字符转化为ASCII码,累计得到一个整数
only_name = os.path.splitext(filename)[0]
int_filename = 0
for every_char in only_name:
int_filename += ord(every_char)
if int_filename % number_process == num:
image_path = os.path.join(path, filename)
# 加载图片,失败时重试
for i in range(times_retry):
try:
unknown_image = face_recognition.load_image_file(image_path)
except:
if i == times_retry - 1:
count_failed += 1
print('%s.jpg load_image_file() failed.' %only_name)
fail_flag = 1
break
print('%s.jpg load_image_file() retried %d times.' %(only_name, i+1))
time.sleep(i + 1)
continue
else:
break
if fail_flag == 1:
fail_flag = 0
continue
# 找到图中所有人脸的位置,失败时重试
for i in range(times_retry):
try:
face_locations = face_recognition.face_locations(unknown_image)
# face_locations = face_recognition.face_locations(unknown_image, number_of_times_to_upsample=0, model="cnn")
except:
if i == times_retry - 1:
count_failed += 1
print('%s.jpg face_locations() failed.' %only_name)
fail_flag = 1
break
print('%s.jpg face_locations() retried %d times.' %(only_name, i+1))
time.sleep(i + 1)
continue
else:
break
if fail_flag == 1:
fail_flag = 0
continue
# 根据位置加载人脸编码的列表,失败时重试
for i in range(times_retry):
try:
face_encodings = face_recognition.face_encodings(unknown_image, face_locations)
except:
if i == times_retry - 1:
count_failed += 1
print('%s.jpg face_encodings() failed.' %only_name)
fail_flag = 1
break
print('%s.jpg face_encodings() retried %d times.' %(only_name, i+1))
time.sleep(i + 1)
continue
else:
count_checked += 1
break
if fail_flag == 1:
fail_flag = 0
continue
# 遍历所有人脸编码,与已知人脸对比
for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
# 获得对比结果,失败时重试
for i in range(times_retry):
try:
matches = face_recognition.compare_faces(known_face_encodings, face_encoding, tolerance=0.4)
except:
if i == times_retry - 1:
count_failed += 1
print('%s.jpg compare_faces() failed.' % only_name)
fail_flag = 1
break
print('%s.jpg compare_faces() retried %d times.' %(only_name, i+1))
time.sleep(i + 1)
continue
else:
break
if fail_flag == 1:
fail_flag = 0
break
# 获得比对成功的姓名(未做进一步处理),并复制文件到指定目录
name = "Unknown"
if True in matches:
first_match_index = matches.index(True)
name = known_face_names[first_match_index]
# 若同一个图片中有多个已知人脸,重复复制文件会报错
try:
shutil.copy(image_path, "f:/images_family")
count_copied += 1
break
except shutil.Error:
break
print('No.%d process: %d pictures checked, %d pics copied, and %d pics failed.' %(num, count_checked, count_copied, count_failed))
#主进程开始
if __name__ == '__main__':
#显示有几个CPU,Pool()最大进程数默认为CPU的个数
print('%d CPUs found.' %os.cpu_count())
# 测试起始时间
t1 = datetime.now()
#创建子进程池,子进程异步并发执行
poo = Pool()
for i in range(number_process):
poo.apply_async(face_check_and_copy, (i,))
# 关闭子进程池
poo.close()
# 等待所有子进程结束e
poo.join()
# 测试结束时间
t2 = datetime.now()
# 显示总的时间开销
print('time spend: %d seconds, %d microseconds.' %((t2-t1).seconds, (t2-t1).microseconds))
4个进程并发执行, 调用face_recognition函数时频繁报错,而且报错以后所占用的内存一直不释放,直到所有子进程执行完毕、主进程结束时相关内存才最终得到释放。测试的结果很糟糕:

所以,结论是:真正靠谱的健壮性及可扩展性必须由软件库函数的底层来保证;应用层的加固虽然能部分增强程序的健壮性,但仅局限于有限的效果上(当我们同时运行其他占据大量内存的应用时,3个进程并发执行时也会出现调用face_recognition函数报错的情况,这时使用上面的加强健壮性的代码就能够安全处理这些异常,并最终顺利完成对所有图片文件的处理)。
(完)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)