Spaces:
Paused
Paused
| import streamlit as st | |
| import easyocr | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont | |
| from groq import Groq | |
| import io | |
| import time | |
| import os | |
| st.set_page_config(page_title="Image Translator + Groq", page_icon="🌐", layout="wide") | |
| st.title("🌐 Image Translator (OCR + Xóa chữ + Dịch Groq)") | |
| # ==================== SIDEBAR ==================== | |
| with st.sidebar: | |
| st.header("🔑 Groq API") | |
| groq_api_key = st.text_input("Groq API Key", type="password", placeholder="gsk_...") | |
| model = st.selectbox("Model Groq", [ | |
| "llama-3.3-70b-versatile", | |
| "llama-3.1-8b-instant", | |
| "gemma2-9b-it" | |
| ]) | |
| st.markdown("---") | |
| source_lang = st.selectbox("Ngôn ngữ nguồn", [ | |
| "English", "Tiếng Việt", "Chinese", "Japanese", "Korean", "Thai", "Other" | |
| ]) | |
| target_lang = st.selectbox("Ngôn ngữ đích", [ | |
| "Vietnamese", "English", "Chinese", "Japanese", "Korean", "Thai", "French", "Spanish" | |
| ]) | |
| padding = st.slider("Padding mask", 5, 30, 12) | |
| font_size = st.slider("Cỡ chữ", 16, 50, 28) | |
| # ==================== CACHE OCR ==================== | |
| def get_reader(): | |
| # Chỉ load English + Vietnamese để nhẹ hơn | |
| return easyocr.Reader(['en', 'vi'], gpu=False, verbose=False) | |
| def create_mask(img, boxes, pad=12): | |
| mask = np.zeros(img.shape[:2], dtype=np.uint8) | |
| for box in boxes: | |
| pts = np.array(box, np.int32) | |
| x1 = max(0, int(np.min(pts[:,0]) - pad)) | |
| y1 = max(0, int(np.min(pts[:,1]) - pad)) | |
| x2 = min(img.shape[1], int(np.max(pts[:,0]) + pad)) | |
| y2 = min(img.shape[0], int(np.max(pts[:,1]) + pad)) | |
| cv2.rectangle(mask, (x1, y1), (x2, y2), 255, -1) | |
| return mask | |
| def translate_texts(texts, target, api_key, model_name): | |
| if not api_key: | |
| return [f"[Cần API Key] {t}" for t in texts] | |
| client = Groq(api_key=api_key) | |
| results = [] | |
| for text in texts: | |
| if not text.strip(): | |
| results.append("") | |
| continue | |
| try: | |
| prompt = f"Translate this text to {target}. Only return the translation, nothing else:\n\n{text}" | |
| res = client.chat.completions.create( | |
| model=model_name, | |
| messages=[{"role": "user", "content": prompt}], | |
| temperature=0.1, | |
| max_tokens=300 | |
| ) | |
| results.append(res.choices[0].message.content.strip()) | |
| except Exception as e: | |
| results.append(f"[Lỗi] {str(e)[:40]}") | |
| return results | |
| def draw_text(img, boxes, texts, size=28): | |
| pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) | |
| draw = ImageDraw.Draw(pil) | |
| try: | |
| font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size) | |
| except: | |
| font = ImageFont.load_default() | |
| for box, text in zip(boxes, texts): | |
| if not text or text.startswith("["): | |
| continue | |
| pts = np.array(box) | |
| x, y = int(np.min(pts[:,0])), int(np.min(pts[:,1])) | |
| bbox = draw.textbbox((x, y), text, font=font) | |
| draw.rectangle([bbox[0]-4, bbox[1]-2, bbox[2]+4, bbox[3]+2], fill=(255,255,255)) | |
| draw.text((x, y), text, fill=(0,0,0), font=font) | |
| return cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR) | |
| # ==================== MAIN ==================== | |
| uploaded = st.file_uploader("Upload ảnh", type=["jpg", "jpeg", "png", "webp"]) | |
| if uploaded: | |
| file_bytes = np.asarray(bytearray(uploaded.read()), dtype=np.uint8) | |
| image = cv2.imdecode(file_bytes, 1) | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| st.subheader("Ảnh gốc") | |
| st.image(cv2.cvtColor(image, cv2.COLOR_BGR2RGB), use_container_width=True) | |
| if st.button("🚀 Bắt đầu dịch", type="primary", use_container_width=True): | |
| with st.spinner("Đang xử lý..."): | |
| reader = get_reader() | |
| results = reader.readtext(image) | |
| if not results: | |
| st.warning("Không tìm thấy chữ") | |
| st.stop() | |
| boxes = [r[0] for r in results] | |
| texts = [r[1] for r in results] | |
| mask = create_mask(image, boxes, padding) | |
| cleaned = cv2.inpaint(image, mask, 7, cv2.INPAINT_TELEA) | |
| translated = translate_texts(texts, target_lang, groq_api_key, model) | |
| final = draw_text(cleaned, boxes, translated, font_size) | |
| with col2: | |
| st.subheader("Đã xóa chữ") | |
| st.image(cv2.cvtColor(cleaned, cv2.COLOR_BGR2RGB), use_container_width=True) | |
| with col3: | |
| st.subheader("Đã dịch") | |
| st.image(cv2.cvtColor(final, cv2.COLOR_BGR2RGB), use_container_width=True) | |
| st.dataframe({ | |
| "#": list(range(1, len(texts)+1)), | |
| "Gốc": texts, | |
| "Dịch": translated | |
| }, use_container_width=True) | |
| buf = io.BytesIO() | |
| Image.fromarray(cv2.cvtColor(final, cv2.COLOR_BGR2RGB)).save(buf, "PNG") | |
| st.download_button("⬇️ Tải ảnh đã dịch", buf.getvalue(), "translated.png", "image/png") | |
| else: | |
| st.info("Upload ảnh rồi nhấn nút xanh để bắt đầu") |