diff --git "a/docs/03_TECH/TECH_02_\343\202\267\343\202\271\343\203\206\343\203\240\346\247\213\346\210\220\344\273\225\346\247\230.txt" "b/docs/03_TECH/TECH_02_\343\202\267\343\202\271\343\203\206\343\203\240\346\247\213\346\210\220\344\273\225\346\247\230.txt" index 14bd004..93307ae 100644 --- "a/docs/03_TECH/TECH_02_\343\202\267\343\202\271\343\203\206\343\203\240\346\247\213\346\210\220\344\273\225\346\247\230.txt" +++ "b/docs/03_TECH/TECH_02_\343\202\267\343\202\271\343\203\206\343\203\240\346\247\213\346\210\220\344\273\225\346\247\230.txt" @@ -36,9 +36,9 @@ 2-1. カメラ画像の取得 - ・Picamera2 を使用してフレームを取得する. - ・取得した画像を PC に送信する. - ※ 送信フォーマット(JPEG 圧縮 or RAW 等)は実装時に決定する. + ・Picamera2 を使用してグレースケール(Y8 フォーマット)でフレームを取得する. + ・取得した画像を JPEG 圧縮して PC に送信する. + ※ グレースケールで取得することで,転送データ量を BGR 比で 1/3 に削減する. 2-2. 操舵量の受信 @@ -71,13 +71,13 @@ ・受信した画像から黒線の位置を検出する. ・処理手順: - 1. グレースケール変換 - 2. CLAHE によるコントラスト強調 - 3. ガウシアンブラー(ノイズ除去) - 4. 固定閾値で二値化 - 5. オープニングで孤立ノイズ除去 - 6. 横方向クロージングで途切れ補間 - 7. 白ピクセルに2次多項式フィッティング + 1. CLAHE によるコントラスト強調 + 2. ガウシアンブラー(ノイズ除去) + 3. 固定閾値で二値化 + 4. オープニングで孤立ノイズ除去 + 5. 横方向クロージングで途切れ補間 + 6. 白ピクセルに2次多項式フィッティング + ※ グレースケール変換は Pi 側(撮影時)で完了しているため不要 ・位置偏差・傾き・曲率を算出する. ・詳細は `TECH_01_操舵量計算仕様.txt` を参照する. @@ -132,7 +132,7 @@ そのため,操舵量計算を他の処理から独立させ,計算式やアルゴリズムの 変更が通信・モーター制御・GUI のコードに影響しない構成とする. - ・入力: カメラ画像(NumPy 配列) + ・入力: グレースケールのカメラ画像(NumPy 配列) ・出力: throttle(float: -1.0 ~ +1.0),steer(float: -1.0 ~ +1.0) この入出力を維持する限り,計算の中身(偏差の取り方,制御式, diff --git "a/docs/04_ENV/ENV_02_PC\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" "b/docs/04_ENV/ENV_02_PC\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" index f9f2b97..3c8a5eb 100644 --- "a/docs/04_ENV/ENV_02_PC\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" +++ "b/docs/04_ENV/ENV_02_PC\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" @@ -82,10 +82,67 @@ バージョンが表示されることを確認する. -4. 動作確認 (Verification) +4. 環境変数の設定 (Environment Variables) ------------------------------------------------------------------------ - 4-1. ライブラリの import 確認 + 4-1. .env ファイルの作成 + + プロジェクトルートにある `.env.example` をコピーして `.env` を作成する. + + $ cp .env.example .env + + ・`.env` は Git 管理外のため,環境ごとに作成する必要がある. + ・`.env.example` は設定項目のテンプレートとして Git 管理されている. + + 4-2. 設定項目 + + `.env` に以下の値を記入する. + + PC_IP= # Raspberry Pi からの接続先 + IMAGE_PORT=<ポート番号> # 画像受信ポート(Pi → PC) + CONTROL_PORT=<ポート番号> # 操舵量送信ポート(PC → Pi) + + ・PC_IP は `ipconfig`(Windows)または `ip a`(Linux/Mac)で確認する. + ・ポート番号は Pi 側の `.env` と一致させること. + ・Pi 側の `.env` は `deploy.sh` で転送される + (詳細は `ENV_03_RaspPi環境構築手順.txt` を参照). + + 4-3. 設定例 + + PC_IP=192.168.1.10 + IMAGE_PORT=5555 + CONTROL_PORT=5556 + + +5. アプリの起動 (Run Application) +------------------------------------------------------------------------ + + 5-1. 起動コマンド + + venv を有効化した状態で `src/` ディレクトリ内から以下を実行する. + + $ cd src + $ python -m pc.main + + または,プロジェクトルートから `PYTHONPATH` を指定して実行する. + + $ PYTHONPATH=src python -m pc.main + + ・`src/pc/main.py` がエントリーポイントとなる. + ・プロジェクトルートから `python -m src.pc.main` とすると + モジュール解決に失敗するため,上記いずれかの方法で起動すること. + ・Raspberry Pi との通信を行う場合は,先に Pi 側を起動しておくこと + (Pi 側の起動手順は `ENV_03_RaspPi環境構築手順.txt` を参照). + + 5-2. 終了 + + GUI ウィンドウを閉じる,またはターミナルで Ctrl+C を押す. + + +6. 動作確認 (Verification) +------------------------------------------------------------------------ + + 6-1. ライブラリの import 確認 以下を Python で実行し,エラーが出ないことを確認する. diff --git "a/docs/04_ENV/ENV_03_RaspPi\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" "b/docs/04_ENV/ENV_03_RaspPi\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" index 99edb9b..5b4f0ce 100644 --- "a/docs/04_ENV/ENV_03_RaspPi\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" +++ "b/docs/04_ENV/ENV_03_RaspPi\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.txt" @@ -137,9 +137,5 @@ 4-2. PC 側の起動 - PC 側で以下を実行し,GUI の「接続開始」ボタンを押す. - - $ cd src - $ ../.venv/Scripts/python.exe -m pc.main - - カメラ映像がリアルタイムで表示されれば通信成功. + PC 側の起動手順は `ENV_02_PC環境構築手順.txt` の「5. アプリの起動」を参照する. + GUI の「接続開始」ボタンを押し,カメラ映像がリアルタイムで表示されれば通信成功. diff --git a/src/pc/comm/zmq_client.py b/src/pc/comm/zmq_client.py index 625a580..d373c71 100644 --- a/src/pc/comm/zmq_client.py +++ b/src/pc/comm/zmq_client.py @@ -42,7 +42,7 @@ """画像を非ブロッキングで受信する Returns: - 受信した画像の NumPy 配列,受信データがない場合は None + 受信したグレースケール画像の NumPy 配列,受信データがない場合は None """ if self._image_socket is None: return None @@ -50,7 +50,7 @@ data = self._image_socket.recv(zmq.NOBLOCK) frame = cv2.imdecode( np.frombuffer(data, dtype=np.uint8), - cv2.IMREAD_COLOR, + cv2.IMREAD_GRAYSCALE, ) return frame except zmq.Again: diff --git a/src/pc/gui/main_window.py b/src/pc/gui/main_window.py index 81e35a6..10101f4 100644 --- a/src/pc/gui/main_window.py +++ b/src/pc/gui/main_window.py @@ -4,6 +4,7 @@ カメラ映像のリアルタイム表示と操作 UI を提供する """ +import cv2 import numpy as np from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QImage, QKeyEvent, QPixmap @@ -1013,16 +1014,19 @@ """NumPy 配列の画像を QLabel に表示する Args: - frame: BGR 形式の画像 + frame: グレースケールの画像 """ + # グレースケール → BGR 変換(カラーオーバーレイ描画のため) + bgr = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR) + # オーバーレイ描画 - frame = draw_overlay( - frame, self._last_detect_result, + bgr = draw_overlay( + bgr, self._last_detect_result, self._overlay_flags, ) # BGR → RGB 変換 - rgb = frame[:, :, ::-1].copy() + rgb = bgr[:, :, ::-1].copy() h, w, ch = rgb.shape image = QImage( rgb.data, w, h, ch * w, diff --git a/src/pc/steering/pd_control.py b/src/pc/steering/pd_control.py index fde5a4b..1f3a7fc 100644 --- a/src/pc/steering/pd_control.py +++ b/src/pc/steering/pd_control.py @@ -56,7 +56,7 @@ """カメラ画像から PD 制御で操舵量を計算する Args: - frame: BGR 形式のカメラ画像 + frame: グレースケールのカメラ画像 Returns: 計算された操舵量 diff --git a/src/pc/vision/line_detector.py b/src/pc/vision/line_detector.py index 5a97102..dde4275 100644 --- a/src/pc/vision/line_detector.py +++ b/src/pc/vision/line_detector.py @@ -122,7 +122,7 @@ params.method に応じて検出手法を切り替える Args: - frame: BGR 形式のカメラ画像 + frame: グレースケールのカメラ画像 params: 画像処理パラメータ(None でデフォルト) Returns: @@ -148,8 +148,6 @@ frame: np.ndarray, params: ImageParams, ) -> LineDetectResult: """現行手法: CLAHE + 固定閾値 + 全ピクセルフィッティング""" - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # CLAHE でコントラスト強調 clahe = cv2.createCLAHE( clipLimit=params.clahe_clip, @@ -158,7 +156,7 @@ params.clahe_grid, ), ) - enhanced = clahe.apply(gray) + enhanced = clahe.apply(frame) # ガウシアンブラー blur_k = params.blur_size | 1 @@ -205,15 +203,13 @@ Black-hat 変換で背景より暗い構造を直接抽出し, 固定閾値 + 距離変換 + 行ごと中心抽出で検出する """ - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # Black-hat 変換(暗い構造の抽出) bh_k = params.blackhat_ksize | 1 bh_kernel = cv2.getStructuringElement( cv2.MORPH_ELLIPSE, (bh_k, bh_k), ) blackhat = cv2.morphologyEx( - gray, cv2.MORPH_BLACKHAT, bh_kernel, + frame, cv2.MORPH_BLACKHAT, bh_kernel, ) # ガウシアンブラー @@ -257,15 +253,13 @@ 背景除算で照明勾配を除去し, 適応的閾値で局所ムラにも対応する二重防壁構成 """ - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # 背景除算正規化 bg_k = params.bg_blur_ksize | 1 bg = cv2.GaussianBlur( - gray, (bg_k, bg_k), 0, + frame, (bg_k, bg_k), 0, ) normalized = ( - gray.astype(np.float32) * 255.0 + frame.astype(np.float32) * 255.0 / (bg.astype(np.float32) + 1.0) ) normalized = np.clip( @@ -310,15 +304,13 @@ Black-hat + 適応的閾値の二重正規化に加え, RANSAC で外れ値を除去する最もロバストな構成 """ - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) - # Black-hat 変換 bh_k = params.blackhat_ksize | 1 bh_kernel = cv2.getStructuringElement( cv2.MORPH_ELLIPSE, (bh_k, bh_k), ) blackhat = cv2.morphologyEx( - gray, cv2.MORPH_BLACKHAT, bh_kernel, + frame, cv2.MORPH_BLACKHAT, bh_kernel, ) # 適応的閾値(BINARY: Black-hat 後は線が白) diff --git a/src/pi/camera/capture.py b/src/pi/camera/capture.py index 68fdbc7..712e7c4 100644 --- a/src/pi/camera/capture.py +++ b/src/pi/camera/capture.py @@ -21,7 +21,7 @@ camera_config = self._camera.create_preview_configuration( main={ "size": (config.FRAME_WIDTH, config.FRAME_HEIGHT), - "format": "BGR888", + "format": "Y8", }, ) self._camera.configure(camera_config) @@ -31,7 +31,7 @@ """1フレームを取得する Returns: - BGR 形式の画像(NumPy 配列) + グレースケールの画像(NumPy 配列) """ return self._camera.capture_array()