Spaces:
Sleeping
Sleeping
Download app.py from Gulzhanatali/SafeAd_AI: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/spaces/Gulzhanatali/SafeAd_AI/resolve/main/app.py
- Command line
-
hf download hf://spaces/Gulzhanatali/SafeAd_AI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Gulzhanatali/SafeAd_AI/resolve/main/app.py
18.8 kB
| import gradio as gr | |
| from transformers import pipeline | |
| from PIL import Image, ImageOps, ImageEnhance | |
| import numpy as np | |
| # опционалды кітапханалар | |
| try: | |
| import cv2 | |
| cv2_available = True | |
| except Exception: | |
| cv2_available = False | |
| try: | |
| import pytesseract | |
| pytesseract_available = True | |
| except Exception: | |
| pytesseract_available = False | |
| # Жеңіл мәтін классификаторы (спам/жарнама анықтау) | |
| classifier = None | |
| classifier_err = None | |
| try: | |
| classifier = pipeline("text-classification", model="mrm8488/bert-tiny-finetuned-sms-spam-detection") | |
| except Exception as e: | |
| classifier_err = str(e) | |
| # OCR pipeline (балама: TroCR) | |
| ocr_pipeline = None | |
| ocr_err = None | |
| try: | |
| ocr_pipeline = pipeline("image-to-text", model="microsoft/trocr-small-printed") | |
| except Exception as e: | |
| ocr_err = str(e) | |
| # ➊ Сурет мазмұнын талдау модельдері | |
| nsfw_cls, nsfw_err = None, None | |
| try: | |
| nsfw_cls = pipeline("image-classification", model="Falconsai/nsfw_image_detection") | |
| except Exception as e: | |
| nsfw_err = str(e) | |
| zs_cls, zs_err = None, None | |
| try: | |
| zs_cls = pipeline("zero-shot-image-classification", model="openai/clip-vit-base-patch32") | |
| except Exception as e: | |
| zs_err = str(e) | |
| captioner, caption_err = None, None | |
| try: | |
| # жеңілі — base нұсқа | |
| captioner = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base") | |
| except Exception as e: | |
| caption_err = str(e) | |
| dangerous_keywords = [ | |
| "ұтып ал", "ставка", "казино", "ақша", "бонус", "18+","vape","electronic cigarette","vaping device","cigarette","smoking","hookah","shisha", "drug", "cocaine", "heroin", "marijuana", "cannabis", "weed", | |
| "жақын таныстық", "тегін", "ойын", "арықта", "салмақ таста","alcohol","beer","wine","vodka","whiskey","champagne", "резать", "бить", "убить", "колоть", "ссылка", "карта", | |
| "жалаңаш", "танысу", "миллионер", "сілтеме", "сыйлық","suicide", "selfharm", "depression", "hopeless", "cut", "die", "kill myself", "pain", "sad", "loneliness", "anxiety", | |
| "шылым", "алкоголь", "карта", "transaction", "card", "discount", "nude","nudity","naked person","bare chest","underwear","bikini", "sex", "porn", "xxx", "nude", "naked", "body", "erotic", "adult", "sexy", "lingerie", "hot girl", "hot boy", "kiss", "strip", "webcam", | |
| "casino", "drug", "violence", "rulet", "bet", "betting", "poker", "jackpot", "lottery", "slot", "roulette", "spin", "gamble", "win big", "bonus", "prize", "money game", "777", "fortune" | |
| ] | |
| # ➋ Zero-shot үшін англ. белгілер (модель ағылшынша жақсы түсінеді) | |
| ZS_DANGER = [ | |
| # темекі/вэйп | |
| "vape","electronic cigarette","vaping device","cigarette","smoking","hookah","shisha", "drug", "cocaine", "heroin", "marijuana", "cannabis", "weed", | |
| # алкоголь | |
| "alcohol","beer","wine","vodka","whiskey","champagne", | |
| # жалаңаштық Жыныстық мазмұн | |
| "nude","nudity","naked person","bare chest","underwear","bikini", "sex", "porn", "xxx", "nude", "naked", "body", "erotic", "adult", "sexy", "lingerie", "hot girl", "hot boy", "kiss", "strip", "webcam", | |
| # қару-жарақ (қаласаңыз) | |
| "gun","knife","weapon", "violence", "fight", "kill", "blood", "war", "bomb", "attack", "shoot", "murder", "death", "weapon", "assault", | |
| # Өзін-өзі зақымдау, психологиялық қауіп (Self-harm / Depression) | |
| "suicide", "selfharm", "depression", "hopeless", "cut", "die", "kill myself", "pain", "sad", "loneliness", "anxiety", | |
| # құмар ойындар | |
| "casino", "drug", "violence", "rulet", "bet", "betting", "poker", "jackpot", "lottery", "slot", "roulette", "spin", "gamble", "win big", "bonus", "prize", "money game", "777", "fortune" | |
| ] | |
| ZS_SAFE = [ | |
| "flower", "bouquet of flowers", "rose", "tulip", "daisy", | |
| "plant", "nature", "tree" | |
| ] | |
| def preprocess_image(image: Image.Image) -> Image.Image: | |
| if image.mode != "RGB": | |
| image = image.convert("RGB") | |
| if cv2_available: | |
| arr = np.array(image) | |
| gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY) | |
| blur = cv2.medianBlur(gray, 3) | |
| # Otsu бинаризациясы | |
| _, thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
| # фон қою болса инверсия | |
| if np.mean(thresh) < 127: | |
| thresh = cv2.bitwise_not(thresh) | |
| # жұқа әріптерді жақсарту үшін аздап dilation | |
| kernel = np.ones((1,1), np.uint8) | |
| thresh = cv2.dilate(thresh, kernel, iterations=1) | |
| return Image.fromarray(thresh) | |
| else: | |
| gray = ImageOps.grayscale(image) | |
| enh = ImageEnhance.Contrast(gray) | |
| enhanced = enh.enhance(1.8) | |
| bw = enhanced.point(lambda x: 0 if x < 128 else 255, '1') | |
| return bw.convert("RGB") | |
| def extract_text_from_image(image: Image.Image, lang: str = "kaz+rus+eng", engine_choice: str = "auto") -> str: | |
| """ | |
| engine_choice: "auto" | "tesseract" | "trocr" | |
| """ | |
| # 1) pytesseract (негізгі) | |
| if engine_choice in ("auto", "tesseract") and pytesseract_available: | |
| try: | |
| proc = preprocess_image(image) | |
| text = pytesseract.image_to_string(proc, lang=lang, config="--oem 1 --psm 6") | |
| text = text.strip() | |
| if text or engine_choice == "tesseract": | |
| return text | |
| except Exception as e: | |
| if engine_choice == "tesseract": | |
| return f"[pytesseract error: {e}]" | |
| # 2) TroCR fallback | |
| if engine_choice in ("auto", "trocr") and ocr_pipeline is not None: | |
| try: | |
| out = ocr_pipeline(image) | |
| if isinstance(out, list) and len(out) > 0: | |
| text = out[0].get("generated_text", "") or out[0].get("text", "") or str(out[0]) | |
| else: | |
| text = str(out) | |
| return text.strip() | |
| except Exception as e: | |
| return f"[ocr pipeline error: {e}]" | |
| return "[OCR қолжетімсіз: pytesseract және/немесе OCR pipeline жоқ]" | |
| # ➌ Сурет мазмұнын қауіптілікке тексеру | |
| def analyze_image_content(image: Image.Image) -> str: | |
| lines = [] | |
| reasons = [] | |
| # A) NSFW детекция | |
| if nsfw_cls is not None: | |
| try: | |
| nsfw_res = nsfw_cls(image) | |
| # мысалы: [{'label': 'nsfw', 'score': 0.97}, {'label':'sfw','score':0.03}] | |
| scores = {d["label"].lower(): d["score"] for d in nsfw_res} | |
| nsfw_score = max(scores.get("nsfw", 0.0), scores.get("unsafe", 0.0)) | |
| if nsfw_score >= 0.60: | |
| reasons.append(f"Жалаңаш/NSFW ықтималдығы жоғары ({nsfw_score:.2f}).") | |
| except Exception as e: | |
| lines.append(f"ℹ️ NSFW моделі қатесі: {e}") | |
| else: | |
| if nsfw_err: | |
| lines.append(f"ℹ️ NSFW моделі жүктелмеді: {nsfw_err}") | |
| # B) Zero-shot (CLIP) — мақсатты белгілер | |
| zs_scores = {} | |
| if zs_cls is not None: | |
| try: | |
| labels = ZS_DANGER + ZS_SAFE | |
| res = zs_cls(image, candidate_labels=labels, hypothesis_template="a photo of {}") | |
| # res сортталған, бәрін dict-ке түсіреміз | |
| for r in res: | |
| zs_scores[r["label"].lower()] = float(r["score"]) | |
| # шектер | |
| th_vape = 0.35 | |
| th_nude = 0.35 | |
| th_alc = 0.30 | |
| th_weapon = 0.35 | |
| th_casino = 0.35 | |
| th_selfharm = 0.35 | |
| # темекі/есірткі | |
| if max([zs_scores.get(k, 0) for k in ["vape","electronic cigarette","vaping device","cigarette","smoking","hookah","shisha", "drug", "cocaine", "heroin", "marijuana", "cannabis", "weed"]], default=0) >= th_vape: | |
| s = max([zs_scores.get(k, 0) for k in ["vape","electronic cigarette","vaping device","cigarette","smoking","hookah","shisha", "drug", "cocaine", "heroin", "marijuana", "cannabis", "weed"]]) | |
| reasons.append(f"Темекі/есірткі белгісі ({s:.2f}).") | |
| # жалаңаштық Жыныстық мазмұн | |
| if max([zs_scores.get(k, 0) for k in ["nude","nudity","naked person","bare chest","underwear","bikini", "sex", "porn", "xxx", "nude", "naked", "body", "erotic", "adult", "sexy", "lingerie", "hot girl", "hot boy", "kiss", "strip", "webcam"]], default=0) >= th_nude: | |
| s = max([zs_scores.get(k, 0) for k in ["nude","nudity","naked person","bare chest","underwear","bikini", "sex", "porn", "xxx", "nude", "naked", "body", "erotic", "adult", "sexy", "lingerie", "hot girl", "hot boy", "kiss", "strip", "webcam"]]) | |
| reasons.append(f"Жалаңаштық/жеке аймақ ({s:.2f}).") | |
| # алкоголь | |
| if max([zs_scores.get(k, 0) for k in ["alcohol","beer","wine","vodka","whiskey","champagne"]], default=0) >= th_alc: | |
| s = max([zs_scores.get(k, 0) for k in ["alcohol","beer","wine","vodka","whiskey","champagne"]]) | |
| reasons.append(f"Алкоголь белгісі ({s:.2f}).") | |
| # қару-жарақ зорлық-зомбылық | |
| if max([zs_scores.get(k, 0) for k in ["gun","knife","weapon", "violence", "fight", "kill", "blood", "war", "bomb", "attack", "shoot", "murder", "death", "weapon", "assault"]], default=0) >= th_weapon: | |
| s = max([zs_scores.get(k, 0) for k in ["gun","knife","weapon", "violence", "fight", "kill", "blood", "war", "bomb", "attack", "shoot", "murder", "death", "weapon", "assault"]]) | |
| reasons.append(f"Қару-жарақ, зорлық-зомбылық белгісі ({s:.2f}).") | |
| # құмар ойындар | |
| if max([zs_scores.get(k, 0) for k in ["casino", "drug", "violence", "rulet", "bet", "betting", "poker", "jackpot", "lottery", "slot", "roulette", "spin", "gamble", "win big", "bonus", "prize", "money game", "777", "fortune"]], default=0) >= th_casino: | |
| s = max([zs_scores.get(k, 0) for k in ["casino", "drug", "violence", "rulet", "bet", "betting", "poker", "jackpot", "lottery", "slot", "roulette", "spin", "gamble", "win big", "bonus", "prize", "money game", "777", "fortune"]]) | |
| reasons.append(f"Құмар ойындар белгісі ({s:.2f}).") | |
| # Өзін-өзі зақымдау, психологиялық қауіп (Self-harm / Depression) | |
| if max([zs_scores.get(k, 0) for k in ["suicide", "selfharm", "depression", "hopeless", "cut", "die", "kill myself", "pain", "sad", "loneliness", "anxiety"]], default=0) >= th_selfharm: | |
| s = max([zs_scores.get(k, 0) for k in ["suicide", "selfharm", "depression", "hopeless", "cut", "die", "kill myself", "pain", "sad", "loneliness", "anxiety"]]) | |
| reasons.append(f"Психологиялық қауіп белгісі ({s:.2f}).") | |
| except Exception as e: | |
| lines.append(f"ℹ️ Zero-shot қатесі: {e}") | |
| else: | |
| if zs_err: | |
| lines.append(f"ℹ️ Zero-shot моделі жүктелмеді: {zs_err}") | |
| # C) Caption (түсіндірме үшін) | |
| caption = "" | |
| if captioner is not None: | |
| try: | |
| out = captioner(image) | |
| if isinstance(out, list) and len(out) > 0: | |
| caption = out[0].get("generated_text", "") or out[0].get("text", "") | |
| except Exception as e: | |
| lines.append(f"ℹ️ Caption қатесі: {e}") | |
| else: | |
| if caption_err: | |
| lines.append(f"ℹ️ Caption моделі жүктелмеді: {caption_err}") | |
| # Нәтижені қалыптастыру | |
| if reasons: | |
| lines.append("⚠️ Сурет қауіпті (сіздің денсаулығыңыз бен жүйкеңізге зиянды болуы мүмкін):") | |
| for r in reasons: | |
| lines.append(f"• {r}") | |
| else: | |
| # қауіпсіздікке ишара — гүл/табиғат жоғары болса | |
| safe_score = max([zs_scores.get(k, 0) for k in ["flower","bouquet of flowers","rose","tulip","daisy","plant","nature","tree"]], default=0) | |
| if safe_score >= 0.30: | |
| lines.append(f"✅ Сурет қауіпсіз (гүл/табиғат ықтималдығы {safe_score:.2f}).") | |
| else: | |
| lines.append("ℹ️ Нақты қауіп белгісі табылмады. (Белгісіз/бейтарап)") | |
| if caption: | |
| lines.append(f"📝 Автожазба (caption): {caption}") | |
| return "\n".join(lines) | |
| def analyze_ad(text, image, lang, engine_choice): | |
| parts = [] | |
| if classifier_err: | |
| parts.append(f"⚠️ Классификатор жүктелмеді: {classifier_err}") | |
| if ocr_err and not pytesseract_available: | |
| parts.append(f"⚠️ OCR pipeline жүктелмеді: {ocr_err}") | |
| if nsfw_err: | |
| parts.append(f"ℹ️ Ескерту (NSFW): {nsfw_err}") | |
| if zs_err: | |
| parts.append(f"ℹ️ Ескерту (Zero-shot): {zs_err}") | |
| if caption_err: | |
| parts.append(f"ℹ️ Ескерту (Caption): {caption_err}") | |
| # 1) қолмен енгізілген мәтін | |
| if text and text.strip(): | |
| parts.append("📄 Енгізілген мәтін:") | |
| parts.append(text) | |
| if any(k in text.lower() for k in dangerous_keywords): | |
| parts.append("⚠️ Қауіпті жарнама (кілт сөз бойынша анықталды).") | |
| else: | |
| if classifier is None: | |
| parts.append("ℹ️ Классификатор жоқ — модельдік тексеру жүргізілмеді.") | |
| else: | |
| try: | |
| pred = classifier(text)[0] | |
| label = pred.get("label", "").lower() | |
| score = pred.get("score", 0.0) | |
| if ("spam" in label or "toxic" in label) and score > 0.5: | |
| parts.append(f"⚠️ Қауіпті жарнама (модель шешімі, сенімділік {score:.2f}).") | |
| else: | |
| parts.append(f"✅ Қауіпсіз жарнама (модель шешімі, сенімділік {score:.2f}).") | |
| except Exception as e: | |
| parts.append(f"⚠️ Мәтінді классификациялау қатесі: {e}") | |
| else: | |
| parts.append("📄 Мәтін енгізілмеді.") | |
| # 2) суреттен OCR | |
| if image is not None: | |
| parts.append("\n🖼️ Суреттен мәтін алу (OCR):") | |
| text_from_image = extract_text_from_image(image, lang=lang, engine_choice=engine_choice) | |
| parts.append(text_from_image or "(модель мәтін таппады)") | |
| if text_from_image.startswith("[") and "error" in text_from_image.lower(): | |
| parts.append("❌ OCR қатесі пайда болды. Логтарды тексеріңіз.") | |
| else: | |
| if text_from_image.strip(): | |
| if any(k in text_from_image.lower() for k in dangerous_keywords): | |
| parts.append("⚠️ Қауіпті жарнама (кілт сөз бойынша анықталды).") | |
| else: | |
| if classifier is None: | |
| parts.append("ℹ️ Классификатор жоқ — модель арқылы тексерілмеді.") | |
| else: | |
| try: | |
| pred = classifier(text_from_image)[0] | |
| label = pred.get("label", "").lower() | |
| score = pred.get("score", 0.0) | |
| if ("spam" in label or "toxic" in label) and score > 0.5: | |
| parts.append(f"⚠️ Қауіпті жарнама (модель шешімі, сенімділік {score:.2f}).") | |
| else: | |
| parts.append(f"✅ Қауіпсіз жарнама (модель шешімі, сенімділік {score:.2f}).") | |
| except Exception as e: | |
| parts.append(f"⚠️ Суреттен алынған мәтінді классификациялау қатесі: {e}") | |
| else: | |
| parts.append("(Суреттен мәтін табылмады.)") | |
| # 3) сурет мазмұны (визуал) — жаңа бөлім | |
| parts.append("\n🧩 Сурет мазмұнын қауіптілікке талдау:") | |
| parts.append(analyze_image_content(image)) | |
| else: | |
| parts.append("\n🖼️ Сурет жүктелмеді.") | |
| return "\n".join(parts) | |
| # Интерфейс параметрлері | |
| # Интерфейс параметрлері (дизайнмен) | |
| custom_css = """ | |
| body { | |
| background-color: #f0f6ff; | |
| } | |
| #component-0 { | |
| border-radius: 15px; | |
| background-color: #e6f0ff; | |
| padding: 20px; | |
| box-shadow: 0 0 10px rgba(0,0,0,0.1); | |
| } | |
| textarea { | |
| border-radius: 10px; | |
| border: 1px solid #5a8dee; | |
| padding: 10px; | |
| font-size: 16px; | |
| } | |
| .gradio-container .gr-button { padding: 10px 18px; font-weight: 700; } | |
| .gradio-container .gr-button-primary { box-shadow: 0 2px 6px rgba(22,163,74,.25); } | |
| .gradio-container .gr-button-secondary { border: 1px solid #cbd5e1 !important; } | |
| """ | |
| demo = gr.Interface( | |
| fn=analyze_ad, | |
| inputs=[ | |
| gr.Textbox(label="✏️ Жарнама мәтінін енгізіңіз", placeholder="Мысалы: «Тегін iPhone ұтып ал!»"), | |
| gr.Image(type="pil", label="🖼️ Жарнама суретін жүктеңіз"), | |
| gr.Dropdown( | |
| choices=["kaz", "kaz+rus", "kaz+rus+eng", "rus", "eng"], | |
| value="kaz+rus+eng", label="🗣️ OCR тілі" | |
| ), | |
| gr.Radio(choices=["auto", "tesseract", "trocr"], value="auto", label="⚙️ OCR қозғалтқышы"), | |
| ], | |
| outputs=gr.Textbox(label="🔍 Талдау нәтижесі", lines=18), | |
| title="🧠 Қауіпті жарнамаларды анықтау (OCR + Image Safety)", | |
| description="Жүйе мәтін мен суреттегі мазмұнды талдап, қауіпті элементтерді анықтайды.", | |
| css=custom_css, | |
| submit_btn="Талдау", | |
| clear_btn="Тазалау", | |
| ) | |
| demo.launch() |