楼主 发表于 2025-5-16 21:28:47

ESP32-AI摄像头物体检测+边框追踪

本帖最后由 楼主 于 2025-5-16 21:32 编辑

本次测试的内容为ESP32 AI camera实现物体检测和物体追踪 五一前收到了心心念念的新一代ESP32 camera,听说从硬件上对产品做了很大的提升。不过最近工作任务太多,测试早早做完了,但是一直没有发帖子分享,这周终于挤出了时间码一篇心得,话不多说看看实测效果。一、实验目的​​掌握ESP32 AI Camera的硬件配置与开发环境搭建方法。设计算法实现动态物体追踪,结合摄像头锁定目标。二、实验器材与软件环境​​硬件​​1.ESP32 AI Camera开发板(带OV2640摄像头模块)2.伺服舵机(用于物理追踪)3.USB数据线、杜邦线4.PC(Windows/Linux)
软件​​1.Arduino IDE2.OpenCV库(模型训练与数据预处理)三、实验原理​​1.物体检测​​使用封装好的openCVLibrary348库的模型进行实时图像推理。通过摄像头采集图像,模型输出目标类别与边界框坐标。2.物体追踪​​​​中心点追踪​​:计算检测框中心与画面中心的偏移量,控制云台转动。​​手动追踪​​:通过手动锁定物体框进行追踪,控制云台转动。四、实验步骤​​1.环境搭建​​安装Arduino ESP32开发板支持包导入openCV库及摄像头驱动连接摄像头模块至开发板2.代码测试#include <WebSocketsServer.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include "camera_wrap.h"

// #define DEBUG
// #define SAVE_IMG

enum TRACK{
TRACK_NONE = 0,
TRACK_FW,
TRACK_LEFT,
TRACK_RIGHT,
TRACK_STOP
};

const char* ssid = "PS4";    // <<< change this as yours
const char* password = "22222222"; // <<< change this as yours
//holds the current upload
int cameraInitState = -1;
uint8_t* jpgBuff = new uint8_t;
size_t   jpgLength = 0;
uint8_t camNo=0;
bool clientConnected = false;

//Creating UDP Listener Object.
WiFiUDP UDPServer;
IPAddress addrRemote;
unsigned int portRemote;
unsigned int UDPPort = 6868;
const int RECVLENGTH = 16;
byte packetBuffer;

WebSocketsServer webSocket = WebSocketsServer(86);
String html_home;

const int LED_BUILT_IN      = 4;
const uint8_t TRACK_DUTY      = 100;
const int PIN_SERVO_PITCH   = 12;
// const int PIN_SERVO_YAW       = 2;
const int PINDC_LEFT_BACK   = 13;
const int PINDC_LEFT_FORWARD= 15;
const int PINDC_RIGHT_BACK    = 14;
const int PINDC_RIGHT_FORWARD = 2;
const int LEFT_CHANNEL      = 2;
const int RIGHT_CHANNEL       = 3;
const int SERVO_PITCH_CHANNEL = 4;
const int SERVO_YAW_CHANNEL   = 5;
const int SERVO_RESOLUTION    = 16;
unsigned long previousMillisServo = 0;
const unsigned long intervalServo = 10;
bool servoUp = false;
bool servoDown = false;
bool servoRotateLeft = false;
bool servoRotateRight = false;
int posServo = 75;
int PWMTrackHIGH = 138;
int PWMTrackLOW = 138;

void servoWrite(uint8_t channel, uint8_t angle) {
// regarding the datasheet of sg90 servo, pwm period is 20 ms and duty is 1->2ms
uint32_t maxDuty = (pow(2,SERVO_RESOLUTION)-1)/10;
uint32_t minDuty = (pow(2,SERVO_RESOLUTION)-1)/20;
uint32_t duty = (maxDuty-minDuty)*angle/180 + minDuty;
ledcWrite(channel, duty);
}

void controlServo(){
if(servoUp){
    if(posServo>2){
      posServo -= 2;
    }
}
if(servoDown){
    if(posServo<180){
      posServo += 2;
    }
}
servoWrite(SERVO_PITCH_CHANNEL,posServo);
}

void controlDC(int left0, int left1, int right0, int right1){
digitalWrite(PINDC_LEFT_BACK, left0);
if(left1 == HIGH){
    ledcWrite(LEFT_CHANNEL, 255);
}else{
    ledcWrite(LEFT_CHANNEL, 0);
}
digitalWrite(PINDC_RIGHT_BACK, right0);
if(right1 == HIGH){
    ledcWrite(RIGHT_CHANNEL, 255);
}else{
    ledcWrite(RIGHT_CHANNEL, 0);
}
}

void controlDCTrack(int left, int right){
digitalWrite(PINDC_LEFT_BACK, 0);
ledcWrite(LEFT_CHANNEL, left);
digitalWrite(PINDC_RIGHT_BACK, 0);
ledcWrite(RIGHT_CHANNEL, right);
}

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {

switch(type) {
      case WStype_DISCONNECTED:
          Serial.printf("[%u] Disconnected!\n", num);
          camNo = num;
          clientConnected = false;
          break;
      case WStype_CONNECTED:
          Serial.printf("[%u] Connected!\n", num);
          clientConnected = true;
          break;
      case WStype_TEXT:
      case WStype_BIN:
      case WStype_ERROR:
      case WStype_FRAGMENT_TEXT_START:
      case WStype_FRAGMENT_BIN_START:
      case WStype_FRAGMENT:
      case WStype_FRAGMENT_FIN:
          Serial.println(type);
          break;
}
}

std::vector<String> splitString(String data, String delimiter){
    std::vector<String> ret;
    // initialize first part (string, delimiter)
    char* ptr = strtok((char*)data.c_str(), delimiter.c_str());

    while(ptr != NULL) {
      ret.push_back(String(ptr));
      // create next part
      ptr = strtok(NULL, delimiter.c_str());
    }
    return ret;
}

void processUDPData(){
int cb = UDPServer.parsePacket();

if (cb) {
      UDPServer.read(packetBuffer, RECVLENGTH);
      addrRemote = UDPServer.remoteIP();
      portRemote = UDPServer.remotePort();

      String strPackage = String((const char*)packetBuffer);
#ifdef DEBUG
      Serial.print("receive: ");
      // for (int y = 0; y < RECVLENGTH; y++){
      //   Serial.print(packetBuffer);
      //   Serial.print("\n");
      // }
      Serial.print(strPackage);
      Serial.print(" from: ");
      Serial.print(addrRemote);
      Serial.print(":");
      Serial.println(portRemote);
#endif
      if(strPackage.equals("whoami")){
          UDPServer.beginPacket(addrRemote, portRemote-1);
          String res = "ESP32-CAM";
          UDPServer.write((const uint8_t*)res.c_str(),res.length());
          UDPServer.endPacket();
          Serial.println("response");
      }else if(strPackage.equals("forward")){
      controlDC(LOW,HIGH,LOW,HIGH);
      }else if(strPackage.equals("backward")){
      controlDC(HIGH,LOW,HIGH,LOW);
      }else if(strPackage.equals("left")){
      controlDC(LOW,LOW,LOW,HIGH);
      }else if(strPackage.equals("right")){
      controlDC(LOW,HIGH,LOW,LOW);
      }else if(strPackage.equals("stop")){
      controlDC(LOW,LOW,LOW,LOW);
      }else if(strPackage.equals("camup")){
      servoUp = true;
      }else if(strPackage.equals("camdown")){
      servoDown = true;
      }else if(strPackage.equals("camstill")){
      servoUp = false;
      servoDown = false;
      }else if(strPackage.equals("ledon")){
      digitalWrite(LED_BUILT_IN, HIGH);
      }else if(strPackage.equals("ledoff")){
      digitalWrite(LED_BUILT_IN, LOW);
      }else if(strPackage.equals("lefttrack")){
      controlDCTrack(0, PWMTrackHIGH);
      }else if(strPackage.equals("righttrack")){
      controlDCTrack(PWMTrackHIGH, 0);
      }else if(strPackage.equals("fwtrack")){
      controlDCTrack(PWMTrackLOW, PWMTrackLOW);
      }

      memset(packetBuffer, 0, RECVLENGTH);
}

}

void setup(void) {

Serial.begin(115200);
Serial.print("\n");
#ifdef DEBUG
Serial.setDebugOutput(true);
#endif

pinMode(LED_BUILT_IN, OUTPUT);
digitalWrite(LED_BUILT_IN, LOW);

pinMode(PINDC_LEFT_BACK, OUTPUT);
ledcSetup(LEFT_CHANNEL, 100, 8);//channel, freq, resolution
ledcAttachPin(PINDC_LEFT_FORWARD, LEFT_CHANNEL);
pinMode(PINDC_RIGHT_BACK, OUTPUT);
ledcSetup(RIGHT_CHANNEL, 100, 8);//channel, freq, resolution
ledcAttachPin(PINDC_RIGHT_FORWARD, RIGHT_CHANNEL);

controlDC(LOW,LOW,LOW,LOW);

// 1. 50hz ==> period = 20ms (sg90 servo require 20ms pulse, duty cycle is 1->2ms: -90=>90degree)
// 2. resolution = 16, maximum value is 2^16-1=65535
// From 1 and 2 => -90=>90 degree or 0=>180degree ~ 3276=>6553
ledcSetup(SERVO_PITCH_CHANNEL, 50, 16);//channel, freq, resolution
ledcAttachPin(PIN_SERVO_PITCH, SERVO_PITCH_CHANNEL);// pin, channel
servoWrite(SERVO_PITCH_CHANNEL, posServo);

// ledcSetup(SERVO_YAW_CHANNEL, 50, 16);//channel, freq, resolution
// ledcAttachPin(PIN_SERVO_YAW, SERVO_YAW_CHANNEL);// pin, channel
// servoWrite(SERVO_YAW_CHANNEL, posServo);

cameraInitState = initCamera();

Serial.printf("camera init state %d\n", cameraInitState);

if(cameraInitState != 0){
    return;
}

//WIFI INIT
Serial.printf("Connecting to %s\n", ssid);
if (String(WiFi.SSID()) != String(ssid)) {
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
}

while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
}
Serial.println("");
Serial.print("Connected! IP address: ");
String ipAddress = WiFi.localIP().toString();;
Serial.println(ipAddress);

webSocket.begin();
webSocket.onEvent(webSocketEvent);

UDPServer.begin(UDPPort);
}

void loop(void) {
webSocket.loop();
if(clientConnected == true){
    grabImage(jpgLength, jpgBuff);
    webSocket.sendBIN(camNo, jpgBuff, jpgLength);
    // Serial.print("send img: ");
    // Serial.println(jpgLength);
}

unsigned long currentMillis = millis();
if (currentMillis - previousMillisServo >= intervalServo) {
    previousMillisServo = currentMillis;
    processUDPData();
    controlServo();
}

#ifdef DEBUG
if (Serial.available()) {
    String data = Serial.readString();
    Serial.println(data);
    std::vector<String> vposVals = splitString(data, ",");
    if(vposVals.size() != 4){
      return;
    }
    int left0 = vposVals.toInt();
    int left1 = vposVals.toInt();
    int left2 = vposVals.toInt();
    int left3 = vposVals.toInt();
    controlDC(left0, left1, left2, left3);
}
#endif
}
五、实验结果​

六、实验总结​​本次实验成功在ESP32上实现了实时物体检测与基础追踪功能,验证了边缘计算设备的视觉处理能力。尽管存在处理速度与精度的平衡挑战,同时APP软件我只能使用其他国外创客者的开发产品(需要恶补一下app开发的知识,然后将这个软件再做进一步优化).
页: [1]
查看完整版本: ESP32-AI摄像头物体检测+边框追踪