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
を使用しないでください
>>> 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]
1. 入力タイプ#
入力画像のデータ範囲とデータ型を維持することを目的としていますが、関数はこれらのデータ型のサブセットのみをサポートする場合があります。この場合、入力は必要な場合(可能であれば)必要なタイプに変換され、メモリーコピーが必要な場合は警告メッセージがログに出力されます。ドキュメント文字列にタイプの要件を記入する必要があります。
メインパッケージの次のユーティリティ関数は、開発者とユーザーが使用できます
関数名 |
説明 |
---|---|
img_as_float |
浮動小数点に変換します(整数型は64ビット浮動小数点になります) |
img_as_ubyte |
8ビットunsignedに変換します |
img_as_uint |
16ビットunsignedに変換します |
img_as_int |
16ビットintに変換します |
これらの関数は、イメージを必要なデータ型に変換し、その値を適切に再スケーリングします
>>> 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()
の内挿には、範囲が [0, 1] であるべき float 型のイメージが必要です。そのため、デフォルトでは入力イメージはこの範囲に再スケーリングされます。ただし、場合によっては、イメージの値はユーザーが再スケーリングを望まない気温や降水量などの物理的な測定値を表します。 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 で使用したり、その逆を行ったりする必要がある場合があります。OpenCV イメージデータは NumPy(したがって scikit-image でも)でコピーせずにアクセスできます。OpenCV はカラのイメージに BGR(scikit-image の RGB の代わり)を使用し、そのデータ型はデフォルトで uint8 です(イメージデータのタイプとその意味 を参照)。BGR は Blue Green Red の略です
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. OpenCVでskimage
の画像を使用する#
逆の処理はimg_as_ubyte()
を使用して実行できます
>>> import skimage as ski
>>> cv_image = ski.util.img_as_ubyte(any_skimage_image)
5.4. 画像処理パイプライン#
このdtypeの動作により、画像のdtypeを気にすることなく任意のskimage
関数を連結できます。一方、特定の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になるようにfloat画像の縮尺を変更する場合など)関数は闇雲に画像の強度を伸ばさないようにする必要があります。これは画像を大きく歪める可能性があるからです。たとえば、暗い画像の中で明るいマーカーを探している場合、マーカーが存在しない画像があるかもしれません。その入力強度を全範囲に伸ばすと、バックグラウンドノイズがマーカーのように見えてしまいます。
しかし、全強度の範囲に収まるべきなのに収まらない画像がある場合があります。たとえば、一部のカメラは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)
この動作は対称的です。符号なしデータ型の値は、符号付きデータ型の正の範囲全体に広げられます