影象中的畫素處理

齊滇大聖發表於2018-08-04

影象的深度和通道

影象的深度

影象中畫素點佔得bit位數,就是影象的深度,比如以下影象的深度。

二值影象:

影象的畫素點不是0 就是1 (影象不是黑色就是白色),影象畫素點佔的位數就是 1 位,影象的深度就是1,也稱作點陣圖。

灰度影象:

影象的畫素點位於0-255之間,(0:全黑,255代表:全白,在0-255之間插入了255個等級的灰度)。最大值255的二進位制表示為11111111,佔有8個bit位,即2^8=256,影象的深度是8。

影象的通道

通道,是數字影象中儲存不同型別資訊的灰度影象。一個影象最多可以有數十個通道,常用的RGB和Lab影象預設有三個通道,而CMYK影象則預設有四個通道。 一張RGB影象含有三個通道:紅(Red)、綠(Green)、藍(Blue)。 一張CMYK影象含有四個通道:青色(Cyan)、品紅(Magenta)、黃色、黑色。

所以想灰度圖就只有一個通道,佔有8個bit位,也就是8點陣圖。所以RGB影象佔有三個通道,3*8=24,所以RGB影象就是24點陣圖。

影象在記憶體中的儲存

image

image

影象畫素點的儲存就是對應的原圖從左到右,從上到下,依次排列,每個點的值就是就是畫素點的值,每個點的地址就是畫素畫素點的地址。

如第一幅圖就是灰度圖的儲存,只有單通道。在記憶體中的儲存即可用一個一維陣列來表示,根據順序從左到右,從上到下,依次按順序存入陣列。

圖二則為RGB影象的儲存模型,每一個畫素有3個通道,所以需要一個二維陣列來表示,順序也是從左到右,從上到下,如[[234,200,0],[234,0,0],[255,55,0],....]這樣,當然其中的數子,在記憶體中需要用對應的二進位制來表示。

python輸出影象資料

我們來用python來輸出一個圖片的畫素資料,來驗證看看上面所說的儲存模型。

import sys
import tensorflow as tf
from PIL import Image, ImageFilter
import numpy as np

def imageprepare(argv):
    testImage=Image.open(argv).convert('L')
    testImage = testImage.resize((6, 4))
    test_input=np.array(testImage)
    print(test_input)


def main(argv):
    """
    Main function.
    """
    imvalue = imageprepare(argv)
    
if __name__ == "__main__":
    main(sys.argv[1])
複製程式碼

我這裡傳進去一張圖片,然後轉換成L模型(L表示灰度圖),設定寬高位(6,4),輸出如下所示,這裡np.array把圖片資料轉換成了一個二維陣列,方便根據(x,y)來讀取:

[[254 255 254  97 255 248]
 [246 255  15 180 255 255]
 [252 227 227 246  44 252]
 [244 254 229 151 243 248]]
複製程式碼

如我們之前所說根據從左到右,從上到下儲存的話,則可以方便的用以下方法來讀取:

width = testImage.size[0]
height = testImage.size[1]
y = 0
while y<height:
    x = 0
    while x<width:
        print(test_input[y,x])
        x += 1
    y += 1
複製程式碼

對應的RGB圖片,我們轉換模型改一下testImage=Image.open(argv).convert('RGB'),轉換為array之後,就變成了一個rowscolschannels的三維矩陣,輸出讀取如下所示:

[[[254 254 254]
  [255 255 255]
  [254 254 254]
  [ 97  97  97]
  [255 255 255]
  [248 248 248]]

 [[246 246 246]
  [255 255 255]
  [ 15  15  15]
  [180 180 180]
  [255 255 255]
  [255 255 255]]

 [[252 252 252]
  [227 227 227]
  [227 227 227]
  [246 246 246]
  [ 44  44  44]
  [252 252 252]]

 [[244 244 244]
  [254 254 254]
  [229 229 229]
  [151 151 151]
  [243 243 243]
  [248 248 248]]]
複製程式碼
width = testImage.size[0]
height = testImage.size[1]
y = 0
while y<height:
    x = 0
    while x<width:
        # 畫素的3通道值
        print(test_input[y,x])
        print('R: ' + str(test_input[y,x,0]))
        print('G: ' + str(test_input[y,x,1]))
        print('B: ' + str(test_input[y,x,2]))
        x += 1
    y += 1
複製程式碼

iOS仿python影象處理庫PIL

python程式碼

以下為python中PIL把圖片轉換為畫素資料陣列的程式碼,我們先把圖片轉化為RGBA格式,然後輸出對應位置的畫素資料。當然RGBA一個畫素有4個通道,所以我們可以依次輸出每個通道的值,如R通道:test_input[y,x,0]

def imageprepare(argv):
    testImage=Image.open(argv).convert('RGBA')
    testImage = testImage.resize((28, 28))
    test_input=np.array(testImage)
    print(test_input)
    width = testImage.size[0]
    height = testImage.size[1]
    y = 0
    while y<height:
        x = 0
        while x<width:
            print(test_input[y,x])
            # print('R: ' + str(test_input[y,x,0]))
            x += 1
        y += 1
複製程式碼

iOS程式碼

在iOS中圖片轉化為圖片資料格式相對於python和Android中來講相對麻煩一些,所以我這裡封裝了一個iOS圖片轉圖片資料的類。輸出的格式跟python中類似,但是python支援多種編碼格式,分別為1,L,P,RGB,RGBA,CMYK,YCbCr,I,F。這裡iOS開發中只支援RGBA,CMYK。

在iOS中我們會先根據圖片的編碼格式來生成一個CGContextRef(畫布),以下程式碼是對RGBA格式圖片處理生成的CGContextRef。

- (CGContextRef) newBitmapRGBA8ContextFromImage:(CGImageRef) image {
    
    CGContextRef context = NULL;
    CGColorSpaceRef colorSpace;
    uint32_t *bitmapData;
    
    size_t bitsPerPixel = 32;
    size_t bitsPerComponent = 8;
    size_t bytesPerPixel = bitsPerPixel / bitsPerComponent;
    
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
    
    size_t bytesPerRow = width * bytesPerPixel;
    size_t bufferLength = bytesPerRow * height;
    
    colorSpace = CGColorSpaceCreateDeviceRGB();
    
    if(!colorSpace) {
        NSLog(@"Error allocating color space Gray\n");
        return NULL;
    }
    
    // Allocate memory for image data
    bitmapData = (uint32_t *)malloc(bufferLength);
    
    if(!bitmapData) {
        NSLog(@"Error allocating memory for bitmap\n");
        CGColorSpaceRelease(colorSpace);
        return NULL;
    }
    
    //Create bitmap context
    
    context = CGBitmapContextCreate(bitmapData,
                                    width,
                                    height,
                                    bitsPerComponent,
                                    bytesPerRow,
                                    colorSpace,
                                    kCGImageAlphaPremultipliedLast); 
    if(!context) {
        free(bitmapData);
        NSLog(@"Bitmap context not created");
    }
    
    CGColorSpaceRelease(colorSpace);
    
    return context;
}
複製程式碼

要理解以上程式碼,首先要知道什麼是畫素格式:

點陣圖其實就是一個畫素陣列,而畫素格式則是用來描述每個畫素的組成格式,它包括以下資訊:

Bits per component :一個畫素中每個獨立的顏色分量使用的 bit 數;
Bits per pixel : 一個畫素使用的總 bit 數;
Bytes per row : 點陣圖中的每一行使用的位元組數。

有一點需要注意的是,對於點陣圖來說,畫素格式並不是隨意組合的,目前iOS、Mac OS X開發只支援以下有限的 17 種特定組合: 官方文件

原始碼

DSImageBitmaps這是我iOS原始碼的地址,其中包含了python的程式碼,我iOS裡面的圖片直接使用的是python裁剪過大小的圖片,然後能發現資料的資料是一樣的。

但是我用iOS裡面直接裁剪大小後的圖片就跟python處理過大小的圖片輸出的資料就不一樣了,說是python的image.resize用到了濾波器,具體是什麼我也不太清楚。反正就是iOS和python處理圖片大小內部的演算法有些許差異,但是你能發現每一個畫素上的資料差異不大,具體到一張圖顯示的話人眼是識別不出來的。

還有就算要注意iOS中處理的圖片大小問題,也就是iOS中畫素和image.size的關係:

test.png (畫素 20*20) test@2x.png(畫素40*40) test@3x.png(畫素 60*60)

UIImage *image = [UIImageimageNamed:@"test.png"];

image.size輸出大小為(20,20);


UIImage *image = [UIImage imageNamed:@"test@2x.png"];

image.size輸出大小為(20,20);


UIImage *image = [UIImage imageNamed:@"test@3x.png"];

image.size輸出大小為(20,20);


image.size輸出的大小會自動識別圖片是幾倍的,如果是3倍的輸出的結果就是畫素除以3,2倍的畫素除以2。
複製程式碼

參考

Converting UIImage to RGBA8 Bitmaps and Back

一張圖片引發的深思

談談 iOS 中圖片的解壓縮

相關文章