ios图像处理第2部分:核心图形,核心图像,GPUImage
原文地址:http://www.raywenderlich.com/71151/image-processing-ios-part-2-core-graphics-core-image-gpuimage?utm_source=tuicool
泰然翻譯組: Lyubishchev。 校對:lareina。
學習在iOS中處理圖像和創建酷炫的效果!
歡迎來到本系列教程的第二節,iOS中的圖像!
在本系列的第一節,我們學會了如何訪問和修改圖像的原始像素值。
在本系列的第二節或者說最終節中,你將學習如何使用其他的庫來執行同樣的任務:Core Graphics, Core Image 和GPUImage。你將學習它們各自的優點和缺點,這樣你就可以針對你的情況做出更好的選擇。
本教程從上一節結束的地方開始。如果你沒有項目文件,你可以在這里下載它。
如果你在第一節中表現得很好,你要好好享受這一節!既然你理解了工作原理,你將充分理解這些庫進行圖像處理是多么的簡單。
超級SpookCam之Core Graphics版本
Core Graphics是Apple基于Quartz 2D繪圖引擎的繪圖API。它提供了底層API,如果你熟悉OpenGL可能會覺得它們很相似。
如果你曾經重寫過視圖的-drawRect:函數,你其實已經與Core Graphics交互過了,它提供了很多繪制對象、斜度和其他很酷的東西到你的視圖中的函數。
這個網站已經有大量的Core Graphics教程,比如這個和這個。所以,這本教程中,我們將關注于如何使用Core Graphics來做一些基本的圖像處理。
在開始之前,我們需要熟悉一個概念Graphics Context。
概念:Graphics Contexts是OpenGl和Core Graphics的核心概念,它是渲染中最常見的類型。它是一個持有所有關于繪制信息的全局狀態對象。?
在Core Graphics中,包括了當前的填充顏色,描邊顏色,變形,蒙版,在哪里繪制等。在iOS中,還有其他不同類型的context比如PDF context,它可以讓你繪制一個PDF文件。
在本教程中,你只會使用到Bitmap context,它可以繪制位圖。
在-drawRect:函數中,你會發現你可以直接調用UIGraphicsGetCurrentContext()來使用context。系統被設置為你可以直接在視圖上繪制被渲染的圖像。
在-drawRect:函數外,通常沒有圖形context可用。你可以像第一個項目中一樣用CGContextCreate()創建,或者你可以使用UIGraphicsBeginImageContext()和UIGraphicsGetCurrentContext()抓取創建的context。
這叫做離屏-渲染,意思是你不是在任何地方直接繪制,而是在離屏緩沖區渲染。
在Core Graphics中,你可以獲得context中的UIImage然后把它顯示在屏幕上。使用OpenGL,你可以直接把這個緩沖區與當前渲染在屏幕中的交換,然后直接顯示它。
使用Core Graphics處理圖像利用了在緩沖區渲染圖像的離屏渲染,它從context抓取圖像,并適用任何你想要的效果。
好了,概念介紹完了,是時候變一些代碼的魔術了!添加下面的新函數到ImageProcessor.m中:
- (UIImage *)processUsingCoreGraphics:(UIImage*)input {CGRect imageRect = {CGPointZero,input.size};NSInteger inputWidth = CGRectGetWidth(imageRect);NSInteger inputHeight = CGRectGetHeight(imageRect);// 1) Calculate the location of GhostyUIImage * ghostImage = [UIImage imageNamed:@"ghost.png"];CGFloat ghostImageAspectRatio = ghostImage.size.width / ghostImage.size.height;NSInteger targetGhostWidth = inputWidth * 0.25;CGSize ghostSize = CGSizeMake(targetGhostWidth, targetGhostWidth / ghostImageAspectRatio);CGPoint ghostOrigin = CGPointMake(inputWidth * 0.5, inputHeight * 0.2);CGRect ghostRect = {ghostOrigin, ghostSize};// 2) Draw your image into the context.UIGraphicsBeginImageContext(input.size);CGContextRef context = UIGraphicsGetCurrentContext();CGAffineTransform flip = CGAffineTransformMakeScale(1.0, -1.0);CGAffineTransform flipThenShift = CGAffineTransformTranslate(flip,0,-inputHeight);CGContextConcatCTM(context, flipThenShift);CGContextDrawImage(context, imageRect, [input CGImage]);CGContextSetBlendMode(context, kCGBlendModeSourceAtop);CGContextSetAlpha(context,0.5);CGRect transformedGhostRect = CGRectApplyAffineTransform(ghostRect, flipThenShift);CGContextDrawImage(context, transformedGhostRect, [ghostImage CGImage]);// 3) Retrieve your processed imageUIImage * imageWithGhost = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();// 4) Draw your image into a grayscale context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();context = CGBitmapContextCreate(nil, inputWidth, inputHeight,8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaNone);CGContextDrawImage(context, imageRect, [imageWithGhost CGImage]);CGImageRef imageRef = CGBitmapContextCreateImage(context);UIImage * finalImage = [UIImage imageWithCGImage:imageRef];// 5) CleanupCGColorSpaceRelease(colorSpace);CGContextRelease(context);CFRelease(imageRef);return finalImage; }這個函數內容可真夠多的,讓我們一點一點分析它。
1) 計算Ghosty的位置
UIImage * ghostImage = [UIImage imageNamed:@"ghost.png"]; CGFloat ghostImageAspectRatio = ghostImage.size.width / ghostImage.size.height;NSInteger targetGhostWidth = inputWidth * 0.25; CGSize ghostSize = CGSizeMake(targetGhostWidth, targetGhostWidth / ghostImageAspectRatio); CGPoint ghostOrigin = CGPointMake(inputWidth * 0.5, inputHeight * 0.2);CGRect ghostRect = {ghostOrigin, ghostSize};創建一個新的CGContext。
像前面討論的,這里創建了一個“離屏”(“off-screen”)的context。還記得嗎?CGContext的坐標系以左下角為原點,相反的UIImage使用左上角為原點。
有趣的是,如果你使用UIGraphicsBeginImageContext()來創建一個context,系統會把坐標翻轉,把原點設為左上角。因此,你需要變換你的context把它翻轉回來,從而使CGImage能夠進行正確的繪制。
如果你直接在這個context中繪制UIImage,你不需要執行變換,坐標系統將會自動匹配。設置這個context的變換將影響所有你后面繪制的圖像。
2) 把你的圖像繪制到context中。
UIGraphicsBeginImageContext(input.size); CGContextRef context = UIGraphicsGetCurrentContext();CGAffineTransform flip = CGAffineTransformMakeScale(1.0, -1.0); CGAffineTransform flipThenShift = CGAffineTransformTranslate(flip,0,-inputHeight); CGContextConcatCTM(context, flipThenShift);CGContextDrawImage(context, imageRect, [input CGImage]);CGContextSetBlendMode(context, kCGBlendModeSourceAtop); CGContextSetAlpha(context,0.5); CGRect transformedGhostRect = CGRectApplyAffineTransform(ghostRect, flipThenShift); CGContextDrawImage(context, transformedGhostRect, [ghostImage CGImage]);在繪制完圖像后,你context的alpha值設為了0.5。這只會影響后面繪制的圖像,所以本次繪制的輸入圖像使用了全alpha。
你也需要把混合模式設置為kCGBlendModeSourceAtop。
這里為context設置混合模式是為了讓它使用之前的相同的alpha混合公式。在設置完這些參數之后,翻轉幽靈的坐標然后把它繪制在圖像中。
3) 取回你處理的圖像
UIImage * imageWithGhost = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();為了把你的圖像轉換成黑白的,你將創建一個使用灰度(grayscale)色彩的新的CGContext。它將把所有你在context中繪制的圖像轉換成灰度的。
因為你使用CGBitmapContextCreate()來創建了這個context,坐標則是以左下角為原點,你不需要翻轉它來繪制CGImage。
4) 繪制你的圖像到一個灰度(grayscale)context中
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); context = CGBitmapContextCreate(nil, inputWidth, inputHeight,8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaNone);CGContextDrawImage(context, imageRect, [imageWithGhost CGImage]);CGImageRef imageRef = CGBitmapContextCreateImage(context); UIImage * finalImage = [UIImage imageWithCGImage:imageRef];取回你最終的圖像。
為什么你不可以使用UIGraphicsGetImageFromCurrentImageContext()呢,因為你沒有把當前的圖形context設置為灰度context。
因此,你需要自己創建它。你需要使用CGBitmapContextCreateImage()來渲染context中的圖像。
5) 清理。
CGColorSpaceRelease(colorSpace); CGContextRelease(context); CFRelease(imageRef);return finalImage;在最后,你需要釋放所有你創建的對象。然后 – 完成了!
內存使用:當執行圖像處理時,密切關注內存使用情況。像在第一節中討論的一樣,一個8M像素的圖像占用了高達32M的內存。盡量避免在內存中同一時間保持同一圖像的多個復制。
注意到為什么我們第二次需要釋放context而第一次不需要了嗎?這是因為第一次時,你使用UIGraphicsGetCurrentImageContext()獲取了context。這里的關鍵詞是‘get’。
‘Get’意味著你獲取了當前context的引用,你并不持有它。
在第二次中,你調用了CGBitmapContextCreateImage(),Create意味著你持有這個對象,并需要管理它的生命周期。這也是你為什么需要釋放imageRef的原因,因為你是通過CGBitmapContextCreateImage()創建它的。
干得漂亮!現在,替換processImage中的第一行:調用這個新的函數替換掉processUsingPixels::
UIImage * outputImage = [self processUsingCoreGraphics:inputImage];生成和運行一下。你應該能看到和之前一樣的輸出。
好靈異!你可以在這里下載到本節中完整項目的代碼。
在這個簡單的例子中,使用Core Graphics看起來好像不比直接操作像素更簡單。
然而,想象一個更復雜的操作,比如旋轉圖像。在像素操作中,這需要相當復雜的數學。
但是,使用Core Graphics,你只需要在繪制圖像前給context設置一個旋轉的變換就可以了。因為,你處理的內容越復雜,你使用Core Graphics則能節省更多的時間。
介紹完了兩種方法,下面還有兩種方法。下一個:Core Image!
超超SpookCam之Core Image版本
這個網站也已經有大量好的Core Image教程,比如IOS 6中的這個。我們也在我們的iOS教程系列中有很多關于Core Image的章節。
在本教程中,你將看到有很多關于Core Image與其他幾種方法對比的討論。
Core Image是Apple的圖像處理的解決方案。它避免了所有底層的像素操作方法,轉而使用高級別的濾鏡替代了它們。
Core Image最好的部分在于它對比操作原始像素或Core Graphics有著極好的性能。這個庫使用CPU和GPU混合處理提供接近實時的性能。
Apple還提供了巨大的預先制作的濾鏡庫。在OSX中,你甚至可以使用Core Image Kernel Language創建你自己的濾鏡,它跟OpenGL中的著色語言GLSL很相似。在寫本教程時,你還不能在iOS中制作你自己的Core Image濾鏡(只支持Mac OS X)。
它還有一些比Core Graphics更好的效果。正如你在代碼中看到的,你用Core Graphics來充分利用Core Image。
添加這個新函數到ImageProcessor.m中:
- (UIImage *)processUsingCoreImage:(UIImage*)input {CIImage * inputCIImage = [[CIImage alloc] initWithImage:input];// 1. Create a grayscale filterCIFilter * grayFilter = [CIFilter filterWithName:@"CIColorControls"];[grayFilter setValue:@(0) forKeyPath:@"inputSaturation"];// 2. Create your ghost filter// Use Core Graphics for thisUIImage * ghostImage = [self createPaddedGhostImageWithSize:input.size];CIImage * ghostCIImage = [[CIImage alloc] initWithImage:ghostImage];// 3. Apply alpha to GhostyCIFilter * alphaFilter = [CIFilter filterWithName:@"CIColorMatrix"];CIVector * alphaVector = [CIVector vectorWithX:0 Y:0 Z:0.5 W:0];[alphaFilter setValue:alphaVector forKeyPath:@"inputAVector"];// 4. Alpha blend filterCIFilter * blendFilter = [CIFilter filterWithName:@"CISourceAtopCompositing"];// 5. Apply your filters[alphaFilter setValue:ghostCIImage forKeyPath:@"inputImage"];ghostCIImage = [alphaFilter outputImage];[blendFilter setValue:ghostCIImage forKeyPath:@"inputImage"];[blendFilter setValue:inputCIImage forKeyPath:@"inputBackgroundImage"];CIImage * blendOutput = [blendFilter outputImage];[grayFilter setValue:blendOutput forKeyPath:@"inputImage"];CIImage * outputCIImage = [grayFilter outputImage];// 6. Render your output imageCIContext * context = [CIContext contextWithOptions:nil];CGImageRef outputCGImage = [context createCGImage:outputCIImage fromRect:[outputCIImage extent]];UIImage * outputImage = [UIImage imageWithCGImage:outputCGImage];CGImageRelease(outputCGImage);return outputImage; }我們看一下這個代碼跟之前的函數有多大區別。
使用Core Image,你設置了大量的濾鏡來處理你的圖像 – 你使用了CIColorControls濾鏡來設置灰度,CIColorMatrix和CISourceAtopCompositing來設置混合,最后把它們連接在一起。
現在,讓我們瀏覽一遍這個函數來學習它的每一個步驟。
這個方法使用了一個叫做-createPaddedGhostImageWithSize:的幫助函數,它使用Core Graphics創建了輸入圖像25%大小縮小版的填充的幽靈。你自己能實現這個函數嗎?
自己試一下。如果你被卡住了,請看下面的解決方案:
解決方案
- (UIImage *)createPaddedGhostImageWithSize:(CGSize)inputSize {UIImage * ghostImage = [UIImage imageNamed:@"ghost.png"];CGFloat ghostImageAspectRatio = ghostImage.size.width / ghostImage.size.height;NSInteger targetGhostWidth = inputSize.width * 0.25;CGSize ghostSize = CGSizeMake(targetGhostWidth, targetGhostWidth / ghostImageAspectRatio);CGPoint ghostOrigin = CGPointMake(inputSize.width * 0.5, inputSize.height * 0.2);CGRect ghostRect = {ghostOrigin, ghostSize};UIGraphicsBeginImageContext(inputSize);CGContextRef context = UIGraphicsGetCurrentContext();CGRect inputRect = {CGPointZero, inputSize};CGContextClearRect(context, inputRect);CGAffineTransform flip = CGAffineTransformMakeScale(1.0, -1.0);CGAffineTransform flipThenShift = CGAffineTransformTranslate(flip,0,-inputSize.height);CGContextConcatCTM(context, flipThenShift);CGRect transformedGhostRect = CGRectApplyAffineTransform(ghostRect, flipThenShift);CGContextDrawImage(context, transformedGhostRect, [ghostImage CGImage]);UIImage * paddedGhost = UIGraphicsGetImageFromCurrentImageContext();UIGraphicsEndImageContext();return paddedGhost; }最后,替換掉processImage中的第一行: 來調用你的新的函數:
UIImage * outputImage = [self processUsingCoreImage:inputImage];現在生成并運行。這次,你應該看到相同的幽靈圖像。
你可以在這里下載到本節項目的所有代碼。
Core Image提供了大量的濾鏡,你可以使用它們來創建幾乎任何你想要的效果。它是你處理圖像時的好伙伴。
現在到了最后一個解決方案,也是本教程中附帶的唯一的第三方選項:GPUImage。
大型超超SpookCam之GPUImage版本
GPUImage是一個活躍的iOS上基于GPU的圖像處理庫。它在這個網站中的十佳iOS庫中贏得了一席之地!
GPUImage隱藏了在iOS中所有需要使用OpenGL ES的復雜的代碼,并用極其簡單的接口以很快的速度處理圖像。GPUImage的性能甚至在很多時候擊敗了Core Image,但是Core Image仍然在很多函數中有優勢。
在開始學習GPUImage之前,你需要把它包含到你的項目中。這可以使用Cocoapods在項目中生成靜態庫或直接嵌入源碼來完成。
項目應用已經包含一個建立在外部的靜態框架。你可以根據下面的步驟簡單的把它復制到項目中:
說明:
在命令行中運行build.sh。生成的庫和頭文件將會被放在build/Release-iphone。
你也可以通過修改build.sh中的IOSSDK_VER變量來修改iOS SDK的版本(你可以通過使用xcodebuild -showsdks來查看所有可用的版本)。
你可以通過下面來自Github倉庫的說明把源代碼嵌入你的項目:
說明:
-
拖拽GPUImage.xcodeproj文件到你Xcode項目中來把框架嵌入到你的項目中。?
-
然后,到應用程序的target添加GPUImage為一個target依賴。
-
從GPUImage框架新產品文件夾中拖拽libGPUImage.a庫到你應用程序target中的Link Binary With Librariesbuild phase。
GPUImage需要鏈接一些其他框架到你的應用程序,所以你需要添加如下的相關庫到你的應用程序target:
CoreMedia
CoreVideo
OpenGLES
AVFoundation
QuartzCore
然后你需要找到框架的頭文件。在你項目的build設置中,設置Header Search Paths的相對路徑為你應用程序中框架/子文件夾中的GPUImage源文件目錄。使Header Search Paths是遞歸的。
添加GPUImage到你的項目中后,一定要在ImageProcessor.m中包含頭文件。
如果你想包含靜態的框架,使用#import GPUImage/GPUImage.h。如果你想直接在項目中包含它,使用#import “GPUImage.h”。
添加新的處理函數到ImageProcessor.m中:
- (UIImage *)processUsingGPUImage:(UIImage*)input {// 1. Create the GPUImagePicturesGPUImagePicture * inputGPUImage = [[GPUImagePicture alloc] initWithImage:input];UIImage * ghostImage = [self createPaddedGhostImageWithSize:input.size];GPUImagePicture * ghostGPUImage = [[GPUImagePicture alloc] initWithImage:ghostImage];// 2. Set up the filter chainGPUImageAlphaBlendFilter * alphaBlendFilter = [[GPUImageAlphaBlendFilter alloc] init];alphaBlendFilter.mix = 0.5;[inputGPUImage addTarget:alphaBlendFilter atTextureLocation:0];[ghostGPUImage addTarget:alphaBlendFilter atTextureLocation:1];GPUImageGrayscaleFilter * grayscaleFilter = [[GPUImageGrayscaleFilter alloc] init];[alphaBlendFilter addTarget:grayscaleFilter];// 3. Process & grab output image[grayscaleFilter useNextFrameForImageCapture];[inputGPUImage processImage];[ghostGPUImage processImage];UIImage * output = [grayscaleFilter imageFromCurrentFramebuffer];return output; }嘿!它看來很明確。這是它的具體內容:
創建和鏈接你將要使用的濾鏡。這種鏈接與Core Image中的濾鏡鏈接不同,它類似于管道。在你完成后,它看起來是這樣的:
GPUImageAlphaBlendFilter接受兩個輸入,在這種情況下為頂部和底部的圖像,紋理的位置很重要。-addTarget:atTextureLocation: 設置紋理為正確的輸入(位置)。
在鏈中的最后一個濾鏡調用-useNextFrameForImageCapture然后對兩個輸入調用-processImage 。這可以確保濾鏡知道你想要從中抓取圖像然后持有它。
最后,替換processImage的第一行代碼: 來調用新的函數:
UIImage * outputImage = [self processUsingGPUImage:inputImage];就是這樣。生成并運行。幽靈看起來和往常一樣好!
正如你看到的,GPUImage很容易操作。你也可以在GLSL里制作你自己的著色器并創建你自己的濾鏡。查看這里的GPUImage文檔來更多的學習如何使用本框架。
在這里下載本節項目中的所有代碼。
下一步?
恭喜!你已經用四種不同方式實現了SpookCam。這里是所有的下載鏈接:
- SpookCam-Starter
- SpookCam-Pixel
- SpookCam-CoreGraphics
- SpookCam-CoreImage
- SpookCam-GPUImage
當然,除本教程外還有很多其他有趣的圖像處理概念:
- 內核和卷積。內核與圖像采樣濾鏡協同工作。例如,模糊濾鏡。
- 圖像分析。有時候你需要對圖像進行深入的分析,例如你想進行人臉識別。Core Image為這個過程提供了CIDetector類。
最后但同樣重要的,沒有圖像處理教程沒有提及OpenCV就結束了。
OpenCV是所有圖像處理事實上的庫,它還有一個iOS的build!然后,它還遠遠不是輕量級的。這更多用于技術領域,比如特性跟蹤。在這里學習更多的OpenCV知識。
這個網站也有OpenCV教程。
下一步是選擇一種方法來開始創建你自己革命性的自拍app。不要停止學習!
我真心希望你能喜歡本教程。如果你有任何疑問或意見,請在下面的論壇留言告訴我們。
注: 圖片出自于Free Range Stock,由Roxana Gonzalez拍攝。
總結
以上是生活随笔為你收集整理的ios图像处理第2部分:核心图形,核心图像,GPUImage的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在ubuntu14.04(64位)编
- 下一篇: 支持Android4.0以下webp的使