找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

385

积分

0

好友

45

主题
发表于 昨天 04:45 | 查看: 7| 回复: 0

本文介绍了使用工业树莓派 CM0 NANO 单板计算机,结合 OpenCV 人脸识别和 PWM 舵机控制,实现一个完整智能门禁系统的项目设计。内容涵盖硬件连接、舵机控制、人脸识别、网页前后端开发、流程图、完整代码和效果演示,为在边缘AI设备上快速开发应用提供了参考。

项目介绍

项目主要分为以下三个部分:

  • 准备工作:硬件连接、OpenCV 安装、人脸识别模型获取、训练图像准备等。
  • 舵机控制:PWM输出、舵机转速和角度控制、代码实现及效果演示。
  • 门禁系统集成:完整的文件目录、系统流程图、前后端代码实现及最终效果。

准备工作

准备工作包括硬件连接、虚拟环境创建、OpenCV 安装、模型下载和图像训练等步骤。

硬件连接

首先,确保设备可以正常连接WiFi实现无线通信,并使用 Micro-USB 数据线为设备供电。

树莓派CM0 NANO开发板连接示意图

根据板载的 40pin GPIO 引脚定义,驱动舵机需要使用支持 PWM 输出的物理引脚。在本项目中,我们使用物理引脚 12,其对应的 BCM 引脚编号为 18。你可以在 树莓派引脚定义网站 上查看详细的引脚图。

树莓派GPIO 40引脚功能定义图

舵机(以SG90为例)通常有三根线:信号线(黄色)、电源线(红色)和地线(棕色)。连接方式如下:

Raspberry Pi SG90 描述
GPIO18 (Pin12) S (Yellow) 信号线
5V (Pin2) 5V (Red) 电源
GND (Pin6) GND (Brown) 地线

SG90舵机三线连接定义图

OpenCV 安装

建议在虚拟环境中安装OpenCV,以方便管理依赖。

  • 创建并激活虚拟环境

    mkdir ~/cv && cd ~/cv    # 创建 cv 文件夹,便于管理
    python3 -m venv venv     # 创建虚拟环境 venv
    source venv/bin/activate # 激活虚拟环境 venv
  • 安装 numpy 和 opencv

    pip install -U pip numpy                         # 安装 numpy
    pip install opencv-python opencv-contrib-python  # opencv 主模块及 contrib
  • 验证安装

    python3 -c "import cv2,sys,numpy;print('OpenCV:',cv2.__version__,'NumPy:',numpy.__version__)"

更多关于OpenCV的信息,可以访问其 官方网站

人脸识别

本项目使用OpenCV进行人脸识别,流程包括:注册并训练目标人脸,使用 YuNet 模型检测人脸,再结合 sface 模型进行特征匹配与识别。

模型文件可以从OpenCV Zoo获取。这个在 人工智能 领域广泛应用的开源模型库,为我们提供了可靠的预训练模型。

  • 模型获取
    下载所需的人脸检测和识别模型文件。

    wget https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx
    wget https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx

    下载后,请将文件存放在 ./model 目录下。

  • 训练图片
    准备用于注册的人脸图片。建议将图片裁剪至合适大小,并以对应的人名作为文件名(例如 Edward.jpg),然后放置在 ./face 文件夹中。

用于人脸注册的训练图片示例

舵机控制

我们利用树莓派板载 GPIO 的 PWM 功能,来驱动和控制 SG90 舵机的旋转速度和角度。

代码实现

在终端执行 touch servo360.py 新建程序文件,并添加以下代码:

import sys, time
import RPi.GPIO as GPIO

GPIO_PIN  = 18
FREQ      = 50
CENTER    = 7.5
RANGE     = 2.5
# --------- Parameters ---------
SPEED_DPS = 480              # 实测:每秒 480 度
PWM_DEAD  = 0.05             # 停转
# ----------------------------
def duty(speed):
    return CENTER + max(-1, min(1, speed)) * RANGE

def rotate(target_deg, speed=1.0):
    """
    target_deg : 角度,负值反转
    speed      : 0~1,默认全速
    """
    if not target_deg:
        return
    direction = 1 if target_deg > 0 else -1
    run_speed = speed * direction
    run_time  = abs(target_deg) / (SPEED_DPS * speed)   # 时长
    pwm = GPIO.PWM(GPIO_PIN, FREQ)
    pwm.start(0)
    pwm.ChangeDutyCycle(duty(run_speed))
    time.sleep(run_time)
    pwm.ChangeDutyCycle(CENTER)   # 停
    time.sleep(PWM_DEAD)
    pwm.stop()

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("缺少角度"); sys.exit(1)
    deg = float(sys.argv[1])
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_PIN, GPIO.OUT)
    try:
        rotate(deg)
    finally:
        GPIO.cleanup()

保存代码。这是一个典型的在树莓派上进行硬件交互的 开源实战 示例。

效果演示

在终端执行指令 python servo360.py 90,舵机将逆时针转动 90 度。

SG90舵机转动效果示意图

门禁系统

在完成人脸识别和舵机控制的基础上,我们将它们集成起来,实现完整的智能门禁系统。

文件目录

项目的整体文件结构如下:

~/AI/FaceRecognition $ tree
.
├── access.names
├── app.py
├── face
│   ├── Arnold.jpg
│   ├── Clarke.jpg
│   ├── Perry.jpg
│   └── Robert.jpg
├── model
│   ├── face_detection_yunet_2023mar.onnx
│   ├── face_recognition_sface_2021dec.onnx
│   └── face_registry.pkl
├── static
│   └── result.jpg
└── templates
    └── index.html

流程图

系统的工作流程如下图所示:

人脸识别门禁系统工作流程图

代码实现

系统包含三个核心代码文件:./access.names 是白名单,./app.py 是 Flask 服务器后端,./templates/index.html 是网页前端。

  • Flask 后端 (app.py)
    在终端执行 touch app.py 新建文件,并添加以下代码:
    
    #!/usr/bin/env python3
    import os, cv2, numpy as np, pickle, time
    from pathlib import Path
    from flask import Flask, request, jsonify, render_template, url_for
    import RPi.GPIO as GPIO
    import threading

PIN_SERVO = 18
FREQ      = 50
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN_SERVO, GPIO.OUT)
pwm = GPIO.PWM(PIN_SERVO, FREQ)
pwm.start(0)

读取白名单

ACCESS_LIST = set(line.strip() for line in open('access.names') if line.strip())

---------- 人脸模型 ----------

detector   = cv2.FaceDetectorYN_create("model/face_detection_yunet_2023mar.onnx", "", (320, 320))
recognizer = cv2.FaceRecognizerSF_create("model/face_recognition_sface_2021dec.onnx", "")
registry   = pickle.loads(Path("model/face_registry.pkl").read_bytes()) if Path("model/face_registry.pkl").exists() else {}

def rotate(angle, speed=480):
duty = 2.5 if angle > 0 else 12.5
pwm.ChangeDutyCycle(duty)
time.sleep(abs(angle) / speed)
pwm.ChangeDutyCycle(0)

def door_cycle():
rotate(90); time.sleep(3); rotate(-90)   # 门禁控制

---------- Flask ----------

app = Flask(name)

@app.route('/')
def index():
return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload():
file = request.files['image']
img  = cv2.imdecode(np.frombuffer(file.read(), np.uint8), cv2.IMREAD_COLOR)
h, w = img.shape[:2]
detector.setInputSize((w, h))
faces = detector.detect(img)[1]
name, score = "Unknown", 0.0

if faces is not None:
    face = faces[0]
    aligned = recognizer.alignCrop(img, face)
    feat    = recognizer.feature(aligned)
    for reg_name, reg_feat in registry.items():
        s = recognizer.match(feat, reg_feat, cv2.FaceRecognizerSF_FR_COSINE)
        if s > score:
            score, name = s, reg_name
    if score < 0.3:          # 识别阈值
        name = "Unknown"

# 门禁动作
if name != "Unknown" and name in ACCESS_LIST:
    threading.Thread(target=door_cycle, daemon=True).start()
    tip = f"{name} 请通行"
else:
    tip = f"{name} 无权限,拒绝通行"

# 保存识别结果
if faces is not None:
    x, y, w_box, h_box = map(int, face[:4])
    cv2.rectangle(img, (x, y), (x + w_box, y + h_box), (0, 255, 0), 2)
    cv2.putText(img, f"{name}:{score:.2f}", (x, y - 6),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
out_path = "./static/result.jpg"
cv2.imwrite(out_path, img)

return jsonify(name=name, score=round(score, 3), tip=tip,
               result_url=url_for('static', filename='result.jpg'))

---------- 退出 ----------

import atexit
atexit.register(lambda: (pwm.stop(), GPIO.cleanup()))

if name == 'main':
app.run(host='0.0.0.0', port=5000, debug=False)


*   **Web 前端 (index.html)**
在 `./templates` 目录下执行 `touch index.html` 新建文件,并添加以下代码:
```html
<!doctype html>
<html lang="zh">
<head>
  <meta charset="utf-8">
  <title>树莓派门禁</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <style>
    :root{
      --accent:#00c853;
      --danger:#ff1744;
      --bg:#e3f2fd;
      --card:rgba(255,255,255,.75);
      --radius:16px;
      --trans:.35s cubic-bezier(.4,0,.2,1)
    }
    body{margin:0;height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg);font-family:system-ui,Arial}
    #card{width:320px;padding:32px 24px;background:var(--card);backdrop-filter:blur(12px);border-radius:var(--radius);box-shadow:0 8px 32px rgba(0,0,0,.1);text-align:center;transition:var(--trans)}
    h2{margin:0 0 20px;font-size:22px;font-weight:600;color:#0d47a1}
    input[type=file]{display:none}
    label{display:inline-block;padding:10px 20px;border:2px dashed var(--accent);border-radius:var(--radius);cursor:pointer;color:var(--accent);transition:var(--trans)}
    label:hover{background:var(--accent);color:#fff}
    button{margin-top:16px;padding:10px 0;width:100%;border:none;border-radius:var(--radius);background:var(--accent);color:#fff;font-size:16px;cursor:pointer;transition:var(--trans);box-shadow:0 2px 8px rgba(0,200,83,.3)}
    button:active{transform:scale(.97)}
    .status{margin-top:18px;font-size:17px;height:24px;opacity:0;transition:var(--trans)}
    .status.show{opacity:1}
    .status.ok{color:var(--accent)}
    .status.no{color:var(--danger)}
    img{width:100%;border-radius:var(--radius);margin-top:16px;box-shadow:0 4px 16px rgba(0,0,0,.08);display:none}
  </style>
</head>
<body>
  <div id="card">
    <h2>人脸识别门禁</h2>
    <input type="file" id="f" accept="image/*">
    <label for="f">选择照片</label>
    <button onclick="up()">上传识别</button>
    <div id="s" class="status"></div>
    <img id="i">
  </div>
  <script>
    async function up(){
      const file=f.files[0];
      if(!file) return alert('请选择图片');
      s.className='status show';s.textContent='识别中…';
      const fd=new FormData();fd.append('image',file);
      const r=await(fetch('/upload',{method:'POST',body:fd}).then(x=>x.json()));
      s.textContent=r.tip;
      s.classList.toggle('ok',!r.tip.includes('拒绝'));
      s.classList.toggle('no',r.tip.includes('拒绝'));
      i.src=r.result_url+'?t='+Date.now();i.style.display='block';
      setTimeout(()=>{s.textContent='已关门,等待识别';i.style.display='none'},3000)
    }
  </script>
</body>
</html>
  • 白名单 (access.names)
    在终端执行 touch access.names 新建文件,添加允许通行的人名列表,每行一个。
    Linda
    Edward
    Clarke

效果演示

  1. 启动服务:在终端执行指令 python app.py 运行程序。
  2. 访问网页:终端会打印 Web 服务器地址,例如 http://192.168.31.117:5000/。在浏览器中打开此地址。

Flask应用在树莓派终端中启动的截图

  1. 识别操作
    • 点击“选择照片”按钮,加载待识别的人脸图片。
    • 点击“上传识别”按钮,系统将立即显示识别结果和是否允许通行。

人脸识别成功并允许通行的网页界面

人脸识别成功但无权限被拒绝的网页界面

  1. 门禁动作
    • 如果识别出的人脸在白名单中,舵机会逆时针转动90度,模拟开门。
    • 等待3秒钟后,舵机顺时针转回90度,模拟关门。
    • 同时,网页前端的状态会更新为“已关门,等待识别”。

门禁系统处于等待识别状态的网页界面

动态效果

人脸识别门禁系统完整工作流程动态演示

总结

本项目详细演示了如何利用树莓派 CM0 NANO 这款低成本、小体积的边缘计算设备,结合成熟的OpenCV人脸识别技术和简单的舵机控制,快速搭建一个功能完整的智能门禁原型系统。从环境部署、模型获取到关键代码解析和效果演示,为开发者提供了一个在资源受限的嵌入式平台上实现AI应用的清晰范例。

如果你对这类结合硬件与AI的实战项目感兴趣,欢迎在 云栈社区 与更多开发者交流探讨。




上一篇:基于AI描述自动生成前端界面:json-render工具实践
下一篇:MySQL InnoDB Checkpoint机制深度解析:数据持久化与故障恢复的核心
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 19:47 , Processed in 0.276305 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表