Spaces:
Paused
Paused
Delete watermark-remover-free
Browse files- watermark-remover-free/.DS_Store +0 -0
- watermark-remover-free/Dockerfile +0 -14
- watermark-remover-free/README.md +0 -30
- watermark-remover-free/requirements.txt +0 -8
- watermark-remover-free/server.py +0 -121
- watermark-remover-free/static/style.css +0 -8
- watermark-remover-free/templates/index.html +0 -61
watermark-remover-free/.DS_Store
DELETED
|
Binary file (6.15 kB)
|
|
|
watermark-remover-free/Dockerfile
DELETED
|
@@ -1,14 +0,0 @@
|
|
| 1 |
-
FROM python:3.11-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
COPY requirements.txt ./
|
| 5 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 6 |
-
|
| 7 |
-
COPY server.py ./
|
| 8 |
-
COPY templates ./templates
|
| 9 |
-
COPY static ./static
|
| 10 |
-
|
| 11 |
-
ENV PORT=7860
|
| 12 |
-
EXPOSE 7860
|
| 13 |
-
|
| 14 |
-
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watermark-remover-free/README.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
| 1 |
-
# Watermark Remover (Free Deploy)
|
| 2 |
-
|
| 3 |
-
This is a free, self-contained web + API for watermark removal using OpenCV inpainting.
|
| 4 |
-
You can deploy **for free** on Hugging Face Spaces (Docker) or run locally.
|
| 5 |
-
|
| 6 |
-
## Quick Deploy: Hugging Face Spaces (Docker)
|
| 7 |
-
1. Create a new Space -> **Docker**
|
| 8 |
-
2. Upload all files from this zip (`server.py`, `requirements.txt`, `Dockerfile`, `templates/`, `static/`)
|
| 9 |
-
3. Wait for the build to finish. The app will be available at `https://<user>-<space>.hf.space`
|
| 10 |
-
4. API endpoint: `POST /api/remove-watermark` (multipart/form-data)
|
| 11 |
-
|
| 12 |
-
### API (form-data fields)
|
| 13 |
-
- `image`: file (required)
|
| 14 |
-
- `mask`: file (optional; white=remove, black=keep)
|
| 15 |
-
- `engine`: `opencv` (default) or `lama` (requires LaMa server URL)
|
| 16 |
-
- `method`: `telea` (default) or `ns` (OpenCV only)
|
| 17 |
-
- `radius`: int (OpenCV only; default 3)
|
| 18 |
-
- `auto_mask`: 1 or 0 (default 1)
|
| 19 |
-
|
| 20 |
-
> Note: LaMa backend is optional and requires a separate running service. For free CPU Spaces, use `opencv`.
|
| 21 |
-
|
| 22 |
-
## Local Run
|
| 23 |
-
```bash
|
| 24 |
-
pip install -r requirements.txt
|
| 25 |
-
uvicorn server:app --port 7860
|
| 26 |
-
# open http://localhost:7860
|
| 27 |
-
```
|
| 28 |
-
|
| 29 |
-
## Legal
|
| 30 |
-
Use only on content you have rights to edit.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watermark-remover-free/requirements.txt
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
fastapi==0.112.2
|
| 2 |
-
uvicorn[standard]==0.30.6
|
| 3 |
-
python-multipart==0.0.9
|
| 4 |
-
Jinja2==3.1.4
|
| 5 |
-
opencv-python==4.10.0.84
|
| 6 |
-
numpy==1.26.4
|
| 7 |
-
Pillow==10.4.0
|
| 8 |
-
requests==2.32.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watermark-remover-free/server.py
DELETED
|
@@ -1,121 +0,0 @@
|
|
| 1 |
-
import io
|
| 2 |
-
import os
|
| 3 |
-
from typing import Optional
|
| 4 |
-
|
| 5 |
-
import cv2
|
| 6 |
-
import numpy as np
|
| 7 |
-
from PIL import Image
|
| 8 |
-
import requests
|
| 9 |
-
from fastapi import FastAPI, File, Form, UploadFile, HTTPException, Request
|
| 10 |
-
from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
|
| 11 |
-
from fastapi.staticfiles import StaticFiles
|
| 12 |
-
from fastapi.templating import Jinja2Templates
|
| 13 |
-
|
| 14 |
-
app = FastAPI(title="Watermark Remover API")
|
| 15 |
-
|
| 16 |
-
# Serve static + templates
|
| 17 |
-
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 18 |
-
templates = Jinja2Templates(directory="templates")
|
| 19 |
-
|
| 20 |
-
LAMA_URL = os.getenv("LAMA_URL", "http://localhost:5000") # optional
|
| 21 |
-
|
| 22 |
-
def read_image_to_cv2(file: UploadFile) -> np.ndarray:
|
| 23 |
-
data = file.file.read()
|
| 24 |
-
img_arr = np.frombuffer(data, np.uint8)
|
| 25 |
-
img = cv2.imdecode(img_arr, cv2.IMREAD_COLOR)
|
| 26 |
-
if img is None:
|
| 27 |
-
raise HTTPException(400, detail="Invalid image file")
|
| 28 |
-
return img
|
| 29 |
-
|
| 30 |
-
def pil_bytes_from_cv2(img: np.ndarray, fmt: str = "PNG") -> io.BytesIO:
|
| 31 |
-
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 32 |
-
pil_img = Image.fromarray(img_rgb)
|
| 33 |
-
buf = io.BytesIO()
|
| 34 |
-
pil_img.save(buf, format=fmt)
|
| 35 |
-
buf.seek(0)
|
| 36 |
-
return buf
|
| 37 |
-
|
| 38 |
-
def auto_text_mask(img: np.ndarray) -> np.ndarray:
|
| 39 |
-
"""Simple heuristic mask for text/logo-like overlays."""
|
| 40 |
-
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 41 |
-
gray = cv2.equalizeHist(gray)
|
| 42 |
-
mser = cv2.MSER_create(_delta=5, _min_area=60, _max_area=10000)
|
| 43 |
-
regions, _ = mser.detectRegions(gray)
|
| 44 |
-
mask = np.zeros(gray.shape, dtype=np.uint8)
|
| 45 |
-
for p in regions:
|
| 46 |
-
hull = cv2.convexHull(p.reshape(-1, 1, 2))
|
| 47 |
-
cv2.drawContours(mask, [hull], -1, 255, thickness=-1)
|
| 48 |
-
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
|
| 49 |
-
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
|
| 50 |
-
mask = cv2.dilate(mask, kernel, iterations=1)
|
| 51 |
-
return mask
|
| 52 |
-
|
| 53 |
-
def inpaint_opencv(img: np.ndarray, mask: np.ndarray, method: str = "telea", radius: int = 3) -> np.ndarray:
|
| 54 |
-
flag = cv2.INPAINT_TELEA if method.lower() == "telea" else cv2.INPAINT_NS
|
| 55 |
-
if mask.ndim == 3:
|
| 56 |
-
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
|
| 57 |
-
_, mask_bin = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
|
| 58 |
-
result = cv2.inpaint(img, mask_bin, radius, flag)
|
| 59 |
-
return result
|
| 60 |
-
|
| 61 |
-
def call_lama_server(img: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
| 62 |
-
if mask.ndim == 3:
|
| 63 |
-
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
|
| 64 |
-
_, mask_bin = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)
|
| 65 |
-
|
| 66 |
-
def to_png_bytes(arr: np.ndarray) -> bytes:
|
| 67 |
-
a = arr
|
| 68 |
-
if a.ndim == 2:
|
| 69 |
-
a = cv2.cvtColor(a, cv2.COLOR_GRAY2BGR)
|
| 70 |
-
ok, buf = cv2.imencode('.png', a)
|
| 71 |
-
if not ok:
|
| 72 |
-
raise HTTPException(500, detail="Encoding error")
|
| 73 |
-
return buf.tobytes()
|
| 74 |
-
|
| 75 |
-
files = {
|
| 76 |
-
'image': ('image.png', to_png_bytes(img), 'image/png'),
|
| 77 |
-
'mask': ('mask.png', to_png_bytes(mask_bin), 'image/png'),
|
| 78 |
-
}
|
| 79 |
-
data = {'method': 'lama'}
|
| 80 |
-
try:
|
| 81 |
-
resp = requests.post(f"{LAMA_URL}/inpaint", data=data, files=files, timeout=120)
|
| 82 |
-
resp.raise_for_status()
|
| 83 |
-
except Exception as e:
|
| 84 |
-
raise HTTPException(502, detail=f"LaMa server error: {e}")
|
| 85 |
-
nparr = np.frombuffer(resp.content, np.uint8)
|
| 86 |
-
out = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
| 87 |
-
if out is None:
|
| 88 |
-
raise HTTPException(502, detail="Invalid response from LaMa server")
|
| 89 |
-
return out
|
| 90 |
-
|
| 91 |
-
@app.get("/", response_class=HTMLResponse)
|
| 92 |
-
def index(request: Request):
|
| 93 |
-
return templates.TemplateResponse("index.html", {"request": request})
|
| 94 |
-
|
| 95 |
-
@app.get("/health")
|
| 96 |
-
def health():
|
| 97 |
-
return JSONResponse({"ok": True})
|
| 98 |
-
|
| 99 |
-
@app.post("/api/remove-watermark")
|
| 100 |
-
def remove_watermark(
|
| 101 |
-
image: UploadFile = File(...),
|
| 102 |
-
mask: Optional[UploadFile] = File(None),
|
| 103 |
-
engine: str = Form("opencv"), # "opencv" | "lama"
|
| 104 |
-
method: str = Form("telea"), # opencv: telea | ns
|
| 105 |
-
radius: int = Form(3),
|
| 106 |
-
auto_mask: int = Form(1), # 1=true, 0=false
|
| 107 |
-
):
|
| 108 |
-
img = read_image_to_cv2(image)
|
| 109 |
-
|
| 110 |
-
if mask is not None:
|
| 111 |
-
mask_img = read_image_to_cv2(mask)
|
| 112 |
-
else:
|
| 113 |
-
mask_img = auto_text_mask(img) if auto_mask else np.zeros(img.shape[:2], dtype=np.uint8)
|
| 114 |
-
|
| 115 |
-
if engine == "lama":
|
| 116 |
-
out = call_lama_server(img, mask_img)
|
| 117 |
-
else:
|
| 118 |
-
out = inpaint_opencv(img, mask_img, method=method, radius=radius)
|
| 119 |
-
|
| 120 |
-
buf = pil_bytes_from_cv2(out, fmt="PNG")
|
| 121 |
-
return StreamingResponse(buf, media_type="image/png")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watermark-remover-free/static/style.css
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
body { font-family: system-ui, Arial, sans-serif; padding: 24px; }
|
| 2 |
-
main { max-width: 840px; margin: auto; }
|
| 3 |
-
h1 { margin-bottom: 16px; }
|
| 4 |
-
form label { display: block; margin: 8px 0; }
|
| 5 |
-
.row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
|
| 6 |
-
button { padding: 8px 12px; }
|
| 7 |
-
.hidden { display: none; }
|
| 8 |
-
#output img { max-width: 100%; height: auto; margin-top: 12px; border: 1px solid #ddd; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
watermark-remover-free/templates/index.html
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="utf-8" />
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
-
<title>Watermark Remover</title>
|
| 7 |
-
<link rel="stylesheet" href="/static/style.css" />
|
| 8 |
-
</head>
|
| 9 |
-
<body>
|
| 10 |
-
<main>
|
| 11 |
-
<h1>Watermark Remover</h1>
|
| 12 |
-
<form id="form">
|
| 13 |
-
<label>Image <input type="file" name="image" accept="image/*" required></label>
|
| 14 |
-
<label>Mask (optional) <input type="file" name="mask" accept="image/*"></label>
|
| 15 |
-
<div class="row">
|
| 16 |
-
<label>Engine
|
| 17 |
-
<select name="engine">
|
| 18 |
-
<option value="opencv" selected>OpenCV (fast, free)</option>
|
| 19 |
-
<option value="lama">LaMa (needs separate server)</option>
|
| 20 |
-
</select>
|
| 21 |
-
</label>
|
| 22 |
-
<label>Method
|
| 23 |
-
<select name="method">
|
| 24 |
-
<option value="telea" selected>Telea</option>
|
| 25 |
-
<option value="ns">Navier-Stokes</option>
|
| 26 |
-
</select>
|
| 27 |
-
</label>
|
| 28 |
-
<label>Radius <input type="number" name="radius" value="3" min="1" max="50"></label>
|
| 29 |
-
<label>Auto Mask <input type="checkbox" name="auto_mask" checked></label>
|
| 30 |
-
</div>
|
| 31 |
-
<button type="submit">Remove</button>
|
| 32 |
-
</form>
|
| 33 |
-
|
| 34 |
-
<section id="output" class="hidden">
|
| 35 |
-
<h2>Result</h2>
|
| 36 |
-
<img id="result-img" alt="result" />
|
| 37 |
-
<a id="download" download="result.png">Download</a>
|
| 38 |
-
</section>
|
| 39 |
-
</main>
|
| 40 |
-
|
| 41 |
-
<script>
|
| 42 |
-
const form = document.getElementById('form');
|
| 43 |
-
const out = document.getElementById('output');
|
| 44 |
-
const img = document.getElementById('result-img');
|
| 45 |
-
const dl = document.getElementById('download');
|
| 46 |
-
|
| 47 |
-
form.addEventListener('submit', async (e) => {
|
| 48 |
-
e.preventDefault();
|
| 49 |
-
const data = new FormData(form);
|
| 50 |
-
data.set('auto_mask', form.auto_mask.checked ? 1 : 0);
|
| 51 |
-
const res = await fetch('/api/remove-watermark', { method: 'POST', body: data });
|
| 52 |
-
if (!res.ok) { alert('Failed: ' + res.status); return; }
|
| 53 |
-
const blob = await res.blob();
|
| 54 |
-
const url = URL.createObjectURL(blob);
|
| 55 |
-
img.src = url;
|
| 56 |
-
dl.href = url;
|
| 57 |
-
out.classList.remove('hidden');
|
| 58 |
-
});
|
| 59 |
-
</script>
|
| 60 |
-
</body>
|
| 61 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|