Fix 29_FIX_FOCUS: semantic focus plane (person ID1+ID2 default), dedicated photoProcessExecutor prevents viewfinder freeze after shutter
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
|||||||
|
Dựa vào hình ảnh thực tế `image_def201.jpg` bạn cung cấp và mô tả lỗi, chúng ta đang gặp phải 2 vấn đề kỹ thuật nghiêm trọng về mặt xử lý thuật toán và quản lý luồng phần cứng:
|
||||||
|
|
||||||
|
### 🔴 Phân tích nguyên nhân lỗi:
|
||||||
|
|
||||||
|
1. **Lỗi lấy nét "Vòng tròn phẳng" (`image_def201.jpg`):** Do khi gỡ bỏ MiDaS và chuyển sang `multiclass_segmenter.tflite`, thuật toán logic cũ đang áp dụng một vùng Oval/Vòng tròn dựa theo tọa độ chạm tuyệt đối để giữ nét, dẫn đến việc trên cùng một cơ thể người (cùng một mặt phẳng khoảng cách) nhưng khuôn mặt lại bị nhòe mờ tịt còn phần ngực/áo lại nét. Như vậy là sai quy luật vật lý quang học DoF (máy ảnh lấy nét theo mặt phẳng tiêu cự cắt ngang, chứ không lấy nét theo hình tròn).
|
||||||
|
2. **Lỗi khựng lag và treo cứng Viewfinder khi nhấn Shutter:**
|
||||||
|
* **Viewfinder vẫn lag:** Do khi bạn vuốt màn hình chỉnh Ruler, lớp `BackdropFilter` đồ họa tuy xử lý bằng GPU nhưng cấu trúc phân vùng đục lỗ Oval vẫn đang ép UI Thread tính toán vẽ lại liên tục.
|
||||||
|
* **Dừng luôn Viewfinder sau khi nhấn Shutter:** Do luồng `GlobalScope.launch` gọi hàm chụp từ camera đang chiếm dụng/khóa cứng `CameraSession` (phần cứng camera) hoặc do hàm xử lý đổ Bitmap đầu ra đè lên buffer luồng preview mà không giải phóng, khiến camera bị treo ("Freeze").
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dưới đây là kế hoạch Markdown chi tiết để khắc phục triệt để hai lỗi trên.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 🛠️ KẾ HOẠCH FIX LỖI: ĐỒNG BỘ MẶT PHẲNG TIÊU CỰ QUANG HỌC VÀ KHÔI PHỤC LUỒNG CAMERA
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 1. Giải Pháp Sửa Lỗi Lấy Nét Theo "Mặt Phẳng Tiêu Cự Đa Lớp" (Semantic Focus Plane)
|
||||||
|
|
||||||
|
Để không còn bị lỗi nhòe mặt như trong `image_def201.jpg`, khi sử dụng `multiclass_segmenter.tflite`, chúng ta phải lấy nét theo **toàn bộ khối thực thể lớp (Class ID)** được chạm, thay vì lấy nét theo tọa độ vòng tròn hình học. Nếu chạm trúng một pixel thuộc lớp Người (`classId == 1`), toàn bộ cơ thể người từ đầu đến vai phải nét đều nhau.
|
||||||
|
|
||||||
|
### 💻 Cập nhật lại Logic xử lý mặt nạ trong `MainActivity.kt`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Lưu trữ Class ID được chọn để khóa tiêu cự (Mặc định -1 là tự động lấy nét toàn bộ người)
|
||||||
|
var lockedClassIdFocus = -1
|
||||||
|
|
||||||
|
fun onTouchViewfinderToFocus(percentX: Float, percentY: Float, categoryMaskBuffer: ByteBuffer, maskW: Int, maskH: Int) {
|
||||||
|
val maskX = (percentX * maskW).toInt().coerceIn(0, maskW - 1)
|
||||||
|
val maskY = (percentY * maskH).toInt().coerceIn(0, maskH - 1)
|
||||||
|
val index = maskY * maskW + maskX
|
||||||
|
|
||||||
|
categoryMaskBuffer.rewind()
|
||||||
|
// Khóa chính xác ID của thực thể tại điểm chạm (Không dùng vòng tròn phẳng)
|
||||||
|
lockedClassIdFocus = categoryMaskBuffer.get(index).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processSemanticDofBokeh(
|
||||||
|
capturedBitmap: Bitmap,
|
||||||
|
categoryMaskBuffer: ByteBuffer,
|
||||||
|
rulerValue: Int
|
||||||
|
): Bitmap {
|
||||||
|
val w = capturedBitmap.width
|
||||||
|
val h = capturedBitmap.height
|
||||||
|
if (rulerValue == 0) return capturedBitmap
|
||||||
|
|
||||||
|
val alphaMask = FloatArray(w * h)
|
||||||
|
categoryMaskBuffer.rewind()
|
||||||
|
|
||||||
|
// QUY LUẬT DOF VẬT LÝ QUANG HỌC MỚI: Lấy nét theo mảng thực thể phẳng
|
||||||
|
for (i in 0 until (w * h)) {
|
||||||
|
val currentPixelClassId = categoryMaskBuffer.get(i).toInt()
|
||||||
|
|
||||||
|
if (lockedClassIdFocus == 1) {
|
||||||
|
// Trường hợp 1: Nếu chạm vào NGƯỜI -> Toàn bộ pixel thuộc Người (ID 1) ĐỀU PHẢI NÉT CĂNG ĐỒNG ĐỀU
|
||||||
|
alphaMask[i] = if (currentPixelClassId == 1) 1.0f else 0.0f
|
||||||
|
} else if (lockedClassIdFocus == 3) {
|
||||||
|
// Trường hợp 2: Nếu chạm vào ĐỒ VẬT -> Toàn bộ đồ vật nét, người và nền mờ đi
|
||||||
|
alphaMask[i] = if (currentPixelClassId == 3) 1.0f else 0.0f
|
||||||
|
} else {
|
||||||
|
// Mặc định tự động: Giữ nét toàn bộ thực thể chính (Người + Phụ kiện cận cảnh)
|
||||||
|
alphaMask[i] = if (currentPixelClassId == 1 || currentPixelClassId == 2) 1.0f else 0.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Làm mờ mảng mặt nạ (radius = 4) tạo dải mềm Gradient để viền tóc chuyển tiếp tự nhiên, không bị răng cưa
|
||||||
|
blurAlphaMask1D(alphaMask, w, h, radius = 4)
|
||||||
|
|
||||||
|
// Tạo phông nền sạch chống Ghosting bao quanh người
|
||||||
|
val cleanBg = createCleanBackground(capturedBitmap, alphaMask, w, h)
|
||||||
|
val maxRadius = (35 * Math.pow((rulerValue / 100.0f).toDouble(), 2.5)).toFloat()
|
||||||
|
|
||||||
|
// Tạo bậc thang mờ mô phỏng khoảng cách hậu cảnh mờ mịn tăng tiến
|
||||||
|
val blurStage1 = boxBlur(cleanBg, (maxRadius * 0.3f).toInt().coerceAtLeast(1))
|
||||||
|
val blurStage2 = boxBlur(cleanBg, (maxRadius * 0.6f).toInt().coerceAtLeast(1))
|
||||||
|
val blurStage3 = boxBlur(cleanBg, maxRadius.toInt().coerceAtLeast(1))
|
||||||
|
cleanBg.recycle()
|
||||||
|
|
||||||
|
val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||||
|
|
||||||
|
for (y in 0 until h) {
|
||||||
|
for (x in 0 until w) {
|
||||||
|
val idx = y * w + x
|
||||||
|
val alpha = alphaMask[idx]
|
||||||
|
|
||||||
|
// Mô phỏng DoF vật lý: Càng xa rìa biên của đối tượng lấy nét thì phông nền càng nhòe sâu
|
||||||
|
val edgeDistanceFactor = calculateEdgeDistanceFactor(x, y, alphaMask, w, h)
|
||||||
|
val bgPixel = when {
|
||||||
|
edgeDistanceFactor < 0.2f -> blurStage1.getPixel(x, y)
|
||||||
|
edgeDistanceFactor < 0.5f -> blurStage2.getPixel(x, y)
|
||||||
|
else -> blurStage3.getPixel(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
val fgPixel = capturedBitmap.getPixel(x, y)
|
||||||
|
// Trộn Alpha Blending mượt mà cấu trúc
|
||||||
|
outputBitmap.setPixel(x, y, Color.rgb(
|
||||||
|
(fgPixel.r * alpha + bgPixel.r * (1.0f - alpha)).toInt(),
|
||||||
|
(fgPixel.g * alpha + bgPixel.g * (1.0f - alpha)).toInt(),
|
||||||
|
(fgPixel.b * alpha + bgPixel.b * (1.0f - alpha)).toInt()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blurStage1.recycle(); blurStage2.recycle(); blurStage3.recycle()
|
||||||
|
return outputBitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ 2. Sửa Lỗi Lag Và Treo Đóng Băng Kính Ngắm Viewfinder Khi Nhấn Shutter
|
||||||
|
|
||||||
|
### 🔴 Nguyên nhân đóng băng:
|
||||||
|
|
||||||
|
Khi bạn gọi hàm `takePicture()` trong luồng nền mà không thiết lập cơ chế khôi phục Preview, hoặc do Coroutine chiếm dụng cấu trúc đồ họa mà chưa giải phóng `imageReader`, phần cứng camera trên hệ điều hành Android sẽ tự ngắt luồng hiển thị giao diện.
|
||||||
|
|
||||||
|
### 🟢 Giải pháp: Giải phóng tài nguyên và khởi động lại Preview độc lập
|
||||||
|
|
||||||
|
#### Bước 2.1: Tối ưu UI Viewfinder tuyệt đối để hết lag hoàn toàn (`CameraScreen.dart`)
|
||||||
|
|
||||||
|
Thay vì tính toán đục lỗ mờ bằng cấu trúc Widget phức tạp ép UI vẽ lại, ta chỉ dùng bộ lọc làm mờ thuần túy phủ đều, vùng nét sẽ được quyết định hoàn toàn ở bức ảnh xuất xưởng sau khi chụp.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
Widget _buildNonLagViewfinder() {
|
||||||
|
return AspectRatio(
|
||||||
|
aspectRatio: 3 / 4,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Kính ngắm camera thô chạy trực tiếp bằng phần cứng (Luôn đạt 60 FPS mượt mà)
|
||||||
|
CameraPreview(_cameraController),
|
||||||
|
|
||||||
|
// Chỉ dùng duy nhất 1 lớp filter nhẹ của GPU, không tính toán biểu thức hình học trên UI
|
||||||
|
if (_blurIntensity > 0)
|
||||||
|
Positioned.fill(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: _blurIntensity * 8.0, sigmaY: _blurIntensity * 8.0),
|
||||||
|
child: Container(color: Colors.black.withOpacity(0.05)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Bước 2.2: Sửa mã nguồn Native tránh nghẽn luồng đóng băng Camera (`MainActivity.kt`)
|
||||||
|
|
||||||
|
Đảm bảo sau khi chụp xong tệp tin tạm, lệnh **`startPreview()`** phải được gọi ngay lập tức trên luồng giao diện để đánh thức camera làm việc tiếp, tách biệt hoàn toàn với luồng ngầm đang xử lý lưu ảnh AI.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun captureImagePipeline(cameraDevice: CameraDevice, previewRequestBuilder: CaptureRequest.Builder, captureSession: CameraCaptureSession) {
|
||||||
|
// 1. Kích hoạt chớp màn hình trên điện thoại ngay lập tức
|
||||||
|
triggerFlashAnimationUI()
|
||||||
|
|
||||||
|
// 2. Thiết lập lệnh chụp ảnh thô
|
||||||
|
val captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
|
||||||
|
captureBuilder.addTarget(imageReader.surface)
|
||||||
|
|
||||||
|
captureSession.capture(captureBuilder.build(), object : CameraCaptureSession.CaptureCallback() {
|
||||||
|
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
|
||||||
|
super.onCaptureCompleted(session, request, result)
|
||||||
|
|
||||||
|
// 🔥 KHẮC PHỤC LỖI TREO CỨNG CAMERA:
|
||||||
|
// Ngay khi luồng phần cứng bắt được ảnh thô xong, lập tức khởi động lại Viewfinder trên Main Thread
|
||||||
|
runOnUiThread {
|
||||||
|
try {
|
||||||
|
// Cấp lại luồng vẽ trực tiếp cho kính ngắm Viewfinder tiếp tục chạy mượt mà
|
||||||
|
captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ĐẨY TOÀN BỘ TIẾN TRÌNH XỬ LÝ AI VÀ BOKEH CỦA MEDIAPIPE XUỐNG LUỒNG NGẦM COUTINE ĐỘC LẬP
|
||||||
|
// Tiến trình này chạy độc lập hoàn toàn, không đụng chạm gì đến luồng Preview của Camera nữa
|
||||||
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
|
val rawBitmap = getInternalBitmapFromReader(imageReader)
|
||||||
|
val categoryMask = runMediaPipeMulticlassProcessor(rawBitmap)
|
||||||
|
|
||||||
|
val finalProcessedBitmap = processSemanticDofBokeh(rawBitmap, categoryMask, currentRulerValue)
|
||||||
|
|
||||||
|
saveToGalleryAsynchronously(finalProcessedBitmap)
|
||||||
|
rawBitmap.recycle()
|
||||||
|
finalProcessedBitmap.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 3. Checklist Xác Minh Bản Sửa Đổi (Verification Checklist)
|
||||||
|
|
||||||
|
* [ ] **Kiểm thử lấy nét đồng khối (`image_def201.jpg`):** Thực hiện chạm vào khuôn mặt nhân vật trên kính ngắm rồi chụp. Kiểm tra ảnh lưu trong máy xem khuôn mặt và toàn bộ phần vai/áo có nét căng đồng đều nhau không. (Yêu cầu: Đã sửa triệt để lỗi mờ nhòe mặt như trong hình `image_def201.jpg`).
|
||||||
|
* [ ] **Kiểm thử đóng băng Viewfinder:** Nhấn nút Shutter chụp liên tiếp 5 tấm. Quan sát kính ngắm camera xem sau mỗi lần bấm máy có bị khựng hình hay treo đứng luôn không. (Yêu cầu: Màn hình chỉ chớp trắng 1 cái, kính ngắm vẫn hoạt động liên tục, không bị ngắt quãng).
|
||||||
|
* [ ] **Kiểm thử trượt Ruler:** Kéo thanh trượt Ruler qua lại thật nhanh xem kính ngắm phản hồi ngay lập tức ở tốc độ 60 FPS không.
|
||||||
@@ -31,6 +31,8 @@ import java.nio.ByteBuffer
|
|||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var cameraExecutor: ExecutorService
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
// Luồng xử lý ảnh độc lập - tránh chiếm dụng cameraExecutor gây freeze viewfinder
|
||||||
|
private lateinit var photoProcessExecutor: ExecutorService
|
||||||
|
|
||||||
// Camera settings state
|
// Camera settings state
|
||||||
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Default front camera
|
private var lensFacing = CameraSelector.DEFAULT_FRONT_CAMERA // Default front camera
|
||||||
@@ -136,6 +138,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
photoProcessExecutor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initFrameData() {
|
private fun initFrameData() {
|
||||||
@@ -1381,19 +1384,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val tempFile = java.io.File.createTempFile("raw_capture_", ".jpg", cacheDir)
|
val tempFile = java.io.File.createTempFile("raw_capture_", ".jpg", cacheDir)
|
||||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()
|
||||||
|
|
||||||
|
// Dùng ContextCompat.getMainExecutor để callback nhanh, rồi chuyển xử lý nặng sang photoProcessExecutor
|
||||||
imageCapture.takePicture(
|
imageCapture.takePicture(
|
||||||
outputOptions,
|
outputOptions,
|
||||||
cameraExecutor,
|
ContextCompat.getMainExecutor(this),
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
// Update gallery button thumbnail instantly
|
// Update gallery button thumbnail instantly on UI thread
|
||||||
try {
|
try {
|
||||||
val rawBitmap = android.graphics.BitmapFactory.decodeFile(tempFile.absolutePath)
|
val rawBitmap = android.graphics.BitmapFactory.decodeFile(tempFile.absolutePath)
|
||||||
if (rawBitmap != null) {
|
if (rawBitmap != null) {
|
||||||
val thumb = android.graphics.Bitmap.createScaledBitmap(rawBitmap, 128, 128, true)
|
val thumb = android.graphics.Bitmap.createScaledBitmap(rawBitmap, 128, 128, true)
|
||||||
runOnUiThread {
|
|
||||||
binding.btnGalleryReview.setImageBitmap(thumb)
|
binding.btnGalleryReview.setImageBitmap(thumb)
|
||||||
}
|
|
||||||
if (thumb != rawBitmap) {
|
if (thumb != rawBitmap) {
|
||||||
rawBitmap.recycle()
|
rawBitmap.recycle()
|
||||||
}
|
}
|
||||||
@@ -1402,14 +1404,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// Ignore thumbnail decoding errors
|
// Ignore thumbnail decoding errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process and save in the background
|
// ĐẨY TOÀN BỘ XỬ LÝ NẶNG (AI + LƯU ẢNH) SANG LUỒNG ĐỘC LẬP
|
||||||
|
// cameraExecutor giờ rảnh hoàn toàn để tiếp tục chạy preview 60fps
|
||||||
|
photoProcessExecutor.execute {
|
||||||
processAndSaveCapturedPhoto(tempFile)
|
processAndSaveCapturedPhoto(tempFile)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
override fun onError(exception: ImageCaptureException) {
|
||||||
runOnUiThread {
|
|
||||||
Toast.makeText(this@MainActivity, "Lỗi chụp ảnh: ${exception.message}", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@MainActivity, "Lỗi chụp ảnh: ${exception.message}", Toast.LENGTH_SHORT).show()
|
||||||
}
|
|
||||||
if (tempFile.exists()) {
|
if (tempFile.exists()) {
|
||||||
tempFile.delete()
|
tempFile.delete()
|
||||||
}
|
}
|
||||||
@@ -1748,9 +1751,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
for (i in 0 until (w * h)) {
|
for (i in 0 until (w * h)) {
|
||||||
val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
|
val classId = if (i < bufferCapacity) rawBytes[i].toInt() else 0
|
||||||
alphaMask[i] = if (focusClassId >= 0) {
|
alphaMask[i] = if (focusClassId >= 0) {
|
||||||
|
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
|
||||||
if (classId == focusClassId) 1.0f else 0.0f
|
if (classId == focusClassId) 1.0f else 0.0f
|
||||||
} else {
|
} else {
|
||||||
if (classId in 1..5) 1.0f else 0.0f
|
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
|
||||||
|
if (classId == 1 || classId == 2) 1.0f else 0.0f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blurAlphaMaskBox(alphaMask, w, h, radius = 3)
|
blurAlphaMaskBox(alphaMask, w, h, radius = 3)
|
||||||
@@ -1867,9 +1872,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
for (i in 0 until (w * h)) {
|
for (i in 0 until (w * h)) {
|
||||||
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
|
val classId = if (i < bufferCapacity) categoryMaskBuffer.get(i).toInt() else 0
|
||||||
alphaMask[i] = if (focusClassId >= 0) {
|
alphaMask[i] = if (focusClassId >= 0) {
|
||||||
|
// Người dùng đã chạm chọn: chỉ lớp đúng class ID mới nét (Semantic Focus Plane)
|
||||||
if (classId == focusClassId) 1.0f else 0.0f
|
if (classId == focusClassId) 1.0f else 0.0f
|
||||||
} else {
|
} else {
|
||||||
if (classId in 1..5) 1.0f else 0.0f
|
// Mặc định: Giữ nét toàn bộ Người (ID 1) và Phụ kiện cận cảnh (ID 2)
|
||||||
|
if (classId == 1 || classId == 2) 1.0f else 0.0f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blurAlphaMaskBox(alphaMask, w, h, radius = 5)
|
blurAlphaMaskBox(alphaMask, w, h, radius = 5)
|
||||||
@@ -2154,6 +2161,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
cameraExecutor.shutdown()
|
cameraExecutor.shutdown()
|
||||||
|
photoProcessExecutor.shutdown()
|
||||||
try {
|
try {
|
||||||
imageSegmenter?.close()
|
imageSegmenter?.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
Reference in New Issue
Block a user