Как стать автором
Поиск
Написать публикацию
Обновить

Детектор движения на основе биоинспирированного модуля OpenCV

Время на прочтение7 мин
Количество просмотров18K
image

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

Немного о Retina


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

image

Помехи на выходе канала 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
<?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;
    };


image

Пример работы детектора движения.
Теги:
Хабы:
Всего голосов 24: ↑23 и ↓1+22
Комментарии20

Публикации

Ближайшие события