Elettracompany.com

Компьютерный справочник
12 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java распознавание изображений

Тессеракт OCR с Java с примерами

В этой статье мы узнаем, как работать с Tesseract OCR в Java, используя Tesseract API .

Что такое Тессеракт OCR?
Tesseract OCR — это оптический механизм чтения символов, разработанный лабораториями HP в 1985 году и открытый в 2005 году. С 2006 года он разрабатывается Google. Tesseract поддерживает Unicode (UTF-8) и может распознавать более 100 языков «из коробки» и, таким образом, может использоваться для создания различных программ сканирования языков. Последняя версия Tesseract — это Tesseract 4 . Он добавляет новый механизм распознавания текста на основе нейронной сети (LSTM), который ориентирован на распознавание линий, но также поддерживает устаревший механизм распознавания текста Tesseract, который работает, распознавая шаблоны символов.

Обычно OCR работает следующим образом:

  1. Предварительная обработка данных изображения, например: преобразование в оттенки серого, сглаживание, удаление перекоса, фильтрация.
  2. Определить строки, слова и символы.
  3. Создайте ранжированный список кандидатов-персонажей на основе обученного набора данных. (здесь метод setDataPath () используется для установки пути данных тренера)
  4. Проводите обработку распознаваемых символов, выбирайте лучшие символы на основе достоверности из предыдущего шага и языковых данных. Языковые данные включают словарь, грамматические правила и т. Д.

Преимущества OCR многочисленны, но именно:

  • это повышает эффективность и результативность офисной работы
  • Возможность мгновенного поиска по контенту чрезвычайно полезна, особенно в офисных условиях, которые имеют дело с большим объемом сканирования или большим потоком документов.
  • OCR быстро гарантирует, что содержимое документа остается неизменным, а также экономит время.
  • Рабочий процесс увеличивается, поскольку сотрудникам больше не нужно тратить время на ручной труд, и они могут работать быстрее и эффективнее.
  • OCR ограничен распознаванием языка.
  • Требуется много усилий, чтобы собрать данные тренера на разных языках и реализовать их.
  • Также необходимо проделать дополнительную работу по обработке изображений, так как это самая важная часть, которая действительно имеет значение, когда дело доходит до производительности оптического распознавания текста.
  • Выполнив такой большой объем работы, ни одно OCR не может предложить точность 100%, и даже после OCR мы должны определить неопознанный символ с помощью соседних методов машинного обучения или исправить его вручную.

Как использовать Tesseract OCR

  1. Первый шаг — загрузить API Tess4J по ссылке.
  2. Извлеките файлы из загруженного файла
  3. Откройте вашу IDE и создайте новый проект
  4. Свяжите файл jar с вашим проектом. Перейдите по этой ссылке .
  5. Пожалуйста, мигрируйте по этому пути «../Tess4J-3.4.8-src/Tess4J/dist».

Теперь вы закончили со своим ссылочным флягой в своем проекте и готовы использовать движок tesseract.

Выполнение OCR на чистых изображениях

Теперь, когда вы связали файл jar, мы можем начать с нашей части кодирования. Следующий код читает файл изображения, выполняет распознавание и отображает текст на консоли.

Java распознавание изображений

Недавно по работе столкнулся с необходимостью реализовать поиск фрагмента изображения. После продолжительного интернет серфинга на эту тему я пришел к выводу, что лучше всего для решения данной задачи подходит opencv. Ссылка на официальный сайт

Opencv — это супер библиотека для работы с изображениями, которая написана под все популярные операционные системы, очень много всего умеет, есть хорошая документация. Но поскольку я привык работать с java, я нашел библиотеку-обертку на java для opencv: Javacv .
В javacv, к сожалению, с документацией и примерами все хуже, но при желании тоже можно разобраться. Сначала это у меня не получалось и я даже перешел на C++, но потом понял, что можно читать официальную документацию для C++ и пользоваться ей на java. Все классы и методы в java именованы либо точно так же, либо очень похоже.

Ниже я расскажу кратко об алгоритме, и как с нуля написать на java программу для поиска фрагмента изображения.

В качестве тренировочной задачи для себя я решил сделать детектор детской игрушки. Для получения результата мне необходимо было сделать следующий действия:

  1. Сделать много фотографий где игрушка есть в кадре (положительная выборка)
  2. Сделать много фотографий, где нет игрушки. (отрицательная выборка)
  3. Установить opencv
  4. Обучить алгоритм распознавания, получив на выходе xml файл с результатом
  5. Подключить javacv
  6. Написать небольшую программу на java, распознающую изображение по данному xml файлу

Об алгоритме

Что видно? Нос яркий, но слева и справа от носа темные участки. Под бровями светло, а ниже темно, щеки светлые и т.д. Особенно хорошо это замечают художники, чтобы нарисовать правдоподобный портрет им необходимо точно уловить все эти особенности.

Подготовка выборки для обучения

Чтобы получить положительную выборку, мне пришлось устроить фотосессию игрушке своего сына, сделав приблизительно 150 фотографий. Главное условие выборки: все фотографии должны быть приближены к реальным условиям, на которых планируется работа алгоритма. Я не планировал тестировать свой алгоритм в местах, кроме своей квартиры, поэтому я просто сделал по несколько десятков фотографий в каждой комнате на разном расстоянии при разном освещении. Затем тоже самое, но без игрушки — отрицательная выборка

Установка opencv

  1. Запустить самораспаковывающийся архив, который можно скачать здесь
  2. Установить переменные среды:
    1. OPENCV_DIR = [куда распакавали архив]buildx64vc12 (или vc11 в зависимости от версии Micrisof Visuial C++ )
    2. PATH = PATH + %OPENCV_DIR%bin

Рекомендую устанавливать последнюю версию, с ней работать гораздо легче.

Обучение алгоритма

good.txt — это файл, описывающий положительную выборку. Каждая строка представляет одну фотографию. Пример строки:

Сначала указывается имя файла относительно good.txt, затем через пробел количество искомых элементов на фотографии, затем координаты прямоугольника, внутри которого расположен искомый объект. Удобнее организовывать выборку так, чтобы каждая положительная фотография представляла собой обрезанное изображение обрезанного объекта.
samples.vec — имя файла, куда запишется результат выполнения программы
w и h — размеры образца. Здесь нужно указывать минимальный размер, чтобы отличить объект. Размеры должны быть пропорциональны объекту. Удобно сделать пробный запуск программы с дополнительными параметрами -num 5 -show и наглядно посмотреть результат работы, подготовив 5 пробных образцов.

Читать еще:  Java security cert

Второй шаг является непосредственным обучением:

OpenCV/Обработка и распознавание изображений

Основная информация

В группе рассматриваются алгоритмы, методы, библиотеки обработки и распознавания изображений. Также можно очень много узнать про библиотеки машинного зрения, такие как, OpenCV или CCV.

OpenCV что это такое? OpenCV (англ. Open Source Computer Vision Library, библиотека компьютерного зрения с открытым исходным кодом) — библиотека алгоритмов компьютерного зрения, обработки изображений и численных алгоритмов общего назначения с открытым кодом. Реализована на C/C++, также разрабатывается для Python, Ruby, Matlab, Lua и других языков. Может свободно использоваться в академических и коммерческих целях — распространяется в условиях лицензии BSD.

Распознавание образов что это? Распознавание образов — это научная дисциплина, целью которой является классификация объектов по нескольким категориям или классам. Объекты называются образами.

CCV (A Modern Computer Vision Library), небольшая библиотека современных алгоритмов машинного зрения.

Стена группы

Как Discord каждый день изменяет размер 150 млн картинок с помощью Go и C++

Хотя Discord — это приложение для голосового и текстового чата, каждый день через него проходит более ста миллионов изображений. Конечно, мы бы хотели, чтобы задача была простой: просто перенаправить картинки вашим друзьям по всем каналам. Но в реальности доставка этих изображений создаёт довольно большие технические проблемы. Прямая ссылка на картинки выдаст хосту с картинкой IP-адреса пользователей, а большие изображения расходуют много трафика. Чтобы избежать этих проблем, требуется промежуточный сервис, который будет получать изображения для пользователей и изменять их размер для экономии трафика.

Встречайте Image Proxy

Для выполнения этой работы мы создали сервис Python и креативно назвали его
Image Proxy. Он загружает картинки с удалённых URL, а затем выполняет ресурсоёмкую задачу по ресайзингу с помощью пакета
pillow-simd. Этот пакет работает удивительно быстро, используя где только возможно для ускорения ресайзинга
инструкции x86 SSE. Image Proxy будет получать HTTP-запрос, содержащий URL, чтобы загрузить, изменить размер и, наконец, выдать окончательное изображение.

Текстовые капчи легко распознаются нейронными сетями глубокого обучения

Нейронные сети глубокого обучения достигли больших успехов в распознавании образов. В тоже время текстовые капчи до сих пор используются в некоторых известных сервисах бесплатной электронной почты. Интересно смогут ли нейронные сети глубоко обучения справится с задачей распознавания текстовой капчи? Если да то как?

Космическая съёмка Земли

Cпутниковый снимок в ложных цветах (зелёный, красный, ближний инфракрасный) с пространственным разрешением 3 метра и наложенной маской зданий из OpenStreetMap (спутниковая группировка PlanetScope)

Привет, Хабр! Мы постоянно расширяем источники данных, которые используем для аналитики, поэтому решили добавить ещё и спутниковые снимки. У нас аналитика по спутниковым снимкам полезна в продуктах для предпринимательства и инвестиций. В первом случае статистика по геоданным поможет понять, в каком месте стоит открывать торговые точки, во втором позволяет анализировать деятельность компаний. Например, для строительных компаний можно посчитать, сколько за месяц было построено этажей, для сельскохозяйственных компаний — сколько гектаров урожая взошло и т.д.

В этой статье я постараюсь дать примерное представление о космической съёмке Земли, расскажу о трудностях, с которыми можно столкнуться, начиная работу со спутниковыми снимками: предварительная обработка, алгоритмы для анализа и библиотеки Python для работы со спутниковыми снимками и геоданными. Так что все, кому интересна область компьютерного зрения, добро пожаловать под кат!

Использование SVG в качестве Placeholder’a

Генерация SVG из изображений может использоваться для Placeholder’ов.

Я занимаюсь оптимизацией изображений и картинок для их быстрой загрузки. Одна из самых интересных областей исследования это Placeholder’ы: что показывать, когда изображение еще не загружено.

В последние дни я сталкивался с некоторыми методами загрузки, которые используют SVG, и я хотел бы описать их в этом посте.

В этом посте мы рассмотрим следующие темы:

  • Обзор различных типов Placeholder’ов
  • Placeholder на основе SVG (контуры, фигуры и силуэты)
  • Автоматизация процесса.

Создание Android приложения для распознавания текста за 10 Минут. Mobile Vision CodeLab

Видео версия туториала

Оптическое распознавание символов (англ. Optical Character Recognition, сокр. OCR) дает компьютеру возможность читать текст на изображении, позволяя приложениям понимать знаки, статьи, листовки, страницы текста, меню или что угодно в виде текста. Mobile Vision Text API предоставляет разработчикам Android мощную и надежную возможность OCR , которая поддерживает большинство устройств Android и не увеличивает размер вашего приложения.

В этом туториале вы создадите приложение, в котором в процессе видеосъёмки будет распознаваться и воспроизводиться весь текст, попадающий в кадр.

Также мы публиковали статьи о других функциях Mobile Vision:

Исходный код

Вы можете скачать исходный код …

… или склонировать репозиторий GitHub из командной строки:

Репозиторий visionSamples содержит много примеров проектов, связанных с Mobile Vision . В этом уроке используется только два:

  • ocr-codelab/ocr-reader-start – начальный код, который вы будете использовать в этом уроке.
  • ocr-codelab/ocr-reader-complete – полный код готового приложения. Вы можете использовать его для устранения неполадок или перейти сразу к рабочему приложению.

Обновление сервисов Google Play

Возможно, вам потребуется обновить установленную версию Google Repository , чтобы использовать Mobile Vision Text API .

Откройте Android Studio и откройте SDK Manager :

Убедитесь, что Google Repository обновлен. Он должен быть не менее 26 версии.

Добавление зависимости Google Play Services и создание приложения для запуска

Теперь можно открывать стартовый проект:

  1. Выберите каталог запуска ocr-reader из загруженного кода (File >Open > ocr-codelab/ocr-reader-start ).
  2. Добавьте зависимость Google Play Services к приложению. Без этой зависимости Text API не будет доступен.

Проект может указать на отсутствие файла integer/google_play_services_version и выдать ошибку. Это нормально, мы исправим это на следующем шаге.

Откройте файл build.gradle в app модуле и измените блок зависимостей, включив туда зависимость play-services-vision . Когда все будет готово, файл должен выглядеть так:

  1. Нажмите кнопку синхронизации Gradle .
  2. Нажмите кнопку запуска.
Читать еще:  Умножение матриц java

Через несколько секунд вы увидите экран «Read Text», но это всего лишь черный экран.

Сейчас ничего не происходит, потому что CameraSource не настроен. Давайте сделаем это.

Если у вас что-то не получается, вы можете открыть проект ocr-reader-complete и убедиться, что он работает правильно. Этот проект является готовой версией урока, и если эта версия не работает, вы должны проверить, что всё в порядке с вашим устройством и настройками Android Studio .

Настройте TextRecognizer и CameraSource

Чтобы начать работу, мы создадим наш TextRecognizer . Этот объект-детектор обрабатывает изображения и определяет, какой текст появляется внутри них. После инициализации TextRecognizer может использоваться для обнаружения текста во всех типах изображений. Найдите метод createCameraSource и создайте TextRecognizer :

Теперь TextRecognizer готов к работе. Однако, возможно, он еще не работает. Если на устройстве недостаточно памяти или Google Play Services не может загрузить зависимости OCR , объект TextRecognizer работать не будет. Прежде чем мы начнем использовать его для распознавания текста, мы должны проверить, что он готов. Мы добавим эту проверку в createCameraSource после инициализации TextRecognizer :

Теперь, когда мы проверили, что TextRecognizer готов к работе, мы можем использовать его для распознавания отдельных кадров. Но мы хотим сделать что-то более интересное: читать текст в режиме видеосъёмки. Для этого мы создадим CameraSource , который предварительно настроен для управления камерой. Нам необходимо установить высокое разрешение съёмки и включить автофокусировку, чтобы справиться с задачей распознавания небольшого текста. Если вы уверены, что ваши пользователи будут смотреть на большие блоки текста, например вывески, вы можете использовать более низкое разрешение, и тогда обработка кадров будет происходить быстрее:

Вот как должен выглядеть метод createCameraSource , когда вы закончите:

Если вы запустите приложение, то увидите, что началась видеосъёмка! Но для обработки изображений с камеры нам нужно дописать этот последний TODO в createCameraSource : создать Processor для обработки текста по мере его поступления.

Создание OcrDetectorProcessor

Сейчас ваше приложение может обнаруживать текст на отдельных кадрах, используя метод обнаружения в TextRecognizer . Так можно найти текст, например, на фотографии. Но для того, чтобы читать текст прямо во время видеосъёмки, нужно реализовать Processor , который будет обрабатывать текст, как только он появится на экране.

Перейдите в класс OcrDetectorProcessor реализуйте интерфейс Detector.Processor :

Для реализации этого интерфейса требуется переопределить два метода. Первый, receiveDetections , получает на вход TextBlocks из TextRecognizer по мере их обнаружения. Второй, release , используется для освобождения от ресурсов при уничтожении TextRecognizer . В этом случае нам нужно просто очистить графическое полотно, что приведёт к удалению всех объектов OcrGraphic .

Мы получим TextBlocks и создадим объекты OcrGraphic для каждого текстового блока, обнаруженного процессором. Логику их рисования мы реализуем на следующем шаге.

Теперь, когда процессор готов, мы должны настроить textRecognizer для его использования. Вернитесь к последнему оставшемуся TODO в методе createCameraSource в OcrCaptureActivity :

Теперь запустите приложение. На этом этапе при наведении камеры на текст вы увидите отладочные сообщения «Text detected!» в Android Monitor Logcat ! Но это не очень наглядный способ визуализации того, что видит TextRecognizer , правда?

На следующем шаге мы отрисуем этот текст на экране.

Рисование текста на экране

Давайте реализуем метод draw в OcrGraphic . Нам нужно понять, есть ли на изображении текст, преобразовать координаты его границ в рамки канваса, а затем нарисовать и границы, и текст.

Запустите приложение и протестируйте его на этом образце текста:

Вы должны увидеть, что на экране появляется рамка с текстом в ней! Вы можете поиграть с цветом текста, используя TEXT_COLOR .

Как насчет этого?

Рамка вокруг текста выглядит правильно, но текст находится в нижней её части.

Это связано с тем, что движок передает весь текст, который он распознает в TextBlock в виде одного предложения, даже если он видит предложение, разбитое на несколько строк. Если вам нужно получить целое предложение, то это очень удобно. Но что, если вы хотите знать, где расположена каждая отдельная строка текста?

Вы можете получить Lines из TextBlock , вызвав getComponents , а затем, перебирая каждую строку, запросто получить её местоположение и текст внутри неё. Это позволяет рисовать текст в том месте, где он действительно появляется.

Попробуйте снова этот текст:

Отлично! Вы даже можете разбивать найденный текст на ещё более мелкие составляющие, исходя из ваших потребностей. Можно вызвать getComponents на каждой строке и получить Elements (слова на латинице). Есть возможность настройки textSize , чтобы текст занимал столько места, сколько занимает реальный текст на экране.

Воспроизведение текста при нажатии на нём

Теперь текст с камеры преобразуется в структурированные строки, и эти строки отображаются на экране. Давайте сделаем с ними что-нибудь еще.

Используя TextToSpeech API , встроенный в Android , и метод contains в OcrGraphic , мы можем научить приложение говорить вслух, при нажатии на текст.

Сначала давайте реализуем метод contains в OcrGraphic . Нам просто нужно проверить, находятся ли координаты x и y в пределах рамки отображаемого текста.

Вы можете заметить, что здесь много общего с методом Draw ! В настоящем проекте вам следовало бы добиться переиспользования кода, но здесь мы оставим всё как есть просто ради примера.

Теперь перейдем к методу onTap в OcrCaptureActivity и обработаем нажатие по тексту, если он есть в этом месте.

Вы можете запустить приложение и через Android Monitor Logcat убедиться, что нажатие на текст действительно обрабатывается.

Давайте же заставим наше приложение говорить! Перейдите в начало Activity и найдите метод onCreate . При запуске приложения мы должны инициализировать движок TextToSpeech для дальнейшего использования.

Несмотря на то, что мы корректно инициализировали TextToSpeech , как правило, всё равно нужно обрабатывать общие ошибки, например, когда движок всё ещё не готов при вашем первом нажатии на текст.

Читать еще:  Java sql time

TextToSpeech также зависим от языка распознавания. Вы можете изменить язык на основе языка распознанного текста. Распознавание языка не встроено в Mobile Vision Text API , но оно доступно через Google Translate API . В качестве языка для распознавания текста можно использовать язык устройства пользователя.

Отлично, осталось только добавить код воспроизведения текста в методе onTap .

Теперь, когда вы запустите приложение и нажмёте на обнаруженный текст, ваше устройство воспроизведёт его. Попробуйте!

Завершение

Теперь у вас есть приложение, которое может распознавать текст с камеры и проговаривать его вслух!

Полученные знания по распознаванию текста вы можете применить и в других ваших приложениях. Например, читать адреса и номера телефонов с визитных карточек, производить поиск по тексту с фотографий различных документов. Одним словом, применяйте OCR везде, где вам может потребоваться распознать текст на изображении.

Java распознавание изображений

Python является одним из самых перспективных языков, позволяющий воплощать искусственный интеллект в жизнь. В уроке мы создадим распознавание объектов при помощи Python и ImageAI.

Одна из самых перспективных наук о компьютерах и программах – компьютерное зрение. Его смысл заключается в способности ПК к распознанию и определению сути картинки. Это важнейшая область в искусственном интеллекте, включающая сразу несколько действий: распознание содержимого фотографии, определение предмета и его классификация или генерация. Поиск объектов на картинке, скорее всего, является важнейшей областью компьютерного зрения.

Определение вещей или живых существ на фотографии активно используется в следующих сферах:

  • Поиск автомобилей;
  • Система распознания людей;
  • Поиск и подсчёт количества пешеходов;
  • Усиление системы безопасности;
  • Создание беспилотных автомобилей и т. д.

Сегодня удалось разработать много методов для поиска объектов, которые применяются в зависимости от целевой области. В этой сфере, как и в других направлениях использования ИТ-технологий, многое напрямую зависит от программиста. Это отличный инструмент для творчества, с которым «творение» может получить собственный ум. Как использовать интеллект программы уже зависит от творческого мышления разработчика.

Технология действительно перевернула представление об искусственном интеллекте. В дальнейшем она стала основой для следующих методов R-CNN, Fast-RCNN, Faster-RCNN, RetinaNet. Среди них и высокоточные, быстрые методы — SSD и YOLO. Для применения перечисленных алгоритмов, в основе которых глубокое обучение, требуется наличие глубоких познаний в математике и доскональное понимание фреймворков.

Начнем

Рассмотрение советов следует начинать с функциональной библиотеки ImageAI , написанной на Python. Данный фреймворков позволяет с лёгкостью интегрировать инновационные достижения в сфере компьютерного зрения в уже разработанные или новые программы.

Установка Python

Без инсталляции Python 3 здесь не обойтись. Нужно всего лишь загрузить файл с оф. сайта и запустить процесс установки.

Создание зависимостей

Сейчас самое время для того, чтобы посредством pip установить зависимости. Принцип создания команды прост: pip install и название библиотеки (основные фреймворки описаны в списке ниже). Как это выглядит:

Какие фреймворки нужно добавить:

Просмотреть все фреймворки и команды для их установки вы можете на официальном сайте с документацией по ImageAI .

Retina Net

Теперь стоит скачать файл для модели Retina Net. Он участвует в процессе идентификации объектов на изображениях.

Как только зависимости установлены, уже есть возможность написать первые строки кода для вычисления предметов на картинках. Следует создать файл FirstDetection с расширением .py . В созданный файл следует вставить код из следующего раздела. Ещё нужно скопировать файл из модели Retina и добавить картинку для обработки в папку с файлом Python.

Тестирование

Создайте файл и разместите в нем следующий код:

Осталось запустить код и ожидать появление результатов работы в консоли. Дальше следует пройти в каталог, где установлен файл FirstDetection.py . Здесь же должна появиться новая фотография или несколько. Чтобы лучше понимать, что произошло, следует открыть изначальную и новую картинку.

Время рассмотреть принцип работы кода:

Описание строк:

  • 1 строка: перенос ImageAI и класса для поиска предмета;
  • 2 строка: импорт Python os;
  • 4 строка: создание переменной, в которой указывается путь к директории с файлом Python, RetinaNet, моделью и образом.

Описание строк:

  • 1 строка: объявление нового класса для поиска объектов;
  • 2 строка: установка типа модели RetinaNet;
  • 3 строка: указание пути к модели RetinaNet;
  • 6 строка: загрузка модели внутрь класса для поиска;
  • 8 строка: вызов функции обнаружения (распознавания объектов) и запуск парсинга пути начального и конечного изображений.

ImageAI имеет поддержку массы различных настроек для поиска объектов. Например, можно настроить извлечение всех найденных объектов во время обработки картинки. Класс поиска способен создать отдельную папку с названием image, а затем извлечь, сохранить и вернуть массив с путём ко всем объектам.

Видео обзор

Для более детального рассмотрения библиотеки советуем просмотреть видео обзор этой библиотеки. В ходе видео будет показано не только распознавание объектов на фото, но также вы узнаете про рассмотрение объектов на видео.

Ссылки из видео:

В ходе урока было создано распознавание объектов на видео. Код приведен ниже:

Заключение

В конце советов по глубокому изучению следует добавить небольшую выборку из самых полезных функций ImageAI, ведь её возможности выходят далеко за пределы обычного обнаружения объектов:

  • Установка порога минимальной вероятности: стандартные настройки исключают из выборки все объекты с вероятностью до 50% . Они даже не записываются в лог. При желании можно изменить в большую или меньшую сторону вероятности для определённых случаев;
  • Особые настройки обнаружения: с помощью класса CustomObject, есть возможность попросить приложение передавать информацию об определении некоторых уникальных объектов;
  • Скорость поиска: существует возможность вручную снизить время, которое затрачивает приложение для сканирования фотографии. Есть 3 режима работы: fast, faster, fastest;
  • Входящие типы: поддерживает указание в качесиве пути картинке – Numpy-массива, а также файлового потока;
  • Выходные типы: можно установить, чтобы функция detectObjectsFromImage возвращала картинки файлом или массивом Numpy.

Конечно, охватить всё компьютерное зрение нереально даже за целую книгу, но основные понятия, надеемся, мы смогли донести.

Ссылка на основную публикацию
Adblock
detector