注釈

こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。

参加する理由

  • 専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。

  • 学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。

  • 限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。

  • 特別割引: 最新製品を特別割引でお楽しみいただけます。

  • 季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。

👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!

7. Canny エッジ検出

この章では、Raspberry Pi + Picamera2 を使ってリアルタイム動画を取得し、OpenCV の Canny アルゴリズム によるエッジ検出を行います。 エッジ検出はコンピュータビジョンの基本処理のひとつであり、Canny アルゴリズムは安定性が高く、ノイズにも強い手法として広く知られています。

1. Canny アルゴリズムは何をするのか?

画像における エッジ とは、通常、濃度(グレースケール値)が大きく変化する位置を指します。たとえば:

  • 物体の輪郭

  • 明るい領域と暗い領域の境界

  • 構造を表すエッジ線

Canny エッジ検出の目的は、次のとおりです:

  • 不要な干渉を抑えながら、 エッジ情報を正確に抽出する こと

  • 後続の 輪郭検出物体分割幾何形状認識 (たとえば円や長方形の検出)のための信頼できる基盤を提供すること

  • ロボットビジョンでは、 経路検出障害物認識 によく利用されること

Illustration of Canny edge detection

2. コードの実行

重要

開始する前に、次の項目を確認してください:

  • パンチルトが組み立てられている

  • Raspberry Pi のデスクトップにアクセスできる

  • コードパッケージがインストールされている

  • Fusion HAT+ がインストールされ、設定されている

  • OpenCV がインストールされている

詳細については 0. OpenCV のセットアップ を参照してください。

  1. ターミナルを開き、次のコマンドを入力します:

    cd ~/ai-lab-kit/opencv_python
    python3 cv_7_canny.py
    

    Tip

    動画ファイルを処理するための cv_7_canny_video.py と、リアルタイム映像と動画を組み合わせて表示する cv_7_canny_conbine.py も用意しています。

  2. プログラムを実行すると、次の 2 つの OpenCV ウィンドウが表示されます:

    • Camera – ライブカメラ映像を表示

    • Canny Edges – 検出されたエッジをリアルタイムで表示

    トラックバーを使ってエッジ検出のしきい値を調整できます。 q キーを押すか、いずれかのウィンドウを閉じると終了します。

3. 完全なコード

from picamera2 import Picamera2
import cv2

# Empty callback function for trackbars (required by OpenCV API)
def _noop(x):
   pass

# -----------------------------
# Camera setup
# -----------------------------
picam2 = Picamera2()

# Create a preview configuration:
# size: resolution of the camera image
# format: XRGB8888 (4-channel image, similar to BGRA)
picam2.configure(
   picam2.create_preview_configuration(
      main={"size": (640, 480), "format": "XRGB8888"}
   )
)

# Start the camera
picam2.start()

# -----------------------------
# Create OpenCV windows
# -----------------------------
WIN_CAM = "Camera"        # window for original image
WIN_EDGE = "Canny Edges"  # window for edge detection result

cv2.namedWindow(WIN_CAM)
cv2.namedWindow(WIN_EDGE)

# -----------------------------
# Create trackbars to tune Canny thresholds
# -----------------------------
# low_th: lower threshold for Canny
# high_th: higher threshold for Canny
cv2.createTrackbar("low_th",  WIN_EDGE, 50, 255, _noop)
cv2.createTrackbar("high_th", WIN_EDGE, 150, 255, _noop)

print("Press 'q' to exit")

# -----------------------------
# Main loop
# -----------------------------
while True:
   # Capture one frame from the camera (BGRA format)
   frame_bgra = picam2.capture_array()

   # Convert BGRA to BGR for OpenCV processing
   frame_bgr = cv2.cvtColor(frame_bgra, cv2.COLOR_BGRA2BGR)

   # Convert the frame to grayscale
   gray = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY)

   # Apply Gaussian blur to reduce noise
   blurred = cv2.GaussianBlur(gray, (5, 5), 0)

   # Read current threshold values from trackbars
   low_th = cv2.getTrackbarPos("low_th", WIN_EDGE)
   high_th = cv2.getTrackbarPos("high_th", WIN_EDGE)

   # Ensure high_th is always larger than low_th
   if high_th <= low_th:
      high_th = low_th + 1
      cv2.setTrackbarPos("high_th", WIN_EDGE, high_th)

   # Perform Canny edge detection
   edges = cv2.Canny(blurred, low_th, high_th)

   # Show original camera image
   cv2.imshow(WIN_CAM, frame_bgr)

   # Show edge detection result
   cv2.imshow(WIN_EDGE, edges)

   # Process GUI events and keyboard input
   key = cv2.waitKey(1) & 0xFF

   # Press 'q' to exit the program
   if key == ord("q"):
      break

   # Exit if the user closes any OpenCV window
   if (cv2.getWindowProperty(WIN_CAM, cv2.WND_PROP_VISIBLE) < 1 or
      cv2.getWindowProperty(WIN_EDGE, cv2.WND_PROP_VISIBLE) < 1):
      break

# -----------------------------
# Cleanup
# -----------------------------
picam2.stop()             # Stop the camera
cv2.destroyAllWindows()   # Close all OpenCV windows

4. コード解説

  1. トラックバー用のコールバック関数を定義する:

    def _noop(x):
        pass
    

    OpenCV のトラックバーにはコールバック関数が必要です。 今回は特に処理を行わないため、空の関数で十分です。

  2. Picamera2 を初期化し、プレビュー形式を設定する:

    picam2 = Picamera2()
    picam2.configure(
        picam2.create_preview_configuration(
            main={"size": (640, 480), "format": "XRGB8888"}
        )
    )
    picam2.start()
    

    これにより、Raspberry Pi カメラが 640×480 で起動します。 XRGB8888 は 4 チャンネル形式なので、取得されるフレームは BGRA ライクな形式になります。

  3. 2 つの OpenCV ウィンドウを作成する:

    WIN_CAM = "Camera"
    WIN_EDGE = "Canny Edges"
    
    cv2.namedWindow(WIN_CAM)
    cv2.namedWindow(WIN_EDGE)
    

    ひとつのウィンドウには元のカメラ画像を表示し、もうひとつには Canny の結果を表示します。

  4. Canny のしきい値をリアルタイム調整するためのトラックバーを作成する:

    cv2.createTrackbar("low_th",  WIN_EDGE, 50, 255, _noop)
    cv2.createTrackbar("high_th", WIN_EDGE, 150, 255, _noop)
    
    • low_th: Canny の低しきい値

    • high_th: Canny の高しきい値

    これらのスライダーを動かすことで、エッジ検出の感度を調整できます。

  5. フレームを取得し、OpenCV 処理用に変換する:

    frame_bgra = picam2.capture_array()
    frame_bgr = cv2.cvtColor(frame_bgra, cv2.COLOR_BGRA2BGR)
    

    カメラ出力は 4 チャンネルのため、標準的な 3 チャンネル BGR 形式へ変換します。

  6. グレースケール化し、画像をぼかす:

    gray = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    
    • Canny はグレースケール画像に対して動作します。

    • Gaussian blur はノイズを低減し、不要な偽エッジが出すぎるのを防ぎます。

  7. トラックバーの値を読み取り、妥当な状態に保つ:

    low_th = cv2.getTrackbarPos("low_th", WIN_EDGE)
    high_th = cv2.getTrackbarPos("high_th", WIN_EDGE)
    
    if high_th <= low_th:
        high_th = low_th + 1
        cv2.setTrackbarPos("high_th", WIN_EDGE, high_th)
    

    Canny では high_thlow_th より大きい必要があります。 この処理では、ユーザーが値を近づけすぎた場合でも、自動的に補正します。

  8. Canny エッジ検出を実行する:

    edges = cv2.Canny(blurred, low_th, high_th)
    

    Canny は画像中の強いエッジを強調して抽出します。 しきい値を低くするとエッジは多く検出されますが、ノイズも増えやすくなります。

  9. 2 つのウィンドウを表示する:

    cv2.imshow(WIN_CAM, frame_bgr)
    cv2.imshow(WIN_EDGE, edges)
    

    一方のウィンドウにはライブカメラ映像を表示し、もう一方には検出されたエッジを表示します。

  10. 終了条件( q キーまたはウィンドウを閉じる):

    key = cv2.waitKey(1) & 0xFF
    if key == ord("q"):
        break
    
    if (cv2.getWindowProperty(WIN_CAM, cv2.WND_PROP_VISIBLE) < 1 or
        cv2.getWindowProperty(WIN_EDGE, cv2.WND_PROP_VISIBLE) < 1):
        break
    

    初学者でも、キーボード操作とウィンドウのクローズの 2 通りで停止できるようになっています。

  11. 後片付け:

    picam2.stop()
    cv2.destroyAllWindows()
    

    リソースを解放するため、最後に必ずカメラを停止し、OpenCV のウィンドウをすべて閉じてください。

5. なぜ Canny は有用なのか?

Canny の出力は、その後のビジョンタスクに非常に適しています:

Application

Description

Contour detection

cv2.findContours を Canny の出力に適用して、物体形状を取得できる

Object segmentation

エッジを手がかりとして、ターゲットと背景を分離できる

Shape recognition

Hough 変換と組み合わせて、円や直線などを検出できる

Robot navigation

地面、道路、障害物の輪郭を検出して経路計画を支援できる

OCR / Target localization

文字領域、QR コード、マーカーなどは明確なエッジ特徴を持つことが多い

Canny は単に「見た目が面白い」処理ではなく、より広い CV パイプラインへの 入口 となる重要な処理です。

6. しきい値選択のヒント

Scenario

low_th

high_th

Notes

安定した室内照明

50

150

一般的なケースで、安定した結果が得られる

強い照明・高コントラスト

100

200

偽エッジを減らすため、しきい値を高めに設定する

暗所・ノイズが多い環境

30

100

より多くのディテールを残すため、しきい値を低めにする

非常にぼやけたエッジ

20

80

さらに低く設定して、エッジに対する感度を上げる

トラックバーを使って適切な範囲をすばやく調整し、その後プログラム内に固定値として書き込むとよいでしょう。

7. 発展練習

  • cv2.findContours を Canny の出力に適用して、物体の輪郭を描いてみましょう。

  • Gaussian カーネルのサイズを変更して、エッジ精度がどう変化するか観察してみましょう。

  • 明るい環境と暗い環境で異なるしきい値を試し、ダブルしきい値の効果を理解してみましょう。

  • cv2.HoughLines (直線)や cv2.HoughCircles (円)と組み合わせて、エッジ画像から図形検出を行ってみましょう。