1. 程式人生 > >幾種常見計算機影象處理操作的原理及canvas實現

幾種常見計算機影象處理操作的原理及canvas實現

2013-09-21 • 技術文章 • 評論

前言 即使沒有計算機圖形學基礎知識的讀者也完全不用擔心您是否適合閱讀此文,本文的性質屬於科普文章,將為您揭開諸如Photoshop、Fireworks、GIMP等軟體的影象處理操作的神祕面紗。之前您也許對這些處理技術感到驚奇和迷惑,但筆者相信您讀完本文後會豁然開朗。本文主要介紹幾種常見計算機影象處理操作的原理,為了操作簡便和保證平臺相容性,採用HTML5的canvas作為程式碼實現樣例,當然您也可以使用Qt、VisualStudio系列、Java等進行實現且可以利用多執行緒和GPU程式設計技術提高大畫素檔案的處理效率。本文的原理部分適合所有層面的讀者,程式碼實現部分需要讀者對小學數學的加減乘除運算有一定了解(其實寫一些基礎性程式碼不就是小學數學這種層次的事嗎?非專業讀者完全不用怕!筆者就是在作為計算機白痴的小學生時期就開始寫程式的)。 預備知識1:影象色點在計算機中的表示
對於一個影象,計算機單獨處理組成該影象的每一個畫素點。對於普通的點陣圖(bitmap),每一個畫素點的資料在計算機中是以紅綠藍(RGB)三色外加透明度(也就是Alpha通道,簡記為A)進行儲存的,RGBA四項分別由0-255的值表示,不同的RGB配比將顯示為不同的顏色,A值從0-255代表了從完全透明到完全不透明。255,難道計算機不是用0和1來表示數值嗎?當然,從0到255,恰好是256個數,也即2的8次方,也就是說本質是8位二進位制數。如果我們進行位邏輯運算,當然應該把R/G/B都作為8位二進位制值來進行計算。但是如果是做普通的算術計算,為什麼不用我們熟悉的十進位制呢?所以上面我說的是0-255,而不是00000000-11111111,由於都是很小的整數,我們也沒有必要考慮有些十進位制沒法精確表示成二進位制會帶來浮點誤差(舉個浮點誤差的例子:0.2+0.1=0.30000000000000004,原因是0.2沒法表示成有限二進位制數,只能產生誤差,但一般而言256以內的小整數加減法計算機還是hold住的)。 舉個簡單的例子,當Windows使用者熟練地用畫圖(mspaint)儲存影象時,在儲存格式(可通俗理解為副檔名)選項中可以看到24位點陣圖(.bmp)這一項,其中的24位正是上面所講的RGB的二進位制共計8×3=24位,沒有A值是完全不透明的。 此外,我們再擴充套件一點16進位制(0到F)顏色表示的知識,那就是每4位二進位制表示成一位十六進位制,比如1111就等於F。所以我們經常可以看到不少網頁的樣式中有類似color:#FF6600這樣的表示的顏色,其實就是11110110011000000000的24位RGB,不帶A值。而CSS3中引入了RGBA表示,我們就可以設定一個color:rgba(255,0,0,0.5),也就是半透明的紅色,和上面點陣圖儲存的A值的區別是它使用了0-1來表示透明度而不是0-255。在部分圖形處理程式碼中你可能會看到位運算中有0xFFFFFF之類的表示,0x就是告訴計算機後面這是16進位制數。 預備知識2:卷積核
在計算機圖形處理中,不瞭解卷積矩陣(Convolution Matrix)的計算是萬萬不行的。大多數濾鏡都用到了卷積矩陣計算,所以這是必備知識。數學對於電腦科學是極為重要的,微積分、離散數學、線性代數、概率論與數理統計、數值方法都是基礎性支撐。3x3矩陣和5x5矩陣的卷積計算是最基本的,學習過訊號處理的同學一定對利用卷積計算進行濾波有深入的認識,沒學習過的請繼續向下閱讀本節。 卷積是影象處理常用的方法,給定輸入影象,在輸出影象中每一個畫素是輸入影象中一個小區域中畫素的加權平均,其中權值由一個函式定義,這個函式稱為卷積核(kernel)。這裡所介紹的卷積運算,就是這樣一個過程,影象區域中的每個畫素分別與權矩陣的每個元素對應相乘,所有乘積之和作為區域中心畫素的新值。形象一點來講,對於下圖左側所示的一個影象中的一塊3x3區域和一個權矩陣W=[0 1 0; 0 0 0; 0 0 0]進行卷積核運算:中心畫素值=40×0+42×1+46×0+46×0+50×0+55×0+52×0+56×0+58×0=42,卷積核運算相對於卷積運算要簡單得多。假如我們將除了邊界畫素的其餘畫素點一一作為中心畫素和W矩陣進行卷積核運算,那麼將會實現影象向下位移一個畫素。你看,最左綠框中間居上的42是不是向下移動了一個格子成為了紅框中的值呢?是的,它發生了一個畫素的位移。如果W矩陣中的1位置不同則位移方向不同,這非常易於理解。
W矩陣的不同將帶來各種不同的炫酷效果,接下來幾個部分中我們將舉幾個典型的例子進行說明。 使用Matlab可以很容易地進行各類卷積計算,但是我們下面是用JavaScript實現的計算函式,它的通用性很高,除了卷積核計算外還包含了顏色偏移量和除數這兩個引數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function ConvolutionMatrix(input, m, divisor, offset){
	var output = document.createElement("canvas").getContext('2d').createImageData(input);
	var w = input.width, h = input.height;
	var iD = input.data, oD = output.data;
	// 對除了邊緣的點之外的內部點的 RGB 進行操作,透明度在最後都設為 255
	for (var y = 1; y < h-1; y += 1) {
		for (var x = 1; x < w-1; x += 1) {
			for (var c = 0; c < 3; c += 1) {
				var i = (y*w + x)*4 + c;
				oD[i] = offset
					+(m[0]*iD[i-w*4-4] + m[1]*iD[i-w*4] + m[2]*iD[i-w*4+4]
					+ m[3]*iD[i-4]     + m[4]*iD[i]     + m[5]*iD[i+4]
					+ m[6]*iD[i+w*4-4] + m[7]*iD[i+w*4] + m[8]*iD[i+w*4+4])
					/ divisor;
			}
			oD[(y*w + x)*4 + 3] = 255; // 設定透明度為不透明
		}
	}
	return output;
}
預備知識3:使用canvas對畫素點實現基本的處理操作
1
2
3
4
// 獲取畫素點資料
var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
獲取到的canvasData物件包含下列成員,其中的data陣列結構大概是這樣的,一行一行存,然後一個列點一個列點存,每個點佔4個下標,分別是RGBA唄,則對於座標(x,y)(這裡的y是下方正向),RGBA分別是data[(y*width+x)*4],data[(y*width+x)*4+1],data[(y*width+x)*4+2],data[(y*width+x)*4+3]。
1
2
3
4
5
canvasData {
    width: unsigned long,
    height: unsigned long,
    data: CanvasPixelArray
}
至於畫素資料的重新整理,直接對上面的data[i]賦值不就得了。下面是重新整理影象,只需一行。
1
ctx.putImageData(canvasData, 0, 0);
下面是一個完整處理過程的樣例程式碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var canvas = document.getElementById('myCanvasElt');
var ctx = canvas.getContext('2d');
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (var x = 0; x < canvasData.width; x++) {
    for (var y = 0; y < canvasData.height; y++) {
        var idx = (x + y * canvas.width) * 4;
        var r = canvasData.data[idx + 0];
        var g = canvasData.data[idx + 1];
        var b = canvasData.data[idx + 2];
        var avg = (r + g + b) / 3;
        canvasData.data[idx + 0] = avg;
        canvasData.data[idx + 1] = avg;
        canvasData.data[idx + 2] = avg;
    }
}
ctx.putImageData(canvasData, 0, 0);
牛刀小試:亮度調整、透明化、灰化、反色、對比度增強、侵蝕和膨脹 亮度處理和透明化處理的過程非常簡單,就是重新整理一下RGBA四個值而已。亮度提高可以通過增大RGB值實現,比如我們給RGB三個值分別加100(請放心,如果結果超過255計算機會自動按255處理)就實現了亮度的提高。而我們把A值賦一個127,則實現了半透明。賦值過程使用下面的程式碼替代掉上面程式碼樣例中的幾層for迴圈即可。
1
2
3
4
5
6
7
var offset = 100; //自定義
for (var i=0; i< canvasData.data.length; i+=4) {
	d[i] += offset;
	d[i+1] += offset;
	d[i+2] += offset;
	d[i+3] = 127;
}
灰化的實現要分析人類視覺的特點,人眼弱於識別紅和藍,所以需要調低他們的亮度。科學家們整理出一個灰化公式,將RGB都賦值為 0.2126*r+0.7152*g+0.0722*b即可實現彩色影象灰度化。這很簡單,不再給出程式碼樣例。科學界值得一提的一項設計就是彩色電視訊號無需任何其它處理即可被黑白電視機接受並輸出為黑白顯示結果,當然這與我們這裡的灰化處理並不一樣,只是順便提一句。 反色只要用255減去各點RGB值。 對比度增強只要各點的RGB值乘以2再減掉255或者150(可以根據需要設定),下界為0。 侵蝕:中心畫素取周邊8個畫素的最亮值,可用於去除小的噪點。 膨脹:中心畫素取周邊8個畫素的最暗值,可用於加粗字型、製作氖燈效果。 利劍出鞘:圖形中的字元識別 你沒看錯,就是利用canvas進行影象處理實現字元識別,本節以驗證碼識別為例來展開。一個普通的驗證碼(騰訊、迅雷、Google都有推出連人都很難識別出來的驗證碼,復旦大學選課系統還推出了微積分計算驗證碼,這一類我們就先不讓計算機做嘗試了,這太殘酷了),通常由淺色的噪音干擾和深色字元組成。我們需要將驗證碼的圖形做二值化處理,也就是通過計算,把淺色的統一處理成白色,深色的統一處理成黑色,然後提取出黑白的二進位制RGB值,重新整理足夠多的次數,把0-9的RGB碼值特徵都拿到手。然後對於一個新的驗證碼,我們通過對比這些特徵碼,就可以識別出是哪幾個數字。 首先我們從某站點找到了一種無扭曲的0-9四位驗證碼,然後提取出特徵碼numbers=["111000111100000001100111001001111100001111100001111100001111100001111100001111100001111100100111001100000001111000111111111111111111111111111111","111000111100000111100000111111100111111100111111100111111100111111100111111100111111100111111100111100000000100000000111111111111111111111111111","100000111000000011011111001111111001111111001111110011111100111111001111110011111100111111001111111000000001000000001111111111111111111111111111","100000111000000001011111001111111001111110011100000111100000011111110001111111001111111001011110001000000011100000111111111111111111111111111111","111110011111100011111100011111000011110010011110010011100110011100110011000000000000000000111110011111110011111110011111111111111111111111111111","000000001000000001001111111001111111001111111000001111000000011111110001111111001111111001011110001000000011100000111111111111111111111111111111","111000011110000001100111101100111111001111111001000011000000001000111000001111100001111100100111000100000001111000011111111111111111111111111111","100000000100000000111111100111111101111111001111110011111110111111100111111101111111001111111001111110011111110011111111111111111111111111111111","110000011100000001100111001100111001100011011110000011110000011100110001001111100001111100000111000100000001110000011111111111111111111111111111","110000111100000001000111001001111100001111100000111000100000000110000100111111100111111001101111001100000011110000111111111111111111111111111111"]。通過以下方式處理即可得到其中的4個數字,我們就可以通過console看到識別結果了。如果把結果的值賦給驗證碼input元素的value,再模擬一個click()動作,那麼就可以免輸驗證碼直接登入了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var recResult = "";
var image = document.querySelector("#img1");
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
for (var i = 0; i < 4; i++) {
	var ldString = "";
	var getDat = ctx.getImageData(13 * i + 7, 3, 9, 16);
	var pixels = getDat.data;
	for (var j = 0,length = pixels.length; j < length; j += 4) {
		ldString = ldString + (+(pixels[j]*0.3+pixels[j+1]*0.59+pixels[j+2]*0.11>=140));
	}
	var comms = numbers.map(function (value) {
		return ldString.split("").filter(function(v,index) {
			return value[index] === v;
		}).length
	});
	recResult += comms.indexOf(Math.max.apply(null,comms));
}
console.log(recResult);
數學魅力:卷積核的鬼斧神工 模糊:模糊矩陣可以設定為全1矩陣,除數為9,相當於值全為1/9的矩陣。這個矩陣把周邊元素和中心元素做了一個平均數,從而使點間過渡更加光滑,也就實現了模糊。 銳化:銳化矩陣為[0 -1 0; -1 5 -1; 0 -1 0],本質是使中心點與上下左右幾個點的過渡更加粗糙,也就實現了銳化。 根據計算公式我們可以很清楚地理解矩陣的含義,所以下面不再進行具體說明,僅給出矩陣。 浮雕:[-2 -1 0; -1 1 1; 0 1 2]。 邊緣增強:[0 0 0; -1 1 0; 0 0 0]。 邊緣檢測:[0 1 0; 1 -4 1; 0 0 0]。 索貝爾邊緣檢測:橫向[-1 0 1; -2 0 2; -1 0 1],縱向[1 2 1; 0 0 0; -1 -2 -1]。 將以上矩陣代入ConvolutionMatrix()函式,並對畫素點進行計算即可實現這些效果。 另外,對視訊影象和圖片中的人物等物件進行識別、識圖搜尋也是目前電腦科學領域正在研究的方向,前景廣闊,這其中也有很多卷積運算、微積分等數學知識的應用。 試試看 光說不練假把式,效果預覽請訪問測試頁面,筆者在裡面給出了一些實現的樣例供參考。 如果讀者對canvas圖形感興趣,也可以訪問這個連結以飽眼福。 後記 以上我們介紹了一些影象處理的基礎知識,但通常我們在處理影象時是對區域性進行的,這種情況需要我們利用作業系統的API定位游標位置確定要對哪塊影象進行處理。如果您是專業讀者,建議您在理解這些原理後,自己嘗試開發一款影象處理軟體替代Photoshop,以規避高額的軟體授權費和盜版帶來的法律風險,當然完全替代還需要很多更復雜的理論知識,本文作為科普文章就不多加介紹了。 HTML5的canvas對於圖形的處理非常方便,很受開發人員的歡迎,更多canvas的應用也有待我們去探索。 問與答 如何將canvas處理得到的圖形儲存為檔案?答:canvas提供了toDataURL的介面,可以方便的將canvas畫布轉化成base64編碼的圖形。如果要直接把圖片生成後下載到本地可以直接改圖片的mimeType,強制改成steam流型別。 參考資料與擴充套件閱讀