withoutbg-api / app.py
jinkedon's picture
Update app.py
771763f verified
from flask import Flask, request, jsonify
from flask_cors import CORS
import base64
import io
from PIL import Image
import requests
import logging
# Import the withoutBG library
from withoutbg.core import WithoutBGOpenSource
from huggingface_hub import hf_hub_download
from pathlib import Path
import shutil
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app)
# Model directory
MODEL_DIR = Path("./models")
MODEL_DIR.mkdir(parents=True, exist_ok=True)
def _ensure_model_file(filename: str) -> Path:
"""Download model file from HuggingFace if not exists"""
target = MODEL_DIR / filename
if target.exists():
return target
logger.info(f"๐Ÿ“ฅ Downloading model file: {filename}")
downloaded = Path(hf_hub_download(repo_id="withoutbg/focus", filename=filename))
shutil.copy2(downloaded, target)
logger.info(f"โœ… Model file downloaded: {filename}")
return target
def _create_model() -> WithoutBGOpenSource:
"""Create WithoutBG model instance"""
logger.info("๐Ÿš€ Creating WithoutBG model...")
return WithoutBGOpenSource(
depth_model_path=_ensure_model_file("depth_anything_v2_vits_slim.onnx"),
isnet_model_path=_ensure_model_file("isnet.onnx"),
matting_model_path=_ensure_model_file("focus_matting_1.0.0.onnx"),
refiner_model_path=_ensure_model_file("focus_refiner_1.0.0.onnx"),
)
# Initialize the model once at startup
try:
logger.info("๐Ÿš€ Loading withoutBG model...")
model = _create_model()
logger.info("โœ… Model loaded successfully!")
except Exception as e:
logger.error(f"โŒ Failed to load model: {e}")
model = None
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# ๅ…ฑ้€šๅ‡ฆ็†: PIL Image โ†’ ่ƒŒๆ™ฏๅ‰Š้™ค โ†’ (image_data, mask_data)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def process_image(img: Image.Image, return_mask: bool = False) -> dict:
"""
่ƒŒๆ™ฏๅ‰Š้™คใ‚’ๅฎŸ่กŒใ—ใ€็ตๆžœใ‚’่พžๆ›ธใง่ฟ”ใ™ใ€‚
Returns:
{
'success': True,
'image_data': 'data:image/png;base64,...', # ่ƒŒๆ™ฏๅ‰Š้™คๆธˆใฟ็”ปๅƒ๏ผˆRGBA๏ผ‰
'mask_data': 'data:image/png;base64,...', # ใƒžใ‚นใ‚ฏ็”ปๅƒ๏ผˆreturn_mask=Trueใฎใจใ๏ผ‰
}
"""
if not model:
raise RuntimeError("Model not initialized")
logger.info(f"๐Ÿ–ผ๏ธ Image loaded: {img.size}, mode: {img.mode}")
# ่ƒŒๆ™ฏๅ‰Š้™ค
logger.info("๐Ÿ”„ Removing background with WithoutBGOpenSource...")
result = model.remove_background(img)
logger.info(f"โœ… Background removed! Result mode: {result.mode}, Size: {result.size}")
# RGBA ใซๅค‰ๆ›๏ผˆ้€้ŽPNG๏ผ‰
if result.mode != 'RGBA':
result = result.convert('RGBA')
# โ”€โ”€ ่ƒŒๆ™ฏๅ‰Š้™คๆธˆใฟ็”ปๅƒใ‚’ base64 ใ‚จใƒณใ‚ณใƒผใƒ‰ โ”€โ”€
out_buf = io.BytesIO()
result.save(out_buf, format='PNG')
out_buf.seek(0)
image_b64 = base64.b64encode(out_buf.read()).decode('utf-8')
response = {
'success': True,
'image_data': f'data:image/png;base64,{image_b64}'
}
# โ”€โ”€ ใƒžใ‚นใ‚ฏ็”ปๅƒ๏ผˆใ‚ขใƒซใƒ•ใ‚กใƒใƒฃใƒณใƒใƒซ๏ผ‰ใ‚’ base64 ใ‚จใƒณใ‚ณใƒผใƒ‰ โ”€โ”€
if return_mask:
# RGBAใฎใ‚ขใƒซใƒ•ใ‚กใƒใƒฃใƒณใƒใƒซใ‚’ใใฎใพใพใƒžใ‚นใ‚ฏใจใ—ใฆไฝฟ็”จ
# ๅ‰ๆ™ฏ=็™ฝ(255)ใ€่ƒŒๆ™ฏ=้ป’(0) ใฎใ‚ฐใƒฌใƒผใ‚นใ‚ฑใƒผใƒซ็”ปๅƒ
alpha = result.split()[3] # Aใƒใƒฃใƒณใƒใƒซๅ–ๅพ—
mask_img = alpha.convert('L') # ใ‚ฐใƒฌใƒผใ‚นใ‚ฑใƒผใƒซๅŒ–๏ผˆๅฟตใฎใŸใ‚๏ผ‰
mask_buf = io.BytesIO()
mask_img.save(mask_buf, format='PNG')
mask_buf.seek(0)
mask_b64 = base64.b64encode(mask_buf.read()).decode('utf-8')
response['mask_data'] = f'data:image/png;base64,{mask_b64}'
logger.info(f"๐ŸŽญ Mask generated: {mask_img.size}, mode: {mask_img.mode}")
return response
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# GET / ใƒ˜ใƒซใ‚นใƒใ‚งใƒƒใ‚ฏ
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/', methods=['GET'])
def health_check():
return jsonify({
'service': 'withoutBG API Server',
'status': 'healthy' if model else 'unhealthy',
'model': 'withoutBG Focus v1.0.0',
'version': '1.0.0',
'platform': 'Hugging Face Spaces',
'endpoints': [
'POST /api/remove-bg โ€“ image_url or image_base64, return_mask(opt)',
'POST /api/remove-bg-from-url โ€“ image_url, return_mask(opt)',
'POST /api/remove-bg-base64 โ€“ image_base64, return_mask(opt)',
]
})
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# POST /api/remove-bg ๏ผˆๆ—ขๅญ˜ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใƒปๅพŒๆ–นไบ’ๆ›๏ผ‰
# Body: { image_url or image_base64, return_mask(opt, default false) }
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/api/remove-bg', methods=['POST'])
def remove_background():
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
if not model:
return jsonify({'success': False, 'error': 'Model not initialized'}), 500
return_mask = bool(data.get('return_mask', False))
if 'image_url' in data:
logger.info(f"๐Ÿ“ฅ Downloading image from URL: {data['image_url']}")
r = requests.get(data['image_url'], timeout=30)
r.raise_for_status()
img = Image.open(io.BytesIO(r.content))
elif 'image_base64' in data:
logger.info("๐Ÿ“ฅ Decoding base64 image")
b64 = data['image_base64']
if ',' in b64:
b64 = b64.split(',')[1]
img = Image.open(io.BytesIO(base64.b64decode(b64)))
else:
return jsonify({'success': False, 'error': 'Either image_url or image_base64 is required'}), 400
return jsonify(process_image(img, return_mask=return_mask))
except Exception as e:
logger.error(f"โŒ Error: {e}")
import traceback; traceback.print_exc()
return jsonify({'success': False, 'error': str(e)}), 500
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# POST /api/remove-bg-from-url โ† WebๅดใŒURLใ‚’ๆธกใ™ใจใใซๅ‘ผใถ
# Body: { "image_url": "https://...", "return_mask": true }
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/api/remove-bg-from-url', methods=['POST'])
def remove_background_from_url():
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
if not model:
return jsonify({'success': False, 'error': 'Model not initialized'}), 500
if 'image_url' not in data:
return jsonify({'success': False, 'error': 'image_url is required'}), 400
return_mask = bool(data.get('return_mask', False))
logger.info(f"๐Ÿ“ฅ [from-url] Downloading image: {data['image_url']}")
r = requests.get(data['image_url'], timeout=30)
r.raise_for_status()
img = Image.open(io.BytesIO(r.content))
return jsonify(process_image(img, return_mask=return_mask))
except Exception as e:
logger.error(f"โŒ Error [from-url]: {e}")
import traceback; traceback.print_exc()
return jsonify({'success': False, 'error': str(e)}), 500
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
# POST /api/remove-bg-base64 โ† WebๅดใŒbase64ใ‚’ๆธกใ™ใจใใซๅ‘ผใถ
# Body: { "image_base64": "data:image/...;base64,...", "return_mask": true }
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/api/remove-bg-base64', methods=['POST'])
def remove_background_base64():
try:
data = request.get_json()
if not data:
return jsonify({'success': False, 'error': 'No JSON data provided'}), 400
if not model:
return jsonify({'success': False, 'error': 'Model not initialized'}), 500
if 'image_base64' not in data:
return jsonify({'success': False, 'error': 'image_base64 is required'}), 400
return_mask = bool(data.get('return_mask', False))
logger.info("๐Ÿ“ฅ [base64] Decoding base64 image")
b64 = data['image_base64']
if ',' in b64:
b64 = b64.split(',')[1]
img = Image.open(io.BytesIO(base64.b64decode(b64)))
return jsonify(process_image(img, return_mask=return_mask))
except Exception as e:
logger.error(f"โŒ Error [base64]: {e}")
import traceback; traceback.print_exc()
return jsonify({'success': False, 'error': str(e)}), 500
if __name__ == '__main__':
import os
port = int(os.environ.get('PORT', 7860))
logger.info(f"๐Ÿš€ Starting withoutBG API Server on port {port}...")
app.run(host='0.0.0.0', port=port, debug=False)