231 lines
13 KiB
Markdown
231 lines
13 KiB
Markdown
# Phân Tích Kỹ Thuật & Thuật Toán Clarity Chống Lỗi Viền "Stroke" (Halo-Free Local Contrast)
|
||
|
||
---
|
||
|
||
## 1. Hiện Tượng Viền "Stroke" (Halo Artifact) Là Gì?
|
||
|
||
Khi bạn kéo thanh trượt **Clarity** lên cao ($+50$ đến $+100$) trên một thuật toán chưa hoàn thiện:
|
||
* Quanh tán cây, mép ngọn núi, đường chân trời hoặc viền người nổi lên một **đường viền trắng sáng rực** hoặc **đường viền đen dày** như một nét vẽ viền (Stroke/Outline).
|
||
* Ảnh trông bị gắt, nhân tạo và có cảm giác "bẩn" ở các vùng biên tương phản cao.
|
||
|
||
```
|
||
MẶT CẮT BIÊN ĐỘ ÁNH SÁNG QUA ĐƯỜNG BIÊN ĐỘ TƯƠNG PHẢN (Vách đá vs Bầu trời):
|
||
|
||
Độ Sáng (Luminance)
|
||
▲
|
||
│ [Overshoot: Viền trắng sáng rực]
|
||
│ ┌───┐
|
||
1.0 ├───────────────────────────┘ │ (Bầu trời sáng)
|
||
│ │
|
||
│ ┌───────────┘
|
||
0.2 ├──────┐ │ (Vách đá tối)
|
||
│ └───┐ │
|
||
│ └────────┘
|
||
0.0 ┴─────────────────────────────────────────► Không gian (X)
|
||
[Undershoot: Viền đen sẫm]
|
||
```
|
||
|
||
### Nguyên nhân toán học cốt lõi: Dùng sai bộ lọc làm mờ (Linear Gaussian / Box Filter)
|
||
Thuật toán tăng nét truyền thống (Unsharp Masking - USM) phân rã ảnh theo công thức:
|
||
|
||
$$\text{Detail} = I - \text{GaussianBlur}(I, \sigma)$$
|
||
|
||
$$I_{\text{out}} = I + k \times \text{Detail}$$
|
||
|
||
1. **Bộ lọc Gaussian làm mờ "mù quáng":** Nó tính trung bình trọng số của tất cả các pixel lân cận mà **không quan tâm** điểm ảnh đó thuộc về vách đá đen hay bầu trời sáng.
|
||
2. **Hệ quả tại đường biên (Step Edge):**
|
||
* Về phía bầu trời: Gaussian kéo cả giá trị tối của vách đá vào $\implies$ Giá trị mờ thấp hơn thực tế $\implies$ Khi lấy $I - \text{Blur}$ sẽ sinh ra một giá trị dương rất lớn $\implies$ **Tạo viền trắng rực (Overshoot)**.
|
||
* Về phía vách đá: Gaussian kéo giá trị sáng của bầu trời vào $\implies$ Giá trị mờ cao hơn thực tế $\implies$ Phép trừ tạo ra giá trị âm sâu $\implies$ **Tạo viền đen đậm (Undershoot)**.
|
||
3. Vì Clarity tác động lên dải bán kính trung bình (Meso-radius: $15\text{px} - 40\text{px}$), vùng giao thoa này rộng ra cả chục pixel, tạo thành một "vệt nét viền" dày cộp mắt thường nhìn thấy rõ rệt.
|
||
|
||
---
|
||
|
||
## 2. Phân Định Rõ: Sharpening vs Clarity vs Contrast
|
||
|
||
Nhiều nhà phát triển thường nhầm lẫn giữa ba khái niệm xử lý tương phản:
|
||
|
||
```
|
||
[Micro-Frequency] ───────────── [Meso-Frequency] ───────────── [Macro-Frequency]
|
||
Bán kính: 1px - 2px Bán kính: 15px - 40px Bán kính: Toàn bức ảnh
|
||
◄── SHARPENING ──► ◄──── CLARITY ────► ◄── CONTRAST (S-Curve) ──►
|
||
Tăng chi tiết sợi tóc, Tăng khối 3D, độ nổi rãnh Đẩy toàn bộ ảnh: Trắng sáng
|
||
vân vải, gai lá. đá, mây trời dải trung tính. hơn, Đen tối hơn (Dễ cháy/bết).
|
||
```
|
||
|
||
| Tiêu chí so sánh | Sharpening (Độ sắc nét) | Contrast (Độ tương phản) | Clarity (Độ trong khối) |
|
||
| :--- | :--- | :--- | :--- |
|
||
| **Vùng tác động** | Chi tiết tần số rất cao (Cạnh $1\text{px} - 2\text{px}$). | Toàn cục (Global Tone Curve). | Chi tiết tần số trung bình (Meso-Contrast). |
|
||
| **Mặt nạ kiểm soát** | Không giới hạn dải sáng. | Đẩy cong cả dải Highlight & Shadow. | **Chỉ tác động vào dải Midtones ($20\% - 80\%$).** |
|
||
| **Nguy cơ lỗi** | Nhiễu hạt (Noise/Grain). | Cháy sáng (Clipping) & bết đen. | Viền sáng/tối nhân tạo (Haloing/Stroke). |
|
||
|
||
---
|
||
|
||
## 3. Bí Quyết Của Lightroom: Triệt Tiêu Viền Stroke Như Thế Nào?
|
||
|
||
Từ phiên bản Process Version 2012 (PV2012) trở đi, Adobe đã thay thế hoàn toàn thuật toán Unsharp Masking trong thanh trượt Clarity bằng **Bộ lọc bảo toàn góc cạnh (Edge-Preserving Filter)** kết hợp mô hình **Laplacian Pyramid đa tỉ lệ**:
|
||
|
||
```
|
||
[Ảnh Gốc Input I]
|
||
│
|
||
▼
|
||
[Bộ Lọc Bảo Toàn Cạnh: Guided Filter / Bilateral Filter]
|
||
│
|
||
┌──────────────────┴──────────────────┐
|
||
▼ ▼
|
||
[Base Layer: Khối nền] [Detail Layer: Vân chi tiết]
|
||
(Mịn bề mặt, nhưng GIỮ NGUYÊN Detail = I - Base
|
||
độ sắc của các góc cạnh cứng) (Chỉ chứa vân nổi lăn tăn,
|
||
│ HOÀN TOÀN KHÔNG CHỨA CẠNH CỨNG)
|
||
│ │
|
||
│ ▼
|
||
│ [Nhân Hệ Số Clarity + Mask]
|
||
│ Detail' = Detail * (1 + k * MidMask)
|
||
│ │
|
||
└──────────────────┬──────────────────┘
|
||
▼
|
||
[Reconstructed: Base + Detail']
|
||
│
|
||
▼
|
||
[Kết Quả: Khối Nổi 3D Sâu,
|
||
100% KHÔNG CÓ VIỀN HALO/STROKE]
|
||
```
|
||
|
||
### 3 Trụ Cột Kỹ Thuật Của Lightroom
|
||
|
||
#### A. Khái niệm Edge-Preserving Smoothing (Làm mờ không băng qua cạnh)
|
||
* Bộ lọc (như *Guided Filter* hoặc *Domain Transform*) khi quét qua tán cây trên nền trời sẽ nhận diện: chênh lệch sáng giữa cây và trời là một "bức tường" (Hard Edge).
|
||
* Nó làm mờ tán cây nội bộ, làm mờ bầu trời nội bộ, nhưng **dừng lại ở chân tường**, không trộn lẫn điểm ảnh giữa hai bên.
|
||
* Vì `Base Layer` giữ nguyên vẹn cạnh cứng, nên `Detail Layer` ($I - \text{Base}$) tại đường biên vách núi/bầu trời sẽ có giá trị bằng $0$.
|
||
* Khi bạn tăng Clarity lên $+100$, hệ số khuếch đại nhân với $0$ vẫn là $0 \implies$ **Hoàn toàn triệt tiêu viền Halo**.
|
||
|
||
#### B. Khóa hai đầu (Midtone Luminance Weighting)
|
||
Clarity không can thiệp vào điểm đen tuyệt đối hay vùng mây trắng tinh:
|
||
|
||
$$M(L) = \sin(\pi \times L) \quad \text{hoặc} \quad M(L) = 4.0 \times L \times (1.0 - L)$$
|
||
|
||
* Khi $L = 0.0$ (Black point) $\implies M = 0$.
|
||
* Khi $L = 1.0$ (White point) $\implies M = 0$.
|
||
* Khi $L = 0.5$ (Midtone) $\implies M = 1.0$ (Tác động cực đại).
|
||
|
||
#### C. Xử lý trên không gian tri giác (Perceptual Luminance)
|
||
Clarity chỉ thao tác trên thành phần năng lượng ánh sáng (Luminance), sau đó co giãn đều cả 3 kênh RGB theo tỉ lệ. Nếu cộng thẳng vào từng kênh RGB độc lập, màu sắc sẽ bị biến dạng (chuyển sang màu vàng neon hoặc xanh bùn).
|
||
|
||
---
|
||
|
||
## 4. Mô Hình Toán Học Triển Khai Cho Web UI
|
||
|
||
Để đạt tốc độ $60\text{ fps}$ trên trình duyệt, ta sử dụng **Fast Guided Filter** (hoặc xấp xỉ Bilateral 2-pass):
|
||
|
||
### Bước 1: Trích xuất Perceptual Luminance trong không gian Linear
|
||
$$L = 0.2126 \times R_{\text{linear}} + 0.7152 \times G_{\text{linear}} + 0.0722 \times B_{\text{linear}}$$
|
||
|
||
### Bước 2: Tách lớp Base và Detail
|
||
Cho một texture $I_{\text{base}}$ được làm mờ bằng bộ lọc bảo toàn cạnh:
|
||
|
||
$$\text{Detail} = L - L_{\text{base}}$$
|
||
|
||
### Bước 3: Áp dụng hàm nén phi tuyến (Tone Compression Curve)
|
||
Để vùng tối sâu và vùng sáng gắt không bị bão hòa, lượng chi tiết bù thêm được điều chế:
|
||
|
||
$$L_{\text{new}} = L + \text{Clarity} \times \text{Detail} \times \left(4.0 \times L_{\text{base}} \times (1.0 - L_{\text{base}})\right)$$
|
||
|
||
### Bước 4: Tái cấu trúc RGB
|
||
$$\text{Scale} = \frac{L_{\text{new}} + \epsilon}{L + \epsilon} \implies \text{RGB}_{\text{out}} = \text{RGB}_{\text{in}} \times \text{Scale}$$
|
||
|
||
---
|
||
|
||
## 5. Mã Nguồn WebGL Fragment Shader (Halo-Free Clarity)
|
||
|
||
Shader này nhận vào 2 texture:
|
||
1. `u_image`: Ảnh gốc.
|
||
2. `u_base_texture`: Ảnh đã qua bộ lọc cạnh (chạy qua Box Filter kích thước lớn sau khi downsample $4\times$, hoặc Guided Filter Pass).
|
||
|
||
```glsl
|
||
precision highp float;
|
||
|
||
uniform sampler2D u_image; // Ảnh gốc
|
||
uniform sampler2D u_base_texture; // Base layer từ Edge-Preserving Filter
|
||
uniform float u_clarity_amount; // Slider từ -1.0 đến +1.0 (-100 đến +100 trên UI)
|
||
varying vec2 v_texCoord;
|
||
|
||
// 1. Chuyển đổi sRGB sang Linear Light
|
||
vec3 sRGBToLinear(vec3 c) {
|
||
return pow(c, vec3(2.2));
|
||
}
|
||
|
||
// 2. Chuyển đổi Linear sang sRGB
|
||
vec3 linearTosRGB(vec3 c) {
|
||
return pow(c, vec3(1.0 / 2.2));
|
||
}
|
||
|
||
// 3. Tính Perceptual Luminance Rec.709
|
||
float getLuminance(vec3 c) {
|
||
return dot(c, vec3(0.2126, 0.7152, 0.0722));
|
||
}
|
||
|
||
void main() {
|
||
vec4 srcColor = texture2D(u_image, v_texCoord);
|
||
vec4 baseColor = texture2D(u_base_texture, v_texCoord);
|
||
|
||
vec3 linearOrig = sRGBToLinear(srcColor.rgb);
|
||
vec3 linearBase = sRGBToLinear(baseColor.rgb);
|
||
|
||
float lumOrig = getLuminance(linearOrig);
|
||
float lumBase = getLuminance(linearBase);
|
||
|
||
// BƯỚC 1: Tách lớp High/Medium Frequency Detail
|
||
float detail = lumOrig - lumBase;
|
||
|
||
// BƯỚC 2: Mặt nạ hình chuông (Bell Curve) khóa chặn 2 đầu
|
||
// Đỉnh cực đại tại Luminance = 0.5 (Midtones), triệt tiêu về 0 tại Blacks & Whites
|
||
float midtoneWeight = 4.0 * lumBase * (1.0 - lumBase);
|
||
midtoneWeight = clamp(midtoneWeight, 0.0, 1.0);
|
||
|
||
// BƯỚC 3: Khuếch đại hoặc làm dịu Detail (Clarity)
|
||
// Dùng hàm nén mềm (soft-knee) để tránh clipping khi đẩy Clarity quá cao
|
||
float enhancedDetail = detail * (1.0 + u_clarity_amount * 1.8 * midtoneWeight);
|
||
|
||
// Tính toán Luminance mới
|
||
float newLum = lumBase + enhancedDetail;
|
||
newLum = clamp(newLum, 0.0, 1.0);
|
||
|
||
// BƯỚC 4: Tái cấu trúc RGB theo tỷ lệ Luminance mới để giữ nguyên màu sắc
|
||
float scale = (newLum + 0.0001) / (lumOrig + 0.0001);
|
||
vec3 result = linearOrig * scale;
|
||
|
||
// Giữ an toàn dải giá trị [0.0, 1.0]
|
||
result = clamp(result, 0.0, 1.0);
|
||
|
||
gl_FragColor = vec4(linearTosRGB(result), srcColor.a);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Cách Tạo Texture `u_base_texture` Siêu Tốc Trên Web ($60\text{ fps}$)
|
||
|
||
Nhiều người e ngại Edge-Preserving Filter quá nặng cho trình duyệt. Dưới đây là kiến trúc pipeline xử lý nhẹ nhàng trong 1–2 mili-giây:
|
||
|
||
```
|
||
[Ảnh Gốc (Full Size: 4K)]
|
||
│
|
||
▼ (Blit Downsample 1/4)
|
||
[FrameBuffer Nhỏ (1080p hoặc 720p)]
|
||
│
|
||
▼ (Chạy 2-Pass Separable Guided/Bilateral Filter trên phân giải nhỏ)
|
||
[Base Texture Thu Nhỏ]
|
||
│
|
||
▼ (Bilinear Hardware Upsample tự động của GPU khi texture2D)
|
||
[Đưa vào Shader Clarity ở Mục 5]
|
||
```
|
||
|
||
1. **Downsample trước khi lọc:** Làm mờ bán kính $30\text{px}$ trên ảnh $4\text{K}$ tương đương bán kính $7\text{px}$ trên ảnh $1/4$ độ phân giải. Giảm được **$94\%$ khối lượng tính toán**.
|
||
2. **Upsample tự động qua Texture Sampler:** Thuộc tính `gl.LINEAR` của WebGL sẽ tự động nội suy mịn màng khi upscale texture `u_base_texture` trở lại kích thước đầy đủ.
|
||
|
||
---
|
||
|
||
## 7. Bảng Tự Kiểm Soát (Self-Check Checklist)
|
||
|
||
1. **Không còn đường viền Stroke:** Phóng to $200\%$ ở mép tán cây/đỉnh núi tiếp giáp với mây trời. Nếu không xuất hiện vệt viền trắng phát sáng hoặc viền đen dày, bạn đã thành công.
|
||
2. **Bầu trời không bị tăng nhiễu:** Khi kéo Clarity $+100$, bầu trời phẳng không được phép nổi hạt cát li ti (nhờ mặt nạ Midtone Weight đã dập tắt tác động ở dải sáng $> 0.85$).
|
||
3. **Màu da không bị ố vàng/cam:** Việc chuẩn hóa kênh màu thông qua Perceptual Luminance Scale giúp khuôn mặt người giữ nguyên sắc độ tự nhiên, không bị biến dạng sắc thái màu. |