5. 影像資料類型及其意義#
在 skimage
中,影像只是 numpy 陣列,其支援多種資料類型 [1],即「dtypes」。為了避免扭曲影像強度(請參閱重新縮放強度值),我們假設影像使用以下 dtype 範圍
資料類型 |
範圍 |
---|---|
uint8 |
0 到 255 |
uint16 |
0 到 65535 |
uint32 |
0 到 232 - 1 |
float |
-1 到 1 或 0 到 1 |
int8 |
-128 到 127 |
int16 |
-32768 到 32767 |
int32 |
-231 到 231 - 1 |
請注意,float 影像應限制在 -1 到 1 的範圍內,即使資料類型本身可以超出此範圍;另一方面,所有整數 dtype 的像素強度都可以跨越整個資料類型範圍。除了少數例外,不支援 64 位元 (u)int 影像。
skimage
中的函式設計為接受任何這些 dtype,但為了效率,可能會傳回不同 dtype 的影像(請參閱輸出類型)。如果您需要特定的 dtype,skimage
提供轉換 dtype 並正確重新縮放影像強度的實用函式(請參閱輸入類型)。您絕不應在影像上使用 astype
,因為它違反了關於 dtype 範圍的這些假設
>>> import numpy as np
>>> import skimage as ski
>>> image = np.arange(0, 50, 10, dtype=np.uint8)
>>> print(image.astype(float)) # These float values are out of range.
[ 0. 10. 20. 30. 40.]
>>> print(ski.util.img_as_float(image))
[ 0. 0.03921569 0.07843137 0.11764706 0.15686275]
5.1. 輸入類型#
雖然我們的目標是保留輸入影像的資料範圍和類型,但函式可能僅支援這些資料類型的子集。在這種情況下,輸入將轉換為所需的類型(如果可能),並且如果需要記憶體複製,則會在記錄檔中列印警告訊息。類型要求應在文件字串中註明。
主要套件中的以下實用函式可供開發人員和使用者使用
函式名稱 |
說明 |
---|---|
img_as_float |
轉換為浮點數(整數類型變為 64 位元浮點數) |
img_as_ubyte |
轉換為 8 位元 uint。 |
img_as_uint |
轉換為 16 位元 uint。 |
img_as_int |
轉換為 16 位元 int。 |
這些函式會將影像轉換為所需的 dtype 並正確重新縮放其值
>>> import skimage as ski
>>> image = np.array([0, 0.5, 1], dtype=float)
>>> ski.util.img_as_ubyte(image)
array([ 0, 128, 255], dtype=uint8)
小心!這些轉換可能會導致精度損失,因為 8 位元無法保存與 64 位元相同量的資訊
>>> image = np.array([0, 0.5, 0.503, 1], dtype=float)
>>> ski.util.img_as_ubyte(image)
array([ 0, 128, 128, 255], dtype=uint8)
請注意,skimage.util.img_as_float()
將保留浮點數類型的精度,並且不會自動重新縮放浮點數輸入的範圍。
此外,某些函式會採用 preserve_range
引數,其中範圍轉換很方便但並非必要。例如,skimage.transform.warp()
中的插值需要 float 類型的影像,其範圍應在 [0, 1] 中。因此,預設情況下,輸入影像將重新縮放到此範圍。但是,在某些情況下,影像值表示物理測量,例如溫度或降雨量值,使用者不希望重新縮放這些值。使用 preserve_range=True
,即使輸出是 float 影像,也會保留原始資料範圍。然後,使用者必須確保下游函式正確處理此非標準影像,這些函式可能需要 [0, 1] 中的影像。一般而言,除非函式具有 preserve_range=False
關鍵字引數,否則不會自動重新縮放浮點數輸入。
>>> image = ski.data.coins()
>>> image.dtype, image.min(), image.max(), image.shape
(dtype('uint8'), 1, 252, (303, 384))
>>> rescaled = ski.transform.rescale(image, 0.5)
>>> (rescaled.dtype, np.round(rescaled.min(), 4),
... np.round(rescaled.max(), 4), rescaled.shape)
(dtype('float64'), 0.0147, 0.9456, (152, 192))
>>> rescaled = ski.transform.rescale(image, 0.5, preserve_range=True)
>>> (rescaled.dtype, np.round(rescaled.min()),
... np.round(rescaled.max()), rescaled.shape)
(dtype('float64'), 4.0, 241.0, (152, 192))
5.2. 輸出類型#
函式的輸出類型由函式作者決定,並記錄以利使用者參考。雖然這需要使用者明確將輸出轉換為所需的任何格式,但它可以確保不會發生不必要的資料複製。
需要特定類型輸出的使用者(例如,為了顯示目的)可以寫入
>>> out = ski.util.img_as_uint(sobel(image))
>>> plt.imshow(out)
5.3. 使用 OpenCV#
您可能需要將使用 skimage
建立的影像與 OpenCV 一起使用,反之亦然。可以在 NumPy(因此也可以在 scikit-image 中)存取 OpenCV 影像資料(無需複製)。OpenCV 對於彩色影像使用 BGR(而不是 scikit-image 的 RGB),其 dtype 預設為 uint8(請參閱影像資料類型及其意義)。BGR 代表藍綠紅。
5.3.1. 將 BGR 轉換為 RGB 或反之亦然#
skimage
和 OpenCV 中的彩色影像具有 3 個維度:寬度、高度和顏色。RGB 和 BGR 使用相同的色彩空間,只是顏色的順序相反。
請注意,在 scikit-image
中,我們通常使用 列
和 欄
而不是寬度和高度(請參閱座標慣例)。
對於顏色沿最後一個軸的影像,以下指令有效地反轉了顏色的順序,而列和欄不受影響。
>>> image = image[:, :, ::-1]
5.3.2. 將 OpenCV 的影像與 skimage
一起使用#
如果 cv_image 是無符號位元組的陣列,則 skimage
預設會理解它。如果您喜歡使用浮點影像,可以使用 img_as_float()
來轉換影像
>>> import skimage as ski
>>> image = ski.util.img_as_float(any_opencv_image)
5.3.3. 將 skimage
的影像與 OpenCV 一起使用#
可以使用 img_as_ubyte()
來實現反向轉換
>>> import skimage as ski
>>> cv_image = ski.util.img_as_ubyte(any_skimage_image)
5.4. 影像處理管線#
此 dtype 行為可讓您將任何 skimage
函式串聯在一起,而無需擔心影像 dtype。另一方面,如果您想要使用需要特定 dtype 的自訂函式,則應呼叫其中一個 dtype 轉換函式(此處,func1
和 func2
是 skimage
函式)
>>> import skimage as ski
>>> image = ski.util.img_as_float(func1(func2(image)))
>>> processed_image = custom_func(image)
更好的是,您可以在內部轉換影像並使用簡化的處理管線
>>> def custom_func(image):
... image = ski.util.img_as_float(image)
... # do something
...
>>> processed_image = custom_func(func1(func2(image)))
5.5. 重新縮放強度值#
如果可能,函式應避免盲目地拉伸影像強度(例如,重新縮放浮點影像,使最小和最大強度為 0 和 1),因為這可能會嚴重扭曲影像。例如,如果您正在尋找黑暗影像中的明亮標記,則可能會有一個影像中沒有標記;拉伸其輸入強度以跨越整個範圍會使背景雜訊看起來像標記。
但是,有時您會有應該跨越整個強度範圍但沒有的影像。例如,某些相機儲存每個像素具有 10 位元、12 位元或 14 位元深度的影像。如果這些影像儲存在 dtype 為 uint16 的陣列中,則影像不會延伸到整個強度範圍,因此,看起來會比應該的更暗。為了更正此問題,您可以使用 rescale_intensity()
函式來重新縮放影像,使其使用整個 dtype 範圍
>>> import skimage as ski
>>> image = ski.exposure.rescale_intensity(img10bit, in_range=(0, 2**10 - 1))
在這裡,in_range
引數設定為 10 位元影像的最大範圍。預設情況下,rescale_intensity()
會拉伸 in_range
的值以符合 dtype 的範圍。rescale_intensity()
也接受字串作為 in_range
和 out_range
的輸入,因此上面的範例也可以寫成
>>> image = ski.exposure.rescale_intensity(img10bit, in_range='uint10')
5.6. 關於負值的注意事項#
人們經常使用帶符號的資料類型來表示影像,即使他們只操作影像的正值(例如,在 int8 影像中只使用 0-127)。因此,轉換函數僅將帶符號資料類型的正值擴展到無符號資料類型的整個範圍。換句話說,從帶符號資料類型轉換為無符號資料類型時,負值會被截斷為 0。(在帶符號資料類型之間轉換時,負值會被保留。)為了防止這種截斷行為,您應該事先重新縮放您的影像。
>>> image = ski.exposure.rescale_intensity(img_int32, out_range=(0, 2**31 - 1))
>>> img_uint8 = ski.util.img_as_ubyte(image)
這種行為是對稱的:無符號資料類型中的值只會擴展到帶符號資料類型的正值範圍。