2015-09-14 7 views
1

Я пытаюсь извлечь силуэт пользователя и поставить его над моими изображениями. Я смог сделать маску и вырезать пользователя из изображения rgb. Но контур грязный.Как получить прозрачную маску пользователей в simple-openni?

Вопрос в том, как я могу сделать маску более точной (чтобы соответствовать реальному пользователю). Я пробовал фильтры ERODE-DILATE, но они мало что делают. Может быть, мне нужен фильтр Пера, как в Photoshop. Или я не знаю.

Вот мой код.

import SimpleOpenNI.*; 
SimpleOpenNI context; 
PImage mask; 
void setup() 
{ 
    size(640*2, 480); 
    context = new SimpleOpenNI(this); 
    if (context.isInit() == false) 
    {   
    exit(); 
    return; 
    } 
    context.enableDepth(); 
    context.enableRGB(); 
    context.enableUser(); 
    context.alternativeViewPointDepthToImage();  
} 

void draw() 
{ 
    frame.setTitle(int(frameRate) + " fps");  
    context.update(); 
    int[] userMap = context.userMap(); 
    background(0, 0, 0); 
    mask = loadImage("black640.jpg"); //just a black image 
    int xSize = context.depthWidth(); 
    int ySize = context.depthHeight(); 
    mask.loadPixels(); 
    for (int y = 0; y < ySize; y++) {  
    for (int x = 0; x < xSize; x++) {   
     int index = x + y*xSize;  
     if (userMap[index]>0) { 
     mask.pixels[index]=color(255, 255, 255); 
     } 
    } 
    } 
    mask.updatePixels(); 
    image(mask, 0, 0); 
    mask.filter(DILATE); 
    mask.filter(DILATE);   
    PImage rgb = context.rgbImage(); 
    rgb.mask(mask); 
    image(rgb, context.depthWidth() + 10, 0); 
} 

ответ

1

Я попытался встроенным подрывать-Разбавить-смазанность в обработке. Но они очень неэффективны. Каждый раз, когда я увеличиваю blurAmount в img.filter (BLUR, blurAmount), мой FPS уменьшается на 5 кадров. Итак, я решил попробовать opencv. Это намного лучше. Результат удовлетворительный.

import SimpleOpenNI.*; 
import processing.video.*; 
import gab.opencv.*; 
SimpleOpenNI context; 
OpenCV opencv; 
PImage mask; 
int numPixels = 640*480; 
int dilateAmt = 1; 
int erodeAmt = 1; 
int blurAmt = 1; 
Movie mov; 
void setup(){ 
    opencv = new OpenCV(this, 640, 480); 
    size(640*2, 480); 
    context = new SimpleOpenNI(this); 
    if (context.isInit() == false) {   
    exit(); 
    return; 
    } 
    context.enableDepth(); 
    context.enableRGB(); 
    context.enableUser(); 
    context.alternativeViewPointDepthToImage(); 
    mask = createImage(640, 480, RGB); 
    mov = new Movie(this, "wild.mp4"); 
    mov.play(); 
    mov.speed(5); 
    mov.volume(0); 
} 
void movieEvent(Movie m) { 
    m.read(); 
} 
void draw() { 
    frame.setTitle(int(frameRate) + " fps");  
    context.update(); 
    int[] userMap = context.userMap(); 
    background(0, 0, 0); 
    mask.loadPixels(); 
    for (int i = 0; i < numPixels; i++) { 
    mask.pixels[i] = userMap[i] > 0 ? color(255) : color(0); 
    } 
    mask.updatePixels(); 
    opencv.loadImage(mask); 
    opencv.gray(); 
    for (int i = 0; i < erodeAmt; i++) { 
    opencv.erode(); 
    } 
    for (int i = 0; i < dilateAmt; i++) { 
    opencv.dilate(); 
    } 
    if (blurAmt>0) {//blur with 0 amount causes error 
    opencv.blur(blurAmt); 
    } 
    mask = opencv.getSnapshot(); 
    image(mask, 0, 0); 
    PImage rgb = context.rgbImage(); 
    rgb.mask(mask); 
    image(mov, context.depthWidth() + 10, 0); 
    image(rgb, context.depthWidth() + 10, 0); 
    fill(255); 
    text("erodeAmt: " + erodeAmt + "\tdilateAmt: " + dilateAmt + "\tblurAmt: " + blurAmt, 15, 15); 
} 
void keyPressed() { 
    if (key == 'e') erodeAmt--; 
    if (key == 'E') erodeAmt++; 
    if (key == 'd') dilateAmt--; 
    if (key == 'D') dilateAmt++; 
    if (key == 'b') blurAmt--; 
    if (key == 'B') blurAmt++; 
    //constrain values 
    if (erodeAmt < 0) erodeAmt = 0; 
    if (dilateAmt < 0) dilateAmt = 0; 
    if (blurAmt < 0) blurAmt = 0; 
} 
+0

Хороший вопрос, о OpenCV и обертке, которую вы используете, довольно приятно. Я должен был рекомендовать это с самого начала (но старался держать вещи простыми с точки зрения зависимостей). –

1

Хорошо, что вы выравниваете потоки RGB и глубины. Есть несколько вещей, которые можно было бы улучшить с точки зрения эффективности:

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

mask = loadImage("black640.jpg"); //just a black image 

Кроме того, так как вам не нужно х, у координаты, как вы Переберите пользовательских данных, вы можете использовать один цикл, который должен быть немного быстрее:

for(int i = 0 ; i < numPixels ; i++){ 
    mask.pixels[i] = userMap[i] > 0 ? color(255) : color(0); 
    } 

вместо:

for (int y = 0; y < ySize; y++) {  
    for (int x = 0; x < xSize; x++) {   
     int index = x + y*xSize;  
     if (userMap[index]>0) { 
     mask.pixels[index]=color(255, 255, 255); 
     } 
    } 
    } 

Другой Hacky вещь, которую вы можете сделать, это извлечь из userImage() SimpleOpenNI, вместо userData() и применить фильтр THRESHOLD к ней, что в теории должно дать тот же результат, что и выше.

Например:

int[] userMap = context.userMap(); 
    background(0, 0, 0); 
    mask = loadImage("black640.jpg"); //just a black image 
    int xSize = context.depthWidth(); 
    int ySize = context.depthHeight(); 
    mask.loadPixels(); 
    for (int y = 0; y < ySize; y++) {  
    for (int x = 0; x < xSize; x++) {   
     int index = x + y*xSize;  
     if (userMap[index]>0) { 
     mask.pixels[index]=color(255, 255, 255); 
     } 
    } 
    } 

может быть:

mask = context.userImage(); 
mask.filter(THRESHOLD); 

С точки зрения фильтрации, если вы хотите, чтобы уменьшить силуэт Вам необходимо ERODE и bluring должен дать вам немного этого Photoshop, как оперение.

Обратите внимание, что некоторые filter() вызовов принимают аргументы (как BLUR), но другие не любят ERODE/DILATE морфологических фильтров, но вы можете свернуть свои собственные петли, чтобы иметь дело с этим.

Я также рекомендую иметь какой-то удобный интерфейс для настройки (это может быть причудливый слайдер или простая комбинация клавиш) при игре с фильтрами.

Вот грубая попытка на реорганизованным эскиз с комментариями выше:

import SimpleOpenNI.*; 
SimpleOpenNI context; 
PImage mask; 
int numPixels = 640*480; 

int dilateAmt = 1; 
int erodeAmt = 1; 
int blurAmt = 0; 
void setup() 
{ 
    size(640*2, 480); 
    context = new SimpleOpenNI(this); 

    if (context.isInit() == false) 
    {   
    exit(); 
    return; 
    } 
    context.enableDepth(); 
    context.enableRGB(); 
    context.enableUser(); 
    context.alternativeViewPointDepthToImage(); 
    mask = createImage(640,480,RGB); 
} 

void draw() 
{ 
    frame.setTitle(int(frameRate) + " fps");  
    context.update(); 
    int[] userMap = context.userMap(); 
    background(0, 0, 0); 

    //you don't need to keep reloading the image every single frame since you're updating all the pixels bellow anyway 
// mask = loadImage("black640.jpg"); //just a black image 

// mask.loadPixels(); 

// int xSize = context.depthWidth(); 
// int ySize = context.depthHeight(); 
// for (int y = 0; y < ySize; y++) {  
// for (int x = 0; x < xSize; x++) {   
//  int index = x + y*xSize;  
//  if (userMap[index]>0) { 
//  mask.pixels[index]=color(255, 255, 255); 
//  } 
// } 
// } 

    //a single loop is usually faster than a nested loop and you don't need the x,y coordinates anyway 
    for(int i = 0 ; i < numPixels ; i++){ 
    mask.pixels[i] = userMap[i] > 0 ? color(255) : color(0); 
    } 
    //erode 
    for(int i = 0 ; i < erodeAmt ; i++) mask.filter(ERODE); 
    //dilate 
    for(int i = 0 ; i < dilateAmt; i++) mask.filter(DILATE); 
    //blur 
    mask.filter(BLUR,blurAmt); 

    mask.updatePixels(); 
    //preview the mask after you process it 
    image(mask, 0, 0); 

    PImage rgb = context.rgbImage(); 
    rgb.mask(mask); 
    image(rgb, context.depthWidth() + 10, 0); 

    //print filter values for debugging purposes 
    fill(255); 
    text("erodeAmt: " + erodeAmt + "\tdilateAmt: " + dilateAmt + "\tblurAmt: " + blurAmt,15,15); 
} 
void keyPressed(){ 
    if(key == 'e') erodeAmt--; 
    if(key == 'E') erodeAmt++; 
    if(key == 'd') dilateAmt--; 
    if(key == 'D') dilateAmt++; 
    if(key == 'b') blurAmt--; 
    if(key == 'B') blurAmt++; 
    //constrain values 
    if(erodeAmt < 0) erodeAmt = 0; 
    if(dilateAmt < 0) dilateAmt = 0; 
    if(blurAmt < 0) blurAmt = 0; 
} 

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

Этот эскиз (если он работает) должен позволять вам использовать ключи для управления параметрами фильтра (e/E для уменьшения/увеличения эрозии, d/D для дилатации, b/B для размытия). Надеюсь, вы получите удовлетворительные результаты.

При работе с SimpleOpenNI в целом я советую записать.oni file (проверьте RecorderPlay пример для этого) человека для наиболее распространенного варианта использования. Это позволит вам сэкономить некоторое время в долгосрочной перспективе при тестировании и позволит вам работать удаленно с отсоединенным датчиком. С одной стороны, разрешение на глубину уменьшается на половину записи (но использование булевского флага должно быть безопасным)

Последний и, вероятно, самый важный момент касается качества конечного результата. Полученное изображение не может быть намного лучше, если исходное изображение с трудом работать с самого начала. Данные глубины от исходного датчика Kinect невелики. Датчики Asus чувствуют себя более стабильными, но в большинстве случаев разница незначительна. Если вы собираетесь придерживаться одного из этих датчиков, убедитесь, что у вас есть четкий фон и приличное освещение (без слишком большого прямого теплого света (солнечный свет, лампы накаливания и т. Д.), Так как они могут помешать датчику)

Если вы хотите, чтобы более точный пользовательский разрез и вышеуказанная фильтрация не получили результатов, вы должны перейти к лучшему датчику, подобному KinectV2. Качество глубины намного лучше, а датчик менее восприимчив к прямому теплу. Это может означать, что вы должны использовать Windows, (я вижу, что есть KinectPV2 обертки имеется) или OpenFrameworks (C++ коллекцию библиотек, подобной обработке) с ofxKinectV2