You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

伺服舵机因陷入其他函数无法追踪人脸的技术问题排查

问题分析与解决方案

首先,你的核心问题出在阻塞式的舵机转动逻辑上。看一下你的something()函数:它用了一个for循环加delay(5)来让舵机从180转到0,这段代码会让Arduino完全“卡”在这个循环里,没法去检查串口有没有新数据进来。也就是说,当舵机正在来回转的时候,哪怕Python已经发来了人脸坐标,Arduino也要等整个转动过程跑完(差不多900毫秒),才会回到loop()里去检测Serial.available(),这就导致人脸追踪的触发被严重延迟,甚至看起来完全没生效。

另外,你的track()函数里还有个小语法错误:if(Serial.available() > 0);后面多了个分号,这会让后续的代码块不管串口有没有数据都会执行,虽然这不是主因,但也得修正。

修正方案

1. 将something()改为非阻塞模式

我们用millis()来实现定时转动,避免使用delay()阻塞程序,这样loop()能一直循环,随时响应串口数据。

2. 修正track()的语法错误

去掉if语句后面多余的分号。

3. 修正Python代码里的人脸中心计算错误

你代码里计算人脸中心x坐标的时候用了h(高度),应该用w(宽度),否则中心位置会算错。


修改后的完整代码

Arduino代码(.ino)

#include <Servo.h>
Servo servoVer; //垂直舵机
Servo servoHor; //水平舵机
int x;
int y;
int prevX;
int prevY;
int currentHorPos = 180; // 当前水平舵机位置
unsigned long lastMoveTime = 0; // 记录上次转动的时间
const long moveInterval = 5; // 转动间隔,和原来的delay一致

void setup() {
  Serial.begin(9600);
  servoVer.attach(5); //将垂直舵机连接至引脚5
  servoHor.attach(6); //将水平舵机连接至引脚6
  servoVer.write(90);
  servoHor.write(90);
  currentHorPos = 90; // 初始位置设为90度
}

void Pos() {
  if (prevX != x || prevY != y) {
    int servoX = map(x, 450, 0, 0, 180);
    int servoY = map(y, 450, 0, 180, 0);
    servoX = constrain(servoX, 0, 180); // 用constrain替代min/max更简洁
    servoY = constrain(servoY, 0, 180);
    servoHor.write(servoX);
    servoVer.write(servoY);
    prevX = x;
    prevY = y; // 更新前一次的位置,避免重复转动
  }
}

void something(){
  unsigned long currentMillis = millis();
  // 每隔moveInterval毫秒,转动一次舵机
  if(currentMillis - lastMoveTime >= moveInterval){
    lastMoveTime = currentMillis;
    // 来回转动逻辑:从180转到0,再从0转回180
    if(currentHorPos <= 0){
      currentHorPos = 180;
    } else {
      currentHorPos -= 1;
    }
    servoHor.write(currentHorPos);
  }
}

void track(){
  if(Serial.available() > 0) { // 去掉后面的分号
    if (Serial.read() == 'X') {
      x = Serial.parseInt();
      if (Serial.read() == 'Y') {
        y = Serial.parseInt();
        Pos();
      }
    }
    // 清空串口缓冲区,避免残留数据干扰
    while (Serial.available() > 0) {
      Serial.read();
    }
  }
}

void loop() {
  if ( ! Serial.available() ) {
    something();
  } else {
    track();
  }
}

Python代码(.py)

import numpy as np
import serial
import time
import sys
import cv2

arduino = serial.Serial('COM5', 9600)
time.sleep(2)
print("Connection to arduino...")
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture(0)

while 1:
  ret, img = cap.read()
  img = cv2.flip(img,1)
  cv2.resizeWindow('img', 500,500)
  cv2.line(img,(500,250),(0,250),(0,255,0),1)
  cv2.line(img,(250,0),(250,500),(0,255,0),1)
  cv2.circle(img, (250, 250), 5, (255, 255, 255), -1)
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  faces = face_cascade.detectMultiScale(gray, 1.3)
  
  # 重置中心坐标,如果没有检测到人脸,不发送数据
  has_face = False
  xx, yy = 0, 0
  for (x,y,w,h) in faces:
    has_face = True
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),5)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = img[y:y+h, x:x+w]
    # 修正人脸中心计算:x方向是x到x+w,所以中心是(x + x + w)/2
    xx = int((x + (x + w))/2)
    yy = int((y + (y + h))/2)
    center = (xx,yy)
    print("Center of Rectangle is :", center)
    data = "X{0:d}Y{1:d}Z".format(xx, yy)
    print ("output = '" +data+ "'")
    arduino.write(data.encode())
  
  cv2.imshow('img',img)
  k = cv2.waitKey(30) & 0xff
  if k == 27:
    break

# 释放资源
cap.release()
cv2.destroyAllWindows()
arduino.close()

逻辑说明

  • 非阻塞的something():每次loop()运行时,只会检查是否到了转动时间,如果到了就转动1度,然后立刻回到loop()继续检测串口状态,不会阻塞程序。
  • track():修正语法错误后,只有当串口有数据时才会执行处理逻辑,避免无效操作。
  • Python端:修正了人脸中心计算的错误,并且添加了资源释放的代码,避免程序退出后串口占用。

这样修改后,当检测到人脸时,Arduino会立刻暂停来回转动,切换到人脸追踪模式;当人脸消失后,又会自动恢复来回转动的状态。

内容的提问来源于stack exchange,提问作者primitiveProgrammer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 11:52:46