(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Просмотр изображений OpenCV во время отладки C++ кода в Visual Studio

Источник: habrahabr
mmatrosov

Если вы пишете код для обработки изображений на С++, вы наверняка используете замечательную библиотеку OpenCV. Уверен, вам не раз хотелось посмотреть на изображения в процессе отладки вашего кода. Для этого можно использовать такие удобные функции как imshow или imwrite. Однако это требует модификации исходного кода, а любая современная IDE во время отладки позволяет смотреть значения переменных на лету. Вот было бы здорово так же смотреть изображения?

Если в качестве IDE вы пользуетесь Visual Studio, то знаете, что с .NET в этом плане всё проще. Однако речь идёт про OpenCV, а это только native C++, только хардкор. В этой статье я расскажу, как всё-таки заставить Visual Studio показывать изображения прямо в процессе отладки и дам ссылку на готовое решение. А также коротко расскажу о способах кастомизации Visual Studio.

Введение

Для тех, кто просто хочет воспользоваться готовым решением, сразу приведу ссылки:

Остальным я кратко расскажу об инструментах, с помощью которых можно в принципе кастомизировать Visual Studio, и более подробно о реализации решения одним из них. Все эксперименты выполнялись в Visual Studio 2010, готовое решение было протестировано в Visual Studio 2005, 2008, 2010, 2012 (и по идее должно работать в 2003).

Поиск готового решения

Когда мне по работе потребовалось отлаживать код на С++, использующий OpenCV для работы с изображениями, мне сразу же захотелось уметь смотреть их непосредственно во время отладки. Я радостно полез в гугл, и начал искать готовые решения. И… ничего не нашёл. Ни для изображений OpenCV, ни для вообще визуализации чего-либо из нативного C++ кода. Я поинтересовался у общественности, на что внушающий доверие резидент мне сказал, что отладчик нативного кода в принципе не может быть расширен подобным образом. В этот момент я уверился, что велосипед ещё не изобретён.

Возможности кастомизации Visual Studio

Чтобы выбрать подходящий инструмент для визуализатора, нужно хотя бы приблизительно представлять, с каких сторон вообще можно расширять Visual Studio. Для начала нам понадобится знать два понятия:

  • DTE Интерфейс взаимодействия с Visual Studio, который был введен в .NET-версиях (начиная с 2003). С его помощью можно получить объекты, которые предоставляют доступ к самым разным частям Visual Studio - структуре решения, настройкам оболочки, активному окну, даже к отладчику.
  • MEF Managed Extensibility Framework - более новая модель, доступная начиная с Visual Studio 2010. Позволяет осуществлять более тесную интеграцию и обеспечивает взаимодействие MEF-компонент, которые в свою очередь могут использовать DTE.

Далее представлен список сущностей, используемых для кастомизации Visual Studio. Список составлен с учётом конкретной задачи, поэтому не претендует на полноту. 

  • Макросы (Macros) Отлично подходят для автоматизации каких-либо действий, выполняемых над исходным кодом, структурой проекта и т.п. С их помощью можно выполнить проверку стиля кода, сгенерировать шаблоны, записать и воспроизвести некоторые действия пользователя. Имеют доступ к DTE. Доступны в меню Tools→Macros.
  • Дополнения (Add-Ins) Появились начиная с Visual Studio .NET. Используются для любых мыслимых кастомизаций, от улучшенной подсветки синтаксиса, до интеграции системы контроля версий. Имеют доступ к DTE. Доступны через Tools→Add-In Manager.
  • Расширения (Extensions) Появились начиная с Visual Studio 2010, являются более продвинутой версией дополнений (список отличий тут). Взаимодействуют с MEF. Доступны через Tools→Extension Manager.
  • Визуализаторы (Visualizers) Предназначены для отображения объектов в процессе отладки. Доступны при нажатии маленькой иконки с лупой рядом с именем переменной при отображении её содержимого.

Казалось бы, визуализаторы - именно то, что нужно. Но вот незадача - работают они только с managed C++. Первые три варианта (макросы, дополнения и расширения) могут получить доступ к памяти отлаживаемого процесса только с помощью объекта Debugger, у которого из подходящих функций есть только ExecuteStatement и GetExpression. Однако обе они так или иначе возвращают результаты в виде строк, ограниченных по размеру. Конечно, можно постараться по кусочкам выдрать содержимое изображения и перевести назад в бинарный вид, но получается как-то очень криво.

Существует ещё такой волшебный файлик под названием autoexp.dat. Он устанавливается вместе с Visual Studio и обычно лежит по адресу 

С:\Program Files (x86)\Microsoft Visual Studio <vs_version>\Common7\Packages\Debugger\autoexp.dat

В нём описаны правила, в соответствии с которыми отображается содержимое переменных при отладке нативного C++ кода. Именно он заставляет, например, содержимое контейнера std::vector выглядеть так, как слева, а не так, как справа:

И вот как он это делает:

Правило вывода std::vector в autoexp.dat
;------------------------------------------------------------------------------
;  std::vector from <vector>
;------------------------------------------------------------------------------
; vector is previewed with "[<size>](<elements>)".
; It has [size] and [capacity] children, followed by its elements.
; The other containers follow its example.
std::vector<*>{
	preview (
		#(
			"[",
			$e._Mylast - $e._Myfirst,
			"](",
			#array(
				expr: $e._Myfirst[$i],
				size: $e._Mylast - $e._Myfirst
			),
			")"
		)
	)

	children (
		#(
			#([size] : $e._Mylast - $e._Myfirst),
			#([capacity] : $e._Myend - $e._Myfirst),
			#array(
				expr: $e._Myfirst[$i],
				size: $e._Mylast - $e._Myfirst
			)
		)
	)
}
std::_Vector_iterator<*>/std::_Vector_const_iterator<*>{
	preview (
		*$e._Ptr
	)

	children (
		#([ptr] : $e._Ptr)
	)
}

Это старая, плохо документированная технология. Поддерживается ещё начиная с Visual C++ 6.0. Небольшое пособие по использование здесь. Однако так вышло, что именно она спасёт отца русской демократии позволит нам получить нормальный доступ к памяти отлаживаемого процесса. Кроме того, если вы часто работаете со сложными многоуровневыми структурами (например, считаете геометрию с помощью CGAL), пара вручную добавленных правил в этом файле может серьёзно упростить вам жизнь.

Оказывается, что синтаксис файла autoexp.dat позволяет не только писать выражения для, например, адресной арифметики, но и вообще вызывать любой сторонний код! Это делается с помощью специальной конструкции $ADDIN. За неимением лучшего, именно этим инструментом я и воспользовался для своего визуализатора.

Решение на базе Expression Evaluator Add-In

Небольшое руководство по написанию библиотек для файла autoexp.dat доступно в MSDN, там они его называют Expression Evaluator Add-In. Для вызова библиотечной функции достаточно указать путь к .dll-файлу и имя вызываемой функции. 

cv::Mat=$ADDIN(NativeViewer.dll,CvMatViewer)

Прототип функции в dll выглядит следующим образом:

HRESULT WINAPI CvMatViewer(DWORD dwAddress, DEBUGHELPER* pHelper, 
  int nBase, BOOL bUniStrings, char* pResult, size_t max, DWORD reserved)

В аргументе pHelper передаётся указатель на структуру DEBUGHELPER, которая предоставляет функции для обращения к памяти отлаживаемого процесса и возвращает адрес объекта, отображаемого в отладчике:

Код структуры DEBUGHELPER
typedef struct tagDEBUGHELPER
{
    DWORD dwVersion;
    HRESULT (WINAPI *ReadDebuggeeMemory)( struct tagDEBUGHELPER *pThis, DWORD dwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    // from here only when dwVersion >= 0x20000
    DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );
    HRESULT (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );
} DEBUGHELPER;

В pResult нужно сохранить строку, показываемую отладчиком. По идее, это всё, что должна делать данная функция. Но кто же нас остановит? Доступ к памяти у нас уже есть. А дальше, как говорится, дело техники.

Извлечение содержимого изображения

Единственная информация о типе, доступная нам в данный момент - это его название, cv::Mat. Это значит, что придётся вычислить адреса полей на основе .h-файла какой-то конкретной версии OpenCV. Однако не думаю, что в дальнейшем структура полей этого фундаментального класса будет изменяться.

Я не буду подробно останавливаться на технических моментах, типа как считать объект из памяти зная его адрес и имея .h-файл с его описанием, исходники выложены на SourceForge. Отмечу только, что структура pHelper позволяет узнать битность отлаживаемого процесса. А это значит, что нужно учесть, что указатели могут иметь размер как 4, так и 8 байт. Когда мы считали все поля объекта cv::Mat, мы можем точно так же считать содержимое самого изображения, т.к. его адрес находится в поле data.

Форматирование строки отображения

Ну, раз уж мы всё равно здесь, давайте действительно отформатируем строчку, отображаемую отладчиком. А то стандартные внутренности выглядят не очень красиво. Сделаем что-нибудь типа такого:

Визуализация изображения

Собственно, ради чего мы все здесь и собрались. Раз уж теперь у нас есть изображение, можно делать визуализацию с помощью .NET. Я использовал старую добрую Windows Forms и C#. Форма с диалогом для визуализации находится в отдельной сборке, ей в конструкторе передаётся объект System.Bitmap. Чтобы его сконструировать, оригинальная dll собиралась с поддержкой .NET, с ключом /clr. Это позволило использовать C++/CLI.

Я переживал, что вызов .NET-диалога из нативной библиотеки вызовет сложности и придётся самому создавать AppDomain. Однако, поскольку студия сама является .NET-приложением, ничего такого делать не пришлось. Всё сразу заработало, ну я и не стал разбираться подробнее. Отмечу только, что пришлось показывать диалог из отдельной нити с параметром ApartmentState::STA, иначе возникали проблемы при открытии дополнительных диалогов, например для сохранения изображения на диск.

Теперь мы столкнулись с главным недостатком выбранного подхода - невозможно определить, вызывается функция при форматировании вывода в окно Watch, или при наведении пользователем мыши на переменную в редакторе. Если каждый раз показывать окно с изображением, этим невозможно будет пользоваться.

Единственное адекватное решение, которое я смог придумать, таково: окно показывается только в том случае, если нажата какая-либо спец-клавиша. А именно, Ctrl. В противном случае только форматируется строка вывода. Таким образом, чтобы посмотреть изображение в процессе отладки, пользователю надо зажать Ctrl, навести на переменную в редакторе и тогда выскочит окно. Кривовато, да, но по факту пользоваться оказалось достаточно удобно.

При показе окна с изображением процесс Visual Studio блокируется, т.к. формально мы находимся в функции форматирования вывода отладчика. Это неприятно, но ничего фатального в этом нет, т.к. нет нужды одновременно изучать изображение и продолжать взаимодействовать со студией.

Интеграция с Visual Studio

Решение есть, теперь его надо упаковать. Для этого замечательно подходит существующий механизм расширений. Изначально я ориентировался на Visual Studio 2010, так что выбрал именно более современные расширения, а не более универсальные дополнения.

Создание расширений хорошо документировано. Для начала, нам понадобитсяVisual Studio SDK (для 2010 или 2012). После установки, можно будет создать новый проект Visual C#→Extensibility→Visual Studio Package. Запустится мастер, который спросит информацию о расширении:

По умолчанию он предлагает создать команду меню (Menu Comand), панель инструментов (Tool Window) или подкрутить редактор (Custom Editor). Нам не понадобится ничего из этого. Дело в том, что расширение представляет из себя файл VSIX, который является просто zip-архивом. При установке в Visual Studio, она распаковывает его в одну из пользовательских директорий (подробнеездесь). Там могут быть библиотеки, взаимодействующие с MEF, но могут быть и любые другие файлы. Например, наша библиотека с Expression Evaluator Add-In.

Возникает небольшая проблема с загрузкой сборки с формой для визуализации. Если добавить её в References, то её поиск будет происходить в каталоге с исполнимым файлом devenv.exe и в GAC. Она же находится в директории с расширением, поэтому её приходится грузить вручную при помощи функцииAssembly::LoadFrom. Диалог затем создаётся и показывается с помощьюreflection-методов.

Мне хотелось сделать настройки, которые можно было бы менять из меню Tools→Options. Это хорошо описано в руководстве в MSDN. Там описано, как создавать стандартную сетку ключ/значение, и произвольную форму:

Последняя форма понадобится для автоматического добавления строчки в файл autoexp.dat. К сожалению, не существует возможности выполнить какую-либо команду в момент установки расширения VSIX (см. здесь таблицу Supported Capabilities). Поэтому после установки расширения, пользователю нужно зайти на эту страницу и нажать кнопку "Add entry". 

Чтобы найти файл autoexp.dat, нужно знать, где установлена Visual Studio. Это можно посмотреть в реестре, но лучше спросить у неё самой с помощью DTE. Как получить объект DTE из расширения написано здесь. Свойство FullName вернёт полный путь к файлу devenv.exe.

Приятно удивила Visual Studio 2012. В ней наконец-то предложена замена морально устаревшему файлу autoexp.dat - технология NATVIS. Документации как таковой пока нет, но есть хорошее описание в блоге Microsoft. И, слава обратной совместимости, они оставили возможность вызова стороннего кода через тот же механизм, только теперь он указывается в аргументе LegacyAddin. Единственное описание, что я нашёл, в ответе к этому вопросу. Чувствуется, что технология совсем недавно анонсирована.

Огромный плюс NATIVS заключается в том, что теперь правила визуализации (оформленные в виде отдельных XML-файлов с расширением natvis) могут быть раскиданы по разным директориям. В том числе по пользовательским, а также они могут содержаться в расширениях. Для этого .nativs-файл достаточно добавить в Assets при сборке расширения. Поэтому в расширении NativeViewer для Visual Studio 2012 нет страницы с интеграцией и оно работает из коробки.

По поводу отладки VSIX-расширений. Это оказалось реализовано очень удобно. При запуске VSIX-проекта открывается экспериментальная копия Visual Studio, в которую установлена текущая версия расширения. Отладка работает нормально, мне удавалось отлаживать в исходной студии нативный код в dll, которую грузила экспериментальная копия.

Так как расширения появились только в Visual Studio 2010, на более ранние версии их придётся устанавливать вручную. Пока я написал руководство, а по-хорошему, конечно, следует сделать инсталлятор.

Заключение

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

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

Я очень надеюсь, что это расширение многим окажется полезным. Если кто-то хочет принять участие в развитии проекта - пишите.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 06.12.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Quest Software. TOAD for Oracle Edition
EMS SQL Management Studio for InterBase/Firebird (Business) + 1 Year Maintenance
IBM Domino Messaging Client Access License Authorized User License + SW Subscription & Support 12 Months
ABBYY Lingvo x6 Европейская Профессиональная версия, электронный ключ
Quest Software. SQL Navigator for Oracle
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
СУБД Oracle "с нуля"
Реестр Windows. Секреты работы на компьютере
Новые материалы
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
Мастерская программиста
3D и виртуальная реальность. Все о Macromedia Flash MX.
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100