幾種常見計算機圖像處理操作的原理及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流類型。

參考資料與擴展閱讀

http://hacks.mozilla.org/2009/06/pushing-pixels-with-canvas/(文中有一個賦值錯誤)

http://docs.gimp.org/en/filters-generic.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章