kkkk / app.py
rtgfreuui's picture
kk
90b9784 verified
Raw
History Blame Contribute Delete
5.52 kB
import gradio as gr
import easyocr
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import math
from sklearn.cluster import KMeans
import urllib.request
import os
# Initialize EasyOCR reader (Set to CPU for free web hosting)
print("Initializing EasyOCR...")
reader = easyocr.Reader(['en'], gpu=False)
# Download a default scalable font if it doesn't exist
FONT_PATH = "Roboto-Regular.ttf"
if not os.path.exists(FONT_PATH):
print("Downloading default font...")
urllib.request.urlretrieve(
"https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf",
FONT_PATH
)
def get_text_color(crop_img, mask):
pixels = crop_img.reshape((-1, 3))
mask_pixels = mask.reshape((-1))
text_pixels = pixels[mask_pixels > 0]
if len(text_pixels) == 0:
return (0, 0, 0)
kmeans = KMeans(n_clusters=1, n_init=10)
kmeans.fit(text_pixels)
dominant_color = kmeans.cluster_centers_[0]
return tuple(map(int, dominant_color))
def process_image(image, old_text, new_text):
if image is None or not old_text or not new_text:
return image, "Please provide an image and both text fields."
img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
img_h, img_w = img_cv.shape[:2]
results = reader.readtext(img_cv)
target_bbox = None
for (bbox, text, prob) in results:
if old_text.lower() in text.lower():
target_bbox = bbox
break
if not target_bbox:
return image, f"Text '{old_text}' not found in the image."
(tl, tr, br, bl) = target_bbox
tl, tr, br, bl = (int(tl[0]), int(tl[1])), (int(tr[0]), int(tr[1])), (int(br[0]), int(br[1])), (int(bl[0]), int(bl[1]))
dx = tr[0] - tl[0]
dy = tr[1] - tl[1]
angle = math.degrees(math.atan2(dy, dx))
width = int(math.hypot(dx, dy))
height = int(math.hypot(tl[0] - bl[0], tl[1] - bl[1]))
full_mask = np.zeros((img_h, img_w), dtype=np.uint8)
pts = np.array([tl, tr, br, bl], dtype=np.int32)
cv2.fillPoly(full_mask, [pts], 255)
x_coords, y_coords = [p[0] for p in pts], [p[1] for p in pts]
x_min, x_max = max(0, min(x_coords)), min(img_w, max(x_coords))
y_min, y_max = max(0, min(y_coords)), min(img_h, max(y_coords))
crop_img = img_cv[y_min:y_max, x_min:x_max]
gray_crop = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
_, text_mask_crop = cv2.threshold(gray_crop, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
text_color_bgr = get_text_color(crop_img, text_mask_crop)
text_color_rgb = (text_color_bgr[2], text_color_bgr[1], text_color_bgr[0])
kernel = np.ones((3,3), np.uint8)
text_mask_crop = cv2.dilate(text_mask_crop, kernel, iterations=1)
precise_full_mask = np.zeros((img_h, img_w), dtype=np.uint8)
precise_full_mask[y_min:y_max, x_min:x_max] = text_mask_crop
precise_full_mask = cv2.bitwise_and(precise_full_mask, full_mask)
inpainted_img_cv = cv2.inpaint(img_cv, precise_full_mask, inpaintRadius=3, flags=cv2.INPAINT_NS)
inpainted_img_rgb = cv2.cvtColor(inpainted_img_cv, cv2.COLOR_BGR2RGB)
final_image = Image.fromarray(inpainted_img_rgb)
txt_overlay = Image.new('RGBA', final_image.size, (255, 255, 255, 0))
draw = ImageDraw.Draw(txt_overlay)
font_size = 10
font = ImageFont.truetype(FONT_PATH, font_size)
while True:
bbox = draw.textbbox((0, 0), new_text, font=font)
if bbox[3] - bbox[1] >= height * 0.8:
break
font_size += 1
font = ImageFont.truetype(FONT_PATH, font_size)
text_bbox = draw.textbbox((0, 0), new_text, font=font)
text_w = text_bbox[2] - text_bbox[0]
text_h = text_bbox[3] - text_bbox[1]
text_img = Image.new('RGBA', (text_w, text_h), (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_img)
text_draw.text((0, 0), new_text, font=font, fill=text_color_rgb)
rotated_text_img = text_img.rotate(-angle, expand=True, resample=Image.BICUBIC)
center_x, center_y = sum(x_coords) // 4, sum(y_coords) // 4
paste_x = center_x - (rotated_text_img.width // 2)
paste_y = center_y - (rotated_text_img.height // 2)
txt_overlay.paste(rotated_text_img, (paste_x, paste_y), rotated_text_img)
final_image = Image.alpha_composite(final_image.convert('RGBA'), txt_overlay).convert('RGB')
return final_image, "Success!"
with gr.Blocks(theme=gr.themes.Soft()) as app:
gr.Markdown("# 🪄 Seamless Text-in-Image Replacement AI")
gr.Markdown("Upload an image, specify the text you want to replace, and enter the new text. The AI will erase the old text, reconstruct the background, and render the new text matching the original color, size, and angle.")
with gr.Row():
with gr.Column():
image_input = gr.Image(type="pil", label="Upload Image")
old_text_input = gr.Textbox(label="Old Text (to remove)", placeholder="e.g., Trisoy")
new_text_input = gr.Textbox(label="New Text (to insert)", placeholder="e.g., Ridoy")
submit_btn = gr.Button("Replace Text", variant="primary")
with gr.Column():
image_output = gr.Image(type="pil", label="Result Image")
status_output = gr.Textbox(label="Status", interactive=False)
submit_btn.click(
fn=process_image,
inputs=[image_input, old_text_input, new_text_input],
outputs=[image_output, status_output]
)
if __name__ == "__main__":
app.launch()