Files
RecipesCam/docker/halo_highlight_shadow_white_black.md
T

9.9 KiB

Phân Tích Kỹ Thuật & Giải Pháp Khắc Phục Lỗi "Halo" Khi Chỉnh Highlight, Shadow, White, Black (Tích Hợp Cân Bằng Saturation & Vibrance)


1. Hiện Tượng Halo Artifact Khi Chỉnh 4 Thông Số Ánh Sáng (Highlight, Shadow, White, Black)

Khi người dùng điều chỉnh 4 thanh trượt ánh sáng (Highlights, Shadows, Whites, Blacks) — đặc biệt là kéo mạnh +100 hoặc -100 trên các tệp ảnh RAW (.NEF, .RAF, .DNG, .RW2):

  • Tại các đường biên tương phản cao (Step Edges) như: ngọn núi/tán cây trên nền trời sáng, đường viền chủ thể ngược sáng, khung cửa sổ sáng trên mảng tường tối.
  • Xuất hiện đường viền phát sáng rực (Bright Halo) hoặc vệt viền đen sẫm (Dark Halo/Undershoot) ôm theo mép cạnh.
  • Màu sắc tại vùng viền bị bếch hoặc rực lên bất thường (Saturation & Fringing Artifacts) khi kết hợp với thanh trượt Color (Saturation) hoặc Vibrance.
MẶT CẮT ĐỘ SÁNG TẠI ĐƯỜNG BIÊN TƯƠNG PHẢN CAO (Tán cây vs Bầu trời):

Độ Sáng (Luminance L)
  ▲
1.0 ┤                      [Overshoot: Viền sáng rực (Bright Halo)]
    │                            ┌───┐
0.9 ├────────────────────────────┘   └──────────────────────── (Bầu trời)
    │
    │
0.1 ├────────────────────────────┐   ┌──────────────────────── (Tán cây)
    │                            └───┘
0.0 ┴────────────────────────────────────────────────────────► Khoảng cách (X)
                           [Undershoot: Viền đen sẫm (Dark Halo)]

2. Nguyên Nhân Kỹ Thuật Cốt Lõi

A. Sự Rò Rỉ Không Gian Của Bộ Lọc Nền (Gaussian Neighborhood Leakage)

  1. Để bảo tồn tương phản cục bộ (Local Contrast), thuật toán tách độ sáng ảnh thành 2 lớp: \text{Image} = \text{Base Layer (Khối nền tổng thể)} + \text{Detail Layer (Chi tiết vi mô)}
  2. Lớp Base Layer được trích xuất bằng bộ lọc làm mờ. Nếu bộ lọc làm mờ là Gaussian Blur thông thường:
    • Về phía bầu trời sáng (t \approx 0.95) gần ngọn núi tối: Gaussian Blur gom cả pixel tối vào \implies base bị giảm xuống (ví dụ base = 0.60).
    • Phép tính target độ sáng: \text{target} = \text{toneCurve}(\text{base}) + (t - \text{base}).
    • Vì base = 0.60 thấp hơn thực tế, khi nén Highlight (-Highlight), hàm toneCurve(0.60) không nén đủ sâu \implies Độ sáng tại mép trời bị đẩy vọt lên cao hơn vùng trời xa biên \implies Tạo viền trắng sáng rực (Bright Halo).
    • Ngược lại, phía ngọn núi tối (t \approx 0.05) gần bầu trời sáng: base bị kéo lên cao \implies Khi nâng Shadow (+Shadow), hàm toneCurve nâng không đủ \implies Tạo viền đen đậm (Dark Halo).

B. Tại Sao Ảnh RAW Bị Ảnh Hưởng Nghiêm Trọng Hơn Ảnh JPEG?

  • Ảnh JPEG 8-bit đã qua nén tương phản và kẹp dải sáng (0.0 - 1.0), chênh lệch bước nhảy t - \text{base} ở mức vừa phải (0.05 - 0.15).
  • Ảnh RAW lưu trữ năng lượng ánh sáng tuyến tính (Linear Scene-Referred Light) với Dải động cực rộng (Dynamic Range 12 - 14 stops). Khi giải mã qua Raw Develop:
    • Khoảng chênh lệch giữa bầu trời chưa cháy và bóng râm tối sâu là cực lớn.
    • Bước nhảy |t - \text{base}| tại đường biên tương phản đạt tới 0.30 - 0.75.
    • Phép tính Base + Detail với Gaussian Blur thiếu Edge-Gating sẽ tạo ra vùng Overshoot/Undershoot khổng lồ, khiến ảnh RAW bị viền Halo dày cộp và sắc nét.

C. Tác Động Dây Chuyền Đến Color (Saturation) Và Vibrance

  1. Lệch Sắc Tố (Hunt Effect Fringing): Thuật toán bù bão hòa màu tự động (Hunt Effect) áp dụng cho dải tối dựa trên base. Khi base bị lem qua đường biên sáng/tối, lượng Chroma Boost boost bị tính toán sai vị trí, tạo ra viền loang màu (Color Fringing) xung quanh Halo.
  2. Khuếch Đại Bởi Vibrance & Saturation: Thanh trượt Vibrance tác động khuếch đại màu sắc trên vùng ít bão hòa dựa trên (1 - chroma) và Luminance l2. Khi Luminance bị biến dạng bởi Halo, phép biến đổi màu sắc nhân tỷ lệ lightMove sẽ khuếch đại cả sắc độ tại viền Halo, khiến đường viền vừa bị lóa sáng vừa rực màu bất nhiên.

3. Giải Pháp Thuật Toán: Edge-Aware Range-Gated Tone Mapping

Để triệt tiêu hoàn toàn viền Halo trên cả ảnh JPEG và ảnh RAW mà vẫn giữ nguyên 100\% độ nổi khối chi tiết dải trung tính, thuật toán áp dụng cơ chế Phân hoạch biên độ theo độ chênh lệch ánh sáng (Edge-Aware Range Gating):

[Luminance Pixel (t) & Gaussian Base (base)]
                      │
                      ▼
       [Tính độ lệch: diff = |t - base|]
                      │
                      ▼
 [Mặt nạ kiểm soát cạnh: edgeW = 1.0 - smoothstep(0.08, 0.35, diff)]
                      │
    ┌─────────────────┴─────────────────┐
    ▼ (diff nhỏ < 0.08)                 ▼ (diff lớn > 0.35 - Bước nhảy cạnh cứng)
[edgeW ≈ 1.0: Môi trường bề mặt]    [edgeW ≈ 0.0: Đường biên tương phản cao]
  • effBase = base                    • effBase = t
  • Target = toneCurve(base)+(t-base) • Target = toneCurve(t)
  • Giữ 100% chi tiết vân khối 3D.     • Chuyển mượt về đường cong toàn cục.
                                      • TRIỆT TIÊU HOÀN TOÀN HALO!

Công Thức Toán Học Cho WebGL / SkSL Shader:

// 1. Độ chênh lệch giữa luma điểm ảnh và luma môi trường lân cận
float diff = abs(t - base);

// 2. Mặt nạ suy giảm mượt (Range Falloff Window)
// Với diff < 0.08: edgeW = 1.0 (vùng vân mịn / dải chuyển mượt)
// Với diff > 0.35: edgeW = 0.0 (đường biên tương phản cứng, đặc biệt trên ảnh RAW)
float edgeW = 1.0 - smoothstep(0.08, 0.35, diff);

// 3. Luma môi trường hiệu dụng (Effective Base Luminance)
float effBase = mix(t, base, edgeW);

// 4. Áp dụng toneCurve trên effBase
float o = toneCurve(effBase, clamp(bl + dr * 0.12, -1.0, 1.0), 
                             clamp(sh + dr * 0.06, -1.0, 1.0),
                             clamp(hl - dr * 0.09, -1.0, 1.0), 
                             clamp(wh - dr * 0.18, -1.0, 1.0));

// 5. Tái cấu trúc luma đích không bị vệt halo
float target = clamp(o + (t - effBase), 0.0, 1.0);
vec3 res = lightMove(c, t, target);

// 6. Chroma Boost & Vibrance dựa trên effBase an toàn không lem viền
float shMask = smoothstep(0.02, 0.12, effBase) * (1.0 - smoothstep(0.25, 0.55, effBase));
float uBl = clamp(1.0 - effBase / TONE_BLACK_EDGE, 0.0, 1.0);

4. Cân Chỉnh Sự Tương Tác Với Color (Saturation) & Vibrance

  1. Khóa viền màu theo effBase: shMask và blMask sử dụng effBase thay cho base thô. Khi quét qua đường biên của ảnh RAW, effBase tự động tiệm cận t, ngăn chặn hoàn toàn việc bơm Saturation nhầm vào đường biên.
  2. Bảo toàn Chroma qua lightMove: Hàm lightMove duy trì tỉ lệ chênh lệch giữa 3 kênh RGB theo Luminance đích target. Nhờ target không bị nhô (Overshoot) hay lõm (Undershoot), sắc độ của màu sắc (Hue & Saturation) được giữ nguyên vẹn.
  3. Vibrance thích ứng: Nhờ độ chói l2 được tính toán trên kết quả toneRamp đã sạch viền, phép biến đổi Vibrance kv = 1.0 + vib * 0.75 * (1.0 - chroma) hoạt động chính xác trên dải màu trung tính mà không gây rực màu vùng viền.

5. Mã Nguồn Đã Cập Nhật Trong Codebase

  1. Shader Core (frontend/shared/utils/toneShader.ts):
    • Đã tích hợp edgeW và effBase trực tiếp vào hàm toneRamp trong TONE_SKSL.
  2. Bộ Test Tự Động (frontend/scripts/):
    • highlight-knee-check.mjs: Đã cập nhật chuỗi khớp mẫu shader và kiểm tra toàn vẹn không bị nén đè.
    • tone-base-check.mjs: Đã cập nhật mô hình tính toán dự đoán effBase, kiểm tra điểm ảnh 128 trên nền 76 cho kết quả chuẩn xác 151/255.
    • Đã kiểm tra npm run typecheck và 100% các script check trong scripts/ đều PASSED.

6. Bảng Kiểm Tra Chất Lượng (Quality Assurance Checklist)

Tiêu chí Hành vi chuẩn (Sau khi fix) Dấu hiệu lỗi (Cần kiểm tra lại)
Chỉnh Highlight -100 trên RAW Bầu trời sáng hiện rõ mây, ranh giới ngọn núi đanh gọn, không có viền sáng lóa xung quanh. Xuất hiện dải viền trắng phát sáng chạy dọc mép núi.
Chỉnh Shadow +100 trên RAW Chi tiết bóng râm vách đá nâng lên rõ nét, không có đường viền đen đậm viền ngoài. Có viền đen/tối sẫm bao quanh các tán cây ngược sáng.
Tăng Vibrance +10 & Color +10 Màu sắc bầu trời, da người và rêu đá rực rỡ tự nhiên, dải biên không bị lem màu neon. Đường viền núi bị nổi màu xanh/đỏ rực rỡ như nét tô viền (Color Fringing).