Files
RecipesCam/docker/fix_shadow.md
T

156 lines
7.8 KiB
Markdown

# Phân Tích Kỹ Thuật & Giải Pháp Tối Ưu Tính Năng "Shadow Recovery" Cho Ứng Dụng Web UI
---
## 1. Chẩn Đoán Lỗi Kỹ Thuật Ở Ảnh 2 (Ứng Dụng Hiện Tại)
So sánh giữa **Ảnh 2** (app tự code) và **Ảnh 3** (Lightroom):
```
+-----------------------------------------------------------------------------------+
| Hiện tượng ở Ảnh 2 | Nguyên nhân kỹ thuật cốt lõi |
+--------------------------------+--------------------------------------------------+
| Vùng tối biến thành mảng xám đục| Cộng giá trị tuyến tính vào sRGB (Không gian màu |
| (Milky / Foggy haze) | phi tuyến, nâng cả Black point tuyệt đối lên). |
+--------------------------------+--------------------------------------------------+
| Đường phân tách loang lổ, gãy nét| Dùng ngưỡng cứng (Hard Threshold) hoặc hàm bước |
| (Posterization / Color Banding)| (Step function) thay vì mặt nạ suy giảm mượt mà. |
+--------------------------------+--------------------------------------------------+
| Mất hoàn toàn khối & vân chi | Áp dụng phép biến đổi toàn cục (Global Operator) |
| tiết (Mặt núi, vách đá, tán cây)| thay vì tách lớp tương phản cục bộ (Local Contrast)|
+--------------------------------+--------------------------------------------------+
| Cây cối đổi màu bùn, mất sắc độ| Thay đổi Luminance nhưng không bù Saturation |
| (Chroma wash-out) | hoặc thao tác sai trên từng kênh R, G, B độc lập.|
+--------------------------------+--------------------------------------------------+
```
---
## 2. Bí Quyết Của Lightroom: Cơ Chế "Shadow Recovery" Chuyên Nghiệp
Lightroom **không** dùng công thức đơn giản như `RGB = RGB + shadow * mask`. Thuật toán của Adobe dựa trên 3 trụ cột:
### A. Tách lớp đa tỉ lệ (Multi-Scale Tone Decomposition)
Thay vì nâng sáng trực tiếp từng điểm ảnh, ảnh được phân rã thành:
$$\text{Image} = \text{Base Layer (Ánh sáng tổng thể)} \times \text{Detail Layer (Vân bề mặt & Góc cạnh)}$$
* **Base Layer:** Được trích xuất bằng bộ lọc bảo toàn góc cạnh (**Edge-Preserving Filter** như *Guided Filter* hoặc *Bilateral Filter*).
* Khi kéo **Shadow +100**: Thuật toán chỉ nâng độ sáng trên **Base Layer**, trong khi giữ nguyên hoặc khuếch đại nhẹ **Detail Layer**. Nhờ đó, các rãnh đá, tán lá và gợn sóng giữ nguyên độ sắc nét và chiều sâu.
### B. Không gian màu tính toán (Scene-Referred Linear Space)
* Ảnh sRGB trên web có sẵn Gamma nén ($EOM \approx 2.2$). Mọi phép tính phải đưa về **Linear Light** trước khi thao tác, sau đó mới ánh xạ ngược lại sRGB:
$$I_{linear} = (I_{sRGB})^{2.2} \quad \xrightarrow{\text{Xử lý}} \quad I_{out} = (I_{processed})^{1/2.2}$$
### C. Khóa điểm đen (Anchor Black Point) & Bù trừ bão hòa (Chroma Boost)
* Giữ điểm đen ($0.0$) cố định để ảnh không bị bạc nhược (milky).
* Hiện tượng sinh lý thị giác (*Hunt Effect*): Khi vùng tối sáng lên, mắt người sẽ thấy nó nhạt hơn thực tế. Lightroom tự động tăng nhẹ độ bão hòa màu sắc (Saturation) tỉ lệ thuận với lượng Shadow được kéo lên.
---
## 3. Kiến Trúc Thuật Toán Đề Xuất Cho Web UI (WebGL/GLSL)
Để chạy mượt mà $60\text{ fps}$ trên trình duyệt, quy trình xử lý tối ưu gồm 2 Pass Shader:
```
[Ảnh Đầu Vào sRGB]
│
▼ (Pass 1)
[Chuyển sang Linear Light] ───> [Tạo Base Layer bằng Fast Guided Filter / Box Blur]
│
┌────────────────────────────────────┘
▼ (Pass 2: Composite & Tone Mapping Shader)
[1. Tính Detail Layer: Detail = Input / (Base + ε)]
[2. Tính Hệ số Shadow Mask: w = SmoothFalloff(Base)]
[3. Nâng Base Layer: Base' = Base * (1.0 + ShadowAmount * Curve(Base))]
[4. Khôi phục chi tiết: Reconstructed = Base' * Detail]
[5. Bù trừ Saturation trên không gian OKLab / HSV]
[6. Chuyển ngược về sRGB Output]
```
---
## 4. Mã Nguồn Shader Mẫu (Fragment Shader GLSL)
Đoạn Shader WebGL dưới đây có thể tích hợp trực tiếp vào Canvas WebGL của bạn để khắc phục hoàn toàn lỗi bết màu:
```glsl
precision highp float;
uniform sampler2D u_texture; // Ảnh gốc
uniform sampler2D u_blur_texture; // Ảnh đã qua lọc mờ (Base layer từ Guided Filter / Gaussian)
uniform float u_shadow_amount; // Giá trị từ 0.0 đến 1.0 (tương ứng +0 đến +100)
varying vec2 v_texCoord;
// 1. Chuyển sRGB sang Tuyến tính (Linear)
vec3 sRGBToLinear(vec3 c) {
return pow(c, vec3(2.2));
}
// 2. Chuyển Tuyến tính sang sRGB
vec3 linearTosRGB(vec3 c) {
return pow(c, vec3(1.0 / 2.2));
}
// 3. Tính độ sáng tri giác (Perceptual Luminance)
float getLuminance(vec3 c) {
return dot(c, vec3(0.2126, 0.7152, 0.0722));
}
// 4. Đường cong suy giảm mượt (Smooth Shadow Mask)
// Chỉ tác động mạnh vào vùng tối (0.05 - 0.4), không đụng vào Highlight (>0.6) và khóa điểm đen
float getShadowWeight(float lum) {
// Khóa điểm đen tuyệt đối để tránh màng sương đục (milky haze)
float blackAnchor = smoothstep(0.0, 0.08, lum);
// Suy giảm mượt về 0 khi tiến tới vùng sáng
float highlightFalloff = 1.0 - smoothstep(0.2, 0.65, lum);
return blackAnchor * highlightFalloff;
}
void main() {
vec4 srcColor = texture2D(u_texture, v_texCoord);
vec4 baseColor = texture2D(u_blur_texture, v_texCoord);
// Chuyển sang không gian ánh sáng tuyến tính
vec3 I_orig = sRGBToLinear(srcColor.rgb);
vec3 I_base = sRGBToLinear(baseColor.rgb);
float baseLum = getLuminance(I_base);
float origLum = getLuminance(I_orig);
// Bước A: Tách lớp vân bề mặt (Detail Layer)
// Detail = I_orig / (I_base + eps)
vec3 detail = I_orig / (I_base + vec3(0.001));
// Bước B: Tính toán lượng sáng cần bù vào Base Layer
float mask = getShadowWeight(baseLum);
// Sử dụng công thức mũ tự nhiên thay vì cộng dồn tuyến tính
// Gain = (1.0 - baseLum)^2 giúp kéo vùng tối sâu mà không làm cháy vùng trung gian
float liftFactor = pow(1.0 - baseLum, 1.8) * u_shadow_amount * 2.2;
vec3 liftedBase = I_base * (1.0 + mask * liftFactor);
// Bước C: Tái kết hợp Detail với Base đã được nâng sáng
vec3 result = liftedBase * detail;
// Bước D: Bù trừ màu sắc (Chroma Compensation) để tránh bợt màu
float newLum = getLuminance(result);
if (origLum > 0.001) {
float boost = 1.0 + (mask * u_shadow_amount * 0.35); // Bù màu nhẹ cho vùng tối
result = mix(vec3(newLum), result, boost);
}
// Bước E: Giữ điểm đen và chuyển ngược lại sRGB
result = clamp(result, 0.0, 1.0);
gl_FragColor = vec4(linearTosRGB(result), srcColor.a);
}
```
---
## 5. Danh Sách Kiểm Tra Khi Triển Khai (Action Plan)
1. **Khử ngay phép cộng tuyến tính trên 8-bit RGB:** Không bao giờ viết dạng `r = r + shadowValue`.
2. **Triển khai Box Filter / Guided Filter trước:** Dùng 1 texture phụ chứa bản blur bán kính lớn ($\approx 2\% - 5\%$ chiều rộng ảnh) để làm `u_blur_texture`.
3. **Độ chính xác dấu phẩy động:** Thiết lập WebGL Context với cấu hình Float Texture (`OES_texture_float` hoặc `WebGL2`) để khử triệt để hiện tượng vỡ khối viền (banding).