面向人類和裝置的顏色模型
影像在網路上無處不在。我每天都會檢視、建立、傳送和編輯多張影像,並以多種不同方式將影像新增到我的應用程式中。但是,是什麼讓影像看起來相同或不同,在哪些情況下?我甚至寫過關於影像的部落格文章,我學到的越多,就越發現幕後發生了多少事情才能讓影像看起來正確。在這個系列部落格文章中,我將解釋大多數影像格式使用的核心原理,從我們的眼睛如何看到光線,到裝置如何解釋和再現顏色。
如果你是一名開發人員、設計師,或者只是好奇為什麼你的影像在不同裝置上看起來不總是相同,那麼這篇博文將深入探討色彩視覺理論和顏色表示背後的數學原理,以解釋人類和機器如何表示、轉換和感知顏色和影像。
螢幕上的影像
如果我們從螢幕開始,影像是由螢幕畫素表示的一組發光區域。每個螢幕畫素都以不同的強度和波長髮光。這種波長的差異被人類(以及貓,但那是另一回事)感知為顏色的差異。
注意:光線可以被認為是微小的粒子集合(光子)或不可見的電磁波。你應該考慮到只有少數裝置可以發出“純粹”(單色)的顏色——只包含一個波長的顏色。大多數情況下,我們將不同波長的混合物感知為顏色,而不是單個波長。我們顯示器中的二極體不會發出“純粹”的顏色。
人眼和大腦中的影像
螢幕上影像發出的光線穿過瞳孔,透過晶狀體折射,並聚焦在視網膜上。視網膜包含稱為視杆細胞和視錐細胞的特殊細胞。視杆細胞幫助我們在弱光下看東西,但它們不檢測不同的波長——這就是為什麼在黑暗中所有東西都看起來是灰度深淺的原因。然而,在白天,視杆細胞不活躍。我們感興趣的大多數模型都是為白天條件而構建的。
視錐細胞負責顏色視覺。有三種類型,每種型別對略微不同的波長光譜敏感。S 視錐細胞對藍色最敏感,M 視錐細胞對綠色最敏感,L 視錐細胞對紅色最敏感。這些細胞發出的訊號考慮了光敏感度。視網膜處理所有這些細胞的訊號,然後訊號被髮送到大腦。
本質上,光訊號可以分解為來自不同細胞型別的三種響應,每種響應覆蓋一個廣泛的光譜。我們的大腦和神經系統無法直接處理這個光譜,因此它們使用機制來解釋它。本質上,紅色、綠色和藍色資訊被對映到其他三個引數:亮度、紅-綠和藍-黃。這種對映使我們能夠區分一種顏色與另一種顏色。實際上,顏色並不存在——它們只是我們人類感知電磁波的方式。
注意:亮度和亮度是不同的概念。亮度是測量單位為坎德拉每平方米的物理量,而亮度是基於人類感知並與光照條件相關(例如,燈是開著還是關著)。
大腦在檢測模式和邊緣方面比緩慢變化的梯度要好得多。還有專門負責邊緣檢測的機制。然而,人類對高頻空間成分不那麼敏感。

左側是原始影像,右側是邊緣檢測演算法的輸出影像。儘管原始影像中的大部分資訊已被移除,但你仍然可以在長凳上感知到一隻烏鴉。
人們如何感知影像極大地影響了影像資料的儲存和處理方式。影像格式並不是為了以儘可能高效的方式儲存畫素——它們需要壓縮資訊,同時考慮人類感知和顯示裝置的特性。
研究和紙張上的顏色
CIE(國際照明委員會)進行了大量實驗,建立了代表普通觀察者如何感知光線的顏色模型。第一個示例展示了普通觀察者如何感知不同波長(即不同顏色)。
從圖表中可以明顯看出,我們對綠光更敏感。眼睛中視錐細胞的機制導致了你可能熟悉的第一個顏色表示:每種視錐細胞型別的RGB(紅、綠、藍)。我們有針對此的 CIE 圖表——顏色匹配函式。這些函式表示不同型別視錐細胞的不同響應。
我們可以使用此圖表獲取顏色的 RGB 表示。要獲得三個數字,我們需要獲取光線中包含的每個波長的響應總和。這些數字將代表我們的訊號。是的,不同的光訊號可以具有相同的 RGB 分量。
您可能還會注意到上一個圖表存在一個問題:紅色敏感度變為負值,當然,沒有負色。為了解決這個問題,CIE 提出了另一種稱為 XYZ 的顏色表示,它是 RGB 的一個簡單轉換,沒有負值。結果,我們得到了一個這樣的圖表
讓我們看看它是如何從 RGB 匯出的
請注意綠色顏色的一個大系數。人眼對綠光最敏感,因此綠色的係數應該相當高。Y 代表感知亮度,這至關重要,因為人類可以快速注意到亮度的變化。這就是為什麼這些資訊需要儘可能準確地儲存和表示。X 和 Z 負責顏色。這使我們能夠將顏色資訊與亮度分離,這與大腦處理訊號的方式有關。
顏色空間
現在我們有三個數字來表示一個光訊號。所有這些數字的可能三元組構成了一個顏色空間。我們也可以將其表示為 3D 形狀。如果你有 Mac,可以使用ColorSync Utility應用程式來檢視它的外觀。
然而,3D 並不總是方便的。讓我們透過丟棄亮度 (Y) 來展平圖片。我們將得到一個著名的色度圖。
注意:在繪製色度圖之前,我們需要將 X 和 Z 值除以 X + Y + Z 來進行歸一化。

馬鞍形圖形表示標準 CIE 觀察者可以感知的所有顏色。這個圖表和 XYZ 顏色空間的問題在於,它不能像觀察者感知那樣表示顏色之間的差異。對於編碼畫素值來說,它也效率低下,因為許多 XYZ 值無法對映到可感知的顏色,這導致了位元的浪費。
然而,XYZ 是構建裝置特定顏色空間的非常有用的工具。每個裝置都有其獨特的顏色渲染特性。例如,兩個紅色物體在兩個裝置上可能看起來略有不同。為了解決這個問題,引入了裝置特定顏色空間。
這裡是它如何工作的大致思路:我們獲取對應於(1, 0, 0)的紅色顏色訊號,測量裝置發出的光線,並計算該點的 XYZ。然後我們對綠色、藍色和白色(當 RGB 的所有值都達到最大時)執行相同的操作。這些 XYZ 點稱為RGB 色原。
瞧,我們得到了一組係數,用於將裝置特定的 RGB 基色轉換為裝置獨立的 XYZ 值,反之亦然。我們通常在網路上使用的 RGB 稱為 sRGB 或標準 RGB。它是一組針對“標準”裝置的特定係數。它們在這裡
sRGB 無法表示人眼能感知的所有可能顏色。讓我們回到我們的圖表。這裡是所有可能的顏色

這是用於獲取 sRGB 的 RGB 係數的點。我們被 sRGB 三角形所限制,因此無法表示更多的顏色。

L*a*b* 中的顏色
為了解決顏色距離問題,CIE 引入了另一個顏色空間——CIE L*a*b*,它可以從 XYZ 匯出。為了使其能夠表示與人類感知相似的顏色接近度,XYZ 中的所有座標都以特定的方式進行轉換。這些轉換不是線性的(僅透過線性轉換不可能表示顏色接近度)。L* 代表亮度,並從 Y 匯出,a* 和 b* 代表顏色維度,並從 X 和 Z 匯出。
Lab 顏色空間使用兩個軸來表示顏色:a* 和 b*。a* 編碼紅綠色分量,而 b* 編碼藍黃色分量。在 L*a*b* 顏色空間中,無法同時表示具有強綠色和紅色分量的光訊號。然而,a* 和 b* 可以是負值。對於 RGB,這也不可能:如果我們進行 (1, 1, 0) 這樣的操作,我們將得到黃色。
讓我們以 RGB 轉換為 Lab 的例子來看看它是如何工作的。
| 顏色 | sRGB | Lab (L*, a*, b*) | 解釋 |
|---|---|---|---|
| 紅色 | 1, 0, 0 | 53.23, 80.11, 67.22 | +a*(強紅),+b*(偏黃) |
| 綠色 | 0, 1, 0 | 87.74, -86.18, 83.19 | -a*(強綠),+b*(偏黃) |
| 藍色 | 0, 0, 1 | 32.30, 79.20, -107.85 | +a*(偏紅),-b*(強藍) |
| 黃色 | 1, 1, 0 | 97.14, -21.55, 94.49 | -a*(略帶綠),+b*(強黃) |
這些型別的顏色空間稱為基於對立的。大腦處理訊號的方式被編碼到顏色空間中。Lab 空間中兩種顏色之間的距離旨在對映到感知距離。請看示例:第一行中兩種顏色之間的距離為 7.5,第二行中為 148.09。
如果兩種顏色在人們看來相似,那麼它們在 Lab 空間中的距離就會更小。
還有更多!CIE L*a*b* 不依賴於任何裝置,因為它源自 XYZ。然而,這種推導考慮了光照條件,以用於另一個人類感知特徵:色適應。為了計算 Lab 值,我們需要提供“正常日光條件”的 XYZ 座標,這通常被稱為白點(或“D65”)。最終公式可以在維基百科上找到。
更直觀的 L*a*b* 變換
如果你現在感到不知所措或困惑,讓我們嘗試用其他例子來理解。看看下面兩種 Lab 顏色,試著猜測它們有什麼關係。
63.76, 29.08, 66.2 63.76, -66.2, 29.08
如果你不確定,請檢視 L*a*b* 空間的一種變換,稱為 LCH(亮度、色度、色相)。亮度保持不變,C 是 a*2 + b*2 的平方根,H 是以度為單位的角度。這種表示對大多數人來說更直觀。讓我們將之前的顏色轉換為 LCH,看看我們會得到什麼
63.76, 72.31, 66.29 63.76, 72.31, 156.29
所以現在我們有了相同的亮度,相同的強度,但色相不同了!
.first {
background-color: lch(63.76 72.31 66.29);
}
.second {
background-color: lch(63.76 72.31 calc(66.29 + 90));
}
現代 L*a*b*
CIE L*a*b* 空間早在 1976 年就開發出來了,它不是現今唯一存在的 Lab 空間。CSS Color Module Level 4 指定了兩種 Lab 空間。第一種是 CIE L*a*b*,另一種是 Oklab。
注意:CSS Color Module Level 4 還定義了額外的 CSS 對映,用於前面討論的顏色空間。
Oklab 是在 2020 年更晚開發的,旨在解決 CIE L*a*b* 的幾個問題。
- Oklab 甚至更均勻,尤其是對於藍色調。
- Oklab 更適合機器,因為數值計算更穩定。
- Oklab 源自 sRGB,因此對開發人員來說更熟悉和直觀。
Oklab 有自己的 LCH 表示,稱為 Oklch。它的計算方式與 LCH 分量類似,旨在實現更均勻和直觀的效果。
最常見的 sRGB 空間及其朋友 YUV
L*a*b* 和 XYZ 是豐富的顏色空間,可以表示人類可以感知的每一種顏色。然而,由於它們的值範圍廣,它們在儲存畫素值方面效率不高。此外,它們是現代的,旨在與能夠承擔浪費一些位元來表示顏色和亮度微小細節的裝置一起工作。
影像和影片格式通常使用 sRGB 顏色空間,GPU 硬體也針對 sRGB 進行了最佳化(我們稍後會討論)。RGB 的靈感來自於人眼的工作方式(紅色、綠色和藍色);然而,它對於影像處理來說不是很方便。這就是為什麼使用另一個顏色空間——YUV。YUV 是一組三個值:Y(亮度)、U(藍差)和 V(紅差)。這個空間的主要優點是它將顏色(U 和 V)與亮度(Y)分離。
注意:關於亮度、紅綠和藍黃系統的命名存在一些混淆。您可能會找到 YUV、YCbCr 和 Y'CbCr 的引用。YUV 和 YCbCr 似乎可以互換使用。然而,Y 和 Y' 是不同的東西。請注意,與 L*a*b* 相比,YUV 在感知上不均勻。
如果你檢視影像編解碼器的原始碼,很可能會發現大量與 YUV 轉換相關的程式碼。不要忘記 sRGB 是與裝置相關的。如果我們取一個填充了某種顏色(例如 (0, 255, 0)(綠色))的矩形,它在不同的裝置上會看起來不同。讓我們看看 YUV 座標是如何從 RGB 匯出的。我們之前見過這個原理,首先我們分離亮度
再一次,我們看到綠光貢獻最大。U 和 V 是藍光和紅光與 Y 的差異。請注意,分母中的係數是為了將 U 和 V 從 -0.5 縮放到 0.5。
讓我們在松鼠上試試,並比較原始影像與分離的 Y、U 和 V 通道。

這種分離之所以如此重要,是因為人們對亮度的敏感度高於對顏色的敏感度。為了正確處理影像,通常只對 Y 通道進行一些轉換,或者對 Y 和 UV 通道使用不同的轉換。這些通道通常以不同的方式進行壓縮。
人類和裝置的感知方式不同
讓我們討論感知的另一個特徵——我們如何感知亮度,這與裝置如何再現亮度非常不同。我們以對數方式感知亮度,而裝置以線性方式再現亮度。對數感知意味著我們可以區分各種亮度範圍。例如,5 級對數刻度(1:100000)很好;如果我們的眼睛適應了環境條件,它甚至可以更大。如果你好奇深入挖掘,這種效應可以用韋伯-費希納定律來描述。
這是一個問題,因為普通裝置可以捕獲和顯示的亮度範圍比我們能感知的範圍要小得多。許多專業相機和顯示器支援更廣的亮度範圍,因此它差異很大。讓我們考慮一個例子:哪個漸變看起來更均勻?
.first {
background-image: linear-gradient(
to right,
rgb(0 0 0),
rgb(128 128 128),
rgb(255 255 255)
);
}
.second {
background-image: linear-gradient(
to right,
rgb(0 0 0),
rgb(180 180 180),
rgb(255 255 255)
);
}
對於大多數人來說,第一個漸變看起來更平滑,而真正的“線性”漸變是第二個。如果你仔細觀察這個例子,你可能會注意到一些小小的謊言。第一個漸變中間有 128(255 / 2)。這正是線性的,對嗎?實際上不是,因為我們在 CSS 中使用的 RGB 值是伽馬校正過的。伽馬校正用於提供更好的感知影像並更有效地儲存位。
為了從線性值獲取伽馬校正值,我們將該值縮放至 [0, 1],然後使用冪函式(如韋伯-費希納定律所示)。我們使用 2.2 作為冪,因為它接近人類感知的運作方式。
sRGB 中使用的真實公式稍微複雜一些,但與上述公式非常接近。這是一個實際的轉換:每個通道 R、G、B 都以相同的方式進行轉換。
這也給暗影“更多位元”。“真實”的平均值大約在 180 左右,而不是 128。這與人類感知的運作方式一致——我們對暗影更敏感。
Y' 是 Y 的伽馬校正版本。它的計算方式與 Y 相同,但使用伽馬校正的 R、G、B。用於伽馬校正的特定公式取決於顏色空間。這稱為傳輸函式。
從相機到螢幕
我們經常想用手機拍照,然後把照片分享到我們最喜歡的社交網路。當你的朋友在她的筆記型電腦上看照片時,如果顏色看起來一樣,那該多好,對吧?
要實現這一點,手機攝像頭捕獲的顏色應正確對映到螢幕上的顏色。請記住,高效的顏色空間是裝置相關的。這意味著,例如,(127, 0, 0) 的 RGB 值在不同裝置上會呈現不同的顏色。為了使顏色保持一致,影像中會編碼一個附加的資訊通道。裝置顏色空間有三個特定之處
- 三種基色
- 白點
- 傳輸函式
讓我們看看它如何適用於AVIF 影像。像許多影像格式一樣,AVIF 盡力準確再現顏色。它使用兩件事來實現這一點:顏色配置檔案和 CICP(獨立於編碼的程式碼點)。顏色配置檔案用於在建立影像的裝置上生成的影像格式中,並允許正確重建顏色。它提供精確的顏色控制,幷包含顏色基色、傳輸函式和白點的係數。它還可以儲存帶有預計算轉換的查詢表。CICP 通常用於影片,它比 ICC 配置檔案簡單得多。
注意:您可以在GitHub 上的 libavif wiki上找到一個很棒的直譯器。
它由三個值組成。每個值都引用公開 H.273 標準中表格的行。
- MC
-
一組係數,用於將 RGB 轉換為您想要的任何顏色空間。通常用於將 RGB 轉換為 YUV。
- TC
-
一種非線性傳輸函式,用於考慮裝置特定特性並執行伽馬校正。
- CP
-
將 RGB 轉換為 XYZ 的基色。
例如,它可以是 1/13/6,這意味著從標準中的特定表格中取出第 1、13 和 6 行。如果影像包含顏色配置檔案,則 CP 和 TC 值取自配置檔案,但 MC 仍然取自 CICP。
讓我們看看 AVIF 影像的處理過程。我們正在讀取 CICP 並得到 2/2/6,這意味著
- CP - 未指定
- TC - 未指定
- MC #6 定義了標準的 RGB 到 YUV 轉換,與 JPEG 使用的相同。
要獲取更多資訊,我們需要檢視 ICC 配置檔案。在這裡我們找到顏色基色、傳輸函式和白點。此資訊用於將解碼的位元組流轉換為裝置顏色空間。在此過程中,影像可能會轉換為 XYZ 和中間顏色空間,然後再轉換為裝置顏色空間。
解碼過程超出了本文的範圍,但有一點需要提及的是使用了 MC 係數。結果被髮送到 GPU 並在螢幕上顯示。
如果我想要令人驚歎的照片怎麼辦?

當我看到日落時,我可以看到天空和建築物的細節。然而,如果我用 JPEG 格式拍攝一張照片,我必須選擇捕捉天空還是建築物。在這種情況下,明亮的天空曝光良好,但建築物則融合成一個單一的黑暗模糊。
我們怎樣才能做得更好?我們能夠感知到的顏色和亮度範圍比低端裝置能夠捕捉和再現的範圍要廣得多。這個廣闊的範圍通常被稱為高動態範圍(HDR)。
要獲得影像的最佳效果,我們需要四件事
- 能夠捕捉寬廣顏色和亮度範圍的裝置(例如專業相機或某些智慧手機相機)
- 可以高效儲存此範圍的檔案格式
- 能夠正確解碼影像的軟體
- 可以準確再現該範圍的顯示器
如果所有這些條件都滿足,我們就能得到細節更豐富的陰影和高光,以及鮮豔的色彩的影像。然而,如果這些條件中的任何一個不滿足,我們最終得到的將是 SDR(標準動態範圍)影像。
專業相機擅長捕捉 HDR 照片。它們使用無損 RAW 格式,其檔案大小比標準影像檔案大得多。透過適當的後期處理,可以透過對影像不同部分應用不同的轉換來揭示細節。

至於軟體——現代瀏覽器在某些作業系統上支援 HDR。
許多螢幕可以再現 HDR 影像,但如果裝置無法顯示 HDR,則影像必須轉換為 SDR。為了保留細節,可以使用不同的色調對映技術。這些技術可以由瀏覽器自動應用,也可以由使用者手動應用。如果沒有色調對映,影像可能會顯得太暗、太亮或暗淡。
讓我們檢查一下它在你的情況中是如何工作的。比較兩張圖片
如果第二張圖片看起來更鮮豔,則您的裝置和瀏覽器可以渲染 HDR 影像。
色調對映的問題在於它均勻地應用於整個影像。一個更好的方法是使用增益圖來調整特定區域——甚至每個畫素——的亮度。您可以將增益圖視為影像的灰度版本,其中每個畫素的實際亮度乘以增益圖中相應的值。此增益圖嵌入在影像檔案中。雖然 JPEG 和 AVIF 支援增益圖,但該功能仍未標準化且處於實驗階段。
即使顯示器能夠再現 HDR,儲存和傳輸 RAW 檔案也是昂貴且低效的。最直接的解決方案是使用具有更高位深度的現代影像格式——例如 AVIF。AVIF 可以每通道儲存 10 或 12 位,從而允許更寬的顏色和亮度範圍。
總結
在這篇文章中,我們學習了人類感知如何影響光訊號在裝置中的表示方式。讓我們總結一下我們涵蓋的內容。
顏色空間用於表示光訊號。有些顏色空間是裝置特有的,而另一些則是通用的且與裝置無關。某些顏色空間,如 sRGB,經過最佳化以表示影像和影片中的光訊號。另一些,如 LCH(亮度、色度、色相),旨在以符合人類感知的方式表示顏色關係。
為了在不同裝置上保持一致的顏色,我們必須在裝置特定顏色空間和通用顏色空間(例如 sRGB 或 XYZ)之間進行轉換。此外,還會應用伽馬校正等特殊轉換,以考慮人類感知光和顏色的非線性方式。某些顏色空間可以將顏色與亮度分離,這對於影像壓縮非常有益,我們將在下一篇文章中看到這一點。