Microsoft Kinect and background/environmental noise
Asked Answered
M

2

6

I am currently programming with the Microsoft Kinect for Windows SDK 2 on Windows 8.1. Things are going well, and in a home dev environment obviously there is not much noise in the background compared to the 'real world'.

I would like to seek some advice from those with experience in 'real world' applications with the Kinect. How does Kinect (especially v2) fare in a live environment with passers-by, onlookers and unexpected objects in the background? I do expect, in the space from the Kinect sensor to the user there will usually not be interference however - what I am very mindful of right now is the background noise as such.

While I am aware that the Kinect does not track well under direct sunlight (either on the sensor or the user) - are there certain lighting conditions or other external factors I need to factor into the code?

The answer I am looking for is:

  1. What kind of issues can arise in a live environment?
  2. How did you code or work your way around it?
Meseems answered 9/1, 2015 at 1:10 Comment(2)
I didn't get the second question.Yulan
@howaboutNO is the background noise purely a hardware issue? Or is there something that can be done on the code level? Does the SDK not allow for better tracking?Meseems
C
4

I created an application for home use like you have before, and then presented that same application in a public setting. The result was embarrassing for me, because there were many errors that I would never have anticipated within a controlled environment. However that did help me because it led me to add some interesting adjustments to my code, which is centered around human detection only.

  1. Have conditions for checking the validity of a "human".

    When I showed my application in the middle of a presentation floor with many other objects and props, I found that even chairs could be mistaken for people for brief moments, which led to my application switching between the user and an inanimate object, causing it to lose track of the user and lost their progress. To counter this or other false-positive human detections, I added my own additional checks for a human. My most successful method was comparing the proportions of a humans body. I implemented this measured in head units. (head units picture) Below is code of how I did this (SDK version 1.8, C#)

    bool PersonDetected = false;
    double[] humanRatios = { 1.0f, 4.0, 2.33, 3.0 };
    /*Array indexes
    * 0 - Head (shoulder to head)
    * 1 - Leg length (foot to knee to hip)
    * 2 - Width (shoulder to shoulder center to shoulder)
    * 3 - Torso (hips to shoulder)
    */
    
    ....
    
    double[] currentRatios = new double[4];
    double headSize = Distance(skeletons[0].Joints[JointType.ShoulderCenter], skeletons[0].Joints[JointType.Head]);
    
    currentRatios[0] = 1.0f;
    
    currentRatios[1] = (Distance(skeletons[0].Joints[JointType.FootLeft], skeletons[0].Joints[JointType.KneeLeft]) + Distance(skeletons[0].Joints[JointType.KneeLeft], skeletons[0].Joints[JointType.HipLeft])) / headSize;
    
    currentRatios[2] = (Distance(skeletons[0].Joints[JointType.ShoulderLeft], skeletons[0].Joints[JointType.ShoulderCenter]) + Distance(skeletons[0].Joints[JointType.ShoulderCenter], skeletons[0].Joints[JointType.ShoulderRight])) / headSize;
    
    currentRatios[3] = Distance(skeletons[0].Joints[JointType.HipCenter], skeletons[0].Joints[JointType.ShoulderCenter]) / headSize;
    
    int correctProportions = 0;
    
    for (int i = 1; i < currentRatios.Length; i++)
    {
        diff = currentRatios[i] - humanRatios[i];
    
        if (abs(diff) <= MaximumDiff)//I used .2 for my MaximumDiff
            correctProportions++;
    }
    
    if (correctProportions >= 2)
        PersonDetected = true;
    

    Another method I had success with was finding the average of the sum of the joints distance squared from one another. I found that non-human detections had more variable summed distances, whereas humans are more consistent. The average I learned using a single dimensional support vector machine (I found user's summed distances were generally less than 9)

    //in AllFramesReady or SkeletalFrameReady
    Skeleton data;
    
    ...
    
    float lastPosX = 0; // trying to detect false-positives
    float lastPosY = 0;
    float lastPosZ = 0;
    float diff = 0;
    foreach (Joint joint in data.Joints)
    {
        //add the distance squared
        diff += (joint.Position.X - lastPosX) * (joint.Position.X - lastPosX);
        diff += (joint.Position.Y - lastPosY) * (joint.Position.Y - lastPosY);
        diff += (joint.Position.Z - lastPosZ) * (joint.Position.Z - lastPosZ);
        lastPosX = joint.Position.X;
        lastPosY = joint.Position.Y;
        lastPosZ = joint.Position.Z;
    }
    
    if (diff < 9)//this is what my svm learned
        PersonDetected = true;
    
  2. Use player IDs and indexes to remember who is who

    This ties in with the previous issue, where if Kinect switched the two users that it was tracking to others, then my application would crash because of the sudden changes in data. To counter this, I would keep track of both each player's skeletal index and their player ID. To learn more about how I did this, see Kinect user Detection.

  3. Add adjustable parameters to adopt to varying situations

    Where I was presenting, the same tilt angle and other basic kinect parameters (like near-mode) did not work in the new environment. Let the user be able to adjust some of these parameters so they can get the best setup for the job.

  4. Expect people to do stupid things

    The next time I presented, I had adjustable tilt, and you can guess whether someone burned out the Kinect's motor. Anything that can be broken on Kinect, someone will break. Leaving a warning in your documentation will not be sufficient. You should add in cautionary checks on Kinect's hardware to make sure people don't get upset when they break something inadvertently. Here is some code checking whether the user has used the motor more than 20 times in two minutes.

    int motorAdjustments = 0;
    DateTime firstAdjustment;
    
    ... 
    
    //in motor adjustment code
    if (motorAdjustments == 0)
        firstAdjustment = DateTime.Now;
    ++motorAdjustments;
    
    if (motorAdjustments < 20)
    {
        //adjust the tilt
    }
    
    else
    {
        DateTime timeCheck = firstAdjustment;
    
        if (DateTime.Now > timeCheck.AddMinutes(2))
        {
            //reset all variables
            motorAdjustments = 1;
            firstAdjustment = DateTime.Now;
    
            //adjust the tilt
        }
    }
    

    I would note that all of these were issues for me with the first version of Kinect, and I don't know how many of them have been solved in the second version as I sadly haven't gotten my hands on one yet. However I would still implement some of these techniques if not back-up techniques because there will be exceptions, especially in computer vision.

Coontie answered 11/1, 2015 at 3:22 Comment(4)
@FrancisKim No problem! If you want I can post some examples of code for the user distinction, although it will be older SDK versions...Coontie
Yes, any kind of code reference is much appreciated.Meseems
@Francis Check my updates... I didn't think it was necessary to add code for adjusting tilt or any simple tasks.Coontie
Thanks for your answer Outlaw Lemur - I picked yours as the right answer however have awarded Vangos the bounty as I felt it was more relevant to my particular case. If I could have split it I would have...!Meseems
E
5

Outlaw Lemur has descibed in detail most of the issues you may encounter in real-world scenarios.

Using Kinect for Windows version 2, you do not need to adjust the motor, since there is no motor and the sensor has a larger field of view. This will make your life much easier.

I would like to add the following tips and advice:

1) Avoid direct light (physical or internal lighting)

Kinect has an infrared sensor that might be confused. This sensor should not have direct contact with any light sources. You can emulate such an environment at your home/office by playing with an ordinary laser pointer and torches.

2) If you are tracking only one person, select the closest tracked user

If your app only needs one player, that player needs to be a) fully tracked and b) closer to the sensor than the others. It's an easy way to make participants understand who is tracked without making your UI more complex.

    public static Body Default(this IEnumerable<Body> bodies)
    {
        Body result = null;
        double closestBodyDistance = double.MaxValue;

        foreach (var body in bodies)
        {
            if (body.IsTracked)
            {
                var position = body.Joints[JointType.SpineBase].Position;
                var distance = position.Length();

                if (result == null || distance < closestBodyDistance)
                {
                    result = body;
                    closestBodyDistance = distance;
                }
            }
        }

        return result;
    }

3) Use the tracking IDs to distinguish different players

Each player has a TrackingID property. Use that property when players interfere or move at random positions. Do not use that property as an alternative to face recognition though.

    ulong _trackinfID1 = 0;
    ulong _trackingID2 = 0;

    void BodyReader_FrameArrived(object sender, BodyFrameArrivedEventArgs e)
    {
        using (var frame = e.FrameReference.AcquireFrame())
        {
            if (frame != null)
            {
                frame.GetAndRefreshBodyData(_bodies);

                var bodies = _bodies.Where(b => b.IsTracked).ToList();

                if (bodies != null && bodies.Count >= 2 && _trackinfID1 == 0 && _trackingID2 == 0)
                {
                    _trackinfID1 = bodies[0].TrackingId;
                    _trackingID2 = bodies[1].TrackingId;

                    // Alternatively, specidy body1 and body2 according to their distance from the sensor.
                }

                Body first = bodies.Where(b => b.TrackingId == _trackinfID1).FirstOrDefault();
                Body second = bodies.Where(b => b.TrackingId == _trackingID2).FirstOrDefault();

                if (first != null)
                {
                    // Do something...
                }

                if (second != null)
                {
                    // Do something...
                }
            }
        }
    }

4) Display warnings when a player is too far or too close to the sensor.

To achieve higher accuracy, players need to stand at a specific distance: not too far or too close to the sensor. Here's how to check this:

const double MIN_DISTANCE = 1.0; // in meters
const double MAX_DISTANCE = 4.0; // in meters

double distance = body.Joints[JointType.SpineBase].Position.Z; // in meters, too

if (distance > MAX_DISTANCE)
{
    // Prompt the player to move closer.
}
else if (distance < MIN_DISTANCE)
{
    // Prompt the player to move farther.
}
else
{
    // Player is in the right distance.
}

5) Always know when a player entered or left the scene.

Vitruvius provides an easy way to understand when someone entered or left the scene.

Here is the source code and here is how to use it in your app:

    UsersController userReporter = new UsersController();
    userReporter.BodyEntered += UserReporter_BodyEntered;
    userReporter.BodyLeft += UserReporter_BodyLeft;
    userReporter.Start();

    void UserReporter_BodyEntered(object sender, UsersControllerEventArgs e)
    {
        // A new user has entered the scene. Get the ID from e param.
    }

    void UserReporter_BodyLeft(object sender, UsersControllerEventArgs e)
    {
        // A user has left the scene. Get the ID from e param.
    }

6) Have a visual clue of which player is tracked

If there are a lot of people surrounding the player, you may need to show on-screen who is tracked. You can highlight the depth frame bitmap or use Microsoft's Kinect Interactions.

This is an example of removing the background and keeping the player pixels only.

7) Avoid glossy floors

Some floors (bright, glossy) may mirror people and Kinect may confuse some of their joints (for example, Kinect may extend your legs to the reflected body). If you can't avoid glossy floors, use the FloorClipPlane property of your BodyFrame. However, the best solution would be to have a simple carpet where you expect people to stand. A carpet would also act as an indication of the proper distance, so you would provide a better user experience.

Epispastic answered 13/1, 2015 at 13:33 Comment(2)
Wow I didn't know about the glossy floor thing... +1Coontie
Indeed, if the floor is too glossy, Kinect may extend your legs to the reflected body.Epispastic
C
4

I created an application for home use like you have before, and then presented that same application in a public setting. The result was embarrassing for me, because there were many errors that I would never have anticipated within a controlled environment. However that did help me because it led me to add some interesting adjustments to my code, which is centered around human detection only.

  1. Have conditions for checking the validity of a "human".

    When I showed my application in the middle of a presentation floor with many other objects and props, I found that even chairs could be mistaken for people for brief moments, which led to my application switching between the user and an inanimate object, causing it to lose track of the user and lost their progress. To counter this or other false-positive human detections, I added my own additional checks for a human. My most successful method was comparing the proportions of a humans body. I implemented this measured in head units. (head units picture) Below is code of how I did this (SDK version 1.8, C#)

    bool PersonDetected = false;
    double[] humanRatios = { 1.0f, 4.0, 2.33, 3.0 };
    /*Array indexes
    * 0 - Head (shoulder to head)
    * 1 - Leg length (foot to knee to hip)
    * 2 - Width (shoulder to shoulder center to shoulder)
    * 3 - Torso (hips to shoulder)
    */
    
    ....
    
    double[] currentRatios = new double[4];
    double headSize = Distance(skeletons[0].Joints[JointType.ShoulderCenter], skeletons[0].Joints[JointType.Head]);
    
    currentRatios[0] = 1.0f;
    
    currentRatios[1] = (Distance(skeletons[0].Joints[JointType.FootLeft], skeletons[0].Joints[JointType.KneeLeft]) + Distance(skeletons[0].Joints[JointType.KneeLeft], skeletons[0].Joints[JointType.HipLeft])) / headSize;
    
    currentRatios[2] = (Distance(skeletons[0].Joints[JointType.ShoulderLeft], skeletons[0].Joints[JointType.ShoulderCenter]) + Distance(skeletons[0].Joints[JointType.ShoulderCenter], skeletons[0].Joints[JointType.ShoulderRight])) / headSize;
    
    currentRatios[3] = Distance(skeletons[0].Joints[JointType.HipCenter], skeletons[0].Joints[JointType.ShoulderCenter]) / headSize;
    
    int correctProportions = 0;
    
    for (int i = 1; i < currentRatios.Length; i++)
    {
        diff = currentRatios[i] - humanRatios[i];
    
        if (abs(diff) <= MaximumDiff)//I used .2 for my MaximumDiff
            correctProportions++;
    }
    
    if (correctProportions >= 2)
        PersonDetected = true;
    

    Another method I had success with was finding the average of the sum of the joints distance squared from one another. I found that non-human detections had more variable summed distances, whereas humans are more consistent. The average I learned using a single dimensional support vector machine (I found user's summed distances were generally less than 9)

    //in AllFramesReady or SkeletalFrameReady
    Skeleton data;
    
    ...
    
    float lastPosX = 0; // trying to detect false-positives
    float lastPosY = 0;
    float lastPosZ = 0;
    float diff = 0;
    foreach (Joint joint in data.Joints)
    {
        //add the distance squared
        diff += (joint.Position.X - lastPosX) * (joint.Position.X - lastPosX);
        diff += (joint.Position.Y - lastPosY) * (joint.Position.Y - lastPosY);
        diff += (joint.Position.Z - lastPosZ) * (joint.Position.Z - lastPosZ);
        lastPosX = joint.Position.X;
        lastPosY = joint.Position.Y;
        lastPosZ = joint.Position.Z;
    }
    
    if (diff < 9)//this is what my svm learned
        PersonDetected = true;
    
  2. Use player IDs and indexes to remember who is who

    This ties in with the previous issue, where if Kinect switched the two users that it was tracking to others, then my application would crash because of the sudden changes in data. To counter this, I would keep track of both each player's skeletal index and their player ID. To learn more about how I did this, see Kinect user Detection.

  3. Add adjustable parameters to adopt to varying situations

    Where I was presenting, the same tilt angle and other basic kinect parameters (like near-mode) did not work in the new environment. Let the user be able to adjust some of these parameters so they can get the best setup for the job.

  4. Expect people to do stupid things

    The next time I presented, I had adjustable tilt, and you can guess whether someone burned out the Kinect's motor. Anything that can be broken on Kinect, someone will break. Leaving a warning in your documentation will not be sufficient. You should add in cautionary checks on Kinect's hardware to make sure people don't get upset when they break something inadvertently. Here is some code checking whether the user has used the motor more than 20 times in two minutes.

    int motorAdjustments = 0;
    DateTime firstAdjustment;
    
    ... 
    
    //in motor adjustment code
    if (motorAdjustments == 0)
        firstAdjustment = DateTime.Now;
    ++motorAdjustments;
    
    if (motorAdjustments < 20)
    {
        //adjust the tilt
    }
    
    else
    {
        DateTime timeCheck = firstAdjustment;
    
        if (DateTime.Now > timeCheck.AddMinutes(2))
        {
            //reset all variables
            motorAdjustments = 1;
            firstAdjustment = DateTime.Now;
    
            //adjust the tilt
        }
    }
    

    I would note that all of these were issues for me with the first version of Kinect, and I don't know how many of them have been solved in the second version as I sadly haven't gotten my hands on one yet. However I would still implement some of these techniques if not back-up techniques because there will be exceptions, especially in computer vision.

Coontie answered 11/1, 2015 at 3:22 Comment(4)
@FrancisKim No problem! If you want I can post some examples of code for the user distinction, although it will be older SDK versions...Coontie
Yes, any kind of code reference is much appreciated.Meseems
@Francis Check my updates... I didn't think it was necessary to add code for adjusting tilt or any simple tasks.Coontie
Thanks for your answer Outlaw Lemur - I picked yours as the right answer however have awarded Vangos the bounty as I felt it was more relevant to my particular case. If I could have split it I would have...!Meseems

© 2022 - 2024 — McMap. All rights reserved.