效果
低于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");}// 连接WiFiWiFi.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.3f = 600d = (W * f) / wprint(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) # 使用微软雅黑字体,大小为36draw.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 QLabel2self.user_name_qwidget = self.gu.lineEditself.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.")returnpygame.mixer.init()# 加载音频文件pygame.mixer.music.load('7359.wav') # 靠的太近啦self.capture_active = Truedef play_audio():pygame.mixer.music.play(1)while pygame.mixer.music.get_busy():continuewhile self.capture_active:img = get_esp32cam_image('http://192.168.85.168/cam-hi.jpg') # Get image from ESP32CAMif 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.5f = 600 # 焦距d = (W * f) / wprint(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 valueself.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) # 使用微软雅黑字体,大小为36draw.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.shapebytesPerLine = c * wif 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 QPixmappixmap = QtGui.QPixmap.fromImage(q_img)# Display QPixmap on QLabelself.video_label.setPixmap(pixmap)self.video_label.setScaledContents(True)self.video_label.update()cv2.waitKey(1)def stop_capture(self):self.capture_active = Falsedef 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 imgif __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先连接后再让电脑连)