AI Visual Content Description for Visually Impaired

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Visual Content Description for Visually Impaired
Simple
from 1 business day to 3 business days
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1212
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822

Разработка 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))

        # Selection промпта
        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 недель