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先连接后再让电脑连)

相关推荐

  1. ESP32-WIFI(Arduino)

    2024-04-09 21:36:03       41 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-09 21:36:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-09 21:36:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-09 21:36:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-09 21:36:03       20 阅读

热门阅读

  1. 第 9 场 小白入门赛 字典树考试

    2024-04-09 21:36:03       16 阅读
  2. python爬虫基础知识整理(2)

    2024-04-09 21:36:03       13 阅读
  3. SQL语言

    SQL语言

    2024-04-09 21:36:03      15 阅读
  4. 如何使用Docker容器化改善你的开发流程

    2024-04-09 21:36:03       15 阅读
  5. == 和 ===什么区别呀?

    2024-04-09 21:36:03       17 阅读
  6. Pandas追加写入文件的时候写入到了第一行

    2024-04-09 21:36:03       13 阅读