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
b5bd1c6
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 #cbd5e0; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #4f46e5; | |
| background-color: #f0f4ff; | |
| } | |
| .image-preview { | |
| max-height: 400px; | |
| object-fit: contain; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| canvas { | |
| max-width: 100%; | |
| height: auto; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-10"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">Sticker Remover</h1> | |
| <p class="text-gray-600 max-w-2xl mx-auto">Upload your image and mask to seamlessly remove unwanted stickers using advanced inpainting technology</p> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Upload Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Upload Files</h2> | |
| <!-- Original Image Upload --> | |
| <div class="mb-6"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Original Image</label> | |
| <div id="original-dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> | |
| <i class="fas fa-image text-4xl text-indigo-400 mb-3"></i> | |
| <p class="text-gray-500 mb-2">Drag & drop your image here or click to browse</p> | |
| <p class="text-xs text-gray-400">Supports JPG, PNG (Max 5MB)</p> | |
| <input type="file" id="original-input" class="hidden" accept="image/*"> | |
| </div> | |
| <div id="original-preview" class="mt-4 hidden"> | |
| <p class="text-sm text-gray-600 mb-1">Selected file:</p> | |
| <div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2"> | |
| <span id="original-filename" class="text-sm font-medium"></span> | |
| <button id="original-clear" class="text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mask Image Upload --> | |
| <div class="mb-6"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Sticker Mask</label> | |
| <div id="mask-dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer"> | |
| <i class="fas fa-mask text-4xl text-indigo-400 mb-3"></i> | |
| <p class="text-gray-500 mb-2">Drag & drop your mask here or click to browse</p> | |
| <p class="text-xs text-gray-400">Black & white image showing sticker area</p> | |
| <input type="file" id="mask-input" class="hidden" accept="image/*"> | |
| </div> | |
| <div id="mask-preview" class="mt-4 hidden"> | |
| <p class="text-sm text-gray-600 mb-1">Selected file:</p> | |
| <div class="flex items-center justify-between bg-gray-50 rounded px-3 py-2"> | |
| <span id="mask-filename" class="text-sm font-medium"></span> | |
| <button id="mask-clear" class="text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center mt-6"> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="demo-checkbox" class="rounded text-indigo-600"> | |
| <span class="ml-2 text-sm text-gray-600">Use demo images</span> | |
| </label> | |
| </div> | |
| <button id="process-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| Process Image | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Results Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">Results</h2> | |
| <div class="flex justify-between mb-4"> | |
| <button id="original-tab" class="tab-btn active px-4 py-2 text-indigo-600 border-b-2 border-indigo-600 font-medium">Original</button> | |
| <button id="mask-tab" class="tab-btn px-4 py-2 text-gray-500 font-medium">Mask</button> | |
| <button id="result-tab" class="tab-btn px-4 py-2 text-gray-500 font-medium">Result</button> | |
| </div> | |
| <div class="relative"> | |
| <!-- Loading overlay --> | |
| <div id="loading-overlay" class="absolute inset-0 bg-white bg-opacity-80 flex flex-col items-center justify-center hidden z-10 rounded-lg"> | |
| <div class="w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mb-4"></div> | |
| <p class="text-gray-700">Processing your image...</p> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5 mt-4 max-w-md"> | |
| <div id="progress-bar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <!-- Image display area --> | |
| <div id="image-display" class="flex items-center justify-center bg-gray-100 rounded-lg p-4 min-h-64"> | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Upload images to see preview</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-between"> | |
| <button id="download-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| <i class="fas fa-download mr-2"></i> Download | |
| </button> | |
| <div class="flex space-x-2"> | |
| <button id="reset-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg font-medium"> | |
| <i class="fas fa-redo mr-2"></i> Reset | |
| </button> | |
| <button id="help-btn" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-800 px-4 py-2 rounded-lg font-medium"> | |
| <i class="fas fa-question-circle mr-2"></i> Help | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- How It Works Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mt-8"> | |
| <h2 class="text-xl font-semibold mb-4 text-gray-800">How It Works</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-indigo-50 p-4 rounded-lg"> | |
| <div class="bg-indigo-100 w-12 h-12 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-upload text-indigo-600"></i> | |
| </div> | |
| <h3 class="font-medium mb-2">1. Upload Images</h3> | |
| <p class="text-sm text-gray-600">Upload your original image and a black & white mask where white areas represent the sticker to be removed.</p> | |
| </div> | |
| <div class="bg-indigo-50 p-4 rounded-lg"> | |
| <div class="bg-indigo-100 w-12 h-12 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-cogs text-indigo-600"></i> | |
| </div> | |
| <h3 class="font-medium mb-2">2. Process Image</h3> | |
| <p class="text-sm text-gray-600">Our algorithm analyzes the surrounding pixels to intelligently fill in the masked area.</p> | |
| </div> | |
| <div class="bg-indigo-50 p-4 rounded-lg"> | |
| <div class="bg-indigo-100 w-12 h-12 rounded-full flex items-center justify-center mb-3"> | |
| <i class="fas fa-download text-indigo-600"></i> | |
| </div> | |
| <h3 class="font-medium mb-2">3. Download Result</h3> | |
| <p class="text-sm text-gray-600">Get your clean image without the sticker and download it in high quality.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Help Modal --> | |
| <div id="help-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
| <div class="bg-white rounded-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto"> | |
| <div class="p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-bold">Help & Instructions</h3> | |
| <button id="close-help" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-4"> | |
| <div> | |
| <h4 class="font-medium text-indigo-600 mb-2">Creating a Mask</h4> | |
| <p class="text-gray-600 text-sm">To remove a sticker, you need to provide a mask image where:</p> | |
| <ul class="list-disc pl-5 mt-2 text-sm text-gray-600 space-y-1"> | |
| <li>White areas represent the sticker to be removed</li> | |
| <li>Black areas represent the parts to keep</li> | |
| <li>Use any image editor to create this mask</li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-indigo-600 mb-2">Best Practices</h4> | |
| <ul class="list-disc pl-5 text-sm text-gray-600 space-y-1"> | |
| <li>For best results, the mask should exactly cover the sticker area</li> | |
| <li>Make sure the mask and original image have the same dimensions</li> | |
| <li>Complex backgrounds may require more precise masks</li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="font-medium text-indigo-600 mb-2">Troubleshooting</h4> | |
| <ul class="list-disc pl-5 text-sm text-gray-600 space-y-1"> | |
| <li>If the result looks distorted, try making your mask slightly larger</li> | |
| <li>For large stickers, processing may take longer</li> | |
| <li>Refresh the page if the tool stops responding</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="mt-6 pt-4 border-t border-gray-200"> | |
| <h4 class="font-medium text-indigo-600 mb-2">Example</h4> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div> | |
| <p class="text-sm text-gray-600 mb-1">Original Image</p> | |
| <img src="https://via.placeholder.com/300x200?text=Photo+with+sticker" alt="Example original" class="rounded border border-gray-200"> | |
| </div> | |
| <div> | |
| <p class="text-sm text-gray-600 mb-1">Mask Image</p> | |
| <img src="https://via.placeholder.com/300x200/000000/FFFFFF?text=Black+%26+white+mask" alt="Example mask" class="rounded border border-gray-200"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM Elements | |
| const originalDropzone = document.getElementById('original-dropzone'); | |
| const originalInput = document.getElementById('original-input'); | |
| const originalPreview = document.getElementById('original-preview'); | |
| const originalFilename = document.getElementById('original-filename'); | |
| const originalClear = document.getElementById('original-clear'); | |
| const maskDropzone = document.getElementById('mask-dropzone'); | |
| const maskInput = document.getElementById('mask-input'); | |
| const maskPreview = document.getElementById('mask-preview'); | |
| const maskFilename = document.getElementById('mask-filename'); | |
| const maskClear = document.getElementById('mask-clear'); | |
| const processBtn = document.getElementById('process-btn'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const resetBtn = document.getElementById('reset-btn'); | |
| const helpBtn = document.getElementById('help-btn'); | |
| const demoCheckbox = document.getElementById('demo-checkbox'); | |
| const originalTab = document.getElementById('original-tab'); | |
| const maskTab = document.getElementById('mask-tab'); | |
| const resultTab = document.getElementById('result-tab'); | |
| const imageDisplay = document.getElementById('image-display'); | |
| const loadingOverlay = document.getElementById('loading-overlay'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const helpModal = document.getElementById('help-modal'); | |
| const closeHelp = document.getElementById('close-help'); | |
| // State | |
| let originalImage = null; | |
| let maskImage = null; | |
| let resultImage = null; | |
| let currentTab = 'original'; | |
| // Event Listeners | |
| originalDropzone.addEventListener('click', () => originalInput.click()); | |
| originalDropzone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| originalDropzone.classList.add('active'); | |
| }); | |
| originalDropzone.addEventListener('dragleave', () => { | |
| originalDropzone.classList.remove('active'); | |
| }); | |
| originalDropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| originalDropzone.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| originalInput.files = e.dataTransfer.files; | |
| handleOriginalUpload(); | |
| } | |
| }); | |
| originalInput.addEventListener('change', handleOriginalUpload); | |
| originalClear.addEventListener('click', clearOriginal); | |
| maskDropzone.addEventListener('click', () => maskInput.click()); | |
| maskDropzone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| maskDropzone.classList.add('active'); | |
| }); | |
| maskDropzone.addEventListener('dragleave', () => { | |
| maskDropzone.classList.remove('active'); | |
| }); | |
| maskDropzone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| maskDropzone.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| maskInput.files = e.dataTransfer.files; | |
| handleMaskUpload(); | |
| } | |
| }); | |
| maskInput.addEventListener('change', handleMaskUpload); | |
| maskClear.addEventListener('click', clearMask); | |
| processBtn.addEventListener('click', processImage); | |
| downloadBtn.addEventListener('click', downloadResult); | |
| resetBtn.addEventListener('click', resetAll); | |
| helpBtn.addEventListener('click', () => helpModal.classList.remove('hidden')); | |
| closeHelp.addEventListener('click', () => helpModal.classList.add('hidden')); | |
| demoCheckbox.addEventListener('change', toggleDemoImages); | |
| originalTab.addEventListener('click', () => switchTab('original')); | |
| maskTab.addEventListener('click', () => switchTab('mask')); | |
| resultTab.addEventListener('click', () => switchTab('result')); | |
| // Functions | |
| function handleOriginalUpload() { | |
| const file = originalInput.files[0]; | |
| if (!file) return; | |
| if (!file.type.match('image.*')) { | |
| alert('Please select an image file'); | |
| return; | |
| } | |
| originalFilename.textContent = file.name; | |
| originalPreview.classList.remove('hidden'); | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| originalImage = e.target.result; | |
| displayImage(originalImage, 'original'); | |
| checkProcessButton(); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function handleMaskUpload() { | |
| const file = maskInput.files[0]; | |
| if (!file) return; | |
| if (!file.type.match('image.*')) { | |
| alert('Please select an image file'); | |
| return; | |
| } | |
| maskFilename.textContent = file.name; | |
| maskPreview.classList.remove('hidden'); | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| maskImage = e.target.result; | |
| displayImage(maskImage, 'mask'); | |
| checkProcessButton(); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| function clearOriginal() { | |
| originalInput.value = ''; | |
| originalPreview.classList.add('hidden'); | |
| originalImage = null; | |
| checkProcessButton(); | |
| if (currentTab === 'original') { | |
| imageDisplay.innerHTML = ` | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Upload images to see preview</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| function clearMask() { | |
| maskInput.value = ''; | |
| maskPreview.classList.add('hidden'); | |
| maskImage = null; | |
| checkProcessButton(); | |
| if (currentTab === 'mask') { | |
| imageDisplay.innerHTML = ` | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Upload images to see preview</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| function checkProcessButton() { | |
| processBtn.disabled = !(originalImage && maskImage); | |
| } | |
| function displayImage(src, type) { | |
| if (currentTab === type) { | |
| const img = document.createElement('img'); | |
| img.src = src; | |
| img.alt = type; | |
| img.className = 'image-preview max-w-full h-auto rounded'; | |
| imageDisplay.innerHTML = ''; | |
| imageDisplay.appendChild(img); | |
| } | |
| } | |
| function switchTab(tab) { | |
| currentTab = tab; | |
| // Update tab buttons | |
| originalTab.classList.remove('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| originalTab.classList.add('text-gray-500'); | |
| maskTab.classList.remove('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| maskTab.classList.add('text-gray-500'); | |
| resultTab.classList.remove('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| resultTab.classList.add('text-gray-500'); | |
| if (tab === 'original') { | |
| originalTab.classList.add('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| originalTab.classList.remove('text-gray-500'); | |
| if (originalImage) { | |
| displayImage(originalImage, 'original'); | |
| } else { | |
| imageDisplay.innerHTML = ` | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Upload original image to see preview</p> | |
| </div> | |
| `; | |
| } | |
| } else if (tab === 'mask') { | |
| maskTab.classList.add('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| maskTab.classList.remove('text-gray-500'); | |
| if (maskImage) { | |
| displayImage(maskImage, 'mask'); | |
| } else { | |
| imageDisplay.innerHTML = ` | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Upload mask image to see preview</p> | |
| </div> | |
| `; | |
| } | |
| } else if (tab === 'result') { | |
| resultTab.classList.add('text-indigo-600', 'border-b-2', 'border-indigo-600'); | |
| resultTab.classList.remove('text-gray-500'); | |
| if (resultImage) { | |
| displayImage(resultImage, 'result'); | |
| } else { | |
| imageDisplay.innerHTML = ` | |
| <div class="text-center text-gray-400"> | |
| <i class="fas fa-image fa-3x mb-3"></i> | |
| <p>Process the image to see results</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| } | |
| function processImage() { | |
| if (!originalImage || !maskImage) return; | |
| loadingOverlay.classList.remove('hidden'); | |
| processBtn.disabled = true; | |
| // Simulate processing with progress | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 5; | |
| progressBar.style.width = `${progress}%`; | |
| if (progress >= 100) { | |
| clearInterval(interval); | |
| setTimeout(() => { | |
| // In a real app, this would use OpenCV.js to process the image | |
| // For this demo, we'll just simulate the result | |
| loadingOverlay.classList.add('hidden'); | |
| processBtn.disabled = false; | |
| // Create a canvas to "simulate" processing | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Load original image | |
| const img = new Image(); | |
| img.onload = function() { | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Simulate inpainting by drawing a semi-transparent rectangle | |
| // In a real app, this would be replaced with OpenCV.inpaint() | |
| ctx.globalCompositeOperation = 'destination-out'; | |
| ctx.globalAlpha = 0.5; | |
| ctx.fillRect(50, 50, 100, 100); | |
| ctx.globalAlpha = 1.0; | |
| ctx.globalCompositeOperation = 'source-over'; | |
| resultImage = canvas.toDataURL('image/jpeg'); | |
| displayImage(resultImage, 'result'); | |
| downloadBtn.disabled = false; | |
| switchTab('result'); | |
| }; | |
| img.src = originalImage; | |
| }, 500); | |
| } | |
| }, 100); | |
| } | |
| function downloadResult() { | |
| if (!resultImage) return; | |
| const link = document.createElement('a'); | |
| link.href = resultImage; | |
| link.download = 'sticker-removed.jpg'; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| function resetAll() { | |
| clearOriginal(); | |
| clearMask(); | |
| resultImage = null; | |
| downloadBtn.disabled = true; | |
| demoCheckbox.checked = false; | |
| switchTab('original'); | |
| } | |
| function toggleDemoImages() { | |
| if (demoCheckbox.checked) { | |
| // Use demo images | |
| originalImage = 'https://via.placeholder.com/600x400?text=Photo+with+sticker'; | |
| maskImage = 'https://via.placeholder.com/600x400/000000/FFFFFF?text=Black+%26+white+mask'; | |
| originalFilename.textContent = 'demo-original.jpg'; | |
| originalPreview.classList.remove('hidden'); | |
| maskFilename.textContent = 'demo-mask.png'; | |
| maskPreview.classList.remove('hidden'); | |
| displayImage(originalImage, 'original'); | |
| checkProcessButton(); | |
| } else { | |
| // Clear demo images | |
| if (originalFilename.textContent === 'demo-original.jpg') { | |
| clearOriginal(); | |
| } | |
| if (maskFilename.textContent === 'demo-mask.png') { | |
| clearMask(); | |
| } | |
| } | |
| } | |
| </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/facee1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |