vImage學(xué)習(xí)筆記——卷積(Convolution)
卷積(Convolution)是一個常用的圖像處理技術(shù)跪者,可以改變像素強(qiáng)度,從而影響周圍其他像素的強(qiáng)度川抡。卷積的常用技術(shù)是創(chuàng)建濾鏡,使用卷積技術(shù),你可以獲取一些流行的圖像效果倾鲫,比如模糊(blur)、銳化(sharpen)及邊緣檢測(edge detection)腕够,這些效果在Photo Booth级乍、iPhoto和Aperture都有廣泛使用。
如果你對圖像濾鏡和實(shí)時處理有興趣的話帚湘,你會發(fā)現(xiàn)vImage函數(shù)集的好處玫荣。用圖像濾鏡,卷積操作可以完成一些常用的濾鏡效果大诸,比如浮雕捅厂、模糊及色調(diào)分離贯卦。
vImage卷積技術(shù)對銳化或增強(qiáng)圖像質(zhì)量也很有用。當(dāng)處理一些科學(xué)圖像時焙贷,增強(qiáng)圖像質(zhì)量很有用撵割。此外,由于科學(xué)圖像通常都很大辙芍,就很有必要使用這些vImage技術(shù)來達(dá)到合適的性能需求啡彬。這種情況下你需要用到的技術(shù)有邊緣檢測(edge detection)、銳化故硅、描繪外觀輪廓(surface contour outlining)庶灿、平滑、及動作檢測(motion detection)吃衅。
本章節(jié)講述了卷積技術(shù)往踢,以及如何使用vImage提供的卷積函數(shù)。通過本文徘层,你可以:
- 了解卷積技術(shù)可以實(shí)現(xiàn)哪些效果峻呕;
- 學(xué)習(xí)什么是卷積核以及如何構(gòu)建卷積核;
- 通過代碼示例趣效,學(xué)習(xí)如何對一個圖像使用卷積技術(shù)瘦癌。
卷積核(Convolution Kernels)
圖1展示了一個圖像通過vImage卷積函數(shù)添加了浮雕效果前后的對比圖。為了達(dá)到這個效果英支,vImage使用一個類網(wǎng)格的數(shù)學(xué)概念佩憾,稱為核(kernel),來完成卷積操作干花。
圖1 浮雕

圖2是一個3×3的kernel妄帘。kernel的高度和寬度不必一樣,但必須是奇數(shù)池凄。kernel內(nèi)部的數(shù)值會影響卷積的整體效果抡驼。這些數(shù)值決定了初始圖像像素會如何轉(zhuǎn)換成目標(biāo)圖像像素,這看起來可能不是很直觀肿仑,9個數(shù)字會如何影響到濾鏡效果呢致盟?卷積技術(shù)經(jīng)過一系列的操作,根據(jù)周圍像素的強(qiáng)度改變當(dāng)前像素的強(qiáng)度尤慰。vImage根據(jù)kernel執(zhí)行卷積操作馏锡,這種通過kernel執(zhí)行卷積計(jì)算的過程就稱為kernel convolution(核卷積)。
圖2 3×3卷積

卷積是像素單位的操作伟端,即對每個像素都要執(zhí)行同樣的算法杯道。因此,大圖像比小圖像需要更多的卷積操作责蝠。一個kernel可以被看做一個二維的網(wǎng)格數(shù)據(jù)党巾,而圖像也可以被看做一個二維網(wǎng)格數(shù)據(jù)(如圖3)萎庭,對一個圖像應(yīng)用kernel可以想象成把一個小格子(kernel)平鋪在大格子(圖像)上。
圖3 圖像是二維網(wǎng)格數(shù)據(jù)

kernel內(nèi)部的數(shù)值會作為與它下面的數(shù)值相乘的乘數(shù)齿拂,下面的數(shù)值指的是被kernel數(shù)值覆蓋的像素的強(qiáng)度驳规。在進(jìn)行卷積計(jì)算時,把kernel的中心值覆蓋在待轉(zhuǎn)換的像素上署海,然后將kernel的每個值與其正下方的像素值相乘吗购,最后將所有的結(jié)果相加,相加后的結(jié)果就是新的像素強(qiáng)度叹侄。圖4展示了kernel是如何轉(zhuǎn)化像素的巩搏。
圖4 核卷積

雖然kernel會覆蓋到一些其它的像素,但是最終只有kernel中心值正下方的原始像素會發(fā)生變化趾代。kernel和圖像之間所有乘積相加后的和被稱為加權(quán)和。為了確保處理后的圖像對比原圖不會過于飽和丰辣,vImage有一個常用的方法撒强,就是設(shè)置一個除數(shù)因子,把加權(quán)和進(jìn)行拆分笙什。因?yàn)橛弥車袼氐募訖?quán)和來代替原始像素時常導(dǎo)致像素強(qiáng)度過大(并且圖像整體也過于明亮)飘哨,拆分加權(quán)和可以按比例降低濾鏡效果的強(qiáng)度,并確保維持原始亮度琐凭,這個過程稱為標(biāo)準(zhǔn)化芽隆。這個行為是可選的,被拆分后的加權(quán)和會代替原始像素值统屈。kernel對每個像素重復(fù)這個過程胚吁。
注:如果要執(zhí)行標(biāo)準(zhǔn)化,你必須向卷積函數(shù)提供你要使用的因子愁憔。因子最好是2的冪次方腕扶。你也可以在圖像像素值為整數(shù)的時候再提供因子。浮點(diǎn)型不需要使用吨掌,因?yàn)槟憧梢灾苯右辣壤龥Q定kernel的浮點(diǎn)型數(shù)值來達(dá)到標(biāo)準(zhǔn)化半抱。
kernel的數(shù)據(jù)類型和圖像的數(shù)據(jù)類型必須保持一致,比如膜宋,如果圖像像素?cái)?shù)據(jù)類型是浮點(diǎn)型窿侈,那么kernel中的數(shù)據(jù)類型也必須是浮點(diǎn)型。
記住以上所述的算法vImage都已經(jīng)幫你做好了秋茫,你不需要牢記卷積算法的步驟史简。當(dāng)然,你也可以在自定義的核中實(shí)現(xiàn)該算法学辱。
反卷積
反卷積指的是解除先前的卷積效果——一般是原始圖像中物理攜帶的卷積效果乘瓤,比如鏡頭中的衍射效果环形。通常,反卷積是一個銳化操作衙傀。
反卷積的算法比較多抬吟,vImage用的是Richardson-Lucy deconvolution算法。
Richardson-Lucy deconvolution算法的目標(biāo)是根據(jù)卷積后的像素值找到原始的像素值统抬,以及kernel數(shù)據(jù)火本。
基于以上需求,在使用反卷積函數(shù)時必須提供卷積后的圖像及卷積使用的kernel值聪建。
vImage會自行處理反卷積的每一步操作钙畔,因此不需要牢記這些步驟。使用反卷積的時候金麸,必須提供初始的卷積kernel(如果該kernel不對稱的話擎析,還要額外提供一個對角線翻轉(zhuǎn)的kernel2)。
使用卷積核
現(xiàn)在你最好了解一下核的結(jié)構(gòu)以及卷積的處理過程挥下,是時候使用幾個vImage函數(shù)來看看了揍魂。本章節(jié)展示了如何實(shí)現(xiàn)圖1中的浮雕效果,并解釋了無偏差卷積和帶偏差卷積之間的差別棚瘟。
卷積
vImage可以自動進(jìn)行卷積計(jì)算现斋,而你的工作是提供kernel,即描述卷積應(yīng)該生成什么效果偎蘸。表1展示了如何使用卷積去生成浮雕效果庄蹋。你也可以通過合適的kernel,利用同樣的代碼來生成一個不同的效果迷雪,比如銳化限书。
表1 生成浮雕效果
int myEmboss(void *inData,
unsigned int inRowBytes,
void *outData,
unsigned int outRowBytes,
unsigned int height,
unsigned int width,
void *kernel,
unsigned int kernel_height,
unsigned int kernel_width,
int divisor ,
vImage_Flags flags )
{
uint_8 kernel = {-2, -2, 0, -2, 6, 0, 0, 0, 0}; // 1
vImage_Buffer src = { inData, height, width, inRowBytes }; // 2
vImage_Buffer dest = { outData, height, width, outRowBytes }; // 3
unsigned char bgColor[4] = { 0, 0, 0, 0 }; // 4
vImage_Error err; // 5
err = vImageConvolve_ARGB8888( &src, //const vImage_Buffer *src
&dest, //const vImage_Buffer *dest,
NULL,
0, //unsigned int srcOffsetToROI_X,
0, //unsigned int srcOffsetToROI_Y,
kernel, //const signed int *kernel,
kernel_height, //unsigned int
kernel_width, //unsigned int
divisor, //int
bgColor,
flags | kvImageBackgroundColorFill
//vImage_Flags flags
);
return err;
}
上述代碼都做了哪些工作呢?
- 聲明一個浮雕kernel振乏,即int型數(shù)組蔗包。kernel的數(shù)據(jù)類型要與相應(yīng)的vImage函數(shù)所需數(shù)據(jù)類型相匹配。示例中使用了vImageConvolve_ARGB8888函數(shù)慧邮,因此kernel的數(shù)據(jù)類型應(yīng)該是uint_8(無符號调限,8-bit,整數(shù))误澳。kernel的數(shù)組元素從左至右耻矮,依次是第一行、第二行忆谓、第三行裆装;
- 聲明一個vImage_Buffer變量,用來存儲原始圖像信息。圖像數(shù)據(jù)以數(shù)組的形式進(jìn)行存儲哨免,元素圖像二進(jìn)制數(shù)據(jù)(inData)茎活,另外還存儲高度、寬度和每行的字節(jié)數(shù)琢唾。這樣vImage會知道要處理的圖像大小载荔,并如何合適地處理它。
- 聲明一個vImage_Buffer變量采桃,用來存儲目標(biāo)圖像信息懒熙。
- 聲明一個Pixel8888-格式的像素來表示目標(biāo)圖像的背景色(示例中用的是黑色)。
- 聲明一個vImage_Err變量來存儲卷積函數(shù)的返回值普办。
然后工扎,將這些聲明的變量值傳入vImageConvolve_ARGB8888函數(shù)中,由vImage來處理后續(xù)的計(jì)算衔蹲,并把結(jié)果存儲到dest變量中肢娘。vImageConvolve_ARGB8888函數(shù)是vImage中僅有的幾個卷積函數(shù)之一。通常舆驶,vImage會為每種圖像格式提供4種函數(shù)變體蔬浙,ARGB8888前綴表示該函數(shù)處理的是交叉型圖像(full-color),每個像素由四個8字節(jié)的整數(shù)構(gòu)成一組贞远,代表alpha(A),red(R)笨忌,green(G)和blue(B)四個通道蓝仲。想了解vImage支持的圖像格式的更多細(xì)節(jié),請看vImage概述
示例myEmboss中還使用了vImage_Flags參數(shù)官疲。該參數(shù)由1個或多個flags(用或邏輯運(yùn)算符 | 連接)組成袱结。kvImageBackgroundColorFill表示vImage要使用預(yù)先提供的背景色。
為了熟悉kernel效果的使用方法途凫,請?jiān)谀阕约旱拇a中使用以下兩個kernel垢夹。圖6的kernel可以生成高斯模糊效果,圖7的kernel可以生成邊緣檢測效果维费。
圖6 高斯模糊

圖7 邊緣檢測

帶偏差的卷積
在執(zhí)行卷積操作時果元,可以選擇是否帶偏差。偏差是指在卷積結(jié)果上再額外添加一個來自周圍像素的影響犀盟。由于某些卷積計(jì)算得到的結(jié)果可能為負(fù)值而晒,偏差可以避免信號溢出≡某耄可以把偏差設(shè)為127或128倡怎,來允許負(fù)值也被描繪出來。偏差可能使整體圖像效果變亮或變暗。
每個標(biāo)準(zhǔn)的vImage函數(shù)(比如vImageConvolve_PlanarF)都有一個對應(yīng)的帶偏差的函數(shù)(vImageConvolveWithBias_PlanarF)监署。偏差函數(shù)的使用方法和無偏差函數(shù)一樣颤专,除了必須設(shè)置bias參數(shù)來使用偏差。bias的數(shù)據(jù)類型必須與圖像像素?cái)?shù)據(jù)類型一致钠乏。
圖8 帶偏差與無偏差

使用高速濾鏡
vImage提供了一些特定的卷積函數(shù)栖秕,這些比一般的卷積函數(shù)具有更快的處理速度。OS X v10.4以上的系統(tǒng)缓熟,對于Planar_8和ARGB8888數(shù)據(jù)類型可以使用box濾鏡和tent濾鏡累魔。這些濾鏡可以得到模糊效果,函數(shù)是根據(jù)他們在笛卡爾坐標(biāo)系的形狀命名的够滑。調(diào)用這些函數(shù)不需要提供kernel垦写,效果等同于需要提供kernel的一般的卷積函數(shù)。但是這些函數(shù)比一般的函數(shù)性能上要快大約1個量級彰触。
注:由于這些函數(shù)需要一個穩(wěn)健精確的算法梯投,vImage規(guī)定這些函數(shù)不支持浮點(diǎn)型。浮點(diǎn)型的計(jì)算誤差會導(dǎo)致圖像高密度區(qū)域附近的低密度區(qū)域顯得人工化或粗糙化况毅。
box濾鏡用周圍像素的未加權(quán)的平均數(shù)來代替被處理的像素值分蓖,相當(dāng)于通過所有值都為1的kernel來進(jìn)行卷積處理。對應(yīng)的函數(shù)是vImageBoxConvolve_Planar8和vImageBoxConvolve_ARGB8888尔许。每個轉(zhuǎn)換后的像素都是其周圍像素的平均值(周圍像素的寬么鹤、高即kernel的寬、高)味廊。
圖9 box濾鏡

tent濾鏡用周圍像素的加權(quán)平均值來代替被處理的像素值蒸甜。對應(yīng)的函數(shù)是vImageTentConvolve_Planar8和vImageTentConvolve_ARGB8888。tent濾鏡的模糊操作相當(dāng)于使用值不為1的kernel進(jìn)行的卷積操作余佛。和vImageBoxConvolve_Planar8和vImageBoxConvolve_ARGB8888一樣柠新,不需要向函數(shù)提供kernel值,只需要寬高即可辉巡。
圖10 tent濾鏡

假設(shè)kernel的大小是3×5恨憎。那么第一個矩陣是

第二個矩陣是

那么生成的kernel是

3×5的tent濾鏡操作相當(dāng)于使用上圖中的濾鏡來進(jìn)行卷積操作。
使用多核
vImage允許在單個卷積操作中使用多個kernel郊楣°究遥可以使用vImageConvolveMultiKernel函數(shù),分別定義四個kernel痢甘,每個kernel對應(yīng)一個圖像通道喇嘱。一個kernel控制一個通道的話,你就可以對圖像進(jìn)行更高級別的處理塞栅。例如者铜,你可以利用多核卷積對圖像的顏色通道分別重新采樣腔丧,抵消屏幕上的RGB熒光效果。由于四個kernel可以分別對單個顏色通道進(jìn)行處理作烟,vImageConvolveMultiKernel函數(shù)只能應(yīng)用于交叉型圖像愉粤。
這些函數(shù)的使用方法與單核卷積函數(shù)使用方法相同,唯一不同的是拿撩,需要提供一個指針數(shù)組衣厘,數(shù)組中每個元素指向一個kernel地址。
反卷積
與卷積計(jì)算相同的是压恒,vImage同樣在內(nèi)部封裝了反卷積的計(jì)算過程影暴,你只需要提供一個kernel即可。表2展示了如何把浮雕效果通過反卷積消除的過程探赫。你可以用同樣的代碼型宙、合適的kernel去反卷積各種效果(比如模糊效果)。但是不同于卷積操作的是伦吠,反卷積函數(shù)還需要另一個kernel參數(shù)妆兑。除非kernel的寬和高相同,否則這個參數(shù)不能為NULL毛仪。如果kernel的寬和高不相等搁嗓,必須再提供一個行列反轉(zhuǎn)的kernel。
表2是一段示例代碼箱靴,描述了如何使用vImage對一個ARGB8888-格式的圖像進(jìn)行反卷積腺逛。
int myDeconvolve(void *inData,
unsigned int inRowBytes,
void *outData,
unsigned int outRowBytes,
unsigned int height,
unsigned int width,
void *kernel,
unsigned int kernel_height,
unsigned int kernel_width,
int divisor,
int iterationCount,
vImage_Flags flags )
{
//Prepare data structures
uint_8 kernel = {-2, -2, 0, -2, 6, 0, 0, 0, 0}; // 1
vImage_Error err; // 2
unsigned char bgColor[4] = { 0, 0, 0, 0 }; // 3
vImage_Buffer src = { inData, height, width, inRowBytes }; // 4
vImage_Buffer dest = { outData, height, width, outRowBytes }; // 5
//Send data to vImage for processing
err = vImageRichardsonLucyDeConvolve_ARGB8888( &src, // 6
&dest, //const vImage_Buffer *dest,
NULL,
0, //unsigned int srcOffsetToROI_X,
0, //unsigned int srcOffsetToROI_Y,
kernel, //const signed int *kernel,
NULL, //assumes symmetric kernel
kernel_height, //unsigned int kernel_height,
kernel_width, //unsigned int kernel_width,
0, //height of second kernel
0, //width of second kernel
divisor, //int
0, //for second kernel
bgColor,
iterationCount, //uint32_t
kvImageBackgroundColorFill | flags
//vImage_Flags
);
//Report result
return err; // 7
}
這段代碼都做了什么?
- 定義vImage要進(jìn)行反卷積處理的初始卷積圖像的kernel值衡怀。示例使用的是對稱的kernel(寬高都為3)屉来,因此不用再定義第二個kernel;
- 聲明一個vImage_Error結(jié)構(gòu)體狈癞,來存儲反卷積結(jié)果;
- 聲明一個Pixel8888-類型的像素茂契,用于表示轉(zhuǎn)換后圖像的背景色蝶桶;
- 聲明一個vImage_Buffer結(jié)構(gòu)體,用于存儲初始圖像信息掉冶。圖像數(shù)據(jù)是作為一個字節(jié)型數(shù)組存儲的真竖,包括圖像數(shù)據(jù)inData、寬厌小、高恢共、行字節(jié)等信息。這些信息可以讓vImage知道它要處理的圖像有多大璧亚,從而更好的執(zhí)行操作讨韭;
- 聲明一個vImage_Buffer結(jié)構(gòu)體,用于存儲目標(biāo)圖像信息;
- 把上述聲明的變量傳給vImage函數(shù)透硝。注意示例中在調(diào)用vImageRichardsonDeConvolve_ARGB8888函數(shù)時狰闪,第二個kernel為NULL,這是因?yàn)榈谝粋€kernel是對稱的濒生,沒有必要再進(jìn)行翻轉(zhuǎn)埋泵。
反卷積是一個迭代過程。你可以設(shè)置迭代次數(shù)罪治,次數(shù)不同丽声,得到的結(jié)果會不一樣,因此觉义,為了得到最理想的結(jié)果雁社,可以多試幾次差牛。