okpy

Pythonエンジニア兼テックリーダーが、多くのプロジェクトとチーム運営から得た実践的な知識を共有するブログです。

Pythonでの画像処理、まだ複雑なコードと格闘していませんか?scikit-imageでスマートに解決しよう!

Pythonでの画像処理、まだ複雑なコードと格闘していませんか?scikit-imageでスマートに解決しよう!

📝 TL;DR (3行要約)

scikit-imageは、Pythonで科学的な画像分析や処理を行うための強力なライブラリです。 画像のノイズ除去、物体検出、特徴抽出など、高度な画像タスクを驚くほど簡単なコードで実現します。 NumPyとシームレスに連携し、豊富なアルゴリズムが揃っているため、研究から実務まで幅広く活用できます。


1. 🤔 一体scikit-imageとは何?(核心的な役割と主な使用例)

プログラミングの世界に足を踏み入れたばかりの皆さん、こんにちは!Pythonで画像を扱おうとしたとき、「ピクセルを一つずつ操作するのは大変だ…」「もっと賢い方法はないの?」と感じたことはありませんか?そんな悩みを解決してくれる魔法の道具箱が、今回ご紹介するscikit-imageです。

核心的な役割: 画像データの「科学実験室」🔬

scikit-imageをひと言で表すなら、それは画像の「科学実験室」です。

多くの人が画像を「見る」もの、つまり単なる絵として捉えています。しかし、コンピュータの世界では、画像は数値(ピクセルの明るさや色)が並んだ巨大な行列(マトリックス)です。scikit-imageは、この数値の集まりを単なる絵としてではなく、分析・解析の対象となる「科学データ」として扱うための専門的なツールを提供してくれます。

想像してみてください。あなたは科学者で、目の前には顕微鏡で撮影した細胞の画像があります。この画像から、 - 細胞は全部でいくつあるのか? - それぞれの細胞の大きさや形は? - 健康な細胞とそうでない細胞を見分ける特徴は何か?

といった情報を得たいとします。scikit-imageという実験室には、このような問いに答えるための様々な「実験器具」が揃っています。画像をクリーンにするフィルター(ノイズ除去)、特定の領域を切り出すメス(セグメンテーション)、形やテクスチャを測る測定器(特徴抽出)など、高度な分析に必要なアルゴリズムがすべて用意されているのです。

このライブラリの素晴らしい点は、これらの複雑なアルゴリズムを、私たち開発者が数学的な詳細を深く理解していなくても、まるで実験器具を棚から取り出して使うように、直感的な関数呼び出しで利用できることです。これにより、私たちは画像の背後にある「意味」や「情報」を引き出すことに集中できます。

主な使用例: scikit-imageが輝く舞台✨

この「科学実験室」が、具体的にどのような場面で活躍するのか、代表的な例を3つ見ていきましょう。

  1. 医療画像の解析 🩺

    • タスク: MRICTスキャン、顕微鏡画像から、特定の臓器、腫瘍、細胞などを自動で検出し、その面積や体積を計測する。
    • なぜscikit-image?: 医療画像はノイズが多く、対象物の境界が曖昧なことがよくあります。scikit-imageには、このような難しい条件下でも安定して機能する高度なフィルター処理やセグメンテーション(領域分割)アルゴリズムが豊富に揃っています。例えば、「ウォーターシェッドアルゴリズム」を使えば、隣接し合う細胞を綺麗に分離して個別にカウントすることができます。これにより、医師の診断支援や、創薬研究の効率化に大きく貢献しています。
  2. 衛星画像の解析と地理情報システム(GIS) 🛰️

    • タスク: 衛星写真から森林破壊の進行状況を監視したり、都市部の建物を識別して地図を作成したり、農作物の生育状況を色情報から分析したりする。
    • なぜscikit-image?: 広大な範囲を撮影した衛星画像は巨大なデータです。scikit-imageは、効率的なメモリ管理で大規模な画像を扱うことができ、NumPyと連携して高速な計算を実現します。画像の色やテクスチャ(模様)から特徴を抽出し、「この領域は森林」「ここは市街地」「ここは農地」といったように自動で分類するタスクでその真価を発揮します。これにより、環境保護や都市計画、農業生産性の向上に役立てられています。
  3. 製造業における品質管理(外観検査)🏭

    • タスク: 工場の生産ラインを流れる製品(例えば、電子部品や食品)の画像を高速で撮影し、傷、汚れ、欠け、異物混入などの欠陥をリアルタイムで自動検出する。
    • なぜscikit-image?: 人間の目による検査には限界があり、見逃しや疲労による精度の低下が問題となります。scikit-imageのテンプレートマッチングや特徴点検出といった機能を使えば、正常な製品のパターンをコンピュータに学習させ、それと異なる部分を「欠陥」として瞬時に検出するシステムを構築できます。これにより、製品の品質を均一に保ち、生産効率を大幅に向上させることが可能になります。

このように、scikit-imageは単なる画像加工ツールではなく、画像に含まれる情報を科学的に抽出し、様々な分野で具体的な課題を解決するためのプロフェッショナルなライブラリなのです。


2. 💻 インストール方法

scikit-imageのインストールは非常に簡単です。ターミナル(WindowsならコマンドプロンプトPowerShell)を開いて、以下のコマンドを一行入力するだけです。

pip install scikit-image

もし、Jupyter NotebookやGoogle Colabなどでサンプルコードを動かす場合は、多くの場合すでにインストールされていますが、もしNo module named 'skimage'のようなエラーが出たら、このコマンドを実行してください。

💡 ワンポイントアドバイス: Pythonプロジェクトでは、venvなどを使ってプロジェクトごとに仮想環境を作成することが推奨されています。仮想環境を使えば、ライブラリのバージョンが他のプロジェクトと衝突するのを防ぐことができますよ。


3. 🛠️ 実際に動作するサンプルコード

百聞は一見に如かず。scikit-imageがどれほどパワフルか、実際に動くコードで体感してみましょう。

ここでは、scikit-imageの代表的な機能である画像セグメンテーションに挑戦します。具体的には、一枚の画像に写っている複数のコインを、それぞれ別の物体として認識し、色分けして表示するコードです。

このコードは、必要なライブラリがインストールされていれば、コピー&ペーストするだけでそのまま実行できます。

import numpy as np
import matplotlib.pyplot as plt
from skimage import data
from skimage.transform import rescale
from skimage.filters import threshold_otsu
from skimage.segmentation import clear_border
from skimage.measure import label, regionprops
from skimage.morphology import closing, square
from skimage.color import label2rgb

# 1. データの準備と前処理
# scikit-imageに付属しているサンプル画像「coins」を読み込む
# 画像が大きすぎるため、20%に縮小する
original_image = data.coins()
image = rescale(original_image, 0.2, anti_aliasing=False)

# 2. 2値化処理
# 画像をグレースケールのまま、物体(コイン)と背景に分離する
# Otsu's methodというアルゴリズムで最適な閾値(しきいち)を自動で決定
thresh = threshold_otsu(image)
# 閾値より明るいピクセルをTrue(白)、暗いピクセルをFalse(黒)にする
bw = image > thresh

# 3. ノイズ除去と穴埋め
# 2値化した画像に残っている小さな黒い点(ノイズ)を除去する
# closing処理でコイン内部の小さな穴を埋める
cleared = clear_border(bw)
closed = closing(cleared, square(3))

# 4. ラベリング(物体検出)
# 連結しているピクセルの塊(=各コイン)を検出し、それぞれに一意の番号(ラベル)を割り振る
label_image = label(closed)

# 5. 結果の可視化
# 元の画像、検出した各領域、そして元の画像に検出結果を重ね合わせたものを表示する

# label_imageをカラー表示に変換(背景は黒、各コインは異なる色)
image_label_overlay = label2rgb(label_image, image=image, bg_label=0)

# matplotlibを使って3つの画像を並べて表示
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
ax = axes.ravel()

# 1枚目: オリジナルの縮小画像
ax[0].imshow(image, cmap=plt.cm.gray)
ax[0].set_title('Original Image')
ax[0].axis('off')

# 2枚目: ラベリングされた画像
ax[1].imshow(label_image, cmap='nipy_spectral')
ax[1].set_title('Labeled Coins')
ax[1].axis('off')

# 3枚目: オリジナル画像に重ね合わせた結果
ax[2].imshow(image_label_overlay)
ax[2].set_title('Overlay Result')
ax[2].axis('off')

# 各コインの情報を取得して、個数を表示
regions = regionprops(label_image)
num_coins = len(regions)
fig.suptitle(f'Found {num_coins} coins!', fontsize=16)

plt.tight_layout()
plt.show()

# 検出したコインの情報をいくつか表示
print(f"検出されたコインの数: {num_coins}")
for i, region in enumerate(regions[:5]): # 最初の5つだけ表示
    # 各コインの重心座標と面積を表示
    y0, x0 = region.centroid
    area = region.area
    print(f"Coin #{i+1}: 中心座標({y0:.2f}, {x0:.2f}), 面積: {area} ピクセル")

このコードを実行すると、3枚の画像が表示されるはずです。左から「元の画像」「各コインが別々の色で塗り分けられた画像」「元の画像に色分け結果を重ねた画像」です。見事にコインが一つ一つ認識されているのがわかると思います。


4. 🔍 コードの詳細説明

さて、先ほどの魔法のようなコードが、内部で何をやっているのかをステップごとに見ていきましょう。一行ずつではなく、意味のある塊(チャンク)で解説しますね。

【チャンク1】ライブラリのインポートと画像の準備 📚

import numpy as np
import matplotlib.pyplot as plt
from skimage import data
# ... (skimageからの他のインポート) ...

original_image = data.coins()
image = rescale(original_image, 0.2, anti_aliasing=False)
  • 役割: プログラムの実行に必要な道具(ライブラリ)を揃え、分析対象の画像を読み込んでいます。
  • 解説:
    • numpy数値計算の基本ライブラリで、scikit-imageは内部で画像をNumPy配列として扱います。
    • matplotlib.pyplotは、結果をグラフや画像として表示するためのおなじみのライブラリです。
    • skimageからは、今回使う様々な「実験器具」(関数)をインポートしています。data.coins()でサンプル画像を簡単に取得できるのは便利ですね。
    • rescaleで画像を縮小しているのは、処理を速くするためと、ブログ記事で結果を見やすくするためです。

【チャンク2】2値化処理: 世界を白と黒に分ける 🌓

thresh = threshold_otsu(image)
bw = image > thresh
  • 役割: グレースケールの画像を、はっきりと「物体」と「背景」に分けるための前処理です。
  • 解説:
    • threshold_otsuは非常に賢い関数です。画像の明るさの分布(ヒストグラム)を解析し、「ここを境目にすれば、物体と背景が最も綺麗に分かれるだろう」という最適な境界線(閾値)を自動で計算してくれます。
    • image > threshというシンプルな比較演算で、画像の各ピクセル閾値より明るいか(True)、暗いか(False)を判定し、白黒の画像(専門用語で2値画像)を生成しています。Trueが白、Falseが黒に対応します。

【チャンク3】ノイズ除去と穴埋め: 画像を綺麗にお掃除 🧹

cleared = clear_border(bw)
closed = closing(cleared, square(3))
  • 役割: 2値化した画像に残りやすい小さなゴミ(ノイズ)を取り除き、後の処理がうまくいくように画像を整形します。
  • 解説:
    • clear_borderは、画像の端に接している物体を取り除く関数です。コインが画像の端で切れている場合、それをカウントしないようにするためです。
    • closingモルフォロジー演算と呼ばれる画像処理技術の一つです。小さな穴を埋めたり、オブジェクトの輪郭を滑らかにする効果があります。ここでは、コインの模様によってできてしまった内部の黒い点を埋め、各コインがひとつの塊として認識されるようにしています。

【チャンク4】ラベリング: 一つ一つの物体に名札をつける 🏷️

label_image = label(closed)
  • 役割: このコードの核心部分です。綺麗になった白黒画像から、独立した白い塊(=コイン)を見つけ出し、それぞれに1, 2, 3, ... とユニークな番号(ラベル)を割り振ります。
  • 解説: label関数は、隣接している白いピクセルを探索し、それらを一つのグループと見なします。そして、グループごとに異なる整数値を割り当てた新しい画像を生成します。この結果、背景は0、1つ目のコインの領域はすべて1、2つ目のコインの領域はすべて2といった具合の画像が出来上がります。

【チャンク5】結果の可視化と情報抽出: 分析結果を人間に分かりやすく見せる 📊

image_label_overlay = label2rgb(label_image, image=image, bg_label=0)
# ... (matplotlibによる表示コード) ...
regions = regionprops(label_image)
# ... (コインの情報を表示するコード) ...
  • 役割: 機械が理解した結果(ラベル画像)を、人間が見て直感的に理解できる形(色分けされた画像)に変換し、さらに各物体の具体的な情報(面積や中心座標など)を抽出します。
  • 解説:
    • label2rgbは、ラベル画像をカラフルな画像に変換してくれる便利な関数です。ラベル1は赤、2は青、といったように自動で色を割り当て、元の画像に半透明で重ねて表示することもできます。
    • regionprops(region properties: 領域の特性)は、ラベリング後の画像から、各ラベル(各コイン)に関する様々な幾何学的情報を計算してくれる非常に強力な関数です。このおかげで、region.areaで面積を、region.centroidで中心座標を簡単に取得できるのです。

このように、scikit-imageを使えば、一連の複雑な画像分析パイプラインを、非常に読みやすく直感的なコードで構築できることがお分かりいただけたかと思います。


5. ⚠️ 注意点またはヒント

scikit-imageは非常に強力ですが、初心者が陥りがちな罠や、知っておくと便利なヒントがあります。ここでは特に重要なものを2つだけ紹介します。

罠: 画像のデータ型と値の範囲に注意! 🔢

  • 問題: scikit-imageの多くの関数は、画像を浮動小数点数(float)型で、値の範囲が0.0から1.0であることを期待しています。しかし、一般的な画像ファイル(JPG, PNGなど)を読み込むと、データ型は符号なし8ビット整数(uint8)型で、値の範囲が0から255になっていることがほとんどです。
  • なぜ罠なのか: この違いを知らずにuint8の画像をそのまま関数に渡すと、予期しない真っ黒または真っ白な画像が出力されたり、エラーが出たりします。例えば、0.5閾値にしたいのに、画像の全ピクセル0255の値を持っていたら、意図通りに動作しません。
  • 解決策: 画像を読み込んだ後、skimage.util.img_as_float()という便利な関数を使って、値を0.01.0の範囲に正規化する習慣をつけましょう。逆に、画像を保存する前にはskimage.util.img_as_ubyte()0255の範囲に戻すのが一般的です。
from skimage import io
from skimage.util import img_as_float, img_as_ubyte

# 画像を読み込む (通常は uint8, 0-255)
image_uint8 = io.imread('my_image.png') 

# scikit-imageで処理する前にfloatに変換
image_float = img_as_float(image_uint8)

# ... ここで様々な画像処理 ...

# 保存する前にuint8に戻す
image_to_save = img_as_ubyte(processed_image_float)
io.imsave('processed_image.png', image_to_save)

ヒント: 公式ドキュメントのギャラリーは宝の山! 💎

  • 何がすごいのか: scikit-image公式ウェブサイトにあるギャラリーは、単なる機能紹介ではありません。やりたいこと(例:「エッジを検出したい」「特定の形を見つけたい」)ごとに、具体的な課題とそれを解決するためのサンプルコードが豊富に掲載されています。
  • 活用法: 何か新しい画像処理タスクに挑戦したいと思ったら、まずこのギャラリーを覗いてみてください。自分の目的に近いサンプルを見つけ、そのコードをコピーしてきて、自分の画像に合わせて少しずつ改造していくのが、最も効率的な学習方法です。コードだけでなく、その背景にあるアルゴリズムの簡単な解説もついているので、理解を深めるのにも役立ちます。

6. 🔗 一緒に見ておくと良いライブラリ

scikit-imageをマスターしたら、次の一歩としてぜひ触れてみてほしいのが OpenCV (opencv-python) です。

  • OpenCVとは?: OpenCVもまた、非常に有名な画像処理・コンピュータビジョンライブラリです。scikit-imageが科学計算やアルゴリズムの綺麗さに重点を置いているのに対し、OpenCVはより広範な機能、特にリアルタイム処理、動画解析、そしてディープラーニングと連携した最先端のコンピュータビジョン機能(顔認識、物体追跡など)に強みを持っています。
  • どう使い分ける?:
    • scikit-image: 画像のアルゴリズミックな分析、教育・研究用途、静止画像の精密な処理が得意。コードがPythonicで読みやすい。
    • OpenCV: Webカメラからの映像をリアルタイムで処理したり、動画ファイルから特定のシーンを抜き出したり、高速な処理性能が求められる商用アプリケーションの開発が得意。
  • なぜ一緒に学ぶと良い?: 両者は競合するだけでなく、相互に補完しあう関係です。例えば、OpenCVで高速にカメラから画像を取得し、その画像の詳細な分析をscikit-imageで行う、といった連携も可能です。両方の長所を理解することで、実現できることの幅が格段に広がります。

7. 🎉 まとめ

今日はお疲れ様でした!scikit-imageという、Pythonにおける画像処理の強力な味方について学んできました。

  • scikit-imageは画像の「科学実験室」: 画像を単なる絵ではなく、分析可能なデータとして扱い、そこから有益な情報を引き出すためのツールが満載です。
  • 簡単なコードで高度な処理: コインの検出で見たように、画像の2値化、ノイズ除去、ラベリングといった一連の処理を、直感的な関数を繋ぎ合わせることで実現できました。
  • データ型に注意: uint8 (0-255)float (0.0-1.0) の違いは、初心者がつまずきやすいポイント。意識して変換を行いましょう。
  • 次の一歩はOpenCV: リアルタイム処理や動画にも興味が湧いたら、OpenCVの世界を覗いてみましょう。

さあ、知識をインプットしただけで終わらせるのはもったいないです。ぜひ、ご自身の手でコードを動かして、その面白さを体感してみてください。

【今日の挑戦課題】 🚀 1. サンプルコードで使った data.coins() を、data.page()data.chelsea() など、他のサンプル画像に差し替えて実行してみましょう。 2. threshold_otsu で自動決定された閾値 thresh の値を print() で表示してみてください。そして、bw = image > threshthresh の部分を、手動で少し大きい値や小さい値に変えてみると、結果(コインの検出数)がどのように変わるか観察してみましょう。

この小さな一歩が、あなたを画像処理マスターへと導く大きな飛躍に繋がります。Happy Coding!