3.3. scikit-image: 画像処理

著者: Emmanuelle Gouillart

scikit-image は画像処理専用のPythonパッケージで、NumPyの配列を画像オブジェクトとして使用します。 この章では、様々な画像処理タスクのために scikit-image を使用する方法と、NumPyやSciPyのような他の科学的なPythonモジュールとの関係について説明します。

参考

画像の切り抜きや簡単なフィルタリングのような基本的な画像操作については、NumPyとSciPyだけで多くの簡単な操作を実現することができます。 Image manipulation and processing using NumPy and SciPy を参照してください。

マスキングやラベリングといった基本的な操作が前提になるので、この章を読む前に、前の章の内容をよく理解しておく必要があることに注意してください。

3.3.1. イントロダクションとコンセプト

画像はNumPyの配列 np.ndarray です。

画像:

np.ndarray

ピクセル:

array values: a[2, 3]

channels:

array dimensions

image encoding:

dtype (np.uint8, np.uint16, np.float)

filters:

functions (numpy, skimage, scipy)

>>> import numpy as np
>>> check = np.zeros((8, 8))
>>> check[::2, 1::2] = 1
>>> check[1::2, ::2] = 1
>>> import matplotlib.pyplot as plt
>>> plt.imshow(check, cmap='gray', interpolation='nearest')
<matplotlib.image.AxesImage object at ...>
../../_images/sphx_glr_plot_check_001.png

3.3.1.1. scikit-image と科学的Pythonエコシステム

scikit-imagepipconda ベースの Python インストーラ、およびほとんどの Linux ディストリビューションでパッケージ化されています。 NumPy 配列を操作する画像処理と可視化のための他の Python パッケージには以下のものがあります:

scipy.ndimage

N次元配列の場合 基本的なフィルタリング、数理形態学、領域の特性

Mahotas

高速実装に焦点を当てています。

Napari

Qt で作られた高速でインタラクティブな多次元画像ビューア。

いくつかの強力なC++画像処理ライブラリには、Pythonバインディングもあります:

OpenCV

リアルタイムアプリケーションに焦点を当てた、高度に最適化されたコンピュータビジョンライブラリ。

ITK

Insight ToolKitは、レジストレーションや3D画像の操作に特に便利です。

程度の差こそあれ、これらはあまりPythonicでなくNumPyに優しくない傾向があります。

3.3.1.2. scikit-imageに含まれるもの

このライブラリには、主に画像処理アルゴリズムが含まれていますが、データの取り扱いと処理を容易にするユーティリティ関数も含まれています。 以下のサブモジュールを含みます:

color

色空間の変換。

data

テスト画像とサンプルデータ。

draw

NumPyの配列を操作する描画プリミティブ (線、テキストなど) 。

exposure

画像強度調整、例、ヒストグラム等化など

feature

特徴検出と抽出、例、テクスチャー分析コーナーなど。

filters

シャープネス、エッジ検出、ランクフィルター、閾値処理など。

graph

グラフ理論的操作、例えば最短経路。

io

画像やビデオの読み取り、保存、表示。

measure

画像特性の測定、例えば、リージョンプロパティや輪郭など。

metrics

画像に対応するメトリクス、例えば、距離メトリクス、類似度など。

morphology

形態学的操作、例えば、オープニングやスケルトン化。

restoration

復元アルゴリズム、例えばデコンボリューションアルゴリズム、ノイズ除去など。

segmentation

画像を複数の領域に分割する。

transform

幾何学変換やその他の変換、例えば回転やRadon変換など。

util

汎用ユーティリティ。

3.3.2. Importing

scikit-image をインポートするには:

>>> import skimage as ski

ほとんどの機能はサブパッケージに含まれています、例えば:

>>> image = ski.data.cat()

以下ですべてのサブモジュールを一覧できます:

>>> for m in dir(ski): print(m)
__version__
color
data
draw
exposure
feature
filters
future
graph
io
measure
metrics
morphology
registration
restoration
segmentation
transform
util

ほとんどの scikit-image 関数は、NumPyの ndarrays を引数にとります

>>> camera = ski.data.camera()
>>> camera.dtype
dtype('uint8')
>>> camera.shape
(512, 512)
>>> filtered_camera = ski.filters.gaussian(camera, sigma=1)
>>> type(filtered_camera)
<class 'numpy.ndarray'>

3.3.3. データ例

手始めに、作業用のサンプル画像が必要です。 ライブラリーには、これらのうちのいくつかが同梱されています:

skimage.data

>>> image = ski.data.cat()
>>> image.shape
(300, 451, 3)

3.3.4. 入出力、データ型、色空間

I/O: skimage.io

画像をディスクに保存します: skimage.io.imsave()

>>> ski.io.imsave("cat.png", image)

Reading from files: skimage.io.imread()

>>> cat = ski.io.imread("cat.png")
../../_images/sphx_glr_plot_camera_001.png

これは、 ImageIO ライブラリーがサポートする多くのデータフォーマットで機能します。

ロードはURLでも機能します:

>>> logo = ski.io.imread('https://scikit-image.org/_static/img/logo.png')

3.3.4.1. Data types

../../_images/sphx_glr_plot_camera_uint_001.png

Image ndarrays can be represented either by integers (signed or unsigned) or floats.

整数データ型でのオーバーフローに注意してください

>>> camera = ski.data.camera()
>>> camera.dtype
dtype('uint8')
>>> camera_multiply = 3 * camera

Different integer sizes are possible: 8-, 16- or 32-bytes, signed or unsigned.

警告

An important (if questionable) skimage convention: float images are supposed to lie in [-1, 1] (in order to have comparable contrast for all float images)

>>> camera_float = ski.util.img_as_float(camera)
>>> camera.max(), camera_float.max()
(np.uint8(255), np.float64(1.0))

Some image processing routines need to work with float arrays, and may hence output an array with a different type and the data range from the input array

>>> camera_sobel = ski.filters.sobel(camera)
>>> camera_sobel.max()
np.float64(0.644...)

Utility functions are provided in skimage to convert both the dtype and the data range, following skimage's conventions: util.img_as_float, util.img_as_ubyte, etc.

See the user guide for more details.

3.3.4.2. Colorspaces

Color images are of shape (N, M, 3) or (N, M, 4) (when an alpha channel encodes transparency)

>>> face = sp.datasets.face()
>>> face.shape
(768, 1024, 3)

Routines converting between different colorspaces (RGB, HSV, LAB etc.) are available in skimage.color : color.rgb2hsv, color.lab2rgb, etc. Check the docstring for the expected dtype (and data range) of input images.

3.3.5. 画像の前処理/改良

目標: ノイズ除去、特徴(エッジ)抽出、...

3.3.5.1. 局所フィルター

局所フィルターは、ピクセルの値を局所ピクセルの値の関数で置き換えます。この関数は線形でも非線形でもよいです。

局所: 正方形(サイズを選択)、円盤、またはより複雑な 構造要素

../../_images/kernels.png

例: 水平Sobelフィルター

>>> text = ski.data.text()
>>> hsobel_text = ski.filters.sobel_h(text)

水平勾配の計算には次の線形カーネルを使用します:

1   2   1
0 0 0
-1 -2 -1
../../_images/sphx_glr_plot_sobel_001.png

3.3.5.2. 非局所フィルター

非局所フィルターは、画像の広い領域(または画像全体)を使って、あるピクセルの値を変換します:

>>> camera = ski.data.camera()
>>> camera_equalized = ski.exposure.equalize_hist(camera)

ほぼ均一な広い領域のコントラストを高めます。

../../_images/sphx_glr_plot_equalize_hist_001.png

3.3.5.3. 数学的形態学

数学的形態学については wikipedia を参照してください。

単純な形状( 構造化要素 )で画像を探査し、その形状が局所的に画像にどのようにフィットするか、あるいは画像をどのように外すかによって、この画像を修正します。

Default structuring element: 4-connectivity of a pixel

>>> # Import structuring elements to make them more easily accessible
>>> from skimage.morphology import disk, diamond
>>> diamond(1)
array([[0, 1, 0],
[1, 1, 1],
[0, 1, 0]], dtype=uint8)
../../_images/diamond_kernel.png

Erosion = minimum filter. Replace the value of a pixel by the minimal value covered by the structuring element.:

>>> a = np.zeros((7,7), dtype=np.uint8)
>>> a[1:6, 2:5] = 1
>>> a
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
>>> ski.morphology.binary_erosion(a, diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
>>> #Erosion removes objects smaller than the structure
>>> ski.morphology.binary_erosion(a, diamond(2)).astype(np.uint8)
array([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)

Dilation: maximum filter:

>>> a = np.zeros((5, 5))
>>> a[2, 2] = 1
>>> a
array([[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0.]])
>>> ski.morphology.binary_dilation(a, diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)

Opening: erosion + dilation:

>>> a = np.zeros((5,5), dtype=int)
>>> a[1:4, 1:4] = 1; a[4, 4] = 1
>>> a
array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 1]])
>>> ski.morphology.binary_opening(a, diamond(1)).astype(np.uint8)
array([[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]], dtype=uint8)

Opening removes small objects and smoothes corners.

より高度な数学的形態学が利用できます: tophat、skeletonizationなど。

参考

基本的な数理形態学は scipy.ndimage.morphology にも実装されています。 ``scipy.ndimage``の実装は任意の次元の配列に対して動作します。


3.3.6. 画像分割

画像セグメンテーションとは、画像の異なる領域に異なるラベルを付けることであり、例えば、関心のあるオブジェクトのピクセルを抽出するために行われます。

3.3.6.1. バイナリセグメンテーション: 前景+背景

ヒストグラムに基づく方法: Otsu thresholding

Tip

Otsu method は、前景と背景を分離する閾値を見つけるための単純なヒューリスティックである。

camera = ski.data.camera()
val = ski.filters.threshold_otsu(camera)
mask = camera < val
../../_images/sphx_glr_plot_threshold_001.png

離散画像の連結成分にラベルを付ける

Tip

前景オブジェクトを分離したら、それらを互いに分離するのに使います。 そのために、それぞれに異なる整数ラベルを割り当てることができます。

合成データ:

>>> n = 20
>>> l = 256
>>> im = np.zeros((l, l))
>>> rng = np.random.default_rng()
>>> points = l * rng.random((2, n ** 2))
>>> im[(points[0]).astype(int), (points[1]).astype(int)] = 1
>>> im = ski.filters.gaussian(im, sigma=l / (4. * n))
>>> blobs = im > im.mean()

接続されているすべてのコンポーネントにラベルを貼ります:

>>> all_labels = ski.measure.label(blobs)

接続されているすべての前景コンポーネントにラベルを貼ります:

>>> blobs_labels = ski.measure.label(blobs, background=0)
../../_images/sphx_glr_plot_labels_001.png

参考

scipy.ndimage.find_objects() は、画像内のオブジェクトのスライスを返すのに便利です。

3.3.6.2. マーカーに基づく方法

リージョンのセットの中にマーカーがあれば、それを使ってリージョンを分割することができます。

Watershed セグメンテーション

Watershed (skimage.segmentation.watershed()) は、画像の "basins " を埋める領域拡大アプローチです

>>> # Generate an initial image with two overlapping circles
>>> x, y = np.indices((80, 80))
>>> x1, y1, x2, y2 = 28, 28, 44, 52
>>> r1, r2 = 16, 20
>>> mask_circle1 = (x - x1) ** 2 + (y - y1) ** 2 < r1 ** 2
>>> mask_circle2 = (x - x2) ** 2 + (y - y2) ** 2 < r2 ** 2
>>> image = np.logical_or(mask_circle1, mask_circle2)
>>> # Now we want to separate the two objects in image
>>> # Generate the markers as local maxima of the distance
>>> # to the background
>>> import scipy as sp
>>> distance = sp.ndimage.distance_transform_edt(image)
>>> peak_idx = ski.feature.peak_local_max(
... distance, footprint=np.ones((3, 3)), labels=image
... )
>>> peak_mask = np.zeros_like(distance, dtype=bool)
>>> peak_mask[tuple(peak_idx.T)] = True
>>> markers = ski.morphology.label(peak_mask)
>>> labels_ws = ski.segmentation.watershed(
... -distance, markers, mask=image
... )

ランダムウォーカー セグメンテーション

ランダムウォーカーアルゴリズム (skimage.segmentation.random_walker()) はWatershedに似ていますが、より "確率的" なアプローチです。 画像内のラベルの拡散という考えに基づいています:

>>> # Transform markers image so that 0-valued pixels are to
>>> # be labelled, and -1-valued pixels represent background
>>> markers[~image] = -1
>>> labels_rw = ski.segmentation.random_walker(image, markers)
../../_images/sphx_glr_plot_segmentations_001.png

3.3.7. 領域の特性を測定する

例: 2つの分割された領域のサイズと周囲を計算します:

>>> properties = ski.measure.regionprops(labels_rw)
>>> [float(prop.area) for prop in properties]
[770.0, 1168.0]
>>> [prop.perimeter for prop in properties]
[np.float64(100.91...), np.float64(126.81...)]

参考

プロパティによっては、別のAPIで scipy.ndimage.measurements に関数が用意されています (リストが返されます) 。

3.3.8. データの可視化とインタラクション

意味のある可視化は、与えられた処理パイプラインをテストするときに役立ちます。

いくつかの画像処理操作:

>>> coins = ski.data.coins()
>>> mask = coins > ski.filters.threshold_otsu(coins)
>>> clean_border = ski.segmentation.clear_border(mask)

バイナリ結果の可視化:

>>> plt.figure()
<Figure size ... with 0 Axes>
>>> plt.imshow(clean_border, cmap='gray')
<matplotlib.image.AxesImage object at 0x...>

輪郭の視覚化

>>> plt.figure()
<Figure size ... with 0 Axes>
>>> plt.imshow(coins, cmap='gray')
<matplotlib.image.AxesImage object at 0x...>
>>> plt.contour(clean_border, [0.5])
<matplotlib.contour.QuadContourSet ...>

専用ユーティリティ関数 skimage を使用します:

>>> coins_edges = ski.segmentation.mark_boundaries(
... coins, clean_border.astype(int)
... )
../../_images/sphx_glr_plot_boundaries_001.png

3.3.9. コンピュータビジョンのための特徴抽出

幾何学的記述子やテクスチャ記述子は、以下の目的で画像から抽出することができます

  • 画像の一部を分類する (例:空と建物)

  • 異なる画像の一部を一致させます (例:物体検出)

  • コンピュータビジョン の他の多くのアプリケーション。

例: Harris検出器によるコーナー検出:

tform = ski.transform.AffineTransform(
scale=(1.3, 1.1), rotation=1, shear=0.7,
translation=(210, 50)
)
image = ski.transform.warp(
data.checkerboard(), tform.inverse, output_shape=(350, 350)
)
coords = ski.feature.corner_peaks(
ski.feature.corner_harris(image), min_distance=5
)
coords_subpix = ski.feature.corner_subpix(
image, coords, window_size=13
)
../../_images/sphx_glr_plot_features_001.png

(この例はscikit-imageの plot_corner の例から引用しています)

scikit-imageの plot_matching の例で説明されているように、角のような注目点は、異なる画像内のオブジェクトをマッチングするために使用することができます。

3.3.10. 全コード例

3.3.11. scikit-imageの章の例

画像の作成

Creating an image

シンプルな画像の表示

Displaying a simple image

画像のヒストグラムを均等化する

Equalizing the histogram of an image

Sobelフィルタによる水平勾配の計算

Computing horizontal gradients with the Sobel filter

輪郭セグメンテーション

Segmentation contours

大津の二値化法

Otsu thresholding

アフィン変換

Affine transform

画像の連結成分にラベルを付ける

Labelling connected components of an image

様々なノイズ除去フィルター

Various denoising filters

Gallery generated by Sphinx-Gallery