# トラブルシューティング記録 ## Hugging Face Spaces デプロイ時のバージョン互換性問題 ### 問題 Gradio SDK を使用すると、HF Spaces のベースイメージと Gradio/huggingface_hub のバージョンが競合する。 ### 試行錯誤の記録 | 試行 | 内容 | 結果 | |------|------|------| | 1 | gradio>=5.0.0 | `HfFolder` インポートエラー(huggingface_hub 互換性問題) | | 2 | gradio==4.44.1 + huggingface_hub==0.26.2 | `source` パラメータエラー | | 3 | gradio==5.6.0 + huggingface_hub==0.26.2 | `HfFolder` インポートエラー | | 4 | gradio==4.44.0 + gradio_client==1.3.0 + huggingface_hub==0.25.2 | `sources` パラメータエラー | | 5 | gradio==4.31.0 + gradio_client==0.16.4 | 依存関係競合(gradio は 0.16.2 を要求) | | 6 | gradio==4.31.0(gradio_client 自動解決) | `json_schema_to_python_type` エラー | | 7 | gradio==4.19.2 | `HfFolder` インポートエラー(ベースイメージの huggingface_hub が新しすぎる) | ### 根本原因 - HF Spaces の Gradio SDK ベースイメージは `huggingface-hub>=0.30` と `pydantic~=1.0` を含む - これが様々な Gradio バージョンと競合する - `gradio_client` の `json_schema_to_python_type` 関数に pydantic バージョン依存のバグがある ### 解決策 **Docker SDK を使用する** Docker SDK を使うことで、ベースイメージの制約から解放され、任意のバージョンを使用可能。 ```yaml # README.md のメタデータ sdk: docker ``` ### 教訓 1. HF Spaces で Gradio SDK を使う場合、ベースイメージのバージョンに注意 2. 複雑な依存関係がある場合は最初から Docker SDK を検討 3. gradio_client は gradio が自動管理するため明示指定しない 4. pydantic v1 と v2 の互換性問題に注意 ## Docker ビルド時のパッケージ名問題 ### 問題 Debian Trixie (Testing) で `libgl1-mesa-glx` が非推奨になった。 ### 解決策 ```dockerfile # 古い(エラー) libgl1-mesa-glx libxrender-dev # 新しい(正しい) libgl1 libxrender1 ``` ## huggingface_hub バージョン問題(Docker SDK でも発生) ### 問題 Docker SDK を使っても、pip が最新の huggingface_hub をインストールし、gradio 4.44.1 と互換性がない。 ### 原因 - huggingface_hub 0.25+ で `HfFolder` クラスが削除/移動された - gradio 4.44.1 は `from huggingface_hub import HfFolder` を使用 ### 解決策 requirements.txt で huggingface_hub を gradio より先に固定: ``` huggingface_hub==0.24.0 gradio==4.44.1 ``` ## gradio_client / pydantic バージョン互換性問題 ### 問題 `json_schema_to_python_type` で `TypeError: argument of type 'bool' is not iterable` エラー。 ### 原因 - gradio_client のコードで `"const" in schema` をチェックする箇所がある - pydantic v2 では schema が bool を返す場合があり、`in` 演算子が使えない - gradio_client と pydantic のバージョン不整合 ### エラーログ ``` File "gradio_client/utils.py", line 337, in json_schema_to_python_type if "const" in schema: TypeError: argument of type 'bool' is not iterable ``` ### 解決策 互換性のあるバージョンを固定(gradio_client は明示指定しない): ``` huggingface_hub==0.21.4 gradio==4.19.2 pydantic==2.6.4 ``` ### 重要ポイント - gradio_client は gradio が自動管理するため明示指定しない - pydantic はスキーマ生成に影響するため、互換バージョンを使用 ## gradio_client 明示指定による依存関係エラー ### 問題 ``` ERROR: Cannot install gradio_client==0.11.0 because: gradio 4.19.2 depends on gradio-client==0.10.1 ``` ### 原因 - gradio_client==0.11.0 を明示指定したが、gradio 4.19.2 は gradio-client==0.10.1 を要求 - 既にTROUBLESHOOTING.md の教訓3に「明示指定しない」と記載されていたが無視した ### 解決策 gradio_client の明示指定を削除し、pip に自動解決させる ### 教訓(再確認) **gradio_client は絶対に明示指定しない。gradio が依存関係として自動管理する。** ## Gradio 4.19.2 で streaming が動作しない ### 問題 - カメラ映像は表示されるが、OCR/VLM処理が実行されない - UIの位置情報・検出情報が更新されない ### 原因 - `webcam.stream()` メソッドが Gradio 4.19.2 で期待通り動作しない - `streaming=True` の Image コンポーネントでは `.stream()` より `.change()` が確実 ### 解決策 ```python # 修正前(動作しない) webcam.stream( fn=process_webcam, inputs=[webcam], outputs=[webcam, location_output, info_output], ) # 修正後 webcam.change( fn=process_webcam, inputs=[webcam], outputs=[webcam, location_output, info_output], ) ``` ### デバッグ方法 処理関数にログを追加して Container タブで確認: ```python print(f"[DEBUG] process_webcam called, frame type: {type(frame)}") ``` ## PaddleOCR 3.x で Unknown argument エラー ### 問題 ``` ValueError: Unknown argument: show_log ``` ### 原因 - PaddleOCR 3.x で API が変更され、`show_log`, `enable_mkldnn`, `cpu_threads` などの引数が廃止された ### 解決策 ```python # 修正前(PaddleOCR 2.x) self._ocr = PaddleOCR( use_angle_cls=True, lang=self.lang, use_gpu=self.use_gpu, show_log=False, enable_mkldnn=True, cpu_threads=2, ) # 修正後(PaddleOCR 3.x) self._ocr = PaddleOCR( use_angle_cls=True, lang=self.lang, use_gpu=self.use_gpu, ) ``` ## Gradio でデフォルト外カメラ(背面カメラ)を使用 ### 問題 - スマホでアクセス時、デフォルトで内カメラ(前面)が選択される - 位置特定には外カメラ(背面)が必要 ### 原因 - `gr.Image(sources=["webcam"])` は内カメラをデフォルトにする - Gradio には直接外カメラを指定するオプションがない ### 解決策 JavaScript で `facingMode: 'environment'` を指定: ```python camera_js = """ () => { const setRearCamera = () => { const video = document.querySelector('video'); if (video && video.srcObject) { navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: 'environment' } } }).then(stream => { video.srcObject = stream; }).catch(err => { console.log('外カメラ切り替え失敗:', err); }); } }; setTimeout(setRearCamera, 1000); } """ with gr.Blocks(js=camera_js) as demo: ... ``` ## Gradio 4.19.2 で streaming=True + .change() が動作しない ### 問題 - `streaming=True` で `.change()` を使っても、サーバーにフレームが送信されない - Container ログにデバッグ出力が表示されない ### 原因 - Gradio 4.19.2 の `gr.Image` で `streaming=True` を使っても、 実際にはサーバーへの自動送信が行われない場合がある - ブラウザ側でフレームがキャプチャされるだけで、サーバーに送信されない ### 解決策 手動キャプチャ方式に変更: ```python # streaming=False に変更 webcam = gr.Image( sources=["webcam"], streaming=False, # 手動キャプチャ label="カメラ映像", ) # 解析ボタンを追加 analyze_btn = gr.Button("📸 解析する", variant="primary") # ボタンクリックで処理 analyze_btn.click( fn=process_webcam, inputs=[webcam], outputs=[webcam, location_output, info_output], ) ``` ### カメラ切り替えボタンの実装 ```python switch_camera_js = """ async () => { const video = document.querySelector('video'); const tracks = video.srcObject.getVideoTracks(); const currentFacing = tracks[0].getSettings().facingMode || 'user'; const newFacing = currentFacing === 'user' ? 'environment' : 'user'; tracks.forEach(track => track.stop()); const newStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: newFacing } } }); video.srcObject = newStream; } """ switch_camera_btn.click(fn=None, js=switch_camera_js) ``` ## gr.Image streaming=False で画像がサーバーに送信されない ### 問題 - 解析ボタンを押しても「カメラを起動してください」表示 - Container ログにデバッグ出力なし - `frame=None` がサーバーに渡されている ### 原因 - `gr.Image(sources=["webcam"], streaming=False)` では、カメラUI内の**撮影ボタン**を押して静止画をキャプチャしないと画像がセットされない - カメラプレビューは表示されるが、ユーザーが明示的に撮影しないと `None` のまま ### 解決策 UIの説明を改善して、ユーザーに操作手順を明示: ``` 1. カメラUI内の撮影ボタンで写真を撮る 2. 解析ボタンを押す ``` ### ラベルの改善 ```python webcam = gr.Image( sources=["webcam"], streaming=False, label="📷 カメラで撮影してから解析ボタンを押してください", ) ``` ## デフォルト外カメラが適用されない ### 問題 - `gr.Blocks(js=camera_js)` を設定したが、カメラが内カメラのまま ### 原因 - ページ読み込み時にカメラがまだ起動していない - タイミングの問題で `video.srcObject` が `null` ### 解決策 リトライロジック付きのJavaScriptを使用: ```javascript const initRearCamera = async () => { await new Promise(r => setTimeout(r, 2000)); const video = document.querySelector('video'); if (!video || !video.srcObject) { setTimeout(initRearCamera, 1000); // リトライ return; } // 外カメラに切り替え const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } }); video.srcObject = stream; }; initRearCamera(); ``` ## PaddleOCR 3.x で use_gpu が廃止 ### 問題 ``` ValueError: Unknown argument: use_gpu ``` ### 原因 - PaddleOCR 3.x で `use_gpu` 引数も廃止された - `show_log`, `enable_mkldnn`, `cpu_threads` に続いて `use_gpu` も削除 ### 解決策 ```python # 修正前 self._ocr = PaddleOCR( use_angle_cls=True, lang=self.lang, use_gpu=self.use_gpu, ) # 修正後(PaddleOCR 3.x) self._ocr = PaddleOCR( use_angle_cls=True, lang=self.lang, ) ``` ### 教訓 PaddleOCR 3.x では以下の引数のみ使用: - `use_angle_cls` - `lang` ## カメラ切り替えボタンをオーバーレイ表示 ### 問題 - カメラ切り替えボタンが撮影画面の外にあり使いづらい ### 解決策 JavaScriptでボタンを動的に追加: ```javascript const btn = document.createElement('button'); btn.className = 'camera-switch-overlay'; btn.innerHTML = '🔄 内/外'; btn.style.cssText = 'position:absolute;top:10px;right:10px;z-index:1000;...'; imageContainer.appendChild(btn); ``` MutationObserverでDOMの変更を監視し、ボタンが消えたら再追加。 ## Gradio gr.Image の制限事項 ### 問題 - `streaming=True` でも解析が開始されない - 画面中央タップでカメラが停止する - デフォルトで内カメラになる - 撮影モードへの切り替えが必要 ### 原因 - Gradio の `gr.Image(sources=["webcam"])` は制御が難しい - ブラウザ標準のカメラUIを使用しており、カスタマイズ不可 ### 解決策 **カスタムHTML + JavaScript でカメラUIを自前実装** ```html ``` ```javascript // 外カメラをデフォルトで起動 navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: 'environment' } } }); // 2秒間隔で自動解析 setInterval(() => { // canvasにキャプチャ ctx.drawImage(video, 0, 0); // Base64でサーバーに送信 const imageData = canvas.toDataURL('image/jpeg', 0.8); // Gradioの隠しinputに設定して送信 }, 2000); ``` ### メリット - 画面タップでカメラが停止しない - デフォルト外カメラ - 常時撮影モード - カメラ切り替えボタンをオーバーレイ表示 ## カスタムHTML + JavaScript の Failed to fetch エラー ### 問題 ``` Error: Failed to fetch ``` Containerログに上記のみ表示され、サーバー処理が実行されない ### 原因 - カスタムHTMLからGradioの隠しinputに値を設定してもイベントが正しく発火しない - GradioのセキュリティでAPIへの直接アクセスが制限される可能性 ### 解決策 カスタムHTMLを諦め、Gradio標準の `gr.Image(streaming=True)` + `webcam.stream()` を使用: ```python webcam = gr.Image( sources=["webcam"], streaming=True, type="numpy", ) webcam.stream( fn=process_image, inputs=[webcam], outputs=[location_out, info_out], time_limit=30, stream_every=2, # 2秒間隔 ) ``` ### 補足 - カメラ切り替えは別ボタン + JavaScriptで対応 ## Gradio 4.19.2 で stream() の引数エラー ### 問題 ``` TypeError: event_trigger() got an unexpected keyword argument 'time_limit' ``` ### 原因 - `time_limit` と `stream_every` は Gradio 4.44+ で追加された引数 - Gradio 4.19.2 では使用不可 ### 解決策 ```python # 修正前(Gradio 4.44+) webcam.stream( fn=process_image, inputs=[webcam], outputs=[location_out, info_out], time_limit=30, stream_every=2, ) # 修正後(Gradio 4.19.2 互換) webcam.stream( fn=process_image, inputs=[webcam], outputs=[location_out, info_out], ) ``` ### 教訓 Gradio のバージョンによってAPIが大きく異なるため、使用バージョンを必ず確認すること。 ## Gradio 4.19.2 で常時ストリーミング ### 問題 - `gr.Blocks` + `webcam.stream()` では `time_limit`, `stream_every` が使えない - カメラ画面をクリックしないと撮影が始まらない ### 解決策 `gr.Interface` + `live=True` を使用: ```python demo = gr.Interface( fn=process_and_annotate, inputs=gr.Image(sources=["webcam"], streaming=True), outputs=gr.Image(), live=True, # これが重要! ) ``` 参考: https://www.gradio.app/guides/reactive-interfaces ## 最初から外カメラを強制 ### 問題 - デフォルトで内カメラが起動し、その後外カメラに切り替わる ### 原因 - `facingMode: { ideal: 'environment' }` は「できれば外カメラ」という意味 - ブラウザがまず内カメラを起動してしまう ### 解決策 `{ exact: 'environment' }` を使用して強制: ```javascript const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { exact: 'environment' } } }); ``` 参考: https://stackoverflow.com/questions/52812091/getusermedia-selecting-rear-camera-on-mobile ### 注意 - `exact` は外カメラがない場合エラーになる - フォールバックとして `ideal` も用意しておく ## Gradio 5.0+ へのアップグレード ### 問題 - Gradio 4.19.2 では `gr.WebcamOptions` がサポートされていない - JavaScript で外カメラを強制しても、1クリックが必要 - `live=True` でも映像上にオーバーレイが表示されない ### 原因 - `gr.WebcamOptions` は Gradio 5.24+ で追加された機能 - Gradio 4.x の `gr.Image` は制御が限定的 - iOS ブラウザはセキュリティ上、ユーザークリックなしでカメラ自動起動不可(これは回避不可) ### 解決策 Gradio 5.0+ にアップグレード: ```python # requirements.txt gradio>=5.0.0 numpy>=1.24.0,<2.0.0 # PaddleOCR互換性のため # app.py webcam = gr.Image( sources=["webcam"], streaming=True, webcam_options=gr.WebcamOptions( mirror=False, constraints={"facingMode": {"exact": "environment"}} ) ) webcam.stream( fn=process_and_annotate, inputs=webcam, outputs=output, time_limit=300, stream_every=0.5 ) ``` ### 変更点 1. `gradio>=5.0.0` に変更 2. `huggingface_hub` と `pydantic` の固定削除(gradio に任せる) 3. `numpy<2.0.0` 制限追加(PaddleOCR 互換性) 4. `gr.WebcamOptions` で外カメラ強制 5. `webcam.stream()` で `time_limit`, `stream_every` が使用可能 ### 参考資料 - https://www.gradio.app/docs/gradio/video - https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode - https://pypi.org/project/gradio/ ## gradio-webrtc への移行 ### 問題 - Gradio標準の `gr.Image` では録画ボタンを押す必要がある - 入力と出力が別々のコンポーネントに分かれる - カメラ画面内にオーバーレイが表示できない ### 解決策 gradio-webrtc を使用して単一コンポーネントでストリーミング: ```python from gradio_webrtc import WebRTC with gr.Blocks() as demo: webrtc = WebRTC( label="カメラ", mode="send-receive", # 入出力両方 modality="video", ) webrtc.stream( fn=process_frame, inputs=[webrtc], outputs=[webrtc], # 同じコンポーネントに出力 time_limit=600, ) ``` ### メリット 1. **単一コンポーネント** - 入力と出力が同じ場所に表示 2. **自動ストリーミング** - ボタン不要で常時処理 3. **低遅延** - WebRTCによるリアルタイム通信 4. **オーバーレイ可能** - 処理結果を映像上に直接描画 ### 外カメラ強制 JavaScript で getUserMedia をオーバーライド: ```javascript const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = async (constraints) => { if (constraints && constraints.video) { constraints.video = { ...constraints.video, facingMode: { ideal: 'environment' } }; } return originalGetUserMedia(constraints); }; ``` ### 参考資料 - https://pypi.org/project/gradio-webrtc/ - https://www.gradio.app/guides/object-detection-from-webcam-with-webrtc ## FastRTC への移行 ### 問題 - `gradio-webrtc` が deprecated になり、`fastrtc` への移行が必要 - `get_hf_turn_credentials` が deprecated ### 解決策 ```python # requirements.txt fastrtc # app.py from fastrtc import WebRTC, get_cloudflare_turn_credentials rtc_config = get_cloudflare_turn_credentials if os.getenv("HF_TOKEN") else None webrtc = WebRTC( label="Camera", mode="send-receive", modality="video", track_constraints=TRACK_CONSTRAINTS, rtc_configuration=rtc_config, ) ``` ### 参考資料 - https://fastrtc.org/ - https://fastrtc.org/reference/credentials/ ## Gradio 6.0 Deprecation Warnings ### 問題 ``` DeprecationWarning: The 'css' parameter in the Blocks constructor will be removed in Gradio 6.0 DeprecationWarning: The 'js' parameter in the Blocks constructor will be removed in Gradio 6.0 DeprecationWarning: The 'show_api' parameter in launch() will be removed in Gradio 6.0 ``` ### 解決策 ```python # 修正前 with gr.Blocks(title="dokoCame", css=custom_css, js=auto_start_js) as demo: ... demo.launch(show_api=False) # 修正後 with gr.Blocks(title="dokoCame") as demo: ... demo.launch(css=CUSTOM_CSS, js=AUTO_START_JS) ``` `show_api=False` は現状のバージョンでは代替がないため削除。 ## WebRTC 映像の反転問題 ### 問題 - 外カメラ使用時に映像が左右反転している ### 原因 - WebRTC/FastRTC がデフォルトでミラーリングを適用している ### 解決策 `cv2.flip()` で反転を修正: ```python def process_video_frame(frame: np.ndarray) -> np.ndarray: frame = cv2.flip(frame, 1) # 水平反転(左右のみ) # ... ``` - `cv2.flip(frame, 0)` - 垂直反転(上下) - `cv2.flip(frame, 1)` - 水平反転(左右) - `cv2.flip(frame, -1)` - 両方反転(180度回転) ### ICE Connection 失敗 ### 問題 ``` Received ICE candidate for unknown connection: xxxxx ``` 画面が真っ暗になり接続失敗。 ### 原因 - クラウド環境では TURN サーバーが必要 - NAT/ファイアウォールを超えるため ### 解決策 HF_TOKEN を設定して Cloudflare TURN サーバーを使用: ```python from fastrtc import get_cloudflare_turn_credentials rtc_config = get_cloudflare_turn_credentials if os.getenv("HF_TOKEN") else None ``` HF Spaces の Settings → Repository secrets に `HF_TOKEN` を追加。 ### 参考資料 - https://huggingface.co/blog/fastrtc-cloudflare ## PaddleOCR モデルの事前ダウンロード ### 問題 - 毎回起動時にモデルをダウンロードして時間がかかる ### 解決策 Dockerfile でビルド時に事前ダウンロード: ```dockerfile RUN python -c "from paddleocr import PaddleOCR; PaddleOCR(use_angle_cls=True, lang='japan')" || true ``` ## 再発防止チェックリスト ### 必須確認事項(コード変更時に必ずチェック) 1. **外カメラ設定** - `track_constraints` に `"facingMode": {"exact": "environment"}` が含まれているか - `track_constraints` の構造が正しいか(`{"video": {...}}` 形式) 2. **フッター非表示** - `CUSTOM_CSS` に `footer { display: none !important; }` が含まれているか - `gr.Blocks()` に `css=CUSTOM_CSS` が渡されているか 3. **ボタンラベル** - `button_labels` に日本語ではなく英語("Start", "Stop")が設定されているか - "録音" という文字列がコード内に存在しないか 4. **Stream.ui.launch() は使わない** - `Stream.ui.launch()` はデフォルトUIを使うため、カスタマイズ不可 - 必ず `gr.Blocks()` + `WebRTC` コンポーネントを使用すること ### track_constraints の正しい形式 ```python # 正しい形式 TRACK_CONSTRAINTS = { "video": { "width": {"ideal": 1280}, "height": {"ideal": 720}, "frameRate": {"ideal": 15}, "facingMode": {"exact": "environment"}, } } # 間違った形式(videoキーがない) TRACK_CONSTRAINTS = { "width": {"ideal": 1280}, "facingMode": {"exact": "environment"}, } ```