Core Image框架詳細(xì)解析(十五) —— 創(chuàng)建自定義濾波器 Creating Custom Filters(二)

版本記錄

版本號 時間
V1.0 2018.01.29

前言

Core Image是IOS5中新加入的一個框架,里面提供了強(qiáng)大高效的圖像處理功能,用來對基于像素的圖像進(jìn)行操作與分析。還提供了很多強(qiáng)大的濾鏡,可以實(shí)現(xiàn)你想要的效果,下面我們就一起解析一下這個框架。感興趣的可以參考上面幾篇。
1. Core Image框架詳細(xì)解析(一) —— 基本概覽
2. Core Image框架詳細(xì)解析(二) —— Core Image濾波器參考
3. Core Image框架詳細(xì)解析(三) —— 關(guān)于Core Image
4. Core Image框架詳細(xì)解析(四) —— Processing Images處理圖像(一)
5. Core Image框架詳細(xì)解析(五) —— Processing Images處理圖像(二)
6. Core Image框架詳細(xì)解析(六) —— 圖像中的面部識別Detecting Faces in an Image(一)
7. Core Image框架詳細(xì)解析(七) —— 自動增強(qiáng)圖像 Auto Enhancing Images
8. Core Image框架詳細(xì)解析(八) —— 查詢系統(tǒng)中的過濾器 Querying the System for Filters
9. Core Image框架詳細(xì)解析(九) —— 子類化CIFilter:自定義效果的配方 Subclassing CIFilter: Recipes for Custom Effects(一)
10. Core Image框架詳細(xì)解析(十) —— 子類化CIFilter:自定義效果的配方 Subclassing CIFilter: Recipes for Custom Effects(二)
11. Core Image框架詳細(xì)解析(十一) —— 獲得最佳性能 Getting the Best Performance
12. Core Image框架詳細(xì)解析(十二) —— 使用反饋處理圖像 Using Feedback to Process Images
13. Core Image框架詳細(xì)解析(十三) —— 在寫一個自定義濾波器之前你需要知道什么?
14. Core Image框架詳細(xì)解析(十四) —— 創(chuàng)建自定義濾波器 Creating Custom Filters(一)

Using Your Own Custom Filter - 使用你的自定義濾波器

使用自定義過濾器的過程與使用Core Image提供的任何過濾器的過程相同,只不過您必須初始化過濾器類。 您使用以下代碼行初始化上一節(jié)中創(chuàng)建的haze removal filter類:

[MyHazeFilter class];

Listing 9-8顯示了如何使用haze removal filter。 請注意此代碼與Processing Images中討論的代碼之間的相似性。

注意:如果您將過濾器打包為圖像單元,則需要加載它。 詳情請參閱Processing Images。

// Listing 9-8  Using your own custom filter

- (void)drawRect: (NSRect)rect
{
    CGRect  cg = CGRectMake(NSMinX(rect), NSMinY(rect),
                            NSWidth(rect), NSHeight(rect));
    CIContext *context = [[NSGraphicsContext currentContext] CIContext];
 
    if(filter == nil) {
        NSURL       *url;
 
        [MyHazeFilter class];
 
        url = [NSURL fileURLWithPath: [[NSBundle mainBundle]
                    pathForResource: @"CraterLake"  ofType: @"jpg"]];
        filter = [CIFilter filterWithName: @"MyHazeRemover"
                            withInputParameters:@{
                  kCIInputImageKey: [CIImage imageWithContentsOfURL: url],
                  kCIInputColorKey: [CIColor colorWithRed:0.7 green:0.9 blue:1],
                  }];
    }
 
    [filter setValue: @(distance) forKey: @"inputDistance"];
    [filter setValue: @(slope) forKey: @"inputSlope"];
 
    [context drawImage: [filter valueForKey: kCIOutputImageKey]
        atPoint: cg.origin  fromRect: cg];
}

Supplying an ROI Function - 提供ROI功能

感興趣區(qū)域或ROI定義了采樣器采用像素信息提供給內(nèi)核進(jìn)行處理的源中的區(qū)域。 回想一下在Querying the System for Filters討論的感興趣The Region of Interest過濾器的ROI和DOD的工作空間坐標(biāo)是否完全一致,是相互依賴還是不相關(guān)。 Core Image總是假設(shè)ROI和DOD重合。 如果您所編寫的過濾器屬于這種情況,則不需要提供ROI功能。 但是,如果這個假設(shè)對于你寫的過濾器來說不成立,那么你必須提供一個ROI函數(shù)。 此外,您只能為CPU可執(zhí)行過濾器提供ROI功能。

Note: The ROI and domain of definition for a CPU nonexecutable filter must coincide. This is also the case for color kernels described by the CIColorKernel class. You can’t supply an ROI function for these types of filter. See Writing Nonexecutable Filters. 注意:CPU不可執(zhí)行過濾器的ROI和定義域必須重合。 CIColorKernel類描述的顏色內(nèi)核也是這種情況。 您無法為這些類型的過濾器提供ROI功能。 請參閱Writing Nonexecutable Filters。

您提供的ROI函數(shù)計(jì)算內(nèi)核使用的每個采樣器的感興趣區(qū)域。Core Image調(diào)用您的ROI函數(shù),將采樣器索引,渲染區(qū)域的范圍以及您的例程所需的任何數(shù)據(jù)傳遞給它。 在OS X v10.11及更高版本以及iOS 8.0和更高版本中,推薦的應(yīng)用過濾器的方法是applyWithExtent:roiCallback:arguments:或aapplyWithExtent:roiCallback:inputImage:arguments:方法,將回調(diào)函數(shù)作為塊 (Objective-C)或者關(guān)閉(Swift)。

Note: In OS X v10.10 and earlier, use the setROISelector: method to provide an ROI function before calling the filter’s apply: or apply:arguments:options:method. The discussions below assume the OS X v10.11 and iOS 8.0 API; however, the inner workings of each example ROI function are the same for both the newer and older APIs.For details on the selector form of an ROI function, see the reference documentation for the setROISelector: method. 注意:在OS X v10.10及更早版本中,在調(diào)用過濾器的apply:apply:arguments:options:方法之前,使用setROISelector:方法提供一個ROI函數(shù)。 下面的討論假設(shè)OS X v10.11和iOS 8.0 API,然而,每個示例ROI函數(shù)的內(nèi)部工作對于較新和較舊的API都是相同的。有關(guān)ROI函數(shù)的選擇器形式的詳細(xì)信息,請參閱setROISelector:方法的參考文檔。

ROI回調(diào)是簽名符合CIKernelROICallback類型的callbackclosure。 該塊有兩個參數(shù):第一個index,指定方法計(jì)算ROI的采樣器,第二個rect,指定需要ROI信息的區(qū)域的范圍。Core Image每次通過過濾器都會調(diào)用您的例程。 您的方法基于傳遞給它的矩形計(jì)算ROI,并返回指定為CGRect結(jié)構(gòu)的ROI。

接下來的部分提供了ROI功能的例子。

1. A Simple ROI Function - 一個簡單的ROI函數(shù)

如果您的ROI函數(shù)不需要在userInfo參數(shù)中傳遞數(shù)據(jù),則不需要包含該參數(shù),如Listing 9-9所示。Listing 9-9中的代碼將采樣器置換一個像素,這是由邊緣檢測過濾器或任何3x3卷積使用的計(jì)算

// Listing 9-9  A simple ROI function

CIKernelROICallback callback = ^(int index, CGRect rect) {
    return CGRectInset(rect, -1.0, -1.0);
};

請注意,這個函數(shù)忽略index值。 如果你的內(nèi)核只使用一個采樣器,那么你可以忽略這個索引。 如果您的內(nèi)核使用多個采樣器,則必須確保您返回適用于指定采樣器的ROI。 你會在后面的章節(jié)看到如何做到這一點(diǎn)。

2. An ROI Function for a Glass Distortion Filter - Glass Distortion濾波器的ROI函數(shù)

Listing 9-10顯示了一個glass distortion濾波器的ROI函數(shù)。這個函數(shù)為兩個采樣器返回一個ROI。采樣器0表示要變形的圖像,采樣器1表示用于玻璃的紋理。

與塊(Objective-C)或閉包(Swift)的其他用法一樣,ROI回調(diào)可以從定義的上下文中獲取狀態(tài)。您可以使用此行為為您的例程提供附加參數(shù),如本例所示:外部值scale控制由ROI函數(shù)應(yīng)用的插入。 (當(dāng)使用較舊的setROISelector:API時,可以通過傳遞給apply:arguments:options:方法的option字典中的kCIApplyOptionUserInfo鍵來提供這樣的值。

所有的玻璃紋理(采樣器1)都需要被引用,因?yàn)檫^濾器使用紋理作為矩形圖案。結(jié)果,函數(shù)返回一個不確定的矩形作為ROI。一個不確定的矩形是一個約定,指示使用所有的采樣器。 (常量CGRectInfiniteQuartz 2D API中定義。)

Note: If you use an infinite ROI make sure that the sampler’s domain of definition is not also infinite. Otherwise, Core Image will not be able to render the image. 注意:如果您使用不確定的ROI,請確保采樣器的定義域不是無限的。 否則,Core Image將無法呈現(xiàn)圖像。

// Listing 9-10  An ROI function for a glass distortion filter

float scale = 1.0f;
CIKernelROICallback distortionCallback = ^(int index, CGRect rect) {
    if (index == 0) {
        CGFloat s = scale * 0.5f;
        return CGRectInset(rect, -s,-s);
    }
    return CGRectInfinite;
}

3. An ROI Function for an Environment Map - 環(huán)境映射的ROI函數(shù)

Listing 9-11顯示了一個ROI函數(shù),該函數(shù)返回使用三個采樣器的內(nèi)核的ROI,其中一個是環(huán)境映射。 采樣器0和采樣器1的ROI與DOD一致。 出于這個原因,代碼返回除采樣器2以外的采樣器傳遞給它的destination矩形。

Sampler 2使用捕獲的值來指定環(huán)境貼圖的大小,以創(chuàng)建指定感興趣區(qū)域的矩形。

// Listing 9-11  Supplying a routine that calculates the region of interest

CGRect sampler2ROI = CGRectMake(0, 0, envMapWidth, envMapHeight);
CIKernelROICallback envMapROICallback = ^(int index, CGRect rect) {
    if (samplerIndex == 2) {
        return sampler2ROI;
    }
    return destination;
};

4. Specifying Sampler Order - 指定采樣器順序

正如你從前面的例子看到的,一個采樣器有一個與之相關(guān)的索引。 當(dāng)您提供ROI功能時,Core Image會將采樣器索引傳遞給您。 采樣器索引在傳遞給過濾器的apply方法時根據(jù)其順序進(jìn)行分配。 您可以從過濾器的outputImage例程中調(diào)用apply,如Listing 9-12所示。

在這個清單中,請注意特別是設(shè)置采樣器的編號的代碼行,并顯示如何將它們提供給內(nèi)核。 Listing 9-12給出了每一行的詳細(xì)解釋

// Listing 9-12  An output image routine for a filter that uses an environment map

- (CIImage *)outputImage
{
    int i;
    CISampler *src, *blur, *env;                                      // 1
    CIVector *envscale;
    CIKernel *kernel;
 
    src = [CISampler samplerWithImage:inputImage];                    // 2
    blur = [CISampler samplerWithImage:inputHeightImage];             // 3
    env = [CISampler samplerWithImage:inputEnvironmentMap];           // 4
    envscale = [CIVector vectorWithX:[inputEMapWidth floatValue]
                     Y:[inputEMapHeight floatValue]];
    i = [inputKind intValue];
    if ([inputHeightInAlpha boolValue]) {
        i += 8;
    }
    kernel = roundLayerKernels[i];
    return [kernel applyWithExtent: [self extent]                      // 5
                       roiCallback: envMapROICallback                  // 6
                         arguments: @[                                 // 7
                               blur,
                               env,
                               @( pow(10.0, [inputSurfaceScale floatValue]) ),
                               envscale,
                               inputEMapOpacity,
                         ]];
}

代碼要做的事如下:

    1. 為內(nèi)核所需的三個采樣器中的每一個聲明變量。
    1. 為輸入圖像設(shè)置采樣器。這個采樣器的ROI與DOD相符。
    1. 為用于輸入高度的圖像設(shè)置采樣器。這個采樣器的ROI與DOD相符。
    1. environment map設(shè)置采樣器。這個采樣器的ROI不符合DOD,這意味著你必須提供一個ROI函數(shù)。
    1. 使用以下選項(xiàng)將內(nèi)核應(yīng)用于生成Core Image圖像(CIImage對象)。
    1. 需要使用內(nèi)核的ROI函數(shù)。
    1. 內(nèi)核函數(shù)的參數(shù),必須與內(nèi)核函數(shù)的函數(shù)簽名類型兼容。 (內(nèi)核函數(shù)源在這里沒有顯示,假設(shè)它們在這個例子中是類型兼容的)。
    • 采樣器參數(shù)的順序決定了它的索引。提供給內(nèi)核的第一個采樣器是索引0,在這種情況下,這是src采樣器。提供給kernel-blur的第二個采樣器分配索引1,第三個采樣器env分配索引2。檢查您的ROI函數(shù)以確保為每個采樣器提供適當(dāng)?shù)腞OI是很重要的。

Writing Nonexecutable Filters - 編寫不可執(zhí)行的過濾器

保證CPU不可執(zhí)行的過濾器是安全的。由于此類過濾器只能在GPU上運(yùn)行,因此無法進(jìn)行病毒或特洛伊木馬活動或其他惡意行為。為了保證安全,CPU不可執(zhí)行的過濾器有這些限制:

  • 這種類型的過濾器是一個純粹的內(nèi)核,這意味著它完全包含在.cikernel文件中。因此,它沒有過濾器類,并且限制了它可以提供的處理類型。以下形式的采樣指令是對非可執(zhí)行過濾器有效的唯一采樣指令類型:
color = sample(someSrc,samplerCoord(someSrc));
  • 必須將CPU不可執(zhí)行的過濾器打包為圖像單元的一部分。
  • Core Image假設(shè)ROI與DOD相符。這意味著不可執(zhí)行的濾鏡不適合模糊或失真等效果。

CIDemoImageUnit示例在MyKernelFilter.cikernel文件中包含一個不可執(zhí)行的過濾器。加載圖像單元時,MyKernelFilter過濾器將與圖像單元中的FunHouseMirror過濾器一起加載。然而,FunHouseMirror是一個可執(zhí)行的過濾器。它有一個Objective-C部分以及一個內(nèi)核部分。

當(dāng)您編寫不可執(zhí)行的過濾器時,需要在Descriptions.plist文件中為圖像單元包提供所有過濾器屬性。Listing 9-13顯示了CIDemoImageUnit示例中MyKernelFilter的屬性。

// Listing 9-13  The property list for the MyKernelFilter nonexecutable filter

<key>MyKernelFilter</key>
        <dict>
            <key>CIFilterAttributes</key>
            <dict>
                <key>CIAttributeFilterCategories</key>
                <array>
                    <string>CICategoryStylize</string>
                    <string>CICategoryVideo</string>
                    <string>CICategoryStillImage</string>
                </array>
                <key>CIAttributeFilterDisplayName</key>
                <string>MyKernelFilter</string>
                <key>CIInputs</key>
                <array>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>CIImage</string>
                        <key>CIAttributeDisplayName</key>
                        <string>inputImage</string>
                        <key>CIAttributeName</key>
                        <string>inputImage</string>
                    </dict>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>NSNumber</string>
                        <key>CIAttributeDefault</key>
                        <real>8</real>
                        <key>CIAttributeDisplayName</key>
                        <string>inputScale</string>
                        <key>CIAttributeIdentity</key>
                        <real>8</real>
                        <key>CIAttributeMin</key>
                        <real>1</real>
                        <key>CIAttributeName</key>
                        <string>inputScale</string>
                        <key>CIAttributeSliderMax</key>
                        <real>16</real>
                        <key>CIAttributeSliderMin</key>
                        <real>1</real>
                    </dict>
                    <dict>
                        <key>CIAttributeClass</key>
                        <string>NSNumber</string>
                        <key>CIAttributeDefault</key>
                        <real>1.2</real>
                        <key>CIAttributeDisplayName</key>
                        <string> inputGreenWeight </string>
                        <key>CIAttributeIdentity</key>
                        <real>1.2</real>
                        <key>CIAttributeMin</key>
                        <real>1</real>
                        <key>CIAttributeName</key>
                        <string>inputGreenWeight</string>
                        <key>CIAttributeSliderMax</key>
                        <real>3.0</real>
                        <key>CIAttributeSliderMin</key>
                        <real>1</real>
                    </dict>
                </array>
            </dict>
            <key>CIFilterClass</key>
            <string>MyKernelFilter</string>
            <key>CIHasCustomInterface</key>
            <false/>
            <key>CIKernelFile</key>
            <key> MyKernelFilter </key>

Kernel Routine Examples - 內(nèi)核例程

任何圖像處理濾鏡的本質(zhì)是執(zhí)行像素計(jì)算的內(nèi)核。 本節(jié)中的代碼清單顯示了這些過濾器的一些典型內(nèi)核例程:增亮,乘法和空洞失真。 通過查看這些內(nèi)容,您可以了解如何編寫自己的內(nèi)核例程。 但是,請注意,這些例程是例子。 不要以為這里顯示的代碼是Core Image用于它所提供的過濾器的內(nèi)容。

在編寫自己的內(nèi)核例程之前,您可能需要閱讀Expressing Image Processing Operations in Core Image,以查看哪些操作在Core Image中構(gòu)成挑戰(zhàn)。 你也想看看Core Image Kernel Language Reference

您可以在Image Unit Tutorial中找到有關(guān)編寫內(nèi)核的深入信息以及更多示例。

1. Computing a Brightening Effect - 計(jì)算一個明亮的效果

Listing 9-14計(jì)算了一個增亮效果。 列表后面會出現(xiàn)每行代碼的詳細(xì)說明。

// Listing 9-14  A kernel routine that computes a brightening effect

kernel vec4 brightenEffect (sampler src, float k)
{
  vec4 currentSource = sample (src, samplerCoord (src));         // 1
  currentSource.rgb = currentSource.rgb + k * currentSource.a;   // 2
  return currentSource;                                          // 3
}

代碼作用如下:

  • 在采樣器中查找與當(dāng)前輸出位置關(guān)聯(lián)的源像素。
  • 為像素值添加一個偏差。 偏移量由像素的alpha值進(jìn)行縮放,以確保像素值是預(yù)乘。
  • 返回已更改的像素。

2. Computing a Multiply Effect - 計(jì)算乘法效應(yīng)

Listing 9-15顯示了計(jì)算乘法效果的內(nèi)核例程。 代碼在采樣器中查找源像素,然后將其乘以傳遞給例程的值。

// Listing 9-15  A kernel routine that computes a multiply effect

kernel vec4 multiplyEffect (sampler src, __color mul)
{
  return sample (src, samplerCoord (src)) * mul;
}

3. Computing a Hole Distortion - 計(jì)算孔畸變

Listing 9-16顯示了計(jì)算空洞失真的內(nèi)核例程。 請注意,有許多方法可以計(jì)算空洞失真。 清單后面出現(xiàn)每行編號的詳細(xì)說明。

Listing 9-16  A kernel routine that computes a hole distortion

kernel vec4 holeDistortion (sampler src, vec2 center, vec2 params)   // 1
{
  vec2 t1;
  float distance0, distance1;
 
  t1 = destCoord () - center;                                        // 2
  distance0 = dot (t1, t1);                                          // 3
  t1 = t1 * inversesqrt (distance0);                                 // 4
  distance0 = distance0 * inversesqrt (distance0) * params.x;        // 5
  distance1 = distance0 - (1.0 / distance0);                         // 6
  distance0 = (distance0 < 1.0 ? 0.0 : distance1) * params.y;        // 7
  t1 = t1 * distance0 + center;                                      // 8
 
  return sample (src, samplerTransform (src, t1));                   // 9
}

這里是代碼的作用:

    1. 采用三個參數(shù) - 采樣器,指定孔失真中心的矢量和包含(1/radius, radius)params矢量。
    1. 從中心創(chuàng)建矢量t1到當(dāng)前的工作坐標(biāo)。
    1. 將距離中心的距離平方,并將值賦予distance0變量。
    1. 規(guī)范化t1。 (使t1成為單位向量)
    1. 從中心計(jì)算參數(shù)距離(distance squared * 1/distance) * 1/radius。 該值在中心為0,在距離等于半徑的位置為1。
    1. 用適當(dāng)?shù)呐で鷦?chuàng)建一個洞。 (x - 1 / sqrt(x))
    1. 確保孔內(nèi)的所有像素都映射到中心的像素,然后按半徑放大扭曲的距離函數(shù)。
    1. 縮放矢量以創(chuàng)建distortion,然后將中心添加回來。
    1. 從源紋理返回扭曲的樣本。

后記

本篇已結(jié)束,后面更精彩~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容