
Данная статья будет полезна новичкам, которые только начали использовать библиотеку OpenCV и еще не знают все её возможности. В частности, на основе биоинспирированного модуля библиотеки OpenCV можно сделать адаптивный к освещению детектор движения. Данный детектор движения будет работать в полумраке лучше, чем обычное вычитание двух кадров.
Немного о Retina
Библиотека OpenCV содержит класс Retina, в котором есть пространственно-временной фильтр двух информационных каналов (parvocellular pathway и magnocellular pathway) модели сетчатки глаза. Нас интересует канал magnocellular, который по сути уже и есть детектор движения: остается только получить координаты участка изображения, где есть движение, и каким-то образом не реагировать на помехи, которые возникают, если детектору движения показывают статичную картинку.

Помехи на выходе канала magno при отсутствии движения на изображении
Код
Сначала надо подключить биоинспирированный модуль и инициализировать его. В данном примере модуль настроен на работу без использования цвета.
Подключение и инициализация модуля
#include "opencv2/bioinspired.hpp" // Подключаем модуль cv::Ptr<cv::bioinspired::Retina> cvRetina; // Модуль сетчатки глаза // Инициализация void initRetina(cv::Mat* inputFrame) { cvRetina = cv::bioinspired::createRetina( inputFrame->size(), // Устанавливаем размер изображения false, // Выбранный режим обработки: без обработки цвета cv::bioinspired::RETINA_COLOR_DIAGONAL, // Тип выборки цвета false, // отключить следующие два параметра 1.0, // Не используется. Определяет коэффициент уменьшения выходного кадра 10.0); // Не используется // сохраняем стандартные настройки cvRetina->write("RetinaDefaultParameters.xml"); // загруждаем настройки cvRetina->setup("RetinaDefaultParameters.xml"); // очищаем буфер cvRetina->clearBuffers(); }
В файле *RetinaDefaultParameters.xml* будут сохранены настройки по умолчанию. Возможно, будет смысл их подправить.
RetinaDefaultParameters
Для себя я менял пару параметров (ColorMode и amacrinCellsTemporalCutFrequency). Ниже представлен перевод описания некоторых параметров для выхода magno.
normaliseOutput — определяет, будет ли (true) выход масштабироваться в диапазоне от 0 до 255 не (false)
ColorMode — определяет, будет ли (true) использоваться цвет для обработки, или (false) будет идти обработка серого изображения.
photoreceptorsLocalAdaptationSensitivity — чувствительность фоторецепторов (от 0 до 1).
photoreceptorsTemporalConstant — постоянная времени фильтра нижних частот первого порядка фоторецепторов, использовать его нужно, чтобы сократить высокие временные частоты (шум или быстрое движение). Используется блок кадров, типичное значение 1 кадр.
photoreceptorsSpatialConstant — пространственная константа фильтра нижних частот первого порядка фоторецепторов. Можно использовать его, чтобы сократить высокие пространственные частоты (шум или толстые контуры). Используется блок пикселей, типичное значение — 1 пиксель.
horizontalCellsGain — усиление горизонтальной сети ячеек. Если значение равно 0, то среднее значение выходного сигнала равно нулю. Если параметр находится вблизи 1, то яркость не фильтруется и по-прежнему достижима на выходе. Типичное значение равно 0.
HcellsTemporalConstant — постоянная времени фильтра нижних частот первого порядка горизонтальных клеток. Этот пункт нужен, чтобы вырезать низкие временные частоты (локальные вариации яркости). Используется блок кадров, типичное значение — 1 кадр.
HcellsSpatialConstant — пространственная константа фильтра нижних частот первого порядка горизонтальных клеток. Нужно использовать для того, чтобы вырезать низкие пространственные частоты (локальная яркость). Используется блок пикселей, типичное значение — 5 пикселей.
ganglionCellsSensitivity — сила сжатия локального выхода адаптации ганглиозных клеток, установите значение в диапазоне от 0,6 до 1 для достижения наилучших результатов. Значение возрастает соответственно тому, как падает чувствительность. И выходной сигнал насыщается быстрее. Рекомендуемое значение — 0,7.
<?xml version="1.0"?> <opencv_storage> <OPLandIPLparvo> <colorMode>0</colorMode> <normaliseOutput>1</normaliseOutput> <photoreceptorsLocalAdaptationSensitivity>0.89e-001</photoreceptorsLocalAdaptationSensitivity> <photoreceptorsTemporalConstant>5.0000000000000000e-001</photoreceptorsTemporalConstant> <photoreceptorsSpatialConstant>1.2999997138977051e-001</photoreceptorsSpatialConstant> <horizontalCellsGain>0.3</horizontalCellsGain> <hcellsTemporalConstant>1.</hcellsTemporalConstant> <hcellsSpatialConstant>7.</hcellsSpatialConstant> <ganglionCellsSensitivity>0.89e-001</ganglionCellsSensitivity></OPLandIPLparvo> <IPLmagno> <normaliseOutput>1</normaliseOutput> <parasolCells_beta>0.1</parasolCells_beta> <parasolCells_tau>0.1</parasolCells_tau> <parasolCells_k>7.</parasolCells_k> <amacrinCellsTemporalCutFrequency>1.2000000476837158e+000</amacrinCellsTemporalCutFrequency> <V0CompressionParameter>5.4999998807907104e-001</V0CompressionParameter> <localAdaptintegration_tau>0.</localAdaptintegration_tau> <localAdaptintegration_k>7.</localAdaptintegration_k></IPLmagno> </opencv_storage>
Для себя я менял пару параметров (ColorMode и amacrinCellsTemporalCutFrequency). Ниже представлен перевод описания некоторых параметров для выхода magno.
normaliseOutput — определяет, будет ли (true) выход масштабироваться в диапазоне от 0 до 255 не (false)
ColorMode — определяет, будет ли (true) использоваться цвет для обработки, или (false) будет идти обработка серого изображения.
photoreceptorsLocalAdaptationSensitivity — чувствительность фоторецепторов (от 0 до 1).
photoreceptorsTemporalConstant — постоянная времени фильтра нижних частот первого порядка фоторецепторов, использовать его нужно, чтобы сократить высокие временные частоты (шум или быстрое движение). Используется блок кадров, типичное значение 1 кадр.
photoreceptorsSpatialConstant — пространственная константа фильтра нижних частот первого порядка фоторецепторов. Можно использовать его, чтобы сократить высокие пространственные частоты (шум или толстые контуры). Используется блок пикселей, типичное значение — 1 пиксель.
horizontalCellsGain — усиление горизонтальной сети ячеек. Если значение равно 0, то среднее значение выходного сигнала равно нулю. Если параметр находится вблизи 1, то яркость не фильтруется и по-прежнему достижима на выходе. Типичное значение равно 0.
HcellsTemporalConstant — постоянная времени фильтра нижних частот первого порядка горизонтальных клеток. Этот пункт нужен, чтобы вырезать низкие временные частоты (локальные вариации яркости). Используется блок кадров, типичное значение — 1 кадр.
HcellsSpatialConstant — пространственная константа фильтра нижних частот первого порядка горизонтальных клеток. Нужно использовать для того, чтобы вырезать низкие пространственные частоты (локальная яркость). Используется блок пикселей, типичное значение — 5 пикселей.
ganglionCellsSensitivity — сила сжатия локального выхода адаптации ганглиозных клеток, установите значение в диапазоне от 0,6 до 1 для достижения наилучших результатов. Значение возрастает соответственно тому, как падает чувствительность. И выходной сигнал насыщается быстрее. Рекомендуемое значение — 0,7.
Для ускорения вычислений есть смысл предварительно уменьшить входящее изображение с помощью функции *cv::resize*. Для определения наличия помех можно использовать значение средней яркости изображения или энтропию. Также в одном из проектов я использовал подсчет пикселей выше и ниже определенного уровня яркости. Ограничительные рамки можно получить с помощью функции для поиска контуров. Под спойлером представлен код детектора движения, который не претендует на работоспособность, а лишь показывают примерную возможную реализацию.
Код детектора движения
// размер буфера для медианного фильтра средней яркости и энтропии #define CV_MOTION_DETECTOR_MEDIAN_FILTER_N 512 // буферы для фильтров static float meanBuffer[CV_MOTION_DETECTOR_MEDIAN_FILTER_N]; static float entropyBuffer[CV_MOTION_DETECTOR_MEDIAN_FILTER_N]; // количество кадров static int numFrame = 0; // Возвращает медиану массива float getMedianArrayf(float* data, unsigned long nData); // детектор движения // inputFrame - входное изображение RGB типа CV_8UC3 // arrayBB - массив ограничительных рамок void updateMotionDetector(cv::Mat* inputFrame,std::vector<cv::Rect2f>& arrayBB) { cv::Mat retinaOutputMagno; // изображение на выходе magno cv::Mat imgTemp; // изображение для порогового преобразования float medianEntropy, medianMean; // отфильтрованные значения cvRetina->run(*inputFrame); // загружаем изображение детектора движения cvRetina->getMagno(retinaOutputMagno); // отобразим на экране, если нужно для отладки cv::imshow("retinaOutputMagno", retinaOutputMagno); // подсчет количества кадров до тех пор, пока их меньше заданного числа if (numFrame < CV_MOTION_DETECTOR_MEDIAN_FILTER_N) { numFrame++; } // получаем среднее значение яркости всех пикселей float mean = cv::mean(retinaOutputMagno)[0]; // получаем энтропию float entropy = calcEntropy(&retinaOutputMagno); // фильтруем данные if (numFrame >= 2) { // фильтруем значения энтропии // сначала сдвинем буфер значений // энтропии и запишем новый элемент for (i = numFrame - 1; i > 0; i--) { entropyBuffer[i] = entropyBuffer[i - 1]; } entropyBuffer[0] = entropy; // фильтруем значения средней яркости // сначала сдвинем буфер значений // средней яркости и запишем новый элемент for (i = numFrame - 1; i > 0; i--) { meanBuffer[i] = meanBuffer[i - 1]; } meanBuffer[0] = mean; // для фильтрации применим медианный фильтр medianEntropy = getMedianArrayf(entropyBuffer, numFrame); medianMean = getMedianArrayf(meanBuffer, numFrame); } else { medianEntropy = entropy; medianMean = mean; } // если средняя яркость не очень высокая, то на изображении движение, а не шум // if (medianMean >= mean) { // если энтропия меньше медианы, то на изображении движение, а не шум if ((medianEntropy * 0.85) >= entropy) { // делаем пороговое преобразование // как правило, области с движением достаточно яркие // поэтому можно обойтись и без медианы средней яркости // cv::threshold(retinaOutputMagno, imgTemp,150, 255.0, CV_THRESH_BINARY); // пороговое преобразование с учетом медианы средней яркости cv::threshold(retinaOutputMagno, imgTemp,150, 255.0, CV_THRESH_BINARY); // найдем контуры std::vector<std::vector<cv::Point>> contours; cv::findContours(imgTemp, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); if (contours.size() > 0) { // если контуры есть arrayBB.resize(contours.size()); // найдем ограничительные рамки float xMax, yMax; float xMin, yMin; for (unsigned long i = 0; i < contours.size(); i++) { xMax = yMax = 0; xMin = yMin = imgTemp.cols; for (unsigned long z = 0; z < contours[i].size(); z++) { if (xMax < contours[i][z].x) { xMax = contours[i][z].x; } if (yMax < contours[i][z].y) { yMax = contours[i][z].y; } if (xMin > contours[i][z].x) { xMin = contours[i][z].x; } if (yMin > contours[i][z].y) { yMin = contours[i][z].y; } } arrayBB[i].x = xMin; arrayBB[i].y = yMin; arrayBB[i].width = xMax - xMin ; arrayBB[i].height = yMax - yMin; } } else { arrayBB.clear(); } } else { arrayBB.clear(); } // освободим память retinaOutputMagno.release(); imgTemp.release(); } // быстрая сортировка массива template<typename aData> void quickSort(aData* a, long l, long r) { long i = l, j = r; aData temp, p; p = a[ l + (r - l)/2 ]; do { while ( a[i] < p ) i++; while ( a[j] > p ) j--; if (i <= j) { temp = a[i]; a[i] = a[j]; a[j] = temp; i++; j--; } } while ( i<=j ); if ( i < r ) quickSort(a, i, r); if ( l < j ) quickSort(a, l , j); }; // Возвращает медиану массива float getMedianArrayf(float* data, unsigned long nData) { float medianData; float mData[nData]; register unsigned long i; if (nData == 0) return 0; if (nData == 1) { medianData = data[0]; return medianData; } for (i = 0; i != nData; ++i) { mData[i] = data[i]; } quickSort(mData, 0, nData - 1); medianData = mData[nData >> 1]; return medianData; };

Пример работы детектора движения.
