CoolFace
Apppublic

gbreadman13/sam2-api

sourceHugging Faceapache-2.0updated 10mo agoView on Hugging Face
0likes
web_demo_advanced.html765 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="ru">3<head>4    <meta charset="UTF-8">5    <meta name="viewport" content="width=device-width, initial-scale=1.0">6    <title>SAM2 Advanced Demo - Box + Brush</title>7    <style>8        * {9            margin: 0;10            padding: 0;11            box-sizing: border-box;12        }13        14        body {15            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;16            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);17            min-height: 100vh;18            padding: 20px;19        }20        21        .container {22            max-width: 1600px;23            margin: 0 auto;24        }25        26        h1 {27            color: white;28            text-align: center;29            margin-bottom: 10px;30            font-size: 2.5em;31            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);32        }33        34        .subtitle {35            color: rgba(255,255,255,0.9);36            text-align: center;37            margin-bottom: 30px;38            font-size: 1.1em;39        }40        41        .panel {42            background: white;43            border-radius: 16px;44            padding: 30px;45            box-shadow: 0 20px 60px rgba(0,0,0,0.3);46        }47        48        .upload-section {49            text-align: center;50            padding: 40px;51            border: 3px dashed #667eea;52            border-radius: 12px;53            background: #f8f9ff;54            cursor: pointer;55            transition: all 0.3s;56        }57        58        .upload-section:hover {59            background: #f0f1ff;60            border-color: #764ba2;61        }62        63        input[type="file"] {64            display: none;65        }66        67        .upload-btn {68            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);69            color: white;70            border: none;71            padding: 15px 40px;72            font-size: 1.1em;73            border-radius: 8px;74            cursor: pointer;75            transition: transform 0.2s;76            font-weight: 600;77        }78        79        .upload-btn:hover {80            transform: translateY(-2px);81            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);82        }83        84        .workspace {85            display: none;86            margin-top: 30px;87        }88        89        .workspace.active {90            display: grid;91            grid-template-columns: 1fr 1fr;92            gap: 30px;93        }94        95        .canvas-container {96            position: relative;97            background: #f8f9ff;98            border-radius: 12px;99            padding: 20px;100            box-shadow: inset 0 2px 8px rgba(0,0,0,0.1);101        }102        103        .canvas-title {104            font-size: 1.2em;105            font-weight: 600;106            margin-bottom: 15px;107            color: #333;108        }109        110        .canvas-wrapper {111            position: relative;112            display: inline-block;113        }114        115        canvas {116            display: block;117            max-width: 100%;118            border-radius: 8px;119            box-shadow: 0 4px 12px rgba(0,0,0,0.15);120        }121        122        #sourceCanvas {123            cursor: crosshair;124        }125        126        #drawCanvas {127            position: absolute;128            top: 0;129            left: 0;130            pointer-events: none;131        }132        133        .tools {134            display: flex;135            gap: 10px;136            margin-bottom: 15px;137            flex-wrap: wrap;138            align-items: center;139        }140        141        .tool-btn {142            padding: 10px 20px;143            border: 2px solid #ddd;144            background: white;145            border-radius: 8px;146            cursor: pointer;147            transition: all 0.2s;148            font-weight: 600;149            display: flex;150            align-items: center;151            gap: 8px;152        }153        154        .tool-btn:hover {155            border-color: #667eea;156            background: #f8f9ff;157        }158        159        .tool-btn.active {160            border-color: #667eea;161            background: #667eea;162            color: white;163        }164        165        .tool-btn.box { border-color: #007bff; }166        .tool-btn.box.active { background: #007bff; }167        168        .tool-btn.brush-fg { border-color: #28a745; }169        .tool-btn.brush-fg.active { background: #28a745; }170        171        .tool-btn.brush-bg { border-color: #dc3545; }172        .tool-btn.brush-bg.active { background: #dc3545; }173        174        .brush-size {175            display: flex;176            align-items: center;177            gap: 10px;178            padding: 8px 15px;179            background: #f8f9ff;180            border-radius: 8px;181        }182        183        .brush-size label {184            font-size: 0.9em;185            font-weight: 600;186        }187        188        .brush-size input {189            width: 100px;190        }191        192        .instructions {193            background: #fff3cd;194            border: 2px solid #ffc107;195            border-radius: 8px;196            padding: 15px;197            margin-bottom: 20px;198            font-size: 0.9em;199            line-height: 1.6;200        }201        202        .controls {203            display: flex;204            gap: 15px;205            margin-top: 20px;206            flex-wrap: wrap;207        }208        209        button {210            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);211            color: white;212            border: none;213            padding: 12px 30px;214            font-size: 1em;215            border-radius: 8px;216            cursor: pointer;217            transition: all 0.2s;218            font-weight: 600;219        }220        221        button:hover {222            transform: translateY(-2px);223            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);224        }225        226        button:disabled {227            background: #ccc;228            cursor: not-allowed;229            transform: none;230        }231        232        button.clear {233            background: #dc3545;234        }235        236        button.clear:hover {237            box-shadow: 0 5px 15px rgba(220, 53, 69, 0.4);238        }239        240        .loading {241            display: none;242            text-align: center;243            padding: 30px;244        }245        246        .loading.active {247            display: block;248        }249        250        .spinner {251            border: 4px solid #f3f3f3;252            border-top: 4px solid #667eea;253            border-radius: 50%;254            width: 50px;255            height: 50px;256            animation: spin 1s linear infinite;257            margin: 0 auto 15px;258        }259        260        @keyframes spin {261            0% { transform: rotate(0deg); }262            100% { transform: rotate(360deg); }263        }264        265        .result-info {266            background: #d1ecf1;267            border: 2px solid #bee5eb;268            border-radius: 8px;269            padding: 15px;270            margin-top: 15px;271            font-size: 0.9em;272        }273        274        .result-info h4 {275            margin-bottom: 10px;276            color: #0c5460;277        }278        279        .result-info p {280            margin: 5px 0;281            color: #0c5460;282        }283        284        .error {285            background: #f8d7da;286            border: 2px solid #f5c6cb;287            color: #721c24;288            padding: 15px;289            border-radius: 8px;290            margin-top: 15px;291            display: none;292        }293        294        .error.active {295            display: block;296        }297        298        .badge {299            display: inline-block;300            background: #28a745;301            color: white;302            padding: 4px 12px;303            border-radius: 12px;304            font-size: 0.85em;305            font-weight: 600;306            margin-left: 10px;307        }308        309        .color-indicator {310            width: 20px;311            height: 20px;312            border-radius: 50%;313            display: inline-block;314            border: 2px solid #666;315        }316        317        @media (max-width: 1400px) {318            .workspace.active {319                grid-template-columns: 1fr;320            }321        }322    </style>323</head>324<body>325    <div class="container">326        <h1>🎨 SAM2 Advanced Demo</h1>327        <p class="subtitle">Box выделение ИЛИ рисование кистью (зеленый = объект, красный = фон)</p>328        329        <div class="panel">330            <div class="upload-section" id="uploadSection">331                <h2 style="margin-bottom: 15px;">📸 Загрузи изображение</h2>332                <p style="color: #666; margin-bottom: 20px;">Кликни или перетащи фото сюда</p>333                <button class="upload-btn" onclick="document.getElementById('fileInput').click()">334                    Выбрать файл335                </button>336                <input type="file" id="fileInput" accept="image/*">337            </div>338            339            <div class="workspace" id="workspace">340                <div class="instructions">341                    <strong>📝 Режимы работы:</strong><br>342                    🟦 <strong>Box</strong> - рисуй прямоугольник вокруг объекта<br>343                    🟢 <strong>Brush (FG)</strong> - закрашивай объект зеленым<br>344                    🔴 <strong>Brush (BG)</strong> - закрашивай фон красным для исключения<br><br>345                    💡 <strong>Совет:</strong> Можно комбинировать! Например, нарисуй бокс + закрась проблемные зоны346                </div>347                348                <div>349                    <div class="canvas-container">350                        <div class="canvas-title">351                            Исходное изображение 352                            <span class="badge" id="imageSizeBadge"></span>353                        </div>354                        355                        <div class="tools">356                            <div class="tool-btn box active" id="toolBox" onclick="setTool('box')">357                                📦 Box358                            </div>359                            <div class="tool-btn brush-fg" id="toolBrushFG" onclick="setTool('brush-fg')">360                                🟢 Brush (Объект)361                            </div>362                            <div class="tool-btn brush-bg" id="toolBrushBG" onclick="setTool('brush-bg')">363                                🔴 Brush (Фон)364                            </div>365                            <div class="brush-size">366                                <label>🖌️ Размер:</label>367                                <input type="range" id="brushSize" min="5" max="50" value="15" 368                                       oninput="updateBrushSize(this.value)">369                                <span id="brushSizeLabel">15px</span>370                            </div>371                        </div>372                        373                        <div class="canvas-wrapper">374                            <canvas id="sourceCanvas"></canvas>375                            <canvas id="drawCanvas"></canvas>376                        </div>377                    </div>378                    379                    <div class="controls">380                        <button id="segmentBtn" onclick="segmentImage()">381                            🚀 Сегментировать382                        </button>383                        <button class="clear" onclick="clearDrawing()">384                            🗑️ Очистить рисунок385                        </button>386                        <button class="clear" onclick="resetAll()">387                            🔄 Новое изображение388                        </button>389                    </div>390                </div>391                392                <div>393                    <div class="canvas-container">394                        <div class="canvas-title">Результат (вырезанный объект)</div>395                        <canvas id="resultCanvas"></canvas>396                    </div>397                    398                    <div class="loading" id="loading">399                        <div class="spinner"></div>400                        <p>Обрабатываю изображение...</p>401                    </div>402                    403                    <div class="result-info" id="resultInfo" style="display: none;">404                        <h4>📊 Информация о сегменте:</h4>405                        <p><strong>Площадь:</strong> <span id="resultArea"></span> пикселей</p>406                        <p><strong>BBox:</strong> <span id="resultBBox"></span></p>407                        <p><strong>Confidence:</strong> <span id="resultConfidence"></span></p>408                    </div>409                    410                    <div class="error" id="error"></div>411                </div>412            </div>413        </div>414    </div>415 416    <script>417        const API_URL = 'http://localhost:8000';418        419        let sourceCanvas = document.getElementById('sourceCanvas');420        let drawCanvas = document.getElementById('drawCanvas');421        let resultCanvas = document.getElementById('resultCanvas');422        let sourceCtx = sourceCanvas.getContext('2d');423        let drawCtx = drawCanvas.getContext('2d');424        let resultCtx = resultCanvas.getContext('2d');425        426        let currentImage = null;427        let currentTool = 'box';428        let brushSize = 15;429        let isDrawing = false;430        let startX, startY, endX, endY;431        let boxCoords = null;432        433        // Drag & Drop434        const uploadSection = document.getElementById('uploadSection');435        436        uploadSection.addEventListener('dragover', (e) => {437            e.preventDefault();438            uploadSection.classList.add('dragover');439        });440        441        uploadSection.addEventListener('dragleave', () => {442            uploadSection.classList.remove('dragover');443        });444        445        uploadSection.addEventListener('drop', (e) => {446            e.preventDefault();447            uploadSection.classList.remove('dragover');448            const file = e.dataTransfer.files[0];449            if (file && file.type.startsWith('image/')) {450                loadImage(file);451            }452        });453        454        document.getElementById('fileInput').addEventListener('change', (e) => {455            const file = e.target.files[0];456            if (file) loadImage(file);457        });458        459        function loadImage(file) {460            const reader = new FileReader();461            reader.onload = (e) => {462                const img = new Image();463                img.onload = () => {464                    currentImage = img;465                    setupCanvas(img);466                    document.getElementById('workspace').classList.add('active');467                    document.getElementById('imageSizeBadge').textContent = `${img.width}×${img.height}`;468                };469                img.src = e.target.result;470            };471            reader.readAsDataURL(file);472        }473        474        function setupCanvas(img) {475            const maxWidth = 700;476            const scale = Math.min(1, maxWidth / img.width);477            478            sourceCanvas.width = img.width * scale;479            sourceCanvas.height = img.height * scale;480            drawCanvas.width = sourceCanvas.width;481            drawCanvas.height = sourceCanvas.height;482            resultCanvas.width = sourceCanvas.width;483            resultCanvas.height = sourceCanvas.height;484            485            sourceCtx.drawImage(img, 0, 0, sourceCanvas.width, sourceCanvas.height);486            drawCtx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);487            resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);488            489            boxCoords = null;490            document.getElementById('resultInfo').style.display = 'none';491            document.getElementById('error').classList.remove('active');492        }493        494        function setTool(tool) {495            currentTool = tool;496            document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active'));497            document.getElementById('tool' + tool.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join('')).classList.add('active');498            499            if (tool === 'box') {500                sourceCanvas.style.cursor = 'crosshair';501            } else {502                sourceCanvas.style.cursor = `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="${brushSize*2}" height="${brushSize*2}"><circle cx="${brushSize}" cy="${brushSize}" r="${brushSize}" fill="none" stroke="black" stroke-width="2"/></svg>') ${brushSize} ${brushSize}, crosshair`;503            }504        }505        506        function updateBrushSize(size) {507            brushSize = parseInt(size);508            document.getElementById('brushSizeLabel').textContent = brushSize + 'px';509            if (currentTool !== 'box') {510                setTool(currentTool);511            }512        }513        514        // Mouse events515        sourceCanvas.addEventListener('mousedown', (e) => {516            const rect = sourceCanvas.getBoundingClientRect();517            startX = e.clientX - rect.left;518            startY = e.clientY - rect.top;519            isDrawing = true;520            521            if (currentTool !== 'box') {522                drawBrush(startX, startY);523            }524        });525        526        sourceCanvas.addEventListener('mousemove', (e) => {527            if (!isDrawing) return;528            529            const rect = sourceCanvas.getBoundingClientRect();530            endX = e.clientX - rect.left;531            endY = e.clientY - rect.top;532            533            if (currentTool === 'box') {534                redrawCanvas();535                drawBox(startX, startY, endX, endY);536            } else {537                drawBrush(endX, endY);538                startX = endX;539                startY = endY;540            }541        });542        543        sourceCanvas.addEventListener('mouseup', (e) => {544            if (!isDrawing) return;545            isDrawing = false;546            547            if (currentTool === 'box') {548                const rect = sourceCanvas.getBoundingClientRect();549                endX = e.clientX - rect.left;550                endY = e.clientY - rect.top;551                552                const scaleX = currentImage.width / sourceCanvas.width;553                const scaleY = currentImage.height / sourceCanvas.height;554                555                boxCoords = {556                    x1: Math.min(startX, endX) * scaleX,557                    y1: Math.min(startY, endY) * scaleY,558                    x2: Math.max(startX, endX) * scaleX,559                    y2: Math.max(startY, endY) * scaleY560                };561            }562        });563        564        function drawBrush(x, y) {565            drawCtx.globalAlpha = 0.6;566            drawCtx.lineCap = 'round';567            drawCtx.lineJoin = 'round';568            drawCtx.lineWidth = brushSize * 2;569            570            if (currentTool === 'brush-fg') {571                drawCtx.strokeStyle = '#00ff00';572                drawCtx.fillStyle = '#00ff00';573            } else {574                drawCtx.strokeStyle = '#ff0000';575                drawCtx.fillStyle = '#ff0000';576            }577            578            drawCtx.beginPath();579            drawCtx.arc(x, y, brushSize, 0, Math.PI * 2);580            drawCtx.fill();581        }582        583        function redrawCanvas() {584            drawCtx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);585        }586        587        function drawBox(x1, y1, x2, y2) {588            const width = x2 - x1;589            const height = y2 - y1;590            591            drawCtx.strokeStyle = '#00ff00';592            drawCtx.lineWidth = 3;593            drawCtx.setLineDash([10, 5]);594            drawCtx.strokeRect(x1, y1, width, height);595            596            drawCtx.fillStyle = 'rgba(0, 255, 0, 0.1)';597            drawCtx.fillRect(x1, y1, width, height);598            599            drawCtx.setLineDash([]);600        }601        602        function clearDrawing() {603            boxCoords = null;604            drawCtx.clearRect(0, 0, drawCanvas.width, drawCanvas.height);605            resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);606            document.getElementById('resultInfo').style.display = 'none';607        }608        609        function resetAll() {610            currentImage = null;611            boxCoords = null;612            document.getElementById('workspace').classList.remove('active');613            document.getElementById('fileInput').value = '';614            document.getElementById('resultInfo').style.display = 'none';615            document.getElementById('error').classList.remove('active');616        }617        618        async function segmentImage() {619            // Проверяем есть ли промпты620            const hasDrawing = !isCanvasEmpty(drawCanvas);621            622            if (!boxCoords && !hasDrawing) {623                showError('Нарисуй прямоугольник или закрась области кистью!');624                return;625            }626            627            document.getElementById('loading').classList.add('active');628            document.getElementById('error').classList.remove('active');629            document.getElementById('segmentBtn').disabled = true;630            631            try {632                // Конвертируем изображение633                const canvas = document.createElement('canvas');634                canvas.width = currentImage.width;635                canvas.height = currentImage.height;636                const ctx = canvas.getContext('2d');637                ctx.drawImage(currentImage, 0, 0);638                639                const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.95));640                641                const formData = new FormData();642                formData.append('file', blob, 'image.jpg');643                644                // Строим URL с параметрами645                const params = new URLSearchParams({646                    extract_objects: true,647                    include_masks: false648                });649                650                // Добавляем box если есть651                if (boxCoords) {652                    params.append('box_x1', boxCoords.x1);653                    params.append('box_y1', boxCoords.y1);654                    params.append('box_x2', boxCoords.x2);655                    params.append('box_y2', boxCoords.y2);656                }657                658                // Добавляем маску если есть рисунок659                if (hasDrawing) {660                    const maskData = drawCanvas.toDataURL('image/png');661                    params.append('mask_data', maskData);662                }663                664                const url = `${API_URL}/segment?${params}`;665                console.log('Request URL:', url);666                667                const response = await fetch(url, {668                    method: 'POST',669                    body: formData670                });671                672                if (!response.ok) {673                    throw new Error(`HTTP ${response.status}: ${await response.text()}`);674                }675                676                const result = await response.json();677                console.log('Result:', result);678                679                if (result.success && result.segments.length > 0) {680                    displayResult(result.segments[0]);681                } else {682                    showError('Не удалось найти объект');683                }684                685            } catch (error) {686                console.error('Error:', error);687                showError(`Ошибка: ${error.message}`);688            } finally {689                document.getElementById('loading').classList.remove('active');690                document.getElementById('segmentBtn').disabled = false;691            }692        }693        694        function isCanvasEmpty(canvas) {695            const ctx = canvas.getContext('2d');696            const pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;697            698            for (let i = 3; i < pixelData.length; i += 4) {699                if (pixelData[i] !== 0) return false;700            }701            return true;702        }703        704        function displayResult(segment) {705            if (!segment.extracted_image) {706                showError('Не получен вырезанный объект');707                return;708            }709            710            const img = new Image();711            img.onload = () => {712                resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);713                714                const scale = Math.min(715                    resultCanvas.width / img.width,716                    resultCanvas.height / img.height,717                    1718                );719                720                const w = img.width * scale;721                const h = img.height * scale;722                const x = (resultCanvas.width - w) / 2;723                const y = (resultCanvas.height - h) / 2;724                725                drawCheckerboard(resultCtx, x, y, w, h);726                resultCtx.drawImage(img, x, y, w, h);727                728                document.getElementById('resultArea').textContent = segment.area.toLocaleString();729                document.getElementById('resultBBox').textContent = 730                    `${segment.bbox.width}×${segment.bbox.height} px`;731                document.getElementById('resultConfidence').textContent = 732                    `${(segment.confidence * 100).toFixed(1)}%`;733                document.getElementById('resultInfo').style.display = 'block';734            };735            img.src = segment.extracted_image;736        }737        738        function drawCheckerboard(ctx, x, y, w, h) {739            const size = 10;740            ctx.fillStyle = '#f0f0f0';741            ctx.fillRect(x, y, w, h);742            743            ctx.fillStyle = '#ddd';744            for (let i = 0; i < w; i += size) {745                for (let j = 0; j < h; j += size) {746                    if ((i / size + j / size) % 2 === 0) {747                        ctx.fillRect(x + i, y + j, size, size);748                    }749                }750            }751        }752        753        function showError(message) {754            const errorEl = document.getElementById('error');755            errorEl.textContent = '❌ ' + message;756            errorEl.classList.add('active');757        }758        759        // Инициализация760        setTool('box');761    </script>762</body>763</html>764 765