AI-система описания визуального контента для слабовидящих

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-система описания визуального контента для слабовидящих
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

Разработка AI для описания визуального контента для незрячих

Системы описания изображений для незрячих и слабовидящих пользователей — это не просто image captioning. Требования принципиально другие: описание должно быть практически полезным, а не поэтически красивым. Пользователю важно знать не «красивый закат над морем», а «вы стоите перед стеклянной дверью, на ней написано PUSH, слева — кнопка домофона». Задача делится на несколько сценариев: navigation assistance (что перед пользователем), document reading (текст на упаковке/вывеске), face recognition (кто рядом), currency/product identification.

Image Description Pipeline

import numpy as np
import cv2
import torch
from transformers import (AutoProcessor, AutoModelForVision2Seq,
                           TrOCRProcessor, VisionEncoderDecoderModel)
from PIL import Image
from dataclasses import dataclass, field
from typing import Optional
import re

@dataclass
class VisualDescription:
    scene_summary: str          # главное описание сцены
    text_content: list[str]     # обнаруженные текстовые элементы
    people_count: int
    people_descriptions: list[str]
    objects: list[str]          # ключевые объекты и их расположение
    navigation_hint: str        # подсказка для навигации
    confidence: float
    priority: str               # immediate / informational

class AccessibleImageDescriber:
    """
    Описание изображений для незрячих пользователей.
    Три уровня детализации:
    - Brief (1 предложение): для быстрой ориентации
    - Standard (3-5 предложений): основная информация
    - Detailed (полное описание): для важных документов
    VLM: Qwen2-VL-7B-Instruct или InternVL2-8B.
    """
    # Промпты адаптированы для accessibility
    PROMPTS = {
        'navigation': (
            'Describe this image focusing on what is immediately in front. '
            'Mention obstacles, doors, signs, and distances. '
            'Be concise and practical. Start with the most important element.'
        ),
        'document': (
            'Read all visible text in this image. '
            'List each text element on a new line with its location context. '
            'Include labels, prices, instructions, warnings.'
        ),
        'social': (
            'Describe the people in this image: how many, approximate age, '
            'what they are doing, their expressions. '
            'Be respectful and factual.'
        ),
        'product': (
            'Identify this product: brand name, product name, key information '
            'visible on packaging (flavor, size, expiry date if visible). '
            'Be brief and factual.'
        )
    }

    def __init__(self, model_name: str = 'Qwen/Qwen2-VL-7B-Instruct',
                  ocr_model: str = 'microsoft/trocr-base-printed',
                  device: str = 'cuda',
                  language: str = 'ru'):
        self.device = device
        self.language = language

        self.processor = AutoProcessor.from_pretrained(model_name)
        self.model = AutoModelForVision2Seq.from_pretrained(
            model_name,
            torch_dtype=torch.float16 if device == 'cuda' else torch.float32,
            device_map='auto' if device == 'cuda' else None
        )

        # TrOCR для чёткого распознавания текста (документы, упаковки)
        self.ocr_processor = TrOCRProcessor.from_pretrained(ocr_model)
        self.ocr_model = VisionEncoderDecoderModel.from_pretrained(
            ocr_model
        ).to(device)

        # Детектор текстовых областей (EAST или DB-Net через OpenCV DNN)
        self._text_detector = None  # загружается по требованию

    def describe(self, image: np.ndarray,
                  context: str = 'navigation',
                  lang: Optional[str] = None) -> VisualDescription:
        """
        Основной метод описания.
        context: navigation / document / social / product
        """
        target_lang = lang or self.language
        pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

        # Выбор промпта
        base_prompt = self.PROMPTS.get(context, self.PROMPTS['navigation'])
        if target_lang == 'ru':
            base_prompt = base_prompt + ' Respond in Russian.'

        # VLM inference
        vlm_description = self._run_vlm(pil, base_prompt)

        # OCR для текстового контента
        text_regions = self._extract_text_regions(image)

        # Навигационная подсказка
        nav_hint = self._generate_nav_hint(image, vlm_description)

        # Подсчёт людей
        people_count, people_desc = self._analyze_people(vlm_description)

        return VisualDescription(
            scene_summary=vlm_description,
            text_content=text_regions,
            people_count=people_count,
            people_descriptions=people_desc,
            objects=self._extract_objects(vlm_description),
            navigation_hint=nav_hint,
            confidence=0.85,  # VLM не возвращает confidence напрямую
            priority='immediate' if context == 'navigation' else 'informational'
        )

    @torch.no_grad()
    def _run_vlm(self, pil_image: Image.Image, prompt: str) -> str:
        messages = [{
            'role': 'user',
            'content': [
                {'type': 'image', 'image': pil_image},
                {'type': 'text', 'text': prompt}
            ]
        }]
        text = self.processor.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )
        inputs = self.processor(
            text=[text], images=[pil_image], return_tensors='pt'
        ).to(self.device)

        output = self.model.generate(
            **inputs,
            max_new_tokens=256,
            temperature=0.3,
            do_sample=False
        )
        decoded = self.processor.batch_decode(
            output, skip_special_tokens=True
        )[0]
        # Удаляем промпт из ответа
        if 'assistant' in decoded.lower():
            decoded = decoded.split('assistant')[-1].strip()
        return decoded.strip()

    def _extract_text_regions(self, image: np.ndarray) -> list[str]:
        """Быстрое OCR для текста на изображении"""
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        # EAST text detector через OpenCV
        # Упрощённая версия: прямое TrOCR на всё изображение
        try:
            pil = Image.fromarray(gray).convert('RGB')
            pixel_values = self.ocr_processor(
                images=pil, return_tensors='pt'
            ).pixel_values.to(self.device)
            generated_ids = self.ocr_model.generate(pixel_values)
            text = self.ocr_processor.batch_decode(
                generated_ids, skip_special_tokens=True
            )[0].strip()
            if text and len(text) > 3:
                return [text]
        except Exception:
            pass
        return []

    def _generate_nav_hint(self, image: np.ndarray,
                            description: str) -> str:
        """Генерация краткой навигационной подсказки"""
        # Разделить изображение на 3 зоны: лево/центр/право
        h, w = image.shape[:2]
        zones = {
            'left': image[:, :w//3],
            'center': image[:, w//3:2*w//3],
            'right': image[:, 2*w//3:]
        }
        # Оценить "свободность" каждой зоны по яркости
        zone_brightness = {
            k: float(np.mean(cv2.cvtColor(v, cv2.COLOR_BGR2GRAY)))
            for k, v in zones.items()
        }
        clearest = max(zone_brightness, key=zone_brightness.get)
        return f'Наибольший просвет — {clearest}'

    def _analyze_people(self, description: str) -> tuple[int, list[str]]:
        """Извлечение информации о людях из описания"""
        count = 0
        people_desc = []
        # Простой паттерн для подсчёта
        matches = re.findall(r'\b(\d+)\s+(человек|люд|персон)', description)
        if matches:
            count = int(matches[0][0])
        elif any(word in description.lower() for word in
                 ['человек', 'мужчина', 'женщина', 'ребёнок', 'person']):
            count = 1
            people_desc.append(description[:100])
        return count, people_desc

    def _extract_objects(self, description: str) -> list[str]:
        """Простое извлечение ключевых объектов"""
        # В production — NER модель
        return [s.strip() for s in description.split('.') if len(s.strip()) > 10][:5]


class CurrencyRecognizer:
    """
    Распознавание купюр и монет для незрячих пользователей.
    Датасет: EURO Banknote Dataset, BankNote Authentication.
    Нельзя использовать для верификации подлинности.
    """
    CURRENCY_TEMPLATES = {
        'RUB': {
            5000: {'dominant_hue_range': (10, 25), 'size_ratio': (2.07, 0.98)},
            1000: {'dominant_hue_range': (95, 130), 'size_ratio': (2.07, 0.98)},
            500: {'dominant_hue_range': (55, 75), 'size_ratio': (2.07, 0.98)},
            100: {'dominant_hue_range': (95, 115), 'size_ratio': (2.07, 0.98)},
        }
    }

    def recognize_banknote(self, image: np.ndarray,
                            currency: str = 'RUB') -> dict:
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        dominant_hue = float(np.median(hsv[:, :, 0]))
        h, w = image.shape[:2]
        aspect = w / h

        templates = self.CURRENCY_TEMPLATES.get(currency, {})
        best_match = None
        for denomination, props in templates.items():
            h_min, h_max = props['dominant_hue_range']
            if h_min <= dominant_hue <= h_max:
                best_match = denomination
                break

        return {
            'currency': currency,
            'denomination': best_match,
            'confidence': 0.75 if best_match else 0.0,
            'speech_output': (f'{best_match} рублей' if best_match
                              else 'купюра не распознана')
        }
Сценарий Модель Качество
Навигация в помещении Qwen2-VL-7B SPICE 22–26
Распознавание текста/вывесок TrOCR-base CER 3–8%
Описание людей InternVL2-8B BLEU-4 28–34%
Распознавание купюр EfficientNet-B0 94–98%
Идентификация продуктов CLIP + каталог Recall@5 78–85%

Latency требования: для navigation — не более 2–3 секунды на ответ (пешеход движется); для document reading — 5–10 секунд допустимы. Offline-режим критичен: пользователь должен работать без интернета.

Задача Срок
Navigation description + OCR (один контекст) 5–8 недель
Мульти-контекст + мобильное приложение (iOS/Android) 10–16 недель
Полная accessibility платформа с голосовым UI 18–28 недель