Spaces:
Runtime error
Runtime error
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>画像処理アプリ</title> | |
| <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> | |
| <style> | |
| /* 既存のスタイル */ | |
| body { | |
| background-color: #f0f0f5; | |
| color: #333; | |
| padding: 40px 20px; | |
| } | |
| /* ... 他の既存のスタイル ... */ | |
| /* ヘルプボタンのスタイル */ | |
| .help-button { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 50%; | |
| background-color: #007bff; | |
| color: white; | |
| border: none; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.2); | |
| z-index: 1000; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| } | |
| .help-button:hover { | |
| background-color: #0056b3; | |
| } | |
| /* モーダル内のスタイル */ | |
| .help-section { | |
| margin-bottom: 30px; | |
| padding: 15px; | |
| border-radius: 8px; | |
| background-color: #f8f9fa; | |
| } | |
| .help-section h4 { | |
| color: #007bff; | |
| margin-bottom: 15px; | |
| } | |
| .step-list { | |
| padding-left: 20px; | |
| } | |
| .step-list li { | |
| margin-bottom: 10px; | |
| } | |
| .modal-body img { | |
| max-width: 100%; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
| } | |
| </style> | |
| <style> | |
| body { | |
| background-color: #f0f0f5; | |
| color: #333; | |
| padding: 40px 20px; | |
| } | |
| h1 { | |
| color: #555; | |
| margin-bottom: 30px; | |
| font-weight: bold; | |
| text-align: center; | |
| } | |
| .image-preview, .processed-preview { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| margin-top: 20px; | |
| } | |
| #result { | |
| margin-top: 40px; | |
| display: none; | |
| } | |
| .slider-container { | |
| text-align: left; | |
| margin-top: 20px; | |
| } | |
| .slider-label { | |
| font-size: 1.2rem; | |
| color: #333; | |
| } | |
| .btn-primary { | |
| background-color: #007bff; | |
| border-color: #007bff; | |
| font-size: 1.2rem; | |
| padding: 10px 20px; | |
| border-radius: 50px; | |
| } | |
| .btn-primary:hover { | |
| background-color: #0056b3; | |
| border-color: #004085; | |
| } | |
| .form-control, .custom-select { | |
| border-radius: 20px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); | |
| } | |
| .nav-tabs { | |
| margin-bottom: 30px; | |
| border-bottom: 2px solid #007bff; | |
| } | |
| .nav-tabs .nav-link { | |
| border: none; | |
| color: #555; | |
| font-size: 1.1rem; | |
| padding: 12px 25px; | |
| border-radius: 10px 10px 0 0; | |
| } | |
| .nav-tabs .nav-link.active { | |
| background-color: #007bff; | |
| color: white; | |
| } | |
| .tab-content { | |
| padding: 20px; | |
| background: white; | |
| border-radius: 0 0 10px 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| #loadingSpinner { | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(255,255,255,0.9); | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 0 15px rgba(0,0,0,0.2); | |
| z-index: 9999; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="loadingSpinner" style="display: none;"> | |
| <i class="fas fa-spinner fa-spin fa-3x"></i> | |
| <p>画像を処理中です。少々お待ちください...</p> | |
| </div> | |
| <div class="container"> | |
| <h1><i class="fas fa-image"></i> AIデンティファイ</h1> | |
| <ul class="nav nav-tabs" role="tablist"> | |
| <li class="nav-item"> | |
| <a class="nav-link active" id="general-tab" data-toggle="tab" href="#general" role="tab"> | |
| <i class="fas fa-magic"></i> 個人情報保護モード | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" id="face-tab" data-toggle="tab" href="#face" role="tab"> | |
| <i class="fas fa-user-circle"></i> 顔保護モード | |
| </a> | |
| </li> | |
| </ul> | |
| <div class="tab-content"> | |
| <!-- 一般処理モード --> | |
| <div class="tab-pane fade show active" id="general" role="tabpanel"> | |
| <div class="form-group"> | |
| <label for="uploadImage1">画像をアップロード:</label> | |
| <input type="file" id="uploadImage1" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage1', 'uploadedImage1')"> | |
| </div> | |
| <img id="uploadedImage1" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;"> | |
| <div class="form-group mt-4"> | |
| <label for="processingType">処理方法を選択:</label> | |
| <select id="processingType" class="custom-select"> | |
| <option value="opencv">OpenCVインペイント</option> | |
| <option value="simple_lama">Simple Lamaインペイント</option> | |
| <option value="stamp">stampインペイント</option> | |
| <option value="mosaic">mosaicインペイント</option> | |
| <option value="llm-auto">llm-autoインペイント</option> | |
| <option value="llm">llmインペイント</option> | |
| </select> | |
| </div> | |
| <div class="slider-container"> | |
| <label for="riskLevel1" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel1">50</span></label> | |
| <div id="slider1"></div> | |
| </div> | |
| <button class="btn btn-primary mt-4" onclick="processGeneralImage()">処理開始</button> | |
| </div> | |
| <!-- 顔照合モード --> | |
| <div class="tab-pane fade" id="face" role="tabpanel"> | |
| <div class="form-group"> | |
| <label for="uploadImage2">処理する画像をアップロード:</label> | |
| <input type="file" id="uploadImage2" class="form-control-file" accept="image/*" onchange="previewAndResizeImage('uploadImage2', 'uploadedImage2')"> | |
| </div> | |
| <img id="uploadedImage2" class="image-preview" src="#" alt="アップロードされた画像" style="display: none;"> | |
| <div class="form-group mt-4"> | |
| <label for="faceOption">自分の顔の入力方法を選択:</label> | |
| <select id="faceOption" class="custom-select" onchange="toggleFaceInput()"> | |
| <option value="upload">ファイルからアップロード</option> | |
| <option value="camera">カメラで撮影</option> | |
| </select> | |
| </div> | |
| <div class="form-group" id="uploadFaceGroup"> | |
| <label for="uploadFace">顔画像をアップロード:</label> | |
| <input type="file" id="uploadFace" class="form-control-file" accept="image/*" onchange="previewFaceImage()"> | |
| <img id="facePreview" class="image-preview" src="#" alt="顔画像のプレビュー" style="display: none;"> | |
| </div> | |
| <div class="form-group" id="cameraFaceGroup" style="display: none;"> | |
| <video id="cameraStream" width="100%" autoplay></video> | |
| <button class="btn btn-secondary mt-2" onclick="captureFaceImage()">顔をキャプチャ</button> | |
| <canvas id="cameraCanvas" style="display: none;"></canvas> | |
| </div> | |
| <div class="slider-container"> | |
| <label for="riskLevel2" class="slider-label">リスクレベル (0-100): <span id="riskLevelLabel2">50</span></label> | |
| <div id="slider2"></div> | |
| </div> | |
| <button class="btn btn-primary mt-4" onclick="processFaceImage()">処理開始</button> | |
| </div> | |
| <!-- 処理結果(共通) --> | |
| <div id="result" class="mt-5"> | |
| <h2>処理結果:</h2> | |
| <img id="processedImage" class="processed-preview" src="" alt=""> | |
| <a id="downloadLink" class="btn btn-success mt-3" href="#" download="processed_image.jpg">処理された画像をダウンロード</a> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ヘルプボタン --> | |
| <button class="help-button" data-toggle="modal" data-target="#helpModal"> | |
| <i class="fas fa-question"></i> | |
| </button> | |
| <!-- ヘルプモーダル --> | |
| <div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true"> | |
| <div class="modal-dialog modal-lg" role="document"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title" id="helpModalLabel"> | |
| <i class="fas fa-question-circle"></i> AIデンティファイの使い方 | |
| </h5> | |
| <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | |
| <span aria-hidden="true">×</span> | |
| </button> | |
| </div> | |
| <div class="modal-body"> | |
| <!-- 一般処理モードの説明 --> | |
| <div class="help-section"> | |
| <h4><i class="fas fa-magic"></i> 個人情報保護モードの使い方</h4> | |
| <ol class="step-list"> | |
| <li>このモードではあなたの個人情報を簡単に保護することができます</li> | |
| <li>「画像をアップロード」ボタンをクリックして、処理したい画像を選択します。</li> | |
| <li>処理方法を以下から選択します: | |
| <ul> | |
| <li><strong>OpenCVインペイント:</strong> 一般的な画像補正</li> | |
| <li><strong>Simple Lamaインペイント:</strong> 高度な画像補正</li> | |
| <li><strong>stampインペイント:</strong> スタンプによる画像加工</li> | |
| <li><strong>mosaicインペイント:</strong> モザイクによる加工</li> | |
| </ul> | |
| </li> | |
| <li>スライダーでリスクレベルを調整します(0-100): | |
| <ul> | |
| <li>値が高いほど、より強い保護が適用されます</li> | |
| <li>推奨値は30前後です</li> | |
| </ul> | |
| </li> | |
| <li>「処理開始」ボタンをクリックして処理を実行します。</li> | |
| </ol> | |
| </div> | |
| <!-- 顔照合モードの説明 --> | |
| <div class="help-section"> | |
| <h4><i class="fas fa-user-circle"></i> 顔保護モードの使い方</h4> | |
| <ol class="step-list"> | |
| <li>このモードではあなたの顔以外を隠すことができます。</li> | |
| <li>「処理する画像をアップロード」から、処理したい画像を選択します。</li> | |
| <li>自分の顔の入力方法を選択します: | |
| <ul> | |
| <li><strong>ファイルからアップロード:</strong> 既存の顔写真を使用</li> | |
| <li><strong>カメラで撮影:</strong> Webカメラを使用して顔写真を撮影</li> | |
| </ul> | |
| </li> | |
| <li>選択した方法で顔画像を入力します。</li> | |
| <li>スライダーでリスクレベルを調整します。</li> | |
| <li>「処理開始」ボタンをクリックして処理を実行します。</li> | |
| </ol> | |
| </div> | |
| <!-- 共通の注意事項 --> | |
| <div class="help-section"> | |
| <h4><i class="fas fa-exclamation-triangle"></i> 注意事項</h4> | |
| <ul> | |
| <li>アップロードする画像は1200×1200ピクセル以下に自動でリサイズされます。</li> | |
| <li>処理には数秒から30秒ほどかかる場合があります。</li> | |
| <li>処理が完了すると、処理結果が表示され、画像のダウンロードが可能になります。</li> | |
| <li>ブラウザのプライバシー設定によっては、カメラの使用許可が必要な場合があります。</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- 既存のスクリプト --> | |
| <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> | |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> | |
| <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> | |
| <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> | |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> | |
| <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> | |
| <script> | |
| // スライダーの初期化 | |
| $(function() { | |
| $("#slider1, #slider2").slider({ | |
| range: "min", | |
| value: 50, | |
| min: 0, | |
| max: 100, | |
| slide: function(event, ui) { | |
| const labelId = $(this).attr('id') === 'slider1' ? 'riskLevelLabel1' : 'riskLevelLabel2'; | |
| $("#" + labelId).text(ui.value); | |
| } | |
| }); | |
| }); | |
| let resizedImageBlob1 = null; | |
| let resizedImageBlob2 = null; | |
| let faceImageBlob = null; | |
| function previewAndResizeImage(inputId, imageId) { | |
| const fileInput = document.getElementById(inputId); | |
| const uploadedImage = document.getElementById(imageId); | |
| if (fileInput.files && fileInput.files[0]) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const img = new Image(); | |
| img.onload = function() { | |
| const maxWidth = 1200; | |
| const maxHeight = 1200; | |
| let width = img.width; | |
| let height = img.height; | |
| if (width > maxWidth || height > maxHeight) { | |
| const ratio = Math.min(maxWidth / width, maxHeight / height); | |
| width *= ratio; | |
| height *= ratio; | |
| } | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = width; | |
| canvas.height = height; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.drawImage(img, 0, 0, width, height); | |
| uploadedImage.src = canvas.toDataURL('image/jpeg'); | |
| uploadedImage.style.display = 'block'; | |
| canvas.toBlob((blob) => { | |
| if (inputId === 'uploadImage1') { | |
| resizedImageBlob1 = blob; | |
| } else { | |
| resizedImageBlob2 = blob; | |
| } | |
| }, 'image/jpeg'); | |
| }; | |
| img.src = e.target.result; | |
| }; | |
| reader.readAsDataURL(fileInput.files[0]); | |
| } | |
| } | |
| function previewFaceImage() { | |
| const fileInput = document.getElementById('uploadFace'); | |
| if (fileInput.files && fileInput.files[0]) { | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| document.getElementById('facePreview').src = e.target.result; | |
| document.getElementById('facePreview').style.display = 'block'; | |
| }; | |
| reader.readAsDataURL(fileInput.files[0]); | |
| faceImageBlob = fileInput.files[0]; | |
| } | |
| } | |
| function toggleFaceInput() { | |
| const faceOption = document.getElementById('faceOption').value; | |
| document.getElementById('uploadFaceGroup').style.display = faceOption === 'upload' ? 'block' : 'none'; | |
| document.getElementById('cameraFaceGroup').style.display = faceOption === 'camera' ? 'block' : 'none'; | |
| if (faceOption === 'camera') { | |
| startCamera(); | |
| } else { | |
| stopCamera(); | |
| } | |
| } | |
| function startCamera() { | |
| const video = document.getElementById('cameraStream'); | |
| navigator.mediaDevices.getUserMedia({ video: true }) | |
| .then(stream => video.srcObject = stream) | |
| .catch(error => console.error('カメラの起動に失敗しました:', error)); | |
| } | |
| function stopCamera() { | |
| const video = document.getElementById('cameraStream'); | |
| const stream = video.srcObject; | |
| if (stream) { | |
| stream.getTracks().forEach(track => track.stop()); | |
| video.srcObject = null; | |
| } | |
| } | |
| function captureFaceImage() { | |
| const video = document.getElementById('cameraStream'); | |
| const canvas = document.getElementById('cameraCanvas'); | |
| const context = canvas.getContext('2d'); | |
| canvas.width = video.videoWidth; | |
| canvas.height = video.videoHeight; | |
| context.drawImage(video, 0, 0, canvas.width, canvas.height); | |
| canvas.toBlob(blob => faceImageBlob = blob, 'image/jpeg'); | |
| stopCamera(); | |
| } | |
| function processGeneralImage() { | |
| if (!resizedImageBlob1) { | |
| alert("画像を選択してください。"); | |
| return; | |
| } | |
| const processingType = document.getElementById('processingType').value; | |
| const riskLevel = $("#slider1").slider("value"); | |
| showLoadingSpinner(); | |
| const formData = new FormData(); | |
| formData.append('image', resizedImageBlob1, 'resized_image.jpg'); | |
| formData.append('risk_level', riskLevel); | |
| let apiEndpoint; | |
| if (processingType === "opencv") { | |
| apiEndpoint = "/create-mask-and-inpaint-opencv"; | |
| } else if (processingType === "simple_lama") { | |
| apiEndpoint = "/create-mask-and-inpaint-simple-lama"; | |
| } else if (processingType === "stamp") { | |
| apiEndpoint = "/create-mask-and-inpaint-stamp"; | |
| } else if (processingType === "mosaic") { | |
| apiEndpoint = "/create-mask-and-inpaint-mosaic"; | |
| } else if (processingType === "llm-auto") { | |
| apiEndpoint = "/create-mask-and-inpaint-sum-llm-auto"; | |
| formData.append('x1', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('y1', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('x2', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('y2', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| } else if (processingType === "llm") { | |
| apiEndpoint = "/create-mask-and-inpaint-sum-llm-simple"; | |
| formData.append('x1', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('y1', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('x2', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| formData.append('y2', 0.001); // FastAPIで使うデフォルト値と同じ値を設定 | |
| } else { | |
| alert("無効な処理方法が選択されました。"); | |
| hideLoadingSpinner(); | |
| return; | |
| } | |
| processImageRequest(formData, "https://rein0421-aidentify.hf.space" + apiEndpoint); | |
| } | |
| function processFaceImage() { | |
| if (!resizedImageBlob2 || !faceImageBlob) { | |
| alert("処理する画像と顔画像の両方を設定してください。"); | |
| return; | |
| } | |
| const riskLevel = $("#slider2").slider("value"); | |
| showLoadingSpinner(); | |
| const formData = new FormData(); | |
| formData.append('reference_image', faceImageBlob, 'reference_image.jpg'); | |
| formData.append('test_image', resizedImageBlob2, 'test_image.jpg'); | |
| formData.append('risk_level', riskLevel); | |
| processImageRequest(formData, "https://rein0421-aidentify.hf.space/mosaic_faces"); | |
| } | |
| function processImageRequest(formData, url) { | |
| fetch(url, { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error("Network response was not ok"); | |
| return response.blob(); | |
| }) | |
| .then(blob => { | |
| const objectURL = URL.createObjectURL(blob); | |
| document.getElementById('processedImage').src = objectURL; | |
| document.getElementById('downloadLink').href = objectURL; | |
| document.getElementById('result').style.display = "block"; | |
| }) | |
| .catch(error => { | |
| console.error("画像処理に失敗しました。", error); | |
| alert("画像処理に失敗しました。"); | |
| }) | |
| .finally(() => { | |
| hideLoadingSpinner(); | |
| }); | |
| } | |
| function showLoadingSpinner() { | |
| document.getElementById('loadingSpinner').style.display = 'block'; | |
| } | |
| function hideLoadingSpinner() { | |
| document.getElementById('loadingSpinner').style.display = 'none'; | |
| } | |
| // タブ切り替え時の処理 | |
| $('.nav-tabs a').on('shown.bs.tab', function (e) { | |
| // 結果表示をリセット | |
| document.getElementById('result').style.display = 'none'; | |
| document.getElementById('processedImage').src = ''; | |
| document.getElementById('downloadLink').href = '#'; | |
| }); | |
| </script> | |
| <script> | |
| </script> | |
| </body> | |
| </html> |