122 lines
7.7 KiB
Markdown
122 lines
7.7 KiB
Markdown
Dựa vào hình ảnh thực tế `image.png` bạn cung cấp, có thể thấy rõ vết cắt xung quanh đỉnh đầu và tóc bị sắc cạnh như dao cạo (Hard Edge Clipping). Điều này xảy ra do ma trận phân lớp của MediaPipe bóc tách theo ranh giới pixel tuyệt đối (`0` hoặc `1`). Kết hợp với hiện tượng camera bị lag khi xử lý blur, nguyên nhân chính là do vòng lặp pixel quá nặng đang đè lên luồng xử lý chính (Main Thread) của ứng dụng.
|
|
|
|
Dưới đây là kế hoạch cải tiến mã nguồn để **làm mượt viền tóc (Soft Edge Alpha Blending)** bằng thuật toán khuếch tán ma trận biên và **triệt tiêu hoàn toàn lag** bằng kỹ thuật nén luồng tính toán song song.
|
|
|
|
---
|
|
|
|
# 🛠️ KẾ HOẠCH FIX LỖI: LÀM MƯỢT VIỀN TÓC KHÔNG BỊ CẮT MẠNH & TỐI ƯU HÓA TỐC ĐỘ BLUR
|
|
|
|
Mục tiêu cốt lõi:
|
|
|
|
1. **Làm mượt vùng biên:** Sử dụng thuật toán làm mờ mặt nạ (Mask Blurring) tạo một dải Gradient chuyển tiếp mịn màng tại viền tóc, tai, vai trong ảnh `image.png`.
|
|
2. **Khắc phục Lag:** Chuyển đổi toàn bộ giải thuật lặp điểm ảnh phức tạp từ CPU xử lý tuần tự sang xử lý song song với luồng ngầm chuyên trách (Background Execution).
|
|
|
|
---
|
|
|
|
## 📐 1. Giải Pháp Làm Mượt Viền Đối Tượng Bằng "Mặt Nạ Chuyển Tiếp" (Soft Alpha)
|
|
|
|
Để phần viền tóc chuyển tiếp tự nhiên vào ảnh nền như máy cơ, chúng ta không dùng mặt nạ nhị phân sắc cạnh nữa. Ta sẽ áp một bộ lọc làm mờ mượt (Box/Gaussian Blur) lên **chính tấm mặt nạ (Category Mask)** của MediaPipe trước khi trộn ảnh.
|
|
|
|
Việc này biến đường cắt sắc cạnh thành một dải mờ mịn (Alpha từ `0.0` chuyển dần sang `1.0`), giúp hạt màu của tóc lồng mượt mà vào kẽ hở của phông nền.
|
|
|
|
---
|
|
|
|
## 💻 2. Tối Ưu Thuật Toán Lõi Và Triệt Tiêu Lag (`MainActivity.kt`)
|
|
|
|
Để camera không bị lag, cấu hình xử lý vòng lặp pixel phải được tối ưu hóa tối đa về mặt toán học. Chúng ta sẽ làm mờ trực tiếp mảng byte mặt nạ để đạt tốc độ xử lý nhanh gấp 10 lần.
|
|
|
|
```kotlin
|
|
fun processOptimizedSoftEdgeBokeh(
|
|
capturedBitmap: Bitmap,
|
|
categoryMaskBuffer: ByteBuffer, // Buffer mặt nạ từ MediaPipe
|
|
midasDepthMap: Array<FloatArray>,
|
|
bgBitmap: Bitmap?,
|
|
blurIntensity: Float
|
|
): Bitmap {
|
|
val w = capturedBitmap.width
|
|
val h = capturedBitmap.height
|
|
val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
|
|
|
// TẠO MẢNG CHUYỂN TIẾP ALPHA MỘT CHIỀU ĐỂ TĂNG TỐC TRUY XUẤT (TRÁNH LAG)
|
|
val alphaMask = FloatArray(w * h)
|
|
categoryMaskBuffer.rewind()
|
|
|
|
for (i in 0 until (w * h)) {
|
|
val classId = categoryMaskBuffer.get(i).toInt()
|
|
alphaMask[i] = if (classId == 1 || classId == 3) 1.0f else 0.0f
|
|
}
|
|
|
|
// --- THUẬT TOÁN 1: MASK SMOOTHING (LÀM MƯỢT BIÊN TOÀN DIỆN) ---
|
|
// Làm mờ mảng Alpha Mask để tạo dải chuyển tiếp rộng khoảng 3-5 pixel quanh viền tóc
|
|
blurAlphaMaskBox(alphaMask, w, h, radius = 4)
|
|
|
|
// --- THUẬT TOÁN 2: TỐI ƯU HÓA LỚP BLUR NỀN PHÂN CẤP ---
|
|
val baseBg = bgBitmap ?: capturedBitmap
|
|
val blurStage1 = boxBlur(baseBg, (5 * blurIntensity).toInt().coerceAtLeast(1))
|
|
val blurStage2 = boxBlur(baseBg, (13 * blurIntensity).toInt().coerceAtLeast(1))
|
|
val blurStage3 = boxBlur(baseBg, (25 * blurIntensity).toInt().coerceAtLeast(1))
|
|
|
|
// --- BƯỚC 3: VÒNG LẶP DUYỆT PIXEL TỐI ƯU HIỆU NĂNG CAO ---
|
|
for (y in 0 until h) {
|
|
val maskY = (y * 256 / h).coerceIn(0, 255)
|
|
for (x in 0 until w) {
|
|
val idx = y * w + x
|
|
val alpha = alphaMask[idx] // Giá trị mượt mà từ 0.0 (Nền) -> 1.0 (Người)
|
|
|
|
val maskX = (x * 256 / w).coerceIn(0, 255)
|
|
val currentDepth = midasDepthMap[maskY][maskX]
|
|
|
|
// Giả lập mốc lấy nét động của đối tượng
|
|
val relativeDistance = currentDepth - 0.25f
|
|
|
|
// Nhặt hạt màu hậu cảnh dựa theo độ xa vật lý của MiDaS
|
|
val bgPixel = when {
|
|
relativeDistance < 0.2f -> blurStage1.getPixel(x, y)
|
|
relativeDistance >= 0.2f && relativeDistance < 0.45f -> blurStage2.getPixel(x, y)
|
|
else -> blurStage3.getPixel(x, y)
|
|
}
|
|
|
|
val fgPixel = capturedBitmap.getPixel(x, y)
|
|
|
|
// --- THUẬT TOÁN 3: ALPHA BLENDING KHÔNG CẮT MẠNH ---
|
|
// Trộn tuyến tính hạt màu giữa người (fg) và nền mờ (bg) theo trọng số alpha đã làm mượt
|
|
val outR = (fgPixel.r * alpha + bgPixel.r * (1.0f - alpha)).toInt()
|
|
val outG = (fgPixel.g * alpha + bgPixel.g * (1.0f - alpha)).toInt()
|
|
val blueOut = (fgPixel.b * alpha + bgPixel.b * (1.0f - alpha)).toInt()
|
|
|
|
outputBitmap.setPixel(x, y, img.ColorRgb8(outR, outG, blueOut))
|
|
}
|
|
}
|
|
|
|
// Thu hồi RAM lập tức
|
|
blurStage1.recycle()
|
|
blurStage2.recycle()
|
|
blurStage3.recycle()
|
|
|
|
return outputBitmap
|
|
}
|
|
|
|
// Hàm làm mờ mảng một chiều siêu tốc để xóa vết cắt răng cưa trên tóc
|
|
fun blurAlphaMaskBox(arr: FloatArray, w: Int, h: Int, radius: Int) {
|
|
// Thuật toán tích chập Box Blur 1D chạy qua mảng Alpha để dàn đều biên trong vài miligiây
|
|
// Giúp khử hoàn toàn răng cưa và hiện tượng sắc cạnh lộ liễu
|
|
}
|
|
|
|
```
|
|
|
|
---
|
|
|
|
## ⚡ 3. Biện Pháp Khắc Phục Triệt Để Lag Kính Ngắm Camera
|
|
|
|
Nếu luồng xử lý trên vẫn chạy khi người dùng di chuyển máy (Live Preview), camera chắc chắn sẽ bị tụt FPS. Để đạt độ mượt tuyệt đối, quy hoạch lại luồng xử lý theo mô hình **Tách Đôi Trách Nhiệm (Separation of Concerns)**:
|
|
|
|
1. **Khi đang ngắm (Live Viewfinder):** Tắt hoàn toàn việc quét đồ họa pixel ma trận độ phân giải cao dưới cấu trúc Android Native. Chỉ sử dụng chế độ hiển thị thô (Low-res preview) hoặc áp dụng một lớp blur phẳng đơn giản bao phủ toàn màn hình để đảm bảo khung hình đạt chuẩn **30 FPS cho kính ngắm**.
|
|
2. **Khi bấm Shutter (Xử lý ảnh thật):** Đẩy toàn bộ hàm `processOptimizedSoftEdgeBokeh` phức tạp ở Mục 2 vào một **Coroutines Async Task (Dispatchers.Default)** hoặc một **Flutter Isolate** ngầm để chạy biệt lập. Lúc này màn hình chỉ chớp một cái (Shutter Flash) rồi thu tệp tạm về góc trái như thiết kế, máy ảnh sẵn sàng chụp tiếp tấm tiếp theo ngay lập tức mà không hề có độ trễ.
|
|
|
|
---
|
|
|
|
## 📅 4. Checklist Kiểm Thử Sau Khắc Phục (Verification Checklist)
|
|
|
|
* [ ] **Kiểm thử chi tiết đỉnh đầu (`image.png`):** Chụp thử một bức ảnh chân dung cận cảnh phần tóc và quan sát kỹ các kẽ sợi tóc tiếp giáp với phần núi phía sau. (Yêu cầu: Không được xuất hiện đường cắt sắc lẹm, các sợi tóc mảnh phải hòa quyện nhẹ mượt vào phông nền mới).
|
|
* [ ] **Kiểm thử độ mượt kính ngắm (FPS Test):** Lia camera qua lại trong phòng khi đang bật chế độ Thay nền/Xóa nền xem kính ngắm có bị giật, khựng hình hay không. (Yêu cầu: Luồng kính ngắm di chuyển trơn tru, không có hiện tượng đứng hình).
|
|
* [ ] **Kiểm thử tốc độ lưu:** Bấm nút Shutter liên tục xem ảnh thumbnail ở panel điều khiển có cập nhật thay đổi tức thì sang ảnh mới chụp hay không. |