본문 바로가기
안드로이드 코틀린

[Kotlin][Android] ML Kit으로 QR 코드 인식하고 링크 연결하기

by teamnova 2025. 3. 5.
728x90

 

 

안녕하세요 지난 시간에는 QR 코드 생성하는 기능을 구현해보았는데요 

오늘은 카메라로 QR 코드를 한 뒤 해당 링크로 연결(웹 페이지 열기 등) 해보도록 하겠습니다. 

 

 

이를 위해 구글에서 제공하는 머신러닝 기반의 QR 코드 및 바코드 인식 라이브러리인 ML Kit (Machine Learning Kit) 를 사용할 예정입니다. 

QR 코드에 포함되어 있는 텍스트 URL을 인식해서 자동으로 웹 페이지 이동 가능합니다. 

 

아래는 전체 코드입니다.

 

1. QRCodeScannerActivity.kt

class QRCodeScannerActivity  : AppCompatActivity() { // implementation 'androidx.appcompat:appcompat:1.7.0'  // 최신 버전 24.5월 업뎃함. 현재 25.3

    private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
    private lateinit var previewView: PreviewView  // 카메라 프리뷰
    private val barcodeScanner = BarcodeScanning.getClient()  // ML Kit 바코드 스캐너

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qr_code_scanner)


        requestCameraPermission()  // ✅ 권한 요청 추가

        // ✅ 카메라 시작
        startCamera()

        previewView = findViewById(R.id.previewView)
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA  // 후면 카메라 사용

            val imageAnalysis = ImageAnalysis.Builder()
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), { imageProxy ->
                processQRCode(imageProxy)  // QR 코드 분석
            })

            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
        }, ContextCompat.getMainExecutor(this))
    }

    @OptIn(ExperimentalGetImage::class)
    private fun processQRCode(imageProxy: ImageProxy) {
        val mediaImage = imageProxy.image ?: return
        val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

        barcodeScanner.process(image)
            .addOnSuccessListener { barcodes ->
                for (barcode in barcodes) {
                    val qrCodeText = barcode.rawValue  // QR 코드에서 추출된 텍스트
                    qrCodeText?.let {
                        Toast.makeText(this, "QR 코드 인식: $it", Toast.LENGTH_SHORT).show()
                        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))  // 링크 열기
                        startActivity(intent)
                        finish()  // 액티비티 종료
                    }
                }
            }
            .addOnFailureListener {
                Log.e("QRScanner", "QR 코드 인식 실패", it)
            }
            .addOnCompleteListener {
                imageProxy.close()
            }
    }



    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder()
                .build()
                .also { it.setSurfaceProvider(previewView.surfaceProvider) }

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

            try {
                cameraProvider.unbindAll()  // 기존 바인딩 해제
                cameraProvider.bindToLifecycle(this, cameraSelector, preview)  // 카메라 연결
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }, ContextCompat.getMainExecutor(this))
    }



    private fun requestCameraPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 101)
        } else {
            startCamera() // ✅ 권한이 있으면 바로 카메라 실행
        }
    }


}

 

 

1. 카메라 권한 요청 

안드로이드 6.0(API 23) 이상에서는 카메라 권한을 동적으로 요청해야 합니다.

권한이 없으면 화면이 검게 나올 수 있으니 꼭 추가해주세요 

private fun requestCameraPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 101)
    } else {
        startCamera() // ✅ 권한이 있으면 바로 카메라 실행
    }
}

 

 

2. QR 코드 인식 

아래 내용은 카메라를 설정하고 실시간으로 QR 코드를 스캔하는 역할을 합니다. 

        // 카메라 프리뷰 설정
        previewView = findViewById(R.id.previewView)
        cameraProviderFuture = ProcessCameraProvider.getInstance(this)

		// 카메라 바인딩 및 프리뷰 적용
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            val preview = Preview.Builder().build().also { // 카메라 화면 표시 
                it.setSurfaceProvider(previewView.surfaceProvider) // 카메라 프리뷰를 화면에 표시
            }

		// 후면 카메라를 선택 
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA  // 후면 카메라 사용

		// 이미지 분석(QR 코드 스캔) 설정 
            val imageAnalysis = ImageAnalysis.Builder()
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // STRATEGY_KEEP_ONLY_LATEST는 가장 최근 프레임만 처리하도록 설정
                .build()

            imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), { imageProxy ->
                processQRCode(imageProxy)  // QR 코드 분석
            })

            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
        }, ContextCompat.getMainExecutor(this))
    }

 

 

 

3. QR 코드에 첨부된 링크 열기 

CameraX에서 캡처한 프레임을 ML Kit 바코드 스캐너를 사용해 QR 코드 데이터를 분석하는 역할을 합니다 

QR 코드 스캔이 완료되면 자동으로 링크를 열어 줍니다. 

  @OptIn(ExperimentalGetImage::class)
    private fun processQRCode(imageProxy: ImageProxy) {
    
    	// 캡쳐한 이미지를 가져온다 
        val mediaImage = imageProxy.image ?: return
        val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

		// QR 코드 인식 및 데이터 추출 
        barcodeScanner.process(image)
            .addOnSuccessListener { barcodes ->
                for (barcode in barcodes) {
                    val qrCodeText = barcode.rawValue  // QR 코드에서 추출된 텍스트
                    
                    // QR 코드를 토스트 메시지로 띄워주는 부분
                    qrCodeText?.let {
                        Toast.makeText(this, "QR 코드 인식: $it", Toast.LENGTH_SHORT).show()
                        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(it))  // 링크 열기
                        startActivity(intent)
                        finish()  // 액티비티 종료
                    }
                }
            }
            .addOnFailureListener {
                Log.e("QRScanner", "QR 코드 인식 실패", it)
            }
            .addOnCompleteListener {
                imageProxy.close()
            }
    }

 

 

2. activity_qr_code_scanner.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

 

 

 

3. 의존성 추가 

 //QR 코드 스캔 기능 구현하기 (ML Kit 활용)
    implementation ("com.google.mlkit:barcode-scanning:17.3.0")
    implementation ("androidx.camera:camera-core:1.4.1")  // 카메라X 기본
    implementation ("androidx.camera:camera-camera2:1.4.1")  // CameraX (카메라 기능)
    implementation ("androidx.camera:camera-lifecycle:1.4.1")
    implementation ("androidx.camera:camera-view:1.4.1")

 

 

 

감사합니다