本帖最后由 QQSD 于 2021-1-6 09:23 编辑
这是我大二暑假的一个项目,那时候学了python+opencv+dlib来感觉了直接上手撸了一个人脸识别的项目,主要功能是可以实现对入侵人员的检测。那时候一开始是准备参加省的电设比赛滴,后来吗~~~睡过头了没赶上去比赛现场的车,乌龙了一次。
不过那段时间确实学到了不少东西,所以也没有觉得多遗憾,毕竟吗常言道努力不是为了一个结果而虚度时光。今晚趁着心情好分享一下我的项目框架,有兴趣的小伙伴可以基于这个框架进行更多项目的拓展。
代码环境:python3.7 + dlib(人脸识别)+ opencv(摄像头开启)+ wxpython(界面)
注:这里解释一下为什么要用dlib识别录入人脸而不是自己手写CNN啊那些神经网络来识别,个人理解这里涉及到一个落地工程的思维(个人见解不喜勿喷) ,不得不说卷积神经网络确实发展很快,精度也很高,训练出来的模型无限逼近百分之百。kaggle每年也会举办很多比赛让爱好者们用不同神经网络层搭建出新花样。但是实际工程中对时间、算法复杂度以及平台可移植性这些都是有需求的,所以我最终选择了特征点进行识别的dlib库,可以说就是吃现的,但是环境搭建容易而且跨平台也很容易,总之跑过一次你就会爱上他的。
实用的环境:目前在win10,ubuntu18.04,树莓派发三种环境跑过,很爽~~~
备注:代码原型GitHub开源社区上面的某位大神(具体我也忘了)。若有冒犯请联系删除。
好的介绍了前吸戏直接上效果把〜!!!!这里我将人脸录入识别的框架应用在了智能家居入侵检测中。
下面是软件的主界面,可以看到功能有人脸录入还有实时检测。
点击实时检测之后可以进行入侵检测,如果检测到有“坏人”(就算没有录入的人脸)那么笔记本就会滴滴滴滴报警,同时弹窗提醒。
嘿嘿,效果怎么样(学生党这样已经很不容易了,大佬们看完别喷我ORZ)
接下来说一下这个玩意怎么弄出来的:
首先,安装anaconda(出门左转百度教程哈哈哈)
其次下载的OpenCV, dlib的whl包
然后在Anaconda提示中cd到目录下面,然后用pip install whl的文件名就可以安装啦
最后是安装wxpython,这里提供一下pip指令(为啥要加-i呢?因为换上国内的源下载速度快!!!)
- pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wxpython
复制代码
ok环境配置OK接下来就可以开始操作了,这里需要熟悉一下dlib的基本编程,建议这个百度学习一下,不然你也操作不了(别急,饭要一口一口吃,学东西也是要一步一步来吗)。
附上关键代码(整个工程有点大所以没法上传,有兴趣的私聊我要代码)
app.py(主界面的代码,运行也是只要运行这个就行)
- import wx
- import os
- from importlib import reload
- import webbrowser
- import face_img_register
- import face_recognize_punchcard
- import sys
- from socket import *
- import time
- main ="icon/main.png"
- file_path = os.getcwd()+r'datalogcat.csv'
- class Mainui(wx.Frame):
- def __init__(self,superion):
- wx.Frame.__init__(self,parent=superion,title="智能非法入侵检测报警系统",size=(800,590))
- self.SetBackgroundColour('white')
- self.Center()
- self.frame = ''
- self.RegisterButton = wx.Button(parent=self, pos=(50, 120), size=(80, 50), label='人脸录入')
- self.PunchcardButton = wx.Button(parent=self, pos=(50, 380), size=(80, 50), label='实时检测')
- self.Bind(wx.EVT_BUTTON,self.OnRegisterButtonClicked,self.RegisterButton)
- self.Bind(wx.EVT_BUTTON,self.OnPunchCardButtonClicked,self.PunchcardButton)
- # 封面图片
- self.image_cover = wx.Image(main, wx.BITMAP_TYPE_ANY).Scale(520, 360)
- # 显示图片
- self.bmp = wx.StaticBitmap(parent=self, pos=(180,80), bitmap=wx.Bitmap(self.image_cover))
- def OnRegisterButtonClicked(self,event):
- reload(face_img_register)
- app.frame = face_img_register.RegisterUi(None)
- app.frame.Show()
- def OnPunchCardButtonClicked(self,event):
- reload(face_recognize_punchcard)
- app.frame = face_recognize_punchcard.PunchcardUi(None)
- app.frame.Show()
-
- class MainApp(wx.App):
- def OnInit(self):
- self.frame = Mainui(None)
- self.frame.Show()
- return True
- app = MainApp()
- app.MainLoop()
复制代码
人脸录入界面的代码face_img_register.py
- import numpy as np # 数据处理的库 Numpy
- import cv2 # 图像处理的库 OpenCv
- import os
- import shutil
- import _thread
- import wx
- import csv
- from importlib import reload
- from skimage import io as iio
- import face_recognize_punchcard
- import sys
- # 创建 cv2 摄像头对象
- # C++: VideoCapture::VideoCapture(int device);
- #API:http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/highgui/doc/reading_and_writing_images_and_video.html#videocapture
- # 保存
- path_make_dir = "data/face_img_database/"
- path_feature_all = "data/feature_all.csv"
- info = 'icon/info.png'
- #register ui
- class RegisterUi(wx.Frame):
- def __init__(self,superion):
- wx.Frame.__init__(self,parent=superion,title="人脸录入",size=(800,590),style=wx.DEFAULT_FRAME_STYLE|wx.STAY_ON_TOP)
- self.SetBackgroundColour('white')
- self.Center()
- self.NewButton = wx.Button(parent=self,pos=(50,120),size=(80,50),label='新建录入')
- self.ShortCutButton = wx.Button(parent=self,pos=(50,220),size=(80,50),label='截图保存')
- self.SaveButton = wx.Button(parent=self,pos=(50,320),size=(80,50),label='完成录入')
- # 封面图片
- self.image_info = wx.Image(info, wx.BITMAP_TYPE_ANY).Scale(600, 480)
- # 显示图片
- self.bmp = wx.StaticBitmap(parent=self, pos=(180,20), bitmap=wx.Bitmap(self.image_info))
- self.Bind(wx.EVT_BUTTON,self.OnShortCutButtonClicked,self.ShortCutButton)
- self.Bind(wx.EVT_BUTTON,self.OnNewButtonClicked,self.NewButton)
- self.ShortCutButton.Enable(enable=False)
- self.SaveButton.Enable(False)
- self.Bind(wx.EVT_BUTTON,self.OnSaveButtonClicked,self.SaveButton)
- self.sc_number = 0
- self.register_flag = 0
- self.name = ""
- def OnNewButtonClicked(self, event):
- while self.name == '':
- self.name = wx.GetTextFromUser(message="请先输入录入者的姓名,用于创建姓名文件夹", caption="温馨提示",
- default_value="", parent=None)
- # 监测是否重名
- for exsit_name in (os.listdir(path_make_dir)):
- if self.name == exsit_name:
- wx.MessageBox(message="姓名已存在,请重新输入", caption="警告")
- self.name = ''
- break
- os.makedirs(path_make_dir+self.name)
- print("新建的人脸文件夹: ", path_make_dir+self.name)
- self.NewButton.Enable(enable=False)
- self.ShortCutButton.Enable(enable=True)
- """使用多线程,子线程运行后台的程序,主线程更新前台的UI,这样不会互相影响"""
- # 创建子线程,按钮调用这个方法,
- _thread.start_new_thread(self._open_cap, (event,))
- def OnShortCutButtonClicked(self,event):
- self.SaveButton.Enable(True)
- if len(self.rects) !=0:
- # 计算矩形框大小,保证同步
- height = self.rects[0].bottom() - self.rects[0].top()
- width = self.rects[0].right() - self.rects[0].left()
- self.sc_number += 1
- im_blank = np.zeros((height, width, 3), np.uint8)
- for ii in range(height):
- for jj in range(width):
- im_blank[ii][jj] = self.im_rd[self.rects[0].top() + ii][self.rects[0].left() + jj]
- cv2.imencode('.jpg', im_blank)[1].tofile(path_make_dir+self.name + "/img_face_" + str(self.sc_number) + ".jpg") #正确方法
- print("写入本地:", str(path_make_dir+self.name) + "/img_face_" + str(self.sc_number) + ".jpg")
- else:
- print("未检测到人脸,识别无效,未写入本地")
- def OnSaveButtonClicked(self,event):
- self.bmp.SetBitmap(wx.Bitmap(self.image_info))
- self.NewButton.Enable(True)
- self.SaveButton.Enable(False)
- self.ShortCutButton.Enable(False)
- # 释放摄像头
- self.cap.release()
- # 删除建立的窗口
- #cv2.destroyAllWindows()
- if self.register_flag == 1:
- if os.path.exists(path_make_dir+self.name):
- shutil.rmtree(path_make_dir+self.name)
- print("重复录入,已删除姓名文件夹", path_make_dir+self.name)
- if self.sc_number == 0 and len(self.name)>0:
- if os.path.exists(path_make_dir+self.name):
- shutil.rmtree(path_make_dir+self.name)
- print("您未保存截图,已删除姓名文件夹", path_make_dir+self.name)
- if self.register_flag==0 and self.sc_number!=0:
- pics = os.listdir(path_make_dir+self.name)
- feature_list = []
- feature_average = []
- for i in range(len(pics)):
- pic_path = path_make_dir+self.name + "/" + pics[i]
- print("正在读的人脸图像:", pic_path)
- img = iio.imread(pic_path)
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
- dets = face_recognize_punchcard.detector(img_gray, 1)
- if len(dets) != 0:
- shape = face_recognize_punchcard.predictor(img_gray, dets[0])
- face_descriptor = face_recognize_punchcard.facerec.compute_face_descriptor(img_gray, shape)
- feature_list.append(face_descriptor)
- else:
- face_descriptor = 0
- print("未在照片中识别到人脸")
- if len(feature_list)>0:
- for j in range(128):
- feature_average.append(0)
- for i in range(len(feature_list)):
- feature_average[j] += feature_list[i][j]
- feature_average[j] = (feature_average[j])/len(feature_list)
- feature_average.append(self.name)
- with open(path_feature_all, "a+", newline="") as csvfile:
- writer = csv.writer(csvfile)
- print('写入一条特征人脸入库',feature_average)
- writer.writerow(feature_average)
- self.name = ""
- self.register_flag = 0
- self.sc_number = 0;
- def _open_cap(self,event):
-
- reload(face_recognize_punchcard)
- urlc = 'http://192.168.137.119:8081/'
- self.cap = cv2.VideoCapture(urlc)
- # cap.set(propId, value)
- # 设置视频参数,propId 设置的视频参数,value 设置的参数值
- self.cap.set(3, 480)
- #self.cap.set(cv2.CAP_PROP_FPS,5)
- while self.cap.isOpened():
- # cap.read()
- # 返回两个值:
- # 一个布尔值 true/false,用来判断读取视频是否成功/是否到视频末尾
- # 图像对象,图像的三维矩阵q
- flag, self.im_rd = self.cap.read()
- # 人脸数 rects
- self.rects = face_recognize_punchcard.detector(self.im_rd, 1)
- cv2.waitKey(1) # 必不可少
- # 待会要写的字体
- font = cv2.FONT_HERSHEY_SIMPLEX
- if len(self.rects) != 0:
- # 检测到人脸
- # 矩形框#d是人脸
- # 查重
- features_cap = face_recognize_punchcard.facerec.compute_face_descriptor(self.im_rd, face_recognize_punchcard.predictor(self.im_rd, self.rects[0]))
- for i in range(len(face_recognize_punchcard.features_known_arr)):
- # 将某张人脸与存储的所有人脸数据进行比对
- compare = face_recognize_punchcard.return_euclidean_distance(features_cap, face_recognize_punchcard.features_known_arr[i][0:-1])
- if compare == "same": # 找到了相似脸
- face_name = face_recognize_punchcard.features_known_arr[i][-1]
- print(face_name)
- wx.MessageBox(message=face_name + ",您已录过人脸,请检查是否签过到", caption="警告")
- self.NewButton.Enable(False)
- self.ShortCutButton.Enable(False)
- self.SaveButton.Enable(True)
- self.register_flag = 1
- for k, d in enumerate(self.rects):
- # 根据人脸大小生成空的图像
- # 最后一个参数是线宽
- cv2.rectangle(self.im_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]), (255, 0, 0), 2)
- # 显示人脸数
- cv2.putText(self.im_rd, "Faces: " + str(len(self.rects)), (50, 80), font, 0.8, (255, 0, 0), 1, cv2.LINE_AA)
- cv2.putText(self.im_rd, "Warning: please shortcut having rectangle", (50, 140), font, 0.8, (0, 0, 255), 1,
- cv2.LINE_AA)
- # print(im_rd.shape)
- height, width = self.im_rd.shape[:2]
- image1 = cv2.cvtColor(self.im_rd, cv2.COLOR_BGR2RGB)
- pic = wx.Bitmap.FromBuffer(width, height, image1)
- # 显示图片在panel上
- self.bmp.SetBitmap(pic)
- #直接在***clicked里设置self.bmp.SetBitmap(wx.Bitmap(self.image_cover)),子线程还在运行
- if self.NewButton.IsEnabled()==True and self.ShortCutButton.IsEnabled()==False and self.SaveButton.IsEnabled()==False:
- self.bmp.SetBitmap(wx.Bitmap(self.image_info))
- _thread.exit()
复制代码
人脸识别界面的代码face_recognize_punchcard.py
- import dlib # 人脸识别的库dlib
- import numpy as np # 数据处理的库numpy
- import cv2 # 图像处理的库OpenCv
- import pandas as pd # 数据处理的库Pandas
- import wx
- import os
- import csv
- import datetime
- import _thread
- # face recognition model, the object maps human faces into 128D vectors
- facerec = dlib.face_recognition_model_v1("model/dlib_face_recognition_resnet_model_v1.dat")
- # Dlib 预测器
- detector = dlib.get_frontal_face_detector()
- predictor = dlib.shape_predictor('model/shape_predictor_68_face_landmarks.dat')
- loading = 'icon/loading.png'
- pun_fail = 'icon/pun_fail.png'
- pun_repeat = 'icon/pun_repeat.png'
- pun_success = 'icon/pun_success.png'
- path_logcat_csv = "data/logcat.csv"
- def read_csv_to_recoders():
- recodes = []
- if os.path.exists(path_logcat_csv):
- with open(path_logcat_csv, "r", newline="") as csvfiler:
- reader = csv.reader(csvfiler)
- for row in reader:
- recodes.append(row)#包括header
- else:
- with open(path_logcat_csv, "w", newline="") as csvfilew:
- writer = csv.writer(csvfilew)
- header = ["姓名","日期","时间"]
- writer.writerow(header)
- return recodes
- pass
- # 计算两个向量间的欧式距离
- def return_euclidean_distance(feature_1, feature_2):
- feature_1 = np.array(feature_1)
- feature_2 = np.array(feature_2)
- dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
- print("欧式距离: ", dist)
- if dist > 0.4:
- return "diff"
- else:
- return "same"
- # 处理存放所有人脸特征的csv
- path_feature_known_csv = "data/feature_all.csv"
- # path_features_known_csv= "/media/con/data/code/python/P_dlib_face_reco/data/csvs/features_all.csv"
- csv_rd = pd.read_csv(path_feature_known_csv, header=None,encoding='gbk')
- # 存储的特征人脸个数
- #print(csv_rd.shape)
- #(2,129)
- # 用来存放所有录入人脸特征的数组
- features_known_arr = []
- #print("s0",csv_rd.shape[0],"s1",csv_rd.shape[1])
- for i in range(csv_rd.shape[0]):
- features_someone_arr = []
- for j in range(0, len(csv_rd.iloc[i, :])):
- features_someone_arr.append(csv_rd.iloc[i, :][j])
- # print(features_someone_arr)
- features_known_arr.append(features_someone_arr)
- print("数据库人脸数:", len(features_known_arr))
- # 返回一张图像多张人脸的128D特征 只返回一张人脸
- def get_128d_features(img_gray):
- dets = detector(img_gray, 1)
- shape = predictor(img_gray, dets[0])
- face_des = facerec.compute_face_descriptor(img_gray, shape)
- return face_des
- # if len(dets) != 0:
- # face_des = []
- # for i in range(len(dets)):
- # shape = predictor(img_gray, dets[i])
- # face_des.append(facerec.compute_face_descriptor(img_gray, shape))
- # else:
- # face_des = []
- # return face_des[0]
- class PunchcardUi(wx.Frame):
- def __init__(self,superion):
- wx.Frame.__init__(self,parent=superion,title="智能非法入侵检测报警系统",size=(800,590),style=wx.DEFAULT_FRAME_STYLE|wx.STAY_ON_TOP)
- self.SetBackgroundColour('white')
- self.Center()
- self.OpenCapButton = wx.Button(parent=self,pos=(50,120),size=(90,60),label='开始实时检测')
- self.resultText = wx.StaticText(parent=self,style=wx.ALIGN_CENTER_VERTICAL,pos=(50,320),size=(90,60),label="摄像头检测")
- self.resultText.SetBackgroundColour('white')
- self.resultText.SetForegroundColour('blue')
- font = wx.Font(14, wx.DECORATIVE, wx.ITALIC, wx.NORMAL)
- self.resultText.SetFont(font)
- self.pun_day_num = 0
- # 封面图片
- self.image_loading = wx.Image(loading, wx.BITMAP_TYPE_ANY).Scale(600, 480)
- self.image_fail = wx.Image(pun_fail, wx.BITMAP_TYPE_ANY).Scale(600, 480)
- self.image_repeat = wx.Image(pun_repeat, wx.BITMAP_TYPE_ANY).Scale(600, 480)
- self.image_success = wx.Image(pun_success, wx.BITMAP_TYPE_ANY).Scale(600, 480)
- # 显示图片
- self.bmp = wx.StaticBitmap(parent=self, pos=(180,20), bitmap=wx.Bitmap(self.image_loading))
- self.Bind(wx.EVT_BUTTON,self.OnOpenCapButtonClicked,self.OpenCapButton)
- def OnOpenCapButtonClicked(self,event):
- """使用多线程,子线程运行后台的程序,主线程更新前台的UI,这样不会互相影响"""
- # 创建子线程,按钮调用这个方法,
- _thread.start_new_thread(self._open_cap, (event,))
- def _open_cap(self,event):
- # 创建 cv2 摄像头对象
- urlc = 'http://192.168.137.119:8081/'
- self.cap = cv2.VideoCapture(urlc)
- # cap.set(propId, value)
- # 设置视频参数,propId设置的视频参数,value设置的参数值
- self.cap.set(3, 480)
- #cap是否初始化成功
- while self.cap.isOpened():
- # cap.read()
- # 返回两个值:
- # 一个布尔值true/false,用来判断读取视频是否成功/是否到视频末尾
- # 图像对象,图像的三维矩阵
- flag, im_rd = self.cap.read()
- # 每帧数据延时1ms,延时为0读取的是静态帧
- kk = cv2.waitKey(1)
- # 人脸数 dets
- dets = detector(im_rd, 1)
- # 待会要写的字体
- font = cv2.FONT_HERSHEY_SIMPLEX
- # 存储人脸名字和位置的两个 list
- # list 1 (dets): store the name of faces Jack unknown unknown Mary
- # list 2 (pos_namelist): store the positions of faces 12,1 1,21 1,13 31,1
- # 人脸的名字
- name = ''
- pos = ''
- # pos_namelist = []
- # name_namelist = []
- count = 0
- if len(dets) != 0:
- # 检测到人脸
- # 获取当前捕获到的图像的所有人脸的特征,存储到 features_cap_arr
- features_cap = ''
- shape = predictor(im_rd, dets[0])
- features_cap = facerec.compute_face_descriptor(im_rd, shape)
- # 遍历捕获到的图像中所有的人脸
- # 让人名跟随在矩形框的下方
- # 确定人名的位置坐标
- # 先默认所有人不认识,是 unknown
- name = "unrecognized face"
- # 每个捕获人脸的名字坐标
- pos = tuple([(int)((dets[0].left() + dets[0].right()) / 2) - 50
- , dets[0].bottom() + 20])
- # 对于某张人脸,遍历所有存储的人脸特征
- for i in range(len(features_known_arr)):
- # 将某张人脸与存储的所有人脸数据进行比对
- self.pun_day_num = 0;
- compare = return_euclidean_distance(features_cap, features_known_arr[i][0:-1])
- if compare == "same": # 找到了相似脸
- name = features_known_arr[i][-1]
- recoder = []
- recoder.append(name)
- localtime = datetime.datetime.now()
- date = str(localtime.year)+"/"+str(localtime.month)+"/"+str(localtime.day)
- time = str(localtime.hour)+":"+str(localtime.minute)+":"+str(localtime.minute)
- recoder.append(date)
- recoder.append(time)
- recoders = read_csv_to_recoders()
- # 绘制矩形框
- cv2.rectangle(im_rd, tuple([dets[0].left(), dets[0].top()]), tuple([dets[0].right(), dets[0].bottom()]),
- (255, 0, 0), 2)
- # 写人脸名字
- cv2.putText(im_rd, name, pos, font, 0.8, (255, 0, 255), 1, cv2.LINE_AA)
- if name == "unrecognized face":
- print("dangerrn")
- import time
- import ctypes
- player = ctypes.windll.kernel32
- player.Beep(1000,200)
- for i in range(10):
- time.sleep(1)
- player.Beep(1000,200)
- wx.MessageBox(message="有人入侵了", caption="可怕的消息")
- _thread.exit()
-
- cv2.putText(im_rd, "Faces: " + str(len(dets)), (50, 80), font, 1, (255, 0, 0), 1, cv2.LINE_AA)
- height, width = im_rd.shape[:2]
- image1 = cv2.cvtColor(im_rd, cv2.COLOR_BGR2RGB)
- pic = wx.Bitmap.FromBuffer(width, height, image1)
- # 显示图片在panel上
- self.bmp.SetBitmap(pic)
复制代码
然后再新建一个模型文件夹,存放人脸的模型路径(这个要学一下dlib的操作,里面有引用的)
然后新建一个数据文件夹,存放人脸录入的图片以及识别到的特征。
这里再说明一下face_recognize_punchcard.py和face_img_register.py这处里面的画红框的地方实际上就是摄像头的地址。如果输入0那就是本地笔记本的摄像头,如果输入一个IP地址就是一个IP摄像头。现在IP摄像头模块也多得很啦,像ESP32_CAM,还有树莓派等等。。。不要慌直接买回来上手弄就可以!
然后这里要特意说明一下其实就是像表达说这两个文件的摄像头其实是可以不同的,而实际落地工程中情况也是不同的。通常录入人脸的摄像头是电脑摄像头以及运行的摄像头,而检测用的是IP摄像头或者是其他的与这个软件不同在一个地方的摄像头。
哎有点绕,大概就是这个意思。然后接下来说一下这个工程路径下面的文件布局,便于有兴趣的同学们进行实操:
文件路径下面包括
1. data文件夹
1.1 face_img_database文件夹(存放录入的人脸照片)
1.2feature_all.csv(存放人脸特征点)
1.3logcat.csv(存放识别结果)
2.icon文件夹(存放wxpython需要用到的一些图片)
3.model
3.1shape_predictor_68_face_landmarks.dat(dlib识别包)
3.2dlib_face_recognition_resnet_model_v1.dat(dlib识别包)
4.face_img_register.py(上述代码)
5.face_recognize_punchcard.py(上述代码)
6.myapp.py(上述代码)
这里除了4里面的东西(dlib的包毕竟大)其他的我都上传到这里
。
如果需要详细代码的可以私聊我。
|