プロジェクト — 画像処理
1. プロジェクト:NumPy を使った画像処理
(1) シナリオ
コンピュータビジョンのパイプラインを開発しています。画像をピクセル単位で処理する必要があります。つまり、グレースケールへの変換、フィルタの適用、統計量の算出などを行う必要がありますが、これらすべてをNumPyの配列演算を使って行います。
(2) タスク
1. 合成画像を作成する
PYTHON
import numpy as np
# Create a 100x100 RGB image
rng = np.random.default_rng(42)
image = rng.integers(0, 256, size=(100, 100, 3), dtype=np.uint8)
print(f"Image shape: {image.shape}") # (100, 100, 3)
print(f"Image dtype: {image.dtype}") # uint8
2. グレースケールに変換する
PYTHON
# Grayscale: weighted average of RGB channels
# Standard weights: 0.299 R + 0.587 G + 0.114 B
gray = np.dot(image[..., :3], [0.299, 0.587, 0.114]).astype(np.uint8)
print(f"Gray shape: {gray.shape}") # (100, 100)
3. 簡単なフィルタの適用(水平エッジ検出)
PYTHON
# Edge detection: subtract adjacent rows
edges = np.abs(gray[:-1] - gray[1:])
print(f"Edge map shape: {edges.shape}") # (99, 100)
print(f"Edge intensity: mean={edges.mean():.1f}, max={edges.max()}")
4. カラーチャンネルの抽出
PYTHON
# Extract individual channels
red = image[:, :, 0]
green = image[:, :, 1]
blue = image[:, :, 2]
print(f"Red channel: mean={red.mean():.1f}, min={red.min()}, max={red.max()}")
print(f"Green channel: mean={green.mean():.1f}, min={green.min()}, max={green.max()}")
print(f"Blue channel: mean={blue.mean():.1f}, min={blue.min()}, max={blue.max()}")
5. 画像の統計情報
PYTHON
# Histogram (simplified: count pixels in each intensity range)
bins = np.array([0, 50, 100, 150, 200, 256])
indices = np.digitize(gray.ravel(), bins)
hist = np.bincount(indices, minlength=len(bins)+1)[1:-1]
for i in range(len(bins)-1):
print(f"Intensity {bins[i]:3d}-{bins[i+1]:3d}: {hist[i]:4d} pixels")
(1) ▶ サンプル:合成画像の作成(難易度 ⭐)
PYTHON
import numpy as np
# Create a 50x50 RGB image with a gradient
x = np.arange(50)
y = np.arange(50)
xx, yy = np.meshgrid(x, y)
red = np.zeros((50, 50), dtype=np.uint8)
green = (xx * 5).astype(np.uint8)
blue = (yy * 5).astype(np.uint8)
image = np.stack([red, green, blue], axis=-1)
print("Shape:", image.shape)
print("Red channel:", image[:, :, 0].min(), "-", image[:, :, 0].max())
print("Green channel:", image[:, :, 1].min(), "-", image[:, :, 1].max())
print("Blue channel:", image[:, :, 2].min(), "-", image[:, :, 2].max())
出力:
TEXT形状: (50, 50, 3) 赤チャンネル:0 - 0 グリーンチャンネル:0~245 青チャンネル:0 ~ 245
(2) ▶ サンプル:グレースケール変換(難易度 ⭐)
PYTHON
import numpy as np
# Simulate an RGB image
rng = np.random.default_rng(42)
image = rng.integers(0, 256, size=(10, 10, 3), dtype=np.uint8)
# Standard grayscale weights
gray = (0.299 * image[:, :, 0] + 0.587 * image[:, :, 1] + 0.114 * image[:, :, 2]).astype(np.uint8)
print("Original shape:", image.shape)
print("Gray shape:", gray.shape)
print("Gray pixel [0, 0]:", gray[0, 0])
print("Gray mean:", gray.mean())
出力:
TEXT元の形状:(10, 10, 3) 灰色の図形:(10, 10) グレーのピクセル [0, 0]: 72 グレーの平均値:129.0
(3) ▶ サンプル:簡単なエッジ検出(難易度 ⭐⭐)
PYTHON
import numpy as np
# Create a simple image with a vertical edge
image = np.zeros((10, 10), dtype=np.uint8)
image[:, 5:] = 255
# Horizontal edge detection
h_edges = np.abs(image[:-1, :] - image[1:, :])
print("Horizontal edges:\n", h_edges)
# Vertical edge detection
v_edges = np.abs(image[:, :-1] - image[:, 1:])
print("Vertical edges:\n", v_edges)
出力:
TEXT水平方向の縁: [[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 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 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0] [ 0 0 0 0 255 0 0 0 0]]
❓ よくある質問
Q なぜグレースケールに 0.299 R + 0.587 G + 0.114 B を使うのですか?
A これらの重み付けは人間の知覚に合致しています。人間は緑に最も敏感で、青には最も鈍感だからです。 単純な平均計算(R+G+B)/3ではこの点が考慮されず、色が薄っぺらな結果になってしまいます。
Q NumPyを使って実際の画像を処理することはできますか?
A はい。画像を
matplotlib.pyplot.imread() または imageio.imread() で読み込むと、NumPy配列が返されます。その後、ここで示したのと同じ演算を適用してください。Q 画像において、uint8とfloat32の違いは何ですか?
A uint8(0~255)は、保存および表示のための標準形式です。float32は、オーバーフローや丸め誤差を防ぐために、演算(フィルタ処理や変換など)に使用されます。
📖 まとめ
このプロジェクトでは、以下のことを実践しました:
- RGBチャンネルに対する3D配列のインデックス指定
np.dot:重み付きグレースケール変換用- エッジ検出のための配列のスライシング
np.mean、np.min、np.max(チャネル統計用)- ヒストグラムの計算には
np.digitizeおよびnp.bincountを使用する
📝 練習問題
-
初心者向け(難易度 ⭐):画像の赤チャンネルと青チャンネルを入れ替えてみてください。これにより、どのような色かぶりが生じますか?
-
中級(難易度 ⭐⭐):行の代わりに隣接する列を差し引くことで、垂直方向のエッジ検出器を実装してください。その結果を比較してください。
-
上級 (難易度 ⭐⭐⭐): 簡単なぼかしフィルターを実装してください。各ピクセルを、その周囲3×3のピクセルの平均値に置き換えます。ブロードキャストとスライシングを使用し、ループは使わないでください。