問(wèn)題說(shuō)明
http://bbs.chinaffmpeg.com/a.html
遇到一個(gè)用canvas對(duì)圖片進(jìn)行hue-rotate 90和用ffmpeg fiter處理hue顏色對(duì)不上的問(wèn)題。中間看到很多東西初嘹,分析過(guò)程簡(jiǎn)單記錄一下掖疮。
原始視頻第一幀:
js canvas ctx.filter = 'hue-rotate(90deg)'
ffmpeg: ffmpeg -y -i http://bbs.chinaffmpeg.com/b.mp4 -filter_complex "hue=h=90" -vframes 1 ffmpeg.jpg
分析過(guò)程和中間遇到的問(wèn)題
初步懷疑是canvas使用HSL奉呛,ffmpeg使用HSV仑荐,簡(jiǎn)單的colorspace差異導(dǎo)致的問(wèn)題,對(duì)比了一下HSV和HSL的顏色表吵冒,研究了一下覺(jué)得兩個(gè)都不是直接簡(jiǎn)單的使用了HSL或HSV色彩空間搏屑,而是HCL。
HSL and HSV wiki中說(shuō)明了HSL和HSV的區(qū)別免胃,hue都是指色相音五,相對(duì)于RGB2HSL和RGB2HSV有相同的轉(zhuǎn)換公示,s是飽和度羔沙,L和V是定義上和含義上區(qū)別的躺涝,之前在color space的學(xué)習(xí)中說(shuō)過(guò)這個(gè)問(wèn)題。兩個(gè)顏色空間的S和LV由RGB計(jì)算的表達(dá)公式不一樣扼雏。photoshop的拾色器使用HSV坚嗜,查看花的色相Hue大概在320-350之間。至于為什么ps picker選擇HSB空間诗充,大致是因?yàn)镠SB的表達(dá)能力更強(qiáng)苍蔬、更符合人對(duì)于拾色器的習(xí)慣,請(qǐng)看知乎的一個(gè)討論蝴蜓。對(duì)照wiki碟绑,正向旋轉(zhuǎn)90度可以看出來(lái)大概的色彩。
由于canvas不知道怎么實(shí)現(xiàn)的茎匠,從上面的wiki中發(fā)現(xiàn)問(wèn)題圖片中間的花紅色(320-340 degree之間)格仲,調(diào)整90度hue(50-70),應(yīng)該大致是黃色诵冒,跟canvas和ffmpeg的結(jié)果都對(duì)不上凯肋,相差有些大,到這里就覺(jué)得有些奇怪了汽馋。
先試了一下使用PS調(diào)整Hue否过,ps調(diào)整hue的功能跟picker不一致,我猜測(cè)使用的是HSL惭蟋,因?yàn)長(zhǎng)調(diào)整的時(shí)候是從黑到白的苗桂,而不是由最淺到最深,有的人說(shuō)是ps這里的調(diào)整L是帶著S一起調(diào)節(jié)的告组,還有一個(gè)stack overflow問(wèn)題煤伟,也有直接說(shuō)是用的HSL空間,我個(gè)人更傾向于HSL。調(diào)整90度以后得到的圖為:
還有一個(gè)很常用的圖像處理開(kāi)源項(xiàng)目IM(ImageMagick后面簡(jiǎn)稱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
使用HSB空間調(diào)整90度Hue:
convert ffmpeg-origin.png -define modulate:colorspace=HSB -modulate 100,100,150 ffmpeg-hsv-150.png
HSB和HSL對(duì)于hue的定義是一樣的掸冤,看wiki中RGB->Hue的計(jì)算公式也一致,紅色的花朵變成了黃色友雳,跟顏色空間模型和PS基本都能對(duì)應(yīng)上了稿湿。不過(guò)還是對(duì)不上ffmpeg和canvas。
HCL顏色空間
在wiki中有一章Disadvantages押赊,這里大致是說(shuō)最早HSL或者HSV的提出是基于RGB轉(zhuǎn)換過(guò)來(lái)的饺藤,計(jì)算起來(lái)方便高效,也就是說(shuō)從RGB cube的color model中硬生生算出來(lái)一個(gè)色相流礁、飽和度涕俗、亮度(明度),計(jì)算快速神帅,符合當(dāng)時(shí)硬件的能力再姑,但是實(shí)際上HSL和HSV都不太符合真正的人眼對(duì)于色相飽和度亮度的看法。由于基于RGB變形枕稀,給了一個(gè)HSL的值還需要知道對(duì)應(yīng)的RGB空間询刹,比如sRGB、bt601等萎坷,甚至gamma值凹联,這就很不方便了。而且在HSL和HSV中的亮度都不太符合人眼所認(rèn)為的亮度哆档。
另外蔽挠,這倆空間都有些毛病,尤其是HSV的V和HSL的S瓜浸,比如在HSV中澳淑,純藍(lán)色和純白色有相同的value,在人眼看來(lái)純藍(lán)色明顯有著更高的亮度插佛,HSL中接近白色和純綠色有相同的S杠巡,而人眼看來(lái)純綠色明顯飽和度要高。另外雇寇,在做色相調(diào)整的時(shí)候氢拥,還會(huì)影響到人眼中認(rèn)為的SL/V蚌铜。既然這么多問(wèn)題,有些專家就說(shuō)那就拋棄HSB HSL好了嫩海,推薦用其他的球坐標(biāo)系Lab和Luv好了冬殃。
Luv和Lab都是后來(lái)在1976年提出的,都是直接基于XYZ的叁怪,不基于RGB spaces审葬,這樣就提供了視覺(jué)感知的一致性,而且兩個(gè)都有理論基礎(chǔ)奕谭,就是人眼的拮抗原理涣觉。像之前在color space的講解中說(shuō)的,Luv和Lab都是球坐標(biāo)系展箱,L都是希望是能表示人眼認(rèn)為的不變的亮度旨枯,uv和ab都是指顏色兩個(gè)方向上的“差異”蹬昌,uv或ab應(yīng)該都不是代表什么單詞的縮寫(xiě)混驰。更加類似于視頻處理的YUV中uv,這里借用知乎一篇回答jpg反復(fù)壓縮變綠的圖片皂贩,按照XYZ計(jì)算U的公式得到的結(jié)果栖榨,u更偏向于藍(lán)色的程度,v表示紅色的程度明刷,所以也可以認(rèn)為u是Cb分量婴栽,v是Cr分量。
Luv極坐標(biāo)表示就是LCHuv辈末,這里L(fēng)不變愚争,將uv看作向量,兩個(gè)向量所表示的顏色的模為Chroma挤聘,夾角為Hue轰枝,用sRGB表示出來(lái)的色域圖如下:
對(duì)應(yīng)的還有LCHab,基本原理是一樣的组去。ImageMagick支持很多種colorspaces鞍陨,恰好其中包括LCHuv和LCHab。使用LCHuv得到的結(jié)果:
這里我們看到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都是開(kāi)源的盅藻,這里我看的是chrome源碼版本64.0.3253.1;ImageMagick源碼我看的是7.0.7-8
FFMPEG
FFmpeg中Hue調(diào)整代碼在libavfilter/vf_hue.c中畅铭,基本算法過(guò)程是:
1, compute_sin_and_cos (line:101)
根據(jù)需要調(diào)整的HueContext計(jì)算Hue的sin cos氏淑,對(duì)于飽和度的調(diào)整根Hue一起,乘在sin和cos上
2硕噩,create_chrominance_lut (line:122)
根據(jù)HueContext和計(jì)算出來(lái)sin cos計(jì)算出來(lái)一個(gè)顏色查找表hue_lut假残,這里ffmpeg為了速度并不是對(duì)每個(gè)pixel做Hue調(diào)整,而是對(duì)uv所有可能出現(xiàn)的值u[0-255]v[0-255]計(jì)算出來(lái)目標(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è)初中幾何問(wèn)題了。
這里對(duì)照一下上面那個(gè)知乎上摳出來(lái)的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, 對(duì)AVFrame的成對(duì)的uv直接apply_lut(line:378)
4线罕,對(duì)于亮度直接是y[0-255]計(jì)算出一個(gè)lut,然后對(duì)y pixels apply_lut
很高效的算法窃判,但ffmpeg的做法實(shí)際是有些問(wèn)題的钞楼,只是強(qiáng)把yuv的uv作為Hue調(diào)整的對(duì)象,沒(méi)有考慮color space和transfer袄琳,不過(guò)其實(shí)在Hue調(diào)整處理中询件,這些影響因素可能沒(méi)那么敏感了吧,對(duì)比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)的窗轩,對(duì)每個(gè)RGB pixel進(jìn)行處理(效率不高夯秃,但這不是重點(diǎn)),ConvertRGBToLCHuv在MagickCore/gem.c line:1375痢艺,先將RGB->XYZ仓洼,然后ConvertXYZToLuv,跟wiki公式一致堤舒,另外可以參考另外一篇關(guān)于HSV RGB等相互轉(zhuǎn)換的公式blog账千,這里使用圖片常見(jiàn)的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。不過(guò)這里的轉(zhuǎn)換公式著實(shí)讓人懵逼:
看一下計(jì)算S和Gray的GetSaturateMatrix和GetGrayscaleMatrix好像明白點(diǎn)什么,matrix的第一列就是RGB2XYZ的Y:
Y=0.2126729*r+0.7151522*g+0.0721750*b;
另外參考一本書(shū)InkScape中對(duì)于Saturation 的說(shuō)明能跟canvas的matrix對(duì)應(yīng)上,另外一本書(shū)Colour Reproduction in Electronic Imaging Systems中14.8.1小節(jié)好像也有些關(guān)系玻墅,而且Canvas變換Hue和Saturation的矩陣根SVG源碼中是一樣的,還有一個(gè)什么OpenPalace也都能對(duì)上壮虫,還有一個(gè)人統(tǒng)計(jì)了一堆css應(yīng)該使用的color轉(zhuǎn)換js……
雖然找到了很多一致的地方澳厢,但是大家好像都是抄的css源碼,并沒(méi)有什么“理論”的根據(jù)囚似。firefox line: 423源碼寫(xiě)的還算親民一些剩拢,至少知道了那一堆數(shù)字都是怎么來(lái)的!
最終還是stack over flow的討論找到了答案饶唤。另一個(gè)stackOverFlow問(wèn)題中Michael Mullany的回答徐伐,css中的hue-rotate實(shí)現(xiàn)只是為了效率的線性近似,原始的HSL或HSV的計(jì)算非線性很復(fù)雜募狂,css做了一個(gè)線性近似办素,對(duì)于不是很純色的結(jié)果還算比較接近HSL:
但是對(duì)于純色,CSS filter hue-rotate得到的結(jié)果在0-180度可以說(shuō)是很爛熬尺,在180-360還算可以摸屠。
如果想自己對(duì)比一下css結(jié)果和HSL)谓罗,Mullany給了一個(gè)對(duì)比css粱哼。
After all,css最終使用的近似方程是這樣子檩咱,想看證明的可以看一下MultiplyByZer0的回答:
References
- HSL && HSV wiki
- Image magick document
- HSL && HSV color space disadvantages
- 知乎關(guān)于ps為什么選擇HSB作為拾色器
- LUV color space
- Lab color space
- jsfiddle net: A css online test
- color selector online tool
- Photoshop HSL HSP understanding
- A wikipedia pdf doc: HSL && HSV color space, and photoshop principle
- stack over flow, photoshop hue adjust
- 知乎討論 為何jpg反復(fù)壓縮質(zhì)量奇差且發(fā)綠
- chromium source code: render_surface_filter.cc
- 62.0.3178.1 chromium source code: render_surface_filter.cc
- HCL color space
- StackOverFlow: Why doesn't hue rotation by +180deg and -180deg yield the original color?
- StackOverFlow: How to transform black into any given color using only CSS filters
- Comparison of Hue Rotations: Red (S 50%, L 75%)
- w3.org hue rotate
- An interesting messages below 17 question