目录

前言:

环境及库:

下位机:

上位机:

注意事项:


前言:

        该项目使用python实现人脸识别并进行比对串口反馈至51单片机控制舵机,是一个非常基础的嵌入式+cv的入门项目。若各位初学者在复现过程中遇到困难,可以私信,麻烦各位看官点点关注!

        当科技逐渐融入我们的日常生活,人们对于安全性和便利性的需求也日益增长。人脸识别技术作为一种生物特征识别技术,正在广泛应用于各个领域,从智能手机解锁到安全门禁系统。在本文中,我将分享如何使用Python实现人脸识别并将识别结果通过串口反馈至51单片机,以控制舵机实现简单的人脸锁功能。

        人脸识别技术通过分析人脸图像中的特征点和模式,识别出一个人的身份。与传统的密码或卡片认证方式相比,人脸识别更加安全和便捷,因为每个人的面部特征都是独一无二的。通过将人脸识别技术与微控制器(如51单片机)和舵机结合起来,我们可以创建一个简单而有效的人脸锁系统,只有在正确的人脸识别后才能打开门锁。


环境及库:

C语言库:

CORE\STC8Ax_REG.H

python库:

from multiprocessing.connection import wait
from time import sleep
import cv2
import numpy as np
from PIL import Image
from functools import reduce
from PIL import Image
import math,operator
import pyttsx3
import serial

下位机:

51单片机程序

以下代码的主要目的是控制舵机的角度,通过串口通信接收命令来实现不同角度的转动。代码首先初始化串口通信,配置波特率为9600bps,以便与其他设备进行数据传输。然后,定义了字符发送函数用于向串口发送数据,以及延时函数用于在程序中引入延时等待。舵机的具体控制通过两个子程序实现,zero子程序将舵机转动至0度,three子程序将舵机转动至135度,并在转动过程中关闭特定的引脚(l2)。主函数则包含了主要的控制逻辑,初始化输入输出引脚后,检测串口接收到的字符。如果接收到字符 '1',则调用three子程序,将舵机转动至135度,并关闭l2引脚。如果接收到其他字符或未接收到字符,将调用zero子程序,将舵机转动至0度。

#include ".\CORE\STC8Ax_REG.H"

sbit PWM = P2^1;  // 定义舵机信号线接的 I/O 口

void UartInit(void)  // 9600bps @ 11.0592MHz
{
    SCON = 0x50;     // 8 位数据,可变波特率
    AUXR |= 0x40;    // 定时器时钟 1T 模式
    AUXR &= 0xFE;    // 串口 1 选择定时器 1 为波特率发生器
    TMOD &= 0x0F;    // 设置定时器模式
    TL1 = 0xE0;      // 设置定时初始值
    TH1 = 0xFE;      // 设置定时初始值
    ET1 = 0;         // 禁止定时器%d中断
    TR1 = 1;         // 定时器 1 开始计时
}

void send(char dat)
{
    SBUF = dat;
    while (!TI)
    {
        TI = 0;
    }
}

void dalay(int ms)
{
    int i = 0, j = 0;
    for (i = 0; i < 5000; i++)
    {
        for (j = 0; j < ms; j++);
    }
}

void Delay(unsigned char i)  // 12MHz 延时函数
{
    unsigned char a, b;  // 该段延时函数 Delay(1) = 0.5ms
    for (; i > 0; i--)
    {
        for (b = 71; b > 0; b--)
        {
            for (a = 2; a > 0; a--);
        }
    }
}

void zero(void)  // 0 度 子程序
{
    PWM = 1;
    Delay(1);  // 高电平 Delay(1) = 0.5ms。因为周期为 20ms,所以低电平就是 19.5ms
    PWM = 0;
    Delay(39);  // 低电平 Delay(39) = 19.5ms
}

void three(void)  // 135 度 子程序
{
    PWM = 1;
    Delay(4);  // 高电平 Delay(4) = 2ms
    PWM = 0;
    Delay(36);  // 低电平 18ms
}

sbit l1 = P0^4;
sbit l2 = P0^5;
sbit l3 = P0^6;
sbit l4 = P0^7;
sbit k1 = P1^0;
sbit key = P5^1;
int flag = 1;

void main()
{
    P0M0 = 0;
    P0M1 = 0;
    P2M0 = 0;
    P2M1 = 0;
    UartInit();

    if (SBUF == '1')  // 按键接 P3^1 口。如果按键按下
    {
        Delay(20);
        if (SBUF == '1')
        {
            three();  // 如果按键按下,调用舵机 90 度子程序,实现转动 90 度
            l2 = 0;
        }
    }
    else
    {
        zero();  // 否则,舵机为 0 度
    }
}

上位机:

        这段代码实现了一个简单的人脸识别系统,可以通过摄像头捕获人脸图像,并与之前捕获的图像进行匹配。匹配成功后,通过串口通信控制单片机来执行不同的操作,例如开启或关闭门锁。

        当程序运行:打开摄像头,连续进行人脸检测。当按下键盘上的'A'键时,保存当前摄像头捕捉到的图像并进行人脸裁剪。当按下键盘上的'S'键时,保存当前摄像头捕捉到的图像并对其进行人脸裁剪,然后与先前保存的人脸图像进行特征比对。如果比对成功,将会触发语音提示,显示"匹配成功"和"欢迎xxx进入",同时通过串口向单片机发送数据来控制舵机,之后延迟2秒,再次通过串口向单片机发送数据来复位舵机,同时语音提示"重新上锁"。如果比对失败,将会触发语音提示"匹配失败"。

# 导入所需的库
from email.mime import image  # 用于处理图片类型的邮件附件
from multiprocessing.connection import wait  # 用于等待进程通信连接
from time import sleep  # 用于在代码中添加延迟
import cv2  # OpenCV库,用于图像处理
import numpy as np  # 用于处理数值数组
from PIL import Image  # Python Imaging Library,用于图像处理
from functools import reduce  # 用于函数式编程中的 reduce 操作
import math  # 用于数学计算
import operator  # 用于操作符操作
import pyttsx3  # 用于文本到语音的转换
import serial  # 用于串口通信

# 初始化串口通信,连接到 COM7 端口,波特率为 9600
com = serial.Serial('COM7', 9600)
print(com)

# 定义控制LED的函数
def led1():
    # 向串口发送字符 '1',控制单片机执行相应操作
    success_bytes = com.write('1'.encode())
    print(success_bytes)

def led2():
    # 向串口发送字符 '2',控制单片机执行相应操作
    success_bytes = com.write('2'.encode())
    print(success_bytes)

# 初始化标志变量为 6
flag = 6

# 初始化文本到语音引擎
engine = pyttsx3.init()

# 加载人脸识别器
face_cascade = cv2.CascadeClassifier(
    cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')

# 打开摄像头
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
cap.open(0)

# 进入主循环,不断读取摄像头帧并进行处理
while cap.isOpened():
    flag, frame = cap.read()
    frame = cv2.flip(frame, 1)  # 翻转图像以防止左右颠倒
    faces = face_cascade.detectMultiScale(frame, 1.3, 2)  # 检测人脸
    img = frame
    flag = 6

    # 遍历检测到的人脸并在图像上绘制矩形框
    for (x, y, w, h) in faces:
        img = cv2.rectangle(img, (x, y), (x + w, y + h), (0, 247, 181), 2)
        face_area = img[y:y + h, x:x + w]
        cv2.putText(img, 'people', (x, y - 7), 3, 1.2, (255, 0, 251), 2, cv2.LINE_AA)

    # 检测键盘按键
    key_pressed = cv2.waitKey(60)

    # 如果按下键盘上的数字键 '0',将图像进行边缘检测
    if key_pressed == 48:
        img = cv2.Canny(img, 100, 200)
        img = np.dstack((img, img, img))

    # 如果按下键盘上的字母 'a',进行人脸图像捕获和处理
    if key_pressed == ord("a"):
        # 保存图像到 "photo.jpg"
        cv2.imwrite("photo.jpg", img)
        mying = Image.open("photo.jpg")
        imgcrop = mying.crop((x, y, x + w, y + h))
        mying = imgcrop.resize((150, 150), Image.ANTIALIAS)
        mying.save("faceout.jpg")
        print("-------------------------")

    # 如果按下键盘上的字母 's',进行人脸匹配
    if key_pressed == ord("s"):
        cv2.imwrite("photo.jpg", img)
        mying = Image.open("photo.jpg")
        imgcrop = mying.crop((x, y, x + w + 10, y + h + 10))
        mying = imgcrop.resize((150, 150), Image.ANTIALIAS)
        mying.save("faceout2.jpg")
        print("-------------------------")

        # 计算两张图像的均方根误差(RMS)
        h1 = Image.open("faceout2.jpg").histogram()
        h2 = Image.open("faceout.jpg").histogram()
        RMS = math.sqrt(reduce(operator.add, list(
            map(lambda a, b: (a - b) ** 2, h1, h2))) / len(h1))
        print("RMS= ", RMS)

        # 如果RMS小于70,认为匹配成功,执行相应操作
        if (RMS > 0):
            if (RMS < 70):
                print("匹配成功")
                led1()
                engine.say("匹配成功")
                engine.say("xxx欢迎进入寝室")
                engine.runAndWait()
                sleep(2)
                engine.say("重新上锁")
                engine.runAndWait()
                led2()
            else:
                print("匹配失败")
                engine.say("匹配失败")
                engine.runAndWait()

    # 如果按下键盘上的 'Esc' 键,退出循环
    if key_pressed == 27:
        break

# 释放摄像头资源并关闭窗口
cap.release()
cv2.destroyAllWindows()

注意事项:

当读者在复现此项目时,需要注意三点:

1.舵机需要额外供电,另外,许多读者后台留言舵机无法转动,是因为舵机没有供地。

2.串口波特率统一。

3.python中serial库使用时需要查看设备管理器,确定是哪个com端在通信。