Dưới đây là kế hoạch Markdown tổng hợp toàn diện, kết hợp từ hạ tầng giao diện, kiến trúc luồng dữ liệu cho đến thuật toán xử lý pixel nâng cao để triển khai tính năng **Chân dung xóa phông (Portrait Blur)** và **Thay thế phông nền AI (Background Replacement)**, đảm bảo loại bỏ hoàn toàn hiện tượng răng cưa và lộ vết cắt ở các chi tiết phức tạp như tóc của người hoặc vật. --- # 📊 KẾ HOẠCH TOÀN DIỆN: TÍNH NĂNG CHÂN DUNG XÓA/THAY NỀN VÀ KHỬ LỖI CẮT VIỀN TÓC CHUYÊN NGHIỆP Mục tiêu cốt lõi: 1. Cung cấp bộ công cụ tối giản ở thanh trượt Ruler dưới đáy màn hình để chọn chế độ nền. 2. Tích hợp mô hình AI On-Device để bóc tách thực thể người/vật. 3. Sử dụng các bộ toán học xử lý pixel chuyên sâu nhằm xử lý vùng biên, khử lem màu nền cũ và hòa trộn hạt màu mềm mại. 4. Tích hợp mượt mà vào chuỗi xử lý (Chụp ➔ Can thiệp AI ➔ Tinh chỉnh màu Sliders ➔ Ép khung Instax ➔ Lưu Gallery). --- ## 📦 1. Bổ Sung & Cấu Hình Thư Viện (`pubspec.yaml`) Thêm bộ thư viện AI phân đoạn hình ảnh của Google và công cụ ma trận điểm ảnh vào tệp cấu hình: ```yaml dependencies: flutter: sdk: flutter camera: ^0.10.6 google_mlkit_selfie_segmentation: ^0.7.0 # AI tách nền chạy offline image: ^4.3.0 # Xử lý pixel, Blur và Blend màu gal: ^2.4.0 # Lưu ảnh trực tiếp vào thư viện máy flutter: assets: - assets/frames/instaxframe.png - assets/backgrounds/background-01.png # Ảnh nền phong cảnh mẫu - assets/backgrounds/background-02.png - assets/backgrounds/background-03.png ``` --- ## 📐 2. Thiết Kế Giao Diện Điều Khiển (UI/UX Matrix) Tại Tầng 2 của Editor (Thanh cuộn ngang 14 nút thuộc tính), ta bổ sung 2 công cụ điều khiển: * **Icon Portrait (Xóa phông):** Khi chọn, thước đo Ruler sẽ cho phép tăng giảm độ mịn của nền từ `0` đến `100` (tương ứng giá trị blur từ `0.0` đến `1.0`). * **Icon BG Replace (Thay nền):** Khi nhấn chọn, một dải menu con cuộn ngang sẽ hiển thị danh sách các ảnh nền mẫu để người dùng áp dụng. ### Quản lý trạng thái hệ thống: ```dart enum BackgroundMode { none, blur, replace } BackgroundMode _selectedBgMode = BackgroundMode.none; double _blurIntensity = 0.0; // Độ mờ phông nền (0.0 -> 1.0) String? _selectedBgAssetPath; // Đường dẫn ảnh nền mới được chọn bool _isFrameApplied = false; // Trạng thái ép khung Instax ``` --- ## 💻 3. Thuật Toán Lõi: Xử Lý Viền Tóc & Chống Răng Cưa (Core Engine) Để tóc của nhân vật hoặc các chi tiết nhỏ của vật thể không bị lộ vết cắt thô bạo, thuật toán áp dụng 3 kỹ thuật song song: **Feathering (Làm mềm biên)**, **Color Decontamination (Khử lem màu nền cũ)** và **Alpha Blending (Hòa trộn ma trận màu)**. Hàm xử lý dưới đây sẽ chạy độc lập để trả ra một bức ảnh sạch tuyệt đối: ```dart import 'dart:io'; import 'dart:math'; import 'package:flutter/services.dart'; import 'package:image/image.dart' as img; import 'package:google_mlkit_selfie_segmentation/google_mlkit_selfie_segmentation.dart'; Future _processAdvancedBackgroundAi({ required img.Image capturedImage, required String rawPhotoPath, }) async { // 1. Khởi chạy AI để trích xuất Mặt nạ phân đoạn (Segmentation Mask) final segmenter = SelfieSegmenter(mode: SegmenterMode.single, enableRawSizeMask: true); final inputImage = InputImage.fromFilePath(rawPhotoPath); final mask = await segmenter.processImage(inputImage); if (mask == null) return capturedImage; final w = mask.width; final h = mask.height; final confidences = mask.confidences; // Mảng chứa độ tin cậy thực thể (0.0 -> 1.0) // Đưa ảnh gốc về cùng kích thước ma trận AI để tính toán đồng bộ từng điểm ảnh img.Image fg = img.copyResize(capturedImage, width: w, height: h); img.Image bg = img.Image(width: w, height: h); img.Image output = img.Image(width: w, height: h); // 2. Chuẩn bị phông nền phụ thuộc vào chế độ người dùng chọn if (_selectedBgMode == BackgroundMode.blur) { // CHẾ ĐỘ XÓA PHÔNG: Nhân bản ảnh gốc và làm mịn bằng bộ lọc Gaussian Blur bán kính lớn bg = img.copyResize(fg, width: w, height: h); int blurRadius = (_blurIntensity * 25).toInt().clamp(1, 25); bg = img.gaussianBlur(bg, radius: blurRadius); } else if (_selectedBgMode == BackgroundMode.replace && _selectedBgAssetPath != null) { // CHẾ ĐỘ THAY NỀN: Nạp ảnh nền mới và co giãn cho khớp với ảnh chụp final ByteData bgData = await rootBundle.load(_selectedBgAssetPath!); img.Image? decodedBg = img.decodeImage(bgData.buffer.asUint8List()); if (decodedBg != null) { bg = img.copyResize(decodedBg, width: w, height: h); } } else { segmenter.close(); return capturedImage; // Không bật tính năng -> Trả về ảnh gốc } // 3. VÒNG LẶP DUYỆT PIXEL: KHỬ RĂNG CƯA & TRỘN MÀU MẢNH TÓC for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { final int idx = y * w + x; double alpha = confidences[idx]; // KHÁI NIỆM VÙNG BIÊN: Nơi có điểm số AI mập mờ từ 0.1 (Nền) đến 0.9 (Người) // --- THUẬT TOÁN 1: FEATHERING (LÀM MỀM BIÊN MẶT NẠ) --- if (alpha > 0.1 && alpha < 0.9) { alpha = smoothStep(0.1, 0.9, alpha); // Nội suy giúp viền tóc mềm mại hơn } final fgPixel = fg.getPixel(x, y); final bgPixel = bg.getPixel(x, y); int fgR = fgPixel.r.toInt(); int fgG = fgPixel.g.toInt(); int fgB = fgPixel.b.toInt(); final int bgR = bgPixel.r.toInt(); final int bgG = bgPixel.g.toInt(); final int bgB = bgPixel.b.toInt(); // --- THUẬT TOÁN 2: COLOR DECONTAMINATION (KHỬ LEM MÀU NỀN CŨ) --- // Triệt tiêu ánh màu của phòng/nền cũ dính vào các kẽ tóc mảnh ở vùng viền if (alpha > 0.1 && alpha < 0.85) { final double grayIntensity = (fgR + fgG + fgB) / 3.0; // Hút bớt độ rực màu bị nhiễu của môi trường cũ, ép sợi tóc tiến về màu trung tính fgR = (fgR * alpha + grayIntensity * (1.0 - alpha)).toInt(); fgG = (fgG * alpha + grayIntensity * (1.0 - alpha)).toInt(); fgB = (fgB * alpha + grayIntensity * (1.0 - alpha)).toInt(); } // --- THUẬT TOÁN 3: ALPHA BLENDING (HÒA TRỘN MA TRẬN PHÂN LỚP) --- // Lồng hạt màu của sợi tóc vào kẽ hở của phông nền mới dựa trên tỷ lệ Alpha chuyển tiếp final int outR = (fgR * alpha + bgR * (1.0 - alpha)).toInt(); final int outG = (fgG * alpha + bgG * (1.0 - alpha)).toInt(); final int outB = (fgB * alpha + bgB * (1.0 - alpha)).toInt(); output.setPixel(x, y, img.ColorRgb8(outR, outG, outB)); } } segmenter.close(); return output; } // Hàm bổ trợ tính toán Smoothstep cho dải biên chuyển tiếp đều đặn double smoothStep(double edge0, double edge1, double x) { final double t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0); return t * t * (3.0 - 2.0 * t); } ``` --- ## 🔄 4. Đồng Bộ Vào Chuỗi Pipeline Xử Lý Đồ Họa Khi Chụp Ảnh Chuỗi sự kiện bất đồng bộ khi người dùng nhấn nút Shutter (được ẩn toàn bộ các thông báo `"Tách!..."` thừa thải) sẽ tuân theo cấu trúc tuyến tính khép kín sau: ```dart Future captureAndProcessImagePipeline() async { try { // BƯỚC 1: Chụp ảnh thô từ phần cứng camera final XFile rawPhoto = await _cameraController.takePicture(); // BƯỚC 2: Đọc bytes ảnh vào bộ nhớ hệ thống final Uint8List rawBytes = await rawPhoto.readAsBytes(); img.Image? workingImage = img.decodeImage(rawBytes); if (workingImage == null) return; // BƯỚC 3: CAN THIỆP AI - Xóa/Thay nền khử lỗi viền tóc (Như thuật toán ở Mục 3) if (_selectedBgMode != BackgroundMode.none) { workingImage = await _processAdvancedBackgroundAi( capturedImage: workingImage, rawPhotoPath: rawPhoto.path, ); } // BƯỚC 4: ĐỒNG BỘ SLIDERS - Áp dụng cấu hình bộ lọc màu (JSON Cũ) // Thực hiện sau khi tách nền để màu của người và nền hòa quyện đồng nhất workingImage = _applyUserColorSliders(workingImage); // BƯỚC 5: MERGE FRAME - Ép mảnh khung ảnh Instax PNG trong suốt lên trên cùng if (_isFrameApplied) { final ByteData frameData = await rootBundle.load('assets/frames/instaxframe.png'); final img.Image? instaxFrame = img.decodePng(frameData.buffer.asUint8List()); if (instaxFrame != null) { workingImage = img.copyResize(workingImage, width: instaxFrame.width, height: instaxFrame.height); img.compositeImage(workingImage, instaxFrame); // Ép đè khung cố định lên ảnh } } // BƯỚC 6: XUẤT FILE TẠM & GHI VÀO GALLERY MÁY final Uint8List finalBytes = Uint8List.fromList(img.encodeJpg(workingImage, quality: 95)); final String tempPath = '${Directory.systemTemp.path}/photobooth_${DateTime.now().millisecondsSinceEpoch}.jpg'; final File tempFile = File(tempPath); await tempFile.writeAsBytes(finalBytes); await Gal.putImage(tempFile.path); // Lưu trực tiếp vào thư viện ảnh hệ thống await tempFile.delete(); // Giải phóng tệp tạm thời _showSuccessOverlay(); // Hiển thị giao diện báo lưu thành công nhẹ nhàng } catch (e) { print("Lỗi hệ thống xử lý chuỗi ảnh: $e"); } } ``` --- ## 📅 5. Kế Hoạch Triển Khai Thực Địa (Checklist) * [ ] **Đưa luồng xử lý vào Flutter Isolate:** Do vòng lặp pixel chạy qua hàng triệu điểm ảnh ở Bước 3 và Bước 5 rất nặng, bắt buộc phải dùng lệnh `compute()` hoặc `Isolate.run()` để đẩy toàn bộ tiến trình này xuống nhân CPU chạy ngầm, tránh gây đứng đứng hình giao diện kính ngắm. * [ ] **Cấp quyền hệ thống đầy đủ:** Đảm bảo cấu hình các thẻ xin quyền lưu trữ vào thư viện máy đầy đủ trên cả hai nền tảng Android và iOS để `Gal` thực thi lệnh lưu ảnh an toàn. * [ ] **Kiểm thử biên độ nhiễu màu:** Thực hiện chụp thử nghiệm các mẫu vật phức tạp (như gấu bông nhiều lông mịn hoặc nhân vật tóc xù) trước các hậu cảnh phức tạp để căn chỉnh thông số `smoothStep` đạt độ thẩm mỹ cao nhất.