I'm running an example from a Kinect library for Processing (http://www.shiffman.net/2010/11/14/kinect-and-processing/) and sometimes get a NullPointerException pointing to this line:
int rawDepth = depth[offset];
The depth array is created in this line:
int[] depth = kinect.getRawDepth();
I'm not exactly sure what a NullPointerException is, and much googling hasn't really helped. It seems odd to me that the code compiles 70% of the time and returns the error unpredictably. Could the hardware itself be affecting it?
Here's the whole example if it helps:
// Daniel Shiffman
// Kinect Point Cloud example
// http://www.shiffman.net
// https://github.com/shiffman/libfreenect/tree/master/wrappers/java/processing
import org.openkinect.*;
import org.openkinect.processing.*;
// Kinect Library object
Kinect kinect;
float a = 0;
// Size of kinect image
int w = 640;
int h = 480;
// We'll use a lookup table so that we don't have to repeat the math over and over
float[] depthLookUp = new float[2048];
void setup() {
size(800,600,P3D);
kinect = new Kinect(this);
kinect.start();
kinect.enableDepth(true);
// We don't need the grayscale image in this example
// so this makes it more efficient
kinect.processDepthImage(false);
// Lookup table for all possible depth values (0 - 2047)
for (int i = 0; i < depthLookUp.length; i++) {
depthLookUp[i] = rawDepthToMeters(i);
}
}
void draw() {
background(0);
fill(255);
textMode(SCREEN);
text("Kinect FR: " + (int)kinect.getDepthFPS() + "\nProcessing FR: " + (int)frameRate,10,16);
// Get the raw depth as array of integers
int[] depth = kinect.getRawDepth();
// We're just going to calculate and draw every 4th pixel (equivalent of 160x120)
int skip = 4;
// Translate and rotate
translate(width/2,height/2,-50);
rotateY(a);
for(int x=0; x<w; x+=skip) {
for(int y=0; y<h; y+=skip) {
int offset = x+y*w;
// Convert kinect data to world xyz coordinate
int rawDepth = depth[offset];
PVector v = depthToWorld(x,y,rawDepth);
stroke(255);
pushMatrix();
// Scale up by 200
float factor = 200;
translate(v.x*factor,v.y*factor,factor-v.z*factor);
// Draw a point
point(0,0);
popMatrix();
}
}
// Rotate
a += 0.015f;
}
// These functions come from: http://graphics.stanford.edu/~mdfisher/Kinect.html
float rawDepthToMeters(int depthValue) {
if (depthValue < 2047) {
return (float)(1.0 / ((double)(depthValue) * -0.0030711016 + 3.3309495161));
}
return 0.0f;
}
PVector depthToWorld(int x, int y, int depthValue) {
final double fx_d = 1.0 / 5.9421434211923247e+02;
final double fy_d = 1.0 / 5.9104053696870778e+02;
final double cx_d = 3.3930780975300314e+02;
final double cy_d = 2.4273913761751615e+02;
PVector result = new PVector();
double depth = depthLookUp[depthValue];//rawDepthToMeters(depthValue);
result.x = (float)((x - cx_d) * depth * fx_d);
result.y = (float)((y - cy_d) * depth * fy_d);
result.z = (float)(depth);
return result;
}
void stop() {
kinect.quit();
super.stop();
}
And here are the errors:
processing.app.debug.RunnerException: NullPointerException
at processing.app.Sketch.placeException(Sketch.java:1543)
at processing.app.debug.Runner.findException(Runner.java:583)
at processing.app.debug.Runner.reportException(Runner.java:558)
at processing.app.debug.Runner.exception(Runner.java:498)
at processing.app.debug.EventThread.exceptionEvent(EventThread.java:367)
at processing.app.debug.EventThread.handleEvent(EventThread.java:255)
at processing.app.debug.EventThread.run(EventThread.java:89)
Exception in thread "Animation Thread" java.lang.NullPointerException
at org.openkinect.processing.Kinect.enableDepth(Kinect.java:70)
at PointCloud.setup(PointCloud.java:48)
at processing.core.PApplet.handleDraw(PApplet.java:1583)
at processing.core.PApplet.run(PApplet.java:1503)
at java.lang.Thread.run(Thread.java:637)
You are getting a NullPointerException since the value of the depth array is null. You can see from the source code of the Kinect class, there is a chance of a null value being returned by the getRawDepth() method. It is likely that there is no image being displayed at the time.
The code can be found at:
https://github.com/shiffman/libfreenect/blob/master/wrappers/java/processing/KinectProcessing/src/org/openkinect/processing/Kinect.java
Your code should check if the depth array is null before trying to process it. For example...
int[] depth = kinect.getRawDepth();
if (depth == null) {
// do something here where you handle there being no image
} else {
// We're just going to calculate and draw every 4th pixel (equivalent of 160x120)
int skip = 4;
// Translate and rotate
translate(width/2,height/2,-50);
rotateY(a);
for(int x=0; x<w; x+=skip) {
for(int y=0; y<h; y+=skip) {
int offset = x+y*w;
// Convert kinect data to world xyz coordinate
int rawDepth = depth[offset];
PVector v = depthToWorld(x,y,rawDepth);
stroke(255);
pushMatrix();
// Scale up by 200
float factor = 200;
translate(v.x*factor,v.y*factor,factor-v.z*factor);
// Draw a point
point(0,0);
popMatrix();
}
}
// Rotate
a += 0.015f;
}
I would suggest using a Java Debugger so that you can see the state of the variables at the time the exception is thrown. Some people also like to use log statements to output the values of the variables at different points in the application.
You can then trace the problem back to a point where one of the values is not populated with a non-null value.
The null pointer is happening when offset > kinect.getRawDepth();
You have a lot of code here, I'm not going to look at it all. Why can you assume that offset is < kinect.getRawDepth()?
Edit:
On second though, #Asaph's comment is probably right.
Null Pointer exception happens when depth[offset] does not exist or has not been allocated. Check when depth[offset] is undefined and that is the cause of the nullpointer exception.
Check when kinect.getRawDepth(); is greater than offset.
Related
(please don't mark this question as not clear, I spent a lot of time posting it ;) )
Okay, I am trying to make a simple 2d java game engine as a learning project, and part of it is rendering a filled polygon as a feature.
I am creating this algorithm my self, and I really can't figure out what I am doing wrong.
My though process is something like so:
Loop through every line, get the number of points in that line, then get the X location of every point in that line,
Then loop through the line again this time checking if the x in the loop is inside one of the lines in the points array, if so, draw it.
Disclaimer: the Polygon class is another type of mesh, and its draw method returns an int array with lines drawn through each vertex.
Disclaimer 2: I've tried other people's solutions but none really helped me and none really explained it properly (which is not the point in a learning project).
The draw methods are called one per frame.
FilledPolygon:
#Override
public int[] draw() {
int[] pixels = new Polygon(verts).draw();
int[] filled = new int[width * height];
for (int y = 0; y < height; y++) {
int count = 0;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
count++;
}
}
int[] points = new int[count];
int current = 0;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
points[current] = x;
current++;
}
}
if (count >= 2) {
int num = count;
if (count % 2 != 0)
num--;
for (int i = 0; i < num; i += 2) {
for (int x = points[i]; x < points[i+1]; x++) {
filled[x + y * width] = 0xffffffff;
}
}
}
}
return filled;
}
The Polygon class simply uses Bresenham's line algorithm and has nothing to do with the problem.
The game class:
#Override
public void load() {
obj = new EngineObject();
obj.addComponent(new MeshRenderer(new FilledPolygon(new int[][] {
{0,0},
{60, 0},
{0, 60},
{80, 50}
})));
((MeshRenderer)(obj.getComponent(MeshRenderer.class))).color = CYAN;
obj.transform.position.Y = 100;
}
The expected result is to get this shape filled up.(it was created using the polygon mesh):
The actual result of using the FilledPolygon mesh:
You code seems to have several problems and I will not focus on that.
Your approach based on drawing the outline then filling the "inside" runs cannot work in the general case because the outlines join at the vertices and intersections, and the alternation outside-edge-inside-edge-outside is broken, in an unrecoverable way (you can't know which segment to fill by just looking at a row).
You'd better use a standard polygon filling algorithm. You will find many descriptions on the Web.
For a simple but somewhat inefficient solution, work as follows:
process all lines between the minimum and maximum ordinates; let Y be the current ordinate;
loop on the edges;
assign every vertex a positive or negative sign if y ≥ Y or y < Y (mind the asymmetry !);
whenever the endpoints of an edge have a different sign, compute the intersection between the edge and the line;
you will get an even number of intersections; sort them horizontally;
draw between every other point.
You can get a more efficient solution by keeping a trace of which edges cross the current line, in a so-called "active list". Check the algorithms known as "scanline fill".
Note that you imply that pixels[] has the same width*height size as filled[]. Based on the mangled output, I would say that they are just not the same.
Otherwise if you just want to fill a scanline (assuming everything is convex), that code is overcomplicated, simply look for the endpoints and loop between them:
public int[] draw() {
int[] pixels = new Polygon(verts).draw();
int[] filled = new int[width * height];
for (int y = 0; y < height; y++) {
int left = -1;
for (int x = 0; x < width; x++) {
if (pixels[x + y * width] == 0xffffffff) {
left = x;
break;
}
}
if (left >= 0) {
int right = left;
for (int x = width - 1; x > left; x--) {
if (pixels[x + y * width] == 0xffffffff) {
right = x;
break;
}
}
for (int x = left; x <= right; x++) {
filled[x + y * width] = 0xffffffff;
}
}
}
return filled;
}
However this kind of approach relies on having the entire polygon in the view, which may not always be the case in real life.
I am trying to use an SVM in Android using OpenCV's Android SDK. Everything seems to work correctly besides the predict() function.
I get the following error:
svm CvException error: (-215) samples.cols == var_count && samples.type() == CV_32F in function virtual float cv::ml::SVMImpl::predict(cv::InputArray, cv::OutputArray, int) const ]
I am rather certain that the dimensions and types are correct, but I have a feeling I am overlooking something simple. Below is the description of my code with the relevant code.
I have 60 training examples with 150 features each. All examples and labels are in a 2D float array of dimension 60x151 called dataset_2Darr. The right-most column has the labels stored as -1, 0, +1 for the three classes.
I first place the 60x150 sub-matrix in the Mat object X.
Then, I place the labels in the 60x1 Mat object Y.
X is float - CV_ 32FC1
Y is int - CV_32SC1
The relevant code is executed in 3 parts of the overall program. Here are the three parts:
Part 1: Global Variables in top of MainActivity.java:
static int M = 60; // Rows - examples
static int N = 150; // Cols - features
public static Mat X; // Data
public static Mat Y; // Labels
// Instantiate SVM object globally
static SVM classifier = SVM.create();
Part 2: Inside OnCreate():
// SVM Stuff:
classifier.setKernel(SVM.LINEAR);
classifier.setType(SVM.C_SVC);
classifier.setGamma(0.5);
classifier.setNu(0.5);
classifier.setC(1);
//classifier.setTermCriteria(criteria);
// Dataset stuff:
Y = new Mat(new Size(1,M),CvType.CV_32SC1); // Integer {-1, 0, +1}
int Y_rows = Y.rows(); // 60
int Y_cols = Y.cols(); // 1
X = new Mat(new Size(N,M),CvType.CV_32FC1); // Float
int X_rows = X.rows(); // 60
int X_cols = X.cols(); // 150
Part 3: Inside method that executes code:
for (int i=0; i < 60; i++) {
for(int j=0; j < 150; j++) {
X.put(i, j, dataset_2Darr[i][j]); // Copy 2D array into mat object
}
}
// Iterate down rows of right-most column
for (int i = 0; i < 60; ++i)
Y.put(i,0, (int)dataset_2Darr[i][150]); // Copy right most column into label array
// Train the model using X and Y
classifier.train(X, Ml.ROW_SAMPLE, Y);
// Create 1x150 feature vector to test:
Mat x = new Mat(new Size(150, 1),CvType.CV_32FC1); // Float
int x_rows = x.rows(); // 1
int x_cols = x.cols(); // 150
// Place dummy values inside matrix x
for (int i = 0; i < 150; i++) {
x.put(0, i, 0.2f);
}
//Mat outMat = new Mat();
//float response = classifier.predict(x, outMat, 0);
float prediction = classifier.predict(x);
I met the same problem recently. The shape and type of test data are fine, but somehow the SVM model cannot work with the test data.
The solution is that you need to make sure the dimension of test data is the same as the training data.
Note that the dimension of [1,2,3] is 1, and the dimension of [[1,2,3]] is 2.
I'm trying to create a basic graph for my application, but I'm having a small issue with it. Presently, what happens is that my graph is generated as I'd hope, but when I go to graph my points inside of it, the points are always "off by 1", specifically, my point that should sit at spot 15 on the graph is displayed at 14. The same is true for all other points in my graph, as they are also displayed 1 lower than they should be. I tried to add 1 to cond, but that didn't seem to change any of the actual points as they're displayed, and simply shifted my labels on the y axis to start higher. Below is the code that I think is responsible, but if more is needed, simply ask and I shall post what's asked.
Note: This is for a class assignment where the purpose is to generate 2d graphics, so I can't use a graphing class specifically, although I know it be a far easier job to do so.
The code is below:
public class GraphicsView extends View {
ArrayList<Point> points = new ArrayList<Point>();
Path xPath = new Path();
Path yPath = new Path();
Path zPath = new Path();
Path drawGrid = new Path();
...
Integer xAxis;
Integer yAxis;
#Override
protected void onDraw(Canvas canvas) {
Integer counter = points.size();
Integer xCord;
super.onDraw(canvas);
yAxis = this.getHeight();
xAxis = this.getWidth();
canvas.drawColor(R.color.Background);
<Setting different paint objects to the right colors here>
xPath.moveTo(20, yAxis-20);
yPath.moveTo(20, yAxis-20);
zPath.moveTo(20, yAxis-20);
drawGrid.moveTo(22, yAxis-20);
drawGrid.lineTo(22, 0);
canvas.drawPath(drawGrid,grid);
drawGrid.moveTo(22, yAxis-20);
drawGrid.lineTo(xAxis, yAxis-22);
canvas.drawPath(drawGrid,grid);
//Left right Axis (X)
for (Integer ctr = counter; ctr > 0; ctr--) {
Integer value = counter- ctr + 1;
canvas.drawText(value.toString(), (xAxis-20)/ctr,yAxis-5, grid);
}
//Up down Axis (Y)
for (Integer ctr = 1; ctr < 22; ctr++) {
Integer value = ctr - 1;
canvas.drawText(value.toString(), 5, scale(yAxis, ctr), grid);
}
for (Point point : points)
{
xCord = ((xAxis -20) / counter);
xPath.lineTo(xCord, scale(yAxis,point.getxCord()));
xPath.addCircle(xCord, scale(yAxis, point.getxCord()), 2, Direction.CW);
yPath.lineTo(xCord, scale(yAxis, point.getyCord()));
yPath.addCircle(xCord, scale(yAxis,point.getyCord()), 2, Direction.CW);
zPath.lineTo(xCord, scale(yAxis, point.getzCord()));
zPath.addCircle(xCord, scale(yAxis, point.getzCord()), 2, Direction.CW);
canvas.drawPath(xPath, xPaint);
canvas.drawPath(yPath, yPaint);
canvas.drawPath(zPath, zPaint);
counter -= 1;
}
}
private Integer scale(Integer Axis, Integer cord) {
Integer point = 0;
point = (Axis - (((Axis-20)/21)) * cord);
return point;
}
}
To reiterate, what I'm looking for is a way to resolve it so that when a value is set to 15, it shows it as being along the 15 on the y axis. At present, it shows it as being beside 14.
Whenever you call point.getyCord() just add a + 1 to it.
point.getyCord() + 1
And can you confirm that when you are setting a y value it is actually going in as the desired value.
Your problem lies here:
for (Integer ctr = 1; ctr < 22; ctr++) {
Integer value = ctr - 1;
canvas.drawText(value.toString(), 5, scale(yAxis, ctr), grid);
Your Y axis starts with label 0, but it puts it at the 1 position, since value = ctr - 1. When you use the scale() function later on, you don't offset it by one.
Two methods to fix this:
Modify scale() to offset for you
private Integer scale(Integer Axis, Integer cord) {
Integer point = 0;
point = (Axis - (((Axis-20)/21)) * (cord + 1));
return point;
}
If you use this method, you should also get rid of the value variable you used when drawing the axis, since you can just use ctr for the string as well.
Offset your y coordinate every time
As Colin Gillespie said, you can just add 1 to point.getCord().
I prefer the first method, since it's cleaner, and you only ever have to offset it once, in that function. With the second method, any time you ever pass a coordinate to scale, you'll have to remember to offset it.
it's my first time asking a question here so I'll try to stay on-topic. I'm trying to randomly generate a background by creating an appropriately-sized ArrayList of Bitmap objects, and drawing them in order. This implementation works fine loading a single Bitmap, by the way; it's just stumbling with a list.
Before I get to the code, I'd like to point out that Ideally I would make a single Bitmap by adding the individual pixels or tiles, and indeed have tried a few variations of that, but they all result in black screens; I'm starting to think it might be a problem with how I draw to the Canvas. Anyways, here's what I have:
First, I generate the random ArrayList, only using 3 colors right now. I'd make it return the list, but it's just a private method inside the thread referencing one of the thread's variables so it doesn't matter much.
private void genMap(Resources res)
{
// Load basic tiles.
Bitmap green = BitmapFactory.decodeResource(res, R.drawable.green);
Bitmap red = BitmapFactory.decodeResource(res, R.drawable.red);
Bitmap blue = BitmapFactory.decodeResource(res, R.drawable.blue);
// All tiles must be the same size.
int tile_width = green.getWidth();
int tile_height = green.getHeight();
int num_x = mCanvasWidth / tile_width;
int num_y = mCanvasHeight / tile_height;
for (int j = 0; j < num_y; j++)
{
for (int i = 0; i < num_x; i++)
{
double r = Math.random();
Bitmap tile;
if (r <= 1/3) {tile = green;}
else if (r <= 2/3) {tile = red;}
else {tile = blue;}
// Create a new Bitmap in order to avoid referencing the old value.
mBackgroundImages.add(Bitmap.createBitmap(tile));
}
}
}
So, that's how the random values are mapped to a pattern. The method is called in the thread's constructor, which is in turn called every time onCreate is called; for now, I'm just clearing the list and making a new random pattern each time:
...
Resources res = context.getResources();
mBackgroundImages = new ArrayList<Bitmap>();
genMap(res);
...
And finally, the draw method; it works fine loading a single Bitmap via BitmapFactory.decodeResources, but shows a black screen when doing this:
private void doDraw(Canvas canvas)
{
/* Draw the bg.
* Remember, Canvas objects accumulate.
* So drawn first = most in the background. */
if (canvas != null)
{
if (mBackgroundImages.size() > 0)
{
int tile_width = mBackgroundImages.get(0).getWidth();
int tile_height = mBackgroundImages.get(0).getHeight();
for (int y = 0; y < mCanvasHeight / tile_height; y++)
{
for(int x = 0; x < mCanvasWidth / tile_width; x++)
{
// Draw the Bitmap at the correct position in the list; Y * XWIDTH + X, at pos X * XWIDTH, Y * YWIDTH.
canvas.drawBitmap(mBackgroundImages.get((x + y * (mCanvasWidth/tile_width))), x * tile_width, y * tile_height, null);
}
}
}
}
}
Any help would be appreciated, thanks.
Have you double-checked the order of precedence of the following line?
((x + y * (mCanvasWidth/tile_width)))
Interesting question though: are you drawing solid colors? I would also see if this is actually making a Bitmap:
mBackgroundImages.add(Bitmap.createBitmap(tile));
You might actually be able to just keep references instead of creating a lot of bitmaps, but that can come later
I am trying to read data from multiple Kinect sensors (3 at the moment) and having issues when there's more than 2 devices.
I'm using Daniel Shiffman's OpenKinect Processing wrapper, slightly modified so it allows to open multiple Device instances. Everything works fine with 2 devices. The problem is when I use 3. One Kinect is connected straight into one of the available two usb ports, and the other two are plugged into a USB 2.0 Hub (that has it's own power adapter).
The devices all initialize succesfully:
org.openkinect.Device#1deeb40 initialized
org.openkinect.Device#2c35e initialized
org.openkinect.Device#1cffeb4 initialized
The problem is when I try to get the depth map from the 3rd device, I get an array filled with zeroes. I've thought it's the device, but if swap devices, it's always the 3rd (last connected device) that presents this behaviour.
Here's my code so far:
package librarytests;
import org.openkinect.Context;
import org.openkinect.processing.Kinect;
import processing.core.PApplet;
import processing.core.PVector;
public class PointCloudxN extends PApplet {
// Kinect Library object
int numKinects;// = 3;
Kinect[] kinects;
int[] colours = {color(192,0,0),color(0,192,0),color(0,0,192),color(192,192,0),color(0,192,192),color(192,0,192)};
// Size of kinect image
int w = 640;
int h = 480;
// We'll use a lookup table so that we don't have to repeat the math over and over
float[] depthLookUp = new float[2048];
// Scale up by 200
float factor = 200;
public void setup() {
size(800,600,P3D);
numKinects = Context.getContext().devices();
kinects = new Kinect[numKinects];
for (int i = 0; i < numKinects; i++) {
kinects[i] = new Kinect(this);
kinects[i].start(i);
kinects[i].enableDepth(true);
kinects[i].processDepthImage(false);
}
// Lookup table for all possible depth values (0 - 2047)
for (int i = 0; i < depthLookUp.length; i++) {
depthLookUp[i] = rawDepthToMeters(i);
}
}
public void draw() {
background(0);
translate(width/2,height/2,-50);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(map(mouseY,0,height,-PI,PI));
int skip = 4;//res
//*
for (int i = 0; i < numKinects; i++) {
Kinect kinect = kinects[i];
int[] depth = kinect.getRawDepth();
//if(frameCount % 60 == 0 && i == 2) println(depth);
if (depth != null) {
// Translate and rotate
for(int x=0; x<w; x+=skip) {
for(int y=0; y<h; y+=skip) {
int offset = x+y*w;
// Convert kinect data to world xyz coordinate
int rawDepth = depth[offset];
PVector v = depthToWorld(x,y,rawDepth);
stroke(colours[i]);
// Draw a point
point(v.x*factor,v.y*factor,factor-v.z*factor);
}
}
}
}
//*/
}
public void stop() {
for (int i = 0; i < numKinects; i++) kinects[i].quit();
super.stop();
}
public static void main(String _args[]) {
PApplet.main(new String[] { librarytests.PointCloudxN.class.getName() });
}
// These functions come from: http://graphics.stanford.edu/~mdfisher/Kinect.html
float rawDepthToMeters(int depthValue) {
if (depthValue < 2047) {
return (float)(1.0 / ((double)(depthValue) * -0.0030711016 + 3.3309495161));
}
return 0.0f;
}
PVector depthToWorld(int x, int y, int depthValue) {
final double fx_d = 1.0 / 5.9421434211923247e+02;
final double fy_d = 1.0 / 5.9104053696870778e+02;
final double cx_d = 3.3930780975300314e+02;
final double cy_d = 2.4273913761751615e+02;
PVector result = new PVector();
double depth = depthLookUp[depthValue];//rawDepthToMeters(depthValue);
result.x = (float)((x - cx_d) * depth * fx_d);
result.y = (float)((y - cy_d) * depth * fy_d);
result.z = (float)(depth);
return result;
}
}
The only major change I've done to Daniel's Kinect class was adding an extra start() method:
public void start(int id) {
context = Context.getContext();
if(context.devices() < 1)
{
System.out.println("No Kinect devices found.");
}
device = context.getDevice(id);
//device.acceleration(this);
device.acceleration(new Acceleration()
{
void Acceleration(){
System.out.println("new Acceleration implementation");
}
public void direction(float x, float y, float z)
{
System.out.printf("Acceleration: %f %f %f\n", x ,y ,z);
}
});
kimg = new RGBImage(p5parent);
dimg = new DepthImage(p5parent);
running = true;
super.start();
}
I've also tried with MaxMSP/Jitter and the jit.freenect external and I get the same behaviour: I can get 2 depth maps, but the 3rd is not updating.
So it seems to be an issue related to the driver, not the wrapper, since the same behaviour is present using 2 different wrappers to libfreenect (Java/Processing and Max), but am clueless why this happens to be honest.
Has anyone had a similar issue (getting depth feeds from 3 devices) using the OpenKinect/libfreenect Driver ? Any ideas on how I can get past this issue ?
The Kinect is extremely demanding on USB - generally, you can only get one Kinect per USB host controller on your motherboard (most PCs and laptops have two). The only solution I've seen is to buy a PCI-E USB controller and plug the third one into it.
Also, you might be lucky if you reduce the bandwidth requirements by disabling the RGB stream on all the Kinects (I'm blithely assuming you aren't using it since it wasn't mentioned)