반응형

배경

가속도 데이터를 그래프화하여 실제 데이터 값들을 눈으로 확인할 수 있게 하고 싶었다. 하지만, 유니티에서 제공하는 Line Renderer 는 UI 용으로 사용할 수 없었다. 어떻게 하면 UI 에서 그래프를 구현할 수 있을지 구글링 해본 결과 우리 친절하신 외국인 형님들이 만들어 놓은 UI Linerenderer 를 이용하면 해결할 수 있을 것 같았다.

 

UI Linerenderer

https://github.com/Radishmouse22/UILineRenderer

 

GitHub - Radishmouse22/UILineRenderer: UILineRenderer for Unity

UILineRenderer for Unity. Contribute to Radishmouse22/UILineRenderer development by creating an account on GitHub.

github.com

 

깃헙에서 제공하는 UILineRenderer.cs 를 Canvas 의 오브젝트에 적용하면 UI에서도 LineRenderer를 사용할 수 있다.

 

 

Canvas 내의 오브젝트에 해당 스크립트를 아래와 같이 적용한다. 딱히 건드릴 것 없이 스크립트를 코드에 불러와 데이터를 적용하면 된다.

 

 

public UILineRenderer accXLine;
public UILineRenderer accYLine;
public UILineRenderer accZLine;
public UILineRenderer timestamp;

private List<Vector2> accXList;
private List<Vector2> accYList;
private List<Vector2> accZList;
private List<Vector2> timestampList;

 

Manager.cs 의 최상단에 UI Linerenderer를 선언한 변수를 만들어준다.

 

Vector2 accXVec = new Vector3(elapsedTime, accX);
Vector2 accYVec = new Vector3(elapsedTime, accY);
Vector2 accZVec = new Vector3(elapsedTime, accZ);
Vector2 timestamp = new Vector3(elapsedTime, 0f);

accXList.Add(accXVec);
accYList.Add(accYVec);
accZList.Add(accZVec);
timestampList.Add(timestamp);

 

가속도 데이터 값을 받을 때마다 Vector2 를 하나 생성해 List에 삽입한다.

 

//ScrollView 를 위해 width 늘리기
accXLine.rectTransform.sizeDelta = new Vector2(accXList.Count, 100);
accXLine.points = accXList.ToArray();

accYLine.rectTransform.sizeDelta = new Vector2(accYList.Count, 100);
accYLine.points = accYList.ToArray();

accZLine.rectTransform.sizeDelta = new Vector2(accZList.Count, 100);
accZLine.points = accZList.ToArray();

timestamp.rectTransform.sizeDelta = new Vector2(timestampList.Count, 100);
timestamp.points = timestampList.ToArray();

 

운동이 끝나게 되면 가속도 List를 Array 화 하여 points 에 할당한다.

 

 

조잡하지만 데이터를 그래프화하는 방법을 알아보았다.

반응형
반응형

버튼들을 제어하는 오브젝트만들기


기존 운동선택 씬은 운동을 누르면 바로 운동측정 씬으로 이동한다. 여기서 나는 운동을 더 추가하고 운동마다 횟수 및 중량 입력창을 만들고 좀 더 비쥬얼적인 UI를 나타내 보려고 한다.

 

우선, 구현하고 싶었던 기능은 버튼 하나를 누르면 기존에 선택된 버튼의 색 및 상태를 초기화하고 누르려는 버튼의 상태를 변경하는 작업이다. 웹을 할때는 그냥 css 로 onFocus 나 onClick 으로 쉽게 하는데 유니티는 어떻게하는지 몰라 우리 지피티 형님에게 물어보았다.

 

 

뭐 여튼 정리하자면, 버튼을 관리하는 매니저를 만들어서 상태를 변경하고 함수를 실행하라고 한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; // UI 관련 네임스페이스

public class ButtonManager : MonoBehaviour
{

    public Button[] buttons; // 버튼 배열
    public Color selectedColor; // 선택된 버튼의 색
    private Color defaultColor; // 기본 색
    // Start is called before the first frame update
    void Start()
    {
        defaultColor = buttons[0].GetComponent<Image>().color; // 첫 번째 버튼의 색을 기본 색으로 설정

        foreach (var button in buttons)
        {
            button.onClick.AddListener(() => OnButtonClicked(button)); // 각 버튼에 이벤트 리스너 추가
        }
    }

    void OnButtonClicked(Button clickedButton)
    {
        foreach (var button in buttons)
        {
            SelectExBtn selectedBtn= button.GetComponent<SelectExBtn>();

            selectedBtn.cancelEx();

            button.GetComponent<Image>().color = defaultColor; // 모든 버튼을 기본 색으로 변경
        }

        clickedButton.GetComponent<Image>().color = Color.cyan; // 클릭된 버튼의 색 변경

        SelectExBtn selectBtn = clickedButton.GetComponent<SelectExBtn>();

        selectBtn.selectEx();
    }
}

 

버튼을 클릭하면 모든 버튼 배열의 버튼을 초기화하고 클릭한 버튼의 상태를 변경하는 로직으로 작성했다. 이어서 버튼마다 따로 부착되어있는 스크립트를 통해 해당 운동의 laps, weight 인풋 필드를 켜고 끄게 하고싶어서 버튼마다 SelectExBtn 스크립트를 불러와 원하는 함수를 실행해 주었다.

 

 

오예스 잘된다.

 

UI가 너무 딱딱해 보여서 대충 애셋 스토어를 뒤져 공짜 UI를 가져왔는데 너무 구리다. 이런.

 

이제 선택한 운동 정보들을 다음 씬으로 넘겨야 하기에 DataManager 에 저장해야한다.

 

 

버튼 클릭시 해당 함수들을 호출해 DataManager에 저장 !

반응형
반응형

배경


모델에 양 손에는 Grib 이라는 스크립트가 적용되어 있다. 이 스크립트를 통해 바벨과의 연결을 및 동선을 확인해주는 라인렌더러가 있다. 그런데 어느 순간부터 한쪽 스크립트가 빌드 할 때마다 오류가 나서 스크립트를 귀찮게 자꾸 적용시켜줘야 했다. 이정도 쯤이야 귀찮지만 그냥 빌드할 때마다 바꿔줬다. 하지만, 씬을 여러개 만들고 씬 이동할 때도 똑같은 현상이 발생해 어쩔 수 없이 고쳐야 하는 상황이 됐다.

 

스크립트 실행 순서 변경


부랴부랴 검색 및 지피티 형님께 물어보았고 유니티 캐시를 삭제한다던가 스크립트를 분리해보던가 다해보았지만 되지 않았다.

 

마지막 방법인 유니티 스크립트 실행 순서 를 건드려보았다. RightGrib 의 순서를 기본 값보다 1 우선순위를 주었다.

 

 

Edit / Project Setting / Script Execution Order 에서 설정하면 된다.

 

순서를 건드렸더니 문제가 해결되었다. 정확한 문제의 원인은 모르겠으나 해결하는 방법은 알게 되었으므로 하나 배웠다.

반응형
반응형

배경


스마트워치의 가속도 데이터는 필터링이 거쳐지지 않은 데이터다. 따라서, 노이즈 및 조그마한 변화에도 값이 확 튀게 된다. 이를 위해서 필터링 과정이 필요하다. 필터링을 위해 칼만필터를 사용하기로 했다.

 

칼만필터는 쉽게 말하면 이전 데이터들을 토대로 다음 데이터를 예측하는 필터다. 데이터가 추가될 수록 칼만필터의 필터링 변수들이 최신화되고 최신화된 변수들의 값을 통해 다음 데이터를 예측한다.

class KalmanFilter(q: Float,r: Float,p: Float,  x: Float, k: Float) {

    private var q: Float = q // process noise covariance
    private var r: Float = r // measurement noise covariance
    private var x: Float = x // estimated value
    private var p: Float = p // estimation error covariance
    private var k: Float = k // kalman gain

    fun update(z: Float): Float {
        // prediction update
        p = p + q

        // measurement update
        k = p / (p + r)
        x = x + k * (z - x)
        p = (1 - k) * p

        return x
    }
}

 

자세한 칼만필터 공식은 인터넷 검색하면 잘나온다 ~ ㅎㅎ 나는 이렇게 클래스를 만들어 사용했다.

하지만, 스마트워치의 가속도 센서가 안좋은지는 몰라도 움직임을 트래킹하는데 있어 정확한 자세가 측정되지 않았다.

문제 : 적분 드리프트


가속도 데이터를 이용해 트래킹을 하려면 이동거리 데이터로 이중적분을 해야한다. 하지만, 적분의 기초를 알게되면 적분시 적분상수값이 생성되는 것을 알 수 있다.

 

두번의 적분을 하게된다면 두개의 적분상수가 생성되므로 이게 누적되어 드리프트 (원래 값보다 값이 상승 혹은 하락) 현상이 발생한다.

 

이를 해결하기 위해 여러 방법을 고안했다.

첫번째 방법 : ZUPT 알고리즘


ZUPT(Zero velocity UPdaTe) 영속도 보정 알고리즘을 이용해보았다. 대충 설명해보자면 일정 임계값내의 가속도 데이터 값은 정지상태임을 가정하고 속도를 0으로 계산하는 방법이다.

 

필터링연구를 하면서 ‘정지 상태’를 인지해야함이 중요하다는 것을 알게되었다. 가만히 있어도 가속도데이터는 측정되기 때문에 오차가 발생할 확률이 증가한다.

 

따라서, ZUPT 알고리즘을 도입했고 아래 코드와 같이 제작해보았다.

if ((accX < threshold && accX > -threshold) && (accY < threshold && accY > -threshold) && (accZ < threshold && accZ > -threshold))
        {
            x += veloX * dT;
            y += veloY * dT;
            z += veloZ * dT;

            veloX = 0f;
            veloY = 0f;
            veloZ = 0f;
        }

 

두번째 방법 : 모델의 움직임 제한


 

위의 방법들을 통해서도 드리프트 현상은 해결하지 못했다. 나의 능력 부족이라 생각했다. 하지만, 전시회에서 시연을 해야하므로 일단 해결은 해야했다. 따라서, 모델의 움직임을 제한하고 운동기구가 손에서 벗어나지 않는 범위내에서 움직이게 한다면 그럴싸하게 보일 것 이라 생각했다.

 

따라서, 어깨축을 중심으로 구형태의 범위로 제한하다면 좋은 결과물이 나올것이라고 생각이 들었다.

if (distance > radius)
{
      // 오브젝트를 구의 표면에 놓아서 원점에서 지정된 반지름만큼 떨어뜨립니다.
            // moveObeject 는 바벨
      Vector3 fromOriginToObject = moveObject.transform.position - initPos;
      Vector3 fromOriginToObject2 = moveObject2.transform.position - initPos2;
      fromOriginToObject *= radius / distance; // 거리를 반지름으로 정규화
      fromOriginToObject2 *= radius / distance; // 거리를 반지름으로 정규화
      moveObject.transform.position = initPos + fromOriginToObject;
      moveObject2.transform.position = initPos2 + fromOriginToObject2;

            //범위가 radius 값을 초과하면 값을 끝값에 초기화
            x = moveObject.transform.position.x;
            y = moveObject.transform.position.y;
            z = moveObject.transform.position.z;
}

 

 

움직인 범위를 빨간공으로 나타내어 봤는데 딱 구형태로 잘 움직인는 것을 보니 뿌듯했다.

 

하지만


위의 해결책은 잠시나마 꼼수로 한 것이다. 너무 너무 아쉽지만 나중에 IMU 센서 혹은 MPU6050 을 이용해 나의 해결책이 틀린건지 센서가 이상한건지 교차검증 해보고는 싶다.

반응형

+ Recent posts