コンテンツにスキップ

10.1.4 画像処理技術

画像処理パイプライン図

この節を終えると、あなたは次のことができるようになります。

  • 画像フィルタリングが何をしているかを理解する
  • OpenCV を使って平滑化、エッジ検出、二値化を行う
  • 膨張、収縮などの形態学的処理の直感を理解する
  • 古典的な画像処理タスクの基本コードを読み解く

一、画像処理は何を処理しているの?

Section titled “一、画像処理は何を処理しているの?”

古典的な画像処理は、次のように考えられます。

一連のルールで、ピクセルを再調整すること。

深層学習と違って、「データからルールを学ぶ」のではなく、あらかじめルールを自分で書きます。

代表的なタスクは次のとおりです。

  • ノイズ除去
  • ぼかし
  • エッジ抽出
  • 二値化
  • 輪郭強調

サンプルを外部画像に依存させないため、まずは自分で簡単な画像を生成します。

import cv2
import numpy as np
img = np.zeros((240, 320), dtype=np.uint8)
# 白い長方形と灰色の円を描く
cv2.rectangle(img, (30, 40), (140, 180), 255, -1)
cv2.circle(img, (230, 120), 45, 180, -1)
cv2.imwrite("processing_original.png", img)
print("processing_original.png を保存しました")

実行結果の例:

Terminal window
processing_original.png を保存しました

ここではグレースケール画像を直接使います。あとでエッジや閾値処理をする際に扱いやすいからです。


三、フィルタリング:画像を「少しなめらかにする」

Section titled “三、フィルタリング:画像を「少しなめらかにする」”

フィルタリングの直感は次のようなものです。

周囲のピクセルの値も考慮して、画像をより滑らかにする。

import cv2
import numpy as np
img = cv2.imread("processing_original.png", cv2.IMREAD_GRAYSCALE)
blurred = cv2.blur(img, (7, 7))
cv2.imwrite("processing_blur.png", blurred)
print("processing_blur.png を保存しました")

実行結果の例:

Terminal window
processing_blur.png を保存しました

平均フィルタはエッジをやわらかくしますが、細部も失いやすくなります。

import cv2
img = cv2.imread("processing_original.png", cv2.IMREAD_GRAYSCALE)
gaussian = cv2.GaussianBlur(img, (7, 7), 0)
cv2.imwrite("processing_gaussian.png", gaussian)
print("processing_gaussian.png を保存しました")

実行結果の例:

Terminal window
processing_gaussian.png を保存しました

ガウシアンフィルタは、単純な平均フィルタよりもよく使われます。より自然な見た目になりやすいからです。


四、エッジ検出:変化が最も大きい場所を見つける

Section titled “四、エッジ検出:変化が最も大きい場所を見つける”

エッジは次のように考えられます。

明るさが急に変わる位置

たとえば、黒い背景にある白い長方形の境界は、典型的なエッジです。

import cv2
img = cv2.imread("processing_original.png", cv2.IMREAD_GRAYSCALE)
edges = cv2.Canny(img, threshold1=50, threshold2=150)
cv2.imwrite("processing_edges.png", edges)
print("processing_edges.png を保存しました")

実行結果の例:

Terminal window
processing_edges.png を保存しました

ざっくり次のように覚えるとよいです。

  • 低い閾値より小さい: ほぼエッジではない
  • 高い閾値より大きい: エッジの可能性が高い
  • その中間: 周辺とのつながりも見て判断する

五、閾値処理:グレースケール画像を白黒画像にする

Section titled “五、閾値処理:グレースケール画像を白黒画像にする”

閾値処理は、線を1本引くイメージです。

  • その値より大きいものは白
  • その値より小さいものは黒
import cv2
img = cv2.imread("processing_original.png", cv2.IMREAD_GRAYSCALE)
_, binary = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)
cv2.imwrite("processing_binary.png", binary)
print("processing_binary.png を保存しました")

実行結果の例:

Terminal window
processing_binary.png を保存しました

この操作は、次のような場面でよく使われます。

  • 文書スキャン
  • 前景 / 背景の分離
  • 輪郭抽出の前処理

六、形態学的処理:形を加工する

Section titled “六、形態学的処理:形を加工する”

形態学的処理は、特に二値画像の処理に向いています。

「白い領域を少し揉む、広げる、縮める」と考えるとわかりやすいです。

白い領域が小さくなります。

import cv2
import numpy as np
img = cv2.imread("processing_binary.png", cv2.IMREAD_GRAYSCALE)
kernel = np.ones((5, 5), np.uint8)
eroded = cv2.erode(img, kernel, iterations=1)
cv2.imwrite("processing_eroded.png", eroded)
print("processing_eroded.png を保存しました")

実行結果の例:

Terminal window
processing_eroded.png を保存しました

白い領域が大きくなります。

import cv2
import numpy as np
img = cv2.imread("processing_binary.png", cv2.IMREAD_GRAYSCALE)
kernel = np.ones((5, 5), np.uint8)
dilated = cv2.dilate(img, kernel, iterations=1)
cv2.imwrite("processing_dilated.png", dilated)
print("processing_dilated.png を保存しました")

実行結果の例:

Terminal window
processing_dilated.png を保存しました
  • オープニング = 先に収縮してから膨張。小さなノイズを取り除くのに向いている
  • クロージング = 先に膨張してから収縮。小さな穴を埋めるのに向いている
import cv2
import numpy as np
img = cv2.imread("processing_binary.png", cv2.IMREAD_GRAYSCALE)
kernel = np.ones((5, 5), np.uint8)
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imwrite("processing_opened.png", opened)
cv2.imwrite("processing_closed.png", closed)
print("processing_opened.png と processing_closed.png を保存しました")

実行結果の例:

Terminal window
processing_opened.png と processing_closed.png を保存しました

古典的な画像処理操作の選択図


七、これらの処理をつなげてみる

Section titled “七、これらの処理をつなげてみる”

実際のタスクでは、これらの処理は連続して使われることがよくあります。

たとえば、ある対象の輪郭を抽出したいなら、次のような流れになります。

  1. グレースケールに変換
  2. フィルタリングでノイズ除去
  3. 二値化
  4. 形態学的処理で整える
  5. さらにエッジ検出や輪郭解析を行う

以下に、ひとつの小さな処理フローの例を示します。

import cv2
import numpy as np
img = cv2.imread("processing_original.png", cv2.IMREAD_GRAYSCALE)
# ノイズ除去
smoothed = cv2.GaussianBlur(img, (5, 5), 0)
# 二値化
_, binary = cv2.threshold(smoothed, 100, 255, cv2.THRESH_BINARY)
# クロージングで小さなすき間を埋める
kernel = np.ones((5, 5), np.uint8)
cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# エッジ抽出
edges = cv2.Canny(cleaned, 50, 150)
cv2.imwrite("processing_pipeline_smoothed.png", smoothed)
cv2.imwrite("processing_pipeline_binary.png", binary)
cv2.imwrite("processing_pipeline_cleaned.png", cleaned)
cv2.imwrite("processing_pipeline_edges.png", edges)
print("処理フロー全体の結果を保存しました")

実行結果の例:

Terminal window
処理フロー全体の結果を保存しました

画像処理パイプラインの実行結果比較図


八、なぜ今でも古典的な手法を学ぶの?

Section titled “八、なぜ今でも古典的な手法を学ぶの?”

今でもとても役立つからです。

  • 深層学習の前処理として使える
  • 小さなプロジェクトで素早く効果を出せる
  • 工業用途でルールベースの補完ができる
  • 「画像がどう処理されるか」の直感を身につけられる

初心者はすぐに CNN を学びたくなりがちですが、グレースケール、エッジ、閾値の感覚がないと、あとで画像モデルを理解するのが難しくなります。


フィルタリングは「画像をきれいに見せるため」だけだと思う

Section titled “フィルタリングは「画像をきれいに見せるため」だけだと思う”

それだけではありません。 多くの場合、後続のアルゴリズムを安定させるために使います。

閾値は固定で変わらないと思う

Section titled “閾値は固定で変わらないと思う”

実際の画像では照明の変化が大きいので、閾値は場面に合わせて調整することがよくあります。

API だけ学んで、目的を理解しない

Section titled “API だけ学んで、目的を理解しない”

常に自分にこう問いかけましょう。

  • このステップはノイズ除去?
  • それとも境界の強調?
  • それとも形の整理?

このページを終えたら、この evidence card を残します。

入力画像
実行で使うソース画像または生成画像
配列形状
幅、高さ、channels、dtype、座標規約
処理済み出力
グレースケール、切り抜き、エッジ、しきい値処理、または保存済み中間画像
失敗確認
チャネル順、リサイズの歪み、座標ミス、または過剰処理
期待される成果
前後の画像と、出力された shape またはピクセル値

この節で押さえるべき核心は次のとおりです。

古典的な画像処理とは、ルールを使ってピクセルを並べ替え、選び直すことです。

これは深層学習そのものではありませんが、画像認識タスクを理解するうえでとても重要な土台です。


  1. threshold() の閾値を 60120180 に変えて、二値画像の変化を観察しましょう。
  2. 収縮と膨張のカーネルサイズを (3, 3) から (7, 7) に変えて、形の変化を観察しましょう。
  3. 元の画像に小さな白い点を1つ追加して、オープニングでそれを消せるか試してみましょう。
参考実装と解説
  1. しきい値を低くすると、二値画像で白になる画素は通常増えます。しきい値を高くすると、白になる画素は減ります。大切なのは画像そのものを暗記することではなく、変化の方向を読むことです。
  2. (7, 7) のカーネルは (3, 3) より強く収縮し、強く膨張します。細い構造は消えたり、かなり太くなったりします。
  3. オープニングは、カーネルより小さい孤立した白点なら取り除けます。白点が大きい、または大きな領域につながっている場合は残ることがあります。