關于色相Hue調整的問題

問題說明

http://bbs.chinaffmpeg.com/a.html
遇到一個用canvas對圖片進行hue-rotate 90和用ffmpeg fiter處理hue顏色對不上的問題。中間看到很多東西,分析過程簡單記錄一下。
原始視頻第一幀:

origin-video.png

js canvas ctx.filter = 'hue-rotate(90deg)'
canvas.png

ffmpeg: ffmpeg -y -i http://bbs.chinaffmpeg.com/b.mp4 -filter_complex "hue=h=90" -vframes 1 ffmpeg.jpg
ffmpeg.jpg

分析過程和中間遇到的問題

初步懷疑是canvas使用HSL,ffmpeg使用HSV,簡單的colorspace差異導致的問題,對比了一下HSV和HSL的顏色表,研究了一下覺得兩個都不是直接簡單的使用了HSL或HSV色彩空間,而是HCL。

HSL and HSV wiki中說明了HSL和HSV的區別,hue都是指色相,相對于RGB2HSL和RGB2HSV有相同的轉換公示,s是飽和度,L和V是定義上和含義上區別的,之前在color space的學習中說過這個問題。兩個顏色空間的S和LV由RGB計算的表達公式不一樣。photoshop的拾色器使用HSV,查看花的色相Hue大概在320-350之間。至于為什么ps picker選擇HSB空間,大致是因為HSB的表達能力更強、更符合人對于拾色器的習慣,請看知乎的一個討論。對照wiki,正向旋轉90度可以看出來大概的色彩。

394px-HSL_color_solid_cylinder_alpha_lowgamma.png

394px-HSV_color_solid_cylinder_alpha_lowgamma.png

photoshop-color-wheel.png

600px-HSV-RGB-comparison.svg.png

HSL&HSV&RGB.png

由于canvas不知道怎么實現的,從上面的wiki中發現問題圖片中間的花紅色(320-340 degree之間),調整90度hue(50-70),應該大致是黃色,跟canvas和ffmpeg的結果都對不上,相差有些大,到這里就覺得有些奇怪了。
先試了一下使用PS調整Hue,ps調整hue的功能跟picker不一致,我猜測使用的是HSL,因為L調整的時候是從黑到白的,而不是由最淺到最深,有的人說是ps這里的調整L是帶著S一起調節的,還有一個stack overflow問題,也有直接說是用的HSL空間,我個人更傾向于HSL。調整90度以后得到的圖為:
photoshop-90.jpg

還有一個很常用的圖像處理開源項目IM(ImageMagick后面簡稱IM)有個 modulate接口默認使用HSL顏色空間,很慶幸文檔中恰好給了一個紅花的示例圖片,雖然并不是一樣的紅色。IM支持自己選擇顏色空間,比如HSL、HSV等。上面的modulate命令默認在HSL空間中hue調整90度,得到的結果是

convert ffmpeg-origin.png -modulate 100,100,150 ffmpeg-hsl-150.png

ffmpeg-hsl-150.png

使用HSB空間調整90度Hue:

convert ffmpeg-origin.png -define modulate:colorspace=HSB -modulate 100,100,150 ffmpeg-hsv-150.png

ffmpeg-hsv-150.png

HSB和HSL對于hue的定義是一樣的,看wiki中RGB->Hue的計算公式也一致,紅色的花朵變成了黃色,跟顏色空間模型和PS基本都能對應上了。不過還是對不上ffmpeg和canvas。

HCL顏色空間

在wiki中有一章Disadvantages,這里大致是說最早HSL或者HSV的提出是基于RGB轉換過來的,計算起來方便高效,也就是說從RGB cube的color model中硬生生算出來一個色相、飽和度、亮度(明度),計算快速,符合當時硬件的能力,但是實際上HSL和HSV都不太符合真正的人眼對于色相飽和度亮度的看法。由于基于RGB變形,給了一個HSL的值還需要知道對應的RGB空間,比如sRGB、bt601等,甚至gamma值,這就很不方便了。而且在HSL和HSV中的亮度都不太符合人眼所認為的亮度。

各種稱為亮度的空間對比

另外,這倆空間都有些毛病,尤其是HSV的V和HSL的S,比如在HSV中,純藍色和純白色有相同的value,在人眼看來純藍色明顯有著更高的亮度,HSL中接近白色和純綠色有相同的S,而人眼看來純綠色明顯飽和度要高。另外,在做色相調整的時候,還會影響到人眼中認為的SL/V。既然這么多問題,有些專家就說那就拋棄HSB HSL好了,推薦用其他的球坐標系LabLuv好了。

Luv和Lab都是后來在1976年提出的,都是直接基于XYZ的,不基于RGB spaces,這樣就提供了視覺感知的一致性,而且兩個都有理論基礎,就是人眼的拮抗原理。像之前在color space的講解中說的,Luv和Lab都是球坐標系,L都是希望是能表示人眼認為的不變的亮度,uv和ab都是指顏色兩個方向上的“差異”,uv或ab應該都不是代表什么單詞的縮寫。更加類似于視頻處理的YUV中uv,這里借用知乎一篇回答jpg反復壓縮變綠的圖片,按照XYZ計算U的公式得到的結果,u更偏向于藍色的程度,v表示紅色的程度,所以也可以認為u是Cb分量,v是Cr分量。

yuv-uv.png

Luv極坐標表示就是LCHuv,這里L不變,將uv看作向量,兩個向量所表示的顏色的模為Chroma,夾角為Hue,用sRGB表示出來的色域圖如下:
SRGB_gamut_within_CIELCHuv.png

對應的還有LCHab,基本原理是一樣的。ImageMagick支持很多種colorspaces,恰好其中包括LCHuv和LCHab。使用LCHuv得到的結果:
IM-LCHuv-150.png

IM-LCHab-150.png

這里我們看到LCHuv得到的結果和ffmpeg基本一致,但是還是不同。這里后面看源碼ffmpeg使用的就是LCHuv。LCHab的結果不同,更接近canvas得到的結果。

FFMPEG\IM\Canvas 實現

看看源碼實現吧。ffmpeg的源碼可以直接下到,我看的3.24;canvas的firrefox和chrome都是開源的,這里我看的是chrome源碼版本64.0.3253.1;ImageMagick源碼我看的是7.0.7-8

FFMPEG

FFmpeg中Hue調整代碼在libavfilter/vf_hue.c中,基本算法過程是:

1, compute_sin_and_cos (line:101)
   根據需要調整的HueContext計算Hue的sin cos,對于飽和度的調整根Hue一起,乘在sin和cos上
2,create_chrominance_lut (line:122)
   根據HueContext和計算出來sin cos計算出來一個顏色查找表hue_lut,這里ffmpeg為了速度并不是對每個pixel做Hue調整,而是對uv所有可能出現的值u[0-255]v[0-255]計算出來目標值。這里consider U and V as the components of a 2D vector then its angle is the hue and the norm is the saturation,這樣就是一個初中幾何問題了。    
   這里對照一下上面那個知乎上摳出來的uv圖就容易理解了,從原點隨便一個vector,Saturation逐漸增大,Hue保持不變;確定半徑下旋轉一個vector,是Saturation保持不變,Hue在逐漸調整。  
   uv旋轉以后的新坐標是:  
   new_u = cos * u - sin * v;
   new_v = sin * u + cos * v;
3, 對AVFrame的成對的uv直接apply_lut(line:378)  
4,對于亮度直接是y[0-255]計算出一個lut,然后對y pixels apply_lut

很高效的算法,但ffmpeg的做法實際是有些問題的,只是強把yuv的uv作為Hue調整的對象,沒有考慮color space和transfer,不過其實在Hue調整處理中,這些影響因素可能沒那么敏感了吧,對比IM的結果,ffmpeg得到的結果還有些跑偏。

ImageMagick

IM中調整Hue的代碼在enhance.c中,line:3092

static inline void ModulateLCHuv(const double percent_luma,
  const double percent_chroma,const double percent_hue,double *red,
  double *green,double *blue)
{
  double
    hue,
    luma,
    chroma;

  /*
    Increase or decrease color luma, chroma, or hue.
  */
  ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
  luma*=0.01*percent_luma;
  chroma*=0.01*percent_chroma;
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
  ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
}

IM的算法還是比較標準的,對每個RGB pixel進行處理(效率不高,但這不是重點),ConvertRGBToLCHuv在MagickCore/gem.c line:1375,先將RGB->XYZ,然后ConvertXYZToLuv,跟wiki公式一致,另外可以參考另外一篇關于HSV RGB等相互轉換的公式blog,這里使用圖片常見的sRGB,得到LCHuv以后Hue執行:

hue+=fmod((percent_hue-100.0),200.0)/200.0;

IM的Hue調整是百分比的方式

hue_angle = ( modulate_arg - 100 ) * 180/100 
modulate_arg = ( hue_angle * 100/180 ) + 100

最后轉換會RGB,perfect result!

Canvas hue-rotate

Canvas的執行算法如上參考Chromium源碼render_surface_filters.cc line: 176,或者另外一個地方FEColorMatrix.cpp。不過這里的轉換公式著實讓人懵逼:

chrome-hue.jpg

看一下計算S和Gray的GetSaturateMatrix和GetGrayscaleMatrix好像明白點什么,matrix的第一列就是RGB2XYZ的Y:

Y=0.2126729*r+0.7151522*g+0.0721750*b;

另外參考一本書InkScape中對于Saturation 的說明能跟canvas的matrix對應上,另外一本書Colour Reproduction in Electronic Imaging Systems中14.8.1小節好像也有些關系,而且Canvas變換Hue和Saturation的矩陣根SVG源碼中是一樣的,還有一個什么OpenPalace也都能對上,還有一個人統計了一堆css應該使用的color轉換js……
雖然找到了很多一致的地方,但是大家好像都是抄的css源碼,并沒有什么“理論”的根據。firefox line: 423源碼寫的還算親民一些,至少知道了那一堆數字都是怎么來的!

firefox-hue.jpg

最終還是stack over flow的討論找到了答案。另一個stackOverFlow問題中Michael Mullany的回答,css中的hue-rotate實現只是為了效率的線性近似,原始的HSL或HSV的計算非線性很復雜,css做了一個線性近似,對于不是很純色的結果還算比較接近HSL:
less-pure-color-hue-HSLvsCSS.jpg

但是對于純色,CSS filter hue-rotate得到的結果在0-180度可以說是很爛,在180-360還算可以。
Pure-color-hue-HSLvsCSS.jpg

如果想自己對比一下css結果和HSL),Mullany給了一個對比css

After all,css最終使用的近似方程是這樣子,想看證明的可以看一下MultiplyByZer0的回答

css-hue-rotate-equation.jpg

References

  1. HSL && HSV wiki
  2. Image magick document
  3. HSL && HSV color space disadvantages
  4. 知乎關于ps為什么選擇HSB作為拾色器
  5. LUV color space
  6. Lab color space
  7. jsfiddle net: A css online test
  8. color selector online tool
  9. Photoshop HSL HSP understanding
  10. A wikipedia pdf doc: HSL && HSV color space, and photoshop principle
  11. stack over flow, photoshop hue adjust
  12. 知乎討論 為何jpg反復壓縮質量奇差且發綠
  13. chromium source code: render_surface_filter.cc
  14. 62.0.3178.1 chromium source code: render_surface_filter.cc
  15. HCL color space
  16. StackOverFlow: Why doesn't hue rotation by +180deg and -180deg yield the original color?
  17. StackOverFlow: How to transform black into any given color using only CSS filters
  18. Comparison of Hue Rotations: Red (S 50%, L 75%)
  19. w3.org hue rotate
  20. An interesting messages below 17 question
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,367評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,001評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,213評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,535評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,317評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,868評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,963評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,090評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,599評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,549評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,712評論 1 367
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,233評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,353評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,607評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,321評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,686評論 2 370

推薦閱讀更多精彩內容