Arduino开发 esp32cam+opencv人脸识别距离+语音提醒

效果

低于20厘米语音提醒字体变红

QQ录屏20240406131651

Arduino代码

可直接复制使用(修改自己的WIFI)

#include <esp32cam.h>
#include <WebServer.h>
#include <WiFi.h>
// 设置要连接的WiFi名称和密码
const char* WIFI_SSID = "gumou"; 
const char* WIFI_PASS = "gu3456789";
WebServer server(80);
// 设置不同分辨率的静态变量
static auto loRes = esp32cam::Resolution::find(320, 240);
static auto hiRes = esp32cam::Resolution::find(1280, 1024);
// 处理BMP图像请求
void handleBmp() {
  if (!esp32cam::Camera.changeResolution(loRes)) {
    Serial.println("SET-LO-RES FAIL");
  }
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAIL");
    server.send(503, "", "");
    return;
  }
  
  if (!frame->toBmp()) {
    Serial.println("CONVERT FAIL");
    server.send(503, "", "");
    return;
  }
  
  server.setContentLength(frame->size());
  server.send(200, "image/bmp");
  WiFiClient client = server.client();
  frame->writeTo(client);
}

// 服务JPG图像请求
void serveJpg() {
  auto frame = esp32cam::capture();
  if (frame == nullptr) {
    Serial.println("CAPTURE FAIL");
    server.send(503, "", "");
    return;
  }
  
  server.setContentLength(frame->size());
  server.send(200, "image/jpeg");
  WiFiClient client = server.client();
  frame->writeTo(client);
}

// 处理低分辨率JPG请求
void handleJpgLo() {
  if (!esp32cam::Camera.changeResolution(loRes)) {
    Serial.println("SET-LO-RES FAIL");
  }
  serveJpg();
}

// 处理高分辨率JPG请求
void handleJpgHi() {
  if (!esp32cam::Camera.changeResolution(hiRes)) {
    Serial.println("SET-HI-RES FAIL");
  }
  serveJpg();
}

// 处理JPG请求
void handleJpg() {
  server.sendHeader("Location", "/cam-hi.jpg");
  server.send(302, "", "");
}

// 处理MJPEG流请求
void handleMjpeg() {
  if (!esp32cam::Camera.changeResolution(hiRes)) {
    Serial.println("SET-HI-RES FAIL");
  }
  
  Serial.println("STREAM BEGIN");
  WiFiClient client = server.client();
  auto startTime = millis();
  int res = esp32cam::Camera.streamMjpeg(client);
  if (res <= 0) {
    Serial.printf("STREAM ERROR %d\n", res);
    return;
  }
  
  auto duration = millis() - startTime;
  Serial.printf("STREAM END %dfrm %0.2ffps\n", res, 1000.0 * res / duration);
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  
  // 初始化摄像头
  {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(hiRes);
    cfg.setBufferCount(2);
    cfg.setJpeg(80);
    
    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
  }
  
  // 连接WiFi
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  
  // 打印服务器地址和端口
  Serial.print("http://");
  Serial.println(WiFi.localIP());
  Serial.println("  /cam.bmp");
  Serial.println("  /cam-lo.jpg");
  Serial.println("  /cam-hi.jpg");
  Serial.println("  /cam.mjpeg");

  // 定义服务器路由
  server.on("/cam.bmp", handleBmp);
  server.on("/cam-lo.jpg", handleJpgLo);
  server.on("/cam-hi.jpg", handleJpgHi);
  server.on("/cam.jpg", handleJpg);
  server.on("/cam.mjpeg", handleMjpeg);
  // 启动服务器
  server.begin();
}
void loop() {
  // 处理客户端请求
  server.handleClient();
}

查看Esp32的IP地址

录入后,在串口监视器处查看IP(自动会输出)

录入前要把波特率调整115200

python端计算代码

可更改 1.IP地址 2.提醒语音 3.提醒距离 4.可删除倒数第二行 不显示画面 只测量距离

import urllib
import cv2
import numpy as np
from cvzone.FaceMeshModule import FaceMeshDetector
import pygame
import threading
from PIL import Image, ImageDraw, ImageFont
# 初始化pygame.mixer
pygame.mixer.init()
# 加载音频文件
pygame.mixer.music.load('7359.wav')  # 靠的太近啦音频
detector = FaceMeshDetector(maxFaces=1)
url = 'http://192.168.85.168/cam-hi.jpg'  # 改成自己的IP地址+/cam-hi.jpg
# 定义播放音频的函数
def play_audio():
    pygame.mixer.music.play(1)
    while pygame.mixer.music.get_busy():
        continue
# 函数:从ESP32CAM获取图像
def get_esp32cam_image(url):
    img_resp = urllib.request.urlopen(url)
    img_np = np.array(bytearray(img_resp.read()), dtype=np.uint8)
    img = cv2.imdecode(img_np, -1)
    return img
# 开始检测人脸距离
while True:
    # 从ESP32CAM获取图像
    img = get_esp32cam_image(url)
    # 检测人脸
    img, faces = detector.findFaceMesh(img, draw=False)
    if faces:
        face = faces[0]
        point_left = face[145]
        point_right = face[374]
        w, _ = detector.findDistance(point_left, point_right)
        W = 6.3
        f = 600
        d = (W * f) / w
        print(d)

        # 设置距离颜色
        if d < 20:
            print("过近提醒")
            # 检查是否正在播放音频
            if not pygame.mixer.music.get_busy():
                # 使用线程播放音频,避免阻塞主程序
                audio_thread = threading.Thread(target=play_audio)
                audio_thread.start()
            text_color = (255, 0, 0)  # 红色
        else:
            text_color = (0, 0, 255)  # 蓝色
        # 将Depth文本显示为汉语
        pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(pil_img)
        font = ImageFont.truetype("msyh.ttc", 36)  # 使用微软雅黑字体,大小为36
        draw.text((face[10][0] - 95, face[10][1] - 5), f'距离:{int(d)}厘米', font=font, fill=text_color)
        img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
    cv2.imshow("Distance recognition", img)   #这行注释掉后可以不显示摄像头窗口只输出距离
    if cv2.waitKey(1) == ord('q'):
        break
cv2.destroyAllWindows()

手机端查看  IP地址同上

图片

http://192.168.85.168/cam-hi.jpg

视频

http://192.168.85.168/cam.mjpeg

UI设计

设计ui之后画面较小 若摄像头分辨率较低建议使用上面的代码

import sys
from PyQt5 import QtGui
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import Qt, QPoint, pyqtSignal
from esp32_ui import Ui_Form  # UI
import cv2
from cvzone.FaceMeshModule import FaceMeshDetector
import pygame
import threading
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import urllib.request
import ping
class guWindow(QWidget):
    close_signal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.gu = Ui_Form()
        self.gu.setupUi(self)
        self.gu.lineEdit.returnPressed.connect(self.gumou)  # lineEdit回车运行
        self.video_label = self.gu.label_2# 2 QLabel2
        self.user_name_qwidget = self.gu.lineEdit
        self.progress_bar = self.gu.progressBar  #进度条
        self.setWindowOpacity(0.90)  # 设置窗口透明度
        self.setWindowFlag(Qt.FramelessWindowHint)  # 去除边框
        self.setAttribute(Qt.WA_TranslucentBackground)  # 去除白色背景
        self.offset = QPoint()  # 记录鼠标按下的初始位置
        self.close_signal.connect(self.closeEvent)

    def closeEvent(self, event):
        # 关闭窗口时发送信号
        self.stop_capture()

    def mousePressEvent(self, event):
        self.offset = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            self.move(self.pos() + event.pos() - self.offset)  # 移动窗口位置

    def gumou(self):  # 按钮绑定的函数 功能
        s = self.user_name_qwidget.text()
        self.user_name_qwidget.clear()
        try:
            distance_threshold = float(s)  # 将用户输入的文本转换为浮点数作为距离阈值
        except ValueError:
            print("Invalid input. Please enter a valid number.")
            return

        pygame.mixer.init()
        # 加载音频文件
        pygame.mixer.music.load('7359.wav')  # 靠的太近啦
        self.capture_active = True

        def play_audio():
            pygame.mixer.music.play(1)
            while pygame.mixer.music.get_busy():
                continue

        while self.capture_active:
            img = get_esp32cam_image('http://192.168.85.168/cam-hi.jpg')  # Get image from ESP32CAM
            if img is not None:
                self.detect_and_display(img, distance_threshold, play_audio)

    def detect_and_display(self, img, distance_threshold, play_audio):
        self.detector = FaceMeshDetector(maxFaces=1)
        img, faces = self.detector.findFaceMesh(img, draw=False)
        if faces:
            face = faces[0]
            pointLeft = face[145]
            pointRight = face[374]
            w, _ = self.detector.findDistance(pointLeft, pointRight)
            W = 6.5
            f = 600  # 焦距
            d = (W * f) / w
            print(d)
            # 设置距离颜色
            if d < distance_threshold:  # 使用用户输入的距离阈值作为判断条件
                print("过近提醒")
                # 检查是否正在播放音频
                if not pygame.mixer.music.get_busy():
                    # 使用线程播放音频,避免阻塞主程序
                    audio_thread = threading.Thread(target=play_audio)
                    audio_thread.start()
                text_color = (255, 0, 0)  # 红色
            else:
                text_color = (0, 0, 255)  # 蓝色
            # Update the QProgressBar value
            self.progress_bar.setValue(int(d))
            # 将 Depth 文本显示为汉语
            pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
            draw = ImageDraw.Draw(pil_img)
            font = ImageFont.truetype("msyh.ttc", 36)  # 使用微软雅黑字体,大小为36
            draw.text((face[10][0] - 95, face[10][1] - 5), f'距离:{int(d)}厘米', font=font, fill=text_color)
            img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
        h, w, c = img.shape
        bytesPerLine = c * w
        if c == 3:  # 如果颜色通道为3(BGR)
            q_img = QtGui.QImage(img.data, w, h, bytesPerLine, QtGui.QImage.Format_BGR888)
        else:  # 如果颜色通道为4(BGRA)
            q_img = QtGui.QImage(img.data, w, h, bytesPerLine, QtGui.QImage.Format_BGRA8888)
        # Convert QImage to QPixmap
        pixmap = QtGui.QPixmap.fromImage(q_img)
        # Display QPixmap on QLabel
        self.video_label.setPixmap(pixmap)
        self.video_label.setScaledContents(True)
        self.video_label.update()
        cv2.waitKey(1)

    def stop_capture(self):
        self.capture_active = False

def get_esp32cam_image(url):
    img_resp = urllib.request.urlopen(url)
    img_np = np.array(bytearray(img_resp.read()), dtype=np.uint8)
    img = cv2.imdecode(img_np, -1)
    return img

if __name__ == '__main__':
    app = QApplication(sys.argv)
    icon = QtGui.QIcon(':/jay.ico')
    app.setWindowIcon(icon)
    # 创建可拖动窗口实例
    ui = guWindow()  # 函数
    # 显示窗口
    ui.show()
    # 启动应用程序事件循环
    sys.exit(app.exec_())

项目踩坑

1.驱动ESP32-CAM 这里下载zip自己导入

2.配置开发板 

Arduino中文社区

从这里下载会自动安装指定位置

不要在图中位置配置,速度太慢!!

3.python识别面部距离,需要电脑端和esp32-cam同时连接一个WIFI

由于esp32-cam连WIFI能力较差

(若手机开热点供双方连接,建议esp32-cam先连接后再让电脑连)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/526942.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

指针的深入理解(六)

指针的深入理解&#xff08;六&#xff09; 个人主页&#xff1a;大白的编程日记 感谢遇见&#xff0c;我们一起学习进步&#xff01; 文章目录 指针的深入理解&#xff08;六&#xff09;前言一. sizeof和strlen1.1sizeof1.2strlen1.3sizeof和strlen对比 二.数组名和指针加减…

动态代理

动态代理 动态代理和静态代理角色一致。 代理类是动态生成的,不是我们直接写好的。 动态代理分为俩大类:基于接口的动态代理、基于类的动态代理 基于接口:JDK动态代理(以下示例就是这个) 基于类:cglib java字节码实现:javasist JDK动态代理 InvocationHandler Proxy …

C语言从入门到实战————编译和链接

目录 前言 1. 翻译环境和运行环境 2. 翻译环境 2.1 预处理&#xff08;预编译&#xff09; 2.2 编译 2.2.1 词法分析&#xff1a; 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 2.4 链接 3. 运行环境 前言 编译和链接是将C语言源代码转换成可执行文件的必经过程&a…

分公司=-部门--组合模式

1.1 分公司不就是一部门吗&#xff1f; "我们公司最近接了一个项目&#xff0c;是为一家在全国许多城市都有分销机构的大公司做办公管理系统&#xff0c;总部有人力资源、财务、运营等部门。" "这是很常见的OA系统&#xff0c;需求分析好的话&#xff0…

Linux 内核移植exfat驱动

简介&#xff1a; Linux系统默认可以自动识别到fat32格式的盘&#xff0c;但fat32支持的文件不能大于4G&#xff0c;所以只能将移动硬盘和U盘格式化为NTFS和exFAT这两种格式的&#xff0c;对于U盘最好格式化为exFAT。 Linux5.4以上的内核原生支持exfat格式&#xff0c;不需要你…

【LeetCode: 572. 另一棵树的子树 + 二叉树 + dfs】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

UE4_动画基础_ 使用分层动画(Using Layered Animations)

完成在移动过程中武器发射的角色制作&#xff01; 动画混合仅仅意味着在一个角色或骨架网格体上的两个或多个动画之间进行平滑过渡。在虚幻引擎4中&#xff0c;有多种方法可以应用这种混合&#xff0c;要么通过混合空间&#xff0c;或通过实际组合两个基于加权偏差或alpha值的…

开源免费的多功能PDF工具箱

它支持修改PDF、编辑PDF书签、导出PDF书签、导入书签、生成、合并、拆分、提取页面内容、提取图片、OCR 功能介绍: 修改PDF信息&#xff1a;修改文档属性、页码编号、页面链接、页面尺寸&#xff1b;删除自动打开网页等动作&#xff0c;去除复制及打印限制&#xff1b;设置阅读…

SpringBoot中这样用ObjectMapper,才够优雅!

目录 背景步骤在SpringBoot项目中要实现对象与Json字符串的互转&#xff0c;每次都需要像如下一样new 一个ObjectMapper对象&#xff1a;这样的代码到处可见&#xff0c;有问题吗&#xff1f;我们要使用jmh测试几种方式的区别&#xff1a;所以在我们真正使用的时候不要在方法中…

tesseract-ocr一站式安装与使用

目录 前言 安装tesseract-ocr 添加环境变量 1、在path中添加 2、在系統變量中添加 3、验证是否添加成功 添加语言包 更多语言包下载 示例程序 前言 如果你遇到了&#xff1a;make sure the TESSDATA_PREFIX Failed loading language \‘chi_sim 那么就是语言包缺少这个&#xf…

【简单讲解下Fine-tuning BERT】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

XAMPP本地开发环境软件的最佳替代品

在开发新网站或应用时&#xff0c;选择合适的本地开发环境是至关重要的。本地开发环境让您可以在自己的电脑上搭建和测试网站或应用&#xff0c;直到它们准备好被迁移到线上服务器。一些工具甚至提供了推送到生产环境的功能&#xff0c;以及设置多个本地站点的能力。 XAMPP是一…

34-5 CSRF漏洞 - CSRF分类

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 1)GET 类型 传参: 参数连接在URL后面 POC构造及执行流程: 构造URL,诱导受害者访问点击利用利用标签进行攻击: 构造虚假URL,在链接上添加payload抓包获取数据包,通过CSRF POC…

ping命令返回无法访问目标主机和请求超时浅析

在日常经常用ping命令测试网络是否通信正常&#xff0c;使用ping命令时也经常会遇到这两种情况&#xff0c;那么表示网络出现了问题。 1、请求超时的原因 可以看到“请求超时”没有收到任何回复。要知道&#xff0c;IP数据报是有生存时间的&#xff0c;当其生存时间为零时就会…

K8s学习七(服务发现_2)

Ingress Service 主要用于集群内部的通信和负载均衡&#xff0c;而 Ingress 则是用于将服务暴露到集群外部&#xff0c;并提供灵活的 HTTP 路由规则。在实际应用中&#xff0c;它们通常结合使用&#xff0c;Service 提供内部通信和负载均衡&#xff0c;Ingress 提供外部访问和…

植物糖基转移酶数据库-23年-地表最强系列-文献精读-6

pUGTdb: A comprehensive database of plant UDP-dependent glycosyltransferases pUGTdb&#xff1a;植物UDP依赖糖基转移酶的全面数据库 一篇关于植物糖基转移数据库的综述&#xff0c;地表最强&#xff0c;总结的最全面的版本之一&#xff0c;各位看官有推荐请留言评论区~…

自定义复选款与单选框,input

注&#xff1a;字体文字取自bootstrap字体库https://icons.bootcss.com/icons <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>.checkbox-com,.radio-com {position: relative;display: inlin…

javaWeb物流信息网的设计与实现

摘要 本文讲述了基于JSP物流信息网的设计与实现。该系统使用java语言开发&#xff0c;使系统具有更好的平台性和可扩展性。 该系统实现了用户登录、注册、查询快递信息、快递公司注册成为合作伙伴以及系统管理员对信息进行管理等功能。系统的主要界面会将所有的服务排列好&…

get请求搜索功能爬虫

<!--爬虫仅支持1.8版本的jdk--> <!-- 爬虫需要的依赖--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>…

STM32一个地址未对齐引起的 HardFault 异常

1. 概述 客户在使用 STM32G070 的时候&#xff0c;KEIL MDK 为编译工具&#xff0c;当编译优化选项设置为Level0 的时候&#xff0c;程序会出现 Hard Fault 异常&#xff0c;而当编译优化选项设置为 Level1 的时候&#xff0c;则程序运行正常。表面上看&#xff0c;这似乎是 K…