Введение в разработку мультимедийных приложений с использованием библиотек OpenCV и IPP

Сравнение производительности некоторых алгоритмов в библиотеках OpenCV и IPP

Разбить на страницы
Показывать лекцию целиком

Презентацию к лекции Вы можете скачать здесь.

Дополнительные материалы Вы можете скачать здесь.

Введение

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

Для достижения хорошей эффективности прежде всего необходимо определиться с тем, что это такое, и научиться ее оценивать, как теоретически, так и экспериментально. Эффективность работы алгоритма и соответствующей ему программы можно понимать по-разному. Теория сложности вычислений (см., например, [1]) предоставляет методику теоретического изучения вопроса о времени работы алгоритма в зависимости от размера задачи (объема входных данных). Построенные оценки сложности часто позволяют определить, какой алгоритм является более подходящим для решения задачи. Однако не все так просто. Во-первых, существует немало задач, построение оценок для которых является далеко не тривиальным. Во-вторых, в ряде случаев оказывается, что более эффективные с точки зрения теории алгоритмы на практике проигрывают их, казалось бы, менее эффективным аналогам. Так, например, в задаче умножения матриц алгоритм Копперсмита-Винограда [2], имеющий отличные показатели эффективности, обычно не используется на практике в связи с наличием большой константы в оценке сложности. Выясняется, что в машинной реализации матрицы, при умножении которых мог бы достигаться выигрыш, просто не убираются в оперативную память, из-за чего на первый план выходят совершенно другие вопросы. Иногда хороший в теории алгоритм может плохо "ложиться" на архитектуру современных вычислительных систем. Заметим, что в случае использования параллельных систем ситуация еще более запутывается. Некоторые "хорошие" последовательные алгоритмы плохо поддаются распараллеливанию, что приводит к необходимости использования параллельных модификаций менее эффективных в последовательном случае алгоритмов (пример: алгоритмы сортировки MSD Sort и LSD Sort [10]) либо к разработке принципиально новых алгоритмов, изначально ориентированных на параллельное исполнение. В итоге мы можем заключить, что вопрос оценки эффективности алгоритма и соответствующей ему программы на практике является весьма сложным и, к сожалению, не всегда поддается исключительно теоретическому анализу, требуя проведения вычислительных экспериментов и соответствующей оценки производительности.

Оценка и анализ производительности – вещи субъективные, во многом зависящие от методики постановки и анализа результатов вычислительного эксперимента. Правильная постановка эксперимента для анализа производительности крайне важна. Так, например, подавая уже упорядоченный массив на вход сортировки пузырьком, можно получить великолепные показатели времени работы и ошибочно признать соответствующую реализацию лучшей (важный момент: репрезентативность исходных данных). Примером другой распространенной ошибки являются выводы по результатам одиночного запуска, во время которого неожиданно "проснулся" установленный в системе антивирус, поглотив при этом половину имеющихся ресурсов (важный момент: нивелирование наведенных эффектов). Существуют и другие виды ошибок [3]. Анализ полученных данных о производительности тоже не прост. Для помощи разработчикам программного обеспечения созданы специальные инструменты – профилировщики, помогающие ответить на вопрос: где программа тратила процессорное время? Такие профилировщики помогают анализировать ситуацию в последовательных системах (Intel VTune и др.), в параллельных системах с общей памятью (Intel VTune Amplifier XE и др.), в параллельных системах с распределенной памятью (Intel Trace Collector/Analyzer и др.), в системах с использованием ускорителей GPU (NVIDIA Visual Profiler и др.) и MIC (Intel VTune Amplifier XE). Тем не менее, профилировщик лишь собирает для разработчиков статистическую информацию. Ее правильная интерпретация является нашей задачей. Таким образом, правильно поставить эксперимент по оценке производительности, интерпретировать его результаты, при необходимости внести изменения в программный код весьма непросто. Подобная работа требует существенных теоретических знаний и практических навыков [4, 5, 6, 7].

Данная лабораторная работа не дает исчерпывающего описания методики анализа эффективности алгоритмов и программ, и, тем более, не учит оптимизировать программы по скорости их работы. В контексте курса по разработке мультимедийных приложений с использованием библиотек OpenCV и IPP работа закрепляет сформированные навыки использования библиотек, а также предлагает сравнить производительность функциональности, присутствующей в обеих библиотеках, на тестовых задачах. Основная идея работы заключается вовсе не в том, чтобы показать, что библиотека IPP работает быстрее библиотеки OpenCV или наоборот. Отметим, что постановка вопроса "Кто быстрее?" в данном случае как минимум крайне сомнительна, хотя бы потому, что библиотеки существенно отличаются по назначению и реализованной функциональности. В тех же случаях, когда в обеих библиотеках присутствуют методы решения одних и тех же задач, эти методы не всегда совпадают. В связи с этим для одинаковых входных данных средствами OpenCV и IPP можно получить несколько разные результаты; постановка вопроса о сравнении производительности в этом случае выглядит сомнительно. Тем не менее, в ряде случаев библиотеки предоставляют реализации одних и тех же алгоритмов, применимых при решении задач компьютерного зрения [8, 9], что и обуславливает практический интерес к вопросу о том, что работает быстрее. Производительность некоторых таких алгоритмов сравнивается в данной лабораторной работе.

1. Методические указания

1.1. Цели и задачи работы

Целью работы является сравнение производительности некоторых алгоритмов, реализованных как в библиотеке OpenCV, так и в библиотеке IPP.

Для достижения данной цели необходимо решить следующие задачи:

  • Построить каркас приложения для сравнения производительности, включающий функции загрузки изображения, возможность запуска алгоритмов из библиотеки OpenCV и IPP, сравнения результатов работы алгоритмов, измерения времени работы.
  • Запрограммировать вызов алгоритмов медианной фильтрации, эрозии, дилатации, построения гистограммы из библиотек OpenCV и IPP.
  • Подобрать подходящие тестовые данные.
  • Запустить приложение на выбранных данных, сравнить время работы алгоритмов.
  • 1.2. Структура работы

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

    1.3. Тестовая инфраструктура

    В работе использовалось следующее аппаратное и программное обеспечение:

  • CPU: Intel Core2 Quad Q9550 2.83 GHz.
  • RAM: 5 GB.
  • OS: Windows 7 Pro SP1.
  • Microsoft Visual Studio 2010.
  • IPP из Intel Parallel Studio 2013 XE.
  • OpenCV 2.4.2.
  • 1.4. Рекомендации по проведению занятий

    Рекомендуется придерживаться следующей последовательности при проведении занятия:

  • Выяснить у слушателей степень их владения библиотеками OpenCV и IPP. При необходимости кратко охарактеризовать функциональность и область назначения библиотек, показать примеры работы с ними (см. лабораторные работы, направленные на освоение OpenCV и IPP).
  • Совместно со слушателями разработать каркас тестового приложения. В случае недостатка времени дать слушателям готовый каркас приложения в исходных кодах и объяснить его наполнение.
  • Реализовать вызов тестируемых алгоритмов (медианная фильтрация, эрозия, дилатация, построение гистограммы).
  • Убедиться в корректности программы.
  • Сравнить производительность при запуске на одном изображении.
  • Предложить слушателям расширить приложение для запуска на серии изображений (либо зациклить обработку одного изображения).
  • Обсудить полученные результаты и перспективы совместного использования OpenCV и IPP.
  • 2. Разработка приложения для сравнения производительности операций над изображениями, реализованных на базе OpenCV и Intel IPP

    2.1. Требования к общей организации приложения

    Прежде чем перейти к созданию проекта в среде разработки Microsoft Visual Studio 2010 и написанию кода, обсудим некоторые общие моменты.

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

    Итак, в качестве алгоритмов для сравнения мы используем медианную фильтрацию, относящуюся к алгоритмам размытия, два морфологических преобразования: эрозию и дилатацию, – а также один из статистических алгоритмов: гистограмму.

    2.2. Создание проекта

    Создадим новое Решение (Solution), в которое включим Проект (Project) данной лабораторной работы. Последовательно выполните следующие шаги:

  • Запустите приложение Microsoft Visual Studio 2010.
  • В меню File выполните команду New$$\rightarrow$$Project….
  • Как показано на рис. 1, в диалоговом окне (рис 11.1) Создание решения для лабораторной работы
  • В диалоговом окне Win32 Application Wizard нажмите Next (или выберите Application Settings в дереве слева) и установите флаг Empty Project. Нажмите Finish.
  • В окне Solution Explorer в папке Source Files выполните команду контекстного меню Add$$\rightarrow$$New Item…. В дереве категорий слева выберите Code, в шаблонах справа – C++ File (.cpp), в поле Name введите имя файла main. Нажмите Add. В результате выполненной последовательности действий в окне редактора кода Visual Studio будет открыт пустой файл main.cpp.
  • 2.3. Настройка свойств проекта

    Настроить проект для использования библиотеки IPP из состава пакета Intel Parallel Studio XE 2013 очень просто. Понадобится следующая последовательность действий.

  • Выполните команду контекстного меню Properties, чтобы получить доступ к настройкам проекта.
  • Откройте вкладку Configuration Properties$$\rightarrow$$Intel Performance Libraries.
  • В выпадающем списке напротив свойства Use IPP выберите способ линковки библиотеки Intel® IPP (рис. 11.2). (рис 11.2) Перечень способов линковки библиотеки IPP
  • Для использования функций библиотеки OpenCV потребуется немного больше действий.

  • Сначала установим путь до заголовочных файлов библиотеки OpenCV. Выполните команду контекстного меню Properties, чтобы получить доступ к настройкам проекта. Откройте вкладку Configuration Properties$$\rightarrow$$C/C++$$\rightarrow$$General (рис. 11.3). Сверху в окне свойств в выпадающем списке Configuration выберите значение All Configurations, чтобы установить свойство для всех режимов компиляции (Debug и Release). В поле Additional Include Directories укажите пути до заголовочных файлов библиотеки OpenCV (например, c:\OpenCVuild\include). Нажмите кнопку Apply, чтобы применить указанное свойство. (рис 11.3) Установка путей до заголовочных файлов OpenCV
  • Далее установим путь до подключаемых библиотек (lib-файлов). Откройте вкладку Configuration Properties$$\rightarrow$$Linker$$\rightarrow$$General (рис. 11.4). Сверху в окне свойств в выпадающем списке Configuration выберите значение All Configurations. В поле Additional Library Directories укажите путь до lib-файлов (например, c:\OpenCVuild\x86\vc10\lib\). (рис 11.4) Установка путей до подключаемых библиотек OpenCV
  • Последним действием укажем список подключаемых библиотек. Откройте вкладку Configuration Properties$$\rightarrow$$Linker$$\rightarrow$$Input (рис. 11.5). Сначала в окне свойств в выпадающем списке Configuration выберите значение Debug и установите в поле Additional Dependencies список lib-файлов opencv_core242d.lib, opencv_imgproc242d.lib, opencv_highgui242d.lib, opencv_legacy242d.lib, opencv_video242d.lib, opencv_ml242d.lib, opencv_objdetect242d.lib. Аналогичные действия необходимо проделать для режима Release, указав список lib-файлов с теми же названиями за исключением отсутствия последней буквы d. (рис 11.5) Установка списка подключаемых библиотек OpenCV
  • 2.4. Функция main

    Перейдем теперь к разработке функции main(). Прежде всего необходимо подключить заголовочные файлы. Кроме функций из библиотек OpenCV и IPP нам потребуется также вывод сообщений на консоль с помощью printf() и замеры времени с использованием функции clock().

    #include <stdio.h> 
    #include <time.h> 
    #include <opencv2/opencv.hpp> 
    #include <ippi.h> 
    #include <ippcc.h>   
        

    Для более удобной работы с OpenCV подключим пространство имен cv.

    using namespace cv;   
        

    Имя файла с изображением будем передавать через командную строку.

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

    Далее представлен начальный вариант функции main(), содержащий, кроме указанного выше, также вызовы функций, в которых необходимо будет разместить код, реализующий соответствующие алгоритмы.

    char helper[] =   
      "01_OpenCVvsIPP.exe <img_name> <mode> <num_of_exp>\n\ 
      \t<img_name> - image filename\n\ 
      \t<mode>:\n\ 
      \t\t1 - median filtering\n\ 
      \t\t2 - erode\n\ 
      \t\t3 - dilate\n\ 
      \t\t4 - calc histogram\n\ 
      \t<num_of_exp> - number of experiments\n"; 
     
    int main(int argc, char *argv[]) 
    { 
      Mat srcImgOCV, srcImgIPP, dstImgOCV, dstImgIPP; 
      int mode, i; 
      double ocv_time, ipp_time; 
     
      if (argc < 4) 
      { 
        printf("%s", helper); 
        return 1; 
      } 
     
      // загрузить изображение 
      srcImgOCV = imread(argv[1]); 
      if (srcImgOCV.data == 0) 
      { 
        printf("ERROR!!! imread(...)"); 
        return 1; 
      } 
      srcImgOCV.copyTo(srcImgIPP); 
     
      mode = atoi(argv[2]); 
      switch (mode) 
      { 
      case 1: 
        // отфильтровать с помощью OpenCV 
        ocv_time = median_opencv(srcImgOCV, dstImgOCV); 
        // отфильтровать с помощью IPP 
        ipp_time = median_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 2: 
        // выполнить эрозию с помощью OpenCV 
        ocv_time = erode_opencv(srcImgOCV, dstImgOCV); 
        // выполнить эрозию с помощью IPP 
        ipp_time = erode_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 3: 
        // выполнить дилатацию с помощью OpenCV 
        ocv_time = dilate_opencv(srcImgOCV, dstImgOCV); 
        // выполнить дилатацию с помощью IPP 
        ipp_time = dilate_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 4: 
        // вычислить гистограмму с помощью OpenCV 
        ocv_time = hist_opencv(srcImgOCV, dstImgOCV); 
        // вычислить гистограмму с помощью IPP 
        ipp_time = hist_ipp(srcImgIPP, dstImgIPP); 
        break; 
      } 
     
      printf("ocv time is %.3f\nipp time is %.3f", ocv_time, 
        ipp_time); 
     
      // освободить память 
      srcImgOCV.release(); 
      srcImgIPP.release(); 
      dstImgOCV.release(); 
      dstImgIPP.release(); 
     
      return 0; 
    } 
        

    Предполагается, что каждая из выделенных полужирным шрифтом функций принимает на вход изображение в виде объекта типа Mat из OpenCV, выполняет требуемый алгоритм и возвращает новое изображение также в виде объекта типа Mat. Кроме того считаем, что все эти функции возвращают время выполнения в секундах.

    2.5. Реализация операций с использованием функционала библиотеки OpenCV

    Первые три задачи из выбранных нами средствами OpenCV решаются очень просто. Рассмотрим их подробнее.

    2.5.1. Функция медианной фильтрации

    Медианная фильтрация в библиотеке OpenCV реализуется с помощью функции medianBlur(). Ее подробное описание может быть найдено в лабораторной работе "Базовые операции обработки изображений" настоящего курса. Здесь же лишь отметим, что в экспериментах мы будем использовать размер ядра, равный 3, но предусмотрим возможность его изменения.

    double median_opencv(const Mat srcImg, Mat dstImg, 
      const int kSize = 3); 
     
    double median_opencv(const Mat srcImg, Mat dstImg, 
      const int kSize) 
    { 
      clock_t start, finish; 
       
      start = clock(); 
      medianBlur(srcImg, dstImg, kSize); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    2.5.2. Функция вычисления эрозии

    Эрозия изображения выполняется в OpenCV с помощью функции erode(). Используем простейший вариант ее вызова с тремя параметрами, при котором применяется квадратный шаблон $$3 \times 3$$.

    double erode_opencv(const Mat srcImg, Mat dstImg) 
    { 
      clock_t start, finish; 
     
      start = clock(); 
      Mat element = Mat(); 
      erode(srcImg, dstImg, element); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    2.5.3. Функция вычисления дилатации

    Полностью аналогично обстоит дело с функцией дилатации.

    double dilate_opencv(const Mat srcImg, Mat dstImg) 
    { 
      clock_t start, finish; 
     
      start = clock(); 
      Mat element = Mat(); 
      dilate(srcImg, dstImg, element); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    За описанием функций erode() и dilate() также отсылаем к лабораторной работе "Базовые операции обработки изображений".

    2.5.4. Функция вычисления гистограммы

    Для вычисления гистограммы используем часть кода, приведенного в соответствующем разделе лабораторной работы "Базовые операции обработки изображений". Будем строить гистограмму по каждому каналу цветного изображения, предварительно расщепляя его по каналам с помощью функции split().

    double hist_opencv(const Mat srcImg, Mat dstImg) 
    { 
      Mat bgrChannels[3], bHist, gHist, rHist; 
      // количество бинов гистограммы 
      int kBins = 256; 
      // интервал изменения значений бинов 
      float range[] = {0.0f, 256.0f}; 
      const float* histRange = { range }; 
      // равномерное распределение интервала по бинам 
      bool uniform = true; 
      // запрет очищения перед вычислением гистограммы 
      bool accumulate = false; 
      clock_t start, finish; 
     
      start = clock(); 
      split(srcImg, bgrChannels); 
      // вычисление гистограммы для каждого канала 
      calcHist(bgrChannels[0], 1, 0, Mat(), bHist, 1, 
        kBins, histRange, uniform, accumulate); 
      calcHist(bgrChannels[1], 1, 0, Mat(), gHist, 1, 
        kBins, histRange, uniform, accumulate); 
      calcHist(bgrChannels[2], 1, 0, Mat(), rHist, 1, 
        kBins, histRange, uniform, accumulate); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    2.6. Реализация операций с использованием функционала библиотеки Intel IPP

    2.6.1. Функция медианной фильтрации

    Медианная фильтрация в IPP выполняется при помощи одной из функций вида ippiFilterMedian_<mod>. В случае цветного изображения mod – 3uC3R. Кроме того, в документации к IPP указано, что если мы хотим применить фильтр с шаблоном размера msk, необходимо расширить матрицу изображения на msk / 2 пикселов с каждой стороны. Для расширения используем функцию ippiCopyReplicateBorder_8u_C3R(). Наконец, после получения "расширенного" отфильтрованного изображения, необходимо выполнить обратную операцию – выделить изображение исходного размера. Это можно сделать, просто скопировав внутреннюю часть расширенной матрицы пикселей с помощью функции ippiCopy_8u_C3R().

    Получим следующий код.

    double median_ipp(const Mat srcImg, Mat dstImg, 
      const int msk = 3); 
     
    double median_ipp(const Mat srcImg, Mat dstImg, 
      const int msk) 
    { 
      const int borderSize = msk / 2; 
      IppiSize mskSize = { msk , msk }; 
      IppiPoint anchor = { msk / 2, msk / 2 }; 
      Mat ippSrcImg, ippDstImg; 
      Size ippImgSize; 
      IppiSize ippSrcSize, ippDstSize; 
      Ipp8u *pSrcData, *pDstData; 
      clock_t start, finish; 
     
      ippImgSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippImgSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippSrcImg.create(ippImgSize, srcImg.type()); 
      ippDstImg.create(ippImgSize, srcImg.type()); 
      pSrcData = (Ipp8u *)ippSrcImg.data; 
      pDstData = (Ipp8u *)ippDstImg.data; 
     
      start = clock(); 
      ippSrcSize.width = srcImg.size().width; 
      ippSrcSize.height = srcImg.size().height; 
      ippDstSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippDstSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippiCopyReplicateBorder_8u_C3R(srcImg.data, 
        srcImg.step1(), ippSrcSize, 
        pSrcData, srcImg.step1() + 3 * 2 * borderSize, 
        ippDstSize, borderSize, borderSize); 
     
      ippiFilterMedian_8u_C3R(pSrcData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, pDstData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, ippSrcSize, 
        mskSize, anchor); 
      finish = clock(); 
     
      dstImg.create(srcImg.size(), srcImg.type()); 
      pSrcData = pDstData; 
      pDstData = (Ipp8u *)dstImg.data; 
      ippiCopy_8u_C3R(pSrcData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, pDstData, 
      dstImg.step1(), ippSrcSize); 
     
      ippSrcImg.release(); 
      ippDstImg.release(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    Отметим, что для построения "расширенной" матрицы изображения мы использовали функции OpenCV для работы c типом Mat.

      ippImgSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippImgSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippSrcImg.create(ippImgSize, srcImg.type()); 
      ippDstImg.create(ippImgSize, srcImg.type()); 
      pSrcData = (Ipp8u *)ippSrcImg.data; 
      pDstData = (Ipp8u *)ippDstImg.data;   
          

    Конечно, то же самое можно проделать только средствами IPP. Внести соответствующие изменения предлагаем читателю самостоятельно.

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

    2.6.2. Функция вычисления эрозии

    Реализацию функций для вычисления эрозии и дилатации средствами IPP мы выполним несколько по-другому. Дело в том, что представленные выше варианты вызовов функций erode() и dilate() из OpenCV выполняют обработку только внутренних пикселей исходного изображения. Таким образом, и в IPP-версии нам нет необходимости расширять матрицу изображения. Вместо этого мы можем соответствующим образом определить область интереса (ROI).

    double erode_ipp(const Mat srcImg, Mat dstImg) 
    { 
      IppiMaskSize msk; 
      Ipp8u *pSrcData, *pDstData; 
      IppiSize srcSize, dstRoiSize; 
      clock_t start, finish; 
     
      srcImg.copyTo(dstImg); 
     
      start = clock(); 
      pSrcData = (Ipp8u *)srcImg.data; 
      pDstData = (Ipp8u *)dstImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      msk = ippMskSize3x3; 
      dstRoiSize.width = srcImg.size().width - 2; 
      dstRoiSize.height = srcImg.size().height - 2; 
      ippiErode3x3_8u_C3R(pSrcData + 
        srcSize.width * 3 * 1 + 3 * 1, srcImg.step1(), 
        pDstData + srcSize.width * 3 * 1 + 3 * 1, 
        srcImg.step1(), dstRoiSize); 
     
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    2.6.3. Функция вычисления дилатации

    Реализация функции вычисления дилатации средствами IPP совпадает с таковой для функции эрозии за исключением вызова функции ippiDilate3x3_8u_C3R() вместо ippiErode3x3_8u_C3R().

    double erode_ipp(const Mat srcImg, Mat dstImg) 
    { 
      IppiMaskSize msk; 
      Ipp8u *pSrcData, *pDstData; 
      IppiSize srcSize, dstRoiSize; 
      clock_t start, finish; 
     
      srcImg.copyTo(dstImg); 
     
      start = clock(); 
      pSrcData = (Ipp8u *)srcImg.data; 
      pDstData = (Ipp8u *)dstImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      msk = ippMskSize3x3; 
      dstRoiSize.width = srcImg.size().width - 2; 
      dstRoiSize.height = srcImg.size().height - 2; 
      ippiDilate3x3_8u_C3R(pSrcData + 
        srcSize.width * 3 * 1 + 3 * 1, srcImg.step1(), 
        pDstData + srcSize.width * 3 * 1 + 3 * 1, 
        srcImg.step1(), dstRoiSize); 
     
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }     
          

    2.6.4. Функция вычисления гистограммы

    Вычислить гистограмму для многоканального изображения, можно используя функции IPP вида ippiHistogramRange_<mod>. Как и ранее в нашем случае mod – 8u_C3R. Для вызова этой функции необходимо предварительно подготовить:

  • параметр pHisto вида

      Ipp32s *pHisto[3]; 
      pHisto[0] = new Ipp32s[kBins + 1]; 
      pHisto[1] = new Ipp32s[kBins + 1]; 
      pHisto[2] = new Ipp32s[kBins + 1];   
              

    для размещения гистограмм по каждому каналу (kBins – число бинов гистограммы),

  • константный параметр pLevels вида

      Ipp32s level1[kBins + 1], level2[kBins + 1], 
        level3[kBins + 1]; 
      const Ipp32s *pLevels[3] = { level1, level2, level3 };   
              

    для указания уровней

      for (i = 0; i < kBins + 1; i++) 
        level1[i] = level2[i] = level3[i] = i;   
              
  • и массив nLevels вида

      int nLevels[3] = {kBins + 1, kBins + 1, kBins + 1};   
              

    для задания числа уровней по каждому каналу.

  • Смысл параметров pHisto и pLevels для выбранного канала можно проиллюстрировать так: pHisto[k] – это число пикселей изображения, таких что pLevels[k] <= pHisto[k] < pLevels[k+1].

    Получим следующий код.

    double hist_ipp(const Mat srcImg, Mat dstImg) 
    { 
      // количество бинов гистограммы 
      const int kBins = 256; 
      Ipp8u *pSrcData; 
      IppiSize srcSize; 
      int i; 
      Ipp32s level1[kBins + 1], level2[kBins + 1], 
        level3[kBins + 1]; 
      Ipp32s *pHisto[3]; 
      const Ipp32s *pLevels[3] = { level1, level2, level3 }; 
      int nLevels[3] = {kBins + 1, kBins + 1, kBins + 1}; 
      clock_t start, finish; 
     
      pHisto[0] = new Ipp32s[kBins + 1]; 
      pHisto[1] = new Ipp32s[kBins + 1]; 
      pHisto[2] = new Ipp32s[kBins + 1]; 
     
      for (i = 0; i < kBins + 1; i++) 
        level1[i] = level2[i] = level3[i] = i; 
     
      pSrcData = (Ipp8u *)srcImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      start = clock(); 
      ippiHistogramRange_8u_C3R(pSrcData, srcImg.step1(), 
        srcSize, pHisto, pLevels, nLevels); 
      finish = clock(); 
     
      delete [] pHisto[0]; 
      delete [] pHisto[1]; 
      delete [] pHisto[2]; 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
     
          

    2.7. Анализ корректности

    Для медианного фильтра, эрозии и дилатации результирующие изображения, полученные средствами OpenCV и IPP, можно сравнить попиксельно. Для этого напишем следующую функцию, возвращающую число точек в двух изображениях с несовпадающими значениями.

    int compare(const Mat srcImgOCV, const Mat srcImgIPP) 
    { 
      int kPoints = 0; 
      int w, h, i, j, idx; 
      w = srcImgOCV.size().width; 
      h = srcImgOCV.size().height; 
      for (i = 0; i < h; i++) 
      { 
        idx = 3 * i * w; 
        for (j = 0; j < w; j++, idx += 3) 
          if (((srcImgOCV.data[idx]-srcImgIPP.data[idx])!=0) 
            ||((srcImgOCV.data[idx+1]- 
              srcImgIPP.data[idx+1])!=0) 
            ||((srcImgOCV.data[idx+2]- 
              srcImgIPP.data[idx+2])!=0)) 
        { 
          kPoints++; 
        } 
      } 
     
      return kPoints; 
    }   
        

    Осталось добавить в функцию main() фрагмент для проверки корректности.

      int kDiffPoints = 0; 
    ... 
      // сравнить результаты 
      if (mode < 4) 
      { 
        kDiffPoints = compare(dstImgOCV, dstImgIPP); 
        if (kDiffPoints == 0) 
          printf("OpenCV and IPP give the same result.\n"); 
        else 
          printf("OpenCV and IPP give different results in %d\ 
            points.\n", kDiffPoints); 
      } 
        

    Сравнение результатов построения гистограммы средствами OpenCV и IPP предоставляем провести читателю самостоятельно.

    2.8. Запуск приложения

    С учетом использованных нами функций OpenCV для запуска программы потребуются следующие динамические библиотеки: opencv_core242.dll, opencv_highgui242.dll, opencv_imgproc242.dll, tbb.dll.

    Первые три располагаются в папке build\x86\vc10\bin\. Последняя – в build\common\tbb\ia32\vc10\.

    Использование функций IPP предполагает, что следующий путь должен быть известен операционной системе "C:\Program Files (x86)\Intel\Composer XE 2013\redist\ia32\ipp\".

    Собственно запуск можно выполнить, используя командную строку вида

    OpenCVvsIPP.exe Desert.jpg 1

    3. Сравнение производительности операций над изображениями, реализованных на базе OpenCV и Intel IPP

    3.1. Тестовые данные

    Все рассматриваемые в данной лабораторной работе алгоритмы имеют квадратичную сложность. Чтобы подготовленные нами реализации показывали времена, на которых имеет смысл проводить сравнение, необходимо использовать или подготовить изображение достаточно большого размера. Используем файл Desert.jpg из состава операционной системы Windows 7, расположенный в папке C:\Users\Public\Pictures\Sample Pictures. Этот файл имеет размер 1024?768 пикселей. Создадим из него изображение размера 8192?6144 пиксела, скопировав исходную картинку нужное число раз в любом графическом редакторе.

    На указанном изображении авторами были получены следующие времена.

    OpenCV (время, с) IPP (время, с)
    Медианный фильтр 0.361 0.319
    Эрозия 0.234 0.115
    Дилатация 0.224 0.118
    Гистограмма 0.351 0.130

    3.2. Проведение вычислительных экспериментов

    Для получения более адекватных результатов добавим в функцию main возможность проведения серии запусков для каждого из алгоритмов. Число запусков будем передавать через еще один параметр командной строки.

    Таким образом, необходимо изменить следующие фрагменты.

    Дополнить описание запуска.

    char helper[] = 
      "01_OpenCVvsIPP.exe <img_name> <mode> <num_of_exp>\n\ 
      \t<img_name> - image filename\n\ 
      \t<mode>:\n\ 
      \t\t1 - median filtering\n\ 
      \t\t2 - erode\n\ 
      \t\t3 - dilate\n\ 
      \t\t4 - calc histogram\n\ 
      \t<num_of_exp> - number of experiments\n"; 
       
        

    Исправить проверку на число параметров командной строки

      if (argc < 4) 
      { 
        printf("%s", helper); 
        return 1; 
      }   
        

    Считать значение параметра.

      int numOfExp; 
    ... 
      numOfExp = atoi(argv[3]);   
        

    Изменить вызовы функций

      ocv_time = ipp_time = 0; 
      switch (mode) 
      { 
      case 1: 
        for (i = 0; i < numOfExp; i++) 
        { 
          // отфильтровать с помощью OpenCV 
          ocv_time += median_opencv(srcImgOCV, dstImgOCV); 
          // отфильтровать с помощью IPP 
          ipp_time += median_ipp(srcImgIPP, dstImgIPP); 
        } 
    ...   
        

    Выполните указанные изменения и проведите эксперименты с разными значениями параметра numOfExp.

    4. Дополнительные задания

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

    Презентацию к лекции Вы можете скачать здесь.

    Дополнительные материалы Вы можете скачать здесь.

    Введение

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

    Для достижения хорошей эффективности прежде всего необходимо определиться с тем, что это такое, и научиться ее оценивать, как теоретически, так и экспериментально. Эффективность работы алгоритма и соответствующей ему программы можно понимать по-разному. Теория сложности вычислений (см., например, [1]) предоставляет методику теоретического изучения вопроса о времени работы алгоритма в зависимости от размера задачи (объема входных данных). Построенные оценки сложности часто позволяют определить, какой алгоритм является более подходящим для решения задачи. Однако не все так просто. Во-первых, существует немало задач, построение оценок для которых является далеко не тривиальным. Во-вторых, в ряде случаев оказывается, что более эффективные с точки зрения теории алгоритмы на практике проигрывают их, казалось бы, менее эффективным аналогам. Так, например, в задаче умножения матриц алгоритм Копперсмита-Винограда [2], имеющий отличные показатели эффективности, обычно не используется на практике в связи с наличием большой константы в оценке сложности. Выясняется, что в машинной реализации матрицы, при умножении которых мог бы достигаться выигрыш, просто не убираются в оперативную память, из-за чего на первый план выходят совершенно другие вопросы. Иногда хороший в теории алгоритм может плохо "ложиться" на архитектуру современных вычислительных систем. Заметим, что в случае использования параллельных систем ситуация еще более запутывается. Некоторые "хорошие" последовательные алгоритмы плохо поддаются распараллеливанию, что приводит к необходимости использования параллельных модификаций менее эффективных в последовательном случае алгоритмов (пример: алгоритмы сортировки MSD Sort и LSD Sort [10]) либо к разработке принципиально новых алгоритмов, изначально ориентированных на параллельное исполнение. В итоге мы можем заключить, что вопрос оценки эффективности алгоритма и соответствующей ему программы на практике является весьма сложным и, к сожалению, не всегда поддается исключительно теоретическому анализу, требуя проведения вычислительных экспериментов и соответствующей оценки производительности.

    Оценка и анализ производительности – вещи субъективные, во многом зависящие от методики постановки и анализа результатов вычислительного эксперимента. Правильная постановка эксперимента для анализа производительности крайне важна. Так, например, подавая уже упорядоченный массив на вход сортировки пузырьком, можно получить великолепные показатели времени работы и ошибочно признать соответствующую реализацию лучшей (важный момент: репрезентативность исходных данных). Примером другой распространенной ошибки являются выводы по результатам одиночного запуска, во время которого неожиданно "проснулся" установленный в системе антивирус, поглотив при этом половину имеющихся ресурсов (важный момент: нивелирование наведенных эффектов). Существуют и другие виды ошибок [3]. Анализ полученных данных о производительности тоже не прост. Для помощи разработчикам программного обеспечения созданы специальные инструменты – профилировщики, помогающие ответить на вопрос: где программа тратила процессорное время? Такие профилировщики помогают анализировать ситуацию в последовательных системах (Intel VTune и др.), в параллельных системах с общей памятью (Intel VTune Amplifier XE и др.), в параллельных системах с распределенной памятью (Intel Trace Collector/Analyzer и др.), в системах с использованием ускорителей GPU (NVIDIA Visual Profiler и др.) и MIC (Intel VTune Amplifier XE). Тем не менее, профилировщик лишь собирает для разработчиков статистическую информацию. Ее правильная интерпретация является нашей задачей. Таким образом, правильно поставить эксперимент по оценке производительности, интерпретировать его результаты, при необходимости внести изменения в программный код весьма непросто. Подобная работа требует существенных теоретических знаний и практических навыков [4, 5, 6, 7].

    Данная лабораторная работа не дает исчерпывающего описания методики анализа эффективности алгоритмов и программ, и, тем более, не учит оптимизировать программы по скорости их работы. В контексте курса по разработке мультимедийных приложений с использованием библиотек OpenCV и IPP работа закрепляет сформированные навыки использования библиотек, а также предлагает сравнить производительность функциональности, присутствующей в обеих библиотеках, на тестовых задачах. Основная идея работы заключается вовсе не в том, чтобы показать, что библиотека IPP работает быстрее библиотеки OpenCV или наоборот. Отметим, что постановка вопроса "Кто быстрее?" в данном случае как минимум крайне сомнительна, хотя бы потому, что библиотеки существенно отличаются по назначению и реализованной функциональности. В тех же случаях, когда в обеих библиотеках присутствуют методы решения одних и тех же задач, эти методы не всегда совпадают. В связи с этим для одинаковых входных данных средствами OpenCV и IPP можно получить несколько разные результаты; постановка вопроса о сравнении производительности в этом случае выглядит сомнительно. Тем не менее, в ряде случаев библиотеки предоставляют реализации одних и тех же алгоритмов, применимых при решении задач компьютерного зрения [8, 9], что и обуславливает практический интерес к вопросу о том, что работает быстрее. Производительность некоторых таких алгоритмов сравнивается в данной лабораторной работе.

    1. Методические указания

    1.1. Цели и задачи работы

    Целью работы является сравнение производительности некоторых алгоритмов, реализованных как в библиотеке OpenCV, так и в библиотеке IPP.

    Для достижения данной цели необходимо решить следующие задачи:

  • Построить каркас приложения для сравнения производительности, включающий функции загрузки изображения, возможность запуска алгоритмов из библиотеки OpenCV и IPP, сравнения результатов работы алгоритмов, измерения времени работы.
  • Запрограммировать вызов алгоритмов медианной фильтрации, эрозии, дилатации, построения гистограммы из библиотек OpenCV и IPP.
  • Подобрать подходящие тестовые данные.
  • Запустить приложение на выбранных данных, сравнить время работы алгоритмов.
  • 1.2. Структура работы

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

    1.3. Тестовая инфраструктура

    В работе использовалось следующее аппаратное и программное обеспечение:

  • CPU: Intel Core2 Quad Q9550 2.83 GHz.
  • RAM: 5 GB.
  • OS: Windows 7 Pro SP1.
  • Microsoft Visual Studio 2010.
  • IPP из Intel Parallel Studio 2013 XE.
  • OpenCV 2.4.2.
  • 1.4. Рекомендации по проведению занятий

    Рекомендуется придерживаться следующей последовательности при проведении занятия:

  • Выяснить у слушателей степень их владения библиотеками OpenCV и IPP. При необходимости кратко охарактеризовать функциональность и область назначения библиотек, показать примеры работы с ними (см. лабораторные работы, направленные на освоение OpenCV и IPP).
  • Совместно со слушателями разработать каркас тестового приложения. В случае недостатка времени дать слушателям готовый каркас приложения в исходных кодах и объяснить его наполнение.
  • Реализовать вызов тестируемых алгоритмов (медианная фильтрация, эрозия, дилатация, построение гистограммы).
  • Убедиться в корректности программы.
  • Сравнить производительность при запуске на одном изображении.
  • Предложить слушателям расширить приложение для запуска на серии изображений (либо зациклить обработку одного изображения).
  • Обсудить полученные результаты и перспективы совместного использования OpenCV и IPP.
  • 2. Разработка приложения для сравнения производительности операций над изображениями, реализованных на базе OpenCV и Intel IPP

    2.1. Требования к общей организации приложения

    Прежде чем перейти к созданию проекта в среде разработки Microsoft Visual Studio 2010 и написанию кода, обсудим некоторые общие моменты.

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

    Итак, в качестве алгоритмов для сравнения мы используем медианную фильтрацию, относящуюся к алгоритмам размытия, два морфологических преобразования: эрозию и дилатацию, – а также один из статистических алгоритмов: гистограмму.

    2.2. Создание проекта

    Создадим новое Решение (Solution), в которое включим Проект (Project) данной лабораторной работы. Последовательно выполните следующие шаги:

  • Запустите приложение Microsoft Visual Studio 2010.
  • В меню File выполните команду New$$\rightarrow$$Project….
  • Как показано на рис. 1, в диалоговом окне (рис 11.1) Создание решения для лабораторной работы
  • В диалоговом окне Win32 Application Wizard нажмите Next (или выберите Application Settings в дереве слева) и установите флаг Empty Project. Нажмите Finish.
  • В окне Solution Explorer в папке Source Files выполните команду контекстного меню Add$$\rightarrow$$New Item…. В дереве категорий слева выберите Code, в шаблонах справа – C++ File (.cpp), в поле Name введите имя файла main. Нажмите Add. В результате выполненной последовательности действий в окне редактора кода Visual Studio будет открыт пустой файл main.cpp.
  • 2.3. Настройка свойств проекта

    Настроить проект для использования библиотеки IPP из состава пакета Intel Parallel Studio XE 2013 очень просто. Понадобится следующая последовательность действий.

  • Выполните команду контекстного меню Properties, чтобы получить доступ к настройкам проекта.
  • Откройте вкладку Configuration Properties$$\rightarrow$$Intel Performance Libraries.
  • В выпадающем списке напротив свойства Use IPP выберите способ линковки библиотеки Intel® IPP (рис. 11.2). (рис 11.2) Перечень способов линковки библиотеки IPP
  • Для использования функций библиотеки OpenCV потребуется немного больше действий.

  • Сначала установим путь до заголовочных файлов библиотеки OpenCV. Выполните команду контекстного меню Properties, чтобы получить доступ к настройкам проекта. Откройте вкладку Configuration Properties$$\rightarrow$$C/C++$$\rightarrow$$General (рис. 11.3). Сверху в окне свойств в выпадающем списке Configuration выберите значение All Configurations, чтобы установить свойство для всех режимов компиляции (Debug и Release). В поле Additional Include Directories укажите пути до заголовочных файлов библиотеки OpenCV (например, c:\OpenCVuild\include). Нажмите кнопку Apply, чтобы применить указанное свойство. (рис 11.3) Установка путей до заголовочных файлов OpenCV
  • Далее установим путь до подключаемых библиотек (lib-файлов). Откройте вкладку Configuration Properties$$\rightarrow$$Linker$$\rightarrow$$General (рис. 11.4). Сверху в окне свойств в выпадающем списке Configuration выберите значение All Configurations. В поле Additional Library Directories укажите путь до lib-файлов (например, c:\OpenCVuild\x86\vc10\lib\). (рис 11.4) Установка путей до подключаемых библиотек OpenCV
  • Последним действием укажем список подключаемых библиотек. Откройте вкладку Configuration Properties$$\rightarrow$$Linker$$\rightarrow$$Input (рис. 11.5). Сначала в окне свойств в выпадающем списке Configuration выберите значение Debug и установите в поле Additional Dependencies список lib-файлов opencv_core242d.lib, opencv_imgproc242d.lib, opencv_highgui242d.lib, opencv_legacy242d.lib, opencv_video242d.lib, opencv_ml242d.lib, opencv_objdetect242d.lib. Аналогичные действия необходимо проделать для режима Release, указав список lib-файлов с теми же названиями за исключением отсутствия последней буквы d. (рис 11.5) Установка списка подключаемых библиотек OpenCV
  • 2.4. Функция main

    Перейдем теперь к разработке функции main(). Прежде всего необходимо подключить заголовочные файлы. Кроме функций из библиотек OpenCV и IPP нам потребуется также вывод сообщений на консоль с помощью printf() и замеры времени с использованием функции clock().

    #include <stdio.h> 
    #include <time.h> 
    #include <opencv2/opencv.hpp> 
    #include <ippi.h> 
    #include <ippcc.h>   
        

    Для более удобной работы с OpenCV подключим пространство имен cv.

    using namespace cv;   
        

    Имя файла с изображением будем передавать через командную строку.

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

    Далее представлен начальный вариант функции main(), содержащий, кроме указанного выше, также вызовы функций, в которых необходимо будет разместить код, реализующий соответствующие алгоритмы.

    char helper[] =   
      "01_OpenCVvsIPP.exe <img_name> <mode> <num_of_exp>\n\ 
      \t<img_name> - image filename\n\ 
      \t<mode>:\n\ 
      \t\t1 - median filtering\n\ 
      \t\t2 - erode\n\ 
      \t\t3 - dilate\n\ 
      \t\t4 - calc histogram\n\ 
      \t<num_of_exp> - number of experiments\n"; 
     
    int main(int argc, char *argv[]) 
    { 
      Mat srcImgOCV, srcImgIPP, dstImgOCV, dstImgIPP; 
      int mode, i; 
      double ocv_time, ipp_time; 
     
      if (argc < 4) 
      { 
        printf("%s", helper); 
        return 1; 
      } 
     
      // загрузить изображение 
      srcImgOCV = imread(argv[1]); 
      if (srcImgOCV.data == 0) 
      { 
        printf("ERROR!!! imread(...)"); 
        return 1; 
      } 
      srcImgOCV.copyTo(srcImgIPP); 
     
      mode = atoi(argv[2]); 
      switch (mode) 
      { 
      case 1: 
        // отфильтровать с помощью OpenCV 
        ocv_time = median_opencv(srcImgOCV, dstImgOCV); 
        // отфильтровать с помощью IPP 
        ipp_time = median_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 2: 
        // выполнить эрозию с помощью OpenCV 
        ocv_time = erode_opencv(srcImgOCV, dstImgOCV); 
        // выполнить эрозию с помощью IPP 
        ipp_time = erode_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 3: 
        // выполнить дилатацию с помощью OpenCV 
        ocv_time = dilate_opencv(srcImgOCV, dstImgOCV); 
        // выполнить дилатацию с помощью IPP 
        ipp_time = dilate_ipp(srcImgIPP, dstImgIPP); 
        break; 
      case 4: 
        // вычислить гистограмму с помощью OpenCV 
        ocv_time = hist_opencv(srcImgOCV, dstImgOCV); 
        // вычислить гистограмму с помощью IPP 
        ipp_time = hist_ipp(srcImgIPP, dstImgIPP); 
        break; 
      } 
     
      printf("ocv time is %.3f\nipp time is %.3f", ocv_time, 
        ipp_time); 
     
      // освободить память 
      srcImgOCV.release(); 
      srcImgIPP.release(); 
      dstImgOCV.release(); 
      dstImgIPP.release(); 
     
      return 0; 
    } 
        

    Предполагается, что каждая из выделенных полужирным шрифтом функций принимает на вход изображение в виде объекта типа Mat из OpenCV, выполняет требуемый алгоритм и возвращает новое изображение также в виде объекта типа Mat. Кроме того считаем, что все эти функции возвращают время выполнения в секундах.

    2.5. Реализация операций с использованием функционала библиотеки OpenCV

    Первые три задачи из выбранных нами средствами OpenCV решаются очень просто. Рассмотрим их подробнее.

    2.5.1. Функция медианной фильтрации

    Медианная фильтрация в библиотеке OpenCV реализуется с помощью функции medianBlur(). Ее подробное описание может быть найдено в лабораторной работе "Базовые операции обработки изображений" настоящего курса. Здесь же лишь отметим, что в экспериментах мы будем использовать размер ядра, равный 3, но предусмотрим возможность его изменения.

    double median_opencv(const Mat srcImg, Mat dstImg, 
      const int kSize = 3); 
     
    double median_opencv(const Mat srcImg, Mat dstImg, 
      const int kSize) 
    { 
      clock_t start, finish; 
       
      start = clock(); 
      medianBlur(srcImg, dstImg, kSize); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    2.5.2. Функция вычисления эрозии

    Эрозия изображения выполняется в OpenCV с помощью функции erode(). Используем простейший вариант ее вызова с тремя параметрами, при котором применяется квадратный шаблон $$3 \times 3$$.

    double erode_opencv(const Mat srcImg, Mat dstImg) 
    { 
      clock_t start, finish; 
     
      start = clock(); 
      Mat element = Mat(); 
      erode(srcImg, dstImg, element); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    2.5.3. Функция вычисления дилатации

    Полностью аналогично обстоит дело с функцией дилатации.

    double dilate_opencv(const Mat srcImg, Mat dstImg) 
    { 
      clock_t start, finish; 
     
      start = clock(); 
      Mat element = Mat(); 
      dilate(srcImg, dstImg, element); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    За описанием функций erode() и dilate() также отсылаем к лабораторной работе "Базовые операции обработки изображений".

    2.5.4. Функция вычисления гистограммы

    Для вычисления гистограммы используем часть кода, приведенного в соответствующем разделе лабораторной работы "Базовые операции обработки изображений". Будем строить гистограмму по каждому каналу цветного изображения, предварительно расщепляя его по каналам с помощью функции split().

    double hist_opencv(const Mat srcImg, Mat dstImg) 
    { 
      Mat bgrChannels[3], bHist, gHist, rHist; 
      // количество бинов гистограммы 
      int kBins = 256; 
      // интервал изменения значений бинов 
      float range[] = {0.0f, 256.0f}; 
      const float* histRange = { range }; 
      // равномерное распределение интервала по бинам 
      bool uniform = true; 
      // запрет очищения перед вычислением гистограммы 
      bool accumulate = false; 
      clock_t start, finish; 
     
      start = clock(); 
      split(srcImg, bgrChannels); 
      // вычисление гистограммы для каждого канала 
      calcHist(bgrChannels[0], 1, 0, Mat(), bHist, 1, 
        kBins, histRange, uniform, accumulate); 
      calcHist(bgrChannels[1], 1, 0, Mat(), gHist, 1, 
        kBins, histRange, uniform, accumulate); 
      calcHist(bgrChannels[2], 1, 0, Mat(), rHist, 1, 
        kBins, histRange, uniform, accumulate); 
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }   
          

    2.6. Реализация операций с использованием функционала библиотеки Intel IPP

    2.6.1. Функция медианной фильтрации

    Медианная фильтрация в IPP выполняется при помощи одной из функций вида ippiFilterMedian_<mod>. В случае цветного изображения mod – 3uC3R. Кроме того, в документации к IPP указано, что если мы хотим применить фильтр с шаблоном размера msk, необходимо расширить матрицу изображения на msk / 2 пикселов с каждой стороны. Для расширения используем функцию ippiCopyReplicateBorder_8u_C3R(). Наконец, после получения "расширенного" отфильтрованного изображения, необходимо выполнить обратную операцию – выделить изображение исходного размера. Это можно сделать, просто скопировав внутреннюю часть расширенной матрицы пикселей с помощью функции ippiCopy_8u_C3R().

    Получим следующий код.

    double median_ipp(const Mat srcImg, Mat dstImg, 
      const int msk = 3); 
     
    double median_ipp(const Mat srcImg, Mat dstImg, 
      const int msk) 
    { 
      const int borderSize = msk / 2; 
      IppiSize mskSize = { msk , msk }; 
      IppiPoint anchor = { msk / 2, msk / 2 }; 
      Mat ippSrcImg, ippDstImg; 
      Size ippImgSize; 
      IppiSize ippSrcSize, ippDstSize; 
      Ipp8u *pSrcData, *pDstData; 
      clock_t start, finish; 
     
      ippImgSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippImgSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippSrcImg.create(ippImgSize, srcImg.type()); 
      ippDstImg.create(ippImgSize, srcImg.type()); 
      pSrcData = (Ipp8u *)ippSrcImg.data; 
      pDstData = (Ipp8u *)ippDstImg.data; 
     
      start = clock(); 
      ippSrcSize.width = srcImg.size().width; 
      ippSrcSize.height = srcImg.size().height; 
      ippDstSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippDstSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippiCopyReplicateBorder_8u_C3R(srcImg.data, 
        srcImg.step1(), ippSrcSize, 
        pSrcData, srcImg.step1() + 3 * 2 * borderSize, 
        ippDstSize, borderSize, borderSize); 
     
      ippiFilterMedian_8u_C3R(pSrcData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, pDstData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, ippSrcSize, 
        mskSize, anchor); 
      finish = clock(); 
     
      dstImg.create(srcImg.size(), srcImg.type()); 
      pSrcData = pDstData; 
      pDstData = (Ipp8u *)dstImg.data; 
      ippiCopy_8u_C3R(pSrcData + 
        ippImgSize.width * 3 * borderSize + 3 * borderSize, 
        srcImg.step1() + 3 * 2 * borderSize, pDstData, 
      dstImg.step1(), ippSrcSize); 
     
      ippSrcImg.release(); 
      ippDstImg.release(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    Отметим, что для построения "расширенной" матрицы изображения мы использовали функции OpenCV для работы c типом Mat.

      ippImgSize.width = 
        srcImg.size().width + 2 * borderSize; 
      ippImgSize.height = 
        srcImg.size().height + 2 * borderSize; 
      ippSrcImg.create(ippImgSize, srcImg.type()); 
      ippDstImg.create(ippImgSize, srcImg.type()); 
      pSrcData = (Ipp8u *)ippSrcImg.data; 
      pDstData = (Ipp8u *)ippDstImg.data;   
          

    Конечно, то же самое можно проделать только средствами IPP. Внести соответствующие изменения предлагаем читателю самостоятельно.

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

    2.6.2. Функция вычисления эрозии

    Реализацию функций для вычисления эрозии и дилатации средствами IPP мы выполним несколько по-другому. Дело в том, что представленные выше варианты вызовов функций erode() и dilate() из OpenCV выполняют обработку только внутренних пикселей исходного изображения. Таким образом, и в IPP-версии нам нет необходимости расширять матрицу изображения. Вместо этого мы можем соответствующим образом определить область интереса (ROI).

    double erode_ipp(const Mat srcImg, Mat dstImg) 
    { 
      IppiMaskSize msk; 
      Ipp8u *pSrcData, *pDstData; 
      IppiSize srcSize, dstRoiSize; 
      clock_t start, finish; 
     
      srcImg.copyTo(dstImg); 
     
      start = clock(); 
      pSrcData = (Ipp8u *)srcImg.data; 
      pDstData = (Ipp8u *)dstImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      msk = ippMskSize3x3; 
      dstRoiSize.width = srcImg.size().width - 2; 
      dstRoiSize.height = srcImg.size().height - 2; 
      ippiErode3x3_8u_C3R(pSrcData + 
        srcSize.width * 3 * 1 + 3 * 1, srcImg.step1(), 
        pDstData + srcSize.width * 3 * 1 + 3 * 1, 
        srcImg.step1(), dstRoiSize); 
     
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
          

    2.6.3. Функция вычисления дилатации

    Реализация функции вычисления дилатации средствами IPP совпадает с таковой для функции эрозии за исключением вызова функции ippiDilate3x3_8u_C3R() вместо ippiErode3x3_8u_C3R().

    double erode_ipp(const Mat srcImg, Mat dstImg) 
    { 
      IppiMaskSize msk; 
      Ipp8u *pSrcData, *pDstData; 
      IppiSize srcSize, dstRoiSize; 
      clock_t start, finish; 
     
      srcImg.copyTo(dstImg); 
     
      start = clock(); 
      pSrcData = (Ipp8u *)srcImg.data; 
      pDstData = (Ipp8u *)dstImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      msk = ippMskSize3x3; 
      dstRoiSize.width = srcImg.size().width - 2; 
      dstRoiSize.height = srcImg.size().height - 2; 
      ippiDilate3x3_8u_C3R(pSrcData + 
        srcSize.width * 3 * 1 + 3 * 1, srcImg.step1(), 
        pDstData + srcSize.width * 3 * 1 + 3 * 1, 
        srcImg.step1(), dstRoiSize); 
     
      finish = clock(); 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    }     
          

    2.6.4. Функция вычисления гистограммы

    Вычислить гистограмму для многоканального изображения, можно используя функции IPP вида ippiHistogramRange_<mod>. Как и ранее в нашем случае mod – 8u_C3R. Для вызова этой функции необходимо предварительно подготовить:

  • параметр pHisto вида

      Ipp32s *pHisto[3]; 
      pHisto[0] = new Ipp32s[kBins + 1]; 
      pHisto[1] = new Ipp32s[kBins + 1]; 
      pHisto[2] = new Ipp32s[kBins + 1];   
              

    для размещения гистограмм по каждому каналу (kBins – число бинов гистограммы),

  • константный параметр pLevels вида

      Ipp32s level1[kBins + 1], level2[kBins + 1], 
        level3[kBins + 1]; 
      const Ipp32s *pLevels[3] = { level1, level2, level3 };   
              

    для указания уровней

      for (i = 0; i < kBins + 1; i++) 
        level1[i] = level2[i] = level3[i] = i;   
              
  • и массив nLevels вида

      int nLevels[3] = {kBins + 1, kBins + 1, kBins + 1};   
              

    для задания числа уровней по каждому каналу.

  • Смысл параметров pHisto и pLevels для выбранного канала можно проиллюстрировать так: pHisto[k] – это число пикселей изображения, таких что pLevels[k] <= pHisto[k] < pLevels[k+1].

    Получим следующий код.

    double hist_ipp(const Mat srcImg, Mat dstImg) 
    { 
      // количество бинов гистограммы 
      const int kBins = 256; 
      Ipp8u *pSrcData; 
      IppiSize srcSize; 
      int i; 
      Ipp32s level1[kBins + 1], level2[kBins + 1], 
        level3[kBins + 1]; 
      Ipp32s *pHisto[3]; 
      const Ipp32s *pLevels[3] = { level1, level2, level3 }; 
      int nLevels[3] = {kBins + 1, kBins + 1, kBins + 1}; 
      clock_t start, finish; 
     
      pHisto[0] = new Ipp32s[kBins + 1]; 
      pHisto[1] = new Ipp32s[kBins + 1]; 
      pHisto[2] = new Ipp32s[kBins + 1]; 
     
      for (i = 0; i < kBins + 1; i++) 
        level1[i] = level2[i] = level3[i] = i; 
     
      pSrcData = (Ipp8u *)srcImg.data; 
      srcSize.width = srcImg.size().width; 
      srcSize.height = srcImg.size().height; 
     
      start = clock(); 
      ippiHistogramRange_8u_C3R(pSrcData, srcImg.step1(), 
        srcSize, pHisto, pLevels, nLevels); 
      finish = clock(); 
     
      delete [] pHisto[0]; 
      delete [] pHisto[1]; 
      delete [] pHisto[2]; 
     
      return double(finish - start) / CLOCKS_PER_SEC; 
    } 
     
          

    2.7. Анализ корректности

    Для медианного фильтра, эрозии и дилатации результирующие изображения, полученные средствами OpenCV и IPP, можно сравнить попиксельно. Для этого напишем следующую функцию, возвращающую число точек в двух изображениях с несовпадающими значениями.

    int compare(const Mat srcImgOCV, const Mat srcImgIPP) 
    { 
      int kPoints = 0; 
      int w, h, i, j, idx; 
      w = srcImgOCV.size().width; 
      h = srcImgOCV.size().height; 
      for (i = 0; i < h; i++) 
      { 
        idx = 3 * i * w; 
        for (j = 0; j < w; j++, idx += 3) 
          if (((srcImgOCV.data[idx]-srcImgIPP.data[idx])!=0) 
            ||((srcImgOCV.data[idx+1]- 
              srcImgIPP.data[idx+1])!=0) 
            ||((srcImgOCV.data[idx+2]- 
              srcImgIPP.data[idx+2])!=0)) 
        { 
          kPoints++; 
        } 
      } 
     
      return kPoints; 
    }   
        

    Осталось добавить в функцию main() фрагмент для проверки корректности.

      int kDiffPoints = 0; 
    ... 
      // сравнить результаты 
      if (mode < 4) 
      { 
        kDiffPoints = compare(dstImgOCV, dstImgIPP); 
        if (kDiffPoints == 0) 
          printf("OpenCV and IPP give the same result.\n"); 
        else 
          printf("OpenCV and IPP give different results in %d\ 
            points.\n", kDiffPoints); 
      } 
        

    Сравнение результатов построения гистограммы средствами OpenCV и IPP предоставляем провести читателю самостоятельно.

    2.8. Запуск приложения

    С учетом использованных нами функций OpenCV для запуска программы потребуются следующие динамические библиотеки: opencv_core242.dll, opencv_highgui242.dll, opencv_imgproc242.dll, tbb.dll.

    Первые три располагаются в папке build\x86\vc10\bin\. Последняя – в build\common\tbb\ia32\vc10\.

    Использование функций IPP предполагает, что следующий путь должен быть известен операционной системе "C:\Program Files (x86)\Intel\Composer XE 2013\redist\ia32\ipp\".

    Собственно запуск можно выполнить, используя командную строку вида

    OpenCVvsIPP.exe Desert.jpg 1

    3. Сравнение производительности операций над изображениями, реализованных на базе OpenCV и Intel IPP

    3.1. Тестовые данные

    Все рассматриваемые в данной лабораторной работе алгоритмы имеют квадратичную сложность. Чтобы подготовленные нами реализации показывали времена, на которых имеет смысл проводить сравнение, необходимо использовать или подготовить изображение достаточно большого размера. Используем файл Desert.jpg из состава операционной системы Windows 7, расположенный в папке C:\Users\Public\Pictures\Sample Pictures. Этот файл имеет размер 1024?768 пикселей. Создадим из него изображение размера 8192?6144 пиксела, скопировав исходную картинку нужное число раз в любом графическом редакторе.

    На указанном изображении авторами были получены следующие времена.

    OpenCV (время, с) IPP (время, с)
    Медианный фильтр 0.361 0.319
    Эрозия 0.234 0.115
    Дилатация 0.224 0.118
    Гистограмма 0.351 0.130

    3.2. Проведение вычислительных экспериментов

    Для получения более адекватных результатов добавим в функцию main возможность проведения серии запусков для каждого из алгоритмов. Число запусков будем передавать через еще один параметр командной строки.

    Таким образом, необходимо изменить следующие фрагменты.

    Дополнить описание запуска.

    char helper[] = 
      "01_OpenCVvsIPP.exe <img_name> <mode> <num_of_exp>\n\ 
      \t<img_name> - image filename\n\ 
      \t<mode>:\n\ 
      \t\t1 - median filtering\n\ 
      \t\t2 - erode\n\ 
      \t\t3 - dilate\n\ 
      \t\t4 - calc histogram\n\ 
      \t<num_of_exp> - number of experiments\n"; 
       
        

    Исправить проверку на число параметров командной строки

      if (argc < 4) 
      { 
        printf("%s", helper); 
        return 1; 
      }   
        

    Считать значение параметра.

      int numOfExp; 
    ... 
      numOfExp = atoi(argv[3]);   
        

    Изменить вызовы функций

      ocv_time = ipp_time = 0; 
      switch (mode) 
      { 
      case 1: 
        for (i = 0; i < numOfExp; i++) 
        { 
          // отфильтровать с помощью OpenCV 
          ocv_time += median_opencv(srcImgOCV, dstImgOCV); 
          // отфильтровать с помощью IPP 
          ipp_time += median_ipp(srcImgIPP, dstImgIPP); 
        } 
    ...   
        

    Выполните указанные изменения и проведите эксперименты с разными значениями параметра numOfExp.

    4. Дополнительные задания

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