iOS圖像處理(位圖圖像原圖修改)

總結一下最近看到的關於圖像處理的知識

當前流行的圖片處理方式有

1:位圖圖像原圖修改
2:使用Core Graphics庫
3:使用Core Image庫
4:使用GPUImage庫的第三部分

首先講解一下32位RGBA模式

如同它的名字一樣,32位RGBA模式會將一個顏色值存儲在32位,或者4個字節中。每一個字節存儲一個部分或者一個顏色通道。這4個部分分別是:
 
~ R代表紅色
 
~ G代表綠色
 
~ B代表藍色
 
~ A代表透明度
我們發現每8位代表一個顏色

alpha通道與其它的不同。你可以把它當成透明的東西,就像UIView的alpah屬性。
 
透明顏色意味着沒有任何的顏色,除非在它的後面有另外一種顏色;它的主要功能就是要告訴圖像處理這個像素的透明度是多少,於是,就會有多少顏色值穿透過它而顯示出來。
顏色空間
使用RGB模式表示顏色是顏色空間的一個例子。它只是衆多存儲顏色方法中的一種。另外一種顏色空間是灰階空間。像它的名字一樣,所有的圖形都只有黑和白,只需要保存一個值來表示這種顏色。

另外兩種比較常見的顏色空間是HSV和YUV。
 
HSV,使用色調,飽和度和亮度來直觀的存儲顏色值。你可以把這三個部分這樣來看:
 
·色調就是顏色
·飽和度就是這個顏色有多麼的飽滿
·值就是顏色的亮度有多亮
 

YUV是另外一種常見的顏色空間,電視機使用的就是這種方式。
 
最開始的時候,電視機只有灰階空間一種顏色通道。後來,當彩色電影出現後,就有了2種通道。當然,如果你想在本教程中使用YUV,那麼你需要去研究更多關於YUV和其它顏色空間的相關知識。
 
NOTE:同樣的顏色空間,你也可以使用不同的方法表示顏色。比如16位RGB模式,可以使用5個字節存儲R,6個字節存儲G,5個字節存儲B。
 
爲什麼用6個字節存儲綠色,5個字節存儲藍色?這是一個有意思的問題,答案就是因爲眼球。人類的眼球對綠色比較敏感,所以人類的眼球更空間分辨出綠色的顏色值變化。


座標系統
既然一個圖形是由像素構成的平面地圖,那麼圖像的原點需要說明一下。通常原點在圖像的左上角,Y軸向下;或者原點在圖像的左下,Y軸向上。
 
沒有固定的座標系統,蘋果在不同的地方可能會使用不同的座標系。
 
目前,UIImage和UIView使用的是左上原點座標,Core Image和Core Graphics使用的是左下原點座標。這個概念很重要,當你遇到圖像繪製倒立問題的時候你就知道了。

圖形壓縮
這是在你開始編寫代碼前的最後一個需要了解的概念了!原圖的每一個像素都被存儲在各自的內存中。
 
如果你使用一張8像素的圖形做運算,它將會消耗810^6像素4比特/像素=32兆字節內存。關注一下數據!
 
這就是爲什麼會出現jpeg,png和其它圖形格式的原因。這些都是圖形壓縮格式。
 
當GPU在繪製圖像的時候,會使用大量內存把圖像的原始尺寸進行解壓縮。如果你的程序佔用了過多的內存,那麼操作系統會將進程殺死(程序崩潰)。所以請確定你的程序使用較大的圖像進行過測試。

    UIImage * image = [UIImage imageNamed:@"back.png"];
    // 1.
    /*
     把UIImage對象轉換爲需要的核心圖形庫調用的CGImage對象。同時,得到圖形的寬度和高度。
     */
    CGImageRef inputCGImage = [image CGImage];
    NSUInteger width =  CGImageGetWidth(inputCGImage);
    NSUInteger height = CGImageGetHeight(inputCGImage);
    
    // 2.
    /*
     由於使用的是32位RGB顏色空間模式,你需要定義一些參數bytesPerPixel(每個像素大小),bitsPerComponent(每個顏色通道大小),計算出bytesPerRow(每行多大)。數組存儲像素的值
     */
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    
    UInt32 * pixels;
    pixels = (UInt32 *) calloc(height * width, sizeof(UInt32));

    // 3.
    /*
     創建一個RGB模式的顏色空間CGColorSpace和一個容器CGBitmapContext,將像素指針參數傳遞到容器中緩存進行存儲。
     */
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context =  CGBitmapContextCreate(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    
    // 4.
    /*
     把緩存中的圖形繪製在顯示器上,像素的填充格式有創建context的時候進行指定的
     
     */
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), inputCGImage);
    
    // 5. Cleanup
    /*
     清除colorSpace和context
     */
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);

NOTE:當你繪製圖像的時候,設備的GPU會進行解碼並將它顯示在屏幕。爲了訪問本地數據,你需要一份像素的複製,就像剛纔做的那樣

此時此刻,pixels存儲着圖像的所有像素信息。下面的幾行代碼會對pixels進行遍歷,並打印:

    // 1.
    /*
     定義一些簡單處理32位像素的宏。爲了得到紅色通道的值,你需要得到前8位,以此類推
     */
#define Mask8(x) ( (x) & 0xFF )
#define R(x) ( Mask8(x) )
#define G(x) ( Mask8(x >> 8 ) )
#define B(x) ( Mask8(x >> 16) )
    
    NSLog(@"Brightness of image:");
    // 2.
    /*
     定義一個指向第一個像素的指針,並使用2個for循環來遍歷像素
     */
    UInt32 * currentPixel = pixels;
    for (NSUInteger j = 0; j < height; j++) {
        for (NSUInteger i = 0; i < width; i++) {
            // 3.
            /*
             得到當前像素的值賦值給currentPixel並把它的亮度值打印出來
             */
            UInt32 color = *currentPixel;
            printf("%3.0f ",(R(color)+G(color)+B(color))/3.0);
            // 4.
            /*
             增加currentPixel的值,使它指向下一個像素。如果你對指針的運算比較生疏,記住這個:currentPixel是一個指向UInt32的變量,當你把它加1後,他就會向前移動4字節(32位),然後指向下一個像素值
             提示:還有一種非正統的方法就是把currentPiexl聲明爲一個指向8字節的類型的指針,比如char。這種方法,你每增加1,你將會移動圖形的下一個顏色通道。與它進行位移運算,你會得到顏色通道的8位數值。
             */
            currentPixel++;
        }
        printf("\n");
    }

原圖修改

在本方法中,你會遍歷每一個像素,就像之前做的那個,但這次,將會對每個像素進行新的賦值。
 
這種方法的優點是容易實現和理解;缺點就是掃描大的圖形和效果的時候會更復雜,不精簡。

根據前面所介紹的方法,將圖片的inputImage繪製出來,並且返回在桌面上

#pragma mark - Private

#define Mask8(x) ( (x) & 0xFF )
#define R(x) ( Mask8(x) )
#define G(x) ( Mask8(x >> 8 ) )
#define B(x) ( Mask8(x >> 16) )
#define A(x) ( Mask8(x >> 24) )
#define RGBAMake(r, g, b, a) ( Mask8(r) | Mask8(g) << 8 | Mask8(b) << 16 | Mask8(a) << 24 )
- (UIImage *)processUsingPixels:(UIImage*)inputImage {
  
  // 1. Get the raw pixels of the image
  UInt32 * inputPixels;
  
  CGImageRef inputCGImage = [inputImage CGImage];
  NSUInteger inputWidth = CGImageGetWidth(inputCGImage);
  NSUInteger inputHeight = CGImageGetHeight(inputCGImage);
  
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  
  NSUInteger bytesPerPixel = 4;
  NSUInteger bitsPerComponent = 8;
  
  NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth;
  
  inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32));
  
  CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight,
                                               bitsPerComponent, inputBytesPerRow, colorSpace,
                                               kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
  
  CGContextDrawImage(context, CGRectMake(0, 0, inputWidth, inputHeight), inputCGImage);
  
 
    UIImage * ghostImage = [UIImage imageNamed:@"ghost"];
    CGImageRef ghostCGImage = [ghostImage CGImage];
    //把ghost圖片寬度縮小25%,並把它的圓點設定在ghostOrigin上(在inputImage的位置)
    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);
    //創建ghost圖片的緩存圖
    NSUInteger ghostBytesPerRow = bytesPerPixel * ghostSize.width;
    UInt32 * ghostPixels = (UInt32 *)calloc(ghostSize.width * ghostSize.height,  sizeof(UInt32));
    
    CGContextRef ghostContext = CGBitmapContextCreate(ghostPixels,ghostSize.width, ghostSize.height,bitsPerComponent, ghostBytesPerRow, colorSpace,
        kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGContextDrawImage(ghostContext, CGRectMake(0, 0, ghostSize.width,     ghostSize.height),ghostCGImage);
    
    /*現在已經到了把幽靈圖像合併到你的照片中的最佳時間了。
    合併:像前面提到的,每一個顏色都有一個透明通道來標識透明度。並且,你每創建一張圖像,每一個像素都會有一個顏色值。
    所以,如果遇到有透明度和半透明的顏色值該如何處理呢?
    
    答案是,對透明度進行混合。在最頂層的顏色會使用一個公式與它後面的顏色進行混合。公式如下:
     NewColor = TopColor * TopColor.Alpha + BottomColor * (1 - TopColor.Alpha) ;
     這是一個標準的線性差值方程。
     
     ·當頂層透明度爲1時,新的顏色值等於頂層顏色值。
     ·當頂層透明度爲0時,新的顏色值於底層顏色值。
     ·最後,當頂層的透明度值是0到1之前的時候,新的顏色值會混合借於頂層和底層顏色值之間。
     
     還可以用 premultiplied alpha的方法。
     
     當處理成千上萬像素的時候,他的性能會得以發揮。
     */
    
    //通過對幽靈圖像像素數的循環和offsetPixelCountForInput獲得輸入的圖像。記住,雖然你使用的是2維數據存儲圖像,但在內存他它實際上是一維的。
    NSUInteger offsetPixelCountForInput = ghostOrigin.y * inputWidth + ghostOrigin.x;
    for (NSUInteger j = 0; j < ghostSize.height; j++) {
        for (NSUInteger i = 0; i < ghostSize.width; i++) {
            UInt32 * inputPixel = inputPixels + j * inputWidth + i + offsetPixelCountForInput;
            UInt32 inputColor = *inputPixel;
            
            UInt32 * ghostPixel = ghostPixels + j     * (int)ghostSize.width + i;
            UInt32 ghostColor = *ghostPixel;
            
            // Do some processing here
        } 
    }
    
    
  return inputImage;
}

下一步,添加下面的代碼到註釋語句 Do some processing here的下面來進行混合:
    // Blend the ghost with 50% alpha (重點)
    CGFloat ghostAlpha = 0.5f * (A(ghostColor)     / 255.0); 
    UInt32 newR = R(inputColor) * (1 - ghostAlpha) + R(ghostColor) * ghostAlpha; 
    UInt32 newG = G(inputColor) * (1 - ghostAlpha) + G(ghostColor) * ghostAlpha; 
    UInt32 newB = B(inputColor) * (1 - ghostAlpha) + B(ghostColor) * ghostAlpha; 
      
    // Clamp, not really useful here :p 
    newR = MAX(0,MIN(255, newR)); 
    newG = MAX(0,MIN(255, newG)); 
    newB = MAX(0,MIN(255, newB)); 
      
    *inputPixel = RGBAMake(newR, newG, newB, A(inputColor)); 

這部分有2點需要說明:
 
1:你將幽靈圖像的每一個像素的透明通道都乘以了0.5,使它成爲半透明狀態。然後將它混合到圖像中像之前討論的那樣。
 
2:clamping部分將每個顏色的值範圍進行限定到0到255之間,雖然一般情況下值不會越界。但是,大多數情況下需要進行這種限定防止發生意外的錯誤輸出。

最後一句,將返回值替換

    // Create a new UIImage 
    CGImageRef newCGImage =     CGBitmapContextCreateImage(context); 
    UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; 
      
    return processedImage; 





黑白顏色
最後一種效果。嘗試自己實現黑白顏色效果。爲了做到這點,你需要把每一個像素的紅色,綠色,藍色通道的值設定成三個通道原始顏色值的平均值,就像開始的時候輸出幽靈圖像所有像素亮度值那樣。
 
在註釋語句// create a new UIImage前添加上一步的代碼 。

    // Convert the image to black and white 
    for (NSUInteger j = 0; j < inputHeight; j++) { 
    for (NSUInteger i = 0; i < inputWidth; i++) { 
    UInt32 * currentPixel = inputPixels + (j * inputWidth) + i; 
    UInt32 color = *currentPixel; 
      
        // Average of RGB = greyscale 
        UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0; 
      
        *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color)); 
      } 
    } 

最後的一步就是清除內存。ARC不能代替你對CGImageRefs和CGContexts進行管理。添加如下代碼到返回語句之前。

CGColorSpaceRelease(colorSpace); 
CGContextRelease(context); 
CGContextRelease(ghostContext); 
free(inputPixels); 
free(ghostPixels);




恭喜!你已經完成了自己的第一個圖像處理程序。你可以在這裏下載該工程的源代碼。
 
raywenderlich.com/wp-content/uploads/2014/03/SpookCam-Starter.zip

還不錯吧?你可以嘗試修改一下循環中的代碼創建自己想要的效果,嘗試下實現下面的效果:
 
·嘗試調換圖像的紅色和藍色通道值
·提高圖像的亮度10%
·作爲進一步的挑戰,嘗試只使用基於像素的方法縮放幽靈的圖像,下面是步驟:
1:使用幽靈圖像的尺寸大小創建一個新的CGContext。
2:在原圖像中得到你想要的並賦值到新的緩存圖像中。
3:附加,嘗試在像素之前進行計算並插入相似值像素點。如果你可以在四個像素間進行插入,你自己就已經實現 Bilinear scaling(雙線性插值法)了









發佈了25 篇原創文章 · 獲贊 10 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章