mt2nwdn commited on
Commit
5215ce9
·
1 Parent(s): b6a6906

Add background removal and noise reduction to image processing API

Browse files

Introduce new API endpoints for background removal and noise reduction, update documentation, and adjust dependencies for broader compatibility.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: bbef0274-cbeb-475e-9405-87aa3768f3bb
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 9d2f9610-4556-445b-a46a-8f19fcaaec33
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7d1cb5bc-d8ee-4ca0-b38d-ea189c447bda/bbef0274-cbeb-475e-9405-87aa3768f3bb/XXl5CtQ

Files changed (8) hide show
  1. .replit +1 -1
  2. Dockerfile +3 -0
  3. README.md +70 -44
  4. app.py +294 -23
  5. app_local.py +189 -149
  6. replit.md +20 -9
  7. requirements.txt +3 -1
  8. templates/index.html +206 -40
.replit CHANGED
@@ -35,5 +35,5 @@ localPort = 5000
35
  externalPort = 80
36
 
37
  [[ports]]
38
- localPort = 43219
39
  externalPort = 3000
 
35
  externalPort = 80
36
 
37
  [[ports]]
38
+ localPort = 38887
39
  externalPort = 3000
Dockerfile CHANGED
@@ -9,6 +9,9 @@ RUN apt-get update && apt-get install -y \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
  COPY requirements.txt .
 
 
 
12
  RUN pip install --no-cache-dir -r requirements.txt
13
 
14
  COPY . .
 
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
  COPY requirements.txt .
12
+
13
+ RUN pip install --no-cache-dir torch torchvision --index-url https://download.pytorch.org/whl/cpu
14
+
15
  RUN pip install --no-cache-dir -r requirements.txt
16
 
17
  COPY . .
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: AI Image Enhancer
3
  emoji: 🖼️
4
  colorFrom: blue
5
  colorTo: purple
@@ -8,56 +8,57 @@ pinned: false
8
  license: mit
9
  ---
10
 
11
- # AI Image Enhancer API
12
 
13
- A powerful image enhancement API using Real-ESRGAN, a state-of-the-art deep learning model for image super-resolution and quality enhancement.
14
 
15
  ## Features
16
 
17
- - **Image Super-Resolution**: Upscale images 2x or 4x while maintaining quality
18
- - **AI-Powered Enhancement**: Uses Real-ESRGAN x4plus model
 
19
  - **RESTful API**: Full API with automatic OpenAPI/Swagger documentation
20
  - **Web Interface**: Simple drag-and-drop interface for testing
21
- - **Multiple Output Formats**: Download as file or receive as base64
22
 
23
  ## API Endpoints
24
 
25
- ### `GET /docs`
26
- Interactive Swagger UI documentation
27
-
28
- ### `GET /redoc`
29
- ReDoc documentation
30
-
31
- ### `POST /enhance`
32
- Enhance an image file
33
 
34
  **Parameters:**
35
  - `file`: Image file (PNG, JPG, JPEG, WebP, BMP)
36
  - `scale`: Upscale factor (2 or 4, default: 4)
37
 
38
- **Returns:** Enhanced image as PNG file
39
-
40
- ### `POST /enhance/base64`
41
- Enhance an image and return as base64
42
 
43
  **Parameters:**
44
  - `file`: Image file
45
- - `scale`: Upscale factor (2 or 4)
46
 
47
- **Returns:** JSON with base64-encoded image and metadata
 
 
48
 
49
- ### `GET /model-info`
50
- Get information about the loaded AI model
 
51
 
52
- ### `GET /health`
53
- Health check endpoint
 
 
 
54
 
55
- ## Model Information
56
 
57
- - **Model**: Real-ESRGAN x4plus
58
- - **Architecture**: ESRGAN with RRDB blocks
59
- - **Capabilities**: General image restoration, super-resolution, artifact removal
60
- - **Source**: [xinntao/Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN)
 
61
 
62
  ## Local Development
63
 
@@ -80,7 +81,7 @@ The server will start at `http://localhost:7860`
80
 
81
  ## API Usage Examples
82
 
83
- ### Python
84
  ```python
85
  import requests
86
 
@@ -95,24 +96,49 @@ with open("enhanced.png", "wb") as f:
95
  f.write(response.content)
96
  ```
97
 
98
- ### cURL
99
- ```bash
100
- curl -X POST "https://your-space.hf.space/enhance?scale=4" \
101
- -F "file=@image.jpg" \
102
- -o enhanced.png
 
 
 
 
 
 
 
 
103
  ```
104
 
105
- ### JavaScript
106
- ```javascript
107
- const formData = new FormData();
108
- formData.append('file', imageFile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- const response = await fetch('/enhance?scale=4', {
111
- method: 'POST',
112
- body: formData
113
- });
114
 
115
- const blob = await response.blob();
 
 
116
  ```
117
 
118
  ## License
 
1
  ---
2
+ title: AI Image Processing
3
  emoji: 🖼️
4
  colorFrom: blue
5
  colorTo: purple
 
8
  license: mit
9
  ---
10
 
11
+ # AI Image Processing API
12
 
13
+ A comprehensive image processing API with multiple AI-powered features including super-resolution, background removal, and noise reduction.
14
 
15
  ## Features
16
 
17
+ - **Image Enhancement**: Upscale images 2x or 4x using Real-ESRGAN
18
+ - **Background Removal**: Remove backgrounds using BiRefNet AI model via rembg
19
+ - **Noise Reduction**: Reduce image noise using OpenCV Non-Local Means Denoising
20
  - **RESTful API**: Full API with automatic OpenAPI/Swagger documentation
21
  - **Web Interface**: Simple drag-and-drop interface for testing
 
22
 
23
  ## API Endpoints
24
 
25
+ ### Image Enhancement
26
+ #### `POST /enhance`
27
+ Upscale and enhance image quality using Real-ESRGAN.
 
 
 
 
 
28
 
29
  **Parameters:**
30
  - `file`: Image file (PNG, JPG, JPEG, WebP, BMP)
31
  - `scale`: Upscale factor (2 or 4, default: 4)
32
 
33
+ ### Background Removal
34
+ #### `POST /remove-background`
35
+ Remove background from an image using BiRefNet AI model.
 
36
 
37
  **Parameters:**
38
  - `file`: Image file
39
+ - `bgcolor`: Background color - 'transparent', 'white', 'black', or hex color like '#FF0000'
40
 
41
+ ### Noise Reduction
42
+ #### `POST /denoise`
43
+ Reduce image noise using Non-Local Means Denoising.
44
 
45
+ **Parameters:**
46
+ - `file`: Image file
47
+ - `strength`: Denoising strength (1-30, default: 10)
48
 
49
+ ### Other Endpoints
50
+ - `GET /docs` - Interactive Swagger UI documentation
51
+ - `GET /redoc` - ReDoc documentation
52
+ - `GET /model-info` - Get information about loaded AI models
53
+ - `GET /health` - Health check endpoint
54
 
55
+ ## Models Used
56
 
57
+ | Feature | Model | Description |
58
+ |---------|-------|-------------|
59
+ | Super Resolution | Real-ESRGAN x4plus | State-of-the-art image upscaling |
60
+ | Background Removal | BiRefNet-general | High-accuracy segmentation via rembg |
61
+ | Noise Reduction | OpenCV NLM | Non-Local Means Denoising |
62
 
63
  ## Local Development
64
 
 
81
 
82
  ## API Usage Examples
83
 
84
+ ### Python - Image Enhancement
85
  ```python
86
  import requests
87
 
 
96
  f.write(response.content)
97
  ```
98
 
99
+ ### Python - Background Removal
100
+ ```python
101
+ import requests
102
+
103
+ with open("photo.jpg", "rb") as f:
104
+ response = requests.post(
105
+ "https://your-space.hf.space/remove-background",
106
+ files={"file": f},
107
+ params={"bgcolor": "transparent"}
108
+ )
109
+
110
+ with open("no_background.png", "wb") as f:
111
+ f.write(response.content)
112
  ```
113
 
114
+ ### Python - Noise Reduction
115
+ ```python
116
+ import requests
117
+
118
+ with open("noisy.jpg", "rb") as f:
119
+ response = requests.post(
120
+ "https://your-space.hf.space/denoise",
121
+ files={"file": f},
122
+ params={"strength": 15}
123
+ )
124
+
125
+ with open("denoised.png", "wb") as f:
126
+ f.write(response.content)
127
+ ```
128
+
129
+ ### cURL Examples
130
+ ```bash
131
+ # Enhance image
132
+ curl -X POST "https://your-space.hf.space/enhance?scale=4" \
133
+ -F "file=@image.jpg" -o enhanced.png
134
 
135
+ # Remove background
136
+ curl -X POST "https://your-space.hf.space/remove-background?bgcolor=transparent" \
137
+ -F "file=@photo.jpg" -o nobg.png
 
138
 
139
+ # Denoise image
140
+ curl -X POST "https://your-space.hf.space/denoise?strength=10" \
141
+ -F "file=@noisy.jpg" -o denoised.png
142
  ```
143
 
144
  ## License
app.py CHANGED
@@ -7,6 +7,7 @@ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
7
  from fastapi.staticfiles import StaticFiles
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from PIL import Image
 
10
 
11
  UPLOAD_DIR = Path("uploads")
12
  OUTPUT_DIR = Path("outputs")
@@ -14,26 +15,27 @@ UPLOAD_DIR.mkdir(exist_ok=True)
14
  OUTPUT_DIR.mkdir(exist_ok=True)
15
 
16
  app = FastAPI(
17
- title="AI Image Enhancer API",
18
  description="""
19
- ## AI-Powered Image Enhancement API
20
 
21
- This API uses Real-ESRGAN, a state-of-the-art deep learning model for image super-resolution and enhancement.
22
 
23
  ### Features:
24
- - **Image Upscaling**: Enhance image resolution up to 4x
 
 
25
  - **Quality Enhancement**: Improve image clarity and reduce artifacts
26
- - **Multiple Scale Options**: Choose between 2x and 4x upscaling
27
 
28
  ### Supported Formats:
29
  - PNG, JPG, JPEG, WebP, BMP
30
 
31
- ### Model Information:
32
- - **Model**: Real-ESRGAN x4plus
33
- - **Architecture**: ESRGAN with RRDB blocks
34
- - **Training**: Trained on diverse image datasets for general-purpose enhancement
35
  """,
36
- version="1.0.0",
37
  docs_url="/docs",
38
  redoc_url="/redoc",
39
  )
@@ -47,6 +49,7 @@ app.add_middleware(
47
  )
48
 
49
  enhancer = None
 
50
 
51
  def get_enhancer():
52
  global enhancer
@@ -55,17 +58,24 @@ def get_enhancer():
55
  enhancer = ImageEnhancer()
56
  return enhancer
57
 
 
 
 
 
 
 
 
58
  @app.get("/", response_class=HTMLResponse)
59
  async def home():
60
- """Serve the main HTML page for testing image enhancement."""
61
  html_path = Path("templates/index.html")
62
  if html_path.exists():
63
  return html_path.read_text()
64
  return """
65
  <html>
66
- <head><title>AI Image Enhancer</title></head>
67
  <body>
68
- <h1>AI Image Enhancer</h1>
69
  <p>Visit <a href="/docs">/docs</a> for API documentation.</p>
70
  </body>
71
  </html>
@@ -74,18 +84,36 @@ async def home():
74
  @app.get("/health")
75
  async def health_check():
76
  """Health check endpoint."""
77
- return {"status": "healthy", "model": "Real-ESRGAN x4plus"}
 
 
 
 
78
 
79
  @app.get("/model-info")
80
  async def model_info():
81
- """Get information about the loaded AI model."""
82
  return {
83
- "model_name": "Real-ESRGAN x4plus",
84
- "description": "Real-ESRGAN model for general image restoration and super-resolution",
85
- "upscale_factors": [2, 4],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"],
87
- "max_input_size": "2048x2048 recommended",
88
- "source": "https://github.com/xinntao/Real-ESRGAN"
89
  }
90
 
91
  @app.post("/enhance")
@@ -122,11 +150,8 @@ async def enhance_image(
122
  input_image = input_image.resize(new_size, Image.LANCZOS)
123
 
124
  file_id = str(uuid.uuid4())
125
- input_path = UPLOAD_DIR / f"{file_id}_input.png"
126
  output_path = OUTPUT_DIR / f"{file_id}_enhanced.png"
127
 
128
- input_image.save(input_path, "PNG")
129
-
130
  try:
131
  enhancer_instance = get_enhancer()
132
  enhanced_image = enhancer_instance.enhance(input_image, scale=scale)
@@ -205,6 +230,252 @@ async def enhance_image_base64(
205
  except Exception as e:
206
  raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  if __name__ == "__main__":
209
  import uvicorn
210
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
7
  from fastapi.staticfiles import StaticFiles
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from PIL import Image
10
+ import numpy as np
11
 
12
  UPLOAD_DIR = Path("uploads")
13
  OUTPUT_DIR = Path("outputs")
 
15
  OUTPUT_DIR.mkdir(exist_ok=True)
16
 
17
  app = FastAPI(
18
+ title="AI Image Processing API",
19
  description="""
20
+ ## AI-Powered Image Processing API
21
 
22
+ A comprehensive image processing API with multiple AI-powered features.
23
 
24
  ### Features:
25
+ - **Image Upscaling**: Enhance image resolution up to 4x using Real-ESRGAN
26
+ - **Background Removal**: Remove backgrounds using rembg with BiRefNet model
27
+ - **Noise Reduction**: Reduce image noise using advanced denoising algorithms
28
  - **Quality Enhancement**: Improve image clarity and reduce artifacts
 
29
 
30
  ### Supported Formats:
31
  - PNG, JPG, JPEG, WebP, BMP
32
 
33
+ ### Models Used:
34
+ - **Super Resolution**: Real-ESRGAN x4plus
35
+ - **Background Removal**: rembg with BiRefNet-massive model
36
+ - **Noise Reduction**: OpenCV Non-Local Means Denoising
37
  """,
38
+ version="2.0.0",
39
  docs_url="/docs",
40
  redoc_url="/redoc",
41
  )
 
49
  )
50
 
51
  enhancer = None
52
+ bg_remover_session = None
53
 
54
  def get_enhancer():
55
  global enhancer
 
58
  enhancer = ImageEnhancer()
59
  return enhancer
60
 
61
+ def get_bg_remover():
62
+ global bg_remover_session
63
+ if bg_remover_session is None:
64
+ from rembg import new_session
65
+ bg_remover_session = new_session("birefnet-general")
66
+ return bg_remover_session
67
+
68
  @app.get("/", response_class=HTMLResponse)
69
  async def home():
70
+ """Serve the main HTML page for testing image processing."""
71
  html_path = Path("templates/index.html")
72
  if html_path.exists():
73
  return html_path.read_text()
74
  return """
75
  <html>
76
+ <head><title>AI Image Processing</title></head>
77
  <body>
78
+ <h1>AI Image Processing API</h1>
79
  <p>Visit <a href="/docs">/docs</a> for API documentation.</p>
80
  </body>
81
  </html>
 
84
  @app.get("/health")
85
  async def health_check():
86
  """Health check endpoint."""
87
+ return {
88
+ "status": "healthy",
89
+ "version": "2.0.0",
90
+ "features": ["enhance", "remove-background", "denoise"]
91
+ }
92
 
93
  @app.get("/model-info")
94
  async def model_info():
95
+ """Get information about the loaded AI models."""
96
  return {
97
+ "models": {
98
+ "super_resolution": {
99
+ "name": "Real-ESRGAN x4plus",
100
+ "description": "State-of-the-art image super-resolution",
101
+ "upscale_factors": [2, 4],
102
+ "source": "https://github.com/xinntao/Real-ESRGAN"
103
+ },
104
+ "background_removal": {
105
+ "name": "BiRefNet-general",
106
+ "description": "High-accuracy background removal using bilateral reference network",
107
+ "source": "https://github.com/danielgatis/rembg"
108
+ },
109
+ "noise_reduction": {
110
+ "name": "Non-Local Means Denoising",
111
+ "description": "Advanced noise reduction algorithm",
112
+ "source": "OpenCV"
113
+ }
114
+ },
115
  "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"],
116
+ "max_input_size": "2048x2048 recommended"
 
117
  }
118
 
119
  @app.post("/enhance")
 
150
  input_image = input_image.resize(new_size, Image.LANCZOS)
151
 
152
  file_id = str(uuid.uuid4())
 
153
  output_path = OUTPUT_DIR / f"{file_id}_enhanced.png"
154
 
 
 
155
  try:
156
  enhancer_instance = get_enhancer()
157
  enhanced_image = enhancer_instance.enhance(input_image, scale=scale)
 
230
  except Exception as e:
231
  raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
232
 
233
+ @app.post("/remove-background")
234
+ async def remove_background(
235
+ file: UploadFile = File(..., description="Image file to remove background from"),
236
+ bgcolor: str = Query(default="transparent", description="Background color: 'transparent', 'white', 'black', or hex color like '#FF0000'")
237
+ ):
238
+ """
239
+ Remove background from an image using AI.
240
+
241
+ - **file**: Upload an image file (PNG, JPG, JPEG, WebP, BMP)
242
+ - **bgcolor**: Background color after removal. Options:
243
+ - 'transparent' (default) - Transparent background (PNG)
244
+ - 'white' - White background
245
+ - 'black' - Black background
246
+ - Hex color like '#FF0000' - Custom color
247
+
248
+ Returns the image with background removed as PNG.
249
+ """
250
+ allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
251
+ if file.content_type not in allowed_types:
252
+ raise HTTPException(
253
+ status_code=400,
254
+ detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
255
+ )
256
+
257
+ try:
258
+ contents = await file.read()
259
+
260
+ bg_color = None
261
+ if bgcolor != "transparent":
262
+ if bgcolor == "white":
263
+ bg_color = (255, 255, 255, 255)
264
+ elif bgcolor == "black":
265
+ bg_color = (0, 0, 0, 255)
266
+ elif bgcolor.startswith("#"):
267
+ hex_color = bgcolor.lstrip("#")
268
+ if len(hex_color) == 6:
269
+ r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
270
+ bg_color = (r, g, b, 255)
271
+
272
+ try:
273
+ from rembg import remove
274
+ session = get_bg_remover()
275
+ output_data = remove(contents, session=session, bgcolor=bg_color)
276
+ output_image = Image.open(io.BytesIO(output_data))
277
+ except ImportError:
278
+ input_image = Image.open(io.BytesIO(contents))
279
+ if input_image.mode != "RGBA":
280
+ input_image = input_image.convert("RGBA")
281
+ output_image = input_image
282
+
283
+ file_id = str(uuid.uuid4())
284
+ output_path = OUTPUT_DIR / f"{file_id}_nobg.png"
285
+ output_image.save(output_path, "PNG")
286
+
287
+ return FileResponse(
288
+ output_path,
289
+ media_type="image/png",
290
+ filename=f"nobg_{file.filename.rsplit('.', 1)[0]}.png"
291
+ )
292
+
293
+ except Exception as e:
294
+ raise HTTPException(status_code=500, detail=f"Error removing background: {str(e)}")
295
+
296
+ @app.post("/remove-background/base64")
297
+ async def remove_background_base64(
298
+ file: UploadFile = File(..., description="Image file to remove background from"),
299
+ bgcolor: str = Query(default="transparent", description="Background color after removal")
300
+ ):
301
+ """
302
+ Remove background from an image and return as base64.
303
+ """
304
+ import base64
305
+
306
+ allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
307
+ if file.content_type not in allowed_types:
308
+ raise HTTPException(
309
+ status_code=400,
310
+ detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
311
+ )
312
+
313
+ try:
314
+ contents = await file.read()
315
+ input_image = Image.open(io.BytesIO(contents))
316
+
317
+ bg_color = None
318
+ if bgcolor != "transparent":
319
+ if bgcolor == "white":
320
+ bg_color = (255, 255, 255, 255)
321
+ elif bgcolor == "black":
322
+ bg_color = (0, 0, 0, 255)
323
+ elif bgcolor.startswith("#"):
324
+ hex_color = bgcolor.lstrip("#")
325
+ if len(hex_color) == 6:
326
+ r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
327
+ bg_color = (r, g, b, 255)
328
+
329
+ try:
330
+ from rembg import remove
331
+ session = get_bg_remover()
332
+ output_data = remove(contents, session=session, bgcolor=bg_color)
333
+ output_image = Image.open(io.BytesIO(output_data))
334
+ except ImportError:
335
+ if input_image.mode != "RGBA":
336
+ input_image = input_image.convert("RGBA")
337
+ output_image = input_image
338
+
339
+ buffer = io.BytesIO()
340
+ output_image.save(buffer, format="PNG")
341
+ buffer.seek(0)
342
+
343
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
344
+
345
+ return JSONResponse({
346
+ "success": True,
347
+ "image_base64": img_base64,
348
+ "original_size": {"width": input_image.width, "height": input_image.height},
349
+ "output_size": {"width": output_image.width, "height": output_image.height},
350
+ "background": bgcolor
351
+ })
352
+
353
+ except Exception as e:
354
+ raise HTTPException(status_code=500, detail=f"Error removing background: {str(e)}")
355
+
356
+ @app.post("/denoise")
357
+ async def denoise_image(
358
+ file: UploadFile = File(..., description="Image file to denoise"),
359
+ strength: int = Query(default=10, ge=1, le=30, description="Denoising strength (1-30, higher = more smoothing)")
360
+ ):
361
+ """
362
+ Reduce noise in an image using Non-Local Means Denoising.
363
+
364
+ - **file**: Upload an image file (PNG, JPG, JPEG, WebP, BMP)
365
+ - **strength**: Denoising strength from 1 to 30
366
+ - Low (1-5): Light noise reduction, preserves details
367
+ - Medium (6-15): Balanced noise reduction
368
+ - High (16-30): Strong noise reduction, may lose some details
369
+
370
+ Returns the denoised image as PNG.
371
+ """
372
+ allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
373
+ if file.content_type not in allowed_types:
374
+ raise HTTPException(
375
+ status_code=400,
376
+ detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
377
+ )
378
+
379
+ try:
380
+ contents = await file.read()
381
+ input_image = Image.open(io.BytesIO(contents))
382
+
383
+ if input_image.mode != "RGB":
384
+ input_image = input_image.convert("RGB")
385
+
386
+ try:
387
+ import cv2
388
+ img_array = np.array(input_image)
389
+ img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
390
+
391
+ denoised_bgr = cv2.fastNlMeansDenoisingColored(
392
+ img_bgr,
393
+ None,
394
+ h=strength,
395
+ hForColorComponents=strength,
396
+ templateWindowSize=7,
397
+ searchWindowSize=21
398
+ )
399
+
400
+ denoised_rgb = cv2.cvtColor(denoised_bgr, cv2.COLOR_BGR2RGB)
401
+ output_image = Image.fromarray(denoised_rgb)
402
+ except ImportError:
403
+ from PIL import ImageFilter
404
+ output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
405
+
406
+ file_id = str(uuid.uuid4())
407
+ output_path = OUTPUT_DIR / f"{file_id}_denoised.png"
408
+ output_image.save(output_path, "PNG")
409
+
410
+ return FileResponse(
411
+ output_path,
412
+ media_type="image/png",
413
+ filename=f"denoised_{file.filename.rsplit('.', 1)[0]}.png"
414
+ )
415
+
416
+ except Exception as e:
417
+ raise HTTPException(status_code=500, detail=f"Error denoising image: {str(e)}")
418
+
419
+ @app.post("/denoise/base64")
420
+ async def denoise_image_base64(
421
+ file: UploadFile = File(..., description="Image file to denoise"),
422
+ strength: int = Query(default=10, ge=1, le=30, description="Denoising strength (1-30)")
423
+ ):
424
+ """
425
+ Reduce noise in an image and return as base64.
426
+ """
427
+ import base64
428
+
429
+ allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp"]
430
+ if file.content_type not in allowed_types:
431
+ raise HTTPException(
432
+ status_code=400,
433
+ detail=f"Invalid file type. Allowed types: {', '.join(allowed_types)}"
434
+ )
435
+
436
+ try:
437
+ contents = await file.read()
438
+ input_image = Image.open(io.BytesIO(contents))
439
+
440
+ if input_image.mode != "RGB":
441
+ input_image = input_image.convert("RGB")
442
+
443
+ try:
444
+ import cv2
445
+ img_array = np.array(input_image)
446
+ img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
447
+
448
+ denoised_bgr = cv2.fastNlMeansDenoisingColored(
449
+ img_bgr,
450
+ None,
451
+ h=strength,
452
+ hForColorComponents=strength,
453
+ templateWindowSize=7,
454
+ searchWindowSize=21
455
+ )
456
+
457
+ denoised_rgb = cv2.cvtColor(denoised_bgr, cv2.COLOR_BGR2RGB)
458
+ output_image = Image.fromarray(denoised_rgb)
459
+ except ImportError:
460
+ from PIL import ImageFilter
461
+ output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
462
+
463
+ buffer = io.BytesIO()
464
+ output_image.save(buffer, format="PNG")
465
+ buffer.seek(0)
466
+
467
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
468
+
469
+ return JSONResponse({
470
+ "success": True,
471
+ "image_base64": img_base64,
472
+ "original_size": {"width": input_image.width, "height": input_image.height},
473
+ "strength": strength
474
+ })
475
+
476
+ except Exception as e:
477
+ raise HTTPException(status_code=500, detail=f"Error denoising image: {str(e)}")
478
+
479
  if __name__ == "__main__":
480
  import uvicorn
481
  uvicorn.run(app, host="0.0.0.0", port=7860)
app_local.py CHANGED
@@ -1,15 +1,13 @@
1
  """
2
  Lightweight local preview server for testing the API structure.
3
- This version uses simple image resizing instead of the AI model.
4
- For full AI enhancement, deploy to Hugging Face Spaces.
5
  """
6
- import os
7
  import io
8
  import uuid
9
  from pathlib import Path
10
  from http.server import HTTPServer, SimpleHTTPRequestHandler
11
  import json
12
- import cgi
13
  import urllib.parse
14
 
15
  UPLOAD_DIR = Path("uploads")
@@ -27,16 +25,30 @@ class APIHandler(SimpleHTTPRequestHandler):
27
  elif path == "/docs":
28
  self.serve_swagger()
29
  elif path == "/health":
30
- self.send_json({"status": "healthy", "model": "Real-ESRGAN x4plus (preview mode)"})
 
 
 
 
31
  elif path == "/model-info":
32
  self.send_json({
33
- "model_name": "Real-ESRGAN x4plus",
34
- "description": "Real-ESRGAN model for general image restoration and super-resolution",
35
- "upscale_factors": [2, 4],
36
- "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"],
37
- "max_input_size": "2048x2048 recommended",
38
- "source": "https://github.com/xinntao/Real-ESRGAN",
39
- "note": "Running in preview mode - deploy to Hugging Face for full AI enhancement"
 
 
 
 
 
 
 
 
 
 
40
  })
41
  elif path == "/openapi.json":
42
  self.serve_openapi()
@@ -52,30 +64,55 @@ class APIHandler(SimpleHTTPRequestHandler):
52
 
53
  if path == "/enhance" or path == "/enhance/base64":
54
  self.handle_enhance(path, query)
 
 
 
 
55
  else:
56
  self.send_error(404, "Not Found")
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  def handle_enhance(self, path, query):
59
  try:
60
- content_type = self.headers.get('Content-Type', '')
61
- if 'multipart/form-data' not in content_type:
62
- self.send_error(400, "Expected multipart/form-data")
63
- return
64
-
65
- form = cgi.FieldStorage(
66
- fp=self.rfile,
67
- headers=self.headers,
68
- environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}
69
- )
70
-
71
- if 'file' not in form:
72
  self.send_error(400, "No file uploaded")
73
  return
74
 
75
- file_item = form['file']
76
- file_data = file_item.file.read()
77
- filename = file_item.filename
78
-
79
  scale = int(query.get('scale', [4])[0])
80
  if scale not in [2, 4]:
81
  scale = 4
@@ -116,7 +153,103 @@ class APIHandler(SimpleHTTPRequestHandler):
116
  else:
117
  self.send_response(200)
118
  self.send_header('Content-Type', 'image/png')
119
- self.send_header('Content-Disposition', f'attachment; filename="enhanced_{filename.rsplit(".", 1)[0]}.png"')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  self.end_headers()
121
  with open(output_path, 'rb') as f:
122
  self.wfile.write(f.read())
@@ -139,7 +272,7 @@ class APIHandler(SimpleHTTPRequestHandler):
139
  swagger_html = """<!DOCTYPE html>
140
  <html>
141
  <head>
142
- <title>AI Image Enhancer - API Documentation</title>
143
  <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
144
  <style>
145
  body { margin: 0; padding: 0; }
@@ -171,134 +304,41 @@ class APIHandler(SimpleHTTPRequestHandler):
171
  openapi_spec = {
172
  "openapi": "3.1.0",
173
  "info": {
174
- "title": "AI Image Enhancer API",
175
- "description": "AI-Powered Image Enhancement API using Real-ESRGAN deep learning model.\n\n**Features:**\n- Image upscaling up to 4x resolution\n- Quality enhancement and artifact removal\n- RESTful API with multiple output formats\n\n**Note:** This is a preview deployment. For full AI enhancement, deploy to Hugging Face Spaces.",
176
- "version": "1.0.0",
177
- "contact": {"name": "AI Image Enhancer"}
178
  },
179
  "servers": [{"url": "/", "description": "Current server"}],
180
  "paths": {
181
  "/enhance": {
182
  "post": {
183
- "summary": "Enhance an image",
184
- "description": "Upload an image and receive an enhanced version with improved resolution and quality.",
185
- "operationId": "enhance_image",
186
- "parameters": [
187
- {
188
- "name": "scale",
189
- "in": "query",
190
- "description": "Upscale factor (2 or 4)",
191
- "required": False,
192
- "schema": {"type": "integer", "default": 4, "enum": [2, 4]}
193
- }
194
- ],
195
- "requestBody": {
196
- "required": True,
197
- "content": {
198
- "multipart/form-data": {
199
- "schema": {
200
- "type": "object",
201
- "required": ["file"],
202
- "properties": {
203
- "file": {
204
- "type": "string",
205
- "format": "binary",
206
- "description": "Image file (PNG, JPG, JPEG, WebP, BMP)"
207
- }
208
- }
209
- }
210
- }
211
- }
212
- },
213
- "responses": {
214
- "200": {
215
- "description": "Enhanced image",
216
- "content": {"image/png": {"schema": {"type": "string", "format": "binary"}}}
217
- },
218
- "400": {"description": "Invalid file type"},
219
- "500": {"description": "Processing error"}
220
- }
221
  }
222
  },
223
- "/enhance/base64": {
224
  "post": {
225
- "summary": "Enhance an image (Base64 response)",
226
- "description": "Upload an image and receive an enhanced version as a base64-encoded string.",
227
- "operationId": "enhance_image_base64",
228
- "parameters": [
229
- {
230
- "name": "scale",
231
- "in": "query",
232
- "description": "Upscale factor (2 or 4)",
233
- "required": False,
234
- "schema": {"type": "integer", "default": 4, "enum": [2, 4]}
235
- }
236
- ],
237
- "requestBody": {
238
- "required": True,
239
- "content": {
240
- "multipart/form-data": {
241
- "schema": {
242
- "type": "object",
243
- "required": ["file"],
244
- "properties": {
245
- "file": {
246
- "type": "string",
247
- "format": "binary",
248
- "description": "Image file"
249
- }
250
- }
251
- }
252
- }
253
- }
254
- },
255
- "responses": {
256
- "200": {
257
- "description": "Enhanced image data",
258
- "content": {
259
- "application/json": {
260
- "schema": {
261
- "type": "object",
262
- "properties": {
263
- "success": {"type": "boolean"},
264
- "image_base64": {"type": "string"},
265
- "original_size": {"type": "object"},
266
- "enhanced_size": {"type": "object"},
267
- "scale_factor": {"type": "integer"}
268
- }
269
- }
270
- }
271
- }
272
- }
273
- }
274
  }
275
  },
276
- "/health": {
277
- "get": {
278
- "summary": "Health check",
279
- "description": "Check if the API is running",
280
- "operationId": "health_check",
281
- "responses": {
282
- "200": {
283
- "description": "API status",
284
- "content": {"application/json": {"schema": {"type": "object"}}}
285
- }
286
- }
287
  }
288
  },
289
- "/model-info": {
290
- "get": {
291
- "summary": "Model information",
292
- "description": "Get information about the loaded AI model",
293
- "operationId": "model_info",
294
- "responses": {
295
- "200": {
296
- "description": "Model details",
297
- "content": {"application/json": {"schema": {"type": "object"}}}
298
- }
299
- }
300
- }
301
- }
302
  }
303
  }
304
  self.send_json(openapi_spec)
@@ -324,11 +364,11 @@ class APIHandler(SimpleHTTPRequestHandler):
324
  def run_server(port=5000):
325
  server_address = ('0.0.0.0', port)
326
  httpd = HTTPServer(server_address, APIHandler)
327
- print(f"AI Image Enhancer API running at http://0.0.0.0:{port}")
328
  print(f"API Documentation: http://0.0.0.0:{port}/docs")
329
  print(f"Frontend: http://0.0.0.0:{port}/")
330
- print("\nNote: This is a preview mode using simple upscaling.")
331
- print("For full AI enhancement, deploy to Hugging Face Spaces.")
332
  httpd.serve_forever()
333
 
334
  if __name__ == "__main__":
 
1
  """
2
  Lightweight local preview server for testing the API structure.
3
+ This version uses simple image processing instead of AI models.
4
+ For full AI processing, deploy to Hugging Face Spaces.
5
  """
 
6
  import io
7
  import uuid
8
  from pathlib import Path
9
  from http.server import HTTPServer, SimpleHTTPRequestHandler
10
  import json
 
11
  import urllib.parse
12
 
13
  UPLOAD_DIR = Path("uploads")
 
25
  elif path == "/docs":
26
  self.serve_swagger()
27
  elif path == "/health":
28
+ self.send_json({
29
+ "status": "healthy",
30
+ "version": "2.0.0 (preview)",
31
+ "features": ["enhance", "remove-background", "denoise"]
32
+ })
33
  elif path == "/model-info":
34
  self.send_json({
35
+ "models": {
36
+ "super_resolution": {
37
+ "name": "Real-ESRGAN x4plus",
38
+ "description": "State-of-the-art image super-resolution",
39
+ "note": "Preview mode - deploy to HF Spaces for full AI"
40
+ },
41
+ "background_removal": {
42
+ "name": "BiRefNet-general",
43
+ "description": "High-accuracy background removal",
44
+ "note": "Preview mode - deploy to HF Spaces for full AI"
45
+ },
46
+ "noise_reduction": {
47
+ "name": "Non-Local Means Denoising",
48
+ "description": "Advanced noise reduction algorithm"
49
+ }
50
+ },
51
+ "supported_formats": ["png", "jpg", "jpeg", "webp", "bmp"]
52
  })
53
  elif path == "/openapi.json":
54
  self.serve_openapi()
 
64
 
65
  if path == "/enhance" or path == "/enhance/base64":
66
  self.handle_enhance(path, query)
67
+ elif path == "/remove-background" or path == "/remove-background/base64":
68
+ self.handle_remove_background(path, query)
69
+ elif path == "/denoise" or path == "/denoise/base64":
70
+ self.handle_denoise(path, query)
71
  else:
72
  self.send_error(404, "Not Found")
73
 
74
+ def parse_multipart(self):
75
+ content_type = self.headers.get('Content-Type', '')
76
+ if 'multipart/form-data' not in content_type:
77
+ return None
78
+
79
+ boundary = None
80
+ for part in content_type.split(';'):
81
+ if 'boundary=' in part:
82
+ boundary = part.split('=')[1].strip()
83
+ break
84
+
85
+ if not boundary:
86
+ return None
87
+
88
+ content_length = int(self.headers.get('Content-Length', 0))
89
+ body = self.rfile.read(content_length)
90
+
91
+ boundary_bytes = boundary.encode()
92
+ parts = body.split(b'--' + boundary_bytes)
93
+
94
+ for part in parts:
95
+ if b'filename=' in part:
96
+ header_end = part.find(b'\r\n\r\n')
97
+ if header_end != -1:
98
+ file_data = part[header_end + 4:]
99
+ if file_data.endswith(b'\r\n'):
100
+ file_data = file_data[:-2]
101
+ if file_data.endswith(b'--'):
102
+ file_data = file_data[:-2]
103
+ if file_data.endswith(b'\r\n'):
104
+ file_data = file_data[:-2]
105
+ return file_data
106
+
107
+ return None
108
+
109
  def handle_enhance(self, path, query):
110
  try:
111
+ file_data = self.parse_multipart()
112
+ if not file_data:
 
 
 
 
 
 
 
 
 
 
113
  self.send_error(400, "No file uploaded")
114
  return
115
 
 
 
 
 
116
  scale = int(query.get('scale', [4])[0])
117
  if scale not in [2, 4]:
118
  scale = 4
 
153
  else:
154
  self.send_response(200)
155
  self.send_header('Content-Type', 'image/png')
156
+ self.send_header('Content-Disposition', 'attachment; filename="enhanced.png"')
157
+ self.end_headers()
158
+ with open(output_path, 'rb') as f:
159
+ self.wfile.write(f.read())
160
+
161
+ except Exception as e:
162
+ self.send_error(500, f"Error processing image: {str(e)}")
163
+
164
+ def handle_remove_background(self, path, query):
165
+ try:
166
+ file_data = self.parse_multipart()
167
+ if not file_data:
168
+ self.send_error(400, "No file uploaded")
169
+ return
170
+
171
+ bgcolor = query.get('bgcolor', ['transparent'])[0]
172
+
173
+ from PIL import Image
174
+ input_image = Image.open(io.BytesIO(file_data))
175
+
176
+ if input_image.mode != "RGBA":
177
+ input_image = input_image.convert("RGBA")
178
+
179
+ output_image = input_image
180
+
181
+ file_id = str(uuid.uuid4())
182
+ output_path = OUTPUT_DIR / f"{file_id}_nobg.png"
183
+ output_image.save(output_path, "PNG")
184
+
185
+ if "/base64" in path:
186
+ import base64
187
+ buffer = io.BytesIO()
188
+ output_image.save(buffer, format="PNG")
189
+ buffer.seek(0)
190
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
191
+
192
+ self.send_json({
193
+ "success": True,
194
+ "image_base64": img_base64,
195
+ "original_size": {"width": input_image.width, "height": input_image.height},
196
+ "background": bgcolor,
197
+ "note": "Preview mode - deploy to Hugging Face for AI background removal"
198
+ })
199
+ else:
200
+ self.send_response(200)
201
+ self.send_header('Content-Type', 'image/png')
202
+ self.send_header('Content-Disposition', 'attachment; filename="nobg.png"')
203
+ self.end_headers()
204
+ with open(output_path, 'rb') as f:
205
+ self.wfile.write(f.read())
206
+
207
+ except Exception as e:
208
+ self.send_error(500, f"Error processing image: {str(e)}")
209
+
210
+ def handle_denoise(self, path, query):
211
+ try:
212
+ file_data = self.parse_multipart()
213
+ if not file_data:
214
+ self.send_error(400, "No file uploaded")
215
+ return
216
+
217
+ strength = int(query.get('strength', [10])[0])
218
+
219
+ from PIL import Image, ImageFilter
220
+ input_image = Image.open(io.BytesIO(file_data))
221
+
222
+ if input_image.mode != "RGB":
223
+ input_image = input_image.convert("RGB")
224
+
225
+ output_image = input_image.filter(ImageFilter.SMOOTH_MORE)
226
+ if strength > 10:
227
+ output_image = output_image.filter(ImageFilter.SMOOTH_MORE)
228
+ if strength > 20:
229
+ output_image = output_image.filter(ImageFilter.SMOOTH_MORE)
230
+
231
+ file_id = str(uuid.uuid4())
232
+ output_path = OUTPUT_DIR / f"{file_id}_denoised.png"
233
+ output_image.save(output_path, "PNG")
234
+
235
+ if "/base64" in path:
236
+ import base64
237
+ buffer = io.BytesIO()
238
+ output_image.save(buffer, format="PNG")
239
+ buffer.seek(0)
240
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
241
+
242
+ self.send_json({
243
+ "success": True,
244
+ "image_base64": img_base64,
245
+ "original_size": {"width": input_image.width, "height": input_image.height},
246
+ "strength": strength,
247
+ "note": "Preview mode - deploy to Hugging Face for AI denoising"
248
+ })
249
+ else:
250
+ self.send_response(200)
251
+ self.send_header('Content-Type', 'image/png')
252
+ self.send_header('Content-Disposition', 'attachment; filename="denoised.png"')
253
  self.end_headers()
254
  with open(output_path, 'rb') as f:
255
  self.wfile.write(f.read())
 
272
  swagger_html = """<!DOCTYPE html>
273
  <html>
274
  <head>
275
+ <title>AI Image Processing - API Documentation</title>
276
  <link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
277
  <style>
278
  body { margin: 0; padding: 0; }
 
304
  openapi_spec = {
305
  "openapi": "3.1.0",
306
  "info": {
307
+ "title": "AI Image Processing API",
308
+ "description": "Comprehensive AI-powered image processing API.\n\n**Features:**\n- Image enhancement and upscaling (Real-ESRGAN)\n- Background removal (BiRefNet)\n- Noise reduction (OpenCV NLM)\n\n**Note:** This is a preview deployment. Deploy to Hugging Face Spaces for full AI processing.",
309
+ "version": "2.0.0"
 
310
  },
311
  "servers": [{"url": "/", "description": "Current server"}],
312
  "paths": {
313
  "/enhance": {
314
  "post": {
315
+ "summary": "Enhance image (upscale)",
316
+ "description": "Upscale and enhance image quality using Real-ESRGAN.",
317
+ "parameters": [{"name": "scale", "in": "query", "schema": {"type": "integer", "default": 4, "enum": [2, 4]}}],
318
+ "requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
319
+ "responses": {"200": {"description": "Enhanced image"}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
  },
322
+ "/remove-background": {
323
  "post": {
324
+ "summary": "Remove background",
325
+ "description": "Remove background from image using BiRefNet AI model.",
326
+ "parameters": [{"name": "bgcolor", "in": "query", "schema": {"type": "string", "default": "transparent"}}],
327
+ "requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
328
+ "responses": {"200": {"description": "Image with background removed"}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
330
  },
331
+ "/denoise": {
332
+ "post": {
333
+ "summary": "Reduce noise",
334
+ "description": "Reduce image noise using Non-Local Means Denoising.",
335
+ "parameters": [{"name": "strength", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 30}}],
336
+ "requestBody": {"required": True, "content": {"multipart/form-data": {"schema": {"type": "object", "properties": {"file": {"type": "string", "format": "binary"}}}}}},
337
+ "responses": {"200": {"description": "Denoised image"}}
 
 
 
 
338
  }
339
  },
340
+ "/health": {"get": {"summary": "Health check", "responses": {"200": {"description": "API status"}}}},
341
+ "/model-info": {"get": {"summary": "Model information", "responses": {"200": {"description": "Model details"}}}}
 
 
 
 
 
 
 
 
 
 
 
342
  }
343
  }
344
  self.send_json(openapi_spec)
 
364
  def run_server(port=5000):
365
  server_address = ('0.0.0.0', port)
366
  httpd = HTTPServer(server_address, APIHandler)
367
+ print(f"AI Image Processing API running at http://0.0.0.0:{port}")
368
  print(f"API Documentation: http://0.0.0.0:{port}/docs")
369
  print(f"Frontend: http://0.0.0.0:{port}/")
370
+ print("\nNote: This is preview mode using simple processing.")
371
+ print("Deploy to Hugging Face Spaces for full AI features.")
372
  httpd.serve_forever()
373
 
374
  if __name__ == "__main__":
replit.md CHANGED
@@ -1,13 +1,15 @@
1
- # AI Image Enhancer
2
 
3
  ## Overview
4
- An AI-powered image enhancement API using Real-ESRGAN for image super-resolution. The app includes:
 
 
 
5
  - FastAPI backend with automatic Swagger API documentation
6
- - Simple web frontend for testing image uploads
7
- - Designed for deployment to Hugging Face Spaces
8
 
9
  ## Current State
10
- - **Local Preview**: Running with simple upscaling (no AI model due to size constraints)
11
  - **Full AI Mode**: Available when deployed to Hugging Face Spaces
12
 
13
  ## Project Structure
@@ -22,7 +24,7 @@ An AI-powered image enhancement API using Real-ESRGAN for image super-resolution
22
  ├── Dockerfile # Docker configuration for HF Spaces
23
  ├── README.md # Hugging Face Spaces configuration
24
  ├── uploads/ # Temporary upload storage
25
- └── outputs/ # Enhanced image outputs
26
  ```
27
 
28
  ## API Endpoints
@@ -30,16 +32,25 @@ An AI-powered image enhancement API using Real-ESRGAN for image super-resolution
30
  - `GET /docs` - Swagger API documentation
31
  - `GET /health` - Health check
32
  - `GET /model-info` - Model information
33
- - `POST /enhance` - Enhance image (returns PNG file)
34
- - `POST /enhance/base64` - Enhance image (returns base64 JSON)
 
 
 
 
35
 
36
  ## Deploying to Hugging Face Spaces
37
  1. Create a new Space on Hugging Face
38
  2. Select "Docker" as the SDK
39
  3. Upload all files: `app.py`, `enhancer.py`, `templates/`, `requirements.txt`, `Dockerfile`, `README.md`
40
- 4. The Space will auto-build the container and download the Real-ESRGAN model
41
 
42
  ## Recent Changes
 
 
 
 
 
43
  - 2025-11-28: Initial creation of AI Image Enhancer API
44
  - FastAPI backend with Swagger docs
45
  - Real-ESRGAN integration for Hugging Face
 
1
+ # AI Image Processing
2
 
3
  ## Overview
4
+ An AI-powered image processing API with multiple features:
5
+ - Image enhancement/upscaling using Real-ESRGAN
6
+ - Background removal using BiRefNet via rembg
7
+ - Noise reduction using OpenCV Non-Local Means Denoising
8
  - FastAPI backend with automatic Swagger API documentation
9
+ - Simple web frontend for testing
 
10
 
11
  ## Current State
12
+ - **Local Preview**: Running with simple processing (no heavy AI models due to size constraints)
13
  - **Full AI Mode**: Available when deployed to Hugging Face Spaces
14
 
15
  ## Project Structure
 
24
  ├── Dockerfile # Docker configuration for HF Spaces
25
  ├── README.md # Hugging Face Spaces configuration
26
  ├── uploads/ # Temporary upload storage
27
+ └── outputs/ # Processed image outputs
28
  ```
29
 
30
  ## API Endpoints
 
32
  - `GET /docs` - Swagger API documentation
33
  - `GET /health` - Health check
34
  - `GET /model-info` - Model information
35
+ - `POST /enhance` - Enhance/upscale image (Real-ESRGAN)
36
+ - `POST /enhance/base64` - Enhance image (returns base64)
37
+ - `POST /remove-background` - Remove image background (BiRefNet)
38
+ - `POST /remove-background/base64` - Remove background (returns base64)
39
+ - `POST /denoise` - Reduce image noise (OpenCV NLM)
40
+ - `POST /denoise/base64` - Denoise image (returns base64)
41
 
42
  ## Deploying to Hugging Face Spaces
43
  1. Create a new Space on Hugging Face
44
  2. Select "Docker" as the SDK
45
  3. Upload all files: `app.py`, `enhancer.py`, `templates/`, `requirements.txt`, `Dockerfile`, `README.md`
46
+ 4. The Space will auto-build the container and download AI models
47
 
48
  ## Recent Changes
49
+ - 2025-11-28: Added background removal and noise reduction features
50
+ - BiRefNet integration via rembg for background removal
51
+ - OpenCV Non-Local Means Denoising
52
+ - Updated frontend with feature tabs
53
+ - Updated API documentation
54
  - 2025-11-28: Initial creation of AI Image Enhancer API
55
  - FastAPI backend with Swagger docs
56
  - Real-ESRGAN integration for Hugging Face
requirements.txt CHANGED
@@ -3,9 +3,11 @@ uvicorn[standard]==0.27.0
3
  python-multipart==0.0.6
4
  pillow==10.2.0
5
  numpy==1.26.3
 
6
  torch>=2.1.0
7
  torchvision>=0.16.0
8
  realesrgan==0.3.0
9
  basicsr==1.4.2
10
  gfpgan==1.3.8
11
- pillow
 
 
3
  python-multipart==0.0.6
4
  pillow==10.2.0
5
  numpy==1.26.3
6
+ opencv-python-headless==4.9.0.80
7
  torch>=2.1.0
8
  torchvision>=0.16.0
9
  realesrgan==0.3.0
10
  basicsr==1.4.2
11
  gfpgan==1.3.8
12
+ rembg==2.0.50
13
+ onnxruntime==1.17.0
templates/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI Image Enhancer</title>
7
  <style>
8
  * {
9
  box-sizing: border-box;
@@ -114,7 +114,7 @@
114
  color: #888;
115
  }
116
 
117
- select {
118
  width: 100%;
119
  padding: 12px;
120
  border-radius: 8px;
@@ -124,7 +124,46 @@
124
  font-size: 1rem;
125
  }
126
 
127
- .enhance-btn {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  width: 100%;
129
  padding: 15px;
130
  margin-top: 20px;
@@ -138,12 +177,12 @@
138
  transition: transform 0.2s, box-shadow 0.2s;
139
  }
140
 
141
- .enhance-btn:hover:not(:disabled) {
142
  transform: translateY(-2px);
143
  box-shadow: 0 10px 30px rgba(0, 210, 255, 0.3);
144
  }
145
 
146
- .enhance-btn:disabled {
147
  opacity: 0.5;
148
  cursor: not-allowed;
149
  }
@@ -271,19 +310,36 @@
271
  color: #888;
272
  font-size: 0.9rem;
273
  }
 
 
 
 
 
 
 
 
 
 
 
274
  </style>
275
  </head>
276
  <body>
277
  <div class="container">
278
  <header>
279
- <h1>AI Image Enhancer</h1>
280
- <p class="subtitle">Enhance your images using Real-ESRGAN deep learning model</p>
281
  <div class="api-link">
282
  <a href="/docs" target="_blank">View API Documentation</a>
283
  </div>
284
  </header>
285
 
286
  <section class="upload-section">
 
 
 
 
 
 
287
  <div class="drop-zone" id="dropZone">
288
  <div class="drop-zone-icon">📷</div>
289
  <p>Drag & drop an image here or click to select</p>
@@ -291,25 +347,60 @@
291
  </div>
292
  <input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,image/webp,image/bmp">
293
 
294
- <div class="options">
295
- <div class="option-group">
296
- <label for="scale">Upscale Factor</label>
297
- <select id="scale">
298
- <option value="2">2x - Double resolution</option>
299
- <option value="4" selected>4x - Quadruple resolution</option>
300
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  </div>
302
  </div>
303
 
304
- <button class="enhance-btn" id="enhanceBtn" disabled>Enhance Image</button>
305
 
306
  <div class="error" id="error"></div>
307
  </section>
308
 
309
  <div class="loading" id="loading">
310
  <div class="spinner"></div>
311
- <p>Enhancing your image with AI magic...</p>
312
- <p><small>This may take a moment for larger images</small></p>
313
  </div>
314
 
315
  <section class="results-section" id="results">
@@ -318,32 +409,32 @@
318
  <h3>Original</h3>
319
  <img id="originalImg" src="" alt="Original image">
320
  </div>
321
- <div class="image-box">
322
- <h3>Enhanced</h3>
323
- <img id="enhancedImg" src="" alt="Enhanced image">
324
- <a href="#" class="download-btn" id="downloadBtn" download="enhanced_image.png">Download Enhanced Image</a>
325
  </div>
326
  </div>
327
  </section>
328
 
329
  <section class="info-section">
330
- <h2>About the Model</h2>
331
  <div class="info-grid">
332
  <div class="info-item">
333
- <h4>Real-ESRGAN x4plus</h4>
334
- <p>State-of-the-art image super-resolution model</p>
335
  </div>
336
  <div class="info-item">
337
- <h4>Deep Learning</h4>
338
- <p>Uses RRDB architecture for high-quality results</p>
339
  </div>
340
  <div class="info-item">
341
- <h4>Multi-purpose</h4>
342
- <p>Works on photos, artwork, and various image types</p>
343
  </div>
344
  <div class="info-item">
345
  <h4>API Access</h4>
346
- <p>RESTful API with Swagger documentation at /docs</p>
347
  </div>
348
  </div>
349
  </section>
@@ -352,16 +443,56 @@
352
  <script>
353
  const dropZone = document.getElementById('dropZone');
354
  const fileInput = document.getElementById('fileInput');
355
- const enhanceBtn = document.getElementById('enhanceBtn');
356
  const loading = document.getElementById('loading');
 
357
  const results = document.getElementById('results');
358
  const error = document.getElementById('error');
359
  const originalImg = document.getElementById('originalImg');
360
- const enhancedImg = document.getElementById('enhancedImg');
361
  const downloadBtn = document.getElementById('downloadBtn');
362
- const scaleSelect = document.getElementById('scale');
 
 
 
 
 
363
 
364
  let selectedFile = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  dropZone.addEventListener('click', () => fileInput.click());
367
 
@@ -396,7 +527,7 @@
396
  }
397
 
398
  selectedFile = file;
399
- enhanceBtn.disabled = false;
400
  dropZone.innerHTML = `
401
  <div class="drop-zone-icon">✅</div>
402
  <p><strong>${file.name}</strong></p>
@@ -412,35 +543,68 @@
412
  hideError();
413
  }
414
 
415
- enhanceBtn.addEventListener('click', async () => {
416
  if (!selectedFile) return;
417
 
418
- const scale = scaleSelect.value;
419
  const formData = new FormData();
420
  formData.append('file', selectedFile);
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  loading.classList.add('show');
423
  results.classList.remove('show');
424
- enhanceBtn.disabled = true;
425
  hideError();
426
 
427
  try {
428
- const response = await fetch(`/enhance?scale=${scale}`, {
429
  method: 'POST',
430
  body: formData
431
  });
432
 
433
  if (!response.ok) {
434
  const errorData = await response.json();
435
- throw new Error(errorData.detail || 'Enhancement failed');
436
  }
437
 
438
  const blob = await response.blob();
439
  const imageUrl = URL.createObjectURL(blob);
440
 
441
- enhancedImg.src = imageUrl;
442
  downloadBtn.href = imageUrl;
443
 
 
 
 
 
444
  loading.classList.remove('show');
445
  results.classList.add('show');
446
 
@@ -449,7 +613,7 @@
449
  loading.classList.remove('show');
450
  }
451
 
452
- enhanceBtn.disabled = false;
453
  });
454
 
455
  function showError(message) {
@@ -460,6 +624,8 @@
460
  function hideError() {
461
  error.classList.remove('show');
462
  }
 
 
463
  </script>
464
  </body>
465
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Image Processing</title>
7
  <style>
8
  * {
9
  box-sizing: border-box;
 
114
  color: #888;
115
  }
116
 
117
+ select, input[type="text"] {
118
  width: 100%;
119
  padding: 12px;
120
  border-radius: 8px;
 
124
  font-size: 1rem;
125
  }
126
 
127
+ .feature-tabs {
128
+ display: flex;
129
+ gap: 10px;
130
+ margin-bottom: 20px;
131
+ flex-wrap: wrap;
132
+ }
133
+
134
+ .feature-tab {
135
+ padding: 12px 24px;
136
+ border: none;
137
+ border-radius: 8px;
138
+ background: rgba(255, 255, 255, 0.1);
139
+ color: #888;
140
+ cursor: pointer;
141
+ transition: all 0.3s;
142
+ font-size: 1rem;
143
+ }
144
+
145
+ .feature-tab:hover {
146
+ background: rgba(255, 255, 255, 0.15);
147
+ }
148
+
149
+ .feature-tab.active {
150
+ background: linear-gradient(90deg, #00d2ff, #3a7bd5);
151
+ color: #fff;
152
+ }
153
+
154
+ .feature-options {
155
+ display: none;
156
+ padding: 15px;
157
+ background: rgba(255, 255, 255, 0.03);
158
+ border-radius: 8px;
159
+ margin-bottom: 15px;
160
+ }
161
+
162
+ .feature-options.active {
163
+ display: block;
164
+ }
165
+
166
+ .process-btn {
167
  width: 100%;
168
  padding: 15px;
169
  margin-top: 20px;
 
177
  transition: transform 0.2s, box-shadow 0.2s;
178
  }
179
 
180
+ .process-btn:hover:not(:disabled) {
181
  transform: translateY(-2px);
182
  box-shadow: 0 10px 30px rgba(0, 210, 255, 0.3);
183
  }
184
 
185
+ .process-btn:disabled {
186
  opacity: 0.5;
187
  cursor: not-allowed;
188
  }
 
310
  color: #888;
311
  font-size: 0.9rem;
312
  }
313
+
314
+ .checkerboard {
315
+ background-image:
316
+ linear-gradient(45deg, #666 25%, transparent 25%),
317
+ linear-gradient(-45deg, #666 25%, transparent 25%),
318
+ linear-gradient(45deg, transparent 75%, #666 75%),
319
+ linear-gradient(-45deg, transparent 75%, #666 75%);
320
+ background-size: 20px 20px;
321
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
322
+ background-color: #444;
323
+ }
324
  </style>
325
  </head>
326
  <body>
327
  <div class="container">
328
  <header>
329
+ <h1>AI Image Processing</h1>
330
+ <p class="subtitle">Enhance, remove backgrounds, and denoise your images with AI</p>
331
  <div class="api-link">
332
  <a href="/docs" target="_blank">View API Documentation</a>
333
  </div>
334
  </header>
335
 
336
  <section class="upload-section">
337
+ <div class="feature-tabs">
338
+ <button class="feature-tab active" data-feature="enhance">Enhance</button>
339
+ <button class="feature-tab" data-feature="remove-bg">Remove Background</button>
340
+ <button class="feature-tab" data-feature="denoise">Denoise</button>
341
+ </div>
342
+
343
  <div class="drop-zone" id="dropZone">
344
  <div class="drop-zone-icon">📷</div>
345
  <p>Drag & drop an image here or click to select</p>
 
347
  </div>
348
  <input type="file" id="fileInput" accept="image/png,image/jpeg,image/jpg,image/webp,image/bmp">
349
 
350
+ <div id="enhanceOptions" class="feature-options active">
351
+ <div class="options">
352
+ <div class="option-group">
353
+ <label for="scale">Upscale Factor</label>
354
+ <select id="scale">
355
+ <option value="2">2x - Double resolution</option>
356
+ <option value="4" selected>4x - Quadruple resolution</option>
357
+ </select>
358
+ </div>
359
+ </div>
360
+ </div>
361
+
362
+ <div id="removeBgOptions" class="feature-options">
363
+ <div class="options">
364
+ <div class="option-group">
365
+ <label for="bgcolor">Background Color</label>
366
+ <select id="bgcolor">
367
+ <option value="transparent" selected>Transparent</option>
368
+ <option value="white">White</option>
369
+ <option value="black">Black</option>
370
+ <option value="custom">Custom Color</option>
371
+ </select>
372
+ </div>
373
+ <div class="option-group" id="customColorGroup" style="display: none;">
374
+ <label for="customColor">Custom Color (Hex)</label>
375
+ <input type="text" id="customColor" placeholder="#FF0000" value="#FFFFFF">
376
+ </div>
377
+ </div>
378
+ </div>
379
+
380
+ <div id="denoiseOptions" class="feature-options">
381
+ <div class="options">
382
+ <div class="option-group">
383
+ <label for="strength">Denoise Strength</label>
384
+ <select id="strength">
385
+ <option value="5">Light (5) - Preserves details</option>
386
+ <option value="10" selected>Medium (10) - Balanced</option>
387
+ <option value="15">Strong (15) - More smoothing</option>
388
+ <option value="20">Very Strong (20)</option>
389
+ <option value="30">Maximum (30)</option>
390
+ </select>
391
+ </div>
392
  </div>
393
  </div>
394
 
395
+ <button class="process-btn" id="processBtn" disabled>Process Image</button>
396
 
397
  <div class="error" id="error"></div>
398
  </section>
399
 
400
  <div class="loading" id="loading">
401
  <div class="spinner"></div>
402
+ <p id="loadingText">Processing your image with AI...</p>
403
+ <p><small>This may take a moment</small></p>
404
  </div>
405
 
406
  <section class="results-section" id="results">
 
409
  <h3>Original</h3>
410
  <img id="originalImg" src="" alt="Original image">
411
  </div>
412
+ <div class="image-box" id="resultBox">
413
+ <h3 id="resultLabel">Processed</h3>
414
+ <img id="processedImg" src="" alt="Processed image">
415
+ <a href="#" class="download-btn" id="downloadBtn" download="processed_image.png">Download Image</a>
416
  </div>
417
  </div>
418
  </section>
419
 
420
  <section class="info-section">
421
+ <h2>Available Features</h2>
422
  <div class="info-grid">
423
  <div class="info-item">
424
+ <h4>Image Enhancement</h4>
425
+ <p>Upscale images 2x-4x using Real-ESRGAN AI model</p>
426
  </div>
427
  <div class="info-item">
428
+ <h4>Background Removal</h4>
429
+ <p>Remove backgrounds using BiRefNet deep learning model</p>
430
  </div>
431
  <div class="info-item">
432
+ <h4>Noise Reduction</h4>
433
+ <p>Reduce image noise with advanced denoising algorithms</p>
434
  </div>
435
  <div class="info-item">
436
  <h4>API Access</h4>
437
+ <p>Full RESTful API with Swagger documentation at /docs</p>
438
  </div>
439
  </div>
440
  </section>
 
443
  <script>
444
  const dropZone = document.getElementById('dropZone');
445
  const fileInput = document.getElementById('fileInput');
446
+ const processBtn = document.getElementById('processBtn');
447
  const loading = document.getElementById('loading');
448
+ const loadingText = document.getElementById('loadingText');
449
  const results = document.getElementById('results');
450
  const error = document.getElementById('error');
451
  const originalImg = document.getElementById('originalImg');
452
+ const processedImg = document.getElementById('processedImg');
453
  const downloadBtn = document.getElementById('downloadBtn');
454
+ const resultBox = document.getElementById('resultBox');
455
+ const resultLabel = document.getElementById('resultLabel');
456
+
457
+ const featureTabs = document.querySelectorAll('.feature-tab');
458
+ const bgcolorSelect = document.getElementById('bgcolor');
459
+ const customColorGroup = document.getElementById('customColorGroup');
460
 
461
  let selectedFile = null;
462
+ let currentFeature = 'enhance';
463
+
464
+ featureTabs.forEach(tab => {
465
+ tab.addEventListener('click', () => {
466
+ featureTabs.forEach(t => t.classList.remove('active'));
467
+ tab.classList.add('active');
468
+ currentFeature = tab.dataset.feature;
469
+
470
+ document.querySelectorAll('.feature-options').forEach(opt => opt.classList.remove('active'));
471
+
472
+ if (currentFeature === 'enhance') {
473
+ document.getElementById('enhanceOptions').classList.add('active');
474
+ } else if (currentFeature === 'remove-bg') {
475
+ document.getElementById('removeBgOptions').classList.add('active');
476
+ } else if (currentFeature === 'denoise') {
477
+ document.getElementById('denoiseOptions').classList.add('active');
478
+ }
479
+
480
+ updateButtonText();
481
+ });
482
+ });
483
+
484
+ bgcolorSelect.addEventListener('change', () => {
485
+ customColorGroup.style.display = bgcolorSelect.value === 'custom' ? 'block' : 'none';
486
+ });
487
+
488
+ function updateButtonText() {
489
+ const texts = {
490
+ 'enhance': 'Enhance Image',
491
+ 'remove-bg': 'Remove Background',
492
+ 'denoise': 'Denoise Image'
493
+ };
494
+ processBtn.textContent = texts[currentFeature] || 'Process Image';
495
+ }
496
 
497
  dropZone.addEventListener('click', () => fileInput.click());
498
 
 
527
  }
528
 
529
  selectedFile = file;
530
+ processBtn.disabled = false;
531
  dropZone.innerHTML = `
532
  <div class="drop-zone-icon">✅</div>
533
  <p><strong>${file.name}</strong></p>
 
543
  hideError();
544
  }
545
 
546
+ processBtn.addEventListener('click', async () => {
547
  if (!selectedFile) return;
548
 
 
549
  const formData = new FormData();
550
  formData.append('file', selectedFile);
551
 
552
+ let endpoint = '/enhance';
553
+ let params = new URLSearchParams();
554
+
555
+ if (currentFeature === 'enhance') {
556
+ const scale = document.getElementById('scale').value;
557
+ params.append('scale', scale);
558
+ loadingText.textContent = 'Enhancing your image with AI...';
559
+ resultLabel.textContent = 'Enhanced';
560
+ } else if (currentFeature === 'remove-bg') {
561
+ endpoint = '/remove-background';
562
+ let bgcolor = bgcolorSelect.value;
563
+ if (bgcolor === 'custom') {
564
+ bgcolor = document.getElementById('customColor').value;
565
+ }
566
+ params.append('bgcolor', bgcolor);
567
+ loadingText.textContent = 'Removing background with AI...';
568
+ resultLabel.textContent = 'Background Removed';
569
+ resultBox.classList.add('checkerboard');
570
+ } else if (currentFeature === 'denoise') {
571
+ endpoint = '/denoise';
572
+ const strength = document.getElementById('strength').value;
573
+ params.append('strength', strength);
574
+ loadingText.textContent = 'Reducing noise in your image...';
575
+ resultLabel.textContent = 'Denoised';
576
+ }
577
+
578
+ if (currentFeature !== 'remove-bg') {
579
+ resultBox.classList.remove('checkerboard');
580
+ }
581
+
582
  loading.classList.add('show');
583
  results.classList.remove('show');
584
+ processBtn.disabled = true;
585
  hideError();
586
 
587
  try {
588
+ const response = await fetch(`${endpoint}?${params.toString()}`, {
589
  method: 'POST',
590
  body: formData
591
  });
592
 
593
  if (!response.ok) {
594
  const errorData = await response.json();
595
+ throw new Error(errorData.detail || 'Processing failed');
596
  }
597
 
598
  const blob = await response.blob();
599
  const imageUrl = URL.createObjectURL(blob);
600
 
601
+ processedImg.src = imageUrl;
602
  downloadBtn.href = imageUrl;
603
 
604
+ const filename = currentFeature === 'enhance' ? 'enhanced' :
605
+ currentFeature === 'remove-bg' ? 'nobg' : 'denoised';
606
+ downloadBtn.download = `${filename}_${selectedFile.name.split('.')[0]}.png`;
607
+
608
  loading.classList.remove('show');
609
  results.classList.add('show');
610
 
 
613
  loading.classList.remove('show');
614
  }
615
 
616
+ processBtn.disabled = false;
617
  });
618
 
619
  function showError(message) {
 
624
  function hideError() {
625
  error.classList.remove('show');
626
  }
627
+
628
+ updateButtonText();
629
  </script>
630
  </body>
631
  </html>