色彩空間轉換 - HSL 原理與應用
在前面的章節中,我們已經深入探討了 RGB 色彩空間。我們知道電腦螢幕是如何透過紅 (R)、綠 (G)、藍 (B) 三種色光的疊加來產生我們所見的千萬種顏色。我們也學會了如何讀取圖片的 RGB 數值,並進行一些基本的像素操作(例如:負片效果、轉灰階)。
然而,在實際的影像處理任務中,RGB 模型有時並不那麼好用。
這一章,我們將引入一個全新的視角來觀察顏色:HSL 色彩空間。這將是我們進行更進階影像分析(例如:特定顏色物體追蹤、調整照片氛圍、膚色檢測)的重要基石。
1. 為什麼 RGB 還不夠好?
試著想像一下,你現在是一位畫家,手裡拿著調色盤。
如果我請你:「麻煩幫我調一個亮一點的紅色。」
在現實世界中,你可能會在紅色顏料裡加一點白色。但在 RGB 的世界裡,你該怎麼做?
我們知道純紅色是 (255, 0, 0)。要讓它變「亮」,你可能需要同時增加 G 和 B 的分量,例如變成 (255, 100, 100)(這看起來是粉紅色),或者增加 R 的數值(如果還沒到 255 的話)。
再換個例子,如果我請你:「麻煩把這個綠色變得更鮮豔一點。」
在 RGB 模型中,這很難直觀地達成。你必須小心翼翼地調整三個數值的比例。
RGB 的局限性:機器友好,人類不友好
RGB 是一個面向硬體 (Hardware-oriented) 的色彩模型。它的設計初衷是為了配合顯示器(CRT、LCD、OLED)的發光原理。對於電腦來說,控制三個燈泡的亮度非常簡單直接。
但對於人類的視覺感知來說,RGB 非常不直觀。人類在描述顏色時,通常不會說:「這個顏色含有 200 單位的紅光、50 單位的綠光和 100 單位的藍光。」
我們會說:
- 「這是一個什麼顏色?」(是紅色還是藍色?)
- 「這個顏色有多深?」(是鮮豔的大紅色,還是灰濛濛的暗紅色?)
- 「這個顏色有多亮?」(是深藍色還是淺藍色?)
為了讓電腦能夠理解人類這種描述顏色的方式,科學家們設計出了更符合人類視覺感知的色彩模型,其中最著名的就是 HSL 和 HSV (或稱 HSB)。
2. 認識 HSL 色彩空間
HSL 代表了三個分量:
- Hue (色相)
- Saturation (飽和度)
- Lightness (亮度)
不同於 RGB 的「立方體」模型,HSL 通常被描繪成一個圓柱體 (Cylinder) 或雙圓錐體 (Double Cone)。
接下來,讓我們逐一拆解這三個分量:
2.1 H - Hue (色相)
色相就是我們平常所說的「顏色」本身。
在 HSL 模型中,色相被定義為一個角度,範圍是 0° 到 360°,環繞著圓柱體的中心軸。想像一個標準的色輪:
- 0° (或 360°):純紅色 (Red)
- 60°:黃色 (Yellow)
- 120°:純綠色 (Green)
- 180°:青色 (Cyan)
- 240°:純藍色 (Blue)
- 300°:洋紅色 (Magenta)
應用場景思考:
如果你想要在影像中把所有「紅色」的物體找出來,在 RGB 空間裡你必須寫很複雜的判斷條件(R 要很大,且 G 和 B 要很小,且要有一定的比例...)。但在 HSL 空間裡,你只需要判斷:如果 H 的值在 340° 到 20° 之間,它就是紅色系!這大大簡化了問題。
2.2 S - Saturation (飽和度)
飽和度描述了顏色的「純度」或「鮮豔程度」。
它表示從圓柱體中心軸(灰色軸)向外延伸的距離。通常用百分比 (0% - 100%) 或小數 (0.0 - 1.0) 來表示。
- 100% (或 1.0):最外圈。表示顏色最純、最鮮豔。沒有混入白光或灰光。
- 0% (或 0.0):最中心軸。表示完全沒有色彩,只有灰度。此時無論 H 是多少度,看起來都是灰色。
直觀理解: 飽和度降低,就像是在純色顏料中不斷加入灰色顏料,直到最後完全變成灰色。
2.3 L - Lightness (亮度)
亮度描述了顏色的明暗程度。
它對應於圓柱體的中心垂直軸,從底部的黑色到頂部的白色。同樣用百分比 (0% - 100%) 或小數 (0.0 - 1.0) 表示。
- 0% (或 0.0):最底部。純黑色。此時無論 H 和 S 是多少,結果都是黑色。
- 50% (或 0.5):中間點。這是展現「純色」的最佳亮度。如果 S 也是 100%,這裡就是最標準的紅、綠、藍等顏色。
- 100% (或 1.0):最頂部。純白色。此時無論 H 和 S 是多少,結果都是白色。
注意:HSL 的 Lightness 與 HSV 的 Value 的區別 這是初學者最容易搞混的地方。在我們現在學的 HSL 模型中,亮度 100% 必定是純白色。而在另一個常見的 HSV (Value) 模型中,亮度 (Value) 100% 只代表該顏色「最亮的狀態」,不一定是白色(除非飽和度為 0)。
- HSL 的 L=100% → 白色
- HSL 的 L=50% → 純色 (當 S=100%)
- HSL 的 L=0% → 黑色
3. 小結與思考
這個小時我們介紹了 HSL 色彩空間的基本概念。
- RGB 是為了機器顯示設計的,對人類直覺不友善。
- HSL 是為了符合人類感知設計的,將顏色拆解為色相、飽和度、亮度。
- H (色相) 是角度,決定是什麼顏色。
- S (飽和度) 是半徑,決定顏色有多鮮豔(多純)。
- L (亮度) 是高度,決定顏色多亮或多暗,兩端是純黑與純白。
思考題 (非作業,僅供腦力激盪):
回想一下我們之前做過的「灰階化」處理。在 RGB 模型中,我們通常使用加權平均法 Gray = 0.299R + 0.587G + 0.114B。
如果在 HSL 模型中,一張圖片已經轉成了 HSL 格式,我們要怎麼把它變成灰階圖?需要複雜的公式嗎?還是只需要調整 H、S、L 其中一個分量就可以了?
(提示:回想一下 S 和 L 的定義,什麼情況下圖像會只剩下灰度資訊?)
沒問題,我們繼續第二個小時的課程。
再複習一次,上一個小時我們建立了 HSL 的幾何直覺:色相是角度 (H),飽和度是半徑 (S),亮度是高度 (L)。
這一小時是理論核心,我們要進入數學推導的世界。請別擔心,我們只需要用到加減乘除和一點點邏輯判斷。理解這些公式是如何設計出來的,比死背它們更重要,這能幫助你真正掌握色彩處理的精髓。
4. 從 RGB 立方體到 HSL 雙圓錐體
我們現在面臨的挑戰是:給你一個 RGB 像素值,例如 (200, 50, 50),如何算出它對應的 (H, S, L) 是多少?
這本質上是一個坐標系的轉換問題。我們要把一個定義在笛卡爾坐標系(立方體)中的點,映射到一個圓柱坐標系(雙圓錐體)中。
這個轉換過程並不是隨意的,而是基於我們對色彩感知的定義嚴格推導出來的。整個過程可以分為幾個標準步驟:歸一化、尋找極值、計算亮度、計算飽和度、最後計算色相。
讓我們開始動手算。
5. 步驟一:準備工作——歸一化 (Normalization)
RGB 的數值範圍通常是 0 到 255 (8-bit 整數)。但在進行色彩科學計算時,使用 0.0 到 1.0 的浮點數範圍會方便得多。
假設我們輸入的像素是 $(R, G, B)$。 第一步,我們將它們分別除以 255,得到歸一化後的 $(R', G', B')$:
\begin{align} R' = R / 255 \end{align}
\begin{align} G' = G / 255 \end{align}
\begin{align} B' = B / 255 \end{align}
範例: 如果輸入顏色是鮮紅色
(255, 0, 0)。 歸一化後變成: $R' = 255 / 255 = 1.0$ $G' = 0 / 255 = 0.0$ $B' = 0 / 255 = 0.0$
6. 步驟二:尋找極值與色度 (Extremes & Chroma)
接下來的幾個步驟,都依賴於這三個 $R', G', B'$ 數值中的最大值和最小值。
我們定義:
- $M_{max} = \max(R', G', B')$ —— 三個分量中最亮的的那個值。
- $M_{min} = \min(R', G', B')$ —— 三個分量中最暗的的那個值。
接著,我們計算一個非常重要的中間變數,稱為色度 (Chroma),通常用 $C$ 或 $\Delta$ 表示。它是最大值與最小值的差:
\begin{align} C = M_{max} - M_{min} \end{align}
直觀理解 $C$: 色度 $C$ 代表了這個顏色中「彩色成分」有多少。
- 如果 $C = 0$,意味著 $M_{max} = M_{min}$,也就是 $R' = G' = B'$。回想一下上一章的思考題,當 R=G=B 時是什麼顏色?沒錯,是灰色(包括純黑和純白)。這種情況下,顏色沒有「色相」,也沒有「飽和度」。
範例: 繼續使用鮮紅色
(1.0, 0.0, 0.0)。 $M_{max} = 1.0$ (因為 R' 最大) $M_{min} = 0.0$ (因為 G' 和 B' 最小) $C = 1.0 - 0.0 = 1.0$
7. 步驟三:計算亮度 L (Lightness)
HSL 中的亮度定義非常直觀,它是最大成分和最小成分的平均值。
\begin{align} L = \frac{M_{max} + M_{min}}{2} \end{align}
這個定義確保了:
- 純黑色 (0,0,0):$L = (0+0)/2 = 0$
- 純白色 (1,1,1):$L = (1+1)/2 = 1$
- 純紅色 (1,0,0):$L = (1+0)/2 = 0.5$
這符合我們上一章學到的:在 HSL 模型中,最純正的顏色位於中間亮度 (L=0.5)。
注意:與 HSV 的區別 順帶一提,在 HSV (Value) 模型中,亮度的定義比較簡單粗暴,直接就是 $V = M_{max}$。這就是為什麼 HSV 的純紅色亮度是 100%,而 HSL 的純紅色亮度是 50%。這兩個模型的根本差異就在這裡。
8. 步驟四:計算飽和度 S (Saturation)
飽和度的計算稍微複雜一點點,因為它取決於亮度 L。
回想一下 HSL 的雙圓錐體形狀。
- 當亮度 L 接近 0 (黑色) 或接近 1 (白色) 時,圓錐體收縮成一個點,飽和度的空間變得很小,最後變成 0。
- 當亮度 L 在中間 0.5 時,圓錐體最寬,飽和度可以達到最大。
我們分情況討論:
情況 1:如果是灰階 如果色度 $C = 0$,那麼這個顏色是灰色的。飽和度自然為 0。
\begin{align} S = 0 \quad (\text{若 } C = 0) \end{align}
情況 2:如果不是灰階 ($C > 0$) HSL 的飽和度公式如下:
\begin{align} S = \frac{C}{1 - |2L - 1|} \end{align}
這個公式看起來有點嚇人,但分母 $1 - |2L - 1|$ 的作用其實就是描述那個雙圓錐體的形狀。
- 當 $L=0.5$ 時,分母為 $1 - |1-1| = 1$。此時 $S = C / 1 = C$。
- 當 $L \to 0$ 或 $L \to 1$ 時,分母會趨近於 0,這個公式能確保飽和度被正確地縮放到 0-1 之間。
(註:在程式實作時,如果 L=0 或 L=1,分母會變成 0。所以通常會先判斷,如果 L=0 或 L=1,直接令 S=0,避免除以零錯誤。)
範例: 鮮紅色
(1.0, 0.0, 0.0)。已知 $C=1.0$, $L=0.5$。 $S = \frac{1.0}{1 - |2 \times 0.5 - 1|} = \frac{1.0}{1 - 0} = 1.0$ 飽和度為 100%。合理。
9. 步驟五:計算色相 H (Hue)
最後一步是計算色相角度。這是最麻煩的一步,因為我們要確定顏色落在 360 度色輪的哪個扇區。這取決於 $R', G', B'$ 哪一個是最大值 ($M_{max}$)。
情況 1:如果是灰階 ($C = 0$) 灰色沒有色相。H 的定義是未知的 (undefined)。在程式實作中,通常將其設為 0(但要記得此時 S=0,所以 H 是多少其實不影響顯示結果)。
情況 2:如果不是灰階 ($C > 0$) 我們根據哪個顏色分量主導了最大值來分段計算:
如果 $M_{max} = R'$ (紅色主導,角度在 0° 或 360° 附近): 我們計算 G' 和 B' 的差異,並用 C 來標準化。 $H' = \frac{G' - B'}{C}$ (注意:如果 G' < B',這個值可能是負的,稍後會處理)
如果 $M_{max} = G'$ (綠色主導,角度在 120° 附近): 我們計算 B' 和 R' 的差異。為了把角度移到 120° 附近,我們加上常數 2 (因為 2 * 60° = 120°)。 $H' = \frac{B' - R'}{C} + 2$
如果 $M_{max} = B'$ (藍色主導,角度在 240° 附近): 我們計算 R' 和 G' 的差異。加上常數 4 (因為 4 * 60° = 240°)。 $H' = \frac{R' - G'}{C} + 4$
最後的轉換: 上面計算出來的 $H'$ 是一個介於 -1 到 5 之間的數值。我們需要把它轉換成 0° 到 360° 的角度。
\begin{align} H = H' \times 60^{\circ} \end{align}
處理負角度: 在紅色主導的情況下,如果 $G' < B'$,算出來的 $H$ 可能是負的(例如 -30°,代表偏紫的紅色)。為了保持在 0-360 範圍內,如果最終的 $H < 0$,我們就加上 360。
\begin{align} \text{如果 } H < 0,則 H = H + 360 \end{align}
範例: 鮮紅色
(1.0, 0.0, 0.0)。 $M_{max} = R' = 1.0$。$C=1.0$。 套用紅色主導公式: $H' = (0.0 - 0.0) / 1.0 = 0$ $H = 0 \times 60^{\circ} = 0^{\circ}$。結果:RGB(255,0,0) 轉換為 HSL(0°, 100%, 50%)。完美符合理論!
10. 第二小時小結
恭喜你撐過了最枯燥的數學部分!
我們現在已經掌握了將任何 RGB 顏色轉換為 HSL 數值的完整演算法。這套公式雖然看起來繁瑣,但邏輯是非常嚴謹的:
- 歸一化到 0-1 空間。
- 找出最亮和最暗的分量 ($M_{max}, M_{min}$) 以及它們的差 (色度 C)。
- 亮度 L 是平均值。
- 飽和度 S 是色度 C 根據亮度 L 進行縮放的結果。
- 色相 H 是根據哪個顏色分量最大,來決定在色輪上的基礎角度。
思考檢查點 (Checkpoint):
試著用剛才學到的公式,在腦中(或用紙筆)快速估算一下 RGB (128, 128, 128) 的 HSL 值是多少?
(提示:先算出 R', G', B',然後算出 $M_{max}$ 和 $M_{min}$,再算出 C。一旦你算出 C,答案就呼之欲出了。)
11. 實作準備:從理論到程式碼
在開始寫 code 之前,我們面臨一個實際問題:我們目前的知識是單向街。我們知道如何把 RGB 變成 HSL,但如果我們在 HSL 空間修改了顏色(例如把飽和度提高),要怎麼變回 RGB 讓螢幕顯示呢?
為了讓本章完整,我們需要補上「HSL 轉回 RGB」的拼圖。
11.1 缺失的環節:HSL 轉回 RGB (簡述)
由於時間關係,我們不進行詳細推導,而是直接給出轉換步驟(這在實務上通常也是查表或套公式)。
假設我們有 HSL 值:$H \in [0, 360)$, $S \in [0, 1]$, $L \in [0, 1]$。我們要算出 RGB (0-255)。
步驟 A:計算中間變數 C, X, m 這基本上是 RGB 轉 HSL 的逆運算。
色度 $C$ (Chroma): $C = (1 - |2L - 1|) \times S$
中間值 $X$ (用於計算次要色光分量): $X = C \times (1 - |(\frac{H}{60} \pmod 2) - 1|)$ (註:
H/60 mod 2意思是把 H 除以 60 後,取除以 2 的餘數。這能幫助我們確定在 60 度的扇區內的相對位置)亮度匹配量 $m$: $m = L - \frac{C}{2}$
步驟 B:根據色相角度 H 決定 RGB 的基礎組合 $(R', G', B')$
我們看 H 落在六個 60° 扇區中的哪一個:
- 0° $\le$ H < 60°: $(R', G', B') = (C, X, 0)$
- 60° $\le$ H < 120°: $(R', G', B') = (X, C, 0)$
- 120° $\le$ H < 180°: $(R', G', B') = (0, C, X)$
- 180° $\le$ H < 240°: $(R', G', B') = (0, X, C)$
- 240° $\le$ H < 300°: $(R', G', B') = (X, 0, C)$
- 300° $\le$ H < 360°: $(R', G', B') = (C, 0, X)$
步驟 C:轉回最終的 RGB 最後,加上亮度匹配量 $m$,並乘以 255 轉回整數:
$R = (R' + m) \times 255$ $G = (G' + m) \times 255$ $B = (B' + m) \times 255$
12. 實作:核心轉換函數 (JavaScript/Node.js)
現在我們擁有了雙向轉換的能力。讓我們把它們寫成乾淨的 JavaScript 輔助函數。你可以把這些函數放在一個單獨的檔案中,例如 colorUtils.js。
12.1 RGB 轉 HSL 實作
這完全是基於我們上一小時推導的公式。
/**
* 將 RGB 顏色轉換為 HSL
* @param {number} r - 紅色分量 (0-255)
* @param {number} g - 綠色分量 (0-255)
* @param {number} b - 藍色分量 (0-255)
* @returns {object} { h, s, l } - h 在 [0, 360) 之間,s 和 l 在 [0, 1] 之間
*/
function rgbToHsl(r, g, b) {
// 步驟 1: 歸一化
const rPrime = r / 255;
const gPrime = g / 255;
const bPrime = b / 255;
// 步驟 2: 尋找極值與色度
const cMax = Math.max(rPrime, gPrime, bPrime);
const cMin = Math.min(rPrime, gPrime, bPrime);
const delta = cMax - cMin; // 即色度 C
// 步驟 3: 計算亮度 L
let h = 0;
let s = 0;
let l = (cMax + cMin) / 2;
// 步驟 4 & 5: 計算飽和度 S 和色相 H
if (delta === 0) {
// 灰階情況:飽和度為 0,色相未定義 (設為 0)
h = 0;
s = 0;
}
else {
// 計算飽和度 S
s = delta / (1 - Math.abs(2 * l - 1));
// 計算色相 H
if (cMax === rPrime) {
h = ((gPrime - bPrime) / delta) % 6;
} else if (cMax === gPrime) {
h = (bPrime - rPrime) / delta + 2;
} else {
h = (rPrime - gPrime) / delta + 4;
}
h = Math.round(h * 60);
// 處理負角度
if (h < 0) {
h += 360;
}
}
// 保持精度,通常保留幾位小數即可,這裡為了教學清晰暫不處理
return { h, s, l };
}
// 測試一下 (驗證上一章的課後思考題)
// console.log(rgbToHsl(128, 128, 128));
// 預期輸出: { h: 0, s: 0, l: ~0.5019... } -> 沒錯,是中灰色
// console.log(rgbToHsl(255, 0, 0));
// 預期輸出: { h: 0, s: 1, l: 0.5 } -> 純紅色12.2 HSL 轉 RGB 實作
接下來是逆向轉換函數。
/**
* 將 HSL 顏色轉換為 RGB
* @param {number} h - 色相 (0-360)
* @param {number} s - 飽和度 (0-1)
* @param {number} l - 亮度 (0-1)
* @returns {object} { r, g, b } - 範圍都在 0-255 之間
*/
function hslToRgb(h, s, l) {
// 確保輸入在合理範圍
h = h % 360;
if (h < 0) h += 360;
s = Math.max(0, Math.min(1, s));
l = Math.max(0, Math.min(1, l));
// 步驟 A: 計算中間變數 C, X, m
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
let rPrime = 0;
let gPrime = 0;
let bPrime = 0;
// 步驟 B: 根據色相扇區決定基礎組合
if (h >= 0 && h < 60) {
[rPrime, gPrime, bPrime] = [c, x, 0];
}
else if (h >= 60 && h < 120) {
[rPrime, gPrime, bPrime] = [x, c, 0];
}
else if (h >= 120 && h < 180) {
[rPrime, gPrime, bPrime] = [0, c, x];
}
else if (h >= 180 && h < 240) {
[rPrime, gPrime, bPrime] = [0, x, c];
}
else if (h >= 240 && h < 300) {
[rPrime, gPrime, bPrime] = [x, 0, c];
}
else {
[rPrime, gPrime, bPrime] = [c, 0, x];
}
// 步驟 C: 轉回最終 RGB 整數
const r = Math.round((rPrime + m) * 255);
const g = Math.round((gPrime + m) * 255);
const b = Math.round((bPrime + m) * 255);
// 確保結果在 0-255 範圍內 (防止浮點數誤差導致越界)
return {
r: Math.max(0, Math.min(255, r)),
g: Math.max(0, Math.min(255, g)),
b: Math.max(0, Math.min(255, b))
};
}13. 應用範例:感受 HSL 的威力
有了這兩個核心函數,我們就可以對影像進行非常有趣的「手術」了。
假設在我們的主程式中,我們已經讀取了一張圖片的像素資料,存放在一個名為 pixelData 的 Uint8Array Buffer 中,格式為 [R, G, B, A, R, G, B, A, ...]。
我們將示範兩個場景,請比較一下如果只用 RGB 模型,這些操作會有多困難。
範例一:復古風格濾鏡 (降低飽和度)
目標:我們想讓整張照片看起來有一種褪色的復古感,但又不是完全的黑白照片。 HSL 思路:這太簡單了,只需要把每個像素的 $S$ (飽和度) 乘以一個小於 1 的系數(例如 0.3),保持 H 和 L 不變。
// 假設 pixelData 是圖像的原始 Buffer 資料
// 假設 width 和 height 是圖像寬高
console.log("開始處理:復古風格濾鏡...");
for (let i = 0; i < pixelData.length; i += 4) {
// 1. 讀取當前像素的 RGB
const r = pixelData[i];
const g = pixelData[i + 1];
const b = pixelData[i + 2];
// const a = pixelData[i + 3]; // Alpha 通道我們暫時不動
// 2. 轉換到 HSL 空間
let { h, s, l } = rgbToHsl(r, g, b);
// ========== HSL 操作核心區 ==========
// 將飽和度降低到原來的 30%
s = s * 0.3;
// 思考:如果這裡寫 s = 0; 會發生什麼事?
// 答案:照片會變成完全的灰階圖!這是另一種實現灰階化的方式。
// ====================================
// 3. 轉回 RGB 空間
const newRgb = hslToRgb(h, s, l);
// 4. 寫回像素資料
pixelData[i] = newRgb.r;
pixelData[i + 1] = newRgb.g;
pixelData[i + 2] = newRgb.b;
}
console.log("處理完成!");
// 接下來將 pixelData 存檔即可查看結果範例二:魔幻改色 (色相偏移)
目標:將照片中所有的顏色進行「旋轉」。紅的變綠,綠的變藍,藍的變紅。 HSL 思路:只需調整 $H$ (色相) 角度。我們把每個像素的 $H$ 加上 120 度。
console.log("開始處理:色相偏移...");
for (let i = 0; i < pixelData.length; i += 4) {
const r = pixelData[i];
const g = pixelData[i + 1];
const b = pixelData[i + 2];
let { h, s, l } = rgbToHsl(r, g, b);
// ========== HSL 操作核心區 ==========
// 色相旋轉 120 度
h = h + 120;
// 注意:h 可能超過 360,但我們的 hslToRgb 函數裡已經處理了 % 360 的情況,
// 所以這裡可以直接加。
// ====================================
const newRgb = hslToRgb(h, s, l);
pixelData[i] = newRgb.r;
pixelData[i + 1] = newRgb.g;
pixelData[i + 2] = newRgb.b;
}
console.log("處理完成!");14. 章節總結
這三個小時,我們完成了一次從硬體思維 (RGB) 到人類感知思維 (HSL) 的跨越。
- 理論層面:我們理解了 RGB 立方體的局限性,並引入了 HSL 雙圓錐體模型,將顏色解構為色相、飽和度、亮度這三個符合直覺的分量。
- 數學層面:我們詳細推導了從 RGB 到 HSL 的標準化和幾何轉換過程。
- 實作層面:我們編寫了 Javascript 函數來實現雙向轉換,並看到了在 HSL 空間修改顏色是多麼的直觀和方便。
掌握色彩空間轉換,是進行進階影像處理(如電腦視覺中的物體追蹤、專業級影像調色)的關鍵一步。希望你能從這個章節中體會到數學模型與程式碼結合的樂趣!