안드로이드 YOLO v8 -1

알로에·2023년 3월 27일
4

android camera preview 구현

크게 3가지로 분류 할 수 있다.

1. 권한 허용

2. 라이브러리 추가

3. preview -> previewView에서 확인하기

✔ 1. 권한 허용

AndroidManifest.xml 파일에 아래 내용을 추가한다.
 <uses-permission android:name="android.permission.CAMERA" />

MainActivity에서 Permission 에 대한 상수 추가, 권한을 설정하고 확인하는 메서드를 추가한다.

companion object{
    const val PERMISSION = 1
}

private fun setPermissions() {
    val permissions = ArrayList<String>()
    permissions.add(android.Manifest.permission.CAMERA)

    permissions.forEach {
        if (ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,permissions.toTypedArray(), PERMISSION)
        }
    }
}

위 메서드는 각종 권한들을 ArrayList으로 할당 한 후, 이후 각 권한에 대해 허락되었는지 확인, 허락하지 않았다면 권한을 요청하는 코드이다.
권한이 하나인 경우 ArrayList가 아니여도 상관없지만, 추후에 추가할 수 있으니 동적으로 할당할 수 있게 List의 형태로 저장했다.
이후 onRequestPermissionsResult를 오버라이딩해서 권한거절에 대한 코드를 작성한다. Ctrl + o 로 오버라이딩 할 메서드를 바로 찾아올 수 있다.

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == PERMISSION) {
            grantResults.forEach {
                if (it != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "권한을 허용하지 않으면 사용할 수 없습니다", Toast.LENGTH_SHORT).show()
                    finish()
                }
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

위의 메서드는 권한 요청한 코드에 대해 권한을 거절한 경우 앱을 종료하는 코드이다. 지금은 마찬가지로 요청한 권한이 하나이므로 grantResults도 하나이다. 따라서 grantResults.forEach 가 아닌 grantResults[0]으로 해도 무방하다.

✔ 2. 라이브러리 추가

이 글은 google에서 제공하는 cameraX 라이브러리를 이용해서 미리보기를 구현한다. 따라서 build.gradle에서 아래 라이브러리를 추가한다.

    // CameraX core library using the camera2 implementation
    def camerax_version = "1.3.0-alpha05"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")

첫 두개는 하드웨어 카메라에 접근하기 위해 사용된다.
첫 번째는 카메라의 수명주기를 액티비티에 맞추기 위해서 사용된다.
두 번째는 카메라 뷰를 사용하기 위해서 사용된다.

✔ 3-1. 카메라가 보일 xml파일 수정

메인액티비티에서 화면을 보여줄 예정인데, 초기에는 textview로 "Hello world!"가 쓰여져 있다. Res -> layout -> activity_main.xml 파일에서 확인 할 수 있다.
우측 상단에 Design -> Code 로 변경하여 textView를 제거하고, 화면에 보여줄 previewView를 추가한다.
위에 잠시 말했지만, preview로 카메라에서 받아온 후, previewView로 화면에 보여준다. 따라서 보여줄 previewView의 설정을 해준다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

최상단의 ConstraintLayout은 상관없다. Linearlayout이여도 상관없다.
간단히 설명하자면, 앱의 가로 세로를 부모의 크기로 (전체 크기로) 설정한다. 이후 previewView 객체를 메인 액티비티에 불러온다.
MainActivity에 아래 코드를 추가한다.

private lateinit var previewView: PreviewView

이후 onCreate 내부에서 previewView를 가져온다.

previewView = findViewById(R.id.previewView)

✔ 3-2. 카메라 설정

private fun setCamera() {
        //카메라 제공 객체
        val processCameraProvider = ProcessCameraProvider.getInstance(this).get()

        //전체 화면
        previewView.scaleType = PreviewView.ScaleType.FILL_CENTER

        // 전면 카메라
        val cameraSelector =
            CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
        
        // 16:9 화면으로 받아옴
        val preview = Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_16_9).build()
        
        // preview 에서 받아와서 previewView 에 보여준다.
        preview.setSurfaceProvider(previewView.surfaceProvider)
        
        // 카메라의 수명 주기를 메인 액티비티에 귀속
        processCameraProvider.bindToLifecycle(this, cameraSelector, preview)
}

조금 더 부가설명을 하자면, 16:9 비율로 화면을 받아온다.
이후 해당 preview의 내용은 previewView에 보여준다.
카메라 객체와 그 외 관련된 객체의 수명주기를 메인 액티비티에 귀속시킨다. 따라서 앱이 종료되는 순간 카메라 객체도 자동으로 종료된다.
CameraSelector.LENS_FACING_BACK 는 핸드폰의 카메라 중 뒷 부분을 선택한다. 앞 부분을 원한다면 CameraSelector.LENS_FACING_FRONT 로 수정하면 된다.

정리

전체 코드는 아래와 같다.

import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.camera.core.AspectRatio
import androidx.camera.core.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat

class MainActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView
    
    companion object{
        const val PERMISSION = 1
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        previewView = findViewById(R.id.previewView)

        //권한 허용
        setPermissions()

        //카메라 켜기
        setCamera()

    }

    private fun setCamera() {
        //카메라 제공 객체
        val processCameraProvider = ProcessCameraProvider.getInstance(this).get()

        //전체 화면
        previewView.scaleType = PreviewView.ScaleType.FILL_CENTER

        // 전면 카메라
        val cameraSelector =
            CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

        // 16:9 화면으로 받아옴
        val preview = Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_16_9).build()

        // preview 에서 받아와서 previewView 에 보여준다.
        preview.setSurfaceProvider(previewView.surfaceProvider)

        // 카메라의 수명 주기를 메인 액티비티에 귀속
        processCameraProvider.bindToLifecycle(this, cameraSelector, preview)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == PERMISSION) {
            grantResults.forEach {
                if (it != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "권한을 허용하지 않으면 사용할 수 없습니다", Toast.LENGTH_SHORT).show()
                    finish()
                }
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    private fun setPermissions() {
        val permissions = ArrayList<String>()
        permissions.add(android.Manifest.permission.CAMERA)

        permissions.forEach {
            if (ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, permissions.toTypedArray(), PERMISSION)
            }
        }
    }
}

0개의 댓글