Защита проекта VBA в MS Excel

Всем привет! Я обычный пользователь MS Excel. Не являющийся профессиональным программистом, но накопивший достаточно опыта, для установки и обхода защиты проектов VBA.

Дисклеймер:

В данной статье рассмотрены виды защиты проектов VBA, от несанкционированного доступа. Их сильные и слабые стороны – ранжирование.

Цель статьи показать слабые и сильные стороны каждого вида защиты проекта VBA в MS Office.

Демонстрация разработанных инструментов, в надстройке Macro Tools VBA, для снятия и установки той или иной защиты. 

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

Главная панель Надстройки Macro Tools VBA

Первый вид защиты — Обычный пароль

Время на снятие: мгновенно

Недостаток: быстрый доступ к запароленному модулю VBA

Стандартный инструмент (В среде VBE: панель Tools -> VBAProject Properties -> Protection). 

Самая легко снимающаяся защита. В интернете легко находится код, для снятия данной защиты. 

Данную защиту можно снять следующим инструментом:

Второй вид защиты — Project is Unviewable

Время на снятие:  от 10 до 15 мин (в ручную)

Недостаток: доступ к исходному коду модуля VBA

Один из самых распространённых видов защит.  Встречается в 95% файлах с защитой модуля VBA. При попытке открыть проект, открывается диалоговое окно, с  сообщением:  Project is Unviewable.


Большинство пользователей Excel, не могут снять данную защиту, так как она имеет множество вариации и нюансов, для ее снятие нужно иметь представление о внутренней структуре файла Excel.

Основан, данный вид защиты, на изменение ключей:

DPB=«0B09CE0F8E108E108E»  GC=«CCCE09520B120C120CED»</source> в файле `vbaProject.bin`.   <img src="https://lh3.googleusercontent.com/RACGzRAHgHZAhDpkh5qo-B4MuDJZ-CMUd-BTvwgXBsDl1LMUrQcFmZ5whRrk_cJlZ7p2X5ikFegRPXl2jLoLowoWwH78-T9CANv6rqCCzB8Hoi14DFUaqBr1A0K8KSeccfr-Iss" align="center"> <h2>Кратко, как создается данная защита</h2> Для создания данной защиты нужно, разархивировать файл Excel. Перейти в архиве в папку <strong>xl</strong>, открыть файл <strong>vbaProject.bin</strong>,  в конце файла находятся наши ключи, редактируем значения ключей на пусто, сохраняем файл. Переводим наш архив, обратно в файл Excel. Готово!   Это самый простой вариант данной защиты, но существует множество модификаций.  Алгоритм снятия защиты <strong>Project is Unviewable.</strong>  1)      Разархивируем подопытный файл, переходим в файл  <strong>…\xl\_rels\workbook.xml.rels</strong>  <strong> <img src="https://lh6.googleusercontent.com/SbsqJbKGS7gLk4g26PH3w481mTv15EozjjsNXzbQnDFMRW0DKdk9_X3ymJJN_0F23zBePuBhEYpIRgqzdOK0MID51HdemHlTo1QaTbWECgjNrFs9_8d7Ups-IImsI36aKTReT8g" align="center"> </strong>  2)      В файле<strong> workbook.xml.rels</strong>  ищем строку, содержащую слово  <strong>vbaProject</strong>, обычно имеет следующий вид:  <strong><Relationship Id="rId6" Type="http://schemas.microsoft.com/office/2006/relationships/vbaProject" Target="vbaProject.bin"/></strong>. В этой строке нас интересует ключ <strong>Target</strong>,<strong> </strong>и<strong> </strong>его значение. Значение является название файла, в котором находится проект VBA. Иногда, защищающий меняет значения ключа на <strong>printerSettings.bin</strong>.<strong> </strong>Получается маскировка файла с проектом VBA  под другой файл.  3)      Открываем на редактирование файл, указанный в  ключе <strong>Target</strong>, ищем в файле ключи  <strong>CMG, DPB, GC</strong>. И меняем в их названиях любую букву на любую другую, например: <strong>CMC, DPC, CC</strong>. При поиске нужно быть аккуратным, так как защищающий может поместить  в проект форму,  подписью повторяющую один из ключей, например такую: <strong>DPB=«0B09CE0F8E108E108E»</strong>. При ее изменении проект VBA, будет удален из книги Excel.  Сохраняем и закрываем файл.  4)      Переводим архив обратно в файл Excel.  5)      Запускаем приложение Excel, выполняем следующее: в <i>Центре управления безопасностью -> Параметры макросов  -> Отключить все макросы без уведомления</i>. Перезапускаем Excel. Данная операция нужна, для блокировки защиты, которую иногда ставят авторы макросов. Данная защита реализована следующим образом. В модуле VBA «<strong>ЭтаКнига</strong>», создается процедуры, реагирующие на события открытия книги или закрытия книги. Эти события обычно проверяют, наличие пароля на проект VBA, запрет сохранения и прочее.  6)      Открываем файл. Если все правильно сделано то, Excel, будет ругаться на не правильные ключи, которые мы отредактировали, в пункте 3. Жмем, да, пока данные сообщения не закончатся и диалоговое окно закроется.         Если данное сообщение не появляется то, вы отредактировали не файл который содержит проект VBA.  <img src="https://lh6.googleusercontent.com/-JxpNp2qXgtcvjcx09dOaY0xCTuKNSXMKmcf1BXuto99S7Z_GE-tDDZj0sZqXWY7Trq2rEZd1KJikHM41KqmHkzsMdIG_OXO3FdSIMyMB2bCfX7qKYYu1VVmExD6AaNuJiB8YKo" align="center"> 7)      Открываем проект VBA. После всего, проект VBA должен быть доступен.  8)      Но иногда защита не снимается, тогда нужно сохранить файл, проверить, что он действительно сохранился! И проделать повторно операции с 1 по 7. Обычно так происходит когда в файле <strong>workbook.xml.rels </strong>в ключе <strong>Target</strong>  установлено <strong>printerSettings.bin</strong>.<strong> </strong>При сохранение,  Excel  исправляет это на значение на <strong>vbaProject.bin</strong>  Данную защиту можно установить и снять следующим инструментом:  <img src="https://lh5.googleusercontent.com/dqxpKVuWZ9_2FPe-c4IVoQyeYy5wqdRrsqZjoTmpRW9uge7SFttkYkhJJvjbW7oVu2uTXp73sTPm6mYcihA_tZt4tCci3rn3LExJXgJqGLDGp25ZkJbMtrXc1EkmJQT7qMa_5rQ" align="center"> <strong>Третий вид защиты — Hidden  Module, скрытые модули VBA</strong>  Время на снятие:  от 15 до 20 мин (нужен редактор OLE — объектов, Structured Storage Viewer, например.  Недостаток: доступ к коду модуля VBA  Менее распространенный вид  защиты обычно встречается в комбинации с защитой <strong>Project is Unviewable. </strong>При установке данной защиты модуль VBA не отображается в проекте книги Excel. О его существовании можно узнать, проанализировав код VBA (что требует время!) или открыть файл Excel в программе  <strong>OpenOffice  или  LibreOffice </strong>(так же можно смотреть код при защите Project is Unviewable, но данный способ не дает возможность получить рабочий файл, без пароля).   <img src="https://lh6.googleusercontent.com/dA3BfIzuQg79o3IP_Ez4x8pumirxRSn3JdNX-SE-CLrJ7X-V9HwgbrBujuel9uasdeVI5ZGwzpFMaMyKr0cNWxLn4BJTEVC-WRXuxQnfH8XjzIosuKr5GrZGriv4X7HuTDhQfzk" align="center"> Просмотр кода VBA в  <strong>LibreOffice</strong>  <h2>Кратко, как создается данная защита</h2> Для создания данной защиты нужно отредактировать файл  с проектом VBA — <strong>vbaProject.bin  </strong>или <strong>printerSettings.bin</strong>,<strong> </strong>в зависимости от настроек в файле <strong>…\xl\_rels\workbook.xml.rels</strong>. В конце файла удаляются строки вида: <strong>Module1=32, 32, 635, 330, Z</strong>.  С нужными названиями модулей.  <img src="https://lh6.googleusercontent.com/kGIfWR-aM8dJ54j9YqQI4RhfqvYDeHJDm_22kelGtwasIa5nshjoqdSFBGJhdVVWGgN0URV55fH5WawjcPJM2GpC7qgplXrqcADmWgEahnqc3__DmhCMCeJkaDpQqjdWf-XTLy0" align="center"> Для снятия данной защиты нужно в файле <strong>vbaProject.bin — </strong>восстановить удаленные записи модулей.  Данную защиту можно установить следующим инструментом.  <img src="https://lh5.googleusercontent.com/eYCYXgNpc5vhun4VWEBaLR8VA-co7c9qv2af8OhAvJ1KyPtplzfmZQt9d_jROYG0ci_ysmkd6jIgCzYX4mv6sarBefOngkayo8l-d2wKWGBoJTNvJ5EsWK2-NUHpbtV7VczTZg8" align="center"> <h2>Четвертый вид защиты — Обфускация кода</h2> Время на снятие:  неизвестно, зависит от объема кода и пере использования частей кода  <img src="https://lh6.googleusercontent.com/_O09MKRA_Wg2GupSqHCnAeGJhl7zM3VU_s-5xWF9Cz3AuFefpYnzJhrUwRCVWBneumlfXqz6TSiJY6k30oE0gu9v0sx1cVxUFrN-YEj8PV6aQJ0TZj_6XBqZgxItW6VHNEsrBIY" align="center"> <i> Обфусцированный код VBA</i>  Недостаток:  необходимость тестирование файла после обфускации, на работоспособность  Крайне редкий вид защиты, основанный на изменении исходного кода VBA, в не удобочитаемый вид для человека. Удаляются все комментарии, форматирование кода, переименовываются названия всех переменных,  процедур, функций, модулей и прочего. Злоумышленнику никогда не удастся восстановить первоначальный вид кода, и потребует достаточно много времени для, его восстановления в удобно читаемый вид для человека.    Для де-обфускации кода нужно иметь  время, специализированное ПО.  Данную защиту можно установить следующим инструментом.  <img src="https://lh5.googleusercontent.com/pKd3MdYYw84gzK1HtxteUPQYj302kXbkGLgYX6Qnx8U8Wck9zezpL9WDCMTF_-yIZ0mwMWFJeDg1k72x7zKJxn-HnQUH1uqNQJE82G3TSwTP30kOqqEeOb_KHQnu_0J7HBhGfDw" align="center"> <h2>Пятый вид защиты — Перенос кода в dll</h2> Время на снятие:  неизвестно, зависит от языка программирования и квалификации  Недостаток:  необходимости в дополнительном  файле dll  Один из самых редких видов защиты. Основная идея перенос основного кода в отдельную библиотеку dll, написанную на любом другом языке программирования. Не распространённость данный вид защиты получил по следующей причине,  необходимости за файлом Excel, «таскать» дополнительный файл, dll.  Для получения доступа к коду dll, нужно обладать специальными знаниями.  <h2>Заключение</h2> В заключении хочу выделить бесполезность защит:  <strong>Project is Unviewable и Hidden  Module</strong> которые, по существу ни отчего не защищают. Позволяют просматривать код VBA, без изменения исходного  файла, в таких программах как <strong>OpenOffice  </strong>или<strong>  LibreOffice. </strong>Так и снимаются без особых проблем.

ссылка на оригинал статьи https://habr.com/ru/post/514440/

Три редко используемых возможности Python 3, о которых каждый должен знать

Python 3 существует уже какое-то время и довольно много разработчиков, особенно те, кто только начинает свой путь в Python, уже используют эту версию языка. Несмотря на то, что множество новых возможностей широко используются, похоже, что некоторые остались за кадром. В этой статье я расскажу о трех наименее известных, но полезных, возможностях. Я знаю о них из других языков и они делают Python 3 классным.

Данная статья является переводом 3 Neglected Features in Python 3 That Everyone Should Be Using.

Перечисления

Перечисления я много использовал в Java и Swift. Продолжаю их использовать теперь и в Python.

Объявление перечисления в Python очень просто сделать и это было возможно и до третьей версии (хотя и с ограничениями):

from enum import Enum  class State(Enum):   AIR = 0   LAND = 1   SEA = 2    myState = State.AIR  # Выводит 0 print(myState.value) # Выводит AIR print(myState.name) 

В коде выше перечисление вводится путем объявления класса, наследованного от Enum. А далее просто описываются все нужные состояния. В моем случае: AIR, LAND и SEA.

Функциональность, которая была добавлена в Python 3 — возможность использовать .value и .name. Они позволяют получить число и строку соответствующие перечислению.

Например, вывод значения State.LAND.name будет LAND.

Перечисления полезны в коде, когда вы хотите иметь некоторые текстовые идентификаторы для констант. Например, вместо сравнения состояния с 0 или 1 гораздо показательнее сравнивать с State.MOVING или State.STATIONARY. Константы могут меняться и если кто-то посмотрит код позже, то слово MOVING даст гораздо больше понимания, чем 0. В результате сильно повышается читабельность кода.

Больше информации можно найти в официальной документации Python 3 по Enum.

Форматирование

Добавленные в версии 3.6, fstrings — это мощное средство форматирования текста. Они позволяют создавать гораздо более читабельный и безошибочный код (чем я наслаждаюсь после перехода из Java). Это лучше, чем format, который использовался ранее в Python. Вот пример использования format:

name = 'Михаил' blog_title = 'codeatcpp.com'  # Привет, меня зовут Михаил и я пишу в своем блоге codeatcpp.com. a = "Привет, меня зовут {} и я пишу в своем блоге {}.".format(name, blog_title) 

Легко заметить пустые фигурные скобки внутри строки и после список с названиями переменных в определенном порядке.

Теперь посмотрим на такой же код, но с использованием fstring — более читабельный и очень похожий на способ форматирования в Swift.

name = 'Михаил' blog_title = 'codeatcpp.com'  # Привет, меня зовут Михаил и я пишу в своем блоге codeatcpp.com. a = f"Привет, меня зовут {name} и я пишу в своем блоге {blog_title}." 

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

Использование fstring дает более читабельный и более простой в поддержке код, чем использование классических подходов.

Классы данных

Классы данных может быть более непонятная тема, чем предыдущие, поэтому потребует чуть больше пояснений. Классы данных — это что-то, что мне очень понравилось в языке Kotlin, поэтому я люблю их использовать также и в Python.

Класс данных — это класс, единственная цель которого хранить данные. Класс содержит переменные, которые можно читать и писать, но не имеет никакой дополнительной логики.

Представьте, что у вас есть программа, в которой вам нужно передавать строку и массив чисел между разными классами. У вас могут быть методы вроде pass(str, arr), но гораздо удобнее сделать класс, который содержит строку и массив в качестве единственных членов класса.

Использование класса данных лучше показывает что вы пытаетесь сделать и также упрощает создание юнит-тестов.

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

from dataclasses import dataclass  # Определяем класс данных @dataclass class Vector3D:     x: int     y: int     z: int        # Создаем вектор u = Vector3D(1,1,-1)  # Выводит: Vector3D(x=1, y=1, z=-1) print(u) 

Здесь легко заметить, что определение класса данных очень похоже на определение обычного класса, за исключением того, что используется декоратор @dataclass и затем каждое поле определяется в виде имя: тип.

Хотя функциональность созданного Vector3D сильно ограничена, суть использования класса данных в том, чтобы повысить эффективность и уменьшить количество ошибок в коде. Ведь гораздо лучше передавать в качестве параметра Vector3D, чем набор переменных типа int.

Больше информации про декоратор @dataclass можно найти в официальной документации Python 3.

Заключение

Дайте знать в комментариях, если вы попробовали эти новые возможности. Интересно будет услышать про новые сценарии их использования. Удачного кодирования!

ссылка на оригинал статьи https://habr.com/ru/post/514444/

Дайджест свежих материалов из мира фронтенда за последнюю неделю №427 (3 — 9 августа 2020)

Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.

Медиа    |    Веб-разработка    |    CSS    |    JavaScript    |    Браузеры

Медиа

podcast Подкаст «Веб-стандарты» №241. Сайд-подкасты, онлайн-события, новости браузеров, main вместо master, поиски эмпатии
podcast Подкаст CSSSR: Тестирование Vue-компонентов, roadmap разработки Github, релизы Angular, Next.js и git
podcast Подкаст «Фронтенд Юность (18+)» №149: Нагибабель топит ледники

Веб-разработка

habr Технологии фронтенд-разработки, на которые вы, возможно, не обратили внимания
habr Профессиональное применение инструментов разработчика Chrome: 13 советов
en Руководство по веб-разработке для новичков «The no jargon guide» — Развертывание вашего первого сайта
en Релиз Emmet 2 для Sublime Text

CSS

habr CSS Grid понятно для всех
habr Медиазапросы в SCSS — ещё один удобный способ использования @media screen
habr 6 мощных возможностей CSS, которые позволяют обойтись без JavaScript
en Drop-Shadow: недооцененный CSS фильтр
en Computed Values: больше, чем кажется на первый взгляд
en Легкое решение для Masonry на CSS Grid c фолбеком на JavaScript
en Углубляемся в детали свойства Flex
en Современные решения на CSS grid для большинства проблем с раскладкой
en Реализация реалистичного перелистывания страниц на CSS
en Оптимизация CSS для более быстрой загрузки страницы
en Интерактивный словарь CSS
en Больше контроля над CSS Borders с помощью background-image
en font-weight: 300 может быть вредным (и возможное решение с fontconfig)
en Супергеройский леайут — объединение CSS Grid и CSS Shapes

JavaScript

habr Мои любимые трюки в JavaScript
en Node Modules в состоянии войны: почему CommonJS и ES Modules не могут работать вместе
en RxJS & Firebase 101
en Анонс нового веб-сайта для TypeScript
en Сравнение моделей реактивности — React vs Vue vs Svelte vs MobX vs Solid
en 1Keys — Как я сделал пианино всего в 1 КБ JavaScript

Браузеры

В Firefox началась активация защиты от отслеживания перемещений через редиректы
Новый Edge всем и каждому. Microsoft заблокировала возможность деинсталляции браузера из Windows 10
Рыночная доля Chrome и Edge продолжает расти, а Firefox теряет популярность

Просим прощения за возможные опечатки или неработающие/дублирующиеся ссылки. Если вы заметили проблему — напишите пожалуйста в личку, мы стараемся оперативно их исправлять.

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.

ссылка на оригинал статьи https://habr.com/ru/post/514448/

Три мушкетара — Event Sourcing, Event Storming и Event Store — вступают в бой: Часть 1 — пробуем Event Store ДБ


Привет, Хабр! Решил я значит на время отойти от Scala, Idris и прочего ФП и чуть чуть поговорить о Event Store — базе данных в которой можно сохранят события в потоки событий. Как в старой доброй книге у нас тоже мушкетеров на самом деле 4 и четвертый это DDD. Сначала я с помощью Event Storming выделю команды, события и сущности с ними связанные. Потом сделаю на их основе сохранение состояния объекта и его восстановление. Буду я делать в этой статье обычный TodoList. За подробностями добро пожаловать под кат.

Содержание

  • Изучаю Scala: Часть 2 — Todo лист с возможностью загрузки картинок

Ссылки

Исходники
Образы docker image
Event Store
Event Soucing
Event Storming

Собственно говоря Event Store это БД которая предназначена для хранения событий. Так же она умеет создавать подписки на события чтобы их можно было как-то обрабатывать. Так же там есть проекции которые так же реагируют на события и на их основе аккумулирую какие-то данные. Например можно при событии TodoCreated увеличивать какой-то счетчик Count в проекции. Пока что в этой части я буду использовать Event Store как Read и Write Db. Дальше в следующих статьях я создам отдельную БД для чтения в которую будут данные записываться на основе событий сохраненных в БД для записи тобишь Event Store. Так же будет пример того как делать «Путешествия во времени» откатывая систему к состоянию которая она имела в прошлом.
И так начнем Event Stroming. Обычно для его проведения собирают все заинтересованных людей и экспертов которые рассказывают какие события в той предметной области которую ПО будет моделировать. Например для ПО завода — ИзделиеИзготовлено. Для игры — ПолученУрон. Для Финансового ПО — ДенгиЗачисленыНаСчет и прочее в этом духе. Так как наша предметная область это максимально простой TodoList то событий у нас будет немного. И так, запишем на доске события нашей предметной области (домена)

Теперь добавим команды которые эти события вызывают.

Далее сгруппируем эти события и команды вокруг сущности с изменением состояния которой они связаны.

Команды у меня превратятся просто в названия методов сервиса. Приступим к реализации.
Сначала опишем События в коде.

    public interface IDomainEvent     {       //Техническое поле. Для сохранения id события в Event Strore         Guid EventId { get; }        //Техническое поле. Сюда будем записывать номер события в стриме Event Store         long EventNumber { get; set; }     }      public sealed class TodoCreated : IDomainEvent     {        //Id созданного Todo         public Guid Id { get; set; }        //Имя созданного Todo         public string Name { get; set; }         public Guid EventId => Id;         public long EventNumber { get; set; }     }      public sealed class TodoRemoved : IDomainEvent     {         public Guid EventId { get; set; }         public long EventNumber { get; set; }     }      public sealed class TodoCompleted: IDomainEvent     {         public Guid EventId { get; set; }         public long EventNumber { get; set; }     } 

Теперь наше ядро — сущность

    public sealed class Todo : IEntity<TodoId>     {         private readonly List<IDomainEvent> _events;          public static Todo CreateFrom(string name)         {             var id = Guid.NewGuid();             var e = new List<IDomainEvent>(){new TodoCreated()                 {                     Id = id,                     Name = name                 }};             return new Todo(new TodoId(id), e, name, false);         }          public static Todo CreateFrom(IEnumerable<IDomainEvent> events)         {             var id = Guid.Empty;             var name = String.Empty;             var completed = false;             var ordered = events.OrderBy(e => e.EventNumber).ToList();             if (ordered.Count == 0)                 return null;             foreach (var @event in ordered)             {                 switch (@event)                 {                     case TodoRemoved _:                         return null;                     case TodoCreated created:                         name = created.Name;                         id = created.Id;                         break;                     case TodoCompleted _:                         completed = true;                         break;                     default: break;                 }             }             if (id == default)                 return null;             return new Todo(new TodoId(id), new List<IDomainEvent>(), name, completed);         }          private Todo(TodoId id, List<IDomainEvent> events, string name, bool isCompleted)         {             Id = id;             _events = events;             Name = name;             IsCompleted = isCompleted;             Validate();         }          public TodoId Id { get; }         public IReadOnlyList<IDomainEvent> Events => _events;         public string Name { get; }         public bool IsCompleted { get; private set; }          public void Complete()         {             if (!IsCompleted)             {                 IsCompleted = true;                 _events.Add(new TodoCompleted()                 {                     EventId = Guid.NewGuid()                 });             }         }          public void Delete()         {             _events.Add(new TodoRemoved()             {                 EventId = Guid.NewGuid()             });         }          private void Validate()         {             if (Events == null)                 throw new ApplicationException("Пустой список событий");             if (string.IsNullOrWhiteSpace(Name))                 throw new ApplicationException("Пустое название задачи");             if (Id == default)                 throw new ApplicationException("Пустой идентификатор задачи");         }     } 

Подключаемся к Event Store

            services.AddSingleton(sp =>             { //Подключается к TCP и в случае разрыва соединения пытается восстановить соединение.  //В самой строке есть опции для всего этого. Можно их в документации на оф сайте глянуть.                 var con = EventStoreConnection.Create(new Uri("tcp://admin:changeit@127.0.0.1:1113"), "TodosConnection");                 con.ConnectAsync().Wait();                 return con;             }); 

И так, главная часть. Собственно сохранение и чтение событий из Event Store

    public sealed class EventsRepository : IEventsRepository     {         private readonly IEventStoreConnection _connection;          public EventsRepository(IEventStoreConnection connection)         {             _connection = connection;         }          public async Task<long> Add(Guid collectionId, IEnumerable<IDomainEvent> events)         {             var eventPayload = events.Select(e => new EventData( //Id события                 e.EventId, //Тип события                 e.GetType().Name, //В виде Json (True|False)                 true, //Тело события                 Encoding.UTF8.GetBytes(JsonSerializer.Serialize((object)e)), //Метаданные события                 Encoding.UTF8.GetBytes((string)e.GetType().FullName)             )); //Добавляем в коллекцию событий сущности наше событие             var res = await _connection.AppendToStreamAsync(collectionId.ToString(), ExpectedVersion.Any, eventPayload);             return res.NextExpectedVersion;         }          public async Task<List<IDomainEvent>> Get(Guid collectionId)         {             var results = new List<IDomainEvent>();             long start = 0L;             while (true)             {                 var events = await _connection.ReadStreamEventsForwardAsync(collectionId.ToString(), start, 4096, false);                 if (events.Status != SliceReadStatus.Success)                     return results;                 results.AddRange(Deserialize(events.Events));                 if (events.IsEndOfStream)                     return results;                 start = events.NextEventNumber;             }         }          public async Task<List<T>> GetAll<T>() where T : IDomainEvent         {             var results = new List<IDomainEvent>();             Position start = Position.Start;             while (true)             {                 var events = await _connection.ReadAllEventsForwardAsync(start, 4096, false);                 results.AddRange(Deserialize(events.Events.Where(e => e.Event.EventType == typeof(T).Name)));                 if (events.IsEndOfStream)                     return results.OfType<T>().ToList();                 start = events.NextPosition;             }         }          private List<IDomainEvent> Deserialize(IEnumerable<ResolvedEvent> events) =>             events                 .Where(e => IsEvent(e.Event.EventType))                 .Select(e =>                 {                     var result = (IDomainEvent)JsonSerializer.Deserialize(e.Event.Data, ToType(e.Event.EventType));                     result.EventNumber = e.Event.EventNumber;                     return result;                 })                 .ToList();          private static bool IsEvent(string eventName)         {             return eventName switch             {                 nameof(TodoCreated) => true,                 nameof(TodoCompleted) => true,                 nameof(TodoRemoved) => true,                 _ => false             };         }         private static Type ToType(string eventName)         {             return eventName switch             {                 nameof(TodoCreated) => typeof(TodoCreated),                 nameof(TodoCompleted) => typeof(TodoCompleted),                 nameof(TodoRemoved) => typeof(TodoRemoved),                 _ => throw new NotImplementedException(eventName)             };         }     } 

Хранилище сущностей выглядит совсем просто. Мы достаем из EventStore события сущности и восстанавливаем ее из них или просто сохраняем события сущности.

    public sealed class TodoRepository : ITodoRepository     {         private readonly IEventsRepository _eventsRepository;          public TodoRepository(IEventsRepository eventsRepository)         {             _eventsRepository = eventsRepository;         }          public Task SaveAsync(Todo entity) => _eventsRepository.Add(entity.Id.Value, entity.Events);          public async Task<Todo> GetAsync(TodoId id)         {             var events = await _eventsRepository.Get(id.Value);             return Todo.CreateFrom(events);         }          public async Task<List<Todo>> GetAllAsync()         {             var events = await _eventsRepository.GetAll<TodoCreated>();             var res = await Task.WhenAll(events.Where(t => t != null).Where(e => e.Id != default).Select(e => GetAsync(new TodoId(e.Id))));             return res.Where(t => t != null).ToList();         }     } 

Сервис в котором происходит работа с репозиторием и сущностью

    public sealed class TodoService : ITodoService     {         private readonly ITodoRepository _repository;          public TodoService(ITodoRepository repository)         {             _repository = repository;         }          public async Task<TodoId> Create(TodoCreateDto dto)         {             var todo = Todo.CreateFrom(dto.Name);             await _repository.SaveAsync(todo);             return todo.Id;         }          public async Task Complete(TodoId id)         {             var todo = await _repository.GetAsync(id);             todo.Complete();             await _repository.SaveAsync(todo);         }          public async Task Remove(TodoId id)         {             var todo = await _repository.GetAsync(id);             todo.Delete();             await _repository.SaveAsync(todo);         }          public async Task<List<TodoReadDto>> GetAll()         {             var todos = await _repository.GetAllAsync();             return todos.Select(t => new TodoReadDto()             {                 Id = t.Id.Value,                 Name = t.Name,                 IsComplete = t.IsCompleted             }).ToList();         }          public async Task<List<TodoReadDto>> Get(IEnumerable<TodoId> ids)         {             var todos = await Task.WhenAll(ids.Select(i => _repository.GetAsync(i)));             return todos.Where(t => t != null).Select(t => new TodoReadDto()             {                 Id = t.Id.Value,                 Name = t.Name,                 IsComplete = t.IsCompleted             }).ToList();         }     } 

Ну собственно пока ничего впечатляющего. В следующих статься когда я добавлю отдельную БД для чтения все заиграет другими красками. Это сразу нам повесит консистентность со временем. Event Store и SQL БД по принципу мастер — слейв. Одна белая ES и много черных MS SQL из которых читают данные.
Лирическое отступление. В свете последних событий не мог не пошутить по поводу мастер слейв и черных белых. Эхе, уходит эпоха, будем внукам говорить что жили во времена когда базы при репликации называли мастер и слейв.
В системах где много чтения и мало записи данных (таких большинство) это даст прирост скорости работы. Собственно сама репликация мастер слейв она направленна на то что у вас замедляется запись (как и с индексами) но взамен ускоряется чтение за счет распределения нагрузки по нескольким БД.

ссылка на оригинал статьи https://habr.com/ru/post/514436/

Как работает Object Tracking на YOLO и DeepSort

Object Tracking — очень интересное направление, которое изучается и эволюционирует не первый десяток лет. Сейчас многие разработки в этой области построены на глубоком обучении, которое имеет преимущество над стандартными алгоритмами, так как нейронные сети могут аппроксимировать функции зачастую лучше.

Но как именно работает Object Tracking? Есть множество Deep Learning решений для этой задачи, и сегодня я хочу рассказать о распространенном решении и о математике, которая стоит за ним.

Итак, в этой статье я попробую простыми словами и формулами рассказать про:

  • YOLO — отличный object detector
  • Фильтры Калмана
  • Расстояние Махаланобиса
  • Deep SORT


YOLO — отличный object detector

Сразу нужно сделать очень важную пометку, которую нужно запомнить — Object Detection это не Object Tracking. Для многих это не будет новостью, но часто люди путают эти понятия. Простыми словами:

Object Detection — это просто определение объектов на картинке/кадре. То есть алгоритм или нейронная сеть определяют объект и записывают его позицию и bounding boxes (параметры прямоугольников вокруг объектов). Пока что речи о других кадрах не идет, и алгоритм работает только с одним.

Пример:

Object Tracking — здесь совсем другое дело. Здесь задача не просто определить объекты на кадре, но еще и связать информацию с предыдущих кадров таким образом, чтобы не терять объект, или сделать его уникальным.

Пример:

То есть Object Tracker включает в себя Object Detection для определения объектов, и другие алгоритмы для понимания какой объект на новом кадре принадлежит какому из предыдущего кадра.

Поэтому Object Detection играет очень важную роль в задаче трэкинга.

Почему YOLO? Да потому что YOLO считается эффективнее многих других алгоритмов для определения объектов. Вот небольшой график для сравнения от создателей YOLO:

Здесь мы рассматриваем YOLOv3-4, поскольку это самые последние версии и они эффективнее предыдущих.

Архитектуры разных Object Detectors

Итак, существует несколько архитектур нейронных сетей, созданных для определения объектов. Они в основном разделяются на «двухуровневые», такие как RCNN, fast RCNN и faster RCNN, и «одноуровневые», такие как YOLO.

«Двухуровневые» нейронные сети, перечисленные выше, используют так называемые регионы на картинке, чтобы определить, находится ли в этом регионе определенный объект.

Обычно это выглядит так (для faster RCNN, которая является самой быстрой из перечисленных двухуровневых систем):

  1. Подается картинка/кадр на вход
  2. Кадр прогоняется через CNN для формирования feature maps
  3. Отдельной нейронной сетью определяются регионы с высокой вероятностью нахождения в них объектов
  4. Дальше эти регионы с помощью RoI pooling сжимаются и подаются в нейронную сеть, определяющую класс объекта в регионах

Но в этих нейронных сетях есть две ключевые проблемы: они не смотрят на картинку «полностью», а только на отдельные регионы, и они относительно медленные.

В чем же крутость YOLO? В том, что эта архитектура не имеет двух проблем свыше, и она доказала неоднократно свою эффективность.

Вообще архитектура YOLO в первых блоках не сильно отличается по «логике блоков» от других детекторов, то есть на вход подается картинка, дальше создаются feature maps с помощью CNN (правда в YOLO используется своя CNN под названием Darknet-53), затем эти feature maps определенным образом анализируются (об этом чуть позже), выдавая на выходе позиции и размеры bounding boxes и классы, которым они принадлежат.

Но что такое Neck, Dense Prediction и Sparse Prediction?

С Sparse Prediction мы разобрались немного ранее — это просто повторение того, как двухуровневые алгоритмы работают: определяют по отдельности регионы и затем классифицируют эти регионы.

Neck (или «шея») — это отдельный блок, который создан для того, чтобы агрегировать информацию от отдельных слоев с предыдущих блоков (как показано на рисунке выше) для увеличения аккуратности предсказания. Если Вас заинтересовало это — можете погуглить термины «Path Aggregation Network», «Spatial Attention Module» и «Spatial Pyramid Pooling».

И, наконец, то, что отличает YOLO от всех других архитектур — блок под названием (на нашей картинке выше) Dense Prediction. На нем мы сфокусируемся чуть сильнее, потому что это очень интересное решение, которое как раз позволило YOLO вырваться в лидеры по эффективности определения объектов.

YOLO (You Only Look Once) несет в себе философию смотреть на картинку один раз, и за этот один просмотр (то есть один прогон картинки через одну нейронную сеть) делать все необходимые определения объектов. Как это происходит?

Итак, на выходе от работы YOLO мы обычно хотим вот это:

Что делает YOLO когда учится на данных (простыми словами):

Шаг 1: Обычно картинки решейпят под размер 416×416 перед началом обучения нейронной сети, чтобы можно было их подавать батчами (для ускорения обучения).

Шаг 2: Делим картинку (пока что мысленно) на клетки размером axa. В YOLOv3-4 принято делить на клетки размером 13×13 (о различных скейлах поговорим чуть позже, чтобы было понятнее).

Теперь фокусируемся на эти клеточках, на которые мы разделили картинку/кадр. Такие клетки, которые называются grid cells, лежат в основе идеи YOLO. Каждая клетка является «якорем», к которому прикрепляются bounding boxes. То есть вокруг клетки рисуются несколько прямоугольников для определения объекта (поскольку непонятно, какой формы прямоугольник будет наиболее подходящим, их рисуют сразу несколько и разных форм), и их позиции, ширина и высота вычисляются относительно центра этой клетки.

Как же рисуются эти прямоугольники (bounding boxes) вокруг клетки? Как определяется их размер и позиция? Здесь в борьбу вступает техника anchor boxes (в переводе — якорные коробки, или «якорные прямоугольники»). Они задаются в самом начале либо самим пользователем, либо их размеры определяются исходя из размеров bounding boxes, которые есть в датасете, на котором будет тренироваться YOLO (используется K-means clustering и IoU для определения самых подходящих размеров). Обычно задают порядка 3 различных anchor boxes, которые будут нарисованы вокруг (или внутри) одной клетки:

Зачем это сделано? Сейчас все будет понятно, так как мы обсудим то, как YOLO обучается.

Шаг 3. Картинка из датасета прогоняется через нашу нейронную сеть (заметим, что кроме картинки в тренировочном датасете у нас должны быть определенны позиции и размеры настоящих bounding boxes для объектов, которые есть на ней. Это называется «аннотация» и делается это в основном вручную).

Давайте теперь подумаем, что нам нужно получить на выходе.

Для каждой клетки, нам нужно понять две принципиальные вещи:

  1. Какой из anchor boxes, из 3 нарисованных вокруг клетки, нам подходит больше всего и как его можно немного подправить для того, чтобы он хорошо вписывал в себя объект
  2. Какой объект находится внутри этого anchor box и есть ли он вообще

Какой же должен быть тогда output у YOLO?

1. На выходе для каждой клетки мы хотим получить:

2. Output должен включать в себя вот такие параметры:

Как определяется objectness? На самом деле этот параметр определяется с помощью метрики IoU во время обучения. Метрика IoU работает так:

В начале Вы можете выставить порог для этой метрики, и если Ваш предсказанный bounding box будет выше этого порога, то у него будет objectness равной единице, а все остальные bounding boxes, у которых objectness ниже, будут исключены. Эта величина objectness понадобится нам, когда мы будем считать общий confidence score (на сколько мы уверены, что это именно нужный нам объект расположен внутри предсказанного прямоугольника) у каждого определенного объекта.

А теперь начинается самое интересное. Представим, что мы создатели YOLO и нам нужно натренировать ее на то, чтобы распознавать людей на кадре/картинке. Мы подаем картинку из датасета в YOLO, там происходит feature extraction в начале, а в конце у нас получается CNN слой, который рассказывает нам о всех клеточках, на которые мы «разделили» нашу картинку. И если этот слой рассказывает нам «неправду» о клеточках на картинке, то у нас должен быть большой Loss, чтобы потом его уменьшать при подаче в нейронную сеть следующих картинок.

Чтобы было совсем понятно, есть очень простая схема с тем, как YOLO создает этот последний слой:

Как мы видим из картинки, этот слой, размером 13×13 (для картинок изначального размера 416×416) для того, чтобы рассказывать про «каждую клетку» на картинке. Из этого последнего слоя и достается информация, которую мы хотим.

YOLO предсказывает 5 параметров (для каждого anchor box для определенной клетки):

Чтобы было легче понять, есть хорошая визуализация на эту тему:

Как можно понять их этой картинки, задача YOLO — максимально точно предсказать эти параметры, чтобы максимально точно определять объект на картинке. А confidence score, который определяется для каждого предсказанного bounding box, является неким фильтром для того, чтобы отсеять совсем неточные предсказания. Для каждого предсказанного bounding box мы умножаем его IoU на вероятность того, что это определенный объект (вероятностное распределение рассчитывается во время обучения нейронной сети), берем лучшую вероятность из всех возможных, и если число после умножения превышает определенный порог, то мы можем оставить этот предсказанный bounding box на картинке.

Дальше, когда у нас остались только предсказанные bounding boxes с высоким confidence score, наши предсказания (если их визуализировать) могут выглядеть примерно вот так:

Мы можем теперь использовать технику NMS (non-max suppression), чтобы отфильтровать bounding boxes таким образом, чтобы для одного объекта был только один предсказанный bounding box.

Нужно также знать, что YOLOv3-4 предсказывают на 3-х разных скейлах. То есть картинка делится на 64 grid cells, на 256 клеток и на 1024 клетки, чтобы также видеть маленькие объекты. Для каждой группы клеток алгоритм повторяет необходимые действия во время предсказания/обучения, которые были описаны сверху.

В YOLOv4 было использовано много техник для увеличения точности модели без сильной потери скорости. Но для самого предсказания оставили Dense Prediction таким же, как и у YOLOv3. Если Вы интересуетесь тем, что же такого магического сделали авторы, чтобы поднять так точность не теряя скорости, есть отличная статья, написанная про YOLOv4.

Надеюсь мне удалось немного донести то, как работает YOLO в целом (точнее последние две версии, то есть YOLOv3 и YOLOv4), и в Вас это пробудит желание воспользоваться этой моделью в будущем, или узнать про ее работу чуть подробнее.

Раз мы разобрались с, пожалуй, лучшей нейронной сетью для Object Detection (если оценивать скорость/качество), давайте наконец перейдем к тому, как же нам связывать информацию о наших определенных YOLO объектов между кадрами видео. Как программа может понимать, что человек на предыдущем кадре — это тот же человек, что и на новом?

Deep SORT

Для понимания этой технологии, следует сначала разобраться с парой математических аспектов — расстояние Махалонобиса и фильтр Калмана.

Расстояние Махалонобиса

Рассмотрим очень простой пример, чтобы интуитивно понять, что такое расстояние Махолонобиса и зачем оно нужно. Многим, наверное, известно, что такое евклидово расстояние. Обычно, это расстояние от одной точки до другой в евклидовом пространстве:


Допустим, у нас есть две переменные — X1 и X2. Для каждой из них у нас есть много измерений.

Теперь, допустим, у нас появилось 2 новых измерения:

Как понять, какое из этих двух значений наиболее подходит для нашего распределения? На глаз все очевидно — точка 2 нам подходит. Но вот евклидово расстояние до среднего значения у обоих точек одинаково. Соответственно, простое евклидово расстояние до среднего значения нам не подойдет.

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

С этим как раз справляется расстояние Махалонобиса. Поскольку в обычно датасетах переменных больше чем двух, вместо корреляции мы будем использовать ковариационную матрицу:

Что на самом деле делает расстояние Махалонобиса:

  • Избавляется от ковариации переменных
  • Делает дисперсию (variance) переменных равной 1
  • После этого использует обычное евклидово расстояние для трансформированных данных

Посмотрим на формулу, как вычисляется расстояние Махалонобиса:

Давайте разберемся, что означают составляющие нашей формулы:

  1. Эта разница — разница между нашей новой точкой и средними значениями для каждой переменной
  2. S — это ковариационная матрица, о которой мы говорили чуть ранее

Из формулы можно понять очень важную вещь. Мы по факту умножаем на перевернутую ковариационную матрицу. В этом случае, чем выше корреляция между переменными, тем скорее всего мы сократим дистанцию, так как будем домножать на обратное большему — то есть меньшее число (если простыми словами).

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

Фильтр Калмана

Чтобы понять, что это крутая, проверенная штука, которая может применяться в очень многих областях, достаточно знать, что фильтр Калмана применялся в 1960-х. Да-да, я намекаю именно на это — полет на Луну. Он применялся там в нескольких местах, включая работу с траекторий полета туда и обратно. Фильтр Калмана также часто применяется в анализе временных рядов на финансовых рынках, в анализе показателей различных датчиков на заводах, предприятиях и много где еще. Надеюсь, мне удалось вас немного заинтриговать и мы вкратце опишем фильтр Калмана и как он работает. Я также советую прочитать вот эту статью на Хабре, если Вы хотите узнать о нем подробнее.

Фильтр Калмана

Мы разберем сейчас одномерный пример, чтобы просто понять как работает фильтр Калмана для одной переменной. Для нескольких переменных алгоритм никак не отличается, правда там будут задействованы матрицы, о которых я кратко упомяну в процессе.

Допустим у нас есть термометр и чашка с водой, температуру которой мы хотим измерить. У термометра есть своя погрешность в 4 градуса по Цельсию. Допустим, настоящая температура воды в чашке порядка 72 градусов.

Фильтр Калмана считает 3 принципиально важные вещи:

1) Усиление Калмана (Kalman Gain):

Сразу прошу прощения за картинки, у Хабра какие-то проблемы с формулами (или я не до конца разобрался почему они у меня не отображаются).

2) Оцениваем значение нужной нам переменной на ЭТОМ этапе, с учетом посчитанного нами усиления Калмана и показаний с датчиков (в нашем случае, показание термометра), а также значений с предыдущего этапа, который происходил в прошлом.

3) Оцениваем НОВУЮ ошибку (неопределенность), чтобы применить ее на следующем этапе:

Таким образом, весь алгоритм может выглядеть вот так:

Итак, допустим мы задали изначальную температуру 69 градусов (прикинули примерно), с ошибкой в 2 градуса. Тогда, зная, что у термометра погрешность примерно 4 градуса (ага, супер точный термометр, ну это в моей Вселенной), наше значение KG по формуле (1) будет равно 2/(2+4) = 0.33. Дальше мы можем легко измерить наше новое значение воды (более точное), вставив термометр в воду и померив температуру. Получилось 70 градусов (допустим), и теперь мы можем более точно оценить температуру воды в стакане. Она будет равна по формуле (2), где нам теперь известно все, 68+0.33(70-68)=68.66. А наша новая ошибка по формуле (3) будет равна (1-0.33)2 = 1.32. Теперь на следующих шагах мы будем применять соответсвенно вычисленные нами новые значения для ошибки, температуры воды, и будем смотреть на новые показания прибора, когда мы его заново опустим в воду. График будет выглядеть примерно вот так:

Это потрясающе, как фильтр Калмана может избавлять нас от шума и помогать находить настоящее значение!

Если же у нас есть несколько величин, которые мы хотим использовать для предсказания, то мы будем использовать матрицы, а алгоритм не поменяется, правда немного изменится то, как мы считаем определенные величины (об этом вы можете прочитать в разных постах на Хабре о фильтре Калмана, одну из них я прикреплял выше).

DeepSORT — наконец-то!

Итак, мы теперь знаем, что такое фильтр Калмана и расстояние Махалонобиса. Технология DeepSORT просто связывает эти два понятия между собой для того, чтобы переносить информацию от одного кадра к другому, и добавляет новую метрику, под названием appearance. Сначала с помощью object detection определяются позиция, размер и класс одного bounding box. Потом можно в принципе применить Венгерский алгоритм, чтобы связать определенные объекты с ID объектов, которые раньше были на кадре и отслеживаются с помощью фильтров Калмана — и все будет супер, как в оригинальном SORT. Но технология DeepSORT позволяет улучшить точность определения и уменьшить количество переключений между объектами, когда, допустим, один человек на кадре загораживает ненадолго другого, и теперь человек, которого загородили, считается новым объектом. Как она это делает?

Она добавляет крутой элемент в свою работу — так называемый «внешний вид» людей, которые появляются на кадре (appearance). Этот внешний вид был натренирован отдельной нейронной сетью, которая была создана авторами DeepSORT. Они использовали порядка 1,100,000 картинок с более 1000 разных людей, чтобы нейронная сеть правильно предсказывала В оригинальном SORT есть проблема — так как там не используется внешний вид объекта, то по факту, когда объект что-то закрывает на несколько кадров (например, другой человек или колона внутри здания), то алгоритм затем присваивает другой ID этому человеку — в следствие чего так называемая «память» об объектах у оригинального SORT довольно краткосрочная.

Итак, теперь объекты имеют два свойства — их динамика движения и их внешний вид. Для динамики у нас есть показатели, которые фильтруются и предсказываются с помощью фильтра Калмана — (u,v,a,h,u’,v’,a’,h’), где u,v — это позиция предсказанного прямоугольника по X и Y, a — это соотношение сторон предсказанного прямоугольника (aspect ratio), h — высота прямоугольника, ну и производные по каждой величине. Для внешнего вида тренировали нейронную сеть, которая имела структуру:

И в конце выдавала feature vector, размером 128×1. А дальше, вместо того, чтобы считать дистанцию между определенными объектами с помощью YOLO, и объектами, за которыми мы уже следили на кадре, а потом приписывать определенный ID просто с помощью расстояния Махалонобиса, авторы создали новую метрику для подсчета расстояния, которая включает в себя как предсказания с помощью фильтров Калмана, так и «косинусовое расстояние» (cosine distance), как называют его иначе, коэффициент Отиаи.

В итоге расстояние от определенного YOLO объекта до предсказанного фильтром Калмана объекта (или объекта, который уже есть в числе тех, который наблюдался на предыдущих кадрах) равно:

Где Da — это дистанция по внешней схожести, а Dk — расстояние Махалонобиса. Дальше эта гибридная дистанция применяется в Венгерском алгоритме, чтобы как раз правильно отсортировать определенные объекты с имеющимися ID.

Таким образом, простая дополнительная метрика Da помогла создать новый, элегантный алгоритм DeepSORT, который применяется во многих проблемах и является довольно популярным в задаче Object Tracking.

Статья получилась довольно увесистой, спасибо тем, кто дочитал до конца! Надеюсь, мне удалось рассказать что-то новое и помочь Вам в понимании того, как работает Object Tracking на YOLO и DeepSORT.

ссылка на оригинал статьи https://habr.com/ru/post/514450/