# Unity에서 시뮬레이션 데이터 로깅

Unity에서 로봇 시뮬레이션을 수행하면서 중요한 데이터 수집 과정을 '데이터 로깅'이라고 한다. 이 과정은 다양한 센서나 물리적 인터랙션의 결과값들을 시간에 따라 저장하여 나중에 분석하거나 디버깅하는 데 유용하게 사용할 수 있다. 특히 로봇 시뮬레이션에서 로깅된 데이터는 시스템의 상태, 센서 값, 제어 명령어 등을 포함하여 다양한 분석을 할 수 있게 해준다.

#### 데이터 로깅의 목적

로깅은 주로 다음과 같은 목적을 위해 수행된다:

1. **시스템 성능 분석**: 주행 경로, 로봇의 위치, 속도, 가속도 등을 기록하여 시뮬레이션 성능을 평가할 수 있다.
2. **센서 데이터 저장**: 라이다, IMU, 카메라 등에서 발생한 센서 데이터를 로깅하여 이후의 처리 및 분석에서 사용할 수 있다.
3. **디버깅**: 시스템 오류가 발생할 경우, 문제의 원인을 파악하기 위해 과거 데이터를 분석할 수 있다.

#### Unity에서 데이터 로깅 구현하기

Unity에서 데이터를 로깅하는 기본적인 방법은 **C# 스크립트**를 통해 원하는 데이터를 파일에 저장하는 방식이다. 주로 사용되는 파일 포맷은 **CSV(Comma-Separated Values)** 파일이다. CSV 파일은 각 시간 스텝마다 데이터 값을 저장하기에 적합하며, 후속 분석에서도 활용하기 용이한다.

```csharp
using System.IO;

public class DataLogger : MonoBehaviour
{
    private StreamWriter writer;

    void Start()
    {
        writer = new StreamWriter("simulation_data.csv");
        writer.WriteLine("Time,PositionX,PositionY,PositionZ,VelocityX,VelocityY,VelocityZ");
    }

    void Update()
    {
        Vector3 position = transform.position;
        Vector3 velocity = GetComponent<Rigidbody>().velocity;
        string data = Time.time + "," + position.x + "," + position.y + "," + position.z + "," +
                      velocity.x + "," + velocity.y + "," + velocity.z;
        writer.WriteLine(data);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

이 예제 코드에서 다음과 같은 데이터를 기록한다:

* 시간 $t$
* 로봇의 위치 $\mathbf{p}(t) = \[p\_x(t), p\_y(t), p\_z(t)]^T$
* 로봇의 속도 $\mathbf{v}(t) = \[v\_x(t), v\_y(t), v\_z(t)]^T$

데이터는 CSV 파일에 **각각의 시간 스텝**마다 기록되며, 시뮬레이션이 종료될 때 파일이 닫힌다.

#### 시간 스텝에 따른 데이터 로깅

Unity에서 데이터 로깅의 핵심은 **시간 스텝**에 따라 데이터를 기록하는 것이다. Unity의 프레임워크에서 `Update()` 함수는 매 프레임마다 호출되며, 이를 통해 매 프레임의 데이터를 저장할 수 있다. 그러나 모든 프레임에서 데이터를 로깅하면 과도한 데이터가 생성될 수 있으므로, 특정 주기마다 데이터를 로깅하는 방식도 고려해야 한다.

이때 로깅 주기를 다음과 같이 설정할 수 있다:

$$
\Delta t = \frac{1}{f\_{\text{log}}}
$$

여기서 $\Delta t$는 로깅 간격이고, $f\_{\text{log}}$는 로깅 주파수(Hz)이다. 예를 들어, 10 Hz 주기로 데이터를 기록하려면, 0.1초마다 데이터 로깅이 이루어져야 한다.

이를 구현하기 위한 방법은 다음과 같다.

```csharp
public class TimedDataLogger : MonoBehaviour
{
    private StreamWriter writer;
    public float logFrequency = 10.0f;
    private float nextLogTime = 0.0f;

    void Start()
    {
        writer = new StreamWriter("timed_simulation_data.csv");
        writer.WriteLine("Time,PositionX,PositionY,PositionZ,VelocityX,VelocityY,VelocityZ");
        nextLogTime = Time.time;
    }

    void Update()
    {
        if (Time.time >= nextLogTime)
        {
            Vector3 position = transform.position;
            Vector3 velocity = GetComponent<Rigidbody>().velocity;
            string data = Time.time + "," + position.x + "," + position.y + "," + position.z + "," +
                          velocity.x + "," + velocity.y + "," + velocity.z;
            writer.WriteLine(data);

            nextLogTime += 1.0f / logFrequency;
        }
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

위 코드에서 주기적인 로깅을 위해 $f\_{\text{log}} = 10$으로 설정된 로그 주파수를 사용한다. 실제 데이터는 지정된 주기 $\Delta t = 0.1$마다 기록된다.

#### 다양한 데이터 유형의 로깅

Unity에서 로깅할 수 있는 데이터는 로봇의 위치나 속도뿐만 아니라, 센서 데이터, 로봇의 상태, 외부 환경 정보 등 매우 다양한다. 다양한 유형의 데이터를 로깅하기 위해서는 데이터 형식에 맞는 처리가 필요하며, 데이터 저장 방식도 그에 따라 달라진다.

**1. 센서 데이터 로깅**

센서 데이터는 일반적으로 높은 주파수로 발생하며, 라이다(LiDAR), 카메라, IMU 등의 다양한 센서로부터 얻을 수 있다. 각 센서의 특성에 맞는 데이터 구조를 로깅해야 하며, 각 센서에서 발생하는 노이즈를 포함하여 시뮬레이션 데이터를 기록하는 것이 중요하다.

예를 들어, IMU(Inertial Measurement Unit) 센서에서 나오는 데이터를 로깅한다고 가정하면, 가속도 및 각속도 데이터를 수집할 수 있다. IMU는 다음과 같은 데이터를 생성한다:

* 가속도: $\mathbf{a}(t) = \[a\_x(t), a\_y(t), a\_z(t)]^T$
* 각속도: $\boldsymbol{\omega}(t) = \[\omega\_x(t), \omega\_y(t), \omega\_z(t)]^T$

이를 기록하기 위한 예시는 다음과 같다.

```csharp
public class IMUDataLogger : MonoBehaviour
{
    private StreamWriter writer;

    void Start()
    {
        writer = new StreamWriter("imu_data.csv");
        writer.WriteLine("Time,AccelX,AccelY,AccelZ,GyroX,GyroY,GyroZ");
    }

    void Update()
    {
        Vector3 accel = GetComponent<YourIMUSensor>().GetAcceleration();
        Vector3 gyro = GetComponent<YourIMUSensor>().GetGyroscope();

        string data = Time.time + "," + accel.x + "," + accel.y + "," + accel.z + "," +
                      gyro.x + "," + gyro.y + "," + gyro.z;
        writer.WriteLine(data);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

이 스크립트는 **IMU 센서**에서 얻은 가속도와 각속도 데이터를 시간에 따라 로깅하며, IMU 데이터를 기록하기 위한 포맷을 정의한다. 기록된 데이터는 다음과 같은 구조를 갖는다:

$$
\text{Time}, a\_x, a\_y, a\_z, \omega\_x, \omega\_y, \omega\_z
$$

**2. 로봇 상태 데이터 로깅**

로봇의 상태 데이터를 로깅하는 것도 중요한 작업 중 하나이다. 로봇의 상태는 주로 **로봇 위치**, **속도**, **회전 행렬** 등을 포함한다. 로봇의 회전은 **쿼터니언(Quaternion)** 또는 \*\*오일러 각(Euler angles)\*\*로 표현될 수 있으며, 이 정보를 로그 파일에 저장할 수 있다.

오일러 각은 $\mathbf{\theta}(t) = \[\theta\_x(t), \theta\_y(t), \theta\_z(t)]^T$로 표현되며, 로봇의 회전 상태를 기록할 수 있다.

다음 예제는 로봇의 위치와 오일러 각을 로깅하는 방법을 보여준다.

```csharp
public class RobotStateLogger : MonoBehaviour
{
    private StreamWriter writer;

    void Start()
    {
        writer = new StreamWriter("robot_state.csv");
        writer.WriteLine("Time,PosX,PosY,PosZ,Roll,Pitch,Yaw");
    }

    void Update()
    {
        Vector3 position = transform.position;
        Vector3 rotation = transform.eulerAngles; // 오일러 각

        string data = Time.time + "," + position.x + "," + position.y + "," + position.z + "," +
                      rotation.x + "," + rotation.y + "," + rotation.z;
        writer.WriteLine(data);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

여기서 **오일러 각**을 사용해 로봇의 회전 상태를 기록하며, 로그 데이터는 다음과 같은 형식으로 기록된다:

$$
\text{Time}, p\_x, p\_y, p\_z, \theta\_x, \theta\_y, \theta\_z
$$

**3. 다차원 데이터의 로깅**

때로는 센서 데이터나 로봇의 상태가 **다차원 배열** 형태일 수 있다. 예를 들어, 라이다(LiDAR) 센서의 경우, 각 스캔 포인트는 특정 방향에서의 거리 값을 나타내며, 이 값들은 배열 형태로 저장된다. 이러한 경우, 배열 데이터를 기록하는 방법은 CSV 파일 형식에 맞게 데이터를 직렬화하여 저장하는 방식이 필요하다.

```csharp
public class LidarDataLogger : MonoBehaviour
{
    private StreamWriter writer;

    void Start()
    {
        writer = new StreamWriter("lidar_data.csv");
        writer.WriteLine("Time,ScanData");
    }

    void Update()
    {
        float[] scanData = GetComponent<LidarSensor>().GetScan();

        string scanDataString = string.Join(",", scanData);
        string data = Time.time + "," + scanDataString;
        writer.WriteLine(data);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

이 코드는 **라이다(LiDAR) 센서**에서 얻은 다차원 데이터를 시간에 따라 기록하며, 각 스캔 데이터가 CSV 파일에 직렬화된다.

#### 파일 저장 형식

Unity에서 데이터를 로깅할 때는 주로 **CSV 파일 형식**을 사용하지만, 상황에 따라 다른 파일 형식을 사용할 수도 있다. 여러 형식 중에서 데이터를 효과적으로 저장하고 나중에 처리할 수 있는 몇 가지 형식을 소개한다.

**1. CSV (Comma-Separated Values)**

CSV 파일은 가장 널리 사용되는 형식으로, 각 데이터 포인트를 쉼표로 구분하여 기록한다. CSV 파일은 텍스트 기반이므로 사람이 읽기 쉬우며, 다양한 소프트웨어에서 처리 가능한다. 하지만 **이진 데이터**나 **복잡한 구조**를 저장하는 데는 적합하지 않는다.

CSV 파일의 장점:

* 텍스트 파일로 간단히 작성 가능
* 다양한 데이터 분석 도구와 호환 가능 (예: Excel, Pandas)

CSV 파일의 단점:

* 대용량 데이터를 다룰 때 비효율적 (텍스트 파일이므로 크기가 큼)
* 이진 데이터나 복잡한 배열을 저장할 때 불편함

**2. JSON (JavaScript Object Notation)**

JSON 파일 형식은 **객체 구조**를 표현하는 데 유용하다. 배열, 중첩된 데이터, 이진 데이터 등 복잡한 데이터 구조를 효율적으로 저장할 수 있다. JSON 형식은 특히 **웹 기반의 데이터 처리**나 **네트워크 전송**에 유용하며, **API**와의 연동에서도 자주 사용된다.

```csharp
using System.IO;
using UnityEngine;
using System.Collections.Generic;

public class JsonDataLogger : MonoBehaviour
{
    private List<LogData> logDataList = new List<LogData>();

    [System.Serializable]
    public class LogData
    {
        public float time;
        public Vector3 position;
        public Vector3 velocity;
    }

    void Update()
    {
        LogData logData = new LogData();
        logData.time = Time.time;
        logData.position = transform.position;
        logData.velocity = GetComponent<Rigidbody>().velocity;
        logDataList.Add(logData);
    }

    void OnApplicationQuit()
    {
        string json = JsonUtility.ToJson(new { logs = logDataList }, true);
        File.WriteAllText("simulation_data.json", json);
    }
}
```

이 코드에서는 **JSON 형식**으로 데이터를 저장하여 각 시간의 위치 및 속도를 로깅한다. JSON은 다음과 같은 형식으로 기록된다:

```json
{
  "logs": [
    {
      "time": 0.0,
      "position": { "x": 0.0, "y": 1.0, "z": 0.0 },
      "velocity": { "x": 0.0, "y": 0.0, "z": 0.0 }
    },
    {
      "time": 0.1,
      "position": { "x": 0.1, "y": 1.1, "z": 0.1 },
      "velocity": { "x": 0.2, "y": 0.1, "z": 0.2 }
    }
  ]
}
```

JSON 파일의 장점:

* 객체 구조를 효과적으로 표현 가능
* 네트워크 전송 및 웹 기반 시스템과 호환성 높음

JSON 파일의 단점:

* 텍스트 기반이므로 대용량 데이터에 비효율적
* 바이너리 데이터를 다루기 어려움

**3. 이진 파일 (Binary Files)**

이진 파일 형식은 **대용량 데이터**를 저장할 때 적합한다. 텍스트 파일보다 **크기가 작고** 빠르게 **읽고 쓰기**가 가능한다. 이진 파일은 CSV나 JSON보다 덜 직관적일 수 있지만, 고성능이 요구되는 로깅 시스템에서 자주 사용된다.

```csharp
using System.IO;
using UnityEngine;

public class BinaryDataLogger : MonoBehaviour
{
    private BinaryWriter writer;

    void Start()
    {
        writer = new BinaryWriter(File.Open("simulation_data.bin", FileMode.Create));
    }

    void Update()
    {
        writer.Write(Time.time);
        writer.Write(transform.position.x);
        writer.Write(transform.position.y);
        writer.Write(transform.position.z);
        writer.Write(GetComponent<Rigidbody>().velocity.x);
        writer.Write(GetComponent<Rigidbody>().velocity.y);
        writer.Write(GetComponent<Rigidbody>().velocity.z);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

이 코드에서는 **이진 데이터**로 시간, 위치, 속도 정보를 기록한다. 이진 파일은 매우 효율적이므로 **대량의 실시간 데이터**를 처리할 때 유리한다.

이진 파일의 장점:

* 매우 빠른 읽기/쓰기 속도
* 파일 크기 최소화

이진 파일의 단점:

* 사람이 읽기 어려움 (텍스트가 아님)
* 분석 도구로 바로 처리하기 어려움

#### 로그 데이터의 동기화 문제

Unity에서 로깅 시 **센서 데이터**나 **로봇 상태**는 서로 다른 주기로 발생할 수 있다. 예를 들어, IMU 센서는 100 Hz로 데이터를 생성하는 반면, 라이다는 10 Hz로 데이터를 생성할 수 있다. 이런 경우 \*\*동기화(synchronization)\*\*가 중요하다.

동기화 문제를 해결하기 위해, 각 센서 데이터를 기록할 때 **타임스탬프**를 함께 기록하고, 나중에 데이터 분석 시 동일한 시간 축에서 비교할 수 있도록 데이터를 정렬해야 한다. 각 데이터 포인트에 타임스탬프를 추가하면 다른 주기로 발생한 데이터를 정렬하여 분석할 수 있다.

#### 타임스탬프를 활용한 데이터 동기화

각각의 센서나 로봇 상태 데이터를 동기화하는 가장 효과적인 방법은 각 데이터 포인트에 **타임스탬프**를 추가하는 것이다. 타임스탬프는 각 데이터가 생성된 시점을 나타내며, 서로 다른 주기로 발생한 데이터를 나중에 동일한 시간 축에서 비교할 수 있도록 도와준다.

다음과 같은 방식으로 데이터를 기록할 때 타임스탬프를 포함하여 동기화를 수행할 수 있다.

**1. 타임스탬프 추가**

각 데이터가 기록될 때마다 해당 데이터가 생성된 시점인 타임스탬프를 추가한다. Unity의 경우, `Time.time`을 사용하여 시뮬레이션 시작 후 경과 시간을 가져올 수 있으며, 이를 타임스탬프로 활용할 수 있다.

예를 들어, IMU 센서 데이터와 라이다 센서 데이터를 동시에 기록할 때 타임스탬프를 추가하는 코드를 작성해보겠다.

```csharp
public class SensorDataLogger : MonoBehaviour
{
    private StreamWriter writer;

    void Start()
    {
        writer = new StreamWriter("sensor_data.csv");
        writer.WriteLine("Time,AccelX,AccelY,AccelZ,GyroX,GyroY,GyroZ,LidarScan");
    }

    void Update()
    {
        // IMU 데이터 로깅
        Vector3 accel = GetComponent<YourIMUSensor>().GetAcceleration();
        Vector3 gyro = GetComponent<YourIMUSensor>().GetGyroscope();
        
        // 라이다 데이터 로깅
        float[] lidarScan = GetComponent<LidarSensor>().GetScan();
        string lidarDataString = string.Join(",", lidarScan);

        // 타임스탬프 포함하여 데이터 기록
        string data = Time.time + "," + accel.x + "," + accel.y + "," + accel.z + "," +
                      gyro.x + "," + gyro.y + "," + gyro.z + "," + lidarDataString;
        writer.WriteLine(data);
    }

    void OnApplicationQuit()
    {
        writer.Close();
    }
}
```

위 코드에서는 **IMU 센서 데이터**와 **라이다 센서 데이터**가 타임스탬프와 함께 기록된다. 타임스탬프는 두 센서의 데이터가 서로 다른 주기로 발생하더라도, 나중에 데이터를 정렬하고 분석할 수 있도록 해준다.

**2. 데이터 정렬 및 동기화**

타임스탬프가 있는 데이터를 로깅한 후, 데이터를 동기화하려면 각 센서의 데이터가 발생한 시점을 기준으로 **시간 축**에 맞추어 데이터를 정렬해야 한다. 이를 위해, 각 데이터 포인트는 타임스탬프를 기준으로 정렬될 수 있으며, 필요한 경우 \*\*보간(interpolation)\*\*을 통해 동일한 시간 축에 데이터를 맞출 수 있다.

예를 들어, IMU 데이터는 100 Hz로 발생하고, 라이다 데이터는 10 Hz로 발생한다고 가정하면, IMU 데이터가 라이다 데이터 사이에 존재할 수 있다. 이를 해결하기 위해, 시간 $t$에 따른 데이터를 보간하여 동일한 시간 축에서 데이터를 비교할 수 있다.

데이터를 보간하는 방법으로 \*\*선형 보간(linear interpolation)\*\*이 가장 일반적으로 사용된다. 선형 보간은 두 데이터 포인트 사이의 값을 다음과 같이 계산한다:

$$
y(t) = y\_1 + \frac{(t - t\_1)}{(t\_2 - t\_1)} \cdot (y\_2 - y\_1)
$$

여기서:

* $t$는 보간하려는 시간
* $t\_1, t\_2$는 인접한 두 데이터 포인트의 시간
* $y\_1, y\_2$는 인접한 두 데이터 포인트의 값

**3. 동기화된 데이터의 분석**

동기화된 데이터를 이용해 다양한 분석 작업을 수행할 수 있다. 예를 들어, **로봇의 경로 추적** 성능을 평가하거나, **센서 데이터**의 정확도를 분석할 수 있다. 동기화된 데이터를 활용하면, 라이다 센서에서 발생한 장애물 데이터를 IMU 데이터와 비교하여 로봇의 동작을 분석할 수 있다.

동기화된 데이터를 CSV 파일로 정리하면 다음과 같은 형식이 된다:

| Time | AccelX | AccelY | AccelZ | GyroX | GyroY | GyroZ | LidarScan             |
| ---- | ------ | ------ | ------ | ----- | ----- | ----- | --------------------- |
| 0.01 | 0.1    | 0.0    | 9.8    | 0.02  | 0.01  | -0.01 | 10.5, 20.1, 15.3, ... |
| 0.02 | 0.1    | 0.0    | 9.8    | 0.02  | 0.01  | -0.01 | 10.7, 20.2, 15.5, ... |
| 0.03 | 0.1    | 0.0    | 9.8    | 0.02  | 0.01  | -0.01 | 11.0, 20.3, 15.6, ... |

이처럼 타임스탬프가 있는 데이터를 통해 다양한 주기로 발생하는 데이터를 효과적으로 동기화할 수 있다.
