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


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端计算代码
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
项目踩坑
1.驱动ESP32-CAM 这里下载zip自己导入
2.配置开发板
Arduino中文社区
从这里下载会自动安装指定位置
不要在图中位置配置,速度太慢!!
 
3.python识别面部距离,需要电脑端和esp32-cam同时连接一个WIFI
由于esp32-cam连WIFI能力较差
(若手机开热点供双方连接,建议esp32-cam先连接后再让电脑连)

![[StartingPoint][Tier1]Crocodile](https://img-blog.csdnimg.cn/img_convert/6a6e016ae303f164f5fb76e63ec0f07b.jpeg)












![[VulnHub靶机渗透] pWnOS 2.0](https://img-blog.csdnimg.cn/direct/0b9dd1d29c6e4c6181c58ee04faba763.png)




