Добрый день. Хочу предложить вам небольшую статью о своей работе с кинектом.
Сейчас я делаю небольшую часть рекламного проекта, где используется кинект. Одной из задач является «наложение фильтра» на одного человека в толпе. Об этом и поговорим.
В работе использую OpenNI, OpenCV и Visual Studio 2010.
Раньше я никогда плотно не работал с изображениями, поэтому не представлял с какой стороны браться за дело. После непродолжительных размышлений сложилась такая схема:
1 — получаю картинку с обычной камеры;
2 — получаю userPixels (пиксели, которые принадлежат пользователю);
3 — делаю копию картинки и применяю фильтр;
4 — те пиксели, которые помечены как «пользовательские» перерисовываю из картинки с фильтром в исходное изображение.
Примерный план понятен, поехали!
Объявим нужные переменные.
Собственно теперь все готово, чтобы начать. Создаем генераторы:
Дальше нужно сделать важную вещь. Камеры расположены рядом друг с другом, но не на одном месте, а значит картинка будет разной. Для приведения к одному виду есть специальный метод SetViewPoint. Использоваеть его можно будет после того, как дадим команду StartGeneratingAll(). И важно чтобы OutputMode для обеих камер был одинаковый, иначе будет ошибка.
Прежде чем приступить к написанию showVideo() с основным циклом, нам нужно преобразовать изображение с камеры, для этого напишем функцию рисующую IplImage из XnRGB24Pixel и в ней же определим какие пиксели принадлежат пользователю.
Осталось написать коллбэки и метод showVideo:
В результате у вас должно получиться что-то вроде этого:

На скриншоте ниже показан результат немножко подогнанный под мои цели, с увеличенными границами:

Спасибо за внимание, успехов!
Сейчас я делаю небольшую часть рекламного проекта, где используется кинект. Одной из задач является «наложение фильтра» на одного человека в толпе. Об этом и поговорим.
В работе использую OpenNI, OpenCV и Visual Studio 2010.
Начало
Раньше я никогда плотно не работал с изображениями, поэтому не представлял с какой стороны браться за дело. После непродолжительных размышлений сложилась такая схема:
1 — получаю картинку с обычной камеры;
2 — получаю userPixels (пиксели, которые принадлежат пользователю);
3 — делаю копию картинки и применяю фильтр;
4 — те пиксели, которые помечены как «пользовательские» перерисовываю из картинки с фильтром в исходное изображение.
Примерный план понятен, поехали!
Объявим нужные переменные.
xn::Context context; xn::ImageGenerator imageGenerator; // Этот генератор даст нам картинку с обычной камеры xn::ImageMetaData imageMD; xn::DepthGenerator depthGenerator; // глубина xn::DepthMetaData depthMD; xn::UserGenerator userGenerator; // если есть определенные пользователи, мы об этом узнаем xn::SceneMetaData userPixels; XnUserID users[15]; XnUInt16 nUsers = 15; const XnLabel *pLabels; // метки, если !=0 значит это пользователь const XnRGB24Pixel *pImageRow; // наша картинка XnStatus rc; unsigned int pTexMapX;// = 0; unsigned int pTexMapY;// = 0; XnMapOutputMode outputMode; // формат вывода для камер XnPixelFormat pixelFormat; bool mirrored; // Если хочется зеркального отражения, то его нужно включить bool blurOn; int currentUserId; struct MyPixel { int posX; int posY; unsigned char *vBlue; unsigned char *vGreen; unsigned char *vRed; int uLabel; bool border; }; MyPixel pixels[640][480]; // Почти все объявлено, остались только IplImages IplImage *frame; IplImage *frameBlured;
Собственно теперь все готово, чтобы начать. Создаем генераторы:
int main(){ outputMode.nFPS = 10; outputMode.nXRes = 640; outputMode.nYRes = 480; XnStatus rc; pTexMap = NULL; pTexMapX = 0; pTexMapY = 0; rc = context.Init(); checkStatus(rc, " create context"); // метод проверяет rc == XN_STATUS_OK и если нет, завершает программу с ошибкой rc = depthGenerator.Create(context); checkStatus(rc, " depth create"); rc = imageGenerator.Create(context); checkStatus(rc, " image create"); rc = userGenerator.Create(context); checkStatus(rc," user create"); return 0; }
Дальше нужно сделать важную вещь. Камеры расположены рядом друг с другом, но не на одном месте, а значит картинка будет разной. Для приведения к одному виду есть специальный метод SetViewPoint. Использоваеть его можно будет после того, как дадим команду StartGeneratingAll(). И важно чтобы OutputMode для обеих камер был одинаковый, иначе будет ошибка.
int main (){ ..... imageGenerator.SetMapOutputMode(outputMode); depthGenerator.SetMapOutputMode(outputMode); imageGenerator.SetPixelFormat(XN_PIXEL_FORMAT_RGB24); // говорим что нужна RGB картинка context.StartGeneratingAll(); rc = depthGenerator.GetAlternativeViewPointCap().SetViewPoint(imageGenerator); checkStatus(rc, " user and image view"); // зарегистрируем коллбеки для отслеживания пользователей XnCallbackHandle h1; userGenerator.RegisterUserCallbacks(gotUser,lostUser,NULL, h1); // установим текущего пользователя и флаги. currentUserId = -1; // этот id нам нужен чтобы наложить фильтр только на одного пользователя mirrored = false; blurOn = false; frame = cvCreateImage(cvSize(640,480),8,3); frameBlured = cvCreateImage(cvSize(640,480),8,3); //создадим окно cvNamedWindow ("Filter demo", CV_WINDOW_AUTOSIZE); // и запускаем основной цикл программы. showVideo(); return 0; }
Обработка кадра
Прежде чем приступить к написанию showVideo() с основным циклом, нам нужно преобразовать изображение с камеры, для этого напишем функцию рисующую IplImage из XnRGB24Pixel и в ней же определим какие пиксели принадлежат пользователю.
void fromXnRGBToIplImage(const XnRGB24Pixel* pImageMap, IplImage** iplRGBImage) { userGenerator.GetUsers(aUsers,nUsers); userGenerator.GetUserPixels(aUsers[0],userPixels); pLabels = userPixels.Data(); for(int l_y=0;l_y<XN_VGA_Y_RES;++l_y) //XN_VGA_Y_RES = 480 { for(int l_x=0;l_x<XN_VGA_X_RES;++l_x, ++pLabels) //XN_VGA_X_RES= 640 { pixels[l_x][l_y].uLabel = 0; if(pixels[l_x][l_y].border != true) pixels[l_x][l_y].border = false; if(*pLabels !=0) // а вот и пользователь { currentUserId = (currentUserId == -1)?(*pLabels):currentUserId; // если нет активного юзера, то назначаем его pixels[l_x][l_y].uLabel = *pLabels; // проверяем пограничный ли это пиксель (пригодится если вы захотите улучшить выделенную фигуру) if((l_x >0) && pixels[l_x-1][l_y].uLabel == 0 || (l_x < XN_VGA_X_RES-1) && pixels[l_x+1][l_y].uLabel == 0 || (l_y >0 ) && pixels[l_x][l_y-1].uLabel == 0 || (l_y < XN_VGA_Y_RES-1) && pixels[l_x][l_y+1].uLabel == 0 ) { pixels[l_x][l_y].border = true; } } // Этот вариант перевода в IplImage был честно подсмотрен где-то в OpenNI Group ((unsigned char*)(*iplRGBImage)->imageData)[(l_y*XN_VGA_X_RES +l_x)*3+0] = pImageMap[l_y*XN_VGA_X_RES+l_x].nBlue; ((unsigned char*)(*iplRGBImage)->imageData)[(l_y*XN_VGA_X_RES +l_x)*3+1] = pImageMap[l_y*XN_VGA_X_RES+l_x].nGreen; ((unsigned char*)(*iplRGBImage)->imageData)[(l_y*XN_VGA_X_RES +l_x)*3+2] = pImageMap[l_y*XN_VGA_X_RES+l_x].nRed; } } // Если включен режим блюра, необходимо перерисовать помеченные пиксели if(blurOn){ cvSmooth(*iplRGBImage,frameBlured,CV_BLUR,14,14,0,0); for(int j = 0 ; j < 480; ++j) { for(int i = 0 ; i < 640; ++i) { if( pixels[i][j].border == true && pixels[i][j].uLabel == currentUserId || pixels[i][j].uLabel == currentUserId ){ ((unsigned char*)(*iplRGBImage)->imageData)[(j*XN_VGA_X_RES +i)*3+0] = frameBlured->imageData[(j*XN_VGA_X_RES +i)*3+0]; ((unsigned char*)(*iplRGBImage)->imageData)[(j*XN_VGA_X_RES +i)*3+1] = frameBlured->imageData[(j*XN_VGA_X_RES +i)*3+1]; ((unsigned char*)(*iplRGBImage)->imageData)[(j*XN_VGA_X_RES +i)*3+2] = frameBlured->imageData[(j*XN_VGA_X_RES +i)*3+2]; } pixels[i][j].border = false; pixels[i][j].uLabel = 0; } } } }
Осталось написать коллбэки и метод showVideo:
void XN_CALLBACK_TYPE gotUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie) { // делаем что-то полезное } void XN_CALLBACK_TYPE lostUser(xn::UserGenerator& generator, XnUserID nId, void* pCookie) { if((int)nId == currentUserId) { currentUserId = -1; // } } void showVideo() { while(1) { rc = context.WaitOneUpdateAll(imageGenerator); imageGenerator.GetMetaData(imageMD); pImageRow = imageGenerator.GetRGB24ImageMap(); char c = cvWaitKey(33); if(c == 27) // esc завершает цикл break; if(c == 109) { mirrored = (mirrored == true)?false:true; } if(c == 98) // b { blurOn = (blurOn == true)?false:true; } fromXnRGBToIplImage(pImageRow,&frame); // Вообще, в Context есть метод SetGlobalMirror(bool), но у меня при его использовании появляются артефакты и я заменил на cvFlip if(mirrored) cvFlip(frame, NULL, 1); cvShowImage("Filter demo", frame); } cvReleaseImage( &frame ); cvDestroyWindow("Filter demo" ); }
Итог
В результате у вас должно получиться что-то вроде этого:

На скриншоте ниже показан результат немножко подогнанный под мои цели, с увеличенными границами:

Спасибо за внимание, успехов!
