一、树莓派Opencv以及扩展模块的安装

1、概述:本次在树莓派上安装Opencv及其扩展模块,考虑到树莓派的SD卡容量和内存的限制,不采用直接pip安装方法,而采用编译Opencv源码的方式进行安装。
2、遇到的问题及解决方法

遇到的问题解决方法
缺少”cuda.hpp”将/home/pi/opencv_contrib3.4.1/modules/xfeatures2d/include/opencv2下的xfeatures2d文件夹复制到home/pi/opencv-3.4.1/modules/stitching/include/opencv2下
缺少”bosstdesc_bgm.i”下载对应的文件到opencv_contrib/modules/xfeatures2d/src下
运行至99%时树莓派卡死原本采用make -j4进行源码编译加速,但是多次尝试仍然卡死,之后采用make解决了问题,可能原因是make -j4所需的交换空间太大导致卡死。

3、运行结果

导入opencv库没有问题,说明安装成功。

二、树莓派人脸检测

1、概述:本次在树莓派上检测人脸用Opencv自带的Haar特征分类器。
2、代码编写:
将.xml文件拷贝到mu_code文件夹下,在mu_code下编写代码,则工程的根目录默认在mu_code。

import cv2cap=cv2.VideoCapture(0)cascadePath = "/home/pi/opencv-3.4.3/data/haarcascades/haarcascade_frontalface_default.xml"faceCascade = cv2.CascadeClassifier(cascadePath)while (True):ret, img = cap.read()# 灰度化处理gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)faces = faceCascade.detectMultiScale(gray, 1.3, 5)if len(faces)!=0:x = faces[0][0]y = faces[0][1]w = faces[0][2]h = faces[0][3]cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)cv2.imshow("fle",img)cv2.waitKey(10)

3、运行结果:

由图中可以看出可以正确检测出人脸。

三、树莓派人脸识别

1、概述:
本次在树莓派上进行人脸识别采用OpenCV人脸识别类LBPHFaceRecognizer。
2、代码编写:

import cv2from PIL import Imageimport numpy as npimagePath="face.png"img_face=cv2.imread(imagePath,0)cv2.imshow("fle",img_face)cv2.waitKey(0)recognizer = cv2.face.LBPHFaceRecognizer_create()PIL_img = Image.open(imagePath).convert('L')img_numpy = np.array(PIL_img, 'uint8')faces=[]ids=[]faces.append(img_numpy)ids.append(2)recognizer.train(faces, np.array(ids))id, confidence = recognizer.predict(img_face)print("id=",id)print("confidence=",confidence)

3、运行结果:


人脸图片用于训练,用于训练的id是2,这里只是为了检测代码的正确性,所以依然用这张图片是做识别,识别的结果是id=2,confidence≈9.8。Id号识别正确,confidence接近于0,说明识别率很高。

四、树莓派利用双色LED模拟开关门动作

1、概述:
设计一个双色LED类,该类应该能够向外提供两个方法,分别设置红灯和绿灯的PWM。
这里在主函数调用是都是设置为满PWM。
2、接线

树莓派3mm双色LED模块
GND负极(-)
P17红色灯正极(中间引脚)
P18绿色灯正极
3、代码编写
import RPi.GPIO as GPIOimport timeclass Double_LED_Class:def __init__(self):# double_led 初始化工作makerobo_pins = (11, 12)# PIN管脚字典GPIO.setmode(GPIO.BOARD)# 采用实际的物理管脚给GPIO口GPIO.setwarnings(False)# 去除GPIO口警告GPIO.setup(makerobo_pins, GPIO.OUT)# 设置Pin模式为输出模式GPIO.output(makerobo_pins, GPIO.LOW)# 设置Pin管脚为低电平(0V)关闭LEDself.p_R = GPIO.PWM(makerobo_pins[0], 2000)# 设置频率为2KHzself.p_G = GPIO.PWM(makerobo_pins[1], 2000)# 设置频率为2KHz# 初始化占空比为0(led关闭)self.p_R.start(0)self.p_G.start(0)def makerobo_pwm_map(self,x, in_min, in_max, out_min, out_max):return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_mindef makerobo_set_red_Color(self,col):# 例如:col = 0x1122# 把0-255的范围同比例缩小到0-100之间R_val = self.makerobo_pwm_map(col, 0, 255, 0, 100)self.p_R.ChangeDutyCycle(R_val)# 改变占空比def makerobo_set_green_Color(self,col):# 例如:col = 0x1122# 把0-255的范围同比例缩小到0-100之间G_val = self.makerobo_pwm_map(col, 0, 255, 0, 100)self.p_G.ChangeDutyCycle(G_val)# 改变占空比# 释放资源def makerobo_destroy(self):self.p_G.stop()self.p_R.stop()GPIO.output(self.makerobo_pins, GPIO.LOW)# 关闭所有LEDGPIO.cleanup()# 释放资源# 测试用例if __name__ == "__main__":Hardware_double_led=Double_LED_Class()Hardware_double_led.makerobo_set_red_Color(200)time.sleep(3)#显示红灯3s后显示绿灯Hardware_double_led.makerobo_set_red_Color(0)Hardware_double_led.makerobo_set_green_Color(200)

4、运行结果

五、树莓派门禁系统界面设计与整体逻辑代码整合

1、概述:
树莓派门禁系统总共包括4个界面设计,分别是人脸识别开门界面、管理员登录界面、人脸录入界面、人脸数据库展示界面。这四个界面都用PyQt5进行设计,先在Window上用Qt Designer搭建界面,用Python编写逻辑关系,最后移植到树莓派上,树莓派上只需要安装PyQt库即可运行程序。
下面为四个界面的展示图:




界面控件的逻辑关系如下图:

2、代码编写:
代码思路:

创建了四个界面类,分别继承于用Qt Designer创建 的四个界面类,这样做是为了能够在更改界面的时候不会改变逻辑部分代码。另外创建了一个数据库操作类,主要是为了能够查询,读写数据库,这里采用的是SQlite数据库。
Main文件:

##########################################Author:郭先达#date:2021.12.22#######################################33import sysimport cv2import threadingfrom PyQt5.QtCore import QBasicTimerfrom PyQt5.QtCore import *from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QLineEdit, QGridLayout, QMessageBox, QGroupBoxfrom PyQt5 import QtWidgetsfrom PyQt5 import QtCore, QtGui, QtWidgetsfrom PyQt5.QtWidgets import QWidget, QLabel, QApplicationfrom PIL import Imageimport numpy as npfrom PyQt5.QtWidgets import *from PyQt5.QtCore import *from PyQt5.QtGui import QPalette, QBrush, QPixmapfrom PyQt5.QtSql import *import timefrom double_led import Double_LED_Classimport osfrom MainWindow import Ui_Dialog as Ui_Dialog_MainWindowfrom Admin_enter import Ui_Dialog as Ui_Dialog_Admin_enterfrom Face_rec import Ui_Dialog as Ui_Dialog_Face_recfrom SQliteWindow import Ui_Dialog as Ui_Dialog_SQliteWindow# 导入OpenCV自带的数据集,定义多个是因为在后面有多次调用,用一个的话会报错cascadePath = "haarcascade_frontalface_default.xml"faceCascade1 = cv2.CascadeClassifier(cascadePath)faceCascade2= cv2.CascadeClassifier(cascadePath)faceCascade3 = cv2.CascadeClassifier(cascadePath)faceCascade4=cv2.CascadeClassifier(cascadePath)#人脸识别开门界面class Fle_MainWindow(QDialog,Ui_Dialog_MainWindow):def __init__(self):super(Fle_MainWindow,self).__init__()self.setupUi(self)# 创建定时器,定时器用来定时拍照self.timer_camera = QtCore.QTimer()self.user = []#进入人脸识别开门界面之前先把所有数据训练一遍,以满足新数据的录入self.recognizer = cv2.face.LBPHFaceRecognizer_create()faces, ids = self.getImagesAndLabels("./Face_data")#拍摄的照片放在这个文件夹下self.recognizer.train(faces, np.array(ids))#开始训练self.font = cv2.FONT_HERSHEY_SIMPLEX#摄像头初始化self.camera_init()#绑定函数show_cameraself.timer_camera.timeout.connect(self.show_camera)#30ms拍一次照片self.timer_camera.start(30)# 点击管理员按钮事件self.pushButton_administrators.clicked.connect(self.slot_btn_admin)#新建一个双色LED类self.Hardware_double_led=Double_LED_Class()# 函数获取图像和标签数据def getImagesAndLabels(self,path):imagePaths = [os.path.join(path, f) for f in os.listdir(path)]faceSamples = []#人脸数据ids = []#人脸对应的id标签for imagePath in imagePaths:# 转换为灰度PIL_img = Image.open(imagePath).convert('L')img_numpy = np.array(PIL_img, 'uint8')#print(imagePath),打印出具体信息,怎么分割就怎么设置序号id = int(imagePath.split("/")[2].split(".")[1])faces = faceCascade3.detectMultiScale(img_numpy)for (x, y, w, h) in faces:faceSamples.append(img_numpy[y:y + h, x:x + w])ids.append(id)print(ids)return faceSamples, idsdef camera_init(self):# 打开设置摄像头对象self.cap = cv2.VideoCapture(0)self.__flag_work = 0self.x = 0self.count = 0#这里的minW和minH用于限制人脸的最小宽高,防止误测self.minW = 0.2 * self.cap.get(3)#视频流的帧宽度self.minH = 0.2 * self.cap.get(4)#视频流的帧高度def show_camera(self):flag, self.image = self.cap.read()# 将图片变化成灰度图gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)# 探测图片中的人脸faces = faceCascade1.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=5,minSize=(int(self.minW), int(self.minH)),)# 判断是否检测到人脸,检测到设置为绿灯,没检测到设置为红灯if len(faces)!=0:#下面这个操作是为了能够只检测到一张脸,检测面积最大的那个脸WxH_max=0 #人脸矩形的面积WxH_max_face=faces[0]for i in faces:if(i[2]*i[3]>WxH_max):WxH_max=i[2]*i[3]WxH_max_face=i# 围绕脸的框x=WxH_max_face[0]y = WxH_max_face[1]w = WxH_max_face[2]h = WxH_max_face[3]cv2.rectangle(self.image, (x, y), (x + w, y + h), (0, 255, 0), 2)#用recognizer进行识别判断id, confidence = self.recognizer.predict(gray[y:y + h, x:x + w])# 对置信度进行判断,这里设定为70if (confidence < 70):confidence = "{0}%".format(round(100 - confidence))for i in range(0,mysqlite.get_rows()):if mysqlite.find_data(i,0)==id:self.label_ID.setText(str(mysqlite.find_data(i,1)))self.label_name.setText(str(mysqlite.find_data(i,2)))self.Hardware_double_led.makerobo_set_red_Color(0)self.Hardware_double_led.makerobo_set_green_Color(100)else:confidence = "{0}%".format(round(100 - confidence))self.label_ID.setText("不认识")self.label_name.setText("不认识")self.Hardware_double_led.makerobo_set_red_Color(100)self.Hardware_double_led.makerobo_set_green_Color(0)# 给图片添加文本 图片矩阵, 添加文本名称, 设置文本显示位置,# 字体样式, 字体大小, 字体颜色, 字体粗细cv2.putText(self.image, str(id), (x + 5, y - 5), self.font, 1, (255, 255, 255), 2)cv2.putText(self.image, str(confidence), (x + 5, y + h - 5), self.font, 1, (255, 255, 0), 1)else:self.Hardware_double_led.makerobo_set_red_Color(0)self.Hardware_double_led.makerobo_set_green_Color(0)# 将视频显示在了label上show = cv2.resize(self.image, (640, 480))show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)self.lab_face.setPixmap(QtGui.QPixmap.fromImage(showImage))# 点点击管理员按钮事件def slot_btn_admin(self):#应该在释放摄像头之前先关闭定时器self.timer_camera.stop()self.cap.release()self.logon = Fle_Admin_enter()self.logon.show()self.hide()#其他界面不用双色LED模拟开关门,把灯灭掉self.Hardware_double_led.makerobo_set_red_Color(0)self.Hardware_double_led.makerobo_set_green_Color(0)# 管理员登录界面class Fle_Admin_enter(QDialog,Ui_Dialog_Admin_enter):def __init__(self):super(Fle_Admin_enter, self).__init__()self.setupUi(self)#将输入信息初始化为空self.lineEdit_admin_ID.setText("")self.lineEdit_admin_key.setText("")#设置密码为隐藏方式显示self.lineEdit_admin_key.setEchoMode(QLineEdit.Password)# lineEdit改变事件self.lineEdit_admin_ID.textEdited[str].connect(self.changeEdit_ID)self.lineEdit_admin_key.textEdited[str].connect(self.changeEdit_key)# 点击返回按钮事件self.pushButton_admin_back.clicked.connect(self.slot_btn_back)# 点击登录按钮事件self.pushButton_admin_enter.clicked.connect(self.slot_btn_logon)# 点击Edit_ID事件def changeEdit_ID(self):Edit_ID = self.lineEdit_admin_ID.text()print("Edit_ID=",Edit_ID)# 点击Edit_key事件def changeEdit_key(self):Edit_key = self.lineEdit_admin_key.text()print("Edit_ID=",Edit_key)# 点击返回按钮事件def slot_btn_back(self):self.menu = Fle_MainWindow()self.menu.show()self.hide()# 点击登录按钮事件def slot_btn_logon(self):# 判断账号和密码是否输入正确print(self.lineEdit_admin_ID.text)print(self.lineEdit_admin_key.text)#这里设置管理员的账号和密码,这里都设置为1if self.lineEdit_admin_ID.text() == "1" and self.lineEdit_admin_key.text() == "1":self.manager_face = Fle_Face_rec()self.manager_face.show()self.hide()#print("enter Ui_manager_face")else:QMessageBox.warning(self, "提示", "账号或密码错误!", QMessageBox.Close)#人脸录入界面class Fle_Face_rec(QDialog,Ui_Dialog_Face_rec):def __init__(self):super(Fle_Face_rec, self).__init__()self.setupUi(self)# 初始化为空self.lineEdit_ID.setText("")self.lineEdit_name.setText("")# 初始化进度条定时器self.timer = QBasicTimer()self.step = 0# 创建定时器self.timer_camera = QtCore.QTimer()# 初始化摄像头数据self.camera_init()# 定时器函数,用来显示人脸检测结果,并不录入数据self.timer_camera.timeout.connect(self.show_camera)self.timer_camera.start(30)self.pushButton_begin_rec.clicked.connect(self.slot_btn_enter)self.pushButton_back.clicked.connect(self.slot_btn_back)self.pushButton_show_sqlite.clicked.connect(self.show_sqlitedata)# 初始化摄像头数据def camera_init(self):self.cap = cv2.VideoCapture(0)self.__flag_work = 0self.x =0self.count = 0#设置分辨率为640*480self.cap.set(4,640)self.cap.set(3,480)# 点击返回按键返回上一界面def slot_btn_back(self):self.timer_camera.stop()self.cap.release()self.logon = Fle_MainWindow()self.logon.show()self.hide()def show_camera(self):flag, self.image = self.cap.read()gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)faceCascade2 = cv2.CascadeClassifier(cascadePath);faces = faceCascade2.detectMultiScale(gray,scaleFactor=1.2,minNeighbors=5,minSize=(200, 200))if len(faces)!=0:WxH_max = 0WxH_max_face = faces[0]for i in faces:if (i[2] * i[3] > WxH_max):WxH_max = i[2] * i[3]WxH_max_face = i# 围绕脸的框x = WxH_max_face[0]y = WxH_max_face[1]w = WxH_max_face[2]h = WxH_max_face[3]cv2.rectangle(self.image, (x, y), (x + w, y + h), (255, 0, 0), 2)roi_gray = gray[y:y + h, x:x + w]roi_color = self.image[y:y + h, x:x + w]# 将视频显示在了label上show = cv2.resize(self.image, (640, 480))show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB)showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], QtGui.QImage.Format_RGB888)self.label_face.setPixmap(QtGui.QPixmap.fromImage(showImage))# 点击按钮开启人脸录入线程def slot_btn_enter(self):self.count = 0# 创建线程并开启self.thread = threading.Thread(target=self.thread_pic)self.thread.start()# 开启进度条定时器self.timer.start(100, self)# 加载进度条def timerEvent(self, e):self.progressBar.setValue(self.count)# 录入人脸线程def thread_pic(self):tip="正在录入"+str(self.lineEdit_ID.text())+str(self.lineEdit_name.text())+"的人脸!!"print(tip)# 创建目录,将获取的人脸照片放入指定的文件夹self.file = "./Face_data"file_ID=str(self.lineEdit_ID.text())while (True):# 灰度化处理gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)faces = faceCascade4.detectMultiScale(gray, 1.3, 5)if len(faces)!=0:WxH_max = 0WxH_max_face = faces[0]for i in faces:if (i[2] * i[3] > WxH_max):WxH_max = i[2] * i[3]WxH_max_face = i# 围绕脸的框x = WxH_max_face[0]y = WxH_max_face[1]w = WxH_max_face[2]h = WxH_max_face[3]self.count += 1# 将捕获的图像命名为User.0.1.png,其中0是id号print(self.file + "/User." + file_ID + '.' + str(self.count) + ".png")bool = cv2.imwrite(self.file + "/User." + file_ID + '.' + str(self.count) + ".png", gray[y:y + h, x:x + w])# 取100张人脸样本,停止录像if self.count >= 100:print("人脸数据采集已完成!")break#将数据存入数据库mysqlite.add_row(self.lineEdit_ID.text(),self.lineEdit_xuehao.text(),str(self.lineEdit_name.text()))def show_sqlitedata(self):self.logon = Fle_SQliteWindow()self.logon.show()#self.hide()#人脸数据库展示界面class Fle_SQliteWindow(QDialog,Ui_Dialog_SQliteWindow):def __init__(self):super(Fle_SQliteWindow,self).__init__()self.setupUi(self)self.tableView.setModel(mysqlite.model)self.pushButton_add.clicked.connect(self.addrow)self.pushButton_delete.clicked.connect(lambda: mysqlite.model.removeRow(self.tableView.currentIndex().row()))def addrow(self):# 不是在QTableView上添加,而是在模型上添加,会自动将数据保存到数据库中!# 参数一:数据库共有几行数据参数二:添加几行ret = mysqlite.model.insertRows(mysqlite.model.rowCount(), 1)# 返回是否插入print('数据库共有%d行数据' % mysqlite.model.rowCount())print('insertRow=%s' % str(ret))#数据库操作类class Fle_Sqlite():def __init__(self):self.db = QSqlDatabase.addDatabase('QSQLITE')self.db.setDatabaseName('./people_data.db')if not self.db.open():print('无法建立与数据库的连接')query = QSqlQuery()query.exec('create table people(id varcahr(10),xuehao varcahr(15),name varcahr(50))')self.model = QSqlTableModel()# MVC模式中的模型# 初始化将数据装载到模型当中self.initializeModel()# 初始化def initializeModel(self):self.model.setTable('people')# 当字段变化时会触发一些事件self.model.setEditStrategy(QSqlTableModel.OnFieldChange)# 将整个数据装载到model中self.model.select()# 设置字段头#只是设置字段头,不是显示self.model.setHeaderData(0, Qt.Horizontal, 'ID')self.model.setHeaderData(1, Qt.Horizontal, 'xuehao')self.model.setHeaderData(2, Qt.Horizontal, 'name')#找指定位置的数据def find_data(self, row, col):# 序号从0开始index = self.model.index(row, col)return self.model.data(index)#新加一行def add_row(self,ID,xuehao,name):row = self.model.rowCount()self.model.insertRow(row)self.model.setData(self.model.index(row, 0), ID)self.model.setData(self.model.index(row, 1), xuehao)self.model.setData(self.model.index(row, 2), name)self.model.submitAll()print(ID)print(xuehao)print(name)#删除最后一行def del_row(self):row = self.model.rowCount()-1self.model.removeRow(row)self.model.submitAll()def get_rows(self):#print(self.model.rowCount())return self.model.rowCount()if __name__ == "__main__":mysqlite=Fle_Sqlite()app = QApplication(sys.argv)w = Fle_MainWindow()w.show()sys.exit(app.exec_())

3、运行结果:


由上面两图可以看出识别效果正确。

识别出人脸时亮绿灯。


由上图可知,显示“不认识”时亮红灯。

六、总结与体会

①本次实验采用的LBPH人脸识别模型精度欠缺,受光线影响非常严重,或许可以通过摄像头加红外滤光片外加红外补光灯解决。
②本次实验中多次遇到摄像头调用打开不了导致imread出错的情况,具体原因没有找到,猜测是摄像头的序列号改变了。
③本次实验中创建了多个界面类,而人脸识别界面类和人脸录入界面类都需要调用摄像头,导致了摄像头经常报错,所以在界面切换的时候关掉了摄像头,在界面初始化的时候又打开了摄像头,但是这样做有时也会造成摄像头来不及释放而报错。
④对于数据库的操作,卡了很长的时间才分清楚数据库和数据表的区别,最后才搞清楚读写操作都是对链接到数据库的数据表操作。

附录

代码:
链接:https://pan.baidu.com/s/12RZ_K74Rp8rOkJ6TxA5TMg
提取码:b00q