13 KiB
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}
- 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.
- 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
\impliesGiá trị mờ thấp hơn thực tế\impliesKhi lấyI - \text{Blur}sẽ sinh ra một giá trị dương rất lớn\impliesTạ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
\impliesGiá trị mờ cao hơn thực tế\impliesPhép trừ tạo ra giá trị âm sâu\impliesTạo viền đen đậm (Undershoot).
- Về phía bầu trời: Gaussian kéo cả giá trị tối của vách đá vào
- 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 Layergiữ nguyên vẹn cạnh cứng, nênDetail Layer(I - \text{Base}) tại đường biên vách núi/bầu trời sẽ có giá trị bằng0. - Khi bạn tăng Clarity lên
+100, hệ số khuếch đại nhân với0vẫn là0 \impliesHoà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:
u_image: Ảnh gốc.u_base_texture: Ảnh đã qua bộ lọc cạnh (chạy qua Box Filter kích thước lớn sau khi downsample4\times, hoặc Guided Filter Pass).
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]
- Downsample trước khi lọc: Làm mờ bán kính
30\text{px}trên ảnh4\text{K}tương đương bán kính7\text{px}trên ảnh1/4độ phân giải. Giảm được94\%khối lượng tính toán. - Upsample tự động qua Texture Sampler: Thuộc tính
gl.LINEARcủa WebGL sẽ tự động nội suy mịn màng khi upscale textureu_base_texturetrở lại kích thước đầy đủ.
7. Bảng Tự Kiểm Soát (Self-Check Checklist)
- 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. - 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). - 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.