Перейти к содержимому
Compvision.ru
iamx4nd3r

Трекинг точек на лице

Recommended Posts

Добрый день!

Решаю следующую задачу:

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

На данный момент имею следующее:

post-6498-0-39361800-1367233652_thumb.pn

//03.04.13


#include <OpenCV/highgui.h>

#include <OpenCV/cv.h>

#include <stdio.h>

#include <iostream>

#include <stdlib.h>


char * itoa( int value, char * str, int base )

{

    char * rc;

    char * ptr;

    char * low;

    // Check for supported base.

    if ( base < 2 || base > 36 )

    {

        *str = '\0';

        return str;

    }

    rc = ptr = str;

    // Set '-' for negative decimals.

    if ( value < 0 && base == 10 )

    {

        *ptr++ = '-';

    }

    // Remember where the numbers start.

    low = ptr;

    // The actual conversion.

    do

    {

        // Modulo is negative for negative value. This trick makes abs() unnecessary.

        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + value % base];

        value /= base;

    } while ( value );

    // Terminating the string.

    *ptr-- = '\0';

    // Invert the numbers.

    while ( low < ptr )

    {

        char tmp = *low;

        *low++ = *ptr;

        *ptr-- = tmp;

    }

    return rc;

}





// задаём ROI

int x =  85;

int y =  5;

int width =  170;

int height =  185;

// добавочная величина

int add =   00;

// устанавливаем ROI

// cvSetImageROI(frame, cvRect(x,y,width,height));

// сбрасываем ROI

// cvResetImageROI(frame);


/*CvScalar s;

s=cvGet2D(imgThresh,centerX,centerY); // get the (i,j) pixel value

s.val[0]=180;

s.val[1]=100;

s.val[2]=100;

cvSet2D(imgThresh,centerX,centerY,s); // set the (i,j) pixel value

*/


//cvScalar(75,0,50), cvScalar(150,165,195)


//This function threshold the ROI of HSV image and create a binary image

IplImage* GetThresholdedImage(IplImage* imgHSV){

    IplImage* imgThresh=cvCreateImage(cvGetSize(imgHSV),IPL_DEPTH_8U, 1);

    cvZero(imgThresh);

    cvSetImageROI(imgHSV, cvRect(x,y,width,height));

    cvSetImageROI(imgThresh, cvRect(x,y,width,height));

    cvInRangeS(imgHSV, cvScalar(75,5,50), cvScalar(150,165,195), imgThresh);//(src, lower[3], upper[3])

    cvResetImageROI(imgThresh);

    cvResetImageROI(imgHSV);

    return imgThresh;

}


void GetCentralPoints(CvSeq* contours){



    CvSeq *contour;

    CvMoments moments;

    double M00, M01, M10;

    int centerX, centerY, pointnum;


    contour = contours; //Now contour points to the first contour in the list

    pointnum = 1;


    while(contour){

    cvMoments(contour,&moments);

    M00 = cvGetSpatialMoment(&moments,0,0);

    M10 = cvGetSpatialMoment(&moments,1,0);

    M01 = cvGetSpatialMoment(&moments,0,1);

    centerX = (int)(M10/M00);

    centerY = (int)(M01/M00);

        std::cout << "point№" << pointnum << "\n";

        //printf("X=", centerX);

        std::cout << "X=" << centerX << "  ";

        //printf("\n");

        std::cout << "Y=" << centerY << "\n";

        //printf("Y=", centerY);

        //printf("\n");



        contour = (CvSeq *)(contour->h_next);

        pointnum = pointnum+1;

    }

}


int main(){

    CvCapture* capture =0;

    //capture = cvCaptureFromCAM(0);

    capture = cvCaptureFromAVI("/Users/agortinskiy/Downloads/AudioVisualClip/DC/a1.avi");

    CvMemStorage* g_storage = NULL;

    CvSeq* contours = 0;




    if(!capture){

        printf("Capture failure\n");

        return -1;

    }


    IplImage* frame=0;

    frame = cvQueryFrame(capture);

    if(!frame) return -1;



    cvNamedWindow("Original");

    cvNamedWindow("Contour");

    cvNamedWindow("Binary");



    //iterate through each frames of the video

    while(true){


        frame = cvQueryFrame(capture);

        if(!frame) break;

        frame=cvCloneImage(frame);

        //cvSmooth(frame, frame, CV_BILATERAL,3,3,0.1,0.1); //smooth the original image using Gaussian kernel

        cvSmooth(frame, frame, CV_GAUSSIAN, 3, 3);

        IplImage* imgHSV = cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3);

        cvCvtColor(frame, imgHSV, CV_BGR2HSV); //Change the color format from BGR to HSV

        IplImage* imgThresh = GetThresholdedImage(imgHSV);

        //cvSmooth(imgThresh, imgThresh, CV_BILATERAL, 3, 3, 0.1, 0.1); //smooth the binary image using Gaussian kernel

        cvSmooth(imgThresh, imgThresh, CV_GAUSSIAN, 3, 3);

        if( g_storage==NULL ) {


            g_storage = cvCreateMemStorage(0);

        } else {

            cvClearMemStorage( g_storage );

        }

        cvShowImage("Binary", imgThresh);

        cvFindContours( imgThresh, g_storage,&contours,sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));

        GetCentralPoints(contours);

        cvZero( imgThresh );

        // для отметки контуров

        CvFont font;

        cvInitFont(&font, CV_FONT_HERSHEY_PLAIN, 1.0, 1.0);

        char buf[128];

        int counter=0;


  /*      if( contours ){

            cvDrawContours(

                           imgThresh,

                           contours,

                           cvScalarAll(255),

                           cvScalarAll(255),

                           100

                           );

        // выводим его номер

        CvPoint2D32f point; float rad;

        cvMinEnclosingCircle(contours,&point,&rad); // получим окружность содержащую контур

        cvPutText(imgThresh, itoa(++counter, buf, 10), cvPointFrom32f(point), &font, CV_RGB(255,255,255));

        }


    */


        // нарисуем контуры изображения

        if(contours){

            for(CvSeq* seq0 = contours;seq0!=0;seq0 = seq0->h_next){

                // рисуем контур

                cvDrawContours(imgThresh, seq0, cvScalarAll(255),

                               cvScalarAll(255), 0, 1, 8);

                // выводим его номер

                CvPoint2D32f point; float rad;

                cvMinEnclosingCircle(seq0,&point,&rad); // получим окружность содержащую контур

                cvPutText(imgThresh, itoa(++counter, buf, 10), cvPointFrom32f(point), &font, cvScalarAll(255));

            }

        }


        cvShowImage("Contour", imgThresh);

        cvShowImage("Original", frame);


        //Clean up used images

        cvReleaseImage(&imgHSV);

        cvReleaseImage(&imgThresh);

        cvReleaseImage(&frame);


        //Wait 50mS

        int c = cvWaitKey(10);

        //If 'ESC' is pressed, break the loop

        if((char)c==27 ) break;




    }


    cvDestroyAllWindows() ;

    cvReleaseCapture(&capture);


    return 0;

}


Программа уже преобразует изображение в бинарное с выбранным порогом в hsv цветовой палитре, затем выделяются контуры, считаются моменты для вычисления центров контуров(и есть координаты искомые мной), ну и нумеруются точки.

Проблемы 2:

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

2. Ошибочно детектируются глаза на некоторых кадрах. Видимо, при некоторых положениях головы актера цветовые параметры глаза укладываются в диапозон, заданный для отбора синих точек. Как можно исключить глаза?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

1) Считать для этих точек оптический поток LK-Optical flow (в OpenCV-шных в примерах есть).

Или можно считать расстояния между каждой из точек текущего и следующего кадра, и, при превышении порога отсеивать, а если порог не превышен, ближайшую точку считать это той-же точкой.

2) Детектировать каскадами Хаара или LBP (в OpenCV-шных в примерах тоже есть) и удалять.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

1) Не советую использовать ANSI C интерфейсы в opencv, т.к. уже в opencv 3.0 эти интерфейсы по сути deprecated, в следующих версиях их вероятно выпилят полностью.

2) Судя по примеру картинки - точки не рандомно по лицу натыканы, а имеют какую-то структуру. Отсюда возникает мысль, что имеет смысл построить параметрическую модель(модель в виде графа мне кажется вполне приемлемой) этих точек и искать их положение через задачу оптимизации. Так вы сможете достраивать модель даже в том случае, если некоторое число точек не было найдено.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

1) Не советую использовать ANSI C интерфейсы в opencv, т.к. уже в opencv 3.0 эти интерфейсы по сути deprecated, в следующих версиях их вероятно выпилят полностью.

2) Судя по примеру картинки - точки не рандомно по лицу натыканы, а имеют какую-то структуру. Отсюда возникает мысль, что имеет смысл построить параметрическую модель(модель в виде графа мне кажется вполне приемлемой) этих точек и искать их положение через задачу оптимизации. Так вы сможете достраивать модель даже в том случае, если некоторое число точек не было найдено.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

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

Ну наверно это выглядеть должно примерно как в этом видео: http://www.ecse.rpi.edu/homepages/cvrl/Demo/Demo_Wang.avi

Где узлы "сетки" это ваши синие точки. А оптимизировать вероятно длины ребер и углы между ними.

Более подробно не знаю т.к. не занимался задачей распознавания мимики.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ну тут либо AAM или ASM пахнет, либо надо ограничиться оптическим потоком.

Если эмоции, то тут направление уже есть:

Candide 3 страшненько выглядит :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Кстати, очень классная штука:

http://www.visionopen.com/downloads/open-source-software/vosm/

сейчас как раз ее гоняю (на моем компе в среднем 15-20 ms. на кадр).

Плюс к тому, там же, на сайте, есть модели, предварительно обученные по разным базам лиц .

Видео работы:

Резюме по результатам экспериментов:

1) AAM работают хорошо (как на видео выше), только если тренированы на том же человеке, на котором будут использоваться.

(лучше использовать AAM_CMUICIA или AAM_IAIA).

2) Схема с одним цветовым слоем, в общем случае, работает лучше цветной.

3) ASM_PROFILEND - работает очень даже хорошо (но не так хорошо как AAM на тех лицах для которых создана модель) на незнакомых лицах (время конвергенции 5 мс.).

На последовательности FRANCK лучше всего держалась модель, обученная по базе IMM (кроме родной конечно).

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо за ответы!

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

Посему я стал разбираться с оптическим потоком, и пока имею некоторые сложности.

Правильно ли я понимаю схему использования оптического потока? (ниже)


//берем первый кадр

frame = cvQueryFrame(capture);

//инициализация нужных массивов и переменных

//...

cvGoodFeaturesToTrack(imgThresh, eig_image, tmp_image, cornersA, &corner_count, 0.01, 1);

        cvFindCornerSubPix( imgThresh, cornersA, corner_count, cvSize( win_size, win_size ),

                           cvSize( -1, -1 ), cvTermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03 ) );


//цикл для обработки последующих кадров


while(true){


            frame = cvQueryFrame(capture);


           //доп инициализации

           //..

            cvCalcOpticalFlowPyrLK( imgPrev, imgCurr, pyrA, pyrB, cornersA, cornersB, corner_count,

                                   cvSize(win_size,win_size), 5, features_found, 0,

                                   cvTermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.03 ), 0 );





            CV_SWAP( tmp_image, imgThresh, swap_temp );

            CV_SWAP( pyrA, pyrB, swap_temp );

            CV_SWAP( cornersA, cornersB, swap_points );



}

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Здесь трекер точек:

LKTracker.rar

В методе

bool LKTracker::trackf2f(const Mat& img1, const Mat& img2,vector<Point2f> &points1, vector<cv::Point2f> &points2);

img1 - предыдущий кадр.

points1 - точки на предыдущем кадре (известные).

img2 - текущий кадр.

points2 - точки на текущем кадре (результат).

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Документ с Microsoft Research:

http://faculty.cs.tamu.edu/jchai/projects/face-TOG-2011/Face-final-v11.pdf

Еще здесь посмотрите, там есть ссылка на диссертацию по похожей тематике:

http://www-graphics.stanford.edu/papers/MixedScaleMotionRecovery/content/index.html

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

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

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×