Real-Time Facial Tracking System Based on UNIHIKER and micro:bit
2025-08-19 | By DFRobot
License: Attribution Arduino ESP32 micro:bit
Introduction
In the era of rapid technological advancement, artificial intelligence (AI) technology is becoming increasingly widespread across various fields. Among them, the field of computer vision, particularly real-time facial tracking systems, has attracted significant attention. However, traditional facial tracking systems are limited by hardware resources and algorithm accuracy, failing to meet the high demands of facial tracking. Therefore, this article will introduce a real-time facial tracking system based on UNIHIKER, micro:bit expansion board, and a camera. This system combines hardware control with software algorithms to achieve precise facial tracking.



HARDWARE LIST
1 micro:Driver - Driver Expansion Board for micro:bit / UNIHIKER
1 UNIHIKER - IoT Python Single Board Computer with Touchscreen
Project Design
- Camera Gimbal Control: Utilizing the combination of UNIHIKER and micro:bit expansion board, the camera gimbal is driven by the servo pins on the expansion board, enabling precise control.
- Facial Recognition and Tracking: Utilizing the Mediapipe library for facial recognition, the system obtains the central coordinates of faces and optimizes the recognition results using filtering algorithms to reduce jitter.
- PID Algorithm for Servo Motor Control: The PID algorithm is employed to control servo motor movements, ensuring real-time facial tracking with stability and smoothness.
Specific Steps
- Preparation of Servo Motor Control Files: Obtain the file named microbit_motor.py from the Pinpong library and upload it to UNIHIKER, ensuring it is in the same folder as the main file.

CODE
# -*- coding: utf-8 -*-
import time
from pinpong.board import Board
from microbit_motor import Microbit_Motor
Board("microbit").begin()
#Board("microbit","COM36").begin()
#Board("microbit","/dev/ttyACM0").begin()
#Board("microbit","/dev/cu.usbmodem14101").begin()
motorbit = Microbit_Motor()
while True:
   motorbit.servo(motorbit.S2, 0)
   time.sleep(1)
   motorbit.servo(motorbit.S2, 90)
   time.sleep(1)
   motorbit.servo(motorbit.S2, 180)
   time.sleep(1)
   motorbit.servo(motorbit.S2, 90)
   time.sleep(1)
- Installation of cvzone Library: Use Jupyter notebook on the UNIHIKER web interface or execute the command pip install cvzone in the terminal to install the cvzone library for full-screen camera operations.
CODE
import cvzone.FaceDetectionModule as face
import cv2
import numpy as np
import time
def main():
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cv2.namedWindow('camera',cv2.WND_PROP_FULLSCREEN)    
    cv2.setWindowProperty('camera', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)   
    detector = face.FaceDetector()
# For a 640x480 image center target is 320 and 240
while True:
        success, img = cap.read()
        img, bboxs = detector.findFaces(img)
if bboxs:
            x, y, w, h = bboxs[0]["bbox"]
            cx, cy = bboxs[0]["center"]
            xVal=cx
            yVal=cy
            cv2.putText(img, f'x:{xVal} , y:{yVal} ', (x, y - 100), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
        output_image = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
        cv2.imshow("camera", output_image)
        cv2.waitKey(1)
if __name__ == "__main__":
    main()
- Facial Center Data Filtering: Apply the recursive averaging filtering method to the obtained facial center coordinates to reduce data jitter.
 
Method:
Treat the continuous acquisition of N samples as a queue, following the first-in-first-out principle.
The queue length is fixed at N, with each new data entering the queue's tail while the oldest data is removed from the queue's head.
Perform arithmetic averaging on the N data in the queue to obtain new filtered results.
Choose an appropriate N value based on different application scenarios, such as N=12 for flow rate, N=4 for pressure, N=4~12 for liquid level, and N=1~4 for temperature.
 
Advantages:
Effective suppression of periodic interference, maintaining smoothness.
Suitable for systems with high-frequency oscillations, exhibiting strong stability.
 
Disadvantages:
Lower sensitivity, potentially resulting in poor suppression of occasional pulse interference.
Difficult to eliminate sample value bias caused by pulse interference.
Inappropriate for scenarios with severe pulse interference.
Larger N values may lead to RAM resource waste.
CODE
 
import cvzone.FaceDetectionModule as face
import cv2
import numpy as np
import time
def main():
    cap = cv2.VideoCapture(0)
#cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
#cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
#cv2.namedWindow('camera',cv2.WND_PROP_FULLSCREEN)    
#cv2.setWindowProperty('camera', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)   
    detector = face.FaceDetector()
    s=[0,0,0,0,0,0,0,0,0,0,0,0]
while True:
        PTime = time.time()
        success, img = cap.read()
        img, bboxs = detector.findFaces(img)
if bboxs:
            x, y, w, h = bboxs[0]["bbox"]
            cx, cy = bboxs[0]["center"]
            xVal=cx
            yVal=cy
            cv2.putText(img, f'x:{xVal} , y:{yVal} ', (x, y - 100), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
#output_image = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
            s.pop(0)
            s.append(cx)
            mean=int(np.mean(s))
            cv2.putText(img, f'x:{mean} , y:{yVal} ', (x, y - 50), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
        fps = 1 / (time.time() - PTime)
        cv2.putText(img, f'FPS: {int(fps)}', (20,50), cv2.FONT_HERSHEY_PLAIN,
3, (255, 255, 0), 3)
        cv2.imshow("camera", img)
        cv2.waitKey(1)
if __name__ == "__main__":
    main()
- PID Servo Motor Control: Implementing the PID algorithm to control horizontal servo motors for horizontal facial tracking, ensuring stable and responsive movements.
CODE
import cvzone.FaceDetectionModule as face
import cv2
import numpy as np
import time
from pinpong.board import Board
from microbit_motor import Microbit_Motor 
Board("microbit").begin()
motorbit = Microbit_Motor()
targetVal=0
pError=0
pTime=0
pidVals=[0.03,0,0.01]
I=0
jd_x=90
def pid(cVal):
global I,pidVals,pTime,pError,targetVal
# Current Value - Target Value
        t = time.time() - pTime
        error = cVal - targetVal
        P = pidVals[0] * error
        I = I + (pidVals[1] * error * t)
        D = (pidVals[2] * (error - pError)) / t
        result = P + I + D
        pError = error
        ptime = time.time()
return result
def main():
global ptime,jd_x
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cv2.namedWindow('camera',cv2.WND_PROP_FULLSCREEN)    
    cv2.setWindowProperty('camera', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)   
    detector = face.FaceDetector()
    s=[0,0,0,0,0,0]
    ptime = time.time()
    motorbit.servo(motorbit.S2,jd_x)
    pre_jd_x=0
while True:
        PTime = time.time()
        success, img = cap.read()
        img, bboxs = detector.findFaces(img)
if bboxs:
            x, y, w, h = bboxs[0]["bbox"]
            cx, cy = bboxs[0]["center"]
            xVal=cx
            yVal=cy
            cv2.putText(img, f'x:{xVal} , y:{yVal} ', (x, y - 100), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
            s.pop(0)
            s.append(cx)
            mean=int(np.mean(s))
            jd_x=jd_x+int(pid(160-mean))
if jd_x<1:
                jd_x=1
if jd_x>179:
                jd_x=179
            print(jd_x)
if pre_jd_x !=jd_x:
              pre_jd_x =jd_x
              motorbit.servo(motorbit.S2,jd_x)
            cv2.putText(img, f'x:{mean} , y:{yVal} ', (x, y - 50), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
        fps = 1 / (time.time() - PTime)
        cv2.putText(img, f'FPS: {int(fps)}', (20,50), cv2.FONT_HERSHEY_PLAIN,
3, (255, 255, 0), 3)
        output_image = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)                    
        cv2.imshow("camera", output_image)
        cv2.waitKey(1)
if __name__ == "__main__":
    main()
- Horizontal and Vertical Servo Motor Control: System expansion can further enable tracking in both horizontal and vertical directions, enhancing flexibility and applicability.
Control the horizontal using the PID class
CODE
import cvzone.FaceDetectionModule as face
import cv2
import numpy as np
import time
from pinpong.board import Board
from microbit_motor import Microbit_Motor 
Board("microbit").begin()
motorbit = Microbit_Motor()
targetVal=0
pError=0
pTime=0
pidVals=[0.03,0,0.01]
I=0
jd_x=90
class PID:
def __init__(self, pidVals, targetVal,  limit=None):
        self.pidVals = pidVals
        self.targetVal = targetVal
        self.pError = 0
        self.limit = limit
        self.I = 0
        self.pTime = 0
def update(self,cVal):
# Current Value - Target Value
        t = time.time() - self.pTime
        error = cVal - self.targetVal
        P = self.pidVals[0] * error
        self.I = self.I + (self.pidVals[1] * error * t)
        D = (self.pidVals[2] * (error - self.pError)) / t
        result = P + self.I + D
if self.limit is not None:
            result = float(np.clip(result, self.limit[0], self.limit[1]))
        self.pError = error
        self.ptime = time.time()
return result
def main():
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cv2.namedWindow('camera',cv2.WND_PROP_FULLSCREEN)    
    cv2.setWindowProperty('camera', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)   
    detector = face.FaceDetector()
    s_x=[0,0,0,0,0,0]
    jd_x=90
    pre_jd_x=0
    motorbit.servo(motorbit.S2,jd_x)
    xPID = PID([0.03, 0.000000000001, 0.01], 320 // 2,  limit=[-90, 90])
while True:
        PTime = time.time()
        success, img = cap.read()
        img, bboxs = detector.findFaces(img)
if bboxs:
            x, y, w, h = bboxs[0]["bbox"]
            cx, cy = bboxs[0]["center"]
            xVal=cx
            yVal=cy
            cv2.putText(img, f'x:{xVal} , y:{yVal} ', (x, y - 100), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
            s_x.pop(0)
            s_x.append(cx)
            mean_x=int(np.mean(s_x))
            xVal= int(xPID.update(mean_x))
            jd_x=jd_x-xVal
            print(jd_x)
if pre_jd_x !=jd_x:
              pre_jd_x =jd_x
              motorbit.servo(motorbit.S2,jd_x)
            cv2.putText(img, f'x:{mean_x} , y:{yVal} ', (x, y - 50), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
        fps = 1 / (time.time() - PTime)
        cv2.putText(img, f'FPS: {int(fps)}', (20,50), cv2.FONT_HERSHEY_PLAIN,
3, (255, 255, 0), 3)
        output_image = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)                    
        cv2.imshow("camera", output_image)
        cv2.waitKey(1)
if __name__ == "__main__":
    main() - Control the horizontal and vertical servos
CODE
import cvzone.FaceDetectionModule as face
import cv2
import numpy as np
import time
from pinpong.board import Board
from microbit_motor import Microbit_Motor 
Board("microbit").begin()
motorbit = Microbit_Motor()
targetVal=0
pError=0
pTime=0
pidVals=[0.03,0,0.01]
I=0
jd_x=90
class PID:
def __init__(self, pidVals, targetVal,  limit=None):
        self.pidVals = pidVals
        self.targetVal = targetVal
        self.pError = 0
        self.limit = limit
        self.I = 0
        self.pTime = 0
def update(self,cVal):
# Current Value - Target Value
        t = time.time() - self.pTime
        error = cVal - self.targetVal
        P = self.pidVals[0] * error
        self.I = self.I + (self.pidVals[1] * error * t)
        D = (self.pidVals[2] * (error - self.pError)) / t
        result = P + I + D
if self.limit is not None:
            result = float(np.clip(result, self.limit[0], self.limit[1]))
        self.pError = error
        self.ptime = time.time()
return result
def main():
    cap = cv2.VideoCapture(0)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
    cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
    cv2.namedWindow('camera',cv2.WND_PROP_FULLSCREEN)    
    cv2.setWindowProperty('camera', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)  
    detector = face.FaceDetector()
    s_x=[0,0,0,0,0,0]
    s_y=[0,0,0,0,0,0]
    jd_x=90
    jd_y=135
    pre_jd_y=0
    motorbit.servo(motorbit.S1,jd_y)
    xPID = PID([0.03, 0.000000000001, 0.01], 320 // 2,  limit=[-90, 90])
    yPID = PID([0.03, 0.000000000001, 0.01], 240 // 2,  limit=[-45, 45])
while True:
        PTime = time.time()
        success, img = cap.read()
        img, bboxs = detector.findFaces(img)
if bboxs:
            x, y, w, h = bboxs[0]["bbox"]
            cx, cy = bboxs[0]["center"]
            xVal=cx
            yVal=cy
            cv2.putText(img, f'x:{xVal} , y:{yVal} ', (x, y - 100), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
            s_x.pop(0)
            s_y.pop(0)
            s_x.append(cx)
            s_y.append(cy)
            mean_x=int(np.mean(s_x))
            mean_y=int(np.mean(s_y))
            xVal= int(xPID.update(mean_x))
            yVal= int(yPID.update(mean_y))
            jd_x=jd_x-xVal
            jd_y=jd_y-yVal
            print(jd_x,jd_y)
if pre_jd_x !=jd_x:
              pre_jd_x =jd_x
              motorbit.servo(motorbit.S2,jd_x)
if pre_jd_y !=jd_y:
              pre_jd_y =jd_y
              motorbit.servo(motorbit.S1,jd_y)
            cv2.putText(img, f'x:{mean_x} , y:{mean_y} ', (x, y - 50), cv2.FONT_HERSHEY_PLAIN, 3,
                        (255, 0, 0), 3)
        fps = 1 / (time.time() - PTime)
        cv2.putText(img, f'FPS: {int(fps)}', (20,50), cv2.FONT_HERSHEY_PLAIN,
3, (255, 255, 0), 3)
        output_image = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)                    
        cv2.imshow("camera", output_image)
        cv2.waitKey(1)
if __name__ == "__main__":
    main()Conclusion
Through the introduction and guide in this article, we can understand how to build a real-time facial tracking system using UNIHIKER, micro:bit expansion board, and a camera, and grasp relevant control methods and algorithm principles. This system can be applied not only in laboratories and educational settings but also provides valuable references and insights for a wider range of artificial intelligence applications.
Demo



 
                 
                 
                 
 
 
 
 Settings
        Settings
     Fast Delivery
                                    Fast Delivery
                                 Free Shipping
                                    Free Shipping
                                 Incoterms
                                    Incoterms
                                 Payment Types
                                    Payment Types
                                





 Marketplace Product
                                    Marketplace Product
                                 
 
         
         
         
         
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                     
                                 
                                 
                                 
                         
                                 
                                 
                                 
                                 
                                 
                                 
                                 France
France