You are on page 1of 28

Разработка интерактивных систем

на OpenFrameworks

6. OpenCV, оптический поток,


продвинутый детектор движения

лекции и объявления: вопросы отправляйте на адрес


www.uralvision.blogspot.com perevalovds@gmail.com УрГУ / ИММ весна 2010
Что такое оптический поток
Оптический поток (optical flow, optic flow) - это векторное поле явного движения
объектов, поверхностей и ребер в визуальной сцене, вызванное относительным
движением между наблюдателем (глазом, камерой) и сценой.

Обратите внимание:
похоже, что точка обзора
камеры движется вниз, но
в малотекстурированной
области поток не
находится - так как не
видно, что куда там
движется

http://www.ultimategraphics.co.jp/jp/images/stories/ultimate/BCC/optical.jpg
Основные применения оптического
потока
1. Для определения направления, в котором движутся
объекты в кадре. (В этой лекции мы будем так применять)

2. При производстве фильмов - для осуществления


плавного морфинга между последовательными кадрами,
либо между кадрами, снятыми соседними камерами
(наиболее характерно это использовано в фильме
"Матрица").

3. В стереозрении - для определения расстояния до


объекта.
Методы вычисления оптического
потока
Вход: два кадра. Выход - векторное поле ( DX(x, y), DY(x,y) ) - вектора
геометрического сдвига пикселов с первого кадра на второй.

(I) Блочные (наивные методы)


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

(II) Дифференциальные (наиболее используемые)


Оценка производных по x, y, t.
1. Lucas-Kanade
2. Пирамидальный Lucas-Kanade, вычисляющийся только на "точках интереса"
3. Horn-Schunk
4. CLG

(III) На основе дискретной оптимизации (ресурсоемкие)


Решение строится с помощью методов min-cut, max-flow, линейного
программирования или belief propagation.
Методы вычисления оптического
потока
1. Lucas-Kanade

Локальный метод, анализирующий каждый пиксел.

Входные параметры: размер локального окна (w, h).


Достоинства: быстро вычисляется.
Недостатки: "aperture problem" - на областях с малой
текстурой работает плохо в силу того, что он локальный.
Методы вычисления оптического
потока
2. Пирамидальный Lucas-Kanade на ключевых точках

Многомасштабный метод, который для рассмотрения


использует "пирамиду изображений".

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

Входные параметры: размер локального окна (w, h),


параметры пирамиды, список точек интереса.
Достоинства: быстро вычисляется, более устойчив.
Недостатки: "aperture problem".
Методы вычисления оптического
потока
3. Horn-Schunk

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


функционал, состоящий из части, отвечающей за яркость, и
части, отвечающей за непрерывность получаемого потока.

Достоинства: старается построить непрерывный


оптический поток. То есть он распространяет значения
поток "по непрерывности" в малотекстурированные
области.

Недостатки: считается, что он неустойчив к шумам на


изображении; вычислительно более трудоемок, так как
основан на итерационном процессе.
Методы вычисления оптического
потока
4. CLG (2005 год)

"Combining Local and Global"


Метод, объединяющий Lucas-Kanade и Horn-Schunk

Сейчас (2010 г.) он и его модификации считаются одними


из лучших по соотношению качество / скорость.
Что такое OpenCV
OpenCV (англ. Open Source Computer Vision Library, библиотека
компьютерного зрения с открытым исходным кодом) —
библиотека алгоритмов компьютерного зрения, обработки
изображений и численных алгоритмов общего назначения с
открытым кодом. (Википедия)

(Платная библиотека IPP - Intel Performance Primitives -


позволяет ускорить работу OpenCV на процессорах Intel).

Справка по методам анализа движения:


http://opencv.willowgarage.
com/documentation/motion_analysis_and_object_tracking.html

Готовые примеры - в OpenCV/samples:


http://sourceforge.net/projects/opencvlibrary/
OpenCV в openFrameworks

Библиотека OpenCV включена в openFrameworks в


качестве дополнения (Addon), и есть обертки для ряда
классов.
Есть один пример на применение.

Но нет многих примеров, доступных в OpenCV. Поэтому,


чтобы лучше разобраться, как работать с OpenCV - стоит
скачать и установить его все равно.
Вычисление оптического потока в
openFrameworks + OpenCV

1. Взять за основу и скопировать в новую папку


"OpticalFlow" пример OpenFrameworks,
addonsExamples/opencvexample

Нам этот пример нужен только для того, чтобы подключить


OpenCV к своему проекту, поэтому:

2. Убрать из testApp.h и testApp.cpp все лишнее,


оставить только каркасы функций setup, update, draw.
Пример 1. Вычисление оптического
потока методом Lucas-Kanade
#include "ofxOpenCv.h"

ofVideoGrabber vidGrabber;
int w = 320;
int h = 240;

//OpenCV-изображения, обернутые в OpenFrameworks


ofxCvColorImage colorImg; //кадр с камеры
ofxCvGrayscaleImage grayImage; //полутоновой кадр с камеры
ofxCvGrayscaleImage grayPrev; //предыдущий кадр

//OpenCV-матрицы с результирующим потоком


CvMat *motionX;
CvMat *motionY;

bool inited = false; //Флаг, указывающий, что была инициализация фона


Пример 1. Вычисление оптического
потока методом Lucas-Kanade
void testApp::setup(){

vidGrabber.initGrabber(320,240);

//выделение памяти для изображений и матриц OpenCV


colorImg.allocate( w, h );
grayImage.allocate( w, h );
grayPrev.allocate( w, h );

motionX = cvCreateMat(h, w, CV_32FC1);


motionY = cvCreateMat(h, w, CV_32FC1);
}
Пример 1. Вычисление оптического
потока методом Lucas-Kanade
void testApp::update(){
vidGrabber.grabFrame();
if ( vidGrabber.isFrameNew() ){
colorImg.setFromPixels( vidGrabber.getPixels(), w, h );
grayImage = colorImg; //преобразование в полутоновое изображение
if ( !inited ) { //если нет предыдущего кадра - установить
grayPrev = grayImage;
}
cvCalcOpticalFlowLK( //LK - значит Lucas-Kanade
grayPrev.getCvImage(), grayImage.getCvImage(),
cvSize( 11, 11 ), //размер локального окна для анализа,
//нечетный
motionX, motionY
);
inited = true;
grayPrev = grayImage; //устанавливаем предыдущий кадр
}}
Пример 1. Вычисление оптического
потока методом Lucas-Kanade
void testApp::draw(){
ofBackground(255,255,255);
//кадр с камеры, полупрозрачно
ofEnableAlphaBlending();
ofSetColor( 255, 255, 255, 128);
vidGrabber.draw( 0, 0 );
ofDisableAlphaBlending();

//оптический поток
ofSetColor( 0, 0, 0 );
int r = 10;
for (int y = 0; y < h; y += r) {
for (int x = 0; x < w; x += r ) {
float dx = cvmGet( motionX, y, x ); //Берем значение из матриц,
float dy = cvmGet( motionY, y, x ); //обратите внимание - (y,x)
ofCircle( x, y, 1 );
ofLine( x, y, x + dx, y + dy );
}
}
}
Пример 1. Вычисление оптического
потока методом Lucas-Kanade
Результат - алгоритм очень "шумит".
Путь к улучшению - брать усредненные вектора по некоторому локальному окну.
Кроме того, игнорировать явные "выбросы" - слишком длинные вектора сдвига.
Пример 2.
Lucas-Kanade + сглаживание
void testApp::draw(){
...
//оптический поток
for (int y = 0; y < h-r; y += r) {
for (int x = 0; x < w-r; x += r ) {
ofPoint sum( 0, 0 ); //суммарный вектор
int n = 0; //число точек для суммирования
for (int y1 = 0; y1 < r; y1++) { //суммирование
for (int x1 = 0; x1 < r; x1++) {
float dx = cvmGet( motionX, y + y1, x + x1 );
float dy = cvmGet( motionY, y + y1, x + x1 );
if ( dx * dx + dy * dy <= ( w/10 * w/10 ) ) { //контроль, что не выброс
sum += ofPoint( dx, dy );
n++;
}}}
ofPoint d = sum;
if ( n > 0 ) { d /= n; }
ofCircle( x, y, 1 ); ofLine( x, y, x + d.x, y + d.y );
}
}
Пример 2.
Lucas-Kanade + сглаживание
Результат заметно лучше.
Пример 3.
Horn-Schunk + сглаживание
void testApp::update(){
...

//критерий остановки инетационного процесса


CvTermCriteria term = cvTermCriteria(
CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 30, 0.2
);

//Horn-Schunk
cvCalcOpticalFlowHS( grayPrev.getCvImage(), grayImage.getCvImage(),
(inited) ? 0 : 1, //использовать ли предыдущие вектора движения
motionX, motionY,
1.0, //lambda - параметр алгоритма (сглаживание?)
term );
//обратите внимание, что нет задания размера окна,
//только условие остановки итераций

...
Пример 3.
Horn-Schunk + сглаживание
Заметно, что вектора сдвига распространились и внутрь
нетекстурированных областей.
Вычисляется обычно медленней чем Lucas-Kanade.
Проект "Шарики в оптическом
потоке"
Размер, цвет и положение N шариков генерируются
случайным образом. Затем шарики летают, при этом сила,
действиющая на шарик, считается как средний вектор
оптического потока внутри пикселов, на которых лежит
шарик.

http://www.youtube.com/watch?v=CUaxE7dctrc
Проект "Шарики в оптическом
потоке"
...

const int N = 50; //число шариков


ofColor color[ N ]; //цвет шарика
int size[ N ]; //размер
ofPoint pos[ N ]; //положение
ofPoint speed[ N ]; //скорость

float _lastElapsedTimef = -1; //для расчета dt


Проект "Шарики в оптическом
потоке"
в setup()

...

ofSeedRandom(); //инициализация датчика случайных чисел


for (int i = 0; i < N; i++) {
color[ i ].r = ofRandom(0, 255); //случайное значение 0..255
color[ i ].g = ofRandom(0, 255); //случайное значение 0..255
color[ i ].b = ofRandom(0, 255); //случайное значение 0..255
size[ i ] = ofRandom( 10, 20 );
pos[ i ].x = ofRandom( size[ i ], w - size[ i ] );
pos[ i ].y = ofRandom( size[ i ], h - size[ i ] );
}
Проект "Шарики в оптическом
потоке"
в update()
...
//вычисление dt
float now = ofGetElapsedTimef(); //число секунд со старта приложения
double dt = ( _lastElapsedTimef >= 0 ) ? ( now - _lastElapsedTimef ) : 1.0;
_lastElapsedTimef = now;
//вычисление потока
for (int i=0; i<N; i++) {
ofPoint p = pos[ i ]; //центр
int r = size[ i ];
ofPoint sum( 0, 0 ); //суммарный вектор
int n = 0; //число точек для суммирования
for (int y1 = -r; y1 < r; y1++) {
for (int x1 = -r; x1 < r; x1++) {
if ( x1 * x1 + y1 * y1 <= r * r ) { //внутри круга
Проект "Шарики в оптическом
потоке"
int x2 = p.x + x1;
int y2 = p.y + y1;
if ( x2 >= 0 && x2 < w && y2 >= 0 && y2 < h ) { //внутри изображения
float dx = cvmGet( motionX, y2, x2 );
float dy = cvmGet( motionY, y2, x2 );
if ( dx * dx + dy * dy <= ( w/10 * w/10 ) ) { //контроль, что не выброс
sum += ofPoint( dx, dy );
n++;

}}}}}
ofPoint d = sum;
if ( n > 0 ) {
d /= n;
}
Проект "Шарики в оптическом
потоке"
//физика
ofPoint force = d;
float m = 0.005;
speed[ i ] += force / m * dt;
speed[ i ] *= 0.95;
pos[ i ] += speed[ i ] * dt;

//контроль выхода за границы


if ( pos[ i ].x < 0 )
{ pos[ i ].x *= -1; if ( speed[ i ].x < 0 ) speed[ i ].x *= -1; }
if ( pos[ i ].x >= w )
{ pos[ i ].x = 2*(w - 1) - pos[i].x; if ( speed[ i ].x > 0 ) speed[ i ].x *= -1; }

if ( pos[ i ].y < 0 )


{ pos[ i ].y *= -1; if ( speed[ i ].y < 0 ) speed[ i ].y *= -1; }

if ( pos[ i ].y >= h )


{ pos[ i ].y = 2*(h - 1) - pos[i].y; if ( speed[ i ].y > 0 ) speed[ i ].y *= -1; }

}
Проект "Шарики в оптическом
потоке"
void testApp::draw() {

//камера
ofBackground(255,255,255);
ofSetColor( 255, 255, 255, 255);
vidGrabber.draw( 0, 0 );

//шарики
for (int i=0; i<N; i++) {
ofSetColor( color[i].r, color[i].g, color[i].b);
ofCircle( pos[i].x, pos[i].y, size[i] );
}

}
Проект по управлению сеткой с
помощью оптического потока
Как сделать:
к шарикам добавить связи по закону Гука.

http://www.youtube.com/watch?v=QX5TlIHXhd0

You might also like