Презентацию к лекции Вы можете скачать здесь.
Дополнительные материалы Вы можете скачать здесь.
Производительность является одной из важнейших характеристик программного обеспечения. Так, например, при мониторинге состояния технических объектов от времени, необходимого на предсказание или диагностику возможной неисправности, может зависеть жизнь и здоровье людей. В различных "офисных" приложениях производительность существенно влияет на потребительские свойства продукта и его субъективное восприятие пользователями. В научных приложениях время счета может, в частности, ограничивать размерность решаемых задач, лимитируя возможности ученых по моделированию и исследованию физических, химических, экономических и других процессов. Наконец, в некоторых случаях недостаточно эффективно работающая программа в принципе не позволяет решать поставленную задачу на доступной вычислительной системе за приемлемое время. Заметим, что экстенсивный путь, состоящий в увеличении мощности аппаратной части является затратным с финансовой точки зрения, приводит к необходимости использования дополнительных площадей, увеличивает потребление электроэнергии и, что особенно важно, далеко не всегда приводит к решению проблемы. Перечисленные факторы определяют актуальность постановки вопроса о разработке высокопроизводительного программного обеспечения, эффективно работающего на современных вычислительных системах.
Для достижения хорошей эффективности прежде всего необходимо определиться с тем, что это такое, и научиться ее оценивать, как теоретически, так и экспериментально. Эффективность работы алгоритма и соответствующей ему программы можно понимать по-разному. Теория сложности вычислений (см., например, [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], что и обуславливает практический интерес к вопросу о том, что работает быстрее. Производительность некоторых таких алгоритмов сравнивается в данной лабораторной работе.
Целью работы является сравнение производительности некоторых алгоритмов, реализованных как в библиотеке OpenCV, так и в библиотеке IPP.
Для достижения данной цели необходимо решить следующие задачи:
Работа построена следующим образом: в начале демонстрируется процесс разработки программного приложения, позволяющего единообразно организовать запуск экспериментов по сравнению производительности. Далее поясняется, как можно выполнить сравнение для алгоритмов медианной фильтрации, эрозии, дилатации, построения гистограммы. Приводятся результаты первых прикидочных экспериментов, даются рекомендации и задания для самостоятельной проработки.
В работе использовалось следующее аппаратное и программное обеспечение:
Рекомендуется придерживаться следующей последовательности при проведении занятия:
Прежде чем перейти к созданию проекта в среде разработки Microsoft Visual Studio 2010 и написанию кода, обсудим некоторые общие моменты.
Сравнение производительности мы будем проводить на реализациях некоторых выбранных алгоритмов обработки изображений из библиотек OpenCV и IPP. При этом нашей задачей не является масштабное сравнение указанных библиотек: слишком много экспериментов пришлось бы провести. Выбор конкретных функций для сравнения также субъективен. Дополнительная сложность связана с тем, что в OpenCV и IPP существенно разным образом устроены базовые структуры данных, а поскольку в IPP нет средств загрузки изображений, нам придется выполнять предварительное их преобразование.
Итак, в качестве алгоритмов для сравнения мы используем медианную фильтрацию, относящуюся к алгоритмам размытия, два морфологических преобразования: эрозию и дилатацию, – а также один из статистических алгоритмов: гистограмму.
Создадим новое Решение (Solution), в которое включим Проект (Project) данной лабораторной работы. Последовательно выполните следующие шаги:
Настроить проект для использования библиотеки IPP из состава пакета Intel Parallel Studio XE 2013 очень просто. Понадобится следующая последовательность действий.
(рис 11.2) Перечень способов линковки библиотеки IPPДля использования функций библиотеки OpenCV потребуется немного больше действий.
(рис 11.3) Установка путей до заголовочных файлов OpenCV
(рис 11.4) Установка путей до подключаемых библиотек OpenCV
(рис 11.5) Установка списка подключаемых библиотек OpenCVПерейдем теперь к разработке функции 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. Кроме того считаем, что все эти функции возвращают время выполнения в секундах.
Первые три задачи из выбранных нами средствами OpenCV решаются очень просто. Рассмотрим их подробнее.
Медианная фильтрация в библиотеке 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;
}
Эрозия изображения выполняется в 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;
}
Полностью аналогично обстоит дело с функцией дилатации.
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() также отсылаем к лабораторной работе "Базовые операции обработки изображений".
Для вычисления гистограммы используем часть кода, приведенного в соответствующем разделе лабораторной работы "Базовые операции обработки изображений". Будем строить гистограмму по каждому каналу цветного изображения, предварительно расщепляя его по каналам с помощью функции 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;
}
Медианная фильтрация в 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 по умолчанию.
Реализацию функций для вычисления эрозии и дилатации средствами 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;
}
Реализация функции вычисления дилатации средствами 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;
}
Вычислить гистограмму для многоканального изображения, можно используя функции 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;
}
Для медианного фильтра, эрозии и дилатации результирующие изображения, полученные средствами 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 предоставляем провести читателю самостоятельно.
С учетом использованных нами функций 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
Все рассматриваемые в данной лабораторной работе алгоритмы имеют квадратичную сложность. Чтобы подготовленные нами реализации показывали времена, на которых имеет смысл проводить сравнение, необходимо использовать или подготовить изображение достаточно большого размера. Используем файл 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 |
Для получения более адекватных результатов добавим в функцию 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.
Презентацию к лекции Вы можете скачать здесь.
Дополнительные материалы Вы можете скачать здесь.
Производительность является одной из важнейших характеристик программного обеспечения. Так, например, при мониторинге состояния технических объектов от времени, необходимого на предсказание или диагностику возможной неисправности, может зависеть жизнь и здоровье людей. В различных "офисных" приложениях производительность существенно влияет на потребительские свойства продукта и его субъективное восприятие пользователями. В научных приложениях время счета может, в частности, ограничивать размерность решаемых задач, лимитируя возможности ученых по моделированию и исследованию физических, химических, экономических и других процессов. Наконец, в некоторых случаях недостаточно эффективно работающая программа в принципе не позволяет решать поставленную задачу на доступной вычислительной системе за приемлемое время. Заметим, что экстенсивный путь, состоящий в увеличении мощности аппаратной части является затратным с финансовой точки зрения, приводит к необходимости использования дополнительных площадей, увеличивает потребление электроэнергии и, что особенно важно, далеко не всегда приводит к решению проблемы. Перечисленные факторы определяют актуальность постановки вопроса о разработке высокопроизводительного программного обеспечения, эффективно работающего на современных вычислительных системах.
Для достижения хорошей эффективности прежде всего необходимо определиться с тем, что это такое, и научиться ее оценивать, как теоретически, так и экспериментально. Эффективность работы алгоритма и соответствующей ему программы можно понимать по-разному. Теория сложности вычислений (см., например, [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], что и обуславливает практический интерес к вопросу о том, что работает быстрее. Производительность некоторых таких алгоритмов сравнивается в данной лабораторной работе.
Целью работы является сравнение производительности некоторых алгоритмов, реализованных как в библиотеке OpenCV, так и в библиотеке IPP.
Для достижения данной цели необходимо решить следующие задачи:
Работа построена следующим образом: в начале демонстрируется процесс разработки программного приложения, позволяющего единообразно организовать запуск экспериментов по сравнению производительности. Далее поясняется, как можно выполнить сравнение для алгоритмов медианной фильтрации, эрозии, дилатации, построения гистограммы. Приводятся результаты первых прикидочных экспериментов, даются рекомендации и задания для самостоятельной проработки.
В работе использовалось следующее аппаратное и программное обеспечение:
Рекомендуется придерживаться следующей последовательности при проведении занятия:
Прежде чем перейти к созданию проекта в среде разработки Microsoft Visual Studio 2010 и написанию кода, обсудим некоторые общие моменты.
Сравнение производительности мы будем проводить на реализациях некоторых выбранных алгоритмов обработки изображений из библиотек OpenCV и IPP. При этом нашей задачей не является масштабное сравнение указанных библиотек: слишком много экспериментов пришлось бы провести. Выбор конкретных функций для сравнения также субъективен. Дополнительная сложность связана с тем, что в OpenCV и IPP существенно разным образом устроены базовые структуры данных, а поскольку в IPP нет средств загрузки изображений, нам придется выполнять предварительное их преобразование.
Итак, в качестве алгоритмов для сравнения мы используем медианную фильтрацию, относящуюся к алгоритмам размытия, два морфологических преобразования: эрозию и дилатацию, – а также один из статистических алгоритмов: гистограмму.
Создадим новое Решение (Solution), в которое включим Проект (Project) данной лабораторной работы. Последовательно выполните следующие шаги:
Настроить проект для использования библиотеки IPP из состава пакета Intel Parallel Studio XE 2013 очень просто. Понадобится следующая последовательность действий.
(рис 11.2) Перечень способов линковки библиотеки IPPДля использования функций библиотеки OpenCV потребуется немного больше действий.
(рис 11.3) Установка путей до заголовочных файлов OpenCV
(рис 11.4) Установка путей до подключаемых библиотек OpenCV
(рис 11.5) Установка списка подключаемых библиотек OpenCVПерейдем теперь к разработке функции 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. Кроме того считаем, что все эти функции возвращают время выполнения в секундах.
Первые три задачи из выбранных нами средствами OpenCV решаются очень просто. Рассмотрим их подробнее.
Медианная фильтрация в библиотеке 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;
}
Эрозия изображения выполняется в 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;
}
Полностью аналогично обстоит дело с функцией дилатации.
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() также отсылаем к лабораторной работе "Базовые операции обработки изображений".
Для вычисления гистограммы используем часть кода, приведенного в соответствующем разделе лабораторной работы "Базовые операции обработки изображений". Будем строить гистограмму по каждому каналу цветного изображения, предварительно расщепляя его по каналам с помощью функции 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;
}
Медианная фильтрация в 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 по умолчанию.
Реализацию функций для вычисления эрозии и дилатации средствами 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;
}
Реализация функции вычисления дилатации средствами 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;
}
Вычислить гистограмму для многоканального изображения, можно используя функции 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;
}
Для медианного фильтра, эрозии и дилатации результирующие изображения, полученные средствами 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 предоставляем провести читателю самостоятельно.
С учетом использованных нами функций 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
Все рассматриваемые в данной лабораторной работе алгоритмы имеют квадратичную сложность. Чтобы подготовленные нами реализации показывали времена, на которых имеет смысл проводить сравнение, необходимо использовать или подготовить изображение достаточно большого размера. Используем файл 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 |
Для получения более адекватных результатов добавим в функцию 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.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.