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