Добрый день. Хочу предложить вам небольшую статью о своей работе с кинектом.
Сейчас я делаю небольшую часть рекламного проекта, где используется кинект. Одной из задач является «наложение фильтра» на одного человека в толпе. Об этом и поговорим.
В работе использую 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" );
}
Итог
В результате у вас должно получиться что-то вроде этого:

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

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