import cv2 import numpy as np def remove_sticker(image_path, mask_path, output_path): # قراءة الصورة والقناع (اللاصقة) img = cv2.imread(image_path) mask = cv2.imread(mask_path, 0) # قراءة كصورة رمادية # تطبيق خوارزمية الاستعادة result = cv2.inpaint(img, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA) # حفظ النتيجة cv2.imwrite(output_path, result) - Initial Deployment
ba6a049
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sticker Remover Tool</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .dropzone { | |
| border: 2px dashed #ccc; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #4f46e5; | |
| background-color: #eef2ff; | |
| } | |
| .canvas-container { | |
| position: relative; | |
| margin: 0 auto; | |
| } | |
| .processing-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.7); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| z-index: 10; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 4px solid rgba(255,255,255,0.3); | |
| border-radius: 50%; | |
| border-top-color: #fff; | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .brush-size-control { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: #d1d5db; | |
| } | |
| .brush-size-control::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #4f46e5; | |
| cursor: pointer; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-4xl font-bold text-indigo-600 mb-2">Sticker Remover</h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto"> | |
| Remove unwanted stickers, watermarks, or objects from your images with our AI-powered tool. | |
| </p> | |
| </header> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden max-w-4xl mx-auto"> | |
| <div class="md:flex"> | |
| <!-- Left Panel - Controls --> | |
| <div class="md:w-1/3 p-6 bg-gray-50 border-r border-gray-200"> | |
| <div class="space-y-6"> | |
| <div> | |
| <h2 class="text-lg font-medium text-gray-900 mb-3">Upload Image</h2> | |
| <div id="image-dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> | |
| <div class="flex flex-col items-center justify-center"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-indigo-400 mb-3"></i> | |
| <p class="text-gray-500 mb-1">Drag & drop your image here</p> | |
| <p class="text-sm text-gray-400">or click to browse</p> | |
| <input type="file" id="image-input" accept="image/*" class="hidden"> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="mask-controls" class="hidden"> | |
| <h2 class="text-lg font-medium text-gray-900 mb-3">Mark the Sticker</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1">Brush Size</label> | |
| <input type="range" min="1" max="50" value="15" class="brush-size-control" id="brush-size"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Small</span> | |
| <span>Large</span> | |
| </div> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="draw-btn" class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition flex items-center justify-center"> | |
| <i class="fas fa-paint-brush mr-2"></i> Draw | |
| </button> | |
| <button id="erase-btn" class="flex-1 bg-gray-200 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-300 transition flex items-center justify-center"> | |
| <i class="fas fa-eraser mr-2"></i> Erase | |
| </button> | |
| </div> | |
| <div class="flex space-x-3"> | |
| <button id="clear-btn" class="flex-1 bg-gray-200 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-300 transition"> | |
| Clear Mask | |
| </button> | |
| <button id="process-btn" class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition"> | |
| Remove Sticker | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Canvas --> | |
| <div class="md:w-2/3 p-6"> | |
| <div class="canvas-container"> | |
| <div id="canvas-wrapper" class="relative"> | |
| <canvas id="image-canvas" class="max-w-full border border-gray-200 rounded-lg hidden"></canvas> | |
| <canvas id="mask-canvas" class="absolute top-0 left-0 max-w-full border border-gray-200 rounded-lg hidden" style="opacity: 0.5;"></canvas> | |
| <div id="dropzone-placeholder" class="flex items-center justify-center h-64 bg-gray-100 rounded-lg"> | |
| <div class="text-center p-6"> | |
| <i class="fas fa-image text-4xl text-gray-300 mb-3"></i> | |
| <h3 class="text-lg font-medium text-gray-500">No image selected</h3> | |
| <p class="text-gray-400 mt-1">Upload an image to get started</p> | |
| </div> | |
| </div> | |
| <div id="processing-overlay" class="processing-overlay hidden"> | |
| <div class="spinner mb-4"></div> | |
| <p class="text-xl font-medium mb-2">Processing Image</p> | |
| <p class="text-sm text-gray-300">Removing sticker and reconstructing image...</p> | |
| </div> | |
| </div> | |
| <div id="result-controls" class="mt-4 hidden"> | |
| <div class="flex justify-between"> | |
| <button id="download-btn" class="bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700 transition flex items-center"> | |
| <i class="fas fa-download mr-2"></i> Download Result | |
| </button> | |
| <button id="reset-btn" class="bg-gray-200 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-300 transition"> | |
| Start Over | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-12 bg-white rounded-xl shadow-lg overflow-hidden max-w-4xl mx-auto p-6"> | |
| <h2 class="text-xl font-bold text-gray-800 mb-4">How It Works</h2> | |
| <div class="grid md:grid-cols-3 gap-6"> | |
| <div class="text-center"> | |
| <div class="bg-indigo-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-upload text-indigo-600 text-2xl"></i> | |
| </div> | |
| <h3 class="font-medium text-gray-800 mb-2">1. Upload Image</h3> | |
| <p class="text-gray-600 text-sm">Select an image containing the sticker or object you want to remove.</p> | |
| </div> | |
| <div class="text-center"> | |
| <div class="bg-indigo-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-paint-brush text-indigo-600 text-2xl"></i> | |
| </div> | |
| <h3 class="font-medium text-gray-800 mb-2">2. Mark the Area</h3> | |
| <p class="text-gray-600 text-sm">Use the brush tool to mark the exact area you want to remove.</p> | |
| </div> | |
| <div class="text-center"> | |
| <div class="bg-indigo-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i class="fas fa-magic text-indigo-600 text-2xl"></i> | |
| </div> | |
| <h3 class="font-medium text-gray-800 mb-2">3. Download Result</h3> | |
| <p class="text-gray-600 text-sm">Our AI will seamlessly remove the marked area and reconstruct the image.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const imageDropzone = document.getElementById('image-dropzone'); | |
| const imageInput = document.getElementById('image-input'); | |
| const dropzonePlaceholder = document.getElementById('dropzone-placeholder'); | |
| const imageCanvas = document.getElementById('image-canvas'); | |
| const maskCanvas = document.getElementById('mask-canvas'); | |
| const maskControls = document.getElementById('mask-controls'); | |
| const resultControls = document.getElementById('result-controls'); | |
| const processingOverlay = document.getElementById('processing-overlay'); | |
| const drawBtn = document.getElementById('draw-btn'); | |
| const eraseBtn = document.getElementById('erase-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| const processBtn = document.getElementById('process-btn'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const resetBtn = document.getElementById('reset-btn'); | |
| const brushSizeControl = document.getElementById('brush-size'); | |
| // Canvas contexts | |
| const imageCtx = imageCanvas.getContext('2d'); | |
| const maskCtx = maskCanvas.getContext('2d'); | |
| // State variables | |
| let isDrawing = false; | |
| let isErasing = false; | |
| let brushSize = 15; | |
| let originalImage = null; | |
| // Event Listeners | |
| imageDropzone.addEventListener('click', () => imageInput.click()); | |
| imageInput.addEventListener('change', handleImageUpload); | |
| // Drag and drop events | |
| imageDropzone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| imageDropzone.classList.add('active'); | |
| }); | |
| imageDropzone.addEventListener('dragleave', () => { | |
| imageDropzone.classList.remove('active'); | |
| }); | |
| imageDropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| imageDropzone.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| imageInput.files = e.dataTransfer.files; | |
| handleImageUpload({ target: imageInput }); | |
| } | |
| }); | |
| // Brush size control | |
| brushSizeControl.addEventListener('input', () => { | |
| brushSize = parseInt(brushSizeControl.value); | |
| }); | |
| // Tool buttons | |
| drawBtn.addEventListener('click', () => { | |
| isErasing = false; | |
| drawBtn.classList.remove('bg-gray-200', 'text-gray-700'); | |
| drawBtn.classList.add('bg-indigo-600', 'text-white'); | |
| eraseBtn.classList.remove('bg-indigo-600', 'text-white'); | |
| eraseBtn.classList.add('bg-gray-200', 'text-gray-700'); | |
| }); | |
| eraseBtn.addEventListener('click', () => { | |
| isErasing = true; | |
| eraseBtn.classList.remove('bg-gray-200', 'text-gray-700'); | |
| eraseBtn.classList.add('bg-indigo-600', 'text-white'); | |
| drawBtn.classList.remove('bg-indigo-600', 'text-white'); | |
| drawBtn.classList.add('bg-gray-200', 'text-gray-700'); | |
| }); | |
| clearBtn.addEventListener('click', () => { | |
| maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); | |
| }); | |
| processBtn.addEventListener('click', processImage); | |
| downloadBtn.addEventListener('click', downloadResult); | |
| resetBtn.addEventListener('click', resetTool); | |
| // Canvas drawing events | |
| maskCanvas.addEventListener('mousedown', startDrawing); | |
| maskCanvas.addEventListener('mousemove', draw); | |
| maskCanvas.addEventListener('mouseup', stopDrawing); | |
| maskCanvas.addEventListener('mouseout', stopDrawing); | |
| // Touch events for mobile | |
| maskCanvas.addEventListener('touchstart', handleTouchStart); | |
| maskCanvas.addEventListener('touchmove', handleTouchMove); | |
| maskCanvas.addEventListener('touchend', stopDrawing); | |
| // Functions | |
| function handleImageUpload(e) { | |
| const file = e.target.files[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| originalImage = new Image(); | |
| originalImage.onload = function() { | |
| setupCanvases(originalImage); | |
| dropzonePlaceholder.classList.add('hidden'); | |
| imageCanvas.classList.remove('hidden'); | |
| maskCanvas.classList.remove('hidden'); | |
| maskControls.classList.remove('hidden'); | |
| }; | |
| originalImage.src = event.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function setupCanvases(image) { | |
| const maxWidth = 800; | |
| const maxHeight = 600; | |
| let width = image.width; | |
| let height = image.height; | |
| // Scale down if necessary | |
| if (width > maxWidth || height > maxHeight) { | |
| const ratio = Math.min(maxWidth / width, maxHeight / height); | |
| width = width * ratio; | |
| height = height * ratio; | |
| } | |
| // Set canvas dimensions | |
| imageCanvas.width = width; | |
| imageCanvas.height = height; | |
| maskCanvas.width = width; | |
| maskCanvas.height = height; | |
| // Draw the image | |
| imageCtx.drawImage(image, 0, 0, width, height); | |
| // Initialize mask canvas with transparent | |
| maskCtx.clearRect(0, 0, width, height); | |
| } | |
| function startDrawing(e) { | |
| isDrawing = true; | |
| draw(e); | |
| } | |
| function draw(e) { | |
| if (!isDrawing) return; | |
| const rect = maskCanvas.getBoundingClientRect(); | |
| const x = (e.clientX || e.touches[0].clientX) - rect.left; | |
| const y = (e.clientY || e.touches[0].clientY) - rect.top; | |
| maskCtx.globalCompositeOperation = 'source-over'; | |
| maskCtx.fillStyle = isErasing ? 'rgba(0, 0, 0, 0)' : 'rgba(255, 0, 0, 0.5)'; | |
| maskCtx.beginPath(); | |
| maskCtx.arc(x, y, brushSize, 0, Math.PI * 2); | |
| maskCtx.fill(); | |
| } | |
| function handleTouchStart(e) { | |
| e.preventDefault(); | |
| if (e.touches.length === 1) { | |
| const touch = e.touches[0]; | |
| const mouseEvent = new MouseEvent('mousedown', { | |
| clientX: touch.clientX, | |
| clientY: touch.clientY | |
| }); | |
| startDrawing(mouseEvent); | |
| } | |
| } | |
| function handleTouchMove(e) { | |
| e.preventDefault(); | |
| if (e.touches.length === 1) { | |
| const touch = e.touches[0]; | |
| const mouseEvent = new MouseEvent('mousemove', { | |
| clientX: touch.clientX, | |
| clientY: touch.clientY | |
| }); | |
| draw(mouseEvent); | |
| } | |
| } | |
| function stopDrawing() { | |
| isDrawing = false; | |
| } | |
| function processImage() { | |
| processingOverlay.classList.remove('hidden'); | |
| // Simulate processing delay | |
| setTimeout(() => { | |
| processingOverlay.classList.add('hidden'); | |
| resultControls.classList.remove('hidden'); | |
| // In a real app, this would send the image data to a backend service | |
| // that runs the actual OpenCV code | |
| alert("In a real application, this would send the image to a backend service that uses OpenCV to remove the sticker. The demo simulates this process."); | |
| }, 2000); | |
| } | |
| function downloadResult() { | |
| // Create a temporary canvas with the result | |
| const tempCanvas = document.createElement('canvas'); | |
| tempCanvas.width = imageCanvas.width; | |
| tempCanvas.height = imageCanvas.height; | |
| const tempCtx = tempCanvas.getContext('2d'); | |
| // Draw the original image | |
| tempCtx.drawImage(imageCanvas, 0, 0); | |
| // In a real app, this would apply the actual inpainting algorithm | |
| // Here we just simulate it by blurring the masked area | |
| const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height).data; | |
| tempCtx.save(); | |
| tempCtx.beginPath(); | |
| // Find all red pixels in the mask (our marked area) | |
| for (let y = 0; y < maskCanvas.height; y++) { | |
| for (let x = 0; x < maskCanvas.width; x++) { | |
| const i = (y * maskCanvas.width + x) * 4; | |
| if (maskData[i] > 200 && maskData[i+1] < 50 && maskData[i+2] < 50) { | |
| tempCtx.rect(x, y, 1, 1); | |
| } | |
| } | |
| } | |
| tempCtx.clip(); | |
| // Apply a blur effect to simulate inpainting | |
| tempCtx.filter = 'blur(8px)'; | |
| tempCtx.drawImage(imageCanvas, 0, 0); | |
| tempCtx.filter = 'none'; | |
| tempCtx.restore(); | |
| // Download the result | |
| const link = document.createElement('a'); | |
| link.download = 'sticker-removed.png'; | |
| link.href = tempCanvas.toDataURL('image/png'); | |
| link.click(); | |
| } | |
| function resetTool() { | |
| // Reset all elements to initial state | |
| imageCanvas.classList.add('hidden'); | |
| maskCanvas.classList.add('hidden'); | |
| dropzonePlaceholder.classList.remove('hidden'); | |
| maskControls.classList.add('hidden'); | |
| resultControls.classList.add('hidden'); | |
| // Clear canvases | |
| imageCtx.clearRect(0, 0, imageCanvas.width, imageCanvas.height); | |
| maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); | |
| // Reset input | |
| imageInput.value = ''; | |
| originalImage = null; | |
| // Reset tools | |
| isErasing = false; | |
| drawBtn.classList.remove('bg-gray-200', 'text-gray-700'); | |
| drawBtn.classList.add('bg-indigo-600', 'text-white'); | |
| eraseBtn.classList.remove('bg-indigo-600', 'text-white'); | |
| eraseBtn.classList.add('bg-gray-200', 'text-gray-700'); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=6ee5ali/face2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |