Pan-tilt servo control using PID for face tracking webcam

Currently, I'm working on a face-tracking webcam using a pan-tilt servo with PID controlling.
i have written the following code for Arduino and I'm sending the detected face center from Python code to Arduino. When I run the program for some initial time it's oscillating a lot and after a short time it is following the face and when I hold the face it tracks but slightly oscillates.
The main problem is that when I stop the execution of the program it moves to one of the extreme positions either 0 or 180 slowly. When I again run it repeats.
I have checked different P, I, and D parameters but not getting the result.
Note: My frame resolution is 640X480

#include <Servo.h>
#include <cvzone.h>

Servo panServo;  // Create a servo object for the pan (horizontal) servo
Servo tiltServo; // Create a servo object for the tilt (vertical) servo
SerialData serialData(2, 3); //(numOfValsRec,digitsPerValRec)
int valsRec[2]; // array of int with size numOfValsRec 

// Define the pins for the servo motors
int panPin = 3;
int tiltPin = 10;

// Define variables for PID control
double inputX, inputY; // Current position values received from Python
double outputX, outputY; // Output values to control the servos
double setpointX, setpointY; // Desired position values

// Define the PID constants
double kp = 0.4; // Proportional gain
double ki = 0.1; // Integral gain
double kd = 0.5; // Derivative gain
double prevErrorX = 0, prevErrorY = 0; // Previous error values for calculating the derivative term
double integralX = 0, integralY = 0; // Integral terms


void setup() {
  // Attach the servos to their respective pins
  panServo.attach(panPin);
  tiltServo.attach(tiltPin);
  
  // Set the initial setpoint to the current position
   setpointX = 300;
  setpointY = 200;
  serialData.begin(9600);
  
}

void loop() {
  // Receive position information from Python
  receivePosition();

  // Perform PID calculations for X-axis
  double errorX = setpointX - inputX-10;
  integralX += errorX;
  double derivativeX = errorX - prevErrorX;
  outputX = kp * errorX + ki * integralX + kd * derivativeX;
  prevErrorX = errorX;

  // Perform PID calculations for Y-axis
  double errorY = setpointY - inputY;
  integralY += errorY;
  double derivativeY = errorY - prevErrorY;
  outputY = kp * errorY + ki * integralY + kd * derivativeY;
  prevErrorY = errorY;

  Control the servos using the PID outputs
  controlServos();

  // Delay for a short interval before the next PID update
  delay(10);
}

void receivePosition() {
  // Reading the position information from Python

  if (Serial.available() > 0) {
      serialData.Get(valsRec);    
    // Read the position values
    inputX = valsRec[0];
    inputY = valsRec[1];    
}
    
    
else if(Serial.available()<0){
    panServo.write(90);
       
    
}}
void controlServos() {
  // Mapping the PID outputs to servo angles
  int panAngle = map(outputX, -100, 100, -45, 45); 
  int tiltAngle = map(outputY, -100, 100, -45, 45); 

  // Controlling the pan and tilt servos
  panServo.write(90 + panAngle); 
  tiltServo.write(90 + tiltAngle);
}

Assuming no major program errors, use your favorite search engine with the phrase "PID tuning" to find methods to systematically determine optimal PID K values.

To check for major program errors (like the sign of the error term) put in Serial.print statements to see if the PID output values make any sense.

I'd print out the inputX, errorX, integralX, outputX, inputY, errorY, integralY, outputY and see if they are as expected.

IMO you need only a low pass filter or hysteresis, not a PID. You can implement all that in the Python code.

i tried this code for hysteresis,but it's not working.The code snippet is as follows:

pan_smoothed = 90
tilt_smoothed = 90
hysteresis_high = 20  # Adjust the high threshold value
hysteresis_low = 10  # Adjust the low threshold value

pan = cx - img.shape[1] // 2  # cx,cy centre position
        tilt = cy - img.shape[0] // 2


        if abs(pan) > hysteresis_high:
            pan_move = pan
        elif abs(pan) < hysteresis_low:
            pan_move = 0
        else:
            pan_move = pan_smoothed

        
        if abs(tilt) > hysteresis_high:
            tilt_move = tilt
        elif abs(tilt) < hysteresis_low:
            tilt_move = 0
        else:
            tilt_move = tilt_smoothed


        alpha = 0.3
        pan_smoothed = int(alpha * pan_move + (1 - alpha) * pan_smoothed)
        tilt_smoothed = int(alpha * tilt_move + (1 - alpha) * tilt_smoothed)

        ser.write('{} {}\n'.format(int(pan_smoothed), int(tilt_smoothed)).encode())
       

Remove it, it does not implement anything meaningful and PID does not work together with hysteresis :frowning:
Smoothing looks good as long as e.g. pan_move is the actual input, not pan_smoothed.

1 Like

would you please give a hint to Arduino code considering hysteresis in Python code?

if (abs(new-old) < hysteresis) new = old;

1 Like

Thanks -- I like the idea of using the new = old trick, because I think I'd naively try to ignore the new value and add some complication, but this trick just clamps the new observation to the old value until it changes enough to matter.

I think you should tune the PID loop .
Turn I &D off and adjust to to give a stable result ( which might might have an error in position) , then add integral ( you might then need a little less P) until you achieve set point .
Derivative is a bit more tricky … add a bit if it overshoots setpoint when moving from a distance from that setpoint ( often derivative not needed)

Hysteris is an enemy of PID control and should be avoided - it’s ok for on /off control but not PID ( think about what happens if it overshoots the setpoint slightly , it cannot get back until the integral term winds up and forces across the hysteresis “gap”, then Intrgal windup is too big and you are more prone to oscillation.
Time lag is another big enemy , which results in a tune to give a slow response

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.