10.1.4 画像処理技術

この節を終えると、あなたは次のことができるようになります。
- 画像フィルタリングが何をしているかを理解する
- OpenCV を使って平滑化、エッジ検出、二値化を行う
- 膨張、収縮などの形態学的処理の直感を理解する
- 古典的な画像処理タスクの基本コードを読み解く
一、画像処理は何を処理しているの?
Section titled “一、画像処理は何を処理しているの?”古典的な画像処理は、次のように考えられます。
一連のルールで、ピクセルを再調整すること。
深層学習と違って、「データからルールを学ぶ」のではなく、あらかじめルールを自分で書きます。
代表的なタスクは次のとおりです。
- ノイズ除去
- ぼかし
- エッジ抽出
- 二値化
- 輪郭強調
二、まずはテスト画像を作る
Section titled “二、まずはテスト画像を作る”サンプルを外部画像に依存させないため、まずは自分で簡単な画像を生成します。
import cv2import 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 を保存しました")実行結果の例:
processing_original.png を保存しましたここではグレースケール画像を直接使います。あとでエッジや閾値処理をする際に扱いやすいからです。
三、フィルタリング:画像を「少しなめらかにする」
Section titled “三、フィルタリング:画像を「少しなめらかにする」”フィルタリングの直感は次のようなものです。
周囲のピクセルの値も考慮して、画像をより滑らかにする。
平均フィルタ
Section titled “平均フィルタ”import cv2import 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 を保存しました")実行結果の例:
processing_blur.png を保存しました平均フィルタはエッジをやわらかくしますが、細部も失いやすくなります。
ガウシアンフィルタ
Section titled “ガウシアンフィルタ”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 を保存しました")実行結果の例:
processing_gaussian.png を保存しましたガウシアンフィルタは、単純な平均フィルタよりもよく使われます。より自然な見た目になりやすいからです。
四、エッジ検出:変化が最も大きい場所を見つける
Section titled “四、エッジ検出:変化が最も大きい場所を見つける”エッジは次のように考えられます。
明るさが急に変わる位置
たとえば、黒い背景にある白い長方形の境界は、典型的なエッジです。
Canny エッジ検出
Section titled “Canny エッジ検出”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 を保存しました")実行結果の例:
processing_edges.png を保存しました2つの閾値はどう考える?
Section titled “2つの閾値はどう考える?”ざっくり次のように覚えるとよいです。
- 低い閾値より小さい: ほぼエッジではない
- 高い閾値より大きい: エッジの可能性が高い
- その中間: 周辺とのつながりも見て判断する
五、閾値処理:グレースケール画像を白黒画像にする
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 を保存しました")実行結果の例:
processing_binary.png を保存しましたこの操作は、次のような場面でよく使われます。
- 文書スキャン
- 前景 / 背景の分離
- 輪郭抽出の前処理
六、形態学的処理:形を加工する
Section titled “六、形態学的処理:形を加工する”形態学的処理は、特に二値画像の処理に向いています。
「白い領域を少し揉む、広げる、縮める」と考えるとわかりやすいです。
収縮(Erosion)
Section titled “収縮(Erosion)”白い領域が小さくなります。
import cv2import 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 を保存しました")実行結果の例:
processing_eroded.png を保存しました膨張(Dilation)
Section titled “膨張(Dilation)”白い領域が大きくなります。
import cv2import 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 を保存しました")実行結果の例:
processing_dilated.png を保存しましたオープニングとクロージング
Section titled “オープニングとクロージング”- オープニング = 先に収縮してから膨張。小さなノイズを取り除くのに向いている
- クロージング = 先に膨張してから収縮。小さな穴を埋めるのに向いている
import cv2import 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 を保存しました")実行結果の例:
processing_opened.png と processing_closed.png を保存しました
七、これらの処理をつなげてみる
Section titled “七、これらの処理をつなげてみる”実際のタスクでは、これらの処理は連続して使われることがよくあります。
たとえば、ある対象の輪郭を抽出したいなら、次のような流れになります。
- グレースケールに変換
- フィルタリングでノイズ除去
- 二値化
- 形態学的処理で整える
- さらにエッジ検出や輪郭解析を行う
以下に、ひとつの小さな処理フローの例を示します。
import cv2import 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("処理フロー全体の結果を保存しました")実行結果の例:
処理フロー全体の結果を保存しました
八、なぜ今でも古典的な手法を学ぶの?
Section titled “八、なぜ今でも古典的な手法を学ぶの?”今でもとても役立つからです。
- 深層学習の前処理として使える
- 小さなプロジェクトで素早く効果を出せる
- 工業用途でルールベースの補完ができる
- 「画像がどう処理されるか」の直感を身につけられる
初心者はすぐに CNN を学びたくなりがちですが、グレースケール、エッジ、閾値の感覚がないと、あとで画像モデルを理解するのが難しくなります。
九、初心者がよくやる間違い
Section titled “九、初心者がよくやる間違い”フィルタリングは「画像をきれいに見せるため」だけだと思う
Section titled “フィルタリングは「画像をきれいに見せるため」だけだと思う”それだけではありません。 多くの場合、後続のアルゴリズムを安定させるために使います。
閾値は固定で変わらないと思う
Section titled “閾値は固定で変わらないと思う”実際の画像では照明の変化が大きいので、閾値は場面に合わせて調整することがよくあります。
API だけ学んで、目的を理解しない
Section titled “API だけ学んで、目的を理解しない”常に自分にこう問いかけましょう。
- このステップはノイズ除去?
- それとも境界の強調?
- それとも形の整理?
このページを終えたら、この evidence card を残します。
- 入力画像
- 実行で使うソース画像または生成画像
- 配列形状
- 幅、高さ、channels、dtype、座標規約
- 処理済み出力
- グレースケール、切り抜き、エッジ、しきい値処理、または保存済み中間画像
- 失敗確認
- チャネル順、リサイズの歪み、座標ミス、または過剰処理
- 期待される成果
- 前後の画像と、出力された shape またはピクセル値
この節で押さえるべき核心は次のとおりです。
古典的な画像処理とは、ルールを使ってピクセルを並べ替え、選び直すことです。
これは深層学習そのものではありませんが、画像認識タスクを理解するうえでとても重要な土台です。
threshold()の閾値を60、120、180に変えて、二値画像の変化を観察しましょう。- 収縮と膨張のカーネルサイズを
(3, 3)から(7, 7)に変えて、形の変化を観察しましょう。 - 元の画像に小さな白い点を1つ追加して、オープニングでそれを消せるか試してみましょう。
参考実装と解説
- しきい値を低くすると、二値画像で白になる画素は通常増えます。しきい値を高くすると、白になる画素は減ります。大切なのは画像そのものを暗記することではなく、変化の方向を読むことです。
(7, 7)のカーネルは(3, 3)より強く収縮し、強く膨張します。細い構造は消えたり、かなり太くなったりします。- オープニングは、カーネルより小さい孤立した白点なら取り除けます。白点が大きい、または大きな領域につながっている場合は残ることがあります。