色彩深度的影響
呈現影像的品質除了考慮解析度 (resolution, 呈現影像的像素數量) 外還有色彩深度 (color depth, 單一像素可以呈現出的顏色數量),而描述一個像素 (pixel) 的位元數即表示色彩深度。例如, 8-bits 可呈現 256 色,16-bits 可呈現 65536 色,而 24-bits 則可呈現超過 1,600 萬色 。縱使解析度足夠,但倘若顯示設備的色彩深度不足,此時影像的像素原始顏色無法被精確地呈現,而會以較接近的顏色被顯示出來,如此高色彩深度影像中一些漸層色通常會被犧牲而呈現像帶狀的色塊,除了影響觀看的感覺,影像中的細節也會消失不見。VGA 256 color palette |
色彩模型
電腦顯示設備多以紅、綠、藍 (Red, Green, Blue) 三原色光為主要色彩模型 (color model),有時在色彩處理與演算上若轉換成色相、飽和度、明度 (Hue, Saturation, Value) 會更得方便,因為較為直觀。例如在 VGA 256 indexed colors 的模式中,觀察其預設色盤的顏色會發現它就是一個以 HSV 色彩模型而排列的。所謂 indexed color 是指影像的像素值並不直接描述色彩 RGB 值,而是一個索引值指向一個色盤 (palette) 的位置,設備會從而找出真正描述該像素的 RGB 值以顯示顏色。VGA 256 色模式的預設色盤顏色除去前面 32 色 (16 色相容 CGA,16 色灰階) 與末尾 8 色外,中間的 216 色可看作為 9 組 24 色的 HSV 色輪依色相次序排列,各組間又以飽和度/明度的差異依次排列。
處理色深不足的方法
Dither 是一類極有意思的數位信號處理技術,藉由刻意加入用來減少量化誤差的雜訊,讓人感官覺得數位採樣後再還原的視訊或音訊失真減少了。自然界的訊號大多是連續性的,採樣頻率可以使用兩倍於訊號最大頻率來捕獲資訊,但訊號還原時振幅量化卻是重要的因子,我們的設備表示振幅量化的位元數有限,量化誤差由此產生而讓人察覺到訊號失真。色彩深度即是視覺訊號的振幅量化數值,Floyd-Steinberg Dithering 的原理是將每一像素的色深量化誤差散佈到鄰近的其他像素,人類視覺會混合相鄰像素的色彩而感覺到影像失真程度減少了。
以一幅高彩原始影像 (16-bits, red:green:blue=6:6:4) 執行測試,分別以三種方式顯示:
一、以與影像相符的色深 (65536 colors) 可完整顯示該影像所有顏色。
高彩色深顯示結果 |
256 色 (no dithering) 顯示結果 |
256 色 dithering (Floyd-Steinberg) 顯示結果 |
dithering 部份程式碼
//-- read the raw image pixels, //-- the raw pixel is in 16-bits color depth r:g:b=6:6:4 var img= new Uint16Array(reader.result); //-- prepare 2 lines for dithering process var lines= new Array(2); lines[0]= new Array(640); lines[1]= new Array(640); for(var y= 0; y < 480; ++y){ var x; //-- copy a line of pixels for(x= 0; x < 640; ++x){ var i= x + 640 * y; //-- convert raw pixel to RGB color (24-bits) lines[1][x]= P664.rgb(img[i]); } if(y > 0){ //-- display and dithering for(x= 0; x < 640; ++x){ //-- convert RGB to HSV var hsv= lines[0][x].toHSV(); //-- get the approximate color var rgb= Color256.mappedRGB(hsv); //-- set the canvas context fill style color and draw the pixel ctx.fillStyle= rgb.toCSS(); ctx.fillRect(x, y-1, 1, 1); //-- calculate the quantization error var errp= {}; errp.red= lines[0][x].red - rgb.red; errp.green= lines[0][x].green - rgb.green; errp.blue= lines[0][x].blue - rgb.blue; //-- distribute the error to neighboring pixels lines[1][x].red= clamp(lines[1][x].red + 5*errp.red/16, 0, 255); lines[1][x].green= clamp(lines[1][x].green + 5*errp.green/16, 0, 255); lines[1][x].blue= clamp(lines[1][x].blue + 5*errp.blue/16, 0, 255); if(x < 639){ lines[0][x+1].red= clamp(lines[0][x+1].red + 7*errp.red/16, 0, 255); lines[0][x+1].green= clamp(lines[0][x+1].green + 7*errp.green/16, 0, 255); lines[0][x+1].blue= clamp(lines[0][x+1].blue + 7*errp.blue/16, 0, 255); lines[1][x+1].red= clamp(lines[1][x+1].red + errp.red/16, 0, 255); lines[1][x+1].green= clamp(lines[1][x+1].green + errp.green/16, 0, 255); lines[1][x+1].blue= clamp(lines[1][x+1].blue + errp.blue/16, 0, 255); } if(x > 0){ lines[1][x-1].red= clamp(lines[1][x-1].red + 3*errp.red/16, 0, 255); lines[1][x-1].green= clamp(lines[1][x-1].green + 3*errp.green/16, 0, 255); lines[1][x-1].blue= clamp(lines[1][x-1].blue + 3*errp.blue/16, 0, 255); } } } lines[0]= lines[1].slice(); //-- display the last line if(y == 479) for(x= 0; x < 640; ++x){ ctx.fillStyle= Color256.mappedRGB(lines[0][x].toHSV()).toCSS(); ctx.fillRect(x, y, 1, 1); }
沒有留言:
張貼留言