基于OpenCV的Python人脸识别、检测、框选 (遍历目录下所有照片依次识别 视频随时标注)
一、功能概览
可以实现在摄像头下实时的人脸识别、检测、框选功能
原理是将摄像头下的图像人脸和存放照片的目录下的人脸依次进行对比 调用百度的API人脸识别接口 返回相似度的值进行识别
识别成功和失败均有提示 成功时能将对应的信息写入到识别记录中 并终止程序 当所有照片对比后均失败则提示失败 终止程序
窗口利用tkinter函数所写 利用PIL和opencv对图像进行处理 最后将识别结果和人脸框选图像在窗口中显示出来
人脸框选功能用到了opencv官方的人脸识别器 然后利用opencv画出相应外框即可
另外在测试时 用matplotlib.pyplot的相关函数进行输出图像的测试
文件资源包:(文章末尾有百度网盘版)https://download.csdn.net/download/weixin_53403301/20670370?spm=1001.2014.3001.5501
主要代码为工程包内的api_face.py和gui_face_new.py文件 另外 gui_face.py文件所对应的程序功能为选择任一目录下的指定图像进行识别 在我之前的文章中有写到 gui_face_new.py文件是在其上的改进
之前的文章和资源: https://blog.csdn.net/weixin_53403301/article/details/117464715 https://download.csdn.net/download/weixin_53403301/19651352?spm=1001.2014.3001.5503
另外还有应用在树莓派和89C52单片机上的硬件控制版本: https://blog.csdn.net/weixin_53403301/article/details/118575731 https://download.csdn.net/download/weixin_53403301/20086349
如下图为源文件根目录下的所有文件图示 二、使用说明
主要代码为工程包内的api_face.py和gui_face_new.py文件 运行gui_face_new.py文件即可工作
OpenCV版本号为4.40.46 4.5.1版本以上调用摄像头会出现很明显的色差问题
需要在img目录先放入照片 最好为正脸证件照
tmp目录为摄像头截图的临时文件目录
运行程序 点击“使用相机识别”则识别开始 依次对img目录下的照片和摄像头内容进行对比识别
返回的相似度值大于80则成功 小于80则失败
若识别成功 则不在遍历目录下的剩余文件 直接提示成功窗口 中断识别
若识别失败 则继续遍历目录下的剩余文件进行识别 直到识别成功
若所有文件都以识别完 但仍然未识别成功 则提示失败窗口 中断识别
已有的识别记录为Windows系统下创建
Linux系统下的识别记录若为乱码 则需要更改code编码 或删除后重新运行程序 自动建立
在Linux系统建立的识别记录文件 在Windows环境下可以正常读取 不受影响
三、分部测试功能程序
1.遍历目录功能 采用os库遍历img目录下的所有文件 并将其文件名存入到列表中 再利用opencv读取 mat进行显示测试 代码如下:
"""
Created on Sun Aug 1 11:16:18 2021
@author: 16016
"""
import cv2
import os
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def pshow(words,picture):
plt.imshow(picture[:,:,::-1])
plt.title(words), plt.xticks([]), plt.yticks([])
plt.show()
dir_name='./img'
fileimg_list = []
fileimg_list=os.listdir(dir_name)
file_num=len(fileimg_list)
i=0
while True:
if i<file_num:
print(i)
img=cv2.imread(dir_name + '/' + fileimg_list[i])
pshow(fileimg_list[i],img)
i=i+1
else:
break
运行结果如图所示: 2.界面获取目录功能 此功能是应用在gui_face.py文件夹 目的是通过按钮选择图像并输出图像路径 代码如下:
import tkinter as tk
from tkinter import ttk
from tkinter.filedialog import *
import tkinter.messagebox
tk=Tk()
tk.title("个人GUI界面学习")
mainfarm=Frame(tk,width=800, height=100,bg="black")
mainfarm.grid_propagate(0)
mainfarm.grid()
fram=Frame(mainfarm,width=400, height=100,bg="black")
fram.grid_propagate(0)
fram.grid()
e = Entry(fram)
e.grid(row=0,column=2)
e.delete(0, END)
e.insert(0, '选择人像图片')
filepath=StringVar()
def filefound():
filepath= askopenfilename()
print (filepath)
e.delete(0, END)
e.insert(0, filepath)
button2=Button(fram,text="选择文件",command=filefound).grid(row=0,column=3)
mainloop()
运行结果如图所示: 3.人脸框选功能 加载目录下的opencv官方人脸识别器(haarcascade_frontalface_default.xml) 进行识别后画出外框 代码如下:
import cv2
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def pshow(words,picture):
plt.imshow(picture[:,:,::-1])
plt.title(words), plt.xticks([]), plt.yticks([])
plt.show()
filepath = 'img/123.jpg'
faceImg = cv2.imread(filepath)
gray = cv2.cvtColor(faceImg,cv2.COLOR_BGR2GRAY)
classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
color = (0,255,0)
faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
if len(faceRects):
for faceRect in faceRects:
x,y,w,h = faceRect
cv2.rectangle(faceImg,(x, y), (x + h, y + w), color, 2)
pshow('1',faceImg)
运行结果如下: 4.摄像头及框选功能 同上一个功能 不过是对摄像头图像进行实时的框选 并实时显示 同时还加上了人眼部分框选(haarcascade_eye.xml) 通过opencv进行显示 同时按esc键退出 代码如下:
import cv2
cap = cv2.VideoCapture(0)
while True:
ok, img = cap.read()
if ok is False:
print('无法读取到摄像头!')
break
faceImg = img
gray = cv2.cvtColor(faceImg,cv2.COLOR_BGR2GRAY)
classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
color = (0,255,0)
faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
if len(faceRects):
for faceRect in faceRects:
x,y,w,h = faceRect
cv2.rectangle(faceImg,(x, y), (x + h, y + w), color, 2)
gray = cv2.cvtColor(faceImg,cv2.COLOR_BGR2GRAY)
classifier = cv2.CascadeClassifier('haarcascade_eye.xml')
color = (255,0,0)
faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
if len(faceRects):
for faceRect in faceRects:
x,y,w,h = faceRect
cv2.rectangle(faceImg,(x, y), (x + h, y + w), color, 2)
cv2.imshow("faceImg",faceImg)
k = cv2.waitKey(10)
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
运行结果如下: 5.延时功能 此功能主要用于测试time库 获取系统时间的功能 测试这个功能的目的主要是为了在主程序中 通过控制时间减少资源的使用 使其每隔一段时间读取、识别一次(0.1s) 主程序中 识别函数为一个循环 读取一次摄像头并进行人脸识别 若识别成功 则中断循环 若识别失败 也跳出循环 代码如下:
"""
Created on Tue Jun 1 00:14:45 2021
@author: ZHOU
"""
import time
import threading
def delay(a):
print( time.time(),'10s',a)
print( time.time())
s = threading.Timer(10,delay,("delay",))
s.start()
print( time.time())
def main():
print('1')
delay(10)
print('2')
main()
运行结果如下: 延时时间为10s 四、主程序部分 1.API算法部分 调用 requests的HTTP协议库 调用os多操作系统接口库 调用base64编码库 调用JavaScript Object Notation数据交换格式 登入百度的人脸识别API 然后对给出的两个图像进行对比识别 输出相相似度 所有相关功能和函数的语句已在代码中注释好了 代码如下:
"""
Created on Mon May 31 23:40:16 2021
@author: ZHOU
"""
import requests
import os
import base64
import json
ACCESS_TOKEN = ''
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INFO_CONFIG = {
'ID': '15788358',
'API_KEY': 'ohtGa5yYoQEZ8Try8lnL99UK',
'SECRET_KEY': 'qaDjyuXkf5MZ28g5C8pwFngDZenhswC3'
}
URL_LIST_URL = {
'ACCESS_TOKEN_URL': 'https://aip.baidubce.com/oauth/2.0/token?' + 'grant_type=client_credentials&client_id={API_KEYS}&client_secret={SECRET_KEYS}&'.format(
API_KEYS=INFO_CONFIG['API_KEY'], SECRET_KEYS=INFO_CONFIG['SECRET_KEY']),
'FACE_PLATE': 'https://aip.baidubce.com/rest/2.0/face/v3/match',
}
class AccessTokenSuper(object):
pass
class AccessToken(AccessTokenSuper):
def getToken(self):
accessToken = requests.post(url=URL_LIST_URL['ACCESS_TOKEN_URL'])
accessTokenJson = accessToken.json()
if dict(accessTokenJson).get('error') == 'invalid_client':
return '获取accesstoken错误,请检查API_KEY,SECRET_KEY是否正确!'
return accessTokenJson
ACCESS_TOKEN = AccessToken().getToken()['access_token']
LICENSE_PLATE_URL = URL_LIST_URL['FACE_PLATE'] + '?access_token={}'.format(ACCESS_TOKEN)
class faceSuper(object):
pass
class face(faceSuper):
def __init__(self, image=None, image2=None):
self.HEADER = {
'Content-Type': 'application/json; charset=UTF-8',
}
if image is not None:
imagepath = os.path.exists(image)
if imagepath == True:
images = image
with open(images, 'rb') as images:
img1 = base64.b64encode(images.read())
else:
print("图像1不存在")
return
if image2 is not None:
imagepath2 = os.path.exists(image2)
if imagepath2 == True:
images2 = image2
with open(images2, 'rb') as images2:
img2 = base64.b64encode(images2.read())
else:
print("图像2不存在")
return
self.img = img1
self.imgs = img2
self.IMAGE_CONFIG1 = {"image": str(img1, 'utf-8'), "image_type": "BASE64"}
self.IMAGE_CONFIG2 = {"image": str(img2, 'utf-8'), "image_type": "BASE64"}
self.IMAGE_CONFIG = json.dumps([self.IMAGE_CONFIG1, self.IMAGE_CONFIG2])
def postface(self):
if (self.img==None and self.imgs==None):
return '图像不存在'
face = requests.post(url=LICENSE_PLATE_URL, headers=self.HEADER, data=self.IMAGE_CONFIG)
return face.json()
def facef(FA1, FA2):
testAccessToken = AccessToken()
testface = face(image=FA1, image2=FA2)
result_json = testface.postface()
result = result_json['result']['score']
print('人脸相似度:', result)
if result > 80:
print("人脸匹配成功!")
else:
print("人脸匹配失败!")
return '人脸相似度:' + str(result), result
此函数需要配合网络进行使用 单独运行时不报错则可以连接上API
利用facef(FA1, FA2)函数 传入入两个图像值 则能进行识别
2.界面程序部分 测试时 利用mat显示图像 pshow函数展示 在实际运行中 为了加快运行速度 将测试部分的代码注释掉了 所有相关功能和函数的语句已在代码中注释好了 代码如下:
"""
Created on Mon May 31 23:39:19 2021
@author: ZHOU
"""
import tkinter as tk
from tkinter import ttk
from tkinter.filedialog import *
import tkinter.messagebox
from PIL import Image, ImageTk
import api_face
import cv2
import threading
import time
import os
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
def pshow(words,picture):
plt.imshow(picture[:,:,::-1])
plt.title(words), plt.xticks([]), plt.yticks([])
plt.show()
class Login(ttk.Frame):
def __init__(self, win):
ttk.Frame.__init__(self, win)
frame0 = ttk.Frame(self)
frame1 = ttk.Frame(self)
win.title("人脸识别")
win.minsize(1240, 620)
self.center_window()
self.thread_run = None
self.thread_run2 = None
self.camera = None
frame0.pack(side=TOP, fill=tk.Y, expand=1)
frame1.pack(side=TOP, fill=tk.Y, expand=1)
self.facer = ttk.Label(frame1, text='', font=('Times', '20'))
self.facer.pack()
self.pic_path3='./star.png'
self.pilImage = Image.open(self.pic_path3)
self.photo = self.pilImage.resize((500,500))
self.tkImage = ImageTk.PhotoImage(image=self.photo)
self.image_ctl = tk.Label(frame0, image=self.tkImage)
self.image_ctl.pack()
self.url_face_button = ttk.Button(frame1, text="使用相机识别", width=15, command=self.cv_face)
self.url_face_button.pack(side=TOP)
self.pack(fill=tk.BOTH, expand=tk.YES, padx="10", pady="10")
def center_window(self):
screenwidth = log.winfo_screenwidth()
screenheight = log.winfo_screenheight()
log.update()
width = log.winfo_width()
height = log.winfo_height()
size = '+%d+%d' % ((screenwidth - width)/2, (screenheight - height)/2)
log.geometry(size)
def cv_face(self):
if self.thread_run:
if self.camera.isOpened():
self.camera.release()
print("关闭摄像头")
self.camera = None
self.thread_run = False
return
if self.camera is None:
self.camera = cv2.VideoCapture(1)
if not self.camera.isOpened():
self.camera = None
print("没有外置摄像头")
self.camera = cv2.VideoCapture(0)
if not self.camera.isOpened():
print("没有内置摄像头")
tkinter.messagebox.showinfo('警告', '摄像头打开失败!')
self.camera = None
return
else:
print("打开内置摄像头")
else:
print("打开外置摄像头")
self.thread = threading.Thread(target=self.video_thread)
self.thread.setDaemon(True)
self.thread.start()
self.thread_run = True
def video_thread(self):
self.thread_run = True
self.thread2 = threading.Thread(target=self.video_pic)
self.thread2.setDaemon(True)
self.thread2.start()
self.thread_run2 = True
while self.thread_run:
_, img_bgr = self.camera.read()
gray = cv2.cvtColor(img_bgr,cv2.COLOR_BGR2GRAY)
classifier = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
color = (0,255,0)
faceRects = classifier.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=3,minSize=(32, 32))
if len(faceRects):
for faceRect in faceRects:
x,y,w,h = faceRect
cv2.rectangle(img_bgr,(x, y), (x + h, y + w), color, 2)
img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
im = Image.fromarray(img)
w, h = im.size
pil_image_resized = self.resize(w, h, im)
self.imgtk = ImageTk.PhotoImage(image=pil_image_resized)
self.image_ctl.configure(image=self.imgtk)
print("结束运行")
def video_pic(self):
dir_name='./img'
fileimg_list = []
fileimg_list=os.listdir(dir_name)
file_num=len(fileimg_list)
fileimg_num=0
self.thread_run2 = True
predict_time = time.time()
while self.thread_run2:
if time.time() - predict_time > 0.1:
print("正在识别中")
_, img_bgr = self.camera.read()
cv2.imwrite("tmp/test.jpg", img_bgr)
self.pic_path = "tmp/test.jpg"
try:
if fileimg_num<file_num:
self.pic_path2 = dir_name + '/' + fileimg_list[fileimg_num]
facestr, result = api_face.facef(self.pic_path, self.pic_path2)
self.facer.configure(text=str(facestr))
if result > 80:
tkinter.messagebox.showinfo('提示', '人脸匹配成功!')
print('人像图片文件名:'+fileimg_list[fileimg_num])
try:
f=open("识别记录.txt","r")
fi=open("识别记录.txt","a")
txt=time.ctime()
fi.write(txt+' 人像图片路径: '+fileimg_list[fileimg_num]+" 人脸匹配成功! \n")
f.close()
fi.close()
except:
f=open("识别记录.txt","w")
txt=time.ctime()
f.write(txt+' 人像图片路径: '+fileimg_list[fileimg_num]+" 人脸匹配成功! \n")
f.close()
break
else:
fileimg_num=fileimg_num+1
else:
tkinter.messagebox.showinfo('提示', '人脸匹配失败!')
break
except:
pass
predict_time = time.time()
print("识别结束")
pass
def resize(self, w, h, pil_image):
w_box = 1000
h_box = 500
f1 = 1.0*w_box/w
f2 = 1.0*h_box/h
factor = min([f1, f2])
width = int(w*factor)
height = int(h*factor)
return pil_image.resize((width, height), Image.ANTIALIAS)
def close_window():
print("已关闭人脸识别")
if Login.thread_run:
Login.thread_run = False
Login.thread.join(2.0)
log.destroy()
if __name__ == '__main__':
log = tk.Tk()
login = Login(log)
log.protocol('清除窗口', close_window)
log.mainloop()
五、最终测试 1.识别成功测试 读取img目录下所有图像 对摄像头图像依次进行对比识别 相似度不断变化 当大于80时 输出成功提示 并获取图像名称、写入记录文档中 如图所示,我的图像位于img目录下的第4个 列表序号为3 程序应在识别到此的时候停止 初始界面: 点击“使用相机识别”后开始开启摄像头识别 如图所示为识别成功结果: 点击确认后终止程序 在识别记录.txt文档中可以看到刚刚的照片名称 识别时间 在实际运行时 对前三个图像的相似度较低 最大只有36 成功后程序停止 不再对后面的图像进行识别 如图所示: 2.识别失败功能测试 戴上口罩进行识别(或删掉目录下对应人物的图像) 相似度达不到80 遍历的图片都无法成功识别 最后提示失败
删除对应图像: 戴上口罩 相似度最高只有60多 识别失败: 六、总结 参考文章链接: https://blog.csdn.net/weixin_53403301/article/details/118575731 https://blog.csdn.net/weixin_53403301/article/details/118005313 https://blog.csdn.net/weixin_53403301/article/details/117464715 主程序部分代码注释完整 基本上每一行都有说明和注释 直接看主程序代码就可以弄懂其原理 关于树莓派的人脸识别 目前还是之前的版本(指定图像进行识别) 由于还没开学 没有硬件可以测试 所以尚未加入遍历目录下所有文件的功能进行 等加上以后 我会单独再写一篇 树莓派效果展示图:
最后 此次开源系统的百度网盘资源包: 链接:https://pan.baidu.com/s/1w5rLXgp_mi8lBPzXAROfoQ 提取码:1ber
这里是光电Mike Zhou 一位光电专业在读的网易独家签约音乐人 歌手页面: https://music.163.com/#/artist?id=12115205
|