伺服舵机因陷入其他函数无法追踪人脸的技术问题排查
问题分析与解决方案
首先,你的核心问题出在阻塞式的舵机转动逻辑上。看一下你的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
相关产品推荐
相关产品推荐

