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