Implement 34_FIX_BG.md: restore high-quality AI background replacement and blur blending for captured photos in the background thread, while maintaining lag-free early bypass for viewfinder preview
This commit is contained in:
+185
@@ -0,0 +1,185 @@
|
||||
Dưới đây là kế hoạch Markdown chi tiết để **khôi phục lại toàn bộ tính năng Xóa phông (Blur Background) và Chèn ảnh nền mới (Replace Background)** bằng mô hình `multiclass_segmenter.tflite` của Google MediaPipe.
|
||||
|
||||
Kế hoạch này đảm bảo **giữ nguyên 100% tính năng Focus vật lý bằng phần cứng thấu kính**, nghĩa là động cơ camera vẫn di chuyển lấy nét theo điểm chạm quang học, còn AI chỉ đảm nhận vai trò bóc tách và xử lý đồ họa kỹ thuật số trên bức ảnh thu được.
|
||||
|
||||
---
|
||||
|
||||
# 📑 KẾ HOẠCH PHỤC HỒI TÍNH NĂNG XÓA NỀN AI VÀ CHÈN HÌNH NỀN (GOOGLE MULTICLASS SEGMENTER)
|
||||
|
||||
Mục tiêu cốt lõi:
|
||||
|
||||
1. **Focus vật lý làm gốc:** Mặc định giữ nguyên luồng điều khiển mô-tơ thấu kính phần cứng (`applyHardwareTouchToFocus`) qua Camera2 API khi người dùng chạm vào Viewfinder.
|
||||
2. **Khôi phục AI tách lớp:** Kích hoạt lại `multiclass_segmenter.tflite` để phân tách pixel ảnh chụp thành các nhãn: Người (ID 1), Đồ vật (ID 3), Nền (ID 0).
|
||||
3. **Phục hồi 2 tính năng đồ họa:**
|
||||
* *Xóa nền:* Làm mờ phông nền thuật toán (kết hợp khử Ghost Shadow và mịn viền tóc).
|
||||
* *Chèn nền:* Thay thế vùng phông nền bằng một Bitmap hình ảnh mới do người dùng chọn.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📐 1. Quy Hoạch Trạng Thái Tính Năng Trên Giao Diện (`CameraScreen.dart`)
|
||||
|
||||
Giao diện sẽ quản lý 3 trạng thái độc lập thông qua Panel điều khiển Tầng 2 (như trong ảnh mẫu):
|
||||
|
||||
* **Chế độ Mặc định:** Chỉ dùng Focus vật lý của thấu kính, ảnh giữ nguyên thô nguyên bản từ cảm biến.
|
||||
* **Chế độ Xóa nền (Blur):** Thấu kính vẫn lấy nét vật lý + AI làm mờ thêm hậu cảnh phần mềm theo thước Ruler `0 - 100`.
|
||||
* **Chế độ Thay nền (Background 1, 2, 3):** Thấu kính vẫn lấy nét vật lý + AI bóc người ra và chèn tấm ảnh nền mới vào.
|
||||
|
||||
```dart
|
||||
enum PhotoEffectMode { defaultFocus, aiBlur, aiReplaceBg }
|
||||
|
||||
PhotoEffectMode _currentMode = PhotoEffectMode.defaultFocus;
|
||||
String? _selectedBgPath; // Đường dẫn ảnh nền mới (Nền 1, Nền 2, Nền 3)
|
||||
double _blurIntensity = 0.0; // Giá trị thước đo Ruler (0.0 -> 1.0)
|
||||
|
||||
Widget _buildActionPanel() {
|
||||
return Row(
|
||||
children: [
|
||||
_buildButton(name: "Mặc định", action: () => setState(() => _currentMode = PhotoEffectMode.defaultFocus)),
|
||||
_buildButton(name: "Xóa nền", action: () => setState(() => _currentMode = PhotoEffectMode.aiBlur)),
|
||||
_buildButton(name: "Nền 1", action: () {
|
||||
setState(() {
|
||||
_currentMode = PhotoEffectMode.aiReplaceBg;
|
||||
_selectedBgPath = "assets/bg/mountain.jpg"; // Hoặc tệp tải về từ nút "..."
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💻 2. Thuật Toán Lõi: Tích Hợp Song Song Thay Nền Và Xóa Phông AI (`MainActivity.kt`)
|
||||
|
||||
Hàm xử lý ảnh dưới tầng Native Android (`MainActivity.kt`) sẽ được khôi phục toàn diện để nhận diện đồng thời cả lệnh chèn hình nền lẫn lệnh làm mờ, triệt tiêu hoàn toàn lỗi vệt xám bọc quanh đầu tóc và bóng ma rế viền (Ghost Shadow).
|
||||
|
||||
```kotlin
|
||||
fun processRestoredAiBackgroundEngine(
|
||||
capturedBitmap: Bitmap, // Ảnh thô thu được sau khi camera đã lấy nét vật lý xong
|
||||
categoryMaskBuffer: ByteBuffer, // Buffer mặt nạ từ mô hình multiclass_segmenter.tflite
|
||||
effectMode: Int, // 0: Mặc định, 1: Xóa nền, 2: Thay nền
|
||||
bgBitmap: Bitmap?, // Bitmap của hình nền mới (nếu có)
|
||||
rulerValue: Int // Độ nhòe từ 0 đến 100
|
||||
): Bitmap {
|
||||
val w = capturedBitmap.width
|
||||
val h = capturedBitmap.height
|
||||
|
||||
// NẾU CHẾ ĐỘ MẶC ĐỊNH HOẶC KHÔNG CÓ HIỆU ỨNG: Xuất ảnh thô vật lý ngay lập tức
|
||||
if (effectMode == 0 || (effectMode == 1 && rulerValue == 0)) {
|
||||
return capturedBitmap
|
||||
}
|
||||
|
||||
// --- BƯỚC 1: TRÍCH XUẤT VÀ LÀM MỊN VIỀN TÓC (SOFT ALPHA MASK) ---
|
||||
val alphaMask = FloatArray(w * h)
|
||||
categoryMaskBuffer.rewind()
|
||||
|
||||
for (i in 0 until (w * h)) {
|
||||
val classId = categoryMaskBuffer.get(i).toInt()
|
||||
// Xác định thực thể chính cần giữ nét: Người (ID 1) và Đồ vật phụ trợ (ID 3)
|
||||
alphaMask[i] = if (classId == 1 || classId == 3) 1.0f else 0.0f
|
||||
}
|
||||
// Thuật toán tích chập 1D làm mờ mặt nạ (radius = 4) để viền tóc lồng mượt vào nền, không bị cắt mạnh gắt
|
||||
blurAlphaMask1D(alphaMask, w, h, radius = 4)
|
||||
|
||||
// --- BƯỚC 2: QUY QUYẾT NGUỒN PHÔNG NỀN (BASE BACKGROUND CANVAS) ---
|
||||
// Nếu chọn THAY NỀN, nguồn nền là tấm ảnh mới (bgBitmap) được scale vừa khung hình.
|
||||
// Nếu chọn XÓA NỀN, nguồn nền chính là ảnh chụp gốc nhưng được xử lý khử Ghost Shadow.
|
||||
val baseBgCanvas = if (effectMode == 2 && bgBitmap != null) {
|
||||
Bitmap.createScaledBitmap(bgBitmap, w, h, true)
|
||||
} else {
|
||||
// Thuật toán khử Ghost Shadow: Lấp đầy các pixel màu da bằng hạt màu nền xung quanh
|
||||
createCleanBackground(capturedBitmap, alphaMask, w, h)
|
||||
}
|
||||
|
||||
// --- BƯỚC 3: XỬ LÝ LÀM MỜ NỀN PHÂN CẤP THEO THƯỚC ĐO SỐ MŨ (RULER DoF) ---
|
||||
val normalizedSlider = rulerValue / 100.0f
|
||||
val opticalIntensity = Math.pow(normalizedSlider.toDouble(), 2.5).toFloat()
|
||||
val maxRadius = (35 * opticalIntensity).coerceAtLeast(1.0f)
|
||||
|
||||
// Tạo các phân lớp mờ tăng tiến mô phỏng chiều sâu quang học tự nhiên
|
||||
val blurStage1 = boxBlur(baseBgCanvas, (maxRadius * 0.3f).toInt().coerceAtLeast(1))
|
||||
val blurStage2 = boxBlur(baseBgCanvas, (maxRadius * 0.6f).toInt().coerceAtLeast(1))
|
||||
val blurStage3 = boxBlur(baseBgCanvas, maxRadius.toInt().coerceAtLeast(1))
|
||||
baseBgCanvas.recycle()
|
||||
|
||||
// --- BƯỚC 4: ALPHA BLENDING TRỘN HÒA QUYỆN ĐỐI TƯỢNG XUẤT XƯỞNG ---
|
||||
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] // Trọng số mượt mà từ 0.0 (Nền) -> 1.0 (Người)
|
||||
|
||||
// Đo cự ly hình học tính từ rìa biên mặt nạ đối tượng để phân cấp độ mờ hậu cảnh
|
||||
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 công thức màu Alpha Blending
|
||||
val outR = (fgPixel.r * alpha + bgPixel.r * (1.0f - alpha)).toInt()
|
||||
val outG = (fgPixel.g * alpha + bgPixel.g * (1.0f - alpha)).toInt()
|
||||
val outB = (fgPixel.b * alpha + bgPixel.b * (1.0f - alpha)).toInt()
|
||||
|
||||
outputBitmap.setPixel(x, y, Color.rgb(outR, outG, outB))
|
||||
}
|
||||
}
|
||||
|
||||
// Giải phóng bộ đệm để chống tràn RAM thiết bị
|
||||
blurStage1.recycle(); blurStage2.recycle(); blurStage3.recycle()
|
||||
|
||||
return outputBitmap
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 3. Đồng Bộ Luồng Chụp Ảnh Ngầm Không Gây Đơ Camera Preview
|
||||
|
||||
Luồng phần cứng thấu kính camera được tách lập độc lập hoàn toàn. Khi nhấn Shutter, ảnh thô (đã được thấu kính lấy nét vật lý chuẩn xác) được chụp lại, và hệ thống camera lập tức mở lại Preview để sẵn sàng cho lần chụp sau. Toàn bộ cụm xử lý AI phân lớp đa mục tiêu và thay/xóa nền được đẩy xuống luồng ngầm Coroutine chạy song song.
|
||||
|
||||
```kotlin
|
||||
fun onShutterActionTriggered(capturedBitmap: Bitmap, rawMaskBuffer: ByteBuffer) {
|
||||
// 1. Phản hồi giao diện lập tức: Chớp màn hình, đẩy ảnh thumbnail về góc trái
|
||||
triggerFlashAnimationUI()
|
||||
pushThumbnailPreviewToScreen(capturedBitmap)
|
||||
|
||||
// 2. Kích hoạt khôi phục luồng Preview vật lý của thấu kính camera ngay lập tức trên UI Thread
|
||||
runOnUiThread {
|
||||
cameraCaptureSession.setRepeatingRequest(previewRequestBuilder.build(), null, null)
|
||||
}
|
||||
|
||||
// 3. Thực thi ngầm luồng xử lý đồ họa nâng cao (Background Thread)
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val finalPhoto = processRestoredAiBackgroundEngine(
|
||||
capturedBitmap = capturedBitmap,
|
||||
categoryMaskBuffer = rawMaskBuffer,
|
||||
effectMode = currentSelectedModeId, // Nhận từ Flutter UI chuyển xuống
|
||||
bgBitmap = currentLoadedBgBitmap,
|
||||
rulerValue = currentRulerValue
|
||||
)
|
||||
|
||||
// Lưu tệp ảnh hoàn chỉnh sạch sẽ vào thư viện ảnh hệ thống
|
||||
saveProcessedBitmapToGallery(finalPhoto)
|
||||
capturedBitmap.recycle()
|
||||
finalPhoto.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 4. Checklist Xác Minh Triển Khai Thực Địa (Verification Checklist)
|
||||
|
||||
* [ ] **Kiểm thử tính năng Thay nền:** Chọn một ảnh nền phong cảnh (`Nền 1`). Nhấn chụp chân dung. (Yêu cầu: Phần cơ thể bạn phải được bóc tách sạch sẽ, đặt khớp lên hình nền mới, đường viền đầu tóc chuyển tiếp mượt mà, không xuất hiện quầng vệt xám loang lổ).
|
||||
* [ ] **Kiểm thử tính năng Xóa nền phối hợp:** Bật chế độ `Xóa nền`, tăng thước trượt lên `+50`. (Yêu cầu: Hậu cảnh gốc bị làm mờ mịn màng tăng tiến, vùng rìa vai áo không xuất hiện bóng ma Ghost Shadow, và khu vực cơ thể nhân vật đạt độ nét đồng khối tự nhiên nhờ thấu kính quang học).
|
||||
* [ ] **Xác minh tính độc lập của Focus vật lý:** Thử chạm vào khuôn mặt xem camera có tự động điều khiển mô-tơ di chuyển thấu kính để lấy nét mặt không. Thử chạm tiếp vào một vật ở xa xem camera có tự động dời thấu kính lấy nét ra xa không. (Yêu cầu: Tiêu cự camera chuyển động quang học bình thường, không bị can thiệp hay xung đột bởi thuật toán AI đồ họa).
|
||||
@@ -1722,9 +1722,17 @@ class MainActivity : AppCompatActivity() {
|
||||
capturedImage: android.graphics.Bitmap,
|
||||
isStreamMode: Boolean = false
|
||||
): android.graphics.Bitmap {
|
||||
// Chỉ chạy các thuật toán xử lý phân đoạn AI hoặc mờ phần mềm khi bật chế độ AI_BLUR
|
||||
if (currentBackgroundMode != BackgroundMode.AI_BLUR) {
|
||||
return capturedImage
|
||||
if (isStreamMode) {
|
||||
// Khi xem trước (Viewfinder stream): chỉ chạy AI khi thực sự thay nền (AI_BLUR) để tránh lag
|
||||
if (currentBackgroundMode != BackgroundMode.AI_BLUR) {
|
||||
return capturedImage
|
||||
}
|
||||
} else {
|
||||
// Khi chụp lưu ảnh (isStreamMode = false): chạy AI khi chèn hình nền HOẶC khi có xóa phông (blurIntensity > 0)
|
||||
val isBgActive = (selectedBgAssetPath != null) || (blurIntensity > 0f)
|
||||
if (!isBgActive) {
|
||||
return capturedImage
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAiSupported) {
|
||||
|
||||
Reference in New Issue
Block a user