diff --git a/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt b/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt index 01988a6..7b70ac3 100644 --- a/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt +++ b/android-project/app/src/main/java/com/photobooth/app/MainActivity.kt @@ -67,48 +67,79 @@ class MainActivity : AppCompatActivity() { private lateinit var backgroundOptions: List private val aiInferenceLock = Any() private var imageSegmenter: ImageSegmenter? = null + private var isAiSupported = true - private fun getImageSegmenter(): ImageSegmenter { + private fun getImageSegmenter(): ImageSegmenter? { synchronized(aiInferenceLock) { + if (!isAiSupported) return null if (imageSegmenter == null) { - val baseOptions = BaseOptions.builder() - .setModelAssetPath("models/multiclass_segmenter.tflite") - .build() + try { + val baseOptions = BaseOptions.builder() + .setModelAssetPath("models/multiclass_segmenter.tflite") + .build() - val options = ImageSegmenterOptions.builder() - .setBaseOptions(baseOptions) - .setOutputCategoryMask(true) - .setOutputConfidenceMasks(false) - .build() + val options = ImageSegmenterOptions.builder() + .setBaseOptions(baseOptions) + .setOutputCategoryMask(true) + .setOutputConfidenceMasks(false) + .build() - imageSegmenter = ImageSegmenter.createFromOptions(this, options) + imageSegmenter = ImageSegmenter.createFromOptions(this, options) + } catch (e: UnsatisfiedLinkError) { + isAiSupported = false + runOnUiThread { + Toast.makeText(this, "Không hỗ trợ xử lý AI trên thiết bị/emulator này (thiếu thư viện Native JNI)", Toast.LENGTH_LONG).show() + } + return null + } catch (e: Exception) { + isAiSupported = false + runOnUiThread { + Toast.makeText(this, "Lỗi khởi tạo AI Segmenter: ${e.message}", Toast.LENGTH_LONG).show() + } + return null + } } - return imageSegmenter!! + return imageSegmenter } } private var tfliteInterpreter: org.tensorflow.lite.Interpreter? = null - private fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter { + private fun getTfliteInterpreter(): org.tensorflow.lite.Interpreter? { synchronized(aiInferenceLock) { + if (!isAiSupported) return null if (tfliteInterpreter == null) { - val assetFileDescriptor = assets.openFd("models/midas.tflite") - val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor) - val fileChannel = inputStream.channel - val startOffset = assetFileDescriptor.startOffset - val declaredLength = assetFileDescriptor.declaredLength - val modelBuffer = fileChannel.map(java.nio.channels.FileChannel.MapMode.READ_ONLY, startOffset, declaredLength) - - val options = org.tensorflow.lite.Interpreter.Options() - options.setNumThreads(4) - tfliteInterpreter = org.tensorflow.lite.Interpreter(modelBuffer, options) + try { + val assetFileDescriptor = assets.openFd("models/midas.tflite") + val inputStream = java.io.FileInputStream(assetFileDescriptor.fileDescriptor) + val fileChannel = inputStream.channel + val startOffset = assetFileDescriptor.startOffset + val declaredLength = assetFileDescriptor.declaredLength + val modelBuffer = fileChannel.map(java.nio.channels.FileChannel.MapMode.READ_ONLY, startOffset, declaredLength) + + val options = org.tensorflow.lite.Interpreter.Options() + options.setNumThreads(4) + tfliteInterpreter = org.tensorflow.lite.Interpreter(modelBuffer, options) + } catch (e: UnsatisfiedLinkError) { + isAiSupported = false + runOnUiThread { + Toast.makeText(this, "Không hỗ trợ xử lý Depth trên thiết bị/emulator này (thiếu thư viện Native JNI)", Toast.LENGTH_LONG).show() + } + return null + } catch (e: Exception) { + isAiSupported = false + runOnUiThread { + Toast.makeText(this, "Lỗi khởi tạo Depth Interpreter: ${e.message}", Toast.LENGTH_LONG).show() + } + return null + } } - return tfliteInterpreter!! + return tfliteInterpreter } } private fun runDepthInference(bitmap: android.graphics.Bitmap): FloatArray { - val interpreter = getTfliteInterpreter() + val interpreter = getTfliteInterpreter() ?: return FloatArray(256 * 256) // Input: 256x256 RGB val scaled = android.graphics.Bitmap.createScaledBitmap(bitmap, 256, 256, true) @@ -1712,12 +1743,30 @@ class MainActivity : AppCompatActivity() { if (!isBgActive) { return capturedImage } + + if (!isAiSupported) { + if (blurIntensity > 0f) { + val radius = (25 * blurIntensity).toInt().coerceIn(1, 25) + return blurBitmap(capturedImage, radius) + } + return capturedImage + } try { + val segmenter = getImageSegmenter() + val interpreter = getTfliteInterpreter() + if (segmenter == null || interpreter == null) { + if (blurIntensity > 0f) { + val radius = (25 * blurIntensity).toInt().coerceIn(1, 25) + return blurBitmap(capturedImage, radius) + } + return capturedImage + } + // 1. Run MediaPipe Multiclass Image Segmentation to locate subject val mpImage = com.google.mediapipe.framework.image.BitmapImageBuilder(capturedImage).build() val segmentationResult = synchronized(aiInferenceLock) { - getImageSegmenter().segment(mpImage) + segmenter.segment(mpImage) } val categoryMaskOptional = segmentationResult.categoryMask() if (!categoryMaskOptional.isPresent) {