代码之家  ›  专栏  ›  技术社区  ›  probitaille

如何精确采样频率为60Hz的数据?

  •  1
  • probitaille  · 技术社区  · 9 年前

    实际上,我使用 InvokeRepeating 方法每1/x秒调用另一个方法。问题是调用和我得到的数据之间的延迟精度不好。

    transform.position 频率为60Hz。

    这是我的代码:

    public class Recorder : MonoBehaviour {
    
    public float samplingRate = 60f; // sample rate in Hz
    public string outputFilePath;
    
    private StreamWriter _sw;
    
    private List<Data> dataList = new List<Data>();
    
    public void OnEnable()
    {
    
        InvokeRepeating("SampleNow", 0, 1 / samplingRate);
    }
    
    public void OnDisable()
    {
        _sw = System.IO.File.AppendText(outputFilePath);
    
        for (int k=0; k< dataList.Count; k++)
        {
            _sw.WriteLine("t {0} x {1} y {2} z {3}",
               dataList[k].time, dataList[k].x, dataList[k].y, dataList[k].z);
        }
    
        _sw.Close();
        CancelInvoke();
    }
    
    public void SampleNow()
    {
        dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
    }
    
    public class Data
    {
        public float time;
        public float x;
        public float y;
        public float z;
    
        public Data(float time, float x, float y, float z)
        {
            this.time = time;
            this.x = x;
            this.y = y;
            this.z = z;
        }
      }
    }
    

    这样,我可以得到这样的结果:

    t 0 x 0 y 0 z 0
    t 0.02 x 0.283776 y -0.76 z 0
    t 0.04 x 0.599808 y -0.52 z 0
    t 0.06 x 0.599808 y -0.52 z 0
    t 0.08 x 0.599808 y -0.52 z 0
    t 0.09999999 x 0.599808 y -0.52 z 0
    t 0.12 x 0.599808 y -0.52 z 0
    t 0.14 x 0.599808 y -0.52 z 0
    t 0.16 x 0.599808 y -0.52 z 0
    t 0.18 x 0.599808 y -0.52 z 0
    t 0.2 x 0.599808 y -0.52 z 0
    t 0.22 x 0.599808 y -0.52 z 0
    t 0.24 x 0.599808 y -0.52 z 0
    t 0.26 x 0.599808 y -0.52 z 0
    t 0.28 x 0.599808 y -0.52 z 0
    t 0.3 x 0.599808 y -0.52 z 0
    t 0.32 x 0.599808 y -0.52 z 0
    t 0.3338465 x 0.599808 y -0.52 z 0
    t 0.3338465 x 0.2918357 y -0.7538424 z 0
    t 0.34 x 0.2918357 y -0.7538424 z 0
    t 0.3539519 x 0.2918357 y -0.7538424 z 0
    t 0.3539519 x 0.6092016 y -0.5125771 z 0
    t 0.3705226 x 0.6092016 y -0.5125771 z 0
    t 0.3870888 x 0.8340279 y -0.3137283 z 0
    t 0.4036556 x 0.9750986 y -0.114934 z 0
    t 0.42 x 0.9865224 y 0.09031145 z 0
    ...
    

    正如你在这个结果中看到的,我可以收集不同时间的重复位置(t=0.04到t=0.32),最糟糕的是,我可以得到不同位置的重复时间(t=0.3539519)(给出无限加速度)。

    事实上,被分析的游戏对象在时间上以线性运动,在X-Y轴上做了一个20秒的圆。

    所以,这段代码没有给我一个科学分析的好精度。

    如何使用Unity3D获得更高的精度?

    1 回复  |  直到 9 年前
        1
  •  2
  •   Programmer    9 年前

    这很复杂。虽然,我不会说是 团结一致。它可以精确地完成,但不能用 InvokeRepeating .

    你还有两种方法可以做到这一点。

    1. . Coroutine

    如果 调用重复 太慢了,可能是因为它使用反射来调用函数,或者可能是实现不完美。

    WaitForSecondsRealtime 而不是 WaitForSeconds . 并且不依赖于等待的帧速率。 等待几秒钟

    public float samplingRate = 60f; // sample rate in Hz
    
    void OnEnable()
    {
        StartCoroutine(startSampling());
    }
    
    IEnumerator startSampling()
    {
        WaitForSecondsRealtime waitTime = new WaitForSecondsRealtime(1f / samplingRate);
        while (true)
        {
            SampleNow();
            yield return waitTime;
        }
    }
    
    public void SampleNow()
    {
        Debug.Log("Called");
        dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
    }
    

    . Thread (推荐)

    我建议您使用它,因为您将完全避免Unity的主线程降低帧速率。在另一个系统中执行计时器操作 线 .

    问题是你 不会 transform.position.x Transform 在另一个线程中初始化。团结阻止你这样做。你必须执行 dataList.Add(new Data(Time.time, transform.position.x... 大体上 线 另一种选择是存储 transform.position 在全球 Vector3 变量,然后从另一个变量访问它 线 .

    不能 也使用 Time.time 线 并将不得不把它放在主要位置 线 ,然后将其存储在将在 线 作用

    您还必须使用 lock 关键字来制作它 线程安全 请注意,使用 关键字会减慢你的游戏速度 小的 一点它将从您的游戏中删除至少2或3个帧,但这是必需的,值得它提供的好处和保护。

    下面的代码将完成我刚才所说的一切。出于测试目的,采样率设置为每秒2次。您可以在中将其更改为60Hz samplingRate 变量,当您认为它是工作属性时。

    private List<Data> dataList = new List<Data>();
    
    Thread samplingThread;
    const float samplingRate = 2f; // sample rate in Hz
    
    Vector3 posInThread;
    float TimetimeInThread;
    
    private System.Object threadLocker = new System.Object();
    
    
    void OnEnable()
    {
        startSampling();
    }
    
    void startSampling()
    {
        samplingThread = new Thread(OnSamplingData);
        samplingThread.Start();
    }
    
    void Update()
    {
        lock (threadLocker)
        {
            //Update this values to be used in another Thread
            posInThread = transform.position;
            TimetimeInThread = Time.time;
        }
    }
    
    void OnSamplingData()
    {
        long oldTime = DateTime.Now.Ticks;
        long currentTime = oldTime;
        const float waitTime = 1f / samplingRate;
        float counter;
    
        while (true)
        {
            currentTime = DateTime.Now.Ticks;
            counter = (float)TimeSpan.FromTicks(currentTime - oldTime).TotalSeconds;
    
            if (counter >= waitTime)
            {
                //Debug.Log("counter: " + counter + "  waitTime: " + waitTime);
                oldTime = currentTime; //Store current Time
                SampleNow();
            }
            Thread.Sleep(0); //Make sure Unity does not freeze
        }
    }
    
    public void SampleNow()
    {
        Debug.Log("Called");
        lock (threadLocker)
        {
            dataList.Add(new Data(TimetimeInThread, posInThread.x, posInThread.y, posInThread.z));
        }
    }
    
    void OnDisable()
    {
        if (samplingThread != null && samplingThread.IsAlive)
        {
            samplingThread.Abort();
        }
        samplingThread.Join(); //Wait for Thread to finish then exit
    }