關(guān)于色相Hue調(diào)整的問題

問題說明

http://bbs.chinaffmpeg.com/a.html
遇到一個(gè)用canvas對圖片進(jìn)行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差異導(dǎo)致的問題,對比了一下HSV和HSL的顏色表,研究了一下覺得兩個(gè)都不是直接簡單的使用了HSL或HSV色彩空間,而是HCL。

HSL and HSV wiki中說明了HSL和HSV的區(qū)別,hue都是指色相,相對于RGB2HSL和RGB2HSV有相同的轉(zhuǎn)換公示,s是飽和度,L和V是定義上和含義上區(qū)別的,之前在color space的學(xué)習(xí)中說過這個(gè)問題。兩個(gè)顏色空間的S和LV由RGB計(jì)算的表達(dá)公式不一樣。photoshop的拾色器使用HSV,查看花的色相Hue大概在320-350之間。至于為什么ps picker選擇HSB空間,大致是因?yàn)镠SB的表達(dá)能力更強(qiáng)、更符合人對于拾色器的習(xí)慣,請看知乎的一個(gè)討論。對照wiki,正向旋轉(zhuǎn)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不知道怎么實(shí)現(xiàn)的,從上面的wiki中發(fā)現(xiàn)問題圖片中間的花紅色(320-340 degree之間),調(diào)整90度hue(50-70),應(yīng)該大致是黃色,跟canvas和ffmpeg的結(jié)果都對不上,相差有些大,到這里就覺得有些奇怪了。
先試了一下使用PS調(diào)整Hue,ps調(diào)整hue的功能跟picker不一致,我猜測使用的是HSL,因?yàn)長調(diào)整的時(shí)候是從黑到白的,而不是由最淺到最深,有的人說是ps這里的調(diào)整L是帶著S一起調(diào)節(jié)的,還有一個(gè)stack overflow問題,也有直接說是用的HSL空間,我個(gè)人更傾向于HSL。調(diào)整90度以后得到的圖為:
photoshop-90.jpg

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

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

ffmpeg-hsl-150.png

使用HSB空間調(diào)整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的計(jì)算公式也一致,紅色的花朵變成了黃色,跟顏色空間模型和PS基本都能對應(yīng)上了。不過還是對不上ffmpeg和canvas。

HCL顏色空間

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

各種稱為亮度的空間對比

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

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

yuv-uv.png

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

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

IM-LCHab-150.png

這里我們看到LCHuv得到的結(jié)果和ffmpeg基本一致,但是還是不同。這里后面看源碼ffmpeg使用的就是LCHuv。LCHab的結(jié)果不同,更接近c(diǎn)anvas得到的結(jié)果。

FFMPEG\IM\Canvas 實(shí)現(xiàn)

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

FFMPEG

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

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

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

ImageMagick

IM中調(diào)整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的算法還是比較標(biāo)準(zhǔn)的,對每個(gè)RGB pixel進(jìn)行處理(效率不高,但這不是重點(diǎn)),ConvertRGBToLCHuv在MagickCore/gem.c line:1375,先將RGB->XYZ,然后ConvertXYZToLuv,跟wiki公式一致,另外可以參考另外一篇關(guān)于HSV RGB等相互轉(zhuǎn)換的公式blog,這里使用圖片常見的sRGB,得到LCHuv以后Hue執(zhí)行:

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

IM的Hue調(diào)整是百分比的方式

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

最后轉(zhuǎn)換會(huì)RGB,perfect result!

Canvas hue-rotate

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

chrome-hue.jpg

看一下計(jì)算S和Gray的GetSaturateMatrix和GetGrayscaleMatrix好像明白點(diǎn)什么,matrix的第一列就是RGB2XYZ的Y:

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

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

firefox-hue.jpg

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

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

如果想自己對比一下css結(jié)果和HSL),Mullany給了一個(gè)對比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. 知乎關(guān)于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反復(fù)壓縮質(zhì)量奇差且發(fā)綠
  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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容