圖像處理之vImage(二)——卷積

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 浮雕

圖1    浮雕
圖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;
}

上述代碼都做了哪些工作呢?

  1. 聲明一個浮雕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ù)組元素從左至右耻矮,依次是第一行、第二行忆谓、第三行裆装;
  2. 聲明一個vImage_Buffer變量,用來存儲原始圖像信息。圖像數(shù)據(jù)以數(shù)組的形式進(jìn)行存儲哨免,元素圖像二進(jìn)制數(shù)據(jù)(inData)茎活,另外還存儲高度、寬度和每行的字節(jié)數(shù)琢唾。這樣vImage會知道要處理的圖像大小载荔,并如何合適地處理它。
  3. 聲明一個vImage_Buffer變量采桃,用來存儲目標(biāo)圖像信息懒熙。
  4. 聲明一個Pixel8888-格式的像素來表示目標(biāo)圖像的背景色(示例中用的是黑色)。
  5. 聲明一個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_8ARGB8888數(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_Planar8vImageBoxConvolve_ARGB8888尔许。每個轉(zhuǎn)換后的像素都是其周圍像素的平均值(周圍像素的寬么鹤、高即kernel的寬、高)味廊。

圖9 box濾鏡

tent濾鏡用周圍像素的加權(quán)平均值來代替被處理的像素值蒸甜。對應(yīng)的函數(shù)是vImageTentConvolve_Planar8vImageTentConvolve_ARGB8888。tent濾鏡的模糊操作相當(dāng)于使用值不為1的kernel進(jìn)行的卷積操作余佛。和vImageBoxConvolve_Planar8vImageBoxConvolve_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
}

這段代碼都做了什么?

  1. 定義vImage要進(jìn)行反卷積處理的初始卷積圖像的kernel值衡怀。示例使用的是對稱的kernel(寬高都為3)屉来,因此不用再定義第二個kernel;
  2. 聲明一個vImage_Error結(jié)構(gòu)體狈癞,來存儲反卷積結(jié)果;
  3. 聲明一個Pixel8888-類型的像素茂契,用于表示轉(zhuǎn)換后圖像的背景色蝶桶;
  4. 聲明一個vImage_Buffer結(jié)構(gòu)體,用于存儲初始圖像信息掉冶。圖像數(shù)據(jù)是作為一個字節(jié)型數(shù)組存儲的真竖,包括圖像數(shù)據(jù)inData、寬厌小、高恢共、行字節(jié)等信息。這些信息可以讓vImage知道它要處理的圖像有多大璧亚,從而更好的執(zhí)行操作讨韭;
  5. 聲明一個vImage_Buffer結(jié)構(gòu)體,用于存儲目標(biāo)圖像信息;
  6. 把上述聲明的變量傳給vImage函數(shù)透硝。注意示例中在調(diào)用vImageRichardsonDeConvolve_ARGB8888函數(shù)時狰闪,第二個kernel為NULL,這是因?yàn)榈谝粋€kernel是對稱的濒生,沒有必要再進(jìn)行翻轉(zhuǎn)埋泵。

反卷積是一個迭代過程。你可以設(shè)置迭代次數(shù)罪治,次數(shù)不同丽声,得到的結(jié)果會不一樣,因此觉义,為了得到最理想的結(jié)果雁社,可以多試幾次差牛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末顾稀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桶现,更是在濱河造成了極大的恐慌厉碟,老刑警劉巖喊巍,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異箍鼓,居然都是意外死亡崭参,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門款咖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來何暮,“玉大人,你說我怎么就攤上這事铐殃『M荩” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵富腊,是天一觀的道長坏逢。 經(jīng)常有香客問我,道長赘被,這世上最難降的妖魔是什么是整? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮民假,結(jié)果婚禮上浮入,老公的妹妹穿的比我還像新娘。我一直安慰自己羊异,他們只是感情好事秀,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布彤断。 她就那樣靜靜地躺著,像睡著了一般秽晚。 火紅的嫁衣襯著肌膚如雪瓦糟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天赴蝇,我揣著相機(jī)與錄音菩浙,去河邊找鬼。 笑死句伶,一個胖子當(dāng)著我的面吹牛劲蜻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播考余,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼先嬉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了楚堤?” 一聲冷哼從身側(cè)響起疫蔓,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎身冬,沒想到半個月后衅胀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酥筝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年滚躯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘿歌。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡掸掏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宙帝,到底是詐尸還是另有隱情丧凤,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布步脓,位于F島的核電站息裸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沪编。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一年扩、第九天 我趴在偏房一處隱蔽的房頂上張望蚁廓。 院中可真熱鬧,春花似錦厨幻、人聲如沸相嵌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饭宾。三九已至批糟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間看铆,已是汗流浹背徽鼎。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弹惦,地道東北人否淤。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像棠隐,于是被迫代替她去往敵國和親石抡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容