중력센서를 사용해 기기의 기울기 각도 확인하기

이번 포스팅에서는 기기의 수평도를 확인하는 법에 대해 알아보도록 하겠습니다.

# 좌표계와 기울기에 대한 이해

안드로이드 시스템의 좌표계는 다음과 같이 구성됩니다.

출처 : https://developer.android.com/guide/topics/sensors/sensors_motion#sensors-motion-rotate

출처 : https://developer.android.com/guide/topics/sensors/sensors_motion#sensors-motion-rotate

기기가 세워진 상태로 정지하고 있을 때 가속도 센서로 각 축에 걸리는 가속도를 측정하면 x, z 축에서는 0, 그리고 y 축에는 9.8 m/s^2 값이 확인됩니다. y축의 -방향으로 9.8 m/s^2의 중력이 걸리고 있으므로 기기는 +방향으로 9.8 m/s^2 만큼 가속되는 것이기 때문입니다. 같은 이유로 기기를 책상에 눕혀놓으면 축이 바뀌면서 x, y 축에는 0, 그리고 z 축에는 +9.8 m/s^2의 가속도가 걸리게 됩니다.

그런데 다음과 같은 3D 좌표계에서 벡터 R과 Rx, Ry, Rz 사이의 각도는 수학적으로 다음과 같이 구할 수 있습니다.

출처 : https://electronics.stackexchange.com/q/21902

출처 : https://electronics.stackexchange.com/q/21902

$$ \begin{align} cos(A_{xr})=\frac{R_{x}}{R} \ cos(A_{yr})=\frac{R_{y}}{R} \ cos(A_{zr})=\frac{R_{z}}{R} \end{align} $$

Rx, Ry, Rz 벡터의 총 크기는 피타고라스 정리에 의해 다음과 같이 정의됩니다.

$$ \begin{align} R=\sqrt{R_{x}^2+R_{y}^2+R_{z}^2} \end{align} $$

기기가 벡터 R처럼 기울었을 때 기기에 걸리는 가속도값은 Rx, Ry, Rz입니다. 그러면 각 축에 대한 벡터R의 기울기는 다음과 같이 계산할 수 있습니다.

$$ \begin{align} A_{xr}=arccos\left(\frac{R_{x}}{R}\right) \ A_{yr}=arccos\left(\frac{R_{y}}{R}\right) \ A_{zr}=arccos\left(\frac{R_{z}}{R}\right) \end{align} $$

수평계는 기기가 화면을 +z 방향으로 하고 누워있을 때의 x축과 y축에 대한 기울기를 구하는 것이니까 AxrAyr을 구하면 됩니다.

그런데 가속도 센서는 노이즈에 민감하고, 또 기기가 사용자에 의해 움직이고 있을 때는 중력가속도에 외력이 더해지기 때문에 각도를 정확하게 측정할 수 없습니다. 그래서 여기서는 가속도 센서 대신 중력 센서를 사용할 겁니다. 중력센서는 가속도센서와 자이로스코프 보정을 통해 각 축의 중력강도만을 반환하기 때문에 외력의 영향을 무시할 수 있습니다.

# 수평계 구현

기기가 회전하면 좌표축이 바뀌게 되므로 우선은 기기가 회전하지 않도록 합니다. onCreate를 초기화하기 전에 setRequestedOrientationSCREEN_ORIENTATION_PORTRAIT로 설정해주면 됩니다.

1
2
3
4
5
6
7
@SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
    // 세로모드 고정
    requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    super.onCreate(savedInstanceState)
    setContentView(binding.root)
}

다음은 센서를 인식시켜 줍니다. SensorManager 전역변수를 만들어주고 액티비티에서 SensorEventListener 를 상속받아 센서값의 변화를 통지받을 수 있게 합니다. onResume에서는 registerListener로 센서를 등록합니다. 그리고 앱이 백그라운드로 전환되면 자원낭비를 막기 위해 unregisterListener로 사용을 해제하게 하면 됩니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class MainActivity : AppCompatActivity(), SensorEventListener {
    ...
    private val sensorManager by lazy {
        getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

    override fun onResume() {
        super.onResume()
        sensorManager.registerListener(this,
            sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY),
            SensorManager.SENSOR_DELAY_FASTEST)
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

센서값이 변화되면 onSensorChanged 콜백으로 값을 확인할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
override fun onSensorChanged(event: SensorEvent?) {
    event?.let {
        val x = event.values[0]
        val y = event.values[1]
        val z = event.values[2]
        val r = sqrt(x.pow(2) + y.pow(2) + z.pow(2))

        Log.d("MainActivity", "onSensorChanged: x: $x, y: $y, z: $z, R: $r")
        val xrAngle = (90 - acos(x / r) * 180 / PI).toFloat()
        val yrAngle = (90 - acos(y / r) * 180 / PI).toFloat()

        binding.textview.text = String.format(
            "x-rotation: %.1f\u00B0 \n y-rotation: %.1f\u00B0", xrAngle, yrAngle)
    }
}

override fun onAccuracyChanged(p0: Sensor?, p1: Int) { }

중력센서의 값은 SensorEvent의 value 속성으로 가져올 수 있습니다. 공식에 따라 r 값을 계산하고 AxrAyr을 계산하면 됩니다. 계산값은 라디안으로 반환되기 때문에 (180/PI)를 곱해서 각도로 변환합니다. 그리고 안드로이드 시스템의 좌표계에서 기기가 누워있는 상태는 x, y축으로 90도 회전한 상태이기 때문에 이 값을 0으로 만들기 위해 90을 빼주면 됩니다.

이렇게 해서 센서를 이용해 기기의 수평도를 계산하는 방법에 대해 알아보았습니다.

Licensed under CC BY 4.0
Built with Hugo
Theme Stack designed by Jimmy