AIdentify / templates /index.html
syurein
indexの修正
7f525ef
raw
history blame
24.3 kB
<!DOCTYPE html>
<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">&times;</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>