NO.48------ C#实现卡尔曼滤波

本文介绍在Unity项目中应用卡尔曼滤波技术消除头部跟踪时的抖动问题,通过处理Dlib实时获取的68个特征点,实现更平滑的头部运动追踪效果。

通俗的讲,卡尔曼滤波就是综合上一步骤对当前步骤的预测值,结合当前步骤的测量值,来估计当前的状态。

       最近在做一个Unity项目时,用到了卡尔曼滤波消除抖动,主要是消除头部抖动,从Dlib实时获得68个特征值的points,由points.x points.y  points.z  获得头部的旋转四元数作为测量值,初始值可以任意定义,因为几次迭代后预测值就逼近真实值了。因为Unity用c#编写,c#本身不存在矩阵库,对矩阵运算比较吃力,需要自己导入矩阵库,方法很简单,首先定义矩阵库的命名空间,然后使用该命名空间即可。

矩阵库:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Slash.Lastwork
{
    public class Matrix
    {
        double[,] A;
        //m行n列
        int m, n;
        string name;
        public Matrix(int am, int an)
        {
            m = am;
            n = an;
            A = new double[m, n];
            name = "Result";
        }
        public Matrix(int am, int an, string aName)
        {
            m = am;
            n = an;
            A = new double[m, n];
            name = aName;
        }

        public int getM
        {
            get { return m; }
        }
        public int getN
        {
            get { return n; }
        }
        public double[,] Detail
        {
            get { return A; }
            set { A = value; }
        }
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }
    public class MatrixOperator
    {
        MatrixOperator()
        { }
        /// <summary>
        /// 矩阵加法
        /// </summary>
        /// <param name="Ma"></param>
        /// <param name="Mb"></param>
        /// <returns></returns>
        public static Matrix MatrixAdd(Matrix Ma, Matrix Mb)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            int m2 = Mb.getM;
            int n2 = Mb.getN;

            if ((m != m2) || (n != n2))
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }

            Matrix Mc = new Matrix(m, n);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;
            double[,] b = Mb.Detail;

            int i, j;
            for (i = 0; i < m; i++)
                for (j = 0; j < n; j++)
                    c[i, j] = a[i, j] + b[i, j];

            return Mc;
        }
        /// <summary>
        /// 矩阵减法
        /// </summary>
        /// <param name="Ma"></param>
        /// <param name="Mb"></param>
        /// <returns></returns>
        public static Matrix MatrixSub(Matrix Ma, Matrix Mb)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            int m2 = Mb.getM;
            int n2 = Mb.getN;

            if ((m != m2) || (n != n2))
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }

            Matrix Mc = new Matrix(m, n);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;
            double[,] b = Mb.Detail;

            int i, j;
            for (i = 0; i < m; i++)
                for (j = 0; j < n; j++)
                    c[i, j] = a[i, j] - b[i, j];

            return Mc;
        }
        /// <summary>
        /// 矩阵打印
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static string MatrixPrint(Matrix Ma)
        {
            string s;
            s = Ma.Name + ":\n";

            int m = Ma.getM;
            int n = Ma.getN;

            double[,] a = Ma.Detail;

            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    s += a[i, j].ToString("0.0000") + "\t";
                }
                s += "\n";
            }
            return s;
        }
        /// <summary>
        /// 矩阵乘法
        /// </summary>
        /// <param name="Ma"></param>
        /// <param name="Mb"></param>
        /// <returns></returns>
        public static Matrix MatrixMulti(Matrix Ma, Matrix Mb)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            int m2 = Mb.getM;
            int n2 = Mb.getN;

            if (n != m2)
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }

            Matrix Mc = new Matrix(m, n2);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;
            double[,] b = Mb.Detail;

            int i, j, k;
            for (i = 0; i < m; i++)
                for (j = 0; j < n2; j++)
                {
                    c[i, j] = 0;
                    for (k = 0; k < n; k++)
                        c[i, j] += a[i, k] * b[k, j];
                }

            return Mc;
        }
        /// <summary>
        /// 矩阵数乘
        /// </summary>
        /// <param name="k"></param>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static Matrix MatrixSimpleMulti(double k, Matrix Ma)
        {
            int n = Ma.getN;
            int m = Ma.getM;
            Matrix Mc = new Matrix(m, n);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;

            int i, j;
            for (i = 0; i < m; i++)
                for (j = 0; j < n; j++)
                    c[i, j] = a[i, j] * k;

            return Mc;
        }
        /// <summary>
        /// 矩阵转置
        /// </summary>
        /// <param name="Ma"></param>
        /// <param name="Mb"></param>
        /// <returns></returns>
        public static Matrix MatrixTrans(Matrix Ma)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            Matrix Mc = new Matrix(n, m);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;

            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                    c[i, j] = a[j, i];

            return Mc;
        }
        /// <summary>
        /// 矩阵求逆(高斯法)
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static Matrix MatrixInv(Matrix Ma)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            if (m != n)
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }
            Matrix Mc = new Matrix(m, n);
            double[,] a0 = Ma.Detail;
            double[,] a = (double[,])a0.Clone();
            double[,] b = Mc.Detail;

            int i, j, row, k;
            double max, temp;

            //单位矩阵
            for (i = 0; i < n; i++)
            {
                b[i, i] = 1;
            }
            for (k = 0; k < n; k++)
            {
                max = 0; row = k;
                //找最大元,其所在行为row
                for (i = k; i < n; i++)
                {
                    temp = Math.Abs(a[i, k]);
                    if (max < temp)
                    {
                        max = temp;
                        row = i;
                    }

                }
                if (max == 0)
                {
                    Exception myException = new Exception("没有逆矩阵");
                    throw myException;
                }
                //交换k与row行
                if (row != k)
                {
                    for (j = 0; j < n; j++)
                    {
                        temp = a[row, j];
                        a[row, j] = a[k, j];
                        a[k, j] = temp;

                        temp = b[row, j];
                        b[row, j] = b[k, j];
                        b[k, j] = temp;
                    }

                }

                //首元化为1
                for (j = k + 1; j < n; j++) a[k, j] /= a[k, k];
                for (j = 0; j < n; j++) b[k, j] /= a[k, k];

                a[k, k] = 1;

                //k列化为0
                //对a
                for (j = k + 1; j < n; j++)
                {
                    for (i = 0; i < k; i++) a[i, j] -= a[i, k] * a[k, j];
                    for (i = k + 1; i < n; i++) a[i, j] -= a[i, k] * a[k, j];
                }
                //对b
                for (j = 0; j < n; j++)
                {
                    for (i = 0; i < k; i++) b[i, j] -= a[i, k] * b[k, j];
                    for (i = k + 1; i < n; i++) b[i, j] -= a[i, k] * b[k, j];
                }
                for (i = 0; i < n; i++) a[i, k] = 0;
                a[k, k] = 1;
            }

            return Mc;
        }
        /// <summary>
        /// 矩阵求逆(伴随矩阵法)
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static Matrix MatrixInvByCom(Matrix Ma)
        {
            double d = MatrixOperator.MatrixDet(Ma);
            if (d == 0)
            {
                Exception myException = new Exception("没有逆矩阵");
                throw myException;
            }
            Matrix Ax = MatrixOperator.MatrixCom(Ma);
            Matrix An = MatrixOperator.MatrixSimpleMulti((1.0 / d), Ax);
            return An;
        }
        /// <summary>
        /// 对应行列式的代数余子式矩阵
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static Matrix MatrixSpa(Matrix Ma, int ai, int aj)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            if (m != n)
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }
            int n2 = n - 1;
            Matrix Mc = new Matrix(n2, n2);
            double[,] a = Ma.Detail;
            double[,] b = Mc.Detail;

            //左上
            for (int i = 0; i < ai; i++)
                for (int j = 0; j < aj; j++)
                {
                    b[i, j] = a[i, j];
                }
            //右下
            for (int i = ai; i < n2; i++)
                for (int j = aj; j < n2; j++)
                {
                    b[i, j] = a[i + 1, j + 1];
                }
            //右上
            for (int i = 0; i < ai; i++)
                for (int j = aj; j < n2; j++)
                {
                    b[i, j] = a[i, j + 1];
                }
            //左下
            for (int i = ai; i < n2; i++)
                for (int j = 0; j < aj; j++)
                {
                    b[i, j] = a[i + 1, j];
                }
            //符号位
            if ((ai + aj) % 2 != 0)
            {
                for (int i = 0; i < n2; i++)
                    b[i, 0] = -b[i, 0];

            }
            return Mc;
        }
        /// <summary>
        /// 矩阵的行列式
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static double MatrixDet(Matrix Ma)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            if (m != n)
            {
                Exception myException = new Exception("数组维数不匹配");
                throw myException;
            }
            double[,] a = Ma.Detail;
            if (n == 1) return a[0, 0];

            double D = 0;
            for (int i = 0; i < n; i++)
            {
                D += a[1, i] * MatrixDet(MatrixSpa(Ma, 1, i));
            }
            return D;
        }
        /// <summary>
        /// 矩阵的伴随矩阵
        /// </summary>
        /// <param name="Ma"></param>
        /// <returns></returns>
        public static Matrix MatrixCom(Matrix Ma)
        {
            int m = Ma.getM;
            int n = Ma.getN;
            Matrix Mc = new Matrix(m, n);
            double[,] c = Mc.Detail;
            double[,] a = Ma.Detail;

            for (int i = 0; i < m; i++)
                for (int j = 0; j < n; j++)
                    c[i, j] = MatrixDet(MatrixSpa(Ma, j, i));

            return Mc;
        }
    }


}

迭代过程:

using UnityEngine;
using System.Collections.Generic;
using System;
using System.IO;
using Slash.Lastwork;

#if UNITY_5_3 || UNITY_5_3_OR_NEWER
using UnityEngine.SceneManagement;
#endif
using OpenCVForUnity;
using DlibFaceLandmarkDetector;
using DlibFaceLandmarkDetectorSample;
using Moments;

namespace Slash.Facemoji
{
    /// <summary>
    /// FaceTracking, Use Dlib to detect face landmark and use Live2D model to track faces
    /// </summary>
    [AddComponentMenu("Facemoji/FaceTracking")]
    [RequireComponent (typeof(WebCamTextureToMatHelper))]
    public class FaceTracking : MonoBehaviour
    {
        ///矩阵的运算
        //F_K 状态转移矩阵
        Matrix stateConvert = new Matrix(6, 6, "stateConvert");
        //P_K 后验估计误差协方差矩阵,度量估计值的精确程度
        Matrix errorCovPost = new Matrix(6, 6, "errorCovPsot");
        //H_K 观测矩阵
        Matrix measureMatrix = new Matrix(6, 3, "measureMatrix");
        //Q_K 过程噪声
        Matrix processNoiseConvert = new Matrix(6, 6, "processNoiseConvert");
        //R_K 观测噪声
        Matrix measureNoiseConvert = new Matrix(3, 3, "measureNoiseConvert");
        //X_0 初始状态
        Matrix originState = new Matrix(3, 1, "originState");
        /// <summary>
        /// All Live2D Texture
        /// </summary>
        private enum WhichTexture
        {
            noir_santa = 1,
            uni = 2,
        };

        /// <summary>
        /// The selected Live2D Texture now.(Default shizuku)
        /// </summary>
        private WhichTexture selectedTexture = WhichTexture.uni;

        /// <summary>
        /// Start Button
        /// </summary>
        public static GameObject startBtn;

        /// <summary>
        /// Finish Button
        /// </summary>
        public static GameObject finishBtn;

        /// <summary>
        /// live2DModel.transform.localScale
        /// </summary>
        public float modelScale = 0.9f;

        /// <summary>
        /// The web cam texture to mat helper.
        /// </summary>
        WebCamTextureToMatHelper webCamTextureToMatHelper;

        /// <summary>
        /// The face landmark detector.
        /// </summary>
        FaceLandmarkDetector faceLandmarkDetector;

        /// <summary>
        /// The live2DModel.
        /// </summary>
        public Live2DModel live2DModel;

        /// <summary>
        /// The frontal face parameter.
        /// </summary>
        FrontalFaceParam frontalFaceParam;

        /// <summary>
        /// The shape_predictor_68_face_landmarks_dat_filepath.
        /// </summary>
        private string shape_predictor_68_face_landmarks_dat_filepath;

        /// <summary>
        /// Model file path.
        /// </summary>
        private string moc_filepath;
        private string physics_filepath;
        private string pose_filepath;
        private string[] texture_filepath = new string[6];

        /// <summary>
        /// 相关矩阵的计算.
        /// </summary>
        //F_K 状态转移矩阵
        Matrix F_K = new Matrix(6, 6, "F_K");
        //P_K 后验估计误差协方差矩阵,度量估计值的精确程度
        Matrix P_K = new Matrix(6, 6, "errorCovPsot");
        //P_k+1
        Matrix P_front = new Matrix(6, 6, "errorCovPsot");
        //H_K 观测矩阵
        Matrix H_K = new Matrix(3, 6, "H_K");
        //Q_K 过程噪声
        Matrix Q_K = new Matrix(6, 6, "Q_K");
        //R_K 观测噪声
        Matrix R_K = new Matrix(3, 3, "R_K");
        //Z_K 测量值
        Matrix Z_K = new Matrix(3, 1, "Z_K");
        //X_K 
        Matrix X_K = new Matrix(6, 1, "state");
        //X_K+1 
        Matrix X_front = new Matrix(6, 1, "state");
        //eye
        Matrix eye = new Matrix(6, 6, "state");


        // Use this for initialization
        void Start()
        {
            startBtn = GameObject.Find("StartButton");
            finishBtn = GameObject.Find("FinishButton");
            startBtn.SetActive(true);
            finishBtn.SetActive(false);

            webCamTextureToMatHelper = gameObject.GetComponent<WebCamTextureToMatHelper>();

#if UNITY_WEBGL && !UNITY_EDITOR
            webCamTextureToMatHelper.flipHorizontal = true;
            StartCoroutine(getFilePathCoroutine());
#else
            // FaceLandmark model filepath
            shape_predictor_68_face_landmarks_dat_filepath = DlibFaceLandmarkDetector.Utils.getFilePath("shape_predictor_68_face_landmarks.dat");

            // Load Texture filepath
            LoadTexture();
            
            Run();
#endif
        }

        private void LoadTexture()
        {
            // Load model filepath
            switch (selectedTexture)
            {
                case WhichTexture.noir_santa:
                    {
                        moc_filepath = OpenCVForUnity.Utils.getFilePath("noir_santa/noir_santa.moc.bytes");
                        physics_filepath = OpenCVForUnity.Utils.getFilePath("noir_santa/noir_santa.physics.json");
                        pose_filepath = OpenCVForUnity.Utils.getFilePath("noir_santa/noir_santa.pose.json");
                        for (int i = 0; i < texture_filepath.Length; i++)
                        {
                            texture_filepath[i] = OpenCVForUnity.Utils.getFilePath("noir_santa/noir_santa.1024/texture_0" + i + ".png");
                        }
                        break;
                    }
                case WhichTexture.uni:
                    {
                        moc_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.moc.bytes");
                        physics_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.physics.json");
                        pose_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.pose.json");
                        for (int i = 0; i < texture_filepath.Length; i++)
                        {
                            texture_filepath[i] = OpenCVForUnity.Utils.getFilePath("uni/uni.1024/texture_0" + i + ".png");
                        }
                        break;
                    }
                default:
                    {
                        moc_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.moc.bytes");
                        physics_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.physics.json");
                        pose_filepath = OpenCVForUnity.Utils.getFilePath("uni/uni.pose.json");
                        for (int i = 0; i < texture_filepath.Length; i++)
                        {
                            texture_filepath[i] = OpenCVForUnity.Utils.getFilePath("uni/uni.1024/texture_0" + i + ".png");
                        }
                        break;
                    }
            }

            live2DModel.textureFiles = new Texture2D[texture_filepath.Length];
            for (int i = 0; i < texture_filepath.Length; i++)
            {
                if (string.IsNullOrEmpty(texture_filepath[i]))
                    continue;

                Texture2D tex = new Texture2D(2, 2);
                tex.LoadImage(File.ReadAllBytes(texture_filepath[i]));
                live2DModel.textureFiles[i] = tex;
            }
            if (!string.IsNullOrEmpty(moc_filepath))
                live2DModel.setMocFileFromBytes(File.ReadAllBytes(moc_filepath));
            if (!string.IsNullOrEmpty(physics_filepath))
                live2DModel.setPhysicsFileFromBytes(File.ReadAllBytes(physics_filepath));
            if (!string.IsNullOrEmpty(pose_filepath))
                live2DModel.setPoseFileFromBytes(File.ReadAllBytes(pose_filepath));

        }

        private void Run ()
        {
            Debug.Log ("Run");

            faceLandmarkDetector = new FaceLandmarkDetector (shape_predictor_68_face_landmarks_dat_filepath);
            frontalFaceParam = new FrontalFaceParam ();

            // Use the front camera to Init
            webCamTextureToMatHelper.Init(null, webCamTextureToMatHelper.requestWidth, webCamTextureToMatHelper.requestHeight, !webCamTextureToMatHelper.requestIsFrontFacing);

            //// Default initialization
            //webCamTextureToMatHelper.Init();
            //webCamTextureToMatHelper.Init(null, 320, 240, false);
        }

        /// <summary>
        /// Raises the web cam texture to mat helper inited event.
        /// </summary>
        public void OnWebCamTextureToMatHelperInited ()
        {
            Debug.Log ("OnWebCamTextureToMatHelperInited");

            Mat webCamTextureMat = webCamTextureToMatHelper.GetMat ();
            
            gameObject.transform.localScale = new Vector3 (webCamTextureMat.cols (), webCamTextureMat.rows (), 1);

            Debug.Log ("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation);

            float width = gameObject.transform.localScale.x;
            float height = gameObject.transform.localScale.y;

            float widthScale = (float)Screen.width / width;
            float heightScale = (float)Screen.height / height;
            if (widthScale < heightScale) {
                Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2;
            } else {
                Camera.main.orthographicSize = height / 2;
            }

            if (live2DModel != null) {
                // Set live2DModel localScale
                live2DModel.transform.localScale = new Vector3 (Camera.main.orthographicSize * modelScale, Camera.main.orthographicSize * modelScale, 1);
            }
            
        }

        /// <summary>
        /// Raises the web cam texture to mat helper disposed event.
        /// </summary>
        public void OnWebCamTextureToMatHelperDisposed ()
        {
            Debug.Log ("OnWebCamTextureToMatHelperDisposed");

        }

        /// <summary>
        /// Raises the web cam texture to mat helper error occurred event.
        /// </summary>
        public void OnWebCamTextureToMatHelperErrorOccurred ()
        {
            Debug.Log("OnWebCamTextureToMatHelperErrorOccurred");
        }

        // Update is called once per frame
        void Update ()
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
                SceneManager.LoadScene("FacemojiStart");
#else
                Application.LoadLevel("FacemojiStart");
#endif
            }

            if (webCamTextureToMatHelper.IsPlaying () && webCamTextureToMatHelper.DidUpdateThisFrame ()) {

                Mat rgbaMat = webCamTextureToMatHelper.GetMat();

                OpenCVForUnityUtils.SetImage(faceLandmarkDetector, rgbaMat);
                //获得特征匹配矩形

                List<UnityEngine.Rect> detectResult = faceLandmarkDetector.Detect ();

                foreach (var rect in detectResult) {
                    //获得68个采样点
                    List<Vector2> points = faceLandmarkDetector.DetectLandmark (rect);

                    if (points.Count > 0) {
                        // angle
                        Vector3 angles = frontalFaceParam.getFrontalFaceAngle(points);
                        //初始状态
                        double[,] originState = X_K.Detail;
                        originState[0, 0] = angles.x;
                        originState[1, 0] = angles.y;
                        originState[2, 0] = angles.z;
                        originState[3, 0] = 0;
                        originState[4, 0] = 0;
                        originState[5, 0] = 0;
                        //eye
                        double[,] unit = eye.Detail;
                        unit[0, 0] = 1;
                        unit[0, 1] = 0;
                        unit[0, 2] = 0;
                        unit[0, 3] = 0;
                        unit[0, 4] = 0;
                        unit[0, 5] = 0;
                        unit[1, 0] = 0;
                        unit[1, 1] = 1;
                        unit[1, 2] = 0;
                        unit[1, 3] = 0;
                        unit[1, 4] = 0;
                        unit[1, 5] = 0;
                        unit[2, 0] = 0;
                        unit[2, 1] = 0;
                        unit[2, 2] = 1;
                        unit[2, 3] = 0;
                        unit[2, 4] = 0;
                        unit[2, 5] = 0;
                        unit[3, 0] = 0;
                        unit[3, 1] = 0;
                        unit[3, 2] = 0;
                        unit[3, 3] = 1;
                        unit[3, 4] = 0;
                        unit[3, 5] = 0;
                        unit[4, 0] = 0;
                        unit[4, 1] = 0;
                        unit[4, 2] = 0;
                        unit[4, 3] = 0;
                        unit[4, 4] = 1;
                        unit[4, 5] = 0;
                        unit[5, 0] = 0;
                        unit[5, 1] = 0;
                        unit[5, 2] = 0;
                        unit[5, 3] = 0;
                        unit[5, 4] = 0;
                        unit[5, 5] = 1;


                        //F_K(F_k)
                        double[,] stateC = F_K.Detail;
                        stateC[0, 0] = 1;
                        stateC[0, 1] = 0;
                        stateC[0, 2] = 0;
                        stateC[0, 3] = 1;
                        stateC[0, 4] = 0;
                        stateC[0, 5] = 0;
                        stateC[1, 0] = 0;
                        stateC[1, 1] = 1;
                        stateC[1, 2] = 0;
                        stateC[1, 3] = 0;
                        stateC[1, 4] = 1;
                        stateC[1, 5] = 0;
                        stateC[2, 0] = 0;
                        stateC[2, 1] = 0;
                        stateC[2, 2] = 1;
                        stateC[2, 3] = 0;
                        stateC[2, 4] = 0;
                        stateC[2, 5] = 1;
                        stateC[3, 0] = 0;
                        stateC[3, 1] = 0;
                        stateC[3, 2] = 0;
                        stateC[3, 3] = 1;
                        stateC[3, 4] = 0;
                        stateC[3, 5] = 0;
                        stateC[4, 0] = 0;
                        stateC[4, 1] = 0;
                        stateC[4, 2] = 0;
                        stateC[4, 3] = 0;
                        stateC[4, 4] = 1;
                        stateC[4, 5] = 0;
                        stateC[5, 0] = 0;
                        stateC[5, 1] = 0;
                        stateC[5, 2] = 0;
                        stateC[5, 3] = 0;
                        stateC[5, 4] = 0;
                        stateC[5, 5] = 1;

                        //P_K(P_k)
                        double[,] errCovPost = P_K.Detail;
                        errCovPost[0, 0] = 0.1;
                        errCovPost[0, 1] = 0;
                        errCovPost[0, 2] = 0;
                        errCovPost[0, 3] = 0;
                        errCovPost[0, 4] = 0;
                        errCovPost[0, 5] = 0;
                        errCovPost[1, 0] = 0;
                        errCovPost[1, 1] = 0.1;
                        errCovPost[1, 2] = 0;
                        errCovPost[1, 3] = 0;
                        errCovPost[1, 4] = 0;
                        errCovPost[1, 5] = 0;
                        errCovPost[2, 0] = 0;
                        errCovPost[2, 1] = 0;
                        errCovPost[2, 2] = 0.1;
                        errCovPost[2, 3] = 0;
                        errCovPost[2, 4] = 0;
                        errCovPost[2, 5] = 0;
                        errCovPost[3, 0] = 0;
                        errCovPost[3, 1] = 0;
                        errCovPost[3, 2] = 0;
                        errCovPost[3, 3] = 0.1;
                        errCovPost[3, 4] = 0;
                        errCovPost[3, 5] = 0;
                        errCovPost[4, 0] = 0;
                        errCovPost[4, 1] = 0;
                        errCovPost[4, 2] = 0;
                        errCovPost[4, 3] = 0;
                        errCovPost[4, 4] = 0.1;
                        errCovPost[4, 5] = 0;
                        errCovPost[5, 0] = 0;
                        errCovPost[5, 1] = 0;
                        errCovPost[5, 2] = 0;
                        errCovPost[5, 3] = 0;
                        errCovPost[5, 4] = 0;
                        errCovPost[5, 5] = 0.1;
                        //Debug.Log(MatrixOperator.MatrixPrint(P_K));

                        //H_K(H_K)
                        double[,] measureMat = H_K.Detail;
                        measureMat[0, 0] = 1;
                        measureMat[0, 1] = 0;
                        measureMat[0, 2] = 0;
                        measureMat[0, 3] = 0;
                        measureMat[0, 4] = 0;
                        measureMat[0, 5] = 0;
                        measureMat[1, 0] = 0;
                        measureMat[1, 1] = 1;
                        measureMat[1, 2] = 0;
                        measureMat[1, 3] = 0;
                        measureMat[1, 4] = 0;
                        measureMat[1, 5] = 0;
                        measureMat[2, 0] = 0;
                        measureMat[2, 1] = 0;
                        measureMat[2, 2] = 1;
                        measureMat[2, 3] = 0;
                        measureMat[2, 4] = 0;
                        measureMat[2, 5] = 0;

                        //Q_K(Q_K)
                        double[,] processNoiseCov = Q_K.Detail;
                        processNoiseCov[0, 0] = 1;
                        processNoiseCov[0, 1] = 0;
                        processNoiseCov[0, 2] = 0;
                        processNoiseCov[0, 3] = 0;
                        processNoiseCov[0, 4] = 0;
                        processNoiseCov[0, 5] = 0;
                        processNoiseCov[1, 0] = 0;
                        processNoiseCov[1, 1] = 1;
                        processNoiseCov[1, 2] = 0;
                        processNoiseCov[1, 3] = 0;
                        processNoiseCov[1, 4] = 0;
                        processNoiseCov[1, 5] = 0;
                        processNoiseCov[2, 0] = 0;
                        processNoiseCov[2, 1] = 0;
                        processNoiseCov[2, 2] = 1;
                        processNoiseCov[2, 3] = 0;
                        processNoiseCov[2, 4] = 0;
                        processNoiseCov[2, 5] = 0;
                        processNoiseCov[3, 0] = 0;
                        processNoiseCov[3, 1] = 0;
                        processNoiseCov[3, 2] = 0;
                        processNoiseCov[3, 3] = 1;
                        processNoiseCov[3, 4] = 0;
                        processNoiseCov[3, 5] = 0;
                        processNoiseCov[4, 0] = 0;
                        processNoiseCov[4, 1] = 0;
                        processNoiseCov[4, 2] = 0;
                        processNoiseCov[4, 3] = 0;
                        processNoiseCov[4, 4] = 1;
                        processNoiseCov[4, 5] = 0;
                        processNoiseCov[5, 0] = 0;
                        processNoiseCov[5, 1] = 0;
                        processNoiseCov[5, 2] = 0;
                        processNoiseCov[5, 3] = 0;
                        processNoiseCov[5, 4] = 0;
                        processNoiseCov[5, 5] = 1;

                        //R_K(R_K)
                        double[,] measureNoiseCov = R_K.Detail;
                        measureNoiseCov[0, 0] = 0.03;
                        measureNoiseCov[0, 1] = 0;
                        measureNoiseCov[0, 2] = 0;
                        measureNoiseCov[1, 0] = 0;
                        measureNoiseCov[1, 1] = 0.03;
                        measureNoiseCov[1, 2] = 0;
                        measureNoiseCov[2, 0] = 0;
                        measureNoiseCov[2, 1] = 0;
                        measureNoiseCov[2, 2] = 0.03;

                        double[] Arr = new double[] { angles.x, angles.y, angles.z};
                        //X_0 初始状态
                        double[,] measure = Z_K.Detail;
                        measure[0, 0] = Arr[0];
                        measure[1, 0] = Arr[1];
                        measure[2, 0] = Arr[2];

                        //首先计算推测值
                        Matrix X_front = MatrixOperator.MatrixMulti(F_K, X_K);
                        //然后计算推测值与真实值之间的误差协方差矩阵
                        Matrix P_front = MatrixOperator.MatrixMulti(MatrixOperator.MatrixMulti(MatrixOperator.MatrixMulti(F_K, P_K), MatrixOperator.MatrixTrans(F_K)), Q_K);
                        //再计算卡尔曼增益
                        Matrix Kg = MatrixOperator.MatrixMulti(MatrixOperator.MatrixMulti(P_front, MatrixOperator.MatrixTrans(H_K)), MatrixOperator.MatrixInv(MatrixOperator.MatrixAdd(MatrixOperator.MatrixMulti(MatrixOperator.MatrixMulti(H_K, P_front), MatrixOperator.MatrixTrans(H_K)), R_K)));

                        //再得到估计值
                        Matrix X_front_ = MatrixOperator.MatrixAdd(X_front, MatrixOperator.MatrixMulti(Kg, MatrixOperator.MatrixSub(Z_K, MatrixOperator.MatrixMulti(H_K, X_front))));
                        //最后计算估计值与真实值之间的误差协方差矩阵,为下次递推做准备
                        Matrix P_front_ = MatrixOperator.MatrixMulti(MatrixOperator.MatrixSub(eye, MatrixOperator.MatrixMulti(Kg, H_K)), P_front);
                        X_K = X_front_;
                        P_K = P_front_;
                        double[,] c = X_K.Detail;
                        double aa = c[0, 0];
                        double bb = c[1,0];
                        double  cc = c[2, 0];
                        angles.x = (int)aa;
                        angles.y = (int)bb;
                        angles.z = (int)cc;

                        float rotateX = (angles.x > 180) ? angles.x - 360 : angles.x;
                        float rotateY = (angles.y > 180) ? angles.y - 360 : angles.y;
                        float rotateZ = (angles.z > 180) ? angles.z - 360 : angles.z;
                        // Coordinate transformation
                        //live2DModel.PARAM_ANGLE.Set(-rotateY, rotateX, -rotateZ);
                        live2DModel.PARAM_ANGLE.Set(rotateX, rotateY, rotateZ);

                        // Make sure your line of sight is always facing the camera
                        // eye_ball_X
                        live2DModel.PARAM_EYE_BALL_X = rotateX / 60f;
                        // eye_ball_Y
                        live2DModel.PARAM_EYE_BALL_Y = rotateY / 60f - 0.25f;

                        live2DModelUpdate (points);

                        break;
                    }
                }
            }

        }

        private void live2DModelUpdate (List<Vector2> points)
        {

            if (live2DModel != null) {


                //live2DModel.PARAM_ANGLE.Set(-Roundf(rotateY, 0.5f), Roundf(rotateX, 0.5f), -Roundf(rotateZ, 0.5f));

                // brow_L_Y
                float brow_L_Y = getRaitoOfBROW_L_Y(points);
                // Keep three decimal places to reduce the vibration
                live2DModel.PARAM_BROW_L_Y = Roundf(brow_L_Y, 1000f);
                //live2DModel.PARAM_BROW_L_Y = (float)Math.Round(brow_L_Y, 2);

                // brow_R_Y
                float brow_R_Y = getRaitoOfBROW_R_Y(points);
                // Keep three decimal places to reduce the vibration
                live2DModel.PARAM_BROW_R_Y = Roundf(brow_R_Y, 1000f);
                //live2DModel.PARAM_BROW_R_Y = (float)Math.Round(brow_R_Y, 2);

                // eye_open_L
                float eyeOpen_L = getRaitoOfEyeOpen_L (points);
                if (eyeOpen_L > 0.6f && eyeOpen_L < 1.1f)
                    eyeOpen_L = 1;
                else if (eyeOpen_L >= 1.1f && brow_L_Y >= 0 )
                    eyeOpen_L = 2;
                else if (eyeOpen_L <= 0.6f)
                    eyeOpen_L = 0;
                live2DModel.PARAM_EYE_L_OPEN = eyeOpen_L;

                // eye_open_R
                float eyeOpen_R = getRaitoOfEyeOpen_R (points);
                if (eyeOpen_R > 0.6f && eyeOpen_R < 1.1f)
                    eyeOpen_R = 1;
                else if (eyeOpen_R >= 1.1f && brow_R_Y >= 0)
                    eyeOpen_R = 2;
                else if (eyeOpen_R < 0.6f)
                    eyeOpen_R = 0;
                live2DModel.PARAM_EYE_R_OPEN = eyeOpen_R;



                // mouth_open
                float mouthOpen = getRaitoOfMouthOpen_Y (points);
                if (mouthOpen < 0.6f)
                    mouthOpen = 0;
                live2DModel.PARAM_MOUTH_OPEN_Y = mouthOpen;

                // mouth_size
                float mouthSize = getRaitoOfMouthSize (points);
                live2DModel.PARAM_MOUTH_SIZE = mouthSize;

            }
        }

        // Keep decimal places to reduce the vibration
        private float Roundf(float f, float multiple)
        {
            if (multiple == 0)
                return f;
            int i = (int)(f * multiple);
            return i / multiple;
        }

        // Calculate the degree of eye opening
        private float getRaitoOfEyeOpen_L (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            return Mathf.Clamp (Mathf.Abs (points [43].y - points [47].y) / (Mathf.Abs (points [43].x - points [44].x) * 0.75f), -0.1f, 2.0f);
        }

        private float getRaitoOfEyeOpen_R (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            return Mathf.Clamp (Mathf.Abs (points [38].y - points [40].y) / (Mathf.Abs (points [37].x - points [38].x) * 0.75f), -0.1f, 2.0f);
        }

        // Eyebrows move up and down
        private float getRaitoOfBROW_L_Y (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            //float y = Mathf.Ceil(Mathf.Abs(points[24].y - points[27].y)) / Mathf.Abs (points [27].y - points [29].y);
            float y = Mathf.Abs(points[24].y - points[27].y) / Mathf.Abs(points[27].y - points[29].y);
            y -= 1;
            y *= 4f;
            
            return Mathf.Clamp (y, -1.0f, 1.0f);
        }

        private float getRaitoOfBROW_R_Y (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            //float y = Mathf.Ceil(Mathf.Abs(points[19].y - points[27].y)) / Mathf.Abs(points[27].y - points[29].y);
            float y = Mathf.Abs(Mathf.Sqrt(Mathf.Pow(points[19].y - points[27].y,2)+Mathf.Pow(points[19].x - points[27].x,2))) / Mathf.Abs(Mathf.Sqrt(Mathf.Pow(points[27].y - points[29].y,2)+Mathf.Pow(points[27].x - points[29].x,2))) ;
            Debug.Log("y是:"+y);
            y -= 1;
            y *= 4f;

            return Mathf.Clamp (y, -1.0f, 1.0f);
        }

        // Calculate the degree of mouth opening
        private float getRaitoOfMouthOpen_Y (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            return (float)((points[57].y - points[51].y) / (points[64].x - points[60].x) - 0.2);

            //return Mathf.Clamp01 (Mathf.Abs (points [62].y - points [66].y) / (Mathf.Abs (points [51].y - points [62].y) + Mathf.Abs (points [66].y - points [57].y)));
        }

        // Calculate the width of the mouth
        private float getRaitoOfMouthSize (List<Vector2> points)
        {
            if (points.Count != 68)
                throw new ArgumentNullException ("Invalid landmark_points.");

            float size = Mathf.Abs (points [48].x - points [54].x) / (Mathf.Abs (points [31].x - points [35].x) * 1.8f);
            size -= 1;
            size *= 4f;

            return Mathf.Clamp (size, -1.0f, 1.0f);
        }

        /// <summary>
        /// Raises the disable event.
        /// </summary>
        void OnDisable ()
        {
            if(webCamTextureToMatHelper != null) webCamTextureToMatHelper.Dispose ();

            if(faceLandmarkDetector != null) faceLandmarkDetector.Dispose ();

            if(frontalFaceParam != null) frontalFaceParam.Dispose ();
        }

        public void OnBackButton()
        {
#if UNITY_5_3 || UNITY_5_3_OR_NEWER
            SceneManager.LoadScene("FacemojiStart");
#else
            Application.LoadLevel("FacemojiStart");
#endif
        }

        /// <summary>
        /// Raises the change camera button event.
        /// </summary>
        public void OnChangeCameraButton ()
        {
            webCamTextureToMatHelper.Init (null, webCamTextureToMatHelper.requestWidth, webCamTextureToMatHelper.requestHeight, !webCamTextureToMatHelper.requestIsFrontFacing);
        }

        public void OnStartButton()
        {
            startBtn.SetActive(false);
            if (!Record.isRecording)
            {
                //Start recording
                Record.isRecording = true;
                finishBtn.SetActive(true);
            }
        }

        public void OnFinishButton()
        {
            finishBtn.SetActive(false);
            if (Record.isRecording)
            {
                //Finish recording
                Record.isRecording = false;
                startBtn.SetActive(true);
            }
        }

        public void OnShizukuButton()
        {
            if(selectedTexture != WhichTexture.noir_santa)
            {
                selectedTexture = WhichTexture.noir_santa;
                LoadTexture();
            }
        }

        public void OnHaruButton()
        {
            if (selectedTexture != WhichTexture.uni)
            {
                selectedTexture = WhichTexture.uni;
                LoadTexture();
            }
        }

    }
}

 

main.c /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "i2c.h" #include "tim.h" #include "usart.h" #include "gpio.h" #include "jy61p.h" #include "pid.h" #include "kalman_filter.h" #include "motor.h" #include "encoder.h" #include "ultrasonic.h" #include "motion.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "stdio.h" #include "string.h" // Ô²ÖùÐÅÏ¢½á¹¹Ìå #define MAX_CYLINDERS 10 typedef struct { int x, y, r; char color; } Cylinder; Cylinder cylinders[MAX_CYLINDERS]; int cylinder_count = 0; uint8_t rx_byte; char openmv_buffer[128]; uint8_t openmv_index = 0; uint32_t start_time = 0; uint32_t timeout = 10000; // 10s /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ void SetTargetYaw(float angle); void AddCylinder(int x, int y, int r, char color); void Path_AroundCylinder(char color); void BuildPath(void); void StartMission(void); uint8_t IsMissionTimeout(void); int CheckColumnSwap(void); void HandleColumnSwap(void); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ // Ä£¿éÊý¾Ý½á¹¹Ìå JY61P_Data imu_data; PID_Controller speed_pid, angle_pid; KalmanFilter kf; // ¿ØÖƲÎÊý float target_speed = 100; float base_pwm = 70; float dt = 0.01; // ¿ØÖÆÖÜÆÚ/Ãë void Control_Straight(void); void Move_Forward(float cm); void Turn_Left(float angle); void Turn_Right(float angle); // PID ¿ØÖÆÖ±ÐÐ void Control_Straight(void) { float left_speed = Get_Left_Encoder(); float right_speed = Get_Right_Encoder(); float error = left_speed - right_speed; float correction = PID_Update(&angle_pid, error, dt); float left_pwm = base_pwm + correction; float right_pwm = base_pwm - correction; Motor_SetSpeed(left_pwm, right_pwm); } // PID ¿ØÖÆ×ªÏòº¯Êý void SetTargetYaw(float angle) { printf("Turning %.1f degrees\n", angle); HAL_Delay(500); } // Ìí¼ÓÔ²Öùµ½µØÍ¼ void AddCylinder(int x, int y, int r, char color) { if (cylinder_count < MAX_CYLINDERS) { cylinders[cylinder_count].x = x; cylinders[cylinder_count].y = y; cylinders[cylinder_count].r = r; cylinders[cylinder_count].color = color; cylinder_count++; } } // ÈÆÏß·¾¶ void Path_AroundCylinder(char color) { if (color == 'W') { SetTargetYaw(-180.0f); // °××ó } else if (color == 'B') { SetTargetYaw(180.0f); // ºÚÓÒ } } // ¹¹½¨Â·¾¶ void BuildPath() { for (int i = 0; i < cylinder_count; i++) { Path_AroundCylinder(cylinders[i].color); } } //¿ªÊ¼¼ÆÊ± void StartMission() { start_time = HAL_GetTick(); } // ÊÇ·ñ³&not;ʱ uint8_t IsMissionTimeout() { return (HAL_GetTick() - start_time) > timeout; } // ¼ì²âÔ²ÖùÊÇ·ñ»¥»» int CheckColumnSwap() { for (int i = 0; i < cylinder_count; i++) { if (cylinders[i].x == 3 && cylinders[i].color == 'B') { return 1; } } return 0; } //´¦ÀíÖù×Ó»¥»» void HandleColumnSwap() { if (CheckColumnSwap()) { printf("Öù×ÓλÖñ仯£&not;ÖØÐ¹滮·Ïß...\n"); BuildPath(); // ÖØÐÂÈÆÖù } } //´®¿Ú½ÓÊջص÷ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { if (rx_byte == '\n') { openmv_buffer[openmv_index] = '\0'; openmv_index = 0; if (strncmp(openmv_buffer, "C ", 2) == 0) { int x, y, r; char color; sscanf(openmv_buffer, "C %d %d %d %c", &x, &y, &r, &color); AddCylinder(x, y, r, color); Path_AroundCylinder(color); } } else { openmv_buffer[openmv_index++] = rx_byte; } HAL_UART_Receive_IT(&huart1, &rx_byte, 1); } } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM2_Init(); MX_TIM3_Init(); MX_TIM4_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_UART_Receive_IT(&huart1, &rx_byte, 1); StartMission(); //Æô¶¯±àÂëÆ÷¼ÆÊý HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); // ³õʼ»¯ IMU JY61P_Init(&hi2c1); // ³õʼ»¯ PID PID_Init(&speed_pid, 1.0f, 0.1f, 0.05f); PID_Init(&angle_pid, 2.0f, 0.0f, 0.1f); // ³õʼ»¯ ¿¨¶ûÂüÂ˲¨ KalmanFilter_Init(&kf, 0.001f, 0.003f, 0.03f); // Æô¶¯ PWM Êä³ö HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ while (1) { float left = Get_Left_Distance(); float right = Get_Right_Distance(); if (left > 10.0f && right > 10.0f) { Control_Straight(); // Ö±ÐпØÖÆ } else { Motor_Stop(); // Í£Ö¹ if (left > right) Turn_Left(90); // ×óת else Turn_Right(90); // ÓÒת Move_Forward(20); // ǰ½ø20cm Turn_Right(90); // »ØÕý Move_Forward(20); // ¼ÌÐøÇ°½ø } Send_Debug_Info(); // ´®¿ÚÊä³öµ÷ÊÔÐÅÏ¢ HAL_Delay(10); // 10ms ÑÓʱ } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ encoder.c #include "encoder.h" #include "main.h" #include "tim.h" int16_t Get_Left_Encoder(void) { return (int16_t)__HAL_TIM_GET_COUNTER(&htim3); } int16_t Get_Right_Encoder(void) { return (int16_t)__HAL_TIM_GET_COUNTER(&htim4); } jy61p.c#include "jy61p.h" #include <string.h> #define JY61P_ADDRESS 0xA0 void JY61P_Init(I2C_HandleTypeDef *hi2c) {} float JY61P_Read_Float(I2C_HandleTypeDef *hi2c, uint8_t reg) { uint8_t tx_data = reg; uint8_t rx_data[4]; HAL_I2C_Master_Transmit(hi2c, JY61P_ADDRESS, &tx_data, 1, HAL_MAX_DELAY); HAL_I2C_Master_Receive(hi2c, JY61P_ADDRESS, rx_data, 4, HAL_MAX_DELAY); float value; memcpy(&value, rx_data, sizeof(float)); return value; } void JY61P_Read_Data(I2C_HandleTypeDef *hi2c, JY61P_Data *data) { data->yaw = JY61P_Read_Float(hi2c, 0x34); data->pitch = JY61P_Read_Float(hi2c, 0x32); data->roll = JY61P_Read_Float(hi2c, 0x30); } klman_filter.c #include "kalman_filter.h" void KalmanFilter_Init(KalmanFilter *kf, float Q_angle, float Q_gyro, float R_angle) { kf->theta = 0.0f; kf->omega = 0.0f; kf->P[0][0] = 1.0f; kf->P[0][1] = 0.0f; kf->P[1][0] = 0.0f; kf->P[1][1] = 1.0f; kf->Q_angle = Q_angle; kf->Q_gyro = Q_gyro; kf->R_angle = R_angle; } float KalmanFilter_Update(KalmanFilter *kf, float measured_angle, float measured_omega, float dt) { kf->omega = measured_omega; kf->theta += dt * kf->omega; kf->P[0][0] += dt * (dt * kf->P[1][1] - kf->P[0][1] - kf->P[1][0] + kf->Q_angle); kf->P[0][1] -= dt * kf->P[1][1]; kf->P[1][0] -= dt * kf->P[1][1]; kf->P[1][1] += kf->Q_gyro * dt; float y = measured_angle - kf->theta; float S = kf->P[0][0] + kf->R_angle; float K0 = kf->P[0][0] / S; float K1 = kf->P[1][0] / S; kf->theta += K0 * y; kf->omega += K1 * y; kf->P[0][0] -= K0 * kf->P[0][0]; kf->P[0][1] -= K0 * kf->P[0][1]; kf->P[1][0] -= K1 * kf->P[0][0]; kf->P[1][1] -= K1 * kf->P[0][1]; return kf->theta; #include "kalman_filter.h" void KalmanFilter_Init(KalmanFilter *kf, float Q_angle, float Q_gyro, float R_angle) { kf->theta = 0.0f; kf->omega = 0.0f; kf->P[0][0] = 1.0f; kf->P[0][1] = 0.0f; kf->P[1][0] = 0.0f; kf->P[1][1] = 1.0f; kf->Q_angle = Q_angle; kf->Q_gyro = Q_gyro; kf->R_angle = R_angle; } float KalmanFilter_Update(KalmanFilter *kf, float measured_angle, float measured_omega, float dt) { kf->omega = measured_omega; kf->theta += dt * kf->omega; kf->P[0][0] += dt * (dt * kf->P[1][1] - kf->P[0][1] - kf->P[1][0] + kf->Q_angle); kf->P[0][1] -= dt * kf->P[1][1]; kf->P[1][0] -= dt * kf->P[1][1]; kf->P[1][1] += kf->Q_gyro * dt; float y = measured_angle - kf->theta; float S = kf->P[0][0] + kf->R_angle; float K0 = kf->P[0][0] / S; float K1 = kf->P[1][0] / S; kf->theta += K0 * y; kf->omega += K1 * y; kf->P[0][0] -= K0 * kf->P[0][0]; kf->P[0][1] -= K0 * kf->P[0][1]; kf->P[1][0] -= K1 * kf->P[0][0]; kf->P[1][1] -= K1 * kf->P[0][1]; return kf->theta; } ulartrasonic.c #include "ultrasonic.h" #include "main.h" #include <math.h> #define TRIG_PORT GPIOB #define ECHO_PORT GPIOB #define TRIG_LEFT_PIN GPIO_PIN_2 #define ECHO_LEFT_PIN GPIO_PIN_3 #define TRIG_RIGHT_PIN GPIO_PIN_4 #define ECHO_RIGHT_PIN GPIO_PIN_5 static uint32_t get_pulse_width(GPIO_TypeDef *port, uint16_t pin) { uint32_t start = 0, end = 0; HAL_GPIO_WritePin(TRIG_PORT, TRIG_LEFT_PIN, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(TRIG_PORT, TRIG_LEFT_PIN, GPIO_PIN_RESET); while (!HAL_GPIO_ReadPin(port, pin)) { start = HAL_GetTick(); if (start > 100) break; } while (HAL_GPIO_ReadPin(port, pin)) { end = HAL_GetTick(); if (end - start > 30) break; } return end - start; } float Get_Left_Distance(void) { uint32_t width = get_pulse_width(ECHO_PORT, ECHO_LEFT_PIN); return width * 0.034 / 2; } float Get_Right_Distance(void) { uint32_t width = get_pulse_width(ECHO_PORT, ECHO_RIGHT_PIN); return width * 0.034 / 2; } pid.c #include "pid.h" void PID_Init(PID_Controller *pid, float Kp, float Ki, float Kd) { pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd; pid->integral = 0.0f; pid->last_error = 0.0f; } float PID_Update(PID_Controller *pid, float error, float dt) { pid->integral += error * dt; float derivative = (error - pid->last_error) / dt; float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; pid->last_error = error; return output; } motor.c #include "motor.h" #include "main.h" #include "tim.h" void Motor_SetSpeed(float left, float right) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, (uint32_t)left); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, (uint32_t)right); } void Motor_Stop(void) { __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 0); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 0); } usart.c /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file usart.c * @brief This file provides code for the configuration * of the USART instances. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "usart.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ UART_HandleTypeDef huart1; /* USART1 init function */ void MX_USART1_UART_Init(void) { /* USER CODE BEGIN USART1_Init 0 */ /* USER CODE END USART1_Init 0 */ /* USER CODE BEGIN USART1_Init 1 */ /* USER CODE END USART1_Init 1 */ huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN USART1_Init 2 */ /* USER CODE END USART1_Init 2 */ } void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* USART1 clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART1 interrupt Init */ HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } } void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle) { if(uartHandle->Instance==USART1) { /* USER CODE BEGIN USART1_MspDeInit 0 */ /* USER CODE END USART1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_USART1_CLK_DISABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10); /* USART1 interrupt Deinit */ HAL_NVIC_DisableIRQ(USART1_IRQn); /* USER CODE BEGIN USART1_MspDeInit 1 */ /* USER CODE END USART1_MspDeInit 1 */ } } /* USER CODE BEGIN 1 */ void Send_Debug_Info(void) { char buffer[100]; sprintf(buffer, "Left: %.2f cm, Right: %.2f cm\r\n", Get_Left_Distance(), Get_Right_Distance()); HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY); } /* USER CODE END 1 */ gpio.c /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file gpio.c * @brief This file provides code for the configuration * of all used GPIO pins. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "gpio.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /*----------------------------------------------------------------------------*/ /* Configure GPIO */ /*----------------------------------------------------------------------------*/ /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /** Configure pins as * Analog * Input * Output * EVENT_OUT * EXTI */ void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2|GPIO_PIN_4, GPIO_PIN_RESET); /*Configure GPIO pins : PB2 PB4 */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_4 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /*Configure GPIO pins : PB3 PB5 */ GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } /* USER CODE BEGIN 2 */ motion.c#include "motion.h" #include "main.h" #include "main.h" #include <stdio.h> void Motor_SetDirection(int left_forward, int right_forward) { // ×óµç»ú·½Ïò¿ØÖÆ(PB12 PB13) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, left_forward ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, left_forward ? GPIO_PIN_RESET : GPIO_PIN_SET); // ÓÒµç»ú·½Ïò¿ØÖÆ(PB14 PB15) HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, right_forward ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, right_forward ? GPIO_PIN_RESET : GPIO_PIN_SET); } void Move_Forward(float cm) { //ʵÏÖǰ½øÂß¼­ Motor_SetDirection(1, 1); // ×óÓÒµç»úÕýת printf("Moving forward %.1f cm\n", cm); HAL_Delay(500); } void Turn_Left(float angle) { // ʵÏÖ×óתÂß¼­ Motor_SetDirection(0, 1); // ×óµç»ú·´×ª£&not;ÓÒµç»úÕýת printf("Turning left %.1f degrees\n", angle); HAL_Delay(500); } void Turn_Right(float angle) { //ʵÏÖÓÒתÂß¼­ Motor_SetDirection(1, 0); // ×óµç»úÕýת£&not;ÓÒµç»ú·´×ª printf("Turning right %.1f degrees\n", angle); HAL_Delay(500); }
最新发布
08-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值