Implement anti-ghosting inpainting for original background blur during photo capture as described in 25_FIX_CAMERA.md
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
Dựa vào hình ảnh thực tế `image.png` bạn cung cấp, có thể thấy rõ lỗi **Ghost Shadow (Bóng ma rế viền)** xung quanh vùng rìa tai, cổ và vai (phần khoanh đỏ).
|
||||
|
||||
### 🔴 Phân tích nguyên nhân lỗi:
|
||||
|
||||
1. **Ghost Shadow:** Xảy ra do hiện tượng "Chảy màu" (Color Bleeding). Khi bạn làm mờ hậu cảnh từ ảnh gốc, các pixel màu da của vai và tai bị nhòe rộng ra nền. Khi đè đối tượng sắc nét lên trên, phần rìa bị nhòe đó lồi ra ngoài tạo thành một lớp bóng mờ bao quanh cơ thể.
|
||||
2. **Viewfinder giật lag:** Do ứng dụng đang ép CPU quét và xử lý thuật toán `boxBlur` đa tầng trực tiếp trên từng khung hình động (Real-time stream) của camera preview ở độ phân giải cao.
|
||||
3. **Độ sâu trường ảnh (DoF) không chính xác:** Do các dải phân lớp khoảng cách (`0.3`, `0.5`, `0.75`) đang được tính tuyến tính cố định, không co giãn tỷ lệ nghịch mượt mà theo tiêu cự quang học khi người dùng trượt Ruler.
|
||||
|
||||
Dưới đây là kế hoạch cải tiến toàn diện mã nguồn để xử lý triệt để các vấn đề trên.
|
||||
|
||||
---
|
||||
|
||||
# 🛠️ KẾ HOẠCH FIX LỖI: TRIỆT TIÊU GHOST SHADOW VÀ TỐI ƯU HÓA FPS VIEWFINDER
|
||||
|
||||
---
|
||||
|
||||
## 📐 1. Thuật Toán Triệt Tiêu Ghost Shadow Bằng "Giãn Cơ Thể Ẩn" (Inpainting Dilate)
|
||||
|
||||
Để loại bỏ hoàn toàn bóng ma quanh vai và tai trong `image.png`, trước khi làm mờ hậu cảnh (`blurStage1, 2, 3`), chúng ta phải xóa bỏ thông tin màu sắc của đối tượng ra khỏi nền bằng cách **giãn rộng mặt nạ nền và lấp đầy vùng biên** (Background Inpainting / Edge Extension).
|
||||
|
||||
### 💻 Cải tiến Logic vòng lặp trong `MainActivity.kt`:
|
||||
|
||||
```kotlin
|
||||
fun processAntiGhostingBokeh(
|
||||
capturedBitmap: Bitmap,
|
||||
alphaMask: FloatArray, // Mảng alpha mượt từ MediaPipe (0.0 -> 1.0)
|
||||
midasDepthMap: Array<FloatArray>,
|
||||
blurIntensity: Float
|
||||
): Bitmap {
|
||||
val w = capturedBitmap.width
|
||||
val h = capturedBitmap.height
|
||||
|
||||
// BƯỚC 1: TẠO ẢNH HẬU CẢNH SẠCH (CLEAN BACKGROUND)
|
||||
// Loại bỏ hoàn toàn pixel người trước khi đem đi Blur để tránh chảy màu da ra nền
|
||||
val cleanBgBitmap = 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]
|
||||
|
||||
if (alpha > 0.01f) {
|
||||
// Nếu là người: Thay bằng các pixel xung quanh viền nền (Edge Padding)
|
||||
// để khi blur không bị lôi màu da ra ngoài
|
||||
cleanBgBitmap.setPixel(x, y, getNearestBackgroundPixel(capturedBitmap, alphaMask, x, y, w, h))
|
||||
} else {
|
||||
cleanBgBitmap.setPixel(x, y, capturedBitmap.getPixel(x, y))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BƯỚC 2: TẠO CÁC LỚP BLUR TỪ NỀN SẠCH (Không còn chứa vệt màu da cũ)
|
||||
val blurStage1 = boxBlur(cleanBgBitmap, (5 * blurIntensity).toInt().coerceAtLeast(1))
|
||||
val blurStage2 = boxBlur(cleanBgBitmap, (13 * blurIntensity).toInt().coerceAtLeast(1))
|
||||
val blurStage3 = boxBlur(cleanBgBitmap, (25 * blurIntensity).toInt().coerceAtLeast(1))
|
||||
|
||||
cleanBgBitmap.recycle() // Giải phóng ngay
|
||||
|
||||
// BƯỚC 3: ĐỒNG BỘ ĐỘ SÂU TRƯỜNG ẢNH TRƠN TRU (EXPONENTIAL DOF)
|
||||
// Thay đổi công thức phân phối khoảng cách dựa trên Ruler để DoF chính xác hơn
|
||||
val outputBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
|
||||
|
||||
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]
|
||||
|
||||
val maskX = (x * 256 / w).coerceIn(0, 255)
|
||||
val currentDepth = depthMap[maskY][maskX]
|
||||
val relativeDistance = (currentDepth - 0.25f).coerceAtLeast(0f)
|
||||
|
||||
// Áp dụng thuật toán chọn lớp mờ mịn chuyển tiếp phi tuyến tính
|
||||
val bgPixel = when {
|
||||
relativeDistance < (0.15f * blurIntensity) -> blurStage1.getPixel(x, y)
|
||||
relativeDistance < (0.4f * blurIntensity) -> blurStage2.getPixel(x, y)
|
||||
else -> blurStage3.getPixel(x, y)
|
||||
}
|
||||
|
||||
val fgPixel = capturedBitmap.getPixel(x, y)
|
||||
|
||||
// Alpha Blending cuối cùng
|
||||
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ộ nhớ các stage
|
||||
blurStage1.recycle()
|
||||
blurStage2.recycle()
|
||||
blurStage3.recycle()
|
||||
|
||||
return outputBitmap
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 2. Giải Pháp Triệt Tiêu Lag Khi Xem Viewfinder (Live Preview Optimization)
|
||||
|
||||
Để đạt được tốc độ **60 FPS** trơn tru khi trượt Ruler trên giao diện kính ngắm thực tế, chúng ta áp dụng cơ chế **Hạ Cấp Kính Ngắm Chủ Động (Downscaled Proxy Blur)**:
|
||||
|
||||
* **Tuyệt đối không chạy Dual-AI (MediaPipe + MiDaS) trên luồng Live Viewfinder.**
|
||||
* Khi người dùng đang ở giao diện ngắm và kéo thước trượt Ruler, ứng dụng sẽ chỉ lấy một luồng ảnh Preview có độ phân giải siêu thấp (Ví dụ: `320x240` pixel) để tính toán mờ dưới lớp render đồ họa, hoặc sử dụng Shader phần cứng (OpenGL/Skia) làm mờ toàn bộ kính ngắm trừ một vùng Oval trung tâm để mô phỏng (Mô phỏng hiệu ứng thị giác tức thì).
|
||||
* Chỉ khi người dùng buông tay khỏi thanh Ruler hoặc nhấn nút **Shutter**, hệ thống mới gom toàn bộ dữ liệu độ phân giải cao cấu hình chuẩn ở Mục 1 thảy vào **Background Coroutine Process** để ép mảnh sắc nét xuất ra tệp JPG.
|
||||
|
||||
---
|
||||
|
||||
## 📅 3. Checklist Xác Minh Sau Khi Sửa Lỗi (Verification Checklist)
|
||||
|
||||
* [ ] **Kiểm thử Ghost Shadow:** Chụp một bức ảnh cận cảnh vai giống góc độ của `image.png`. (Yêu cầu: Phần viền bao quanh áo ba lỗ trắng và da vai không còn các vệt mờ xám mờ lồi ra, ranh giới cắt lồng vào nền mới phải sạch sẽ hoàn toàn).
|
||||
* [ ] **Kiểm thử độ nhạy Viewfinder:** Kéo thanh trượt tăng giảm độ nhòe qua lại liên tục bằng cử chỉ vuốt trên ảnh xem kính ngắm camera phản hồi lập tức không. (Yêu cầu: Giao diện trượt mượt mà, FPS không được rớt dưới 25 khung hình/giây).
|
||||
* [ ] **Kiểm thử phân phối DoF:** Đặt các vật thể ở các cự ly khác nhau (1m, 3m, 5m), kéo từ mức blur thấp lên cao xem các vật ở xa vô cực có đạt độ nhòe tuyến tính tỷ lệ đúng quang học hay không.
|
||||
Reference in New Issue
Block a user